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