From 04ed0c50e26b98cdd6ebe666016e107b7b122e30 Mon Sep 17 00:00:00 2001 From: Anders Olsson Date: Sun, 7 Jun 2026 14:08:08 +0200 Subject: [PATCH] feat(web): indigo brand token + status tokens + Badge success/warning variants (#49) Co-Authored-By: Claude Opus 4.8 (1M context) --- web/src/components/ui/badge.stories.tsx | 8 +++++ web/src/components/ui/badge.tsx | 2 ++ web/src/index.css | 32 +++++++++++++++++--- web/src/objects/visibility-badge.stories.tsx | 11 ++++--- web/src/objects/visibility-badge.tsx | 12 +++----- web/src/search/highlight.tsx | 2 +- 6 files changed, 50 insertions(+), 17 deletions(-) diff --git a/web/src/components/ui/badge.stories.tsx b/web/src/components/ui/badge.stories.tsx index e8676a8..cc2974a 100644 --- a/web/src/components/ui/badge.stories.tsx +++ b/web/src/components/ui/badge.stories.tsx @@ -27,6 +27,14 @@ export const Destructive: Story = { args: { variant: 'destructive', children: 'Error' }, } +export const Success: Story = { + args: { variant: 'success', children: 'Public' }, +} + +export const Warning: Story = { + args: { variant: 'warning', children: 'Internal' }, +} + export const Outline: Story = { args: { variant: 'outline', children: 'Draft' }, } diff --git a/web/src/components/ui/badge.tsx b/web/src/components/ui/badge.tsx index b20959d..c4bdbcd 100644 --- a/web/src/components/ui/badge.tsx +++ b/web/src/components/ui/badge.tsx @@ -14,6 +14,8 @@ const badgeVariants = cva( "bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80", destructive: "bg-destructive/10 text-destructive focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 [a]:hover:bg-destructive/20", + success: "bg-success/10 text-success [a]:hover:bg-success/20", + warning: "bg-warning/10 text-warning [a]:hover:bg-warning/20", outline: "border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground", ghost: diff --git a/web/src/index.css b/web/src/index.css index 2f5c0c9..3e240c9 100644 --- a/web/src/index.css +++ b/web/src/index.css @@ -18,6 +18,12 @@ --color-accent: var(--accent); --color-accent-foreground: var(--accent-foreground); --color-destructive: var(--destructive); + --color-success: var(--success); + --color-success-foreground: var(--success-foreground); + --color-warning: var(--warning); + --color-warning-foreground: var(--warning-foreground); + --color-highlight: var(--highlight); + --color-highlight-foreground: var(--highlight-foreground); --color-border: var(--border); --color-input: var(--input); --color-ring: var(--ring); @@ -35,7 +41,7 @@ --card-foreground: oklch(0.145 0 0); --popover: oklch(1 0 0); --popover-foreground: oklch(0.145 0 0); - --primary: oklch(0.205 0 0); + --primary: oklch(0.511 0.262 276.966); --primary-foreground: oklch(0.985 0 0); --secondary: oklch(0.97 0 0); --secondary-foreground: oklch(0.205 0 0); @@ -44,9 +50,15 @@ --accent: oklch(0.97 0 0); --accent-foreground: oklch(0.205 0 0); --destructive: oklch(0.577 0.245 27.325); + --success: oklch(0.627 0.194 149.214); + --success-foreground: oklch(0.985 0 0); + --warning: oklch(0.666 0.179 58.318); + --warning-foreground: oklch(0.985 0 0); + --highlight: oklch(0.905 0.182 98.111); + --highlight-foreground: oklch(0.205 0 0); --border: oklch(0.922 0 0); --input: oklch(0.922 0 0); - --ring: oklch(0.708 0 0); + --ring: oklch(0.511 0.262 276.966); --radius: 0.625rem; } @@ -57,7 +69,7 @@ --card-foreground: oklch(0.985 0 0); --popover: oklch(0.205 0 0); --popover-foreground: oklch(0.985 0 0); - --primary: oklch(0.985 0 0); + --primary: oklch(0.673 0.182 276.935); --primary-foreground: oklch(0.205 0 0); --secondary: oklch(0.269 0 0); --secondary-foreground: oklch(0.985 0 0); @@ -66,9 +78,15 @@ --accent: oklch(0.269 0 0); --accent-foreground: oklch(0.985 0 0); --destructive: oklch(0.704 0.191 22.216); + --success: oklch(0.723 0.192 149.579); + --success-foreground: oklch(0.205 0 0); + --warning: oklch(0.769 0.188 70.08); + --warning-foreground: oklch(0.205 0 0); + --highlight: oklch(0.852 0.199 91.936); + --highlight-foreground: oklch(0.205 0 0); --border: oklch(1 0 0 / 10%); --input: oklch(1 0 0 / 15%); - --ring: oklch(0.556 0 0); + --ring: oklch(0.673 0.182 276.935); } @layer base { @@ -80,3 +98,9 @@ @apply bg-background text-foreground font-sans; } } + +@layer components { + .label-caption { + @apply text-xs font-medium uppercase tracking-wide text-muted-foreground; + } +} diff --git a/web/src/objects/visibility-badge.stories.tsx b/web/src/objects/visibility-badge.stories.tsx index 53606c7..882d751 100644 --- a/web/src/objects/visibility-badge.stories.tsx +++ b/web/src/objects/visibility-badge.stories.tsx @@ -26,16 +26,17 @@ export const Draft: Story = { args: { visibility: 'draft' }, } -// The single project-wide CssCheck. VisibilityBadge applies `bg-green-100` for -// the `public` visibility (see STYLES in visibility-badge.tsx). A concrete -// resolved background colour proves the shared preview actually loaded the app's -// Tailwind stylesheet — an unstyled badge would report a transparent background. +// The single project-wide CssCheck. VisibilityBadge applies the `success` +// variant for the `public` visibility (`bg-success/10`, see VARIANT in +// visibility-badge.tsx). A concrete resolved background colour proves the shared +// preview actually loaded the app's Tailwind stylesheet — an unstyled badge +// would report a transparent background. export const CssCheck: Story = { args: { visibility: 'public' }, play: async ({ canvas }) => { const badge = canvas.getByText('Public') await expect(getComputedStyle(badge).backgroundColor).toBe( - 'oklch(0.962 0.044 156.743)', + 'oklab(0.627 -0.166662 0.0992956 / 0.1)', ) }, } diff --git a/web/src/objects/visibility-badge.tsx b/web/src/objects/visibility-badge.tsx index 40c398f..3afaa20 100644 --- a/web/src/objects/visibility-badge.tsx +++ b/web/src/objects/visibility-badge.tsx @@ -5,18 +5,16 @@ import { Badge } from "@/components/ui/badge"; type Visibility = components["schemas"]["Visibility"]; -const STYLES: Record = { - draft: "bg-neutral-100 text-neutral-600", - internal: "bg-amber-100 text-amber-800", - public: "bg-green-100 text-green-800", +const VARIANT: Record = { + draft: "secondary", + internal: "warning", + public: "success", }; export function VisibilityBadge({ visibility }: { visibility: Visibility }) { const { t } = useTranslation(); return ( - - {t(`visibility.${visibility}`)} - + {t(`visibility.${visibility}`)} ); } diff --git a/web/src/search/highlight.tsx b/web/src/search/highlight.tsx index e8af6d6..aec0762 100644 --- a/web/src/search/highlight.tsx +++ b/web/src/search/highlight.tsx @@ -31,7 +31,7 @@ export function Highlight({ text }: { text: string }) { } nodes.push( - + {rest.slice(start + PRE.length, end)} , );