refactor(consolidate): ArrowLink tone + FormSuccess-Komponente
A: ArrowLink um tone-Prop (himmel/wald/stein/white) — PostOverview + CalendarCompact inline Arrow-Links → ArrowLink. B: 3× identische Formular-Success-Boxen → internal/FormSuccess.svelte (rounded-lg). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -9,6 +9,7 @@ const meta = {
|
||||
args: { href: '#', label: 'Mehr erfahren' },
|
||||
argTypes: {
|
||||
size: { control: 'select', options: ['sm', 'md'] },
|
||||
tone: { control: 'select', options: ['himmel', 'wald', 'stein', 'white'] },
|
||||
external: { control: 'boolean' },
|
||||
},
|
||||
} satisfies Meta<typeof ArrowLink>;
|
||||
@@ -19,3 +20,5 @@ type Story = StoryObj<typeof meta>;
|
||||
export const Medium: Story = { args: { size: 'md', label: 'Zum Adressbuch' } };
|
||||
export const Klein: Story = { args: { size: 'sm', label: 'cloud.windwiderstand.de' } };
|
||||
export const Extern: Story = { args: { size: 'sm', label: 'Beitreten', external: true } };
|
||||
export const Wald: Story = { args: { tone: 'wald', label: 'Alle Termine' } };
|
||||
export const Stein: Story = { args: { tone: 'stein', size: 'sm', label: 'Alle Beiträge' } };
|
||||
|
||||
@@ -5,17 +5,20 @@
|
||||
* `size`: sm (kompakt, z.B. in Cards) · md (Sektionsköpfe).
|
||||
*/
|
||||
type Size = "sm" | "md";
|
||||
type Tone = "himmel" | "wald" | "stein" | "white";
|
||||
|
||||
let {
|
||||
href,
|
||||
label,
|
||||
external = false,
|
||||
size = "md",
|
||||
tone = "himmel",
|
||||
}: {
|
||||
href: string;
|
||||
label: string;
|
||||
external?: boolean;
|
||||
size?: Size;
|
||||
tone?: Tone;
|
||||
} = $props();
|
||||
|
||||
const textClass: Record<Size, string> = {
|
||||
@@ -26,13 +29,19 @@
|
||||
sm: "size-3.5",
|
||||
md: "size-4",
|
||||
};
|
||||
const toneClass: Record<Tone, string> = {
|
||||
himmel: "text-himmel-500 hover:text-himmel-600",
|
||||
wald: "text-wald-700 hover:text-wald-800",
|
||||
stein: "text-stein-600 hover:text-stein-900",
|
||||
white: "text-white hover:text-white/80",
|
||||
};
|
||||
</script>
|
||||
|
||||
<a
|
||||
{href}
|
||||
target={external ? "_blank" : undefined}
|
||||
rel={external ? "noopener noreferrer" : undefined}
|
||||
class="inline-flex items-center gap-1.5 font-semibold text-himmel-500 no-underline hover:text-himmel-600 {textClass[size]}"
|
||||
class="inline-flex items-center gap-1.5 font-semibold no-underline {toneClass[tone]} {textClass[size]}"
|
||||
>
|
||||
{label}
|
||||
<svg class={arrowClass[size]} viewBox="0 0 24 24" fill="none" aria-hidden="true"><path d="M5 12h14M13 6l6 6-6 6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { getBlockLayoutClasses } from "$lib/block-layout";
|
||||
import ArrowLink from "$lib/components/ArrowLink.svelte";
|
||||
import type { CalendarBlockData, CalendarItemData } from "$lib/block-types";
|
||||
import { t as tStatic, T } from "$lib/translations";
|
||||
import type { Translations } from "$lib/translations";
|
||||
@@ -83,13 +84,9 @@
|
||||
<div class="mb-2 flex items-baseline justify-between gap-4">
|
||||
<h3 class="mb-0">{block.title ?? t(T.calendar_next_events)}</h3>
|
||||
{#if linkHref()}
|
||||
<a
|
||||
href={linkHref()}
|
||||
class="inline-flex shrink-0 items-center gap-1 text-sm font-medium text-wald-700 no-underline hover:text-wald-800 hover:underline"
|
||||
>
|
||||
{t(T.calendar_all_upcoming)}
|
||||
<span aria-hidden="true">→</span>
|
||||
</a>
|
||||
<div class="shrink-0">
|
||||
<ArrowLink href={linkHref() ?? "#"} label={t(T.calendar_all_upcoming)} tone="wald" size="sm" />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
import "$lib/iconify-offline";
|
||||
import { getBlockLayoutClasses } from "$lib/block-layout";
|
||||
import SectionHeader from "$lib/components/SectionHeader.svelte";
|
||||
import ArrowLink from "$lib/components/ArrowLink.svelte";
|
||||
import type { PostOverviewBlockData } from "$lib/block-types";
|
||||
import type { PostEntry } from "$lib/cms";
|
||||
import { postHref } from "$lib/blog-utils";
|
||||
@@ -153,11 +154,8 @@
|
||||
{/if}
|
||||
|
||||
{#if posts.length > 0}
|
||||
<div class="mt-3 text-xs px-2">
|
||||
<a href="/posts/" class="inline-flex items-center gap-1 text-stein-700 hover:text-stein-900 hover:underline !no-underline">
|
||||
{t(T.post_overview_all)}
|
||||
<span aria-hidden="true">→</span>
|
||||
</a>
|
||||
<div class="mt-3 px-2">
|
||||
<ArrowLink href="/posts/" label={t(T.post_overview_all)} tone="stein" size="sm" />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
import Textarea from "$lib/ui/Textarea.svelte";
|
||||
import Checkbox from "$lib/ui/Checkbox.svelte";
|
||||
import { useTranslate, T } from "$lib/translations";
|
||||
import FormSuccess from "./FormSuccess.svelte";
|
||||
import {
|
||||
LIMITS,
|
||||
validateContact,
|
||||
@@ -195,10 +196,7 @@
|
||||
</script>
|
||||
|
||||
{#if formState === "success"}
|
||||
<div class="rounded-sm border border-wald-300 bg-wald-50 px-4 py-5 text-center shadow-sm">
|
||||
<p class="text-base font-semibold text-wald-700">{t(T.contact_success_title)}</p>
|
||||
<p class="mt-1 text-sm text-stein-600">{t(T.contact_success_body)}</p>
|
||||
</div>
|
||||
<FormSuccess title={t(T.contact_success_title)} body={t(T.contact_success_body)} />
|
||||
{:else}
|
||||
<form
|
||||
onsubmit={handleSubmit}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
<script lang="ts">
|
||||
/** Erfolgs-Box nach Formular-Absenden (Kontakt/Newsletter/Mitmachen). */
|
||||
let { title, body }: { title: string; body: string } = $props();
|
||||
</script>
|
||||
|
||||
<div class="rounded-lg border border-wald-300 bg-wald-50 px-4 py-5 text-center shadow-sm">
|
||||
<p class="text-base font-semibold text-wald-700">{title}</p>
|
||||
<p class="mt-1 text-sm text-stein-600">{body}</p>
|
||||
</div>
|
||||
@@ -6,6 +6,7 @@
|
||||
import Textarea from "$lib/ui/Textarea.svelte";
|
||||
import Checkbox from "$lib/ui/Checkbox.svelte";
|
||||
import { useTranslate, T } from "$lib/translations";
|
||||
import FormSuccess from "./FormSuccess.svelte";
|
||||
import {
|
||||
FILE_ACCEPT,
|
||||
LIMITS,
|
||||
@@ -225,10 +226,7 @@
|
||||
</script>
|
||||
|
||||
{#if formState === "success"}
|
||||
<div class="rounded-sm border border-wald-300 bg-wald-50 px-4 py-5 text-center shadow-sm">
|
||||
<p class="text-base font-semibold text-wald-700">{t(T.mitmachen_success_title)}</p>
|
||||
<p class="mt-1 text-sm text-stein-600">{t(T.mitmachen_success_body)}</p>
|
||||
</div>
|
||||
<FormSuccess title={t(T.mitmachen_success_title)} body={t(T.mitmachen_success_body)} />
|
||||
{:else}
|
||||
<form
|
||||
onsubmit={handleSubmit}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
import TextInput from "$lib/ui/TextInput.svelte";
|
||||
import Checkbox from "$lib/ui/Checkbox.svelte";
|
||||
import { useTranslate, T } from "$lib/translations";
|
||||
import FormSuccess from "./FormSuccess.svelte";
|
||||
import {
|
||||
validateNewsletter,
|
||||
type NewsletterErrors,
|
||||
@@ -180,10 +181,7 @@
|
||||
</script>
|
||||
|
||||
{#if formState === "success"}
|
||||
<div class="rounded-sm border border-wald-300 bg-wald-50 px-4 py-5 text-center shadow-sm">
|
||||
<p class="text-base font-semibold text-wald-700">{t(T.newsletter_success_title)}</p>
|
||||
<p class="mt-1 text-sm text-stein-600">{t(T.newsletter_success_body)}</p>
|
||||
</div>
|
||||
<FormSuccess title={t(T.newsletter_success_title)} body={t(T.newsletter_success_body)} />
|
||||
{:else}
|
||||
<form
|
||||
onsubmit={handleSubmit}
|
||||
|
||||
Reference in New Issue
Block a user