import type { Meta, StoryObj } from '@storybook/nextjs-vite';
import { fn } from 'storybook/test';
import {
ArrowRightUpIcon,
Copy01Icon,
Download01Icon,
EyeIcon,
Link01Icon,
PencilIcon,
Settings01Icon,
ShareIcon,
StarIcon,
TrashBlankIcon,
} from '../components/icons-v2-generated';
import {
ActionsMenu,
ActionsMenuDropdown,
type ActionsMenuGroup,
} from '../components/ui/actions-menu';
const meta = {
title: 'UI/ActionsMenu',
component: ActionsMenu,
parameters: {
layout: 'centered',
docs: {
description: {
component:
'Grouped action menu rendered as a flat panel. Supports plain items, icons, checkboxes, submenus, separators, destructive (danger) rows, navigation links, and an optional secondary icon action on the right of a row. Use `ActionsMenu` to render the panel inline, or `ActionsMenuDropdown` to attach it to a trigger button (built on Radix DropdownMenu).',
},
},
},
tags: ['autodocs'],
argTypes: {
groups: {
description: 'Array of groups, each holding an array of menu items',
},
className: {
control: 'text',
description: 'Additional CSS classes for the menu panel',
},
onItemClick: {
description: 'Fires when any item is activated',
},
},
decorators: [
(Story) => (
),
],
} satisfies Meta;
export default meta;
type Story = StoryObj;
/**
* A simple single-group menu with icons.
*/
export const Default: Story = {
args: {
groups: [
{
items: [
{ id: 'edit', label: 'Edit', icon: , onClick: fn() },
{ id: 'duplicate', label: 'Duplicate', icon: , onClick: fn() },
{ id: 'share', label: 'Share', icon: , onClick: fn() },
],
},
],
},
};
/**
* Multiple groups separated by a divider, with a destructive row in the last group.
*/
export const GroupedWithDanger: Story = {
args: {
groups: [
{
id: 'primary',
separator: true,
items: [
{ id: 'view', label: 'View details', icon: , onClick: fn() },
{ id: 'edit', label: 'Edit', icon: , onClick: fn() },
{ id: 'download', label: 'Download', icon: , onClick: fn() },
],
},
{
id: 'destructive',
items: [
{
id: 'delete',
label: 'Delete',
icon: ,
danger: true,
onClick: fn(),
},
],
},
],
},
};
/**
* Checkbox items keep the menu open on click and reflect a `checked` state.
*/
export const WithCheckboxes: Story = {
args: {
groups: [
{
items: [
{
id: 'starred',
label: 'Starred',
type: 'checkbox',
checked: true,
icon: ,
onClick: fn(),
},
{
id: 'archived',
label: 'Archived',
type: 'checkbox',
checked: false,
onClick: fn(),
},
],
},
],
},
};
/**
* A submenu expands a nested set of items on hover/focus. Submenus rely on the
* Radix DropdownMenu root, so they must be rendered through `ActionsMenuDropdown`
* (the inline `ActionsMenu` panel has no menu context).
*/
export const WithSubmenu: StoryObj = {
render: (args) => ,
args: {
groups: [
{
items: [
{ id: 'edit', label: 'Edit', icon: , onClick: fn() },
{
id: 'share',
label: 'Share',
type: 'submenu',
icon: ,
submenu: [
{ id: 'copy-link', label: 'Copy link', icon: , onClick: fn() },
{ id: 'share-via', label: 'Share via…', icon: , onClick: fn() },
],
},
{ id: 'settings', label: 'Settings', icon: , onClick: fn() },
],
},
],
onItemClick: fn(),
},
};
/**
* Disabled rows are non-interactive and dimmed.
*/
export const WithDisabledItem: Story = {
args: {
groups: [
{
items: [
{ id: 'edit', label: 'Edit', icon: , onClick: fn() },
{
id: 'duplicate',
label: 'Duplicate (unavailable)',
icon: ,
disabled: true,
onClick: fn(),
},
{ id: 'share', label: 'Share', icon: , onClick: fn() },
],
},
],
},
};
/**
* A row can carry a secondary icon action — a 40px button on the right with a
* divider that is independently clickable (e.g. "open in new tab").
*/
export const WithSecondaryAction: Story = {
args: {
groups: [
{
items: [
{
id: 'doc',
label: 'Open document',
icon: ,
onClick: fn(),
iconAction: {
icon: ,
'aria-label': 'Open in new tab',
href: 'https://example.com',
openInNewTab: true,
},
},
],
},
],
},
};
const dropdownGroups: ActionsMenuGroup[] = [
{
id: 'primary',
separator: true,
items: [
{ id: 'view', label: 'View details', icon: , onClick: fn() },
{ id: 'edit', label: 'Edit', icon: , onClick: fn() },
{ id: 'duplicate', label: 'Duplicate', icon: , onClick: fn() },
],
},
{
id: 'destructive',
items: [
{
id: 'delete',
label: 'Delete',
icon: ,
danger: true,
onClick: fn(),
},
],
},
];
/**
* `ActionsMenuDropdown` attaches the menu to a default ellipsis trigger button.
*/
export const Dropdown: StoryObj = {
render: (args) => ,
args: {
groups: dropdownGroups,
onItemClick: fn(),
},
};