Refactor authentication and account management with modular UI components (#242)

This commit is contained in:
2026-08-23 01:30:58 -04:00
committed by Aaron Po
parent 6a44afd6b6
commit bfd45842fe
109 changed files with 14743 additions and 10792 deletions

View File

@@ -1,157 +1,160 @@
import type { Meta, StoryObj } from '@storybook/react-vite';
import { expect, within } from 'storybook/test';
import { biergartenThemes } from '../app/lib/themes';
import { biergartenThemes } from '../app/features/theme/themes';
const themesDescription = `Palette reference for all Biergarten themes. Each panel shows the main semantic color pairs, status tokens, and custom content tokens so you can catch contrast issues, pairing mistakes, and mood drift before they show up in real components.`;
function ThemeSwatch({ label, className }: { label: string; className: string }) {
return <div className={`rounded-box p-3 text-sm font-medium ${className}`}>{label}</div>;
}
const ThemeSwatch = ({ label, className }: { label: string; className: string }) => {
return <div className={`rounded-box p-3 text-sm font-medium ${className}`}>{label}</div>;
};
/** For custom tokens not covered by Tailwind utilities (surface, muted, highlight). */
function CssVarSwatch({ label, bg, color }: { label: string; bg: string; color: string }) {
return (
<div
className="rounded-box p-3 text-sm font-medium"
style={{ backgroundColor: `var(${bg})`, color: `var(${color})` }}
>
{label}
</div>
);
}
function TextTokenSample({
label,
background,
text,
}: {
label: string;
background: string;
text: string;
}) {
return (
<div className="rounded-box p-3" style={{ backgroundColor: `var(${background})` }}>
<p className="text-xs font-semibold uppercase tracking-wide text-base-content/50">
const CssVarSwatch = ({ label, bg, color }: { label: string; bg: string; color: string }) => {
return (
<div
className="rounded-box p-3 text-sm font-medium"
style={{ backgroundColor: `var(${bg})`, color: `var(${color})` }}
>
{label}
</p>
<p className="mt-1 text-sm font-medium" style={{ color: `var(${text})` }}>
Secondary copy, placeholders, and helper text.
</p>
</div>
);
}
</div>
);
};
function ThemePanel({ label, value, vibe }: { label: string; value: string; vibe: string }) {
return (
<section
data-theme={value}
className="rounded-box border border-base-300 bg-base-100 shadow-lg"
>
<div className="space-y-4 p-5">
<div className="space-y-1">
<h2 className="text-2xl font-bold">{label}</h2>
<p className="text-sm text-base-content/70">{vibe}</p>
</div>
const TextTokenSample = ({
label,
background,
text,
}: {
label: string;
background: string;
text: string;
}) => {
return (
<div className="rounded-box p-3" style={{ backgroundColor: `var(${background})` }}>
<p className="text-xs font-semibold uppercase tracking-wide text-base-content/50">
{label}
</p>
<p className="mt-1 text-sm font-medium" style={{ color: `var(${text})` }}>
Secondary copy, placeholders, and helper text.
</p>
</div>
);
};
{/* Core palette */}
<div>
<p className="mb-1.5 text-xs font-semibold uppercase tracking-wide text-base-content/50">
Core
</p>
<div className="grid gap-2 sm:grid-cols-2">
<ThemeSwatch label="Primary" className="bg-primary text-primary-content" />
<ThemeSwatch label="Secondary" className="bg-secondary text-secondary-content" />
<ThemeSwatch label="Accent" className="bg-accent text-accent-content" />
<ThemeSwatch label="Neutral" className="bg-neutral text-neutral-content" />
</div>
</div>
const ThemePanel = ({ label, value, vibe }: { label: string; value: string; vibe: string }) => {
return (
<section
data-theme={value}
className="rounded-box border border-base-300 bg-base-100 shadow-lg"
>
<div className="space-y-4 p-5">
<div className="space-y-1">
<h2 className="text-2xl font-bold">{label}</h2>
<p className="text-sm text-base-content/70">{vibe}</p>
</div>
{/* Status tokens */}
<div>
<p className="mb-1.5 text-xs font-semibold uppercase tracking-wide text-base-content/50">
Status
</p>
<div className="grid gap-2 grid-cols-2 sm:grid-cols-4">
<ThemeSwatch label="Info" className="bg-info text-info-content" />
<ThemeSwatch label="Success" className="bg-success text-success-content" />
<ThemeSwatch label="Warning" className="bg-warning text-warning-content" />
<ThemeSwatch label="Error" className="bg-error text-error-content" />
</div>
</div>
{/* Core palette */}
<div>
<p className="mb-1.5 text-xs font-semibold uppercase tracking-wide text-base-content/50">
Core
</p>
<div className="grid gap-2 sm:grid-cols-2">
<ThemeSwatch label="Primary" className="bg-primary text-primary-content" />
<ThemeSwatch
label="Secondary"
className="bg-secondary text-secondary-content"
/>
<ThemeSwatch label="Accent" className="bg-accent text-accent-content" />
<ThemeSwatch label="Neutral" className="bg-neutral text-neutral-content" />
</div>
</div>
{/* Content tokens (custom) */}
<div>
<p className="mb-1.5 text-xs font-semibold uppercase tracking-wide text-base-content/50">
Content
</p>
<div className="grid gap-2 sm:grid-cols-3">
<CssVarSwatch
label="Surface"
bg="--color-surface"
color="--color-surface-content"
/>
<TextTokenSample
label="Muted on Base"
background="--color-base-100"
text="--color-muted"
/>
<CssVarSwatch
label="Highlight"
bg="--color-highlight"
color="--color-highlight-content"
/>
</div>
<div className="mt-2">
<TextTokenSample
label="Muted on Surface"
background="--color-surface"
text="--color-muted"
/>
</div>
</div>
{/* Status tokens */}
<div>
<p className="mb-1.5 text-xs font-semibold uppercase tracking-wide text-base-content/50">
Status
</p>
<div className="grid gap-2 grid-cols-2 sm:grid-cols-4">
<ThemeSwatch label="Info" className="bg-info text-info-content" />
<ThemeSwatch label="Success" className="bg-success text-success-content" />
<ThemeSwatch label="Warning" className="bg-warning text-warning-content" />
<ThemeSwatch label="Error" className="bg-error text-error-content" />
</div>
</div>
<div className="flex flex-wrap gap-2">
<button className="btn btn-primary btn-sm">Primary</button>
<button className="btn btn-secondary btn-sm">Secondary</button>
<button className="btn btn-outline btn-sm">Outline</button>
</div>
{/* Content tokens (custom) */}
<div>
<p className="mb-1.5 text-xs font-semibold uppercase tracking-wide text-base-content/50">
Content
</p>
<div className="grid gap-2 sm:grid-cols-3">
<CssVarSwatch
label="Surface"
bg="--color-surface"
color="--color-surface-content"
/>
<TextTokenSample
label="Muted on Base"
background="--color-base-100"
text="--color-muted"
/>
<CssVarSwatch
label="Highlight"
bg="--color-highlight"
color="--color-highlight-content"
/>
</div>
<div className="mt-2">
<TextTokenSample
label="Muted on Surface"
background="--color-surface"
text="--color-muted"
/>
</div>
</div>
<div role="alert" className="alert alert-success alert-soft">
<span>Semantic tokens stay stable while the atmosphere changes.</span>
<div className="flex flex-wrap gap-2">
<button className="btn btn-primary btn-sm">Primary</button>
<button className="btn btn-secondary btn-sm">Secondary</button>
<button className="btn btn-outline btn-sm">Outline</button>
</div>
<div role="alert" className="alert alert-success alert-soft">
<span>Semantic tokens stay stable while the atmosphere changes.</span>
</div>
</div>
</div>
</section>
);
}
</section>
);
};
const meta = {
title: 'Themes/Biergarten Themes',
parameters: {
layout: 'fullscreen',
docs: {
description: {
component: themesDescription,
},
},
},
tags: ['autodocs'],
render: () => (
<div className="grid gap-6 p-6 lg:grid-cols-2">
{biergartenThemes.map((theme) => (
<ThemePanel key={theme.value} {...theme} />
))}
</div>
),
title: 'Themes/Biergarten Themes',
parameters: {
layout: 'fullscreen',
docs: {
description: {
component: themesDescription,
},
},
},
tags: ['autodocs'],
render: () => (
<div className="grid gap-6 p-6 lg:grid-cols-2">
{biergartenThemes.map((theme) => (
<ThemePanel key={theme.value} {...theme} />
))}
</div>
),
} satisfies Meta;
export default meta;
type Story = StoryObj<typeof meta>;
export const Gallery: Story = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
for (const theme of biergartenThemes) {
await expect(canvas.getByRole('heading', { name: theme.label })).toBeInTheDocument();
}
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
for (const theme of biergartenThemes) {
await expect(canvas.getByRole('heading', { name: theme.label })).toBeInTheDocument();
}
},
};