import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, DropdownMenuCheckboxItem, DropdownMenuRadioItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuGroup, DropdownMenuPortal, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuRadioGroup } from '@hanzo/ui'
A trigger opens a panel of grouped rows, each with an icon on the left and a shortcut hint on the right.
export function Default() {return (<DropdownMenu><DropdownMenuTrigger asChild><Button variant="outline">Account</Button></DropdownMenuTrigger><DropdownMenuContent><DropdownMenuLabel>Signed in as Ada</DropdownMenuLabel><DropdownMenuSeparator /><DropdownMenuGroup><DropdownMenuItem><User size={16} />Profile<DropdownMenuShortcut>⇧⌘P</DropdownMenuShortcut></DropdownMenuItem><DropdownMenuItem><CreditCard size={16} />Billing<DropdownMenuShortcut>⌘B</DropdownMenuShortcut></DropdownMenuItem><DropdownMenuItem><Settings size={16} />Settings<DropdownMenuShortcut>⌘,</DropdownMenuShortcut></DropdownMenuItem><DropdownMenuItem><Keyboard size={16} />Keyboard shortcuts<DropdownMenuShortcut>⌘K</DropdownMenuShortcut></DropdownMenuItem></DropdownMenuGroup><DropdownMenuSeparator /><DropdownMenuItem><LogOut size={16} />Log out<DropdownMenuShortcut>⇧⌘Q</DropdownMenuShortcut></DropdownMenuItem></DropdownMenuContent></DropdownMenu>)}
A destructive row goes red, and a disabled row dims and takes no clicks.
export function Variants() {return (<DropdownMenu><DropdownMenuTrigger asChild><Button variant="outline">invoice-0042.pdf</Button></DropdownMenuTrigger><DropdownMenuContent><DropdownMenuItem variant="default"><Pencil size={16} />Rename</DropdownMenuItem><DropdownMenuItem><Copy size={16} />Duplicate</DropdownMenuItem><DropdownMenuItem><FolderInput size={16} />Move to…</DropdownMenuItem><DropdownMenuItem disabled><Download size={16} />Download</DropdownMenuItem><DropdownMenuSeparator /><DropdownMenuItem variant="destructive"><Trash2 size={16} />Delete<DropdownMenuShortcut>⌘⌫</DropdownMenuShortcut></DropdownMenuItem></DropdownMenuContent></DropdownMenu>)}
Checkbox rows and a radio group keep their state in React, so the caption outside the menu follows every change.
export function Controlled() {const [lineNumbers, setLineNumbers] = useState(true)const [minimap, setMinimap] = useState(false)const [wrap, setWrap] = useState("off")return (<YStack gap="$3" items="flex-start"><DropdownMenu><DropdownMenuTrigger asChild><Button variant="outline">View</Button></DropdownMenuTrigger><DropdownMenuContent><DropdownMenuGroup><DropdownMenuLabel>Editor</DropdownMenuLabel><DropdownMenuCheckboxItem checked={lineNumbers} onCheckedChange={setLineNumbers} >Line numbers</DropdownMenuCheckboxItem><DropdownMenuCheckboxItem checked={minimap} onCheckedChange={setMinimap} >Minimap</DropdownMenuCheckboxItem></DropdownMenuGroup><DropdownMenuSeparator /><DropdownMenuLabel>Word wrap</DropdownMenuLabel><DropdownMenuRadioGroup value={wrap} onValueChange={setWrap}><DropdownMenuRadioItem value="off">Off</DropdownMenuRadioItem><DropdownMenuRadioItem value="column">At column 80</DropdownMenuRadioItem><DropdownMenuRadioItem value="viewport">At viewport</DropdownMenuRadioItem></DropdownMenuRadioGroup></DropdownMenuContent></DropdownMenu><Text fontSize="$2" color="$quiet">Line numbers {lineNumbers ? "on" : "off"} · minimap{" "}{minimap ? "on" : "off"} · wrap {wrap}</Text></YStack>)}
A sub trigger opens a second panel beside the first, wrapped in `DropdownMenuPortal` so it can reach past the first panel's edge; `align="end"` lines the menu up with the right edge of an icon trigger and `sideOffset` sets the gap.
export function Nested() {return (<DropdownMenu><DropdownMenuTrigger asChild><Button size="icon" variant="ghost" aria-label="More"><MoreHorizontal size={16} /></Button></DropdownMenuTrigger><DropdownMenuContent align="end" sideOffset={8}><DropdownMenuItem><Pencil size={16} />Rename</DropdownMenuItem><DropdownMenuItem><Download size={16} />Download</DropdownMenuItem><DropdownMenuSub><DropdownMenuSubTrigger><Share2 size={16} />Share</DropdownMenuSubTrigger><DropdownMenuPortal><DropdownMenuSubContent><DropdownMenuItem><Link size={16} />Copy link</DropdownMenuItem><DropdownMenuItem><Mail size={16} /></DropdownMenuItem></DropdownMenuSubContent></DropdownMenuPortal></DropdownMenuSub><DropdownMenuSeparator /><DropdownMenuItem variant="destructive"><Trash2 size={16} />Delete</DropdownMenuItem></DropdownMenuContent></DropdownMenu>)}
`trigger` and `items` build the same rows from a spec, for a menu whose entries come from data; `selected` marks the current row, `disabled` and `destructive` follow state, and `minWidth` makes room for a description line.
export function Declarative() {const [branch, setBranch] = useState("main")const branches = ["main", "develop", "release/2.4"]return (<YStack gap="$3" items="flex-start"><DropdownMenu minWidth={240} trigger={ <Button variant="outline"> <GitBranch size={16} /> {branch} </Button> } items={[ { type: "label", label: "Switch to" }, ...branches.map((name) => ({ key: name, label: name, icon: <GitBranch size={16} />, selected: branch === name, onSelect: () => setBranch(name), })), { type: "separator" }, { key: "delete", label: "Delete branch", description: branch === "main" ? "The default branch stays" : `Removes ${branch}`, destructive: true, disabled: branch === "main", onSelect: () => setBranch("main"), }, ]} /><Text fontSize="$2" color="$quiet">On {branch}</Text></YStack>)}
export type DropdownMenuProps = Omit<React.ComponentProps<typeof Menu>,"offset"> & {offset?: number/*** Declarative form. Give a `trigger` element and an `items` spec and the menu* builds itself out of the very same compound parts a hand-written `children`* tree would use — one component, one API, one rendering, whichever form the* call site prefers.*/trigger?: React.ReactElementitems?: MenuItemSpec[]minWidth?: numbermaxHeight?: number}