Stack series covers and wrap series into rows

This commit is contained in:
SirBlob committed 2026-11-01 21:27:33 -05:00
1 parent bad7b7d3b3
commit 30656cb580
2 files changed
+93 -17

No files matched your search

@@ -5,11 +5,8 @@
let { series }: { series: BookSeriesGroup } = $props();
const collapsedCount = 8;
let expanded = $state(false);
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 },
@@ -18,7 +15,7 @@
]);
</script>
<section class="series">
<section class="series" class:expanded>
<header class="series-header">
<a class="series-name" href={series.url} target="_blank" rel="noopener noreferrer">
<Icon icon="mdi:bookshelf" width="16" />
@@ -39,26 +36,52 @@
{/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 expanded}
<div class="book-grid">
{#each series.books as book (book.goodreadsId)}
<BookCard {book} showShelf />
{/each}
</div>
{:else}
<div class="cover-stack">
{#each series.books as book, index (book.goodreadsId)}
<a
class="stacked-cover {book.shelf}"
style:z-index={total - index}
href={book.url}
target="_blank"
rel="noopener noreferrer"
title="{book.series?.position ?? ''} {book.title}"
>
{#if book.cover}
<img src={book.cover} alt={book.title} loading="lazy" />
{:else}
<span class="cover-fallback"><Icon icon="mdi:book-outline" width="20" /></span>
{/if}
</a>
{/each}
</div>
{/if}
<button class="toggle" onclick={() => (expanded = !expanded)}>
<Icon icon={expanded ? 'mdi:chevron-up' : 'mdi:chevron-down'} width="16" />
{expanded ? 'Collapse series' : `Expand series (${total})`}
</button>
</section>
<style>
.series {
padding: 0.75rem;
flex: 1 1 auto;
min-width: min(100%, 280px);
max-width: 100%;
border: 1px solid color-mix(in srgb, var(--foreground) 15%, transparent);
}
.series.expanded {
flex-basis: 100%;
}
.series-header {
display: flex;
flex-wrap: wrap;
@@ -121,6 +144,58 @@
background: var(--magenta);
}
.cover-stack {
display: flex;
padding: 0.5rem 0 0.25rem;
overflow-x: auto;
}
.stacked-cover {
position: relative;
flex-shrink: 0;
width: 84px;
height: 126px;
border-bottom: 3px solid var(--muted);
box-shadow: 4px 0 10px color-mix(in srgb, black 45%, transparent);
transition: transform 0.15s ease;
}
.stacked-cover + .stacked-cover {
margin-left: -52px;
}
.stacked-cover:hover {
z-index: 1000 !important;
transform: translateY(-8px);
}
.stacked-cover.read {
border-bottom-color: var(--green);
}
.stacked-cover.reading {
border-bottom-color: var(--blue);
}
.stacked-cover.want-to-read {
border-bottom-color: var(--magenta);
}
.stacked-cover img,
.cover-fallback {
display: block;
width: 100%;
height: 100%;
object-fit: cover;
}
.cover-fallback {
display: grid;
place-items: center;
background: color-mix(in srgb, var(--foreground) 8%, var(--background));
color: var(--muted);
}
.book-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
@@ -112,7 +112,8 @@
.series-list {
display: flex;
flex-direction: column;
flex-wrap: wrap;
align-items: flex-start;
gap: 0.75rem;
}