153 lines
3.2 KiB
Svelte
153 lines
3.2 KiB
Svelte
<script lang="ts">
|
|
import Icon from '@iconify/svelte';
|
|
import type { BookSeriesGroup } from '#lib/books/series';
|
|
import BookCard from './BookCard.svelte';
|
|
|
|
let { series }: { series: BookSeriesGroup } = $props();
|
|
|
|
const collapsedCount = 8;
|
|
|
|
let showAll = $state(false);
|
|
|
|
const shownBooks = $derived(showAll ? series.books : series.books.slice(0, collapsedCount));
|
|
const total = $derived(series.books.length);
|
|
const shelves = $derived([
|
|
{ id: 'read', label: 'read', count: series.counts.read },
|
|
{ id: 'reading', label: 'reading', count: series.counts.reading },
|
|
{ id: 'want-to-read', label: 'want to read', count: series.counts['want-to-read'] }
|
|
]);
|
|
</script>
|
|
|
|
<section class="series">
|
|
<header class="series-header">
|
|
<a class="series-name" href={series.url} target="_blank" rel="noopener noreferrer">
|
|
<Icon icon="mdi:bookshelf" width="16" />
|
|
{series.name}
|
|
</a>
|
|
<span class="series-counts">
|
|
{#each shelves.filter((shelf) => shelf.count > 0) as shelf (shelf.id)}
|
|
<span class="count {shelf.id}">{shelf.count} {shelf.label}</span>
|
|
{/each}
|
|
</span>
|
|
</header>
|
|
|
|
<div class="progress" role="img" aria-label="{series.counts.read} of {total} read">
|
|
{#each shelves as shelf (shelf.id)}
|
|
{#if shelf.count}
|
|
<span class="segment {shelf.id}" style:flex-grow={shelf.count}></span>
|
|
{/if}
|
|
{/each}
|
|
</div>
|
|
|
|
<div class="book-grid">
|
|
{#each shownBooks as book (book.goodreadsId)}
|
|
<BookCard {book} showShelf />
|
|
{/each}
|
|
</div>
|
|
|
|
{#if total > collapsedCount}
|
|
<button class="toggle" onclick={() => (showAll = !showAll)}>
|
|
<Icon icon={showAll ? 'mdi:chevron-up' : 'mdi:chevron-down'} width="16" />
|
|
{showAll ? 'Show less' : `Show all ${total}`}
|
|
</button>
|
|
{/if}
|
|
</section>
|
|
|
|
<style>
|
|
.series {
|
|
padding: 0.75rem;
|
|
border: 1px solid color-mix(in srgb, var(--foreground) 15%, transparent);
|
|
}
|
|
|
|
.series-header {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
align-items: baseline;
|
|
justify-content: space-between;
|
|
gap: 0.4rem 1rem;
|
|
}
|
|
|
|
.series-name {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 0.4rem;
|
|
color: var(--accent);
|
|
font-weight: 700;
|
|
}
|
|
|
|
.series-name:hover {
|
|
text-decoration: underline;
|
|
}
|
|
|
|
.series-counts {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 0.75rem;
|
|
font-size: 0.78rem;
|
|
}
|
|
|
|
.count.read {
|
|
color: var(--green);
|
|
}
|
|
|
|
.count.reading {
|
|
color: var(--blue);
|
|
}
|
|
|
|
.count.want-to-read {
|
|
color: var(--magenta);
|
|
}
|
|
|
|
.progress {
|
|
display: flex;
|
|
gap: 2px;
|
|
height: 4px;
|
|
margin: 0.5rem 0 0.85rem;
|
|
}
|
|
|
|
.segment {
|
|
flex-basis: 0;
|
|
}
|
|
|
|
.segment.read {
|
|
background: var(--green);
|
|
}
|
|
|
|
.segment.reading {
|
|
background: var(--blue);
|
|
}
|
|
|
|
.segment.want-to-read {
|
|
background: var(--magenta);
|
|
}
|
|
|
|
.book-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
|
|
gap: 0.9rem 0.75rem;
|
|
}
|
|
|
|
.toggle {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 0.3rem;
|
|
margin-top: 0.75rem;
|
|
padding: 0.3rem 0.7rem;
|
|
background: none;
|
|
border: 1px solid color-mix(in srgb, var(--foreground) 30%, transparent);
|
|
color: var(--foreground);
|
|
font-size: 0.85rem;
|
|
}
|
|
|
|
.toggle:hover {
|
|
border-color: var(--accent);
|
|
color: var(--accent);
|
|
}
|
|
|
|
@container (max-width: 520px) {
|
|
.book-grid {
|
|
grid-template-columns: repeat(auto-fill, minmax(96px, 1fr));
|
|
}
|
|
}
|
|
</style>
|