Files
HMDesign/src/components/navigation-menu.stories.tsx
T
shkim4 4c29894e56 refactor(storybook): simplify component and preview examples
Drop page / section descriptions, state notes and repeated captions from
the representative Stories of 44 components, the Patterns, Center
Graphic and OrgChart pages. Value readouts, spec lists and captions hide
only on the representative page; hidden verification Stories keep them.
Tabs Default API and the Filter Bar draft / applied counts move to the
hidden DefaultApi and DraftApplied Stories; Center Overview is hidden
with its direct URL kept. Story IDs are unchanged.

Align the component, pattern and OrgChart docs with the pages, point the
quality reference check at the DraftApplied Story, and make the P8
styles check expect no section header on the preview-only Expanded page.
2026-10-23 00:28:05 +09:00

152 lines
5.8 KiB
TypeScript

import type { ReactNode } from "react"
import type { Meta, StoryObj } from "@storybook/react-vite"
import {
NavigationMenu,
NavigationMenuContent,
NavigationMenuIndicator,
NavigationMenuItem,
NavigationMenuLink,
NavigationMenuList,
NavigationMenuTrigger,
} from "@hm-design/ui"
import { StateBoard } from "@/components/story-board"
import { StoryPage, StoryPageHeader, StorySection } from "@/components/story-page"
// Navigation Menu is for destinations (links), not commands. The representative Story opens the real menus with hover, click or
// keyboard; the same specimens also exist as Stories tagged `verification` (hidden from the sidebar). Title + description children
// inside a content link are marked with `data-description` for the secondary text.
function Destination({ href, title, description, active }: { href: string; title: string; description?: string; active?: boolean }) {
return (
<NavigationMenuLink href={href} active={active}>
<span>{title}</span>
{description ? <span data-description="">{description}</span> : null}
</NavigationMenuLink>
)
}
function Specimen({ viewport = true }: { viewport?: boolean }) {
return (
<NavigationMenu viewport={viewport}>
<NavigationMenuList>
<NavigationMenuItem>
<NavigationMenuTrigger>제품</NavigationMenuTrigger>
<NavigationMenuContent>
<ul className="sm:w-[min(360px,calc(100cqw-18px))]" style={{ display: "grid", gap: 4 }}>
<li>
<Destination href="#analytics" title="분석" description="노선과 시설물 데이터를 한눈에 확인합니다." />
</li>
<li>
<Destination href="#maps" title="지도" description="공간 정보를 지도 위에서 관리합니다." />
</li>
<li>
<Destination href="#reports" title="리포트" description="점검 결과를 보고서로 만듭니다." />
</li>
</ul>
</NavigationMenuContent>
</NavigationMenuItem>
<NavigationMenuItem>
<NavigationMenuTrigger>자료</NavigationMenuTrigger>
<NavigationMenuContent>
<ul className="sm:w-[min(300px,calc(100cqw-18px))]" style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 4 }}>
<li>
<Destination href="#docs" title="문서" />
</li>
<li>
<Destination href="#guides" title="가이드" />
</li>
<li>
<Destination href="#api" title="API" />
</li>
<li>
<Destination href="#blog" title="블로그" />
</li>
</ul>
</NavigationMenuContent>
</NavigationMenuItem>
<NavigationMenuItem>
<NavigationMenuLink href="#company" active>
회사
</NavigationMenuLink>
</NavigationMenuItem>
<NavigationMenuItem>
<NavigationMenuLink href="#contact">문의</NavigationMenuLink>
</NavigationMenuItem>
<NavigationMenuItem>
<NavigationMenuTrigger disabled>준비 중</NavigationMenuTrigger>
</NavigationMenuItem>
</NavigationMenuList>
{viewport ? <NavigationMenuIndicator /> : null}
</NavigationMenu>
)
}
// Size specimens to their review area (including the panel's 8px padding + 1px border on each side).
// Mobile uses the content's natural minimum width instead of a fixed desktop specimen width.
const frame = (node: ReactNode) => (
<div
style={{ minHeight: 256, width: "100%", containerType: "inline-size" }}
className="max-md:[&_[data-viewport=false]_[data-slot=navigation-menu-content]]:absolute"
>
{node}
</div>
)
const meta = { title: "Components/Navigation/Navigation Menu", component: NavigationMenu, parameters: { layout: "padded" } } satisfies Meta<typeof NavigationMenu>
export default meta
type Story = StoryObj<typeof meta>
export const All: Story = {
name: "Navigation Menu",
parameters: { layout: "fullscreen", controls: { disable: true } },
render: () => (
<StoryPage>
<StoryPageHeader title="Navigation Menu" />
<StorySection title="Overview">
<StateBoard
min={520}
states={[
{ id: "viewport", label: "Viewport", children: frame(<Specimen />) },
]}
/>
</StorySection>
<StorySection title="Variants">
<StateBoard
min={520}
states={[
{ id: "no-viewport", label: "No viewport", children: frame(<Specimen viewport={false} />) },
]}
/>
</StorySection>
</StoryPage>
),
}
export const Default: Story = { tags: ["verification"], render: () => frame(<Specimen />) }
export const NoViewport: Story = { tags: ["verification"], name: "No Viewport", render: () => frame(<Specimen viewport={false} />) }
// Three enabled triggers with different widths exercise first / middle / last offsets in both positioning modes.
export const IndicatorAlignment: Story = {
tags: ["verification"],
render: () => (
<StateBoard min={520} states={[true, false].map((viewport) => ({
id: viewport ? "viewport" : "no-viewport",
label: `Indicator alignment · viewport=${viewport}`,
children: frame(
<NavigationMenu viewport={viewport}>
<NavigationMenuList>
{["제품", "기술 자료", "지원 센터"].map((label) => (
<NavigationMenuItem key={label}>
<NavigationMenuTrigger>{label}</NavigationMenuTrigger>
<NavigationMenuContent><Destination href="#verification" title={`${label} 안내`} /></NavigationMenuContent>
</NavigationMenuItem>
))}
</NavigationMenuList>
{viewport ? <NavigationMenuIndicator /> : null}
</NavigationMenu>
),
}))} />
),
}