UNPKG

@spectrum-css/menu

Version:
640 lines (625 loc) 16.6 kB
import { default as IconStories } from "@spectrum-css/icon/stories/icon.stories.js"; import { Sizes } from "@spectrum-css/preview/decorators"; import { disableDefaultModes } from "@spectrum-css/preview/modes"; import { isActive, isDisabled, isFocused, isHovered, isSelected, size } from "@spectrum-css/preview/types"; import metadata from "../dist/metadata.json"; import packageJson from "../package.json"; import { MenuItemGroup, MenuTraySubmenu, MenuWithVariants } from "./menu.test.js"; import { DisabledItemGroup, OverflowGroup, SelectionGroup, SubmenuInPopover, Template } from "./template.js"; /** * A menu is used for creating a menu list. The various elements inside a menu can be: a menu group, a menu item, or a * menu divider. Often a menu will appear in a popover so that it displays as a toggling menu. */ export default { title: "Menu", component: "Menu", argTypes: { selectionMode: { name: "Selection mode", description: "Determines whether items in the menu can be selected, and how many", type: { name: "string", required: true }, table: { type: { summary: "string" }, category: "Selection", }, options: ["none", "single", "multiple"], control: "select", }, size: size(["s", "m", "l", "xl"]), shouldTruncate: { name: "Truncate menu item label", type: { name: "boolean" }, table: { type: { summary: "boolean" }, category: "Component", }, control: "boolean", }, hasDividers: { name: "Has dividers", description: "Shows dividers between sections", table: { type: { summary: "boolean" }, category: "Component", }, control: "boolean", }, isTraySubmenu: { name: "Is tray submenu", type: { name: "boolean" }, table: { type: { summary: "boolean" }, category: "Component", }, control: "boolean", }, labelledby: { table: { disable: true } }, items: { table: { disable: true } }, role: { table: { disable: true } }, subrole: { table: { disable: true } }, }, args: { rootClass: "spectrum-Menu", selectionMode: "none", size: "m", shouldTruncate: false, role: "listbox", subrole: "option", hasDividers: false, items: [ { label: "Edit" }, { label: "Select Inverse" }, { label: "Save Selection" }, ], }, parameters: { actions: { handles: ["click .spectrum-Menu-item"], }, docs: { story: { height: "300px" } }, design: { type: "figma", url: "https://www.figma.com/design/Mngz9H7WZLbrCvGQf3GnsY/S2-%2F-Desktop?node-id=37252-553", }, packageJson, metadata, }, }; export const Default = MenuWithVariants.bind({}); Default.tags = ["!autodocs"]; Default.argTypes = { isTraySubmenu: { table: { disable: true } }, }; Default.args = { items: [ { idx: 1, heading: "Menu header - Menu with icons", id: "menu-heading-with-icons", items: [ { label: "Default menu item", iconName: "Export" }, { label: "Focused menu item", iconName: "FolderOpen", isFocused: true, isActive: true, }, { label: "A menu item with a longer label that causes the text to wrap to the next line", iconName: "Send", }, { label: "Menu item with no icon", }, { label: "Disabled menu item", iconName: "Share", isDisabled: true, }, ], }, { type: "divider" }, { idx: 2, heading: "Menu header - With descriptions and icons", id: "menu-heading-short-desc", items: [ { label: "Menu item with description", description: "Short description", }, { label: "Selected item", description: "This item is checked if single-select or multi-select mode is turned on", isSelected: true, }, { label: "Selected item with icon", iconName: "Cloud", description: "This item is checked if single-select or multi-select mode is turned on", isSelected: true, }, ], }, { type: "divider" }, { idx: 3, heading: "Menu header - With actions, icons, short descriptions, and values and longer header text that wraps", id: "menu-heading-desc-icon-value", hasActions: true, items: [ { label: "Menu item with action and a longer label that truncates if it is long enough to truncate", iconName: "Cut", description: "This item has a switch if multi-select mode is turned on.", }, { label: "Menu item with action", iconName: "Copy", description: "In multi-select mode, this item will be switched on. In single-select mode, this item will be checked.", isSelected: true, }, { label: "Menu item with action and value", iconName: "Paste", description: "This item has a value. If multi-select mode is turned on, it also has a switch and the value can be used to label the switch.", value: "⌘ C", }, { label: "Disabled menu item with action", iconName: "Archive", description: "Disabled menu item with description and icon", isDisabled: true, }, ], }, { idx: 4, heading: "Menu header - These menu items have drill-ins for a submenu", id: "menu-heading-drillin", items: [ { label: "Menu item with drill-in", isDrillIn: true, }, { label: "Menu item with drill-in and open submenu (not rendered)", isDrillIn: true, isOpen: true, }, { label: "Menu item with drill-in and value", isDrillIn: true, value: "Value", }, { label: "Disabled menu item with drill-in", isDrillIn: true, isDisabled: true, } ], }, ], }; /** * When a menu is displayed within a tray, a submenu will replace the tray content when the parent menu item is * selected. A submenu displays a back button (labeled by the title of the parent item) at the top of the tray to * return the user to the previous level of the menu. The back arrow size scale used with the various menu sizes are * small: 200, medium: 300, large: 400, and extra large: 500. */ export const TraySubmenu = MenuTraySubmenu.bind({}); TraySubmenu.storyName = "Submenu in tray"; TraySubmenu.argTypes = { selectionMode: { table: { disable: true } }, hasDividers: { table: { disable: true } }, }; TraySubmenu.args = { selectionMode: "multiple", isTraySubmenu: true, items: [ { heading: "Snap to", items: [ { label: "Guides", isSelected: true, }, { label: "Grid", }, { label: "Rulers", }, ] } ], }; TraySubmenu.parameters = { layout: "fullscreen", docs: { story: { inline: false, height: "300px", } }, viewport: { defaultViewport: "mobile2" }, }; export const MenuItem = MenuItemGroup.bind({}); MenuItem.tags = ["!autodocs"]; MenuItem.argTypes = { isDisabled, isActive, isFocused, isHovered, isSelected: { ...isSelected, description: "Used with single or multi-select mode turned on", }, label: { name: "Label", type: { name: "string" }, table: { type: { summary: "string" }, category: "Content", }, }, description: { name: "Description", description: "Additional information about the menu item", type: { name: "string" }, table: { type: { summary: "string" }, category: "Content", }, }, value: { name: "Value", description: "Value of the menu item", type: { name: "string" }, table: { type: { summary: "string" }, category: "Content", }, }, iconName: { ...(IconStories?.argTypes?.iconName ?? {}), if: false, }, hasActions: { name: "Has switches", description: "If multiple selection is enabled, show switches instead of checkboxes to show which items have been selected", type: { name: "boolean" }, table: { type: { summary: "boolean" }, category: "Selection", }, control: "boolean", if: { arg: "selectionMode", eq: "multiple" }, }, // These settings are not used in the MenuItem story hasDividers: { table: { disable: true } }, isTraySubmenu: { table: { disable: true } }, }; MenuItem.args = { label: "Start a chat", iconName: "Chat", description: "Menu item description", value: "⌘ N", isDisabled: false, isActive: false, isFocused: false, isHovered: false, isSelected: false, hasActions: false, }; MenuItem.parameters = { design: { type: "figma", url: "https://www.figma.com/design/Mngz9H7WZLbrCvGQf3GnsY/S2-%2F-Desktop?node-id=37252-2242&node-type=frame&t=Kcz7zeePp3PeRusJ-11", }, }; /** * This option will display submenus in a collapsed, nested format within the parent menu’s container. It can be used * for both popover and tray container styles. * * When displaying collapsible menu items in a tray, the tray should have a fixed height in order to prevent * disorienting behavior when the items are collapsed or expanded. */ export const Collapsible = Template.bind({}); Collapsible.argTypes = { selectionMode: { table: { disable: true } }, hasDividers: { table: { disable: true } }, isTraySubmenu: { table: { disable: true } }, }; Collapsible.parameters = { chromatic: { disableSnapshot: true }, }; Collapsible.args = { shouldTruncate: true, items: [ { label: "Web Design", iconName: "DesktopAndMobile", isCollapsible: true, isOpen: true, items: [ { label: "Web Large", itemValue: "1920 x 1080", }, { label: "Web Medium", itemValue: "1366 x 768", }, { label: "Web Small", itemValue: "1280 x 800", }, ], }, { label: "Mobile", isCollapsible: true, isOpen: true, items: [ { label: "Web Large", itemValue: "1920 x 1080", }, { label: "Web Medium", itemValue: "1366 x 768", }, { label: "Web Small", itemValue: "1280 x 800", }, ], }, { label: "Tablet", iconName: "DeviceTablet", isCollapsible: true, items: [ { label: "Defaults to not visible within closed item" }, ], }, { label: "Social Media", iconName: "ShareAndroid", isCollapsible: true, items: [ { label: "Defaults to not visible within closed item" }, ], }, { label: "Watches", iconName: "Watch", isCollapsible: true, items: [ { label: "Defaults to not visible within closed item" }, ], }, ], }; // ********* DOCS ONLY ********* // /** * A menu item should always have a label that clearly describes the action or option that it represents. A menu item * can also display a related value in the value area. Examples of values include the selected option from a submenu, a * keyboard shortcut for the action, or other content that clarifies the menu item. When necessary, menu items may also * display an icon and additional description text. * * Menu sizes should correspond to the size of the menu trigger component (such as an * [action button](?path=/docs/components-action-button--docs)). Similarly, any components displayed inside a menu item * (such as a [switch](?path=/docs/components-switch--docs)) must also be of the same size. */ export const Sizing = (args, context) => Sizes({ Template, withHeading: false, withBorder: false, ...args, }, context); Sizing.storyName = "Default"; Sizing.tags = ["!dev"]; Sizing.args = { items: [ { idx: 1, label: "Menu item", }, { idx: 2, label: "Menu item with icon", iconName: "Cloud", }, { idx: 3, label: "Menu item with optional description", description: "Short description of menu item", }, { idx: 4, label: "Menu item with value", value: "Value", }, { idx: 5, label: "Menu item with icon and description", description: "Short description of menu item", iconName: "Cloud", }, ] }; Sizing.parameters = { chromatic: { disableSnapshot: true }, }; /** * When a menu item contains a submenu, a drill-in chevron will appear at the end of the menu item to show that a * submenu is available. */ export const DrillInChevron = Template.bind({}); DrillInChevron.storyName = "Submenu drill-in chevron"; DrillInChevron.tags = ["!dev"]; DrillInChevron.parameters = { chromatic: { disableSnapshot: true }, }; DrillInChevron.args = { items: [ { label: "Menu item (with submenu)", isDrillIn: true, }, { label: "Menu item", }, { label: "Menu item", } ], }; export const PopoverSubmenu = SubmenuInPopover.bind({}); PopoverSubmenu.storyName = "Submenu in popover"; PopoverSubmenu.tags = ["!dev"]; PopoverSubmenu.parameters = { layout: "padded", chromatic: { disableSnapshot: true }, }; /** * A menu section has the options of single selection, multiple selection, or having no selection. By default, menu * items have no selection, and perform an action on press. * * For single selection menu sections, menu items show a single checkmark to indicate the selected item. Multiple * selection menu sections display checkboxes or switches beside each menu item. */ export const MenuItemSelection = SelectionGroup.bind({}); MenuItemSelection.storyName = "Selection of menu items"; MenuItemSelection.tags = ["!dev"]; MenuItemSelection.parameters = { chromatic: { disableSnapshot: true }, }; /** * The last item in each of these menus is disabled. A menu item in a disabled state shows that an option exists, but * is not available in that circumstance. This state can be used to maintain layout continuity and to communicate that * an action may become available later. */ export const DisabledItems = DisabledItemGroup.bind({}); DisabledItems.storyName = "Disabled items"; DisabledItems.tags = ["!dev"]; DisabledItems.parameters = { chromatic: { disableSnapshot: true }, }; /** * When a menu item’s label or description exceed the available horizontal space, the default behavior is to wrap the * text to a new line. * * Menu item labels and headings can be truncated with an ellipsis by using the `spectrum-Menu-itemLabel--truncate` * class. Truncation can only occur if the menu has a set `inline-size` or `max-inline-size`, or it is constrained by * the width of its parent element(s). For demonstration purposes, a `max-inline-size` is set on the following menu * examples. When text is truncated, it should be revealed with a tooltip on hover (not displayed here). * * Truncation can be used in conjunction with icons, values, and drill-ins in a menu item. Note that descriptions * within a menu item always wrap by default and do not have a truncation option. */ export const TextOverflow = OverflowGroup.bind({}); TextOverflow.storyName = "Text overflow"; TextOverflow.tags = ["!dev"]; TextOverflow.parameters = { chromatic: { disableSnapshot: true }, }; TextOverflow.args = { customStyles: { "max-inline-size": "150px", } }; // story used in Picker component as well as docs page /** * Menu sections contain groupings of related menu items. Dividers appear between sections when two or more sections * are used within the same menu. */ export const WithDividers = Template.bind({}); WithDividers.storyName = "Sections with dividers"; WithDividers.tags = ["!dev"]; WithDividers.parameters = { chromatic: { disableSnapshot: true }, }; WithDividers.args = { items: [ { label: "Deselect" }, { label: "Select inverse" }, { label: "Feather" }, { label: "Select and mask" }, { type: "divider" }, { label: "Save selection" }, { label: "Make work path", isDisabled: true }, ], hasDividers: true, }; /** * Use a section header when a menu section requires a descriptor. Section headers are helpful when two or more * sections differ in their functionality or relationships. */ export const WithDividersAndHeaders = Template.bind({}); WithDividersAndHeaders.storyName = "Sections with dividers and headers"; WithDividersAndHeaders.tags = ["!dev"]; WithDividersAndHeaders.parameters = { chromatic: { disableSnapshot: true }, }; WithDividersAndHeaders.args = { hasDividers: true, selectionMode: "single", items: [ { idx: 1, heading: "Tools", id: "menu-tools", selectionMode: "single", items: [ { label: "Marquee", isSelected: true, iconName: "Selection", }, { label: "Add", iconName: "SelectAdd", }, { label: "Subtract", iconName: "SelectSubtract", }, ] }, { type: "divider" }, { idx: 2, heading: "Actions", id: "menu-actions", selectionMode: "single", items: [ { label: "Deselect", iconName: "Deselect", isDisabled: true, } ] } ], }; // ********* VRT ONLY ********* // export const WithForcedColors = MenuItemGroup.bind({}); WithForcedColors.tags = ["!autodocs", "!dev"]; WithForcedColors.parameters = { chromatic: { forcedColors: "active", modes: disableDefaultModes }, };