feat: add Tailwind semantic design tokens

This commit is contained in:
donghyeon-ka
2026-07-25 21:09:07 +09:00
parent a023c3b645
commit 9a92c11792
11 changed files with 491 additions and 28 deletions
+1
View File
@@ -5,6 +5,7 @@ import { createAnonymousSessionAdapter } from "../adapters/auth/external-session
import { BootErrorShell } from "../presentation/boundaries/boot-error-shell.jsx";
import { AppRouter } from "../presentation/routes/app-router.jsx";
import { BootConfigError, loadRuntimeConfig } from "./load-runtime-config.js";
import "../presentation/styles/theme.css";
const rootElement = document.getElementById("root");
@@ -11,7 +11,7 @@ export function LoadingSurface({ label = "불러오는 중" }) {
/** @param {{ title?: string, action?: React.ReactNode }} props */
export function EmptySurface({ title = "표시할 항목이 없습니다.", action }) {
return (
<section>
<section className="ui-empty">
<p>{title}</p>
{action}
</section>
@@ -28,10 +28,14 @@ export function EmptySurface({ title = "표시할 항목이 없습니다.", acti
*/
export function TerminalErrorSurface({ userMessageKey, action, onAction }) {
return (
<section role="alert" aria-labelledby="terminal-error-message">
<section
className="ui-terminal-error"
role="alert"
aria-labelledby="terminal-error-message"
>
<p id="terminal-error-message">{userMessageKey}</p>
{action !== "none" && (
<button type="button" onClick={onAction}>
<button className="ui-button" type="button" onClick={onAction}>
{action}
</button>
)}
+7 -5
View File
@@ -10,7 +10,7 @@ import { decideRouteAccess } from "./navigation-policy.js";
function HomePage() {
return (
<main>
<main className="ui-page">
<h1>Clean Architecture Frontend</h1>
<p>런타임 계약이 검증되었습니다.</p>
<Link to={routePath("SAMPLE_RESOURCE_LIST")}>샘플 리소스</Link>
@@ -20,7 +20,7 @@ function HomePage() {
function SamplePlaceholder() {
return (
<main>
<main className="ui-page">
<h1>샘플 리소스</h1>
<p>계약 fixture를 준비하고 있습니다.</p>
</main>
@@ -29,7 +29,7 @@ function SamplePlaceholder() {
function NotFoundPage() {
return (
<main>
<main className="ui-page">
<h1>페이지를 찾을 없습니다.</h1>
<Link to={routePath("APP_HOME")}>홈으로 이동</Link>
</main>
@@ -48,9 +48,11 @@ function GuardedSampleRoute({ authSession }) {
);
if (!decision.allowed) {
return (
<main>
<main className="ui-page">
<h1>세션이 필요합니다.</h1>
<button type="button">로그인</button>
<button className="ui-button" type="button">
로그인
</button>
</main>
);
}
+71
View File
@@ -0,0 +1,71 @@
@import "tailwindcss";
@theme {
--color-surface: oklch(0.985 0.003 247);
--color-surface-muted: oklch(0.94 0.01 247);
--color-content: oklch(0.25 0.025 247);
--color-content-muted: oklch(0.48 0.025 247);
--color-action: oklch(0.55 0.18 255);
--color-action-hover: oklch(0.48 0.2 255);
--color-danger: oklch(0.55 0.2 25);
--color-focus: oklch(0.72 0.16 225);
--radius-control: 0.5rem;
--radius-surface: 0.75rem;
--spacing-page: 1.5rem;
--font-sans: Inter, ui-sans-serif, system-ui, sans-serif;
}
@layer base {
:root {
color: var(--color-content);
background: var(--color-surface);
font-family: var(--font-sans);
}
body {
margin: 0;
}
:focus-visible {
outline: 0.1875rem solid var(--color-focus);
outline-offset: 0.1875rem;
}
}
@layer components {
.ui-page {
@apply mx-auto flex min-h-screen max-w-4xl flex-col gap-6 p-page;
}
.ui-panel {
@apply rounded-surface border border-surface-muted bg-white p-6 shadow-sm;
}
.ui-button {
@apply rounded-control bg-action px-4 py-2 font-semibold text-white;
}
.ui-button:hover {
@apply bg-action-hover;
}
.ui-skeleton {
@apply h-24 animate-pulse rounded-surface bg-surface-muted;
}
.ui-empty,
.ui-terminal-error {
@apply rounded-surface border border-surface-muted p-6;
}
}
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
scroll-behavior: auto !important;
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}
@@ -0,0 +1,15 @@
export function DesignTokenShowcase() {
return (
<section className="ui-panel" aria-labelledby="token-showcase-title">
<h2 id="token-showcase-title" className="text-xl font-semibold">
Design token fixture
</h2>
<p className="text-content-muted">
Semantic tokens style loading, empty, and terminal surfaces.
</p>
<button className="ui-button" type="button">
Token action
</button>
</section>
);
}