import type { Meta, StoryObj } from '@storybook/react-vite' import { IconCopy, IconDots, IconPencil, IconSquareRounded, IconTrash } from '@tabler/icons-react' import { Button } from './Button' import { IconButton } from './IconButton' import { Menu, MenuItem, MenuPanel, MenuRow, MenuSection, MenuSeparator, MenuSub } from './Menu' import { SectionLabel } from './SectionLabel' /** * The shell every menu shares: an elevated container, rows, headings — and all * of a menu's behaviour, which is Base UI's. **The menu owns its trigger**, so * a caller writes no open state, no placement and no dismiss. * * ↑ ↓ walk the rows and wrap, Home and End jump, typing a row's first letters * goes to it, → opens a submenu and ← closes it, Escape closes and gives focus * back to the trigger. * * A live menu portals and places itself against its trigger, so it cannot * stand in a docs canvas: **`Items` and `Sections` draw the surface** with * `MenuPanel` — which is what a menu *is*, and what `Menu` renders — and every * story below them is live (Katerina, D25). */ const meta = { title: 'Overlays/Menu', component: Menu, decorators: [(Story) =>
], args: { trigger: , children: null }, argTypes: { trigger: { control: false }, children: { control: false }, onOpenChange: { control: false }, actionsRef: { control: false } }, } satisfies Meta export default meta type Story = StoryObj const icon = (Icon: typeof IconPencil) => /** The anatomy at rest: rows with a leading icon, a shortcut, and the * destructive colour for the one that deletes. */ export const Items: Story = { parameters: { controls: { disable: true } }, render: () => ( {}} /> {}} /> } onClick={() => {}} /> ), } /** Groups under headings, with a divider between them. The divider runs the * width of the rows it separates, not the width of the panel's text. */ export const Sections: Story = { parameters: { controls: { disable: true } }, render: () => ( {}} /> {}} /> {}} /> {}} /> ), } /** A non-interactive line at row geometry — a value, a note. */ export const WithARow: Story = { parameters: { controls: { disable: true } }, render: () => ( Item one Label {}} /> ), } /** * Live. Click the button, then use only the keyboard: ↑ ↓ walk the rows and * wrap, Home and End jump, typing "de" goes to Delete, Escape closes and puts * focus back on the button. * * The trigger belongs to the menu, so nothing here keeps open state. */ export const FromATrigger: Story = { parameters: { controls: { disable: true } }, render: () => ( Open the menu}> {}} /> {}} /> {}} /> {}} /> {}} /> } onClick={() => {}} /> ), } /** The menu's right edge hangs from the trigger's, for a control at the right * of a bar. */ export const RightAligned: Story = { parameters: { controls: { disable: true }, layout: 'fullscreen' }, render: () => ( /* `items-start`: a flex row stretches its children by default, and an IconButton has no height of its own — without it the 24px square grew to the row's 300px and wore a 300px hover fill (measured 24x268 before). */
}> {}} /> {}} />
), } /** * The shape a card uses: a `⋮` at the end of the row, named by its tooltip, * opening a right-aligned menu on **click**. * * Walk down the rows, out to the submenu and back — it stays open the whole * way, including the diagonal from a row to its panel, and closes on Escape, * on a press outside, or on a second press of the `⋮`. */ export const OnACard: Story = { parameters: { controls: { disable: true } }, render: () => (
A card. Press the control at the end of the row, then walk down the rows and out to the submenu. } className="w-[244px] gap-2" > {}} /> {}} /> {}} /> } onClick={() => {}} />
), }