refactor(badge): eine Badge-Komponente überall, alle 7 CMS-Farben
components/Badge.svelte = einzige Badge-Quelle. colorClasses deckt jetzt alle CMS-badge.color-Werte ab (green/blue/amber/orange/purple/teal/slate) — vorher fielen orange/purple/teal/slate auf grau (Regionen-Badges sahen gleich aus). Ad-hoc Badge-Spans (ContactCard, AdressbuchBlock) durch <Badge> ersetzt. Verwaiste ui/Badge.svelte (0 Nutzungen) + Atoms/Badge-Story entfernt. Molecules/Badge-Story zeigt jetzt alle Farben. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -6,9 +6,21 @@
|
||||
title: 'Molecules/Badge',
|
||||
component: Badge,
|
||||
tags: ['autodocs'],
|
||||
argTypes: {
|
||||
color: {
|
||||
control: 'select',
|
||||
options: ['green', 'blue', 'amber', 'orange', 'purple', 'teal', 'slate', undefined],
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<Story name="Standard">Neu</Story>
|
||||
<Story name="Wald" args={{ color: 'wald' }}>Aktiv</Story>
|
||||
<Story name="Fire" args={{ color: 'fire' }}>Dringend</Story>
|
||||
<!-- Die eine Badge. `color` deckt alle CMS-badge.color-Werte ab. -->
|
||||
<Story name="Default">Neu</Story>
|
||||
<Story name="Green" args={{ color: 'green' }}>Gründer</Story>
|
||||
<Story name="Blue" args={{ color: 'blue' }}>Mitglied</Story>
|
||||
<Story name="Amber" args={{ color: 'amber' }}>Anfrage läuft</Story>
|
||||
<Story name="Orange" args={{ color: 'orange' }}>Südwestthüringen</Story>
|
||||
<Story name="Purple" args={{ color: 'purple' }}>Ostthüringen</Story>
|
||||
<Story name="Teal" args={{ color: 'teal' }}>Mittelthüringen</Story>
|
||||
<Story name="Slate" args={{ color: 'slate' }}>Nordthüringen</Story>
|
||||
|
||||
@@ -7,16 +7,26 @@
|
||||
class: className = "",
|
||||
}: { children?: Snippet; color?: string; class?: string } = $props();
|
||||
|
||||
// Die einzige Badge-Farbquelle. Deckt alle CMS-badge.color-Werte ab
|
||||
// (green/blue/amber/orange/purple/teal/slate) + Fallback. Marken-Töne
|
||||
// (wald/himmel) wo möglich, sonst Tailwind-Palette für klar unterscheidbare
|
||||
// Regionen-Badges. KEINE Eigenimplementierungen woanders — immer <Badge>.
|
||||
function colorClasses(c?: string): string {
|
||||
switch (c) {
|
||||
case "green": return "bg-wald-50 text-wald-700 border border-wald-200";
|
||||
case "blue": return "bg-himmel-50 text-himmel-700 border border-himmel-200";
|
||||
case "amber": return "bg-erde-50 text-erde-700 border border-erde-200";
|
||||
default: return "bg-stein-100 text-stein-600 border border-stein-200";
|
||||
case "green": return "bg-wald-50 text-wald-700 border-wald-200";
|
||||
case "blue": return "bg-himmel-50 text-himmel-700 border-himmel-200";
|
||||
case "amber": return "bg-amber-50 text-amber-700 border-amber-200";
|
||||
case "orange": return "bg-orange-50 text-orange-700 border-orange-200";
|
||||
case "purple": return "bg-purple-50 text-purple-700 border-purple-200";
|
||||
case "teal": return "bg-teal-50 text-teal-700 border-teal-200";
|
||||
case "slate": return "bg-slate-100 text-slate-700 border-slate-300";
|
||||
default: return "bg-stein-100 text-stein-600 border-stein-200";
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<span class="shrink-0 rounded-full px-1.5 py-0 text-[0.65rem] font-medium {colorClasses(color)} {className}">
|
||||
<span
|
||||
class="shrink-0 whitespace-nowrap rounded-full border px-1.5 py-0 text-[0.65rem] font-medium {colorClasses(color)} {className}"
|
||||
>
|
||||
{@render children?.()}
|
||||
</span>
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import "$lib/iconify-offline";
|
||||
import type { ContactEntry } from "$lib/block-types";
|
||||
import { downloadVCard } from "$lib/vcard";
|
||||
import Badge from "$lib/components/Badge.svelte";
|
||||
|
||||
let { contact }: { contact: ContactEntry } = $props();
|
||||
|
||||
@@ -23,14 +24,10 @@
|
||||
{#if contact.role || orgNames.length > 0}
|
||||
<div class="flex flex-wrap gap-1">
|
||||
{#if contact.role}
|
||||
<span class="shrink-0 rounded-full border border-stein-200 bg-stein-100 px-1.5 py-0 text-[0.65rem] font-medium text-stein-600">
|
||||
{contact.role}
|
||||
</span>
|
||||
<Badge>{contact.role}</Badge>
|
||||
{/if}
|
||||
{#each orgNames as org}
|
||||
<span class="shrink-0 rounded-full border border-wald-200 bg-wald-50 px-1.5 py-0 text-[0.65rem] font-medium text-wald-700">
|
||||
{org}
|
||||
</span>
|
||||
<Badge color="green">{org}</Badge>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
import { downloadVCard, downloadVCardAll, buildVCardString } from "$lib/vcard";
|
||||
import QrButton from "$lib/components/QrButton.svelte";
|
||||
import Button from "$lib/ui/Button.svelte";
|
||||
import Badge from "$lib/components/Badge.svelte";
|
||||
|
||||
let { block }: { block: AdressbuchBlockData } = $props();
|
||||
|
||||
@@ -241,14 +242,10 @@
|
||||
{#if contact.role || (contact.organisations ?? []).some((o) => typeof o === "object" && o.name)}
|
||||
<div class="mt-0.5 flex flex-nowrap gap-1 overflow-hidden">
|
||||
{#if contact.role}
|
||||
<span class="shrink-0 whitespace-nowrap rounded-full border border-stein-200 bg-stein-100 px-1.5 py-0 text-[0.65rem] font-medium text-stein-600">
|
||||
{contact.role}
|
||||
</span>
|
||||
<Badge>{contact.role}</Badge>
|
||||
{/if}
|
||||
{#each (contact.organisations ?? []).filter((o): o is { _slug?: string; name?: string } => typeof o === "object" && !!o.name) as org}
|
||||
<span class="shrink-0 whitespace-nowrap rounded-full border border-wald-200 bg-wald-50 px-1.5 py-0 text-[0.65rem] font-medium text-wald-700">
|
||||
{org.name}
|
||||
</span>
|
||||
<Badge color="green">{org.name}</Badge>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
import type { Meta, StoryObj } from '@storybook/sveltekit';
|
||||
import Badge from './Badge.svelte';
|
||||
|
||||
const meta = {
|
||||
title: 'Atoms/Badge',
|
||||
component: Badge,
|
||||
tags: ['autodocs'],
|
||||
args: { label: 'Neu' },
|
||||
} satisfies Meta<typeof Badge>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {};
|
||||
export const AlsLink: Story = { args: { label: 'Mehr', href: '#' } };
|
||||
export const Aktiv: Story = { args: { label: 'Aktiv', active: true } };
|
||||
@@ -1,57 +0,0 @@
|
||||
<script lang="ts">
|
||||
type Variant = 'default' | 'primary' | 'accent' | 'inactive' | 'date';
|
||||
|
||||
let {
|
||||
label = '',
|
||||
variant = 'default',
|
||||
href = null as string | null,
|
||||
active = false,
|
||||
onclick = null as (() => void) | null,
|
||||
} = $props();
|
||||
|
||||
const baseClass =
|
||||
'inline-flex shrink-0 whitespace-nowrap rounded-full py-1 px-2.5 text-[0.6875rem] font-medium transition-colors ring-1 no-underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-1';
|
||||
|
||||
const variantClasses = $derived(
|
||||
{
|
||||
default: 'bg-stein-0 text-stein-800 ring-stein-200 hover:bg-stein-50',
|
||||
primary: 'bg-wald-500 text-white ring-wald-600/30 hover:bg-wald-600',
|
||||
accent: 'bg-himmel-400 text-white ring-himmel-500/30 hover:bg-himmel-500',
|
||||
inactive: 'bg-stein-100 text-stein-500 ring-stein-200',
|
||||
date: 'bg-wald-600 text-white ring-wald-700/40',
|
||||
}[variant]
|
||||
);
|
||||
|
||||
const shadowClass = $derived(
|
||||
href || onclick
|
||||
? variant === 'date'
|
||||
? 'shadow-[0_0_6px_rgba(0,0,0,0.25)]'
|
||||
: 'shadow-[0_2px_6px_rgba(0,0,0,0.12)]'
|
||||
: ''
|
||||
);
|
||||
|
||||
const classes = $derived(
|
||||
`${baseClass} ${variantClasses} ${shadowClass} ${active ? 'pointer-events-none ring-2' : ''}`
|
||||
);
|
||||
</script>
|
||||
|
||||
{#if href}
|
||||
<a
|
||||
href={href}
|
||||
class={classes}
|
||||
aria-current={active ? 'true' : undefined}
|
||||
>
|
||||
{label}
|
||||
</a>
|
||||
{:else if onclick}
|
||||
<button
|
||||
type="button"
|
||||
class={classes}
|
||||
aria-current={active ? 'true' : undefined}
|
||||
onclick={onclick}
|
||||
>
|
||||
{label}
|
||||
</button>
|
||||
{:else}
|
||||
<span class={classes}>{label}</span>
|
||||
{/if}
|
||||
@@ -12,5 +12,4 @@ export { default as Toggle } from './Toggle.svelte';
|
||||
export { default as Checkbox } from './Checkbox.svelte';
|
||||
export { default as Radio } from './Radio.svelte';
|
||||
export { default as Slider } from './Slider.svelte';
|
||||
export { default as Badge } from './Badge.svelte';
|
||||
export { default as Tooltip } from './Tooltip.svelte';
|
||||
|
||||
Reference in New Issue
Block a user