feat: add Tailwind semantic design tokens
This commit is contained in:
@@ -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>
|
||||
)}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user