149 lines
5.2 KiB
TypeScript
149 lines
5.2 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 { STORY_KICKER, 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
|
|
kicker={STORY_KICKER.components}
|
|
title="Menubar"
|
|
description="데스크톱 앱 형태의 상단 메뉴 막대입니다. 메뉴가 열린 상태에서 좌우 방향키로 이웃 메뉴로 이동합니다."
|
|
/>
|
|
<StorySection title="Overview" description="파일(단축키 · Disabled · 하위 메뉴 · 위험 항목), 편집, 보기(Label · Checkbox · Radio)입니다. 메뉴를 눌러 엽니다.">
|
|
<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()
|
|
},
|
|
}
|