Initial commit: HMDesignSystem baseline
This commit is contained in:
commit
b6a0a61103
486 files changed
+68432
No files matched your search
@@ -0,0 +1,148 @@
|
||||
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()
|
||||
},
|
||||
}
|
||||
Reference in new issue
Block a user