Stack series covers and wrap series into rows
This commit is contained in:
1 parent
bad7b7d3b3
commit
30656cb580
2 files changed
+89
-13
No files matched your search
@@ -5,11 +5,8 @@
|
|||||||
|
|
||||||
let { series }: { series: BookSeriesGroup } = $props();
|
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 total = $derived(series.books.length);
|
||||||
const shelves = $derived([
|
const shelves = $derived([
|
||||||
{ id: 'read', label: 'read', count: series.counts.read },
|
{ id: 'read', label: 'read', count: series.counts.read },
|
||||||
@@ -18,7 +15,7 @@
|
|||||||
]);
|
]);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<section class="series">
|
<section class="series" class:expanded>
|
||||||
<header class="series-header">
|
<header class="series-header">
|
||||||
<a class="series-name" href={series.url} target="_blank" rel="noopener noreferrer">
|
<a class="series-name" href={series.url} target="_blank" rel="noopener noreferrer">
|
||||||
<Icon icon="mdi:bookshelf" width="16" />
|
<Icon icon="mdi:bookshelf" width="16" />
|
||||||
@@ -39,26 +36,52 @@
|
|||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{#if expanded}
|
||||||
<div class="book-grid">
|
<div class="book-grid">
|
||||||
{#each shownBooks as book (book.goodreadsId)}
|
{#each series.books as book (book.goodreadsId)}
|
||||||
<BookCard {book} showShelf />
|
<BookCard {book} showShelf />
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
{:else}
|
||||||
{#if total > collapsedCount}
|
<div class="cover-stack">
|
||||||
<button class="toggle" onclick={() => (showAll = !showAll)}>
|
{#each series.books as book, index (book.goodreadsId)}
|
||||||
<Icon icon={showAll ? 'mdi:chevron-up' : 'mdi:chevron-down'} width="16" />
|
<a
|
||||||
{showAll ? 'Show less' : `Show all ${total}`}
|
class="stacked-cover {book.shelf}"
|
||||||
</button>
|
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}
|
{/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>
|
</section>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.series {
|
.series {
|
||||||
padding: 0.75rem;
|
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);
|
border: 1px solid color-mix(in srgb, var(--foreground) 15%, transparent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.series.expanded {
|
||||||
|
flex-basis: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.series-header {
|
.series-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
@@ -121,6 +144,58 @@
|
|||||||
background: var(--magenta);
|
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 {
|
.book-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
|
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
|
||||||
|
|||||||
@@ -112,7 +112,8 @@
|
|||||||
|
|
||||||
.series-list {
|
.series-list {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-wrap: wrap;
|
||||||
|
align-items: flex-start;
|
||||||
gap: 0.75rem;
|
gap: 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user