feat: add design system platform

This commit is contained in:
donghyeon-ka
2026-07-26 15:49:07 +09:00
parent e49d90f713
commit 13f28ef811
53 changed files with 3477 additions and 410 deletions
+59 -59
View File
@@ -5,6 +5,13 @@ import {
NAVIGATION_ROUTES,
routePath,
} from "../../features/installed-feature-contracts.js";
import {
Button,
Drawer,
IconButton,
MenuIcon,
Select,
} from "../design-system/index.js";
import { useSession } from "../providers/session-provider.jsx";
import { useTheme } from "../providers/theme-provider.jsx";
@@ -27,16 +34,6 @@ export function AppShell() {
setNavigationOpen(false);
}, [location.pathname]);
useEffect(() => {
if (!navigationOpen) return undefined;
/** @param {KeyboardEvent} event */
const closeOnEscape = (event) => {
if (event.key === "Escape") setNavigationOpen(false);
};
window.addEventListener("keydown", closeOnEscape);
return () => window.removeEventListener("keydown", closeOnEscape);
}, [navigationOpen]);
async function runSessionAction() {
setSessionActionPending(true);
setSessionActionFailed(false);
@@ -71,26 +68,29 @@ export function AppShell() {
본문으로 건너뛰기
</a>
<header className="app-shell__header">
<button
<IconButton
accessibleName="메뉴"
className="app-shell__menu-button"
type="button"
aria-controls="primary-navigation"
aria-controls="mobile-primary-navigation"
aria-expanded={navigationOpen}
onClick={() => setNavigationOpen((open) => !open)}
variant="secondary"
>
<span aria-hidden="true"></span>
<span>메뉴</span>
</button>
<MenuIcon />
</IconButton>
<NavLink className="app-shell__brand" to={routePath("APP_HOME")}>
Frontend Skeleton
</NavLink>
<div className="app-shell__session">
<label className="visually-hidden" htmlFor="theme-preference">
색상 테마
</label>
<select
<Select
className="theme-selector"
id="theme-preference"
label="색상 테마"
options={[
{ value: "system", label: "시스템 테마" },
{ value: "light", label: "라이트 테마" },
{ value: "dark", label: "다크 테마" },
]}
value={preference}
onChange={(event) =>
setPreference(
@@ -99,23 +99,18 @@ export function AppShell() {
),
)
}
>
<option value="system">시스템 테마</option>
<option value="light">라이트 테마</option>
<option value="dark">다크 테마</option>
</select>
/>
<span className="session-status" data-state={sessionState}>
{SESSION_LABELS[sessionState]}
</span>
{integrationAvailable ? (
<button
className="ui-button ui-button--compact"
type="button"
<Button
disabled={sessionActionPending}
onClick={() => void runSessionAction()}
size="compact"
>
{sessionActionPending ? "처리 중…" : sessionActionLabel}
</button>
</Button>
) : null}
</div>
{sessionActionFailed ? (
@@ -124,40 +119,45 @@ export function AppShell() {
</p>
) : null}
</header>
<aside
className="app-shell__sidebar"
data-open={navigationOpen}
aria-label="사이드바"
>
<nav id="primary-navigation" aria-label="주요 탐색">
<ul className="app-navigation">
{NAVIGATION_ROUTES.map((definition) => (
<li key={definition.routeId}>
<NavLink
className={({ isActive }) =>
`app-navigation__link${isActive ? " is-active" : ""}`
}
end={definition.path === "/"}
to={definition.path}
>
{definition.navigationLabel}
</NavLink>
</li>
))}
</ul>
</nav>
<aside className="app-shell__sidebar" aria-label="사이드바">
<PrimaryNavigation id="primary-navigation" />
</aside>
{navigationOpen ? (
<button
className="app-shell__scrim"
type="button"
aria-label="메뉴 닫기"
onClick={() => setNavigationOpen(false)}
/>
) : null}
<Drawer
closeLabel="메뉴 닫기"
onClose={() => setNavigationOpen(false)}
open={navigationOpen}
title="메뉴"
>
<PrimaryNavigation id="mobile-primary-navigation" />
</Drawer>
<main className="app-shell__content" id="main-content" tabIndex={-1}>
<Outlet />
</main>
</div>
);
}
/**
* @param {{ id: string }} props
*/
function PrimaryNavigation({ id }) {
return (
<nav id={id} aria-label="주요 탐색">
<ul className="app-navigation">
{NAVIGATION_ROUTES.map((definition) => (
<li key={definition.routeId}>
<NavLink
className={({ isActive }) =>
`app-navigation__link${isActive ? " is-active" : ""}`
}
end={definition.path === "/"}
to={definition.path}
>
{definition.navigationLabel}
</NavLink>
</li>
))}
</ul>
</nav>
);
}