feat(carousel): 2 cols tablet, 3 cols desktop; equal-height cards
- PostOverviewBlock carousel: md:2 cols, lg:3 cols via snap-scroll - PostCard: add class prop for external styling - Carousel cards: h-full for equal height Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -22,12 +22,14 @@
|
|||||||
href,
|
href,
|
||||||
tagBase = "/posts/tag",
|
tagBase = "/posts/tag",
|
||||||
commentCount = null,
|
commentCount = null,
|
||||||
|
class: className = "",
|
||||||
}: {
|
}: {
|
||||||
post: PostWithImage;
|
post: PostWithImage;
|
||||||
href: string;
|
href: string;
|
||||||
tagBase?: string;
|
tagBase?: string;
|
||||||
/** Approved comment count. `null` = unknown/loading, hide badge. `0` = also hide. */
|
/** Approved comment count. `null` = unknown/loading, hide badge. `0` = also hide. */
|
||||||
commentCount?: number | null;
|
commentCount?: number | null;
|
||||||
|
class?: string;
|
||||||
} = $props();
|
} = $props();
|
||||||
|
|
||||||
const rawImg = $derived(post._rawImageUrl);
|
const rawImg = $derived(post._rawImageUrl);
|
||||||
@@ -52,7 +54,7 @@
|
|||||||
<Card
|
<Card
|
||||||
{href}
|
{href}
|
||||||
layout="post"
|
layout="post"
|
||||||
class={isPastEvent ? "opacity-70 grayscale" : ""}
|
class="{isPastEvent ? 'opacity-70 grayscale' : ''} {className}"
|
||||||
>
|
>
|
||||||
<div class="relative aspect-3/2 w-full overflow-hidden bg-stein-100">
|
<div class="relative aspect-3/2 w-full overflow-hidden bg-stein-100">
|
||||||
{#if rawImg}
|
{#if rawImg}
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { marked } from "$lib/markdown-safe";
|
import { marked } from "$lib/markdown-safe";
|
||||||
import PostCard from "../PostCard.svelte";
|
import PostCard from "../PostCard.svelte";
|
||||||
import Carousel from "$lib/components/Carousel.svelte";
|
import Icon from "@iconify/svelte";
|
||||||
|
import "$lib/iconify-offline";
|
||||||
import { getBlockLayoutClasses } from "$lib/block-layout";
|
import { getBlockLayoutClasses } from "$lib/block-layout";
|
||||||
import type { PostOverviewBlockData } from "$lib/block-types";
|
import type { PostOverviewBlockData } from "$lib/block-types";
|
||||||
import type { PostEntry } from "$lib/cms";
|
import type { PostEntry } from "$lib/cms";
|
||||||
@@ -21,6 +22,13 @@
|
|||||||
const posts = $derived((block.postsResolved ?? []) as (PostEntry & { _resolvedImageUrl?: string })[]);
|
const posts = $derived((block.postsResolved ?? []) as (PostEntry & { _resolvedImageUrl?: string })[]);
|
||||||
const design = $derived(block.design || "cards");
|
const design = $derived(block.design || "cards");
|
||||||
const tagBase = "/posts/tag";
|
const tagBase = "/posts/tag";
|
||||||
|
|
||||||
|
let scroller: HTMLDivElement | undefined = $state();
|
||||||
|
function scrollByPage(dir: 1 | -1) {
|
||||||
|
if (!scroller) return;
|
||||||
|
scroller.scrollBy({ left: scroller.clientWidth * dir * 0.9, behavior: "smooth" });
|
||||||
|
}
|
||||||
|
|
||||||
// Hide the entire block (headline, intro, footer link) when there are no
|
// Hide the entire block (headline, intro, footer link) when there are no
|
||||||
// posts and no intro text — otherwise an empty section with just a heading
|
// posts and no intro text — otherwise an empty section with just a heading
|
||||||
// sits on the page.
|
// sits on the page.
|
||||||
@@ -74,16 +82,42 @@
|
|||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{:else if design === "carousel" && posts.length > 0}
|
{:else if design === "carousel" && posts.length > 0}
|
||||||
<Carousel count={posts.length} ariaLabel={block.headline ?? "Beiträge"}>
|
<div class="relative">
|
||||||
{#snippet item(i)}
|
<div
|
||||||
|
bind:this={scroller}
|
||||||
|
class="flex gap-4 overflow-x-auto scroll-smooth snap-x snap-mandatory pb-2 [scrollbar-width:thin]"
|
||||||
|
>
|
||||||
|
{#each posts as post}
|
||||||
|
<div class="shrink-0 snap-start flex flex-col w-full md:w-[calc((100%-1rem)/2)] lg:w-[calc((100%-2rem)/3)]">
|
||||||
<PostCard
|
<PostCard
|
||||||
post={posts[i]}
|
post={post}
|
||||||
href={postHref(posts[i])}
|
href={postHref(post)}
|
||||||
tagBase={tagBase}
|
tagBase={tagBase}
|
||||||
commentCount={commentCounts[postSlugForComments(posts[i])] ?? null}
|
commentCount={commentCounts[postSlugForComments(post)] ?? null}
|
||||||
|
class="h-full"
|
||||||
/>
|
/>
|
||||||
{/snippet}
|
</div>
|
||||||
</Carousel>
|
{/each}
|
||||||
|
</div>
|
||||||
|
{#if posts.length > 1}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onclick={() => scrollByPage(-1)}
|
||||||
|
aria-label="Zurück"
|
||||||
|
class="hidden md:flex absolute -left-3 top-1/2 -translate-y-1/2 -translate-x-1/2 size-7 items-center justify-center rounded-full bg-white/90 border border-stein-200 shadow-sm hover:bg-white hover:border-wald-700 transition"
|
||||||
|
>
|
||||||
|
<Icon icon="mdi:chevron-left" class="size-4" aria-hidden="true" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onclick={() => scrollByPage(1)}
|
||||||
|
aria-label="Weiter"
|
||||||
|
class="hidden md:flex absolute -right-3 top-1/2 -translate-y-1/2 translate-x-1/2 size-7 items-center justify-center rounded-full bg-white/90 border border-stein-200 shadow-sm hover:bg-white hover:border-wald-700 transition"
|
||||||
|
>
|
||||||
|
<Icon icon="mdi:chevron-right" class="size-4" aria-hidden="true" />
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if posts.length > 0}
|
{#if posts.length > 0}
|
||||||
|
|||||||
Reference in New Issue
Block a user