115 lines
4.9 KiB
TypeScript
115 lines
4.9 KiB
TypeScript
import { useState } from "react"
|
|
import type { Meta, StoryObj } from "@storybook/react-vite"
|
|
import {
|
|
ContextMenu,
|
|
ContextMenuCheckboxItem,
|
|
ContextMenuContent,
|
|
ContextMenuGroup,
|
|
ContextMenuItem,
|
|
ContextMenuLabel,
|
|
ContextMenuRadioGroup,
|
|
ContextMenuRadioItem,
|
|
ContextMenuSeparator,
|
|
ContextMenuShortcut,
|
|
ContextMenuSub,
|
|
ContextMenuSubContent,
|
|
ContextMenuSubTrigger,
|
|
ContextMenuTrigger,
|
|
} from "@hm-design/ui"
|
|
import { STORY_KICKER, StoryPage, StoryPageHeader, StorySection } from "@/components/story-page"
|
|
|
|
// Context Menu opens from pointer context (right click, the context-menu key / Shift+F10, long press) on its trigger area.
|
|
// The representative Story is a target area: right click it. Radix owns keyboard navigation, focus return and Escape.
|
|
|
|
function Full() {
|
|
const [showStatus, setShowStatus] = useState(true)
|
|
const [showPanel, setShowPanel] = useState(false)
|
|
const [position, setPosition] = useState("bottom")
|
|
return (
|
|
<ContextMenu>
|
|
{/* tabIndex lets keyboard users focus the area and open the menu with Shift+F10 / the context-menu key; Radix Trigger is a plain span. */}
|
|
<ContextMenuTrigger tabIndex={0} className="flex h-40 w-80 items-center justify-center rounded-md border border-dashed text-sm text-muted-foreground focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring">
|
|
이 영역에서 오른쪽 클릭
|
|
</ContextMenuTrigger>
|
|
<ContextMenuContent className="w-56">
|
|
<ContextMenuLabel>파일</ContextMenuLabel>
|
|
<ContextMenuGroup>
|
|
<ContextMenuItem>
|
|
열기 <ContextMenuShortcut>⌘O</ContextMenuShortcut>
|
|
</ContextMenuItem>
|
|
<ContextMenuItem>
|
|
이름 바꾸기 <ContextMenuShortcut>F2</ContextMenuShortcut>
|
|
</ContextMenuItem>
|
|
<ContextMenuItem disabled>붙여넣기 (사용 불가)</ContextMenuItem>
|
|
</ContextMenuGroup>
|
|
<ContextMenuSeparator />
|
|
<ContextMenuLabel>보기</ContextMenuLabel>
|
|
<ContextMenuCheckboxItem checked={showStatus} onCheckedChange={setShowStatus}>
|
|
상태 표시줄
|
|
</ContextMenuCheckboxItem>
|
|
<ContextMenuCheckboxItem checked={showPanel} onCheckedChange={setShowPanel}>
|
|
패널
|
|
</ContextMenuCheckboxItem>
|
|
<ContextMenuSeparator />
|
|
<ContextMenuLabel>위치</ContextMenuLabel>
|
|
<ContextMenuRadioGroup value={position} onValueChange={setPosition}>
|
|
<ContextMenuRadioItem value="top">상단</ContextMenuRadioItem>
|
|
<ContextMenuRadioItem value="bottom">하단</ContextMenuRadioItem>
|
|
<ContextMenuRadioItem value="right">오른쪽</ContextMenuRadioItem>
|
|
</ContextMenuRadioGroup>
|
|
<ContextMenuSeparator />
|
|
<ContextMenuSub>
|
|
<ContextMenuSubTrigger>더 보기</ContextMenuSubTrigger>
|
|
<ContextMenuSubContent>
|
|
<ContextMenuItem>이메일로 공유</ContextMenuItem>
|
|
<ContextMenuItem>링크 복사</ContextMenuItem>
|
|
</ContextMenuSubContent>
|
|
</ContextMenuSub>
|
|
<ContextMenuSeparator />
|
|
<ContextMenuItem variant="destructive">삭제</ContextMenuItem>
|
|
</ContextMenuContent>
|
|
</ContextMenu>
|
|
)
|
|
}
|
|
|
|
const meta = { title: "Components/Overlay/Context Menu", component: ContextMenu, parameters: { layout: "centered" } } satisfies Meta<typeof ContextMenu>
|
|
export default meta
|
|
type Story = StoryObj<typeof meta>
|
|
|
|
// Representative Story: right click the area to open the full menu (item, disabled, separator, shortcut, checkbox, radio group, sub menu, destructive).
|
|
export const All: Story = {
|
|
name: "Context Menu",
|
|
parameters: { layout: "fullscreen", controls: { disable: true } },
|
|
render: () => (
|
|
<StoryPage>
|
|
<StoryPageHeader
|
|
kicker={STORY_KICKER.components}
|
|
title="Context Menu"
|
|
description="영역에서 오른쪽 클릭, 컨텍스트 메뉴 키 / Shift+F10, 길게 누르기로 여는 메뉴입니다."
|
|
/>
|
|
<StorySection title="Overview" description="항목 · Disabled · 단축키 · Checkbox · Radio Group · 하위 메뉴 · 위험 항목을 한 메뉴에서 확인합니다. 점선 영역에서 오른쪽 클릭합니다.">
|
|
<Full />
|
|
</StorySection>
|
|
</StoryPage>
|
|
),
|
|
}
|
|
|
|
// Verification Story: opens the menu at the area center on load (synthetic contextmenu event), hidden from the sidebar (tag `verification`).
|
|
export const Open: Story = {
|
|
tags: ["verification"],
|
|
parameters: { layout: "fullscreen" },
|
|
render: () => (
|
|
<div className="flex min-h-[560px] justify-center p-8">
|
|
<Full />
|
|
</div>
|
|
),
|
|
play: async ({ canvasElement }) => {
|
|
const area = canvasElement.querySelector<HTMLElement>("[data-slot='context-menu-trigger']")
|
|
if (!area) return
|
|
const r = area.getBoundingClientRect()
|
|
area.dispatchEvent(
|
|
new MouseEvent("contextmenu", { bubbles: true, cancelable: true, clientX: r.left + r.width / 2, clientY: r.top + r.height / 2, button: 2 }),
|
|
)
|
|
},
|
|
}
|