forked from cms/tibi-svelte-starter
✨ feat: enhance accessibility with skip to main content button and improve navigation handling
🔧 fix: update navigation href resolution to include localized paths 🆕 feat: add new FeatureIcon component for feature boxes 🎨 style: improve styling for prose elements in richtext blocks 🛠️ refactor: streamline medialib image loading and caching logic 📦 chore: update mock data handling to support new medialib entries 🔄 chore: synchronize i18n initialization and locale management 📝 docs: update video tour descriptions to reflect recent changes
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
<script lang="ts">
|
||||
let { icon = "lightning", className = "" }: { icon?: ContentFeatureBoxEntry["icon"]; className?: string } = $props()
|
||||
</script>
|
||||
|
||||
{#if icon === "palette"}
|
||||
<svg
|
||||
class={className}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
d="M12 2C6.5 2 2 6.5 2 12s4.5 10 10 10c.93 0 1.5-.67 1.5-1.5 0-.39-.15-.74-.39-1.04-.23-.29-.38-.63-.38-1.01 0-.83.67-1.5 1.5-1.5H16c3.31 0 6-2.69 6-6 0-5.17-4.49-9-10-9z"
|
||||
></path>
|
||||
<circle cx="7.5" cy="11.5" r="1.5"></circle>
|
||||
<circle cx="10.5" cy="7.5" r="1.5"></circle>
|
||||
<circle cx="14.5" cy="7.5" r="1.5"></circle>
|
||||
<circle cx="17.5" cy="11.5" r="1.5"></circle>
|
||||
</svg>
|
||||
{:else if icon === "database"}
|
||||
<svg
|
||||
class={className}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path d="M12 22v-5"></path>
|
||||
<path d="M9 8V2"></path>
|
||||
<path d="M15 8V2"></path>
|
||||
<path d="M18 8v5a6 6 0 0 1-12 0V8h12z"></path>
|
||||
</svg>
|
||||
{:else if icon === "globe"}
|
||||
<svg
|
||||
class={className}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<circle cx="12" cy="12" r="10"></circle>
|
||||
<path d="M2 12h20"></path>
|
||||
<path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"></path>
|
||||
</svg>
|
||||
{:else if icon === "monitor"}
|
||||
<svg
|
||||
class={className}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<rect x="2" y="3" width="20" height="14" rx="2"></rect>
|
||||
<path d="M8 21h8"></path>
|
||||
<path d="M12 17v4"></path>
|
||||
</svg>
|
||||
{:else if icon === "flask"}
|
||||
<svg
|
||||
class={className}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path d="M9 2h6"></path>
|
||||
<path
|
||||
d="M10 2v7.527a2 2 0 0 1-.211.896L4.72 20.578A1 1 0 0 0 5.598 22h12.804a1 1 0 0 0 .878-1.422l-5.069-10.155A2 2 0 0 1 14 9.527V2"
|
||||
></path>
|
||||
</svg>
|
||||
{:else}
|
||||
<svg
|
||||
class={className}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"></path>
|
||||
</svg>
|
||||
{/if}
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { reveal } from "../lib/actions/reveal"
|
||||
import FeatureIcon from "./FeatureIcon.svelte"
|
||||
|
||||
let { block }: { block: ContentBlockEntry } = $props()
|
||||
|
||||
@@ -41,10 +42,22 @@
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if block.text}
|
||||
<div class="prose max-w-none" use:reveal={{ delay: 200 }}>
|
||||
{@html block.text}
|
||||
{#if block.featureBoxes?.length}
|
||||
<div class="grid gap-8 sm:grid-cols-2 lg:grid-cols-3" use:reveal={{ delay: 200 }}>
|
||||
{#each block.featureBoxes as item}
|
||||
<article class="feature-card">
|
||||
<div class="feature-icon">
|
||||
<FeatureIcon icon={item.icon} className="w-10 h-10" />
|
||||
</div>
|
||||
{#if item.title}
|
||||
<h3>{item.title}</h3>
|
||||
{/if}
|
||||
{#if item.text}
|
||||
<p>{item.text}</p>
|
||||
{/if}
|
||||
</article>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
@@ -1,10 +1,16 @@
|
||||
<script lang="ts">
|
||||
import { reveal } from "../lib/actions/reveal"
|
||||
import { spaLink } from "../lib/navigation"
|
||||
import MedialibImage from "../widgets/MedialibImage.svelte"
|
||||
|
||||
let { block }: { block: ContentBlockEntry } = $props()
|
||||
|
||||
const hasImage = $derived(block.heroImage?.externalUrl || block.heroImage?.image)
|
||||
const resolvedHeroImage = $derived(
|
||||
block.heroImage?._lookup?.image ||
|
||||
(block._lookup?.["heroImage.image"] as MedialibEntry | null | undefined) ||
|
||||
null
|
||||
)
|
||||
const hasImage = $derived(!!resolvedHeroImage?.file?.src)
|
||||
const isAnchorLink = $derived(block.callToAction?.buttonLink?.startsWith("#"))
|
||||
</script>
|
||||
|
||||
@@ -17,9 +23,12 @@
|
||||
<!-- Background image -->
|
||||
{#if hasImage}
|
||||
<div class="absolute inset-0 z-0">
|
||||
{#if block.heroImage?.externalUrl}
|
||||
<img src={block.heroImage.externalUrl} alt="" class="w-full h-full object-cover" loading="lazy" />
|
||||
{/if}
|
||||
<MedialibImage
|
||||
id={block.heroImage?.image || resolvedHeroImage?.id || resolvedHeroImage?._id || ""}
|
||||
entry={resolvedHeroImage}
|
||||
noPlaceholder
|
||||
style="width:100%;height:100%;object-fit:cover;"
|
||||
/>
|
||||
<div class="absolute inset-0 bg-linear-to-b from-brand-950/80 via-brand-900/70 to-brand-950/90"></div>
|
||||
</div>
|
||||
{:else}
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
<script lang="ts">
|
||||
import { reveal } from "../lib/actions/reveal"
|
||||
import MedialibImage from "../widgets/MedialibImage.svelte"
|
||||
|
||||
let { block }: { block: ContentBlockEntry } = $props()
|
||||
|
||||
const resolvedImage = $derived(block._lookup?.image || null)
|
||||
|
||||
const paddingTop = $derived(
|
||||
block.padding?.top === "lg"
|
||||
? "pt-20"
|
||||
@@ -22,10 +25,10 @@
|
||||
: "pb-4"
|
||||
)
|
||||
|
||||
const hasImage = $derived(block.externalImageUrl || block.image)
|
||||
const hasImage = $derived(block.image || resolvedImage)
|
||||
const imageOnRight = $derived(block.imagePosition === "right")
|
||||
const imageOnLeft = $derived(block.imagePosition === "left")
|
||||
const showImage = $derived(hasImage && (imageOnRight || imageOnLeft))
|
||||
const showImage = $derived(hasImage && !!resolvedImage?.file?.src && (imageOnRight || imageOnLeft))
|
||||
</script>
|
||||
|
||||
<section data-block="richtext" class="richtext-section {paddingTop} {paddingBottom}" id={block.anchorId || undefined}>
|
||||
@@ -54,12 +57,14 @@
|
||||
</div>
|
||||
<div class:order-1={imageOnLeft} class="relative">
|
||||
<div class="rounded-2xl overflow-hidden shadow-xl shadow-brand-900/10">
|
||||
<img
|
||||
src={block.externalImageUrl || ""}
|
||||
alt={block.headline || ""}
|
||||
class="w-full h-auto object-cover aspect-4/3"
|
||||
loading="lazy"
|
||||
/>
|
||||
{#if block.image || resolvedImage}
|
||||
<MedialibImage
|
||||
id={block.image || resolvedImage?.id || resolvedImage?._id || ""}
|
||||
entry={resolvedImage}
|
||||
noPlaceholder
|
||||
style="width:100%;height:auto;aspect-ratio:4/3;object-fit:cover;"
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
<!-- Decorative gradient behind image -->
|
||||
<div
|
||||
|
||||
Reference in New Issue
Block a user