Skip to content

Commit

Permalink
feat: Style the announcement cards and use mansory layout
Browse files Browse the repository at this point in the history
  • Loading branch information
Ushie committed Sep 13, 2024
1 parent e34504c commit 566a777
Show file tree
Hide file tree
Showing 3 changed files with 241 additions and 48 deletions.
140 changes: 140 additions & 0 deletions src/lib/components/Masonry.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
<script>
import { onMount, onDestroy, getContext, setContext, tick } from 'svelte';
export let stretchFirst = false,
gridGap = '0.5em',
colWidth = 'minmax(Min(20em, 100%), 1fr)',
items = []; // pass in data if it's dynamically updated
let grids = [],
masonryElement;
export let reset;
$: if (reset) {
masonryElement = masonryElement;
}
export const refreshLayout = async () => {
// console.log("REFRESHING LAYOUT")
grids.forEach(async (grid) => {
/* get the post relayout number of columns */
let ncol = getComputedStyle(grid._el).gridTemplateColumns.split(' ').length;
grid.items.forEach((c) => {
let new_h = c.getBoundingClientRect().height;
if (new_h !== +c.dataset.h) {
c.dataset.h = new_h;
grid.mod++;
}
});
/* if the number of columns has changed */
if (grid.ncol !== ncol || grid.mod) {
/* update number of columns */
grid.ncol = ncol;
/* revert to initial positioning, no margin */
grid.items.forEach((c) => c.style.removeProperty('margin-top'));
/* if we have more than one column */
if (grid.ncol > 1) {
grid.items.slice(ncol).forEach((c, i) => {
let prev_fin =
grid.items[i].getBoundingClientRect().bottom /* bottom edge of item above */,
curr_ini = c.getBoundingClientRect().top; /* top edge of current item */
c.style.marginTop = `${prev_fin + grid.gap - curr_ini}px`;
});
}
grid.mod = 0;
}
});
};
const calcGrid = async (_masonryArr) => {
await tick();
if (_masonryArr.length && getComputedStyle(_masonryArr[0]).gridTemplateRows !== 'masonry') {
grids = _masonryArr.map((grid) => {
return {
_el: grid,
gap: parseFloat(getComputedStyle(grid).gridRowGap),
items: [...grid.childNodes].filter(
(c) => c.nodeType === 1 && +getComputedStyle(c).gridColumnEnd !== -1
),
ncol: 0,
mod: 0
};
});
refreshLayout(); /* initial load */
}
};
let _window;
onMount(() => {
_window = window;
_window.addEventListener('resize', refreshLayout, false); /* on resize */
});
onDestroy(() => {
if (_window) {
_window.removeEventListener('resize', refreshLayout, false); /* on resize */
}
});
$: if (masonryElement) {
calcGrid([masonryElement]);
}
$: if (items) {
// update if items are changed
masonryElement = masonryElement; // refresh masonryElement
}
</script>

<!--
An almost direct copy and paste of: https://css-tricks.com/a-lightweight-masonry-solution
Usage:
- stretchFirst stretches the first item across the top
<Masonry stretchFirst={true} >
{#each data as o}
<div class="_card _padding">
Here's some stuff {o.name}
<header>
<h3>{o.name}</h3>
</header>
<section>
<p>{o.text}</p>
</section>
</div>
{/each}
</Masonry>
-->

<div
bind:this={masonryElement}
class={`__grid--masonry ${stretchFirst ? '__stretch-first' : ''}`}
style={`--grid-gap: ${gridGap}; --col-width: ${colWidth};`}
>
<slot></slot>
</div>

<!--
$w: var(--col-width); // minmax(Min(20em, 100%), 1fr);
$s: var(--grid-gap); // .5em;
-->

<style>
:global(.__grid--masonry) {
display: grid;
grid-template-columns: repeat(auto-fit, var(--col-width));
grid-template-rows: masonry;
justify-content: center;
grid-gap: var(--grid-gap);
padding: var(--grid-gap);
}
:global(.__grid--masonry > *) {
align-self: start;
}
:global(.__grid--masonry.__stretch-first > *:first-child) {
grid-column: 1/ -1;
}
</style>
114 changes: 88 additions & 26 deletions src/routes/announcements/AnnouncementCard.svelte
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
<script lang="ts">
import moment from 'moment';
import Svg from '$lib/components/Svg.svelte';
import Dialogue from '$lib/components/Dialogue.svelte';
import type { Announcement } from '$lib/types';
export let announcement: Announcement;
import { onMount } from 'svelte';
import NewChip from './NewChip.svelte';
export let id: number;
export let title: string;
Expand Down Expand Up @@ -33,42 +37,100 @@
});
</script>

<div class="announcement-card">
<h2>
{title}
{#if read !== undefined && read}
<NewChip />
{/if}
</h2>
<h3>
by
<a class="author" href="http://github.com/{author}" target="_blank" rel="noopener noreferrer">
{author}
</a>,
<span class="date">{moment(created_at).fromNow()}</span>
</h3>
{#if $$slots.channel}
<div class="channel"><slot name="channel" /></div>
<div class="announcement-card" on:click={showAnnouncement}>
{#if announcement.attachmentUrls}
<img src={announcement.attachmentUrls[0]} />
{/if}
<button class="read-more" on:click={showAnnouncement}>Read more</button>
<div class="content">
<div class="text">
<h3>{announcement.title}</h3>
<div class="description">
{announcement.content}
</div>
<span class="date">{moment(announcement.createdAt).fromNow()}</span>
</div>
<div class="action">
<button on:click={showAnnouncement} aria-label="Settings">
<Svg viewBoxHeight={24} svgHeight={20}>
<path
d="M 19.1 12.9 C 19.1 12.6 19.2 12.3 19.2 12 C 19.2 11.7 19.2 11.4 19.1 11.1 L 21.1 9.5 C 21.3 9.4 21.3 9.1 21.2 8.9 L 19.3 5.6 C 19.2 5.4 18.9 5.3 18.7 5.4 L 16.3 6.4 C 15.8 6 15.3 5.7 14.7 5.5 L 14.3 3 C 14.3 2.8 14.1 2.6 13.8 2.6 L 10 2.6 C 9.8 2.6 9.6 2.8 9.5 3 L 9.2 5.3 C 8.7 5.6 8.1 5.9 7.6 6.3 L 5.2 5.3 C 5 5.2 4.8 5.3 4.6 5.5 L 2.7 8.9 C 2.6 9.1 2.7 9.3 2.9 9.5 L 4.9 11.1 C 4.9 11.4 4.8 11.7 4.8 12 C 4.8 12.3 4.8 12.6 4.9 12.9 L 2.9 14.5 C 2.7 14.6 2.7 14.9 2.8 15.1 L 4.7 18.4 C 4.8 18.6 5.1 18.7 5.3 18.6 L 7.7 17.6 C 8.2 18 8.7 18.3 9.3 18.5 L 9.7 21 C 9.8 21.2 9.9 21.4 10.2 21.4 L 14 21.4 C 14.2 21.4 14.4 21.2 14.5 21 L 14.9 18.5 C 15.5 18.3 16 17.9 16.5 17.6 L 18.9 18.6 C 19.1 18.7 19.4 18.6 19.5 18.4 L 21.4 15.1 C 21.5 14.9 21.5 14.6 21.3 14.5 L 19.1 12.9 Z M 12 15.6 C 10 15.6 8.4 14 8.4 12 C 8.4 10 10 8.4 12 8.4 C 14 8.4 15.6 10 15.6 12 C 15.6 14 14 15.6 12 15.6 Z"
/>
</Svg>
</button>
</div>
</div>
</div>
<!-- {#if $$slots.channel}
<div class="channel"><slot name="channel" /></div>
{/if} -->
<!-- <h4>
by
<a class="author" href="http://github.com/{author}" target="_blank" rel="noopener noreferrer">
{author}
</a>,
</h4> -->
<!-- <button class="read-more" on:click={() => (showContent = true)}>Read more</button> -->

<Dialogue bind:modalOpen={showContent} fullscreen>
<svelte:fragment slot="icon">
<img src="https://github.com/{author}.png" alt="Author avatar" class="author-avatar" />
<img
src="https://github.com/{announcement.author}.png"
alt="Author avatar"
class="author-avatar"
/>
</svelte:fragment>
<svelte:fragment slot="title">{title}</svelte:fragment>
<svelte:fragment slot="title">{announcement.title}</svelte:fragment>
<svelte:fragment slot="description">
<slot name="content" />
</svelte:fragment>
</Dialogue>

<style>
<style lang="scss">
.announcement-card {
border: 1px solid var(--primary);
border-radius: 0.6rem;
padding: 1rem;
display: flex;
flex-direction: column;
height: fit-content;
color: var(--text-four);
background-color: var(--surface-seven);
border: 1px solid var(--border);
border-radius: 12px;
button:hover path {
fill: var(--secondary);
}
.content {
display: flex;
justify-content: space-between;
padding: 12px 16px;
.text {
display: flex;
flex-direction: column;
gap: 12px;
}
}
img {
width: 100%;
border-radius: 12px 12px 0px 0px;
}
button {
background-color: transparent;
fill: var(--primary);
border: none;
cursor: pointer;
display: flex;
justify-content: center;
align-items: center;
}
&:hover {
background-color: var(--surface-four);
}
.description {
display: -webkit-box;
line-clamp: 4;
-webkit-line-clamp: 4;
-webkit-box-orient: vertical;
overflow: hidden;
}
}
</style>
35 changes: 13 additions & 22 deletions src/routes/announcements/CardsList.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import AnnouncementCard from './AnnouncementCard.svelte';
import { queries } from '$data/api';
import ChannelChip from './ChannelChip.svelte';
import Gallery from '$lib/components/Gallery.svelte';
import Masonry from '$lib/components/Masonry.svelte';
let searchParams: Readable<URLSearchParams>;
Expand All @@ -31,29 +31,20 @@
{/each}
</div>

{#each channel ? data.announcements.filter((a) => a.channel === channel) : data.announcements as ann (ann.id)}
<AnnouncementCard
id={ann.id}
title={ann.title}
author={ann.author}
created_at={ann.createdAt.value}
>
<svelte:fragment slot="channel">
<ChannelChip channel={ann.channel} />
</svelte:fragment>
<svelte:fragment slot="content">
<div class="content-container">
<!-- @html since some if not most announcements contain HTML -->
{@html ann.content}
</div>
{#if ann.attachmentUrls && Array.isArray(ann.attachmentUrls) && ann.attachmentUrls.length > 0}
<Gallery imageUrls={ann.attachmentUrls} />
{/if}
</svelte:fragment>
</AnnouncementCard>
{/each}
<!-- <div class="announcements"> -->
<Masonry items={data.announcements}>
{#each channel ? data.announcements.filter((a) => a.channel === channel) : data.announcements as announcement (announcement.id)}
<AnnouncementCard {announcement} />
{/each}
</Masonry>
<!-- </div> -->
</Query>
</div>

<style>
.announcements {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 1rem;
}
</style>

0 comments on commit 566a777

Please sign in to comment.