Files
HMDesign/src/components/navigation-menu.stories.tsx
T

156 lines
6.2 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 { STORY_KICKER, 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
kicker={STORY_KICKER.components}
title="Navigation Menu"
description="목적지(링크)로 이동하는 메뉴입니다. 명령 실행에는 쓰지 않습니다. hover, 클릭 또는 키보드로 엽니다."
/>
<StorySection title="Overview">
<StateBoard
min={520}
states={[
{ id: "viewport", label: "Viewport (default)", note: "제품 / 자료는 content, 회사는 현재 위치(active), 문의는 일반 링크, 준비 중은 disabled trigger", children: frame(<Specimen />) },
]}
/>
</StorySection>
<StorySection title="Variants">
<StateBoard
min={520}
states={[
{ id: "no-viewport", label: "viewport={false}", note: "각 content가 자기 trigger 아래에 열립니다", 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>
),
}))} />
),
}