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.
145 lines
4.8 KiB
TypeScript
145 lines
4.8 KiB
TypeScript
import { useState } from "react"
|
|
import type { Meta, StoryObj } from "@storybook/react-vite"
|
|
import {
|
|
Menubar,
|
|
MenubarCheckboxItem,
|
|
MenubarContent,
|
|
MenubarGroup,
|
|
MenubarItem,
|
|
MenubarLabel,
|
|
MenubarMenu,
|
|
MenubarRadioGroup,
|
|
MenubarRadioItem,
|
|
MenubarSeparator,
|
|
MenubarShortcut,
|
|
MenubarSub,
|
|
MenubarSubContent,
|
|
MenubarSubTrigger,
|
|
MenubarTrigger,
|
|
} from "@hm-design/ui"
|
|
import { StoryPage, StoryPageHeader, StorySection } from "@/components/story-page"
|
|
|
|
// Menubar is a desktop menu bar (Left / Right between top menus). The representative Story is the real bar and opens the real
|
|
// menus; Radix owns the keyboard model and focus. Hidden verification Stories isolate trigger focus and open + focus.
|
|
|
|
function Specimen() {
|
|
const [grid, setGrid] = useState(true)
|
|
const [ruler, setRuler] = useState(false)
|
|
const [zoom, setZoom] = useState("100")
|
|
return (
|
|
<Menubar>
|
|
<MenubarMenu>
|
|
<MenubarTrigger>파일</MenubarTrigger>
|
|
<MenubarContent>
|
|
<MenubarItem>
|
|
새로 만들기 <MenubarShortcut>⌘N</MenubarShortcut>
|
|
</MenubarItem>
|
|
<MenubarItem>
|
|
열기 <MenubarShortcut>⌘O</MenubarShortcut>
|
|
</MenubarItem>
|
|
<MenubarItem disabled>저장 (사용 불가)</MenubarItem>
|
|
<MenubarSeparator />
|
|
<MenubarSub>
|
|
<MenubarSubTrigger>내보내기</MenubarSubTrigger>
|
|
<MenubarSubContent>
|
|
<MenubarItem>PDF</MenubarItem>
|
|
<MenubarItem>이미지</MenubarItem>
|
|
</MenubarSubContent>
|
|
</MenubarSub>
|
|
<MenubarSeparator />
|
|
<MenubarItem variant="destructive">종료</MenubarItem>
|
|
</MenubarContent>
|
|
</MenubarMenu>
|
|
<MenubarMenu>
|
|
<MenubarTrigger>편집</MenubarTrigger>
|
|
<MenubarContent>
|
|
<MenubarItem>
|
|
실행 취소 <MenubarShortcut>⌘Z</MenubarShortcut>
|
|
</MenubarItem>
|
|
<MenubarItem>
|
|
다시 실행 <MenubarShortcut>⇧⌘Z</MenubarShortcut>
|
|
</MenubarItem>
|
|
</MenubarContent>
|
|
</MenubarMenu>
|
|
<MenubarMenu>
|
|
<MenubarTrigger>보기</MenubarTrigger>
|
|
<MenubarContent>
|
|
<MenubarGroup>
|
|
<MenubarLabel>표시</MenubarLabel>
|
|
<MenubarCheckboxItem checked={grid} onCheckedChange={setGrid}>
|
|
격자
|
|
</MenubarCheckboxItem>
|
|
<MenubarCheckboxItem checked={ruler} onCheckedChange={setRuler}>
|
|
눈금자
|
|
</MenubarCheckboxItem>
|
|
</MenubarGroup>
|
|
<MenubarSeparator />
|
|
<MenubarLabel>확대</MenubarLabel>
|
|
<MenubarRadioGroup value={zoom} onValueChange={setZoom}>
|
|
<MenubarRadioItem value="75">75%</MenubarRadioItem>
|
|
<MenubarRadioItem value="100">100%</MenubarRadioItem>
|
|
<MenubarRadioItem value="125">125%</MenubarRadioItem>
|
|
</MenubarRadioGroup>
|
|
</MenubarContent>
|
|
</MenubarMenu>
|
|
</Menubar>
|
|
)
|
|
}
|
|
|
|
const meta = { title: "Components/Navigation/Menubar", component: Menubar, parameters: { layout: "padded" } } satisfies Meta<typeof Menubar>
|
|
export default meta
|
|
type Story = StoryObj<typeof meta>
|
|
|
|
// Representative Story: File (item, shortcut, disabled, separator, sub menu, destructive), Edit, View (label, checkbox, radio).
|
|
export const All: Story = {
|
|
name: "Menubar",
|
|
parameters: { layout: "fullscreen", controls: { disable: true } },
|
|
render: () => (
|
|
<StoryPage>
|
|
<StoryPageHeader title="Menubar" />
|
|
<StorySection title="Overview">
|
|
<div style={{ minHeight: 384 }}>
|
|
<Specimen />
|
|
</div>
|
|
</StorySection>
|
|
</StoryPage>
|
|
),
|
|
}
|
|
|
|
function FocusSpecimen({ open = false }: { open?: boolean }) {
|
|
return (
|
|
<div style={{ minHeight: 160 }}>
|
|
<Menubar defaultValue={open ? "file" : undefined}>
|
|
<MenubarMenu value="file">
|
|
<MenubarTrigger>파일</MenubarTrigger>
|
|
<MenubarContent>
|
|
<MenubarItem>새로 만들기</MenubarItem>
|
|
<MenubarItem disabled>저장 (사용 불가)</MenubarItem>
|
|
</MenubarContent>
|
|
</MenubarMenu>
|
|
<MenubarMenu value="disabled">
|
|
<MenubarTrigger disabled>사용 불가</MenubarTrigger>
|
|
</MenubarMenu>
|
|
</Menubar>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export const TriggerFocus: Story = {
|
|
tags: ["verification"],
|
|
render: () => <FocusSpecimen />,
|
|
play: async ({ canvasElement }) => {
|
|
canvasElement.querySelector<HTMLElement>('[data-slot="menubar-trigger"]')?.focus()
|
|
},
|
|
}
|
|
|
|
export const OpenFocus: Story = {
|
|
tags: ["verification"],
|
|
render: () => <FocusSpecimen open />,
|
|
play: async ({ canvasElement }) => {
|
|
// Verification only: actual trigger focus while its real menu is open.
|
|
// Production keyboard opening still moves focus into the menu (Radix default).
|
|
canvasElement.querySelector<HTMLElement>('[data-slot="menubar-trigger"]')?.focus()
|
|
},
|
|
}
|