@spaced-out/genesis-mcp 1.0.43 → 1.0.44

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "metadata": {
3
- "buildDate": "2026-08-17T11:30:27.986Z",
4
- "version": "0.6.28",
3
+ "buildDate": "2026-08-20T09:12:22.313Z",
4
+ "version": "0.6.29",
5
5
  "designSystemPath": "/home/runner/work/ui-design-system/ui-design-system"
6
6
  },
7
7
  "components": {
@@ -1617,11 +1617,11 @@
1617
1617
  "files": {
1618
1618
  "main": {
1619
1619
  "path": "Menu.d.ts",
1620
- "content": "import * as React from 'react';\nimport type { Flow } from 'flow-to-typescript-codemod';\nimport type { MenuClassNames, MenuLabelTooltip } from '../../types/menu';\nimport type { IconType } from '../../components/Icon/Icon';\ntype ClassNames = MenuClassNames;\ntype OptionClassNames = Readonly<{\n wrapper?: string;\n}>;\nexport type Virtualization = Readonly<{\n enable: boolean;\n itemHeight?: number;\n menuHeight?: number;\n}>;\nexport interface MenuOption {\n key: string;\n classNames?: OptionClassNames;\n label?: string;\n secondaryLabel?: string;\n customComponent?: React.ReactNode;\n iconLeft?: string;\n iconLeftType?: IconType;\n iconRight?: string;\n iconRightType?: IconType;\n disabled?: boolean;\n optionSize?: MenuSizeTypes;\n optionVariant?: MenuOptionsVariant;\n keepMenuOpenOnOptionSelect?: boolean;\n indeterminate?: boolean;\n testId?: string;\n}\nexport interface BaseMenuProps {\n onSelect?: (option: MenuOption, arg2?: React.SyntheticEvent<HTMLElement> | null | undefined) => unknown;\n selectedOption?: MenuOption | null | undefined;\n optionsVariant?: MenuOptionsVariant;\n selectedKeys?: Array<string>;\n classNames?: ClassNames;\n size?: MenuSizeTypes;\n width?: string;\n menuDisabled?: boolean;\n isFluid?: boolean;\n onTabOut?: () => unknown;\n allowSearch?: boolean;\n resolveLabel?: (option: MenuOption) => string | React.ReactNode;\n resolveSecondaryLabel?: (option: MenuOption) => string | React.ReactNode;\n virtualization?: Virtualization;\n header?: React.ReactNode;\n footer?: React.ReactNode;\n showResultText?: boolean;\n staticLabels?: {\n RESULT?: string;\n RESULTS?: string;\n SEARCH_PLACEHOLDER?: string;\n };\n showLabelTooltip?: MenuLabelTooltip;\n allowWrap?: boolean;\n testId?: string;\n}\nexport interface MenuOptionTypes {\n options?: Array<MenuOption>;\n composeOptions?: Array<Array<MenuOption>>;\n groupTitleOptions?: Array<MenuGroupTitleOption>;\n}\nexport type MenuSizeTypes = 'medium' | 'small';\nexport type MenuOptionsVariant = 'checkbox' | 'radio' | 'normal';\nexport interface MenuGroupTitleOption {\n groupTitle?: React.ReactNode;\n options?: Array<MenuOption>;\n showLineDivider?: boolean;\n}\nexport interface MenuProps extends BaseMenuProps, MenuOptionTypes {\n}\nexport interface RenderOptionProps extends Omit<MenuProps, 'searchText'> {\n searchText?: string;\n}\nexport declare const Menu: Flow.AbstractComponent<MenuProps, HTMLDivElement>;\nexport {};\n//# sourceMappingURL=Menu.d.ts.map"
1620
+ "content": "import * as React from 'react';\nimport type { Flow } from 'flow-to-typescript-codemod';\nimport type { MenuClassNames, MenuLabelTooltip } from '../../types/menu';\nimport type { IconType } from '../../components/Icon/Icon';\ntype ClassNames = MenuClassNames;\ntype OptionClassNames = Readonly<{\n wrapper?: string;\n}>;\nexport type Virtualization = Readonly<{\n enable: boolean;\n /**\n * Height of an option row. A number applies to every row; pass a function\n * for per-row heights (e.g. taller rows for options with descriptions).\n * `index` is the position of the option in the filtered option list —\n * group title rows are not counted.\n */\n itemHeight?: number | ((option: MenuOption, index: number) => number);\n /**\n * Height of a group title row when virtualizing `groupTitleOptions`.\n * A number applies to every title; pass a function for per-title heights\n * (e.g. taller custom title nodes).\n */\n groupTitleHeight?: number | ((groupTitle: React.ReactNode, groupIndex: number) => number);\n menuHeight?: number;\n}>;\nexport interface MenuOption {\n key: string;\n classNames?: OptionClassNames;\n label?: string;\n secondaryLabel?: string;\n customComponent?: React.ReactNode;\n iconLeft?: string;\n iconLeftType?: IconType;\n iconRight?: string;\n iconRightType?: IconType;\n disabled?: boolean;\n optionSize?: MenuSizeTypes;\n optionVariant?: MenuOptionsVariant;\n keepMenuOpenOnOptionSelect?: boolean;\n indeterminate?: boolean;\n testId?: string;\n}\nexport interface BaseMenuProps {\n onSelect?: (option: MenuOption, arg2?: React.SyntheticEvent<HTMLElement> | null | undefined) => unknown;\n selectedOption?: MenuOption | null | undefined;\n optionsVariant?: MenuOptionsVariant;\n selectedKeys?: Array<string>;\n classNames?: ClassNames;\n size?: MenuSizeTypes;\n width?: string;\n menuDisabled?: boolean;\n isFluid?: boolean;\n onTabOut?: () => unknown;\n allowSearch?: boolean;\n resolveLabel?: (option: MenuOption) => string | React.ReactNode;\n resolveSecondaryLabel?: (option: MenuOption) => string | React.ReactNode;\n virtualization?: Virtualization;\n header?: React.ReactNode;\n footer?: React.ReactNode;\n showResultText?: boolean;\n staticLabels?: {\n RESULT?: string;\n RESULTS?: string;\n SEARCH_PLACEHOLDER?: string;\n };\n showLabelTooltip?: MenuLabelTooltip;\n allowWrap?: boolean;\n testId?: string;\n}\nexport interface MenuOptionTypes {\n options?: Array<MenuOption>;\n composeOptions?: Array<Array<MenuOption>>;\n groupTitleOptions?: Array<MenuGroupTitleOption>;\n}\nexport type MenuSizeTypes = 'medium' | 'small';\nexport type MenuOptionsVariant = 'checkbox' | 'radio' | 'normal';\nexport interface MenuGroupTitleOption {\n groupTitle?: React.ReactNode;\n options?: Array<MenuOption>;\n showLineDivider?: boolean;\n}\nexport interface MenuProps extends BaseMenuProps, MenuOptionTypes {\n}\nexport interface RenderOptionProps extends Omit<MenuProps, 'searchText'> {\n searchText?: string;\n}\nexport declare const Menu: Flow.AbstractComponent<MenuProps, HTMLDivElement>;\nexport {};\n//# sourceMappingURL=Menu.d.ts.map"
1621
1621
  },
1622
1622
  "story": {
1623
1623
  "path": "Menu.stories.tsx",
1624
- "content": "import * as React from 'react';\n\nimport {difference, union} from 'lodash';\n\nimport {getFilteredOptionsFromSearchText} from 'src/utils/menu';\n\nimport {Avatar} from 'src/components/Avatar';\nimport {Button, UnstyledButton} from 'src/components/Button';\nimport {Icon} from 'src/components/Icon';\nimport type {\n MenuGroupTitleOption,\n MenuOption,\n MenuProps,\n} from 'src/components/Menu/Menu';\nimport {Menu} from 'src/components/Menu/Menu';\nimport {SearchInput} from 'src/components/SearchInput';\nimport {\n ButtonTextMedium,\n ButtonTextSmallUnderline,\n FormLabelMedium,\n FormLabelSmall,\n SubTitleSmall,\n} from 'src/components/Text';\nimport {Tooltip} from 'src/components/Tooltip';\nimport {Truncate} from 'src/components/Truncate';\n\nimport css from 'src/components/Menu/Menu.stories.module.css';\n\n\nconst textOptions: MenuOption[] = [\n {key: '1', label: 'Option one'},\n {key: '2', label: 'Option two with some extra words to increase length'},\n {\n key: '3',\n label:\n 'Option three with more content to demonstrate truncation and test three lines wrap.',\n },\n {\n key: '4',\n label:\n 'Option four very long option that would truncate and now it is even longer than before to ensure truncation happens',\n },\n {key: '5', label: 'Option five(disabled)', disabled: true},\n {\n key: '6',\n label: 'Option six(disabled) very long option that would truncate',\n disabled: true,\n },\n {key: '7', label: 'Option Seven'},\n {key: '8', label: 'Option Eight'},\n {key: '9', label: 'Option Nine'},\n {key: '10', label: 'Option Ten'},\n {key: '11', label: 'Option Eleven'},\n {key: '12', label: 'Option Twelve'},\n {key: '13', label: 'Option Thirteen'},\n];\n\nconst checkboxOptions: MenuOption[] = [\n {key: '1', label: 'Option one', indeterminate: true},\n {key: '2', label: 'Option two'},\n {key: '3', label: 'Option three', indeterminate: true},\n {\n key: '4',\n label: 'Option four very long option that would truncate',\n },\n {key: '5', label: 'Option five(disabled)', disabled: true},\n {\n key: '6',\n label: 'Option six(disabled) very long option that would truncate',\n disabled: true,\n },\n {key: '7', label: 'Option Seven'},\n];\n\nconst textOptionsWithSecondaryLabel: MenuOption[] = [\n {key: '1', label: 'Option one', secondaryLabel: 'Sub Text'},\n {key: '2', label: 'Option two', secondaryLabel: 'Sub Text'},\n {key: '3', label: 'Option three', secondaryLabel: 'Sub Text'},\n {\n key: '4',\n label: 'Option four very long option that would truncate',\n secondaryLabel: 'Option four very long option that would truncate',\n },\n {\n key: '5',\n label: 'Option five(disabled)',\n disabled: true,\n secondaryLabel: 'Sub Text',\n },\n {\n key: '6',\n label: 'Option six(disabled) very long option that would truncate',\n disabled: true,\n secondaryLabel: 'Sub Text',\n },\n {key: '7', label: 'Option Seven', secondaryLabel: 'Sub Text'},\n {key: '8', label: 'Option Eight', secondaryLabel: 'Sub Text'},\n {key: '9', label: 'Option Nine', secondaryLabel: 'Sub Text'},\n];\n\nconst iconLeftOptions: MenuOption[] = [\n {\n key: '1',\n label: 'Option one',\n iconLeft: 'coffee',\n },\n {key: '2', label: 'Option two', iconLeft: 'user'},\n {\n key: '4',\n label: 'Option four very long option that would truncate',\n iconLeft: 'face-party',\n },\n {\n key: '5',\n label: 'Option five(disabled)',\n disabled: true,\n iconLeft: 'flag',\n },\n {\n key: '6',\n label: 'Option six(disabled) very long option that would truncate',\n disabled: true,\n iconLeft: 'camera',\n },\n];\n\nconst coloredOptions: MenuOption[] = [\n {\n key: '1',\n label: 'Edit',\n iconLeft: 'pen',\n },\n {\n key: '2',\n label: 'Delete',\n iconLeft: 'trash',\n classNames: {wrapper: css.dangerText},\n },\n];\n\nconst iconRightOptions: MenuOption[] = [\n {key: '1', label: 'Option one', iconRight: 'coffee'},\n {key: '2', label: 'Option two', iconRight: 'user'},\n {\n key: '4',\n label: 'Option four very long option that would truncate',\n iconRight: 'face-party',\n },\n {\n key: '5',\n label: 'Option five(disabled)',\n disabled: true,\n iconRight: 'flag',\n },\n {\n key: '6',\n label: 'Option six(disabled) very long option that would truncate',\n disabled: true,\n iconRight: 'camera',\n },\n];\n\nconst iconLeftRightOptions: MenuOption[] = [\n {key: '1', label: 'Option one', iconRight: 'coffee', iconLeft: 'coffee'},\n {key: '2', label: 'Option two', iconRight: 'user', iconLeft: 'coffee'},\n {\n key: '4',\n label: 'Option four very long option that would truncate',\n iconRight: 'face-party',\n iconLeft: 'coffee',\n },\n {\n key: '5',\n label: 'Option five(disabled)',\n disabled: true,\n iconRight: 'flag',\n iconLeft: 'coffee',\n },\n {\n key: '6',\n label: 'Option six(disabled) very long option that would truncate',\n disabled: true,\n iconRight: 'camera',\n iconLeft: 'coffee',\n },\n];\n\nexport default {\n tags: ['autodocs'],\n title: 'Components/Menu',\n argTypes: {\n resolveLabel: {\n description:\n 'A function that resolves the label for a MenuOption. This is useful when you want to customize the label of an option based on some condition',\n table: {\n type: {\n summary: '(option: MenuOption) => string | React.Node',\n },\n },\n },\n resolveSecondaryLabel: {\n description:\n 'A function that resolves the secondary label for a MenuOption. This is useful when you want to customize the secondary label of an option based on some condition',\n table: {\n type: {\n summary: '(option: MenuOption) => string | React.Node',\n },\n },\n },\n onSelect: {\n description:\n '**onSelect** handler is triggered when there is a change in selected option',\n action: 'clicked',\n table: {\n type: {\n summary:\n '(option: MenuOption, ?SyntheticEvent<HTMLElement>) => mixed',\n },\n },\n },\n onTabOut: {\n description:\n '**onTabOut** callback is triggered when the user navigates outside of the menu using the tab key',\n action: 'tabout',\n table: {\n type: {summary: '() => mixed'},\n },\n },\n options: {\n description: `list of Options to be displayed in the menu.\n Menu component accepts options / composeOptions / groupTitleOptions whatever is available first and not all.`,\n control: {\n type: 'object',\n },\n table: {\n type: {\n summary: `Array<{\n key?: string,\n label: React.Node,\n customComponent?: React.Node,\n secondaryLabel?: React.Node,\n iconLeft?: string,\n iconLeftType?: IconType,\n iconRight?: string,\n iconRightType?: IconType,\n disabled?: boolean,\n optionSize?: MenuSizeTypes,\n indeterminate?: boolean,\n }>`,\n },\n },\n },\n composeOptions: {\n description: `list of list of Options are grouped and displayed in the menu.\n Menu component accepts options / composeOptions / groupTitleOptions whatever is available first and not all.`,\n control: {\n type: 'object',\n },\n table: {\n type: {\n summary: `Array<Array<{\n key?: string,\n label: React.Node,\n customComponent?: React.Node,\n secondaryLabel?: React.Node,\n iconLeft?: string,\n iconLeftType?: IconType,\n iconRight?: string,\n iconRightType?: IconType,\n disabled?: boolean,\n optionSize?: MenuSizeTypes,\n indeterminate?: boolean,\n }>>`,\n },\n },\n },\n groupTitleOptions: {\n description: `List of Options are grouped with a title(each group contains a set of options) and displayed in the menu.\n Menu component accepts options / composeOptions / groupTitleOptions whatever is available first and not all.`,\n control: {\n type: 'object',\n },\n table: {\n type: {\n summary: `Array<{\n groupTitle?: React.Node,\n options?: Array<MenuOption>\n }>`,\n },\n },\n },\n selectedOption: {\n description: `Used in cases where you want to pre-select an option as soon as the component renders.\n Selection is triggered when the selected option exactly matches one of the passed option.\n **Note:** This prop would be deprecated soon. Please use \\`selectedKeys\\` instead\n `,\n control: {\n type: 'object',\n },\n table: {\n type: {summary: 'MenuOption'},\n },\n },\n optionsVariant: {\n description: 'Menu supports multiple options variant',\n control: {\n type: 'select',\n },\n options: ['checkbox', 'radio', 'normal'],\n table: {\n type: {summary: 'enum'},\n defaultValue: {summary: 'normal'},\n },\n },\n selectedKeys: {\n description:\n 'Selected keys corresponding to options. These options are shown as selected in UI',\n control: {\n type: 'array',\n },\n table: {\n type: {summary: 'Array<string>'},\n },\n },\n classNames: {\n description:\n 'External classnames to be applied. Wrapper gets applied to Menu wrapper and option gets applied to each option in the menu.',\n control: {\n type: 'object',\n },\n table: {\n type: {\n summary:\n '{wrapper?: string, option?: string, groupTitle?: string, optionTextContainer?: string, optionTextLabel?: string, header?: string, footer?: string}',\n },\n },\n },\n size: {\n description:\n 'Menu supports only two size variants namely small and medium. `small` < `medium`',\n control: {\n type: 'radio',\n },\n options: ['small', 'medium'],\n table: {\n type: {summary: 'enum'},\n defaultValue: {summary: 'medium'},\n },\n },\n width: {\n description:\n 'Manual width control. This overrides the default width considerations of Menu',\n control: {\n type: 'text',\n },\n table: {\n type: {summary: 'string'},\n },\n },\n menuDisabled: {\n description: 'if **true**, the option is disabled',\n table: {\n type: {summary: 'boolean'},\n defaultValue: {summary: false},\n },\n },\n isFluid: {\n description:\n 'if **true**, the component would take the width of the container it is placed in',\n control: {\n type: 'boolean',\n },\n table: {\n type: {summary: 'boolean'},\n defaultValue: {summary: false},\n },\n },\n allowSearch: {\n description: `if **true**, the component would show a search input on top of menu.\n This is a simple **static search**\n which filter options by **matching search text to option label**`,\n control: {\n type: 'boolean',\n },\n table: {\n type: {summary: 'boolean'},\n defaultValue: {summary: false},\n },\n },\n virtualization: {\n description: `If **Enabled**, the options will only be rendered when they are within the component's viewport. **menuHeight and itemHeight** accept numerical input, interpreted as pixel values.`,\n control: {\n type: 'object',\n },\n table: {\n type: {\n summary:\n '{enable: boolean, menuHeight?: number, itemHeight?: number}',\n },\n },\n },\n header: {\n description: 'Custom Header For the Menu',\n table: {\n type: {\n summary: 'React.Node',\n },\n },\n },\n footer: {\n description: 'Custom Header For the Menu',\n table: {\n type: {\n summary: 'React.Node',\n },\n },\n },\n showResultText: {\n description: `if **true**, shows result text below **search input**.\n **Note:** For the result text to be displayed below the search input, please ensure that allowSearch is set to true.`,\n control: {\n type: 'boolean',\n },\n table: {\n type: {summary: 'boolean'},\n defaultValue: {summary: true},\n },\n },\n staticLabels: {\n description:\n 'External labels to be assigned to static text displayed in the UI.',\n control: {\n type: 'object',\n },\n table: {\n type: {\n summary: '{[key: string]: string}',\n },\n defaultValue: {\n summary: `{RESULT: 'result', RESULTS: 'results', SEARCH_PLACEHOLDER: 'Search...'}`,\n },\n },\n },\n showLabelTooltip: {\n description: 'Show Tooltip on the label of the menu option if truncated',\n control: {\n type: 'object',\n },\n table: {\n type: {summary: 'MenuLabelTooltip'},\n },\n },\n allowWrap: {\n description:\n 'Allows the label to wrap up to 3 lines. If truncated, a tooltip will display up to 10 lines.',\n control: {\n type: 'boolean',\n },\n table: {\n type: {summary: 'boolean'},\n defaultValue: {summary: false},\n },\n },\n testId: {\n description: 'Test ID for the component',\n control: {\n type: 'text',\n },\n table: {\n type: {summary: 'string'},\n },\n },\n },\n parameters: {\n docs: {\n subtitle: 'Generates a Menu component',\n description: {\n component: `\n\\`\\`\\`js\nimport { Menu } from \"@spaced-out/ui-design-system/lib/components/Menu\";\n\\`\\`\\`\n**Menu** is a list of choices on a temporary surface. It appears on interacts with a button, or other control. This is a standalone Menu component,\nin real world use cases Menu component would be used in conjunction with other component like Button, Input, Typeahead, etc. Each option in the Menu\ncomponent is implemented as an \\`UnstyledButton\\`.\n- The component also supports custom **header** as \\`React Component\\` and this is then passed in\nthe **<a href=\"../?path=/docs/components-menu--docs\">Menu</a>** Component.\n- The component also supports custom **footer** as \\`React Component\\` and this is then passed in\nthe **<a href=\"../?path=/docs/components-menu--docs\">Menu</a>** Component. This might be useful when you need\nto save some state in the parent component.\n `,\n },\n },\n storySource: {\n componentPath: '/src/components/Menu/Menu',\n },\n },\n};\n\nexport const TextOnlyStory = (args: MenuProps) => <Menu {...args} />;\n\nTextOnlyStory.args = {\n isFluid: false,\n menuDisabled: false,\n size: 'medium',\n options: textOptions,\n selectedKeys: ['2'],\n testId: 'menu-text-only-story',\n};\nexport const TextWithTooltipStory = (args: MenuProps) => <Menu {...args} />;\n\nTextWithTooltipStory.args = {\n isFluid: false,\n menuDisabled: false,\n size: 'medium',\n options: textOptions,\n selectedKeys: ['2'],\n showLabelTooltip: {maxLines: 2},\n testId: 'menu-text-with-tooltip-story',\n};\n\nexport const MenuWithWrapStory = (args: MenuProps) => {\n const [selectedKeys, setSelectedKeys] = React.useState(\n args.selectedKeys || [],\n );\n const selectKeys = (option: MenuOption) => {\n setSelectedKeys([option.key]);\n };\n\n return <Menu {...args} selectedKeys={selectedKeys} onSelect={selectKeys} />;\n};\n\nMenuWithWrapStory.args = {\n isFluid: false,\n menuDisabled: false,\n size: 'medium',\n options: textOptions,\n selectedKeys: ['2'],\n allowWrap: true,\n testId: 'menu-with-wrap-story',\n};\n\nexport const WithSearchStory = (args: MenuProps) => <Menu {...args} />;\n\nWithSearchStory.args = {\n isFluid: false,\n menuDisabled: false,\n size: 'medium',\n options: textOptions,\n allowSearch: true,\n testId: 'menu-with-search-story',\n};\n\nexport const TextAndIconLeftStory = (args: MenuProps) => <Menu {...args} />;\n\nTextAndIconLeftStory.args = {\n isFluid: false,\n menuDisabled: false,\n size: 'medium',\n options: iconLeftOptions,\n selectedKeys: ['2'],\n testId: 'menu-text-and-icon-left-story',\n};\n\n/**\n * To make one of the options colored, you can set the classNames wrapper property in the option object.\n * Eg:\n * You would need to set the color of the option and manage the hover, focus and active state yourself.\n * ### JS\n * ```\n * const coloredOptions: MenuOption[] = [\n * {\n * key: '1',\n * label: 'Edit',\n * iconLeft: 'coffee',\n * },\n * {\n * key: '2',\n * label: 'Delete',\n * iconLeft: 'trash',\n * classNames: { wrapper: css.dangerText },\n * },\n * ];\n * ```\n * ### CSS\n * ```\n * .dangerText:hover,\n * .dangerText:active,\n * .dangerText:focus {\n * color: colorTextDanger;\n * }\n * ```\n */\nexport const ColoredOptionStory = (args: MenuProps) => <Menu {...args} />;\n\nColoredOptionStory.args = {\n isFluid: false,\n menuDisabled: false,\n size: 'medium',\n options: coloredOptions,\n testId: 'menu-colored-option-story',\n};\n\nexport const TextAndIconRightStory = (args: MenuProps) => <Menu {...args} />;\n\nTextAndIconRightStory.args = {\n isFluid: false,\n menuDisabled: false,\n size: 'medium',\n options: iconRightOptions,\n selectedKeys: ['2'],\n testId: 'menu-text-and-icon-right-story',\n};\n\nexport const TextAndDualIconStory = (args: MenuProps) => <Menu {...args} />;\n\nTextAndDualIconStory.args = {\n isFluid: false,\n menuDisabled: false,\n size: 'medium',\n options: iconLeftRightOptions,\n selectedKeys: ['2'],\n testId: 'menu-text-and-dual-icon-story',\n};\n\nexport const DualTextStory = (args: MenuProps) => <Menu {...args} />;\n\nDualTextStory.args = {\n isFluid: false,\n menuDisabled: false,\n size: 'medium',\n options: textOptionsWithSecondaryLabel,\n selectedKeys: ['2'],\n testId: 'menu-dual-text-story',\n};\n\nexport const WithResolversStory = (args: MenuProps) => {\n const resolveLabel = (option: MenuOption) => (\n <div className={css.customComponent}>\n {parseInt(option.key) < 6 ? (\n <Avatar\n imageSrc={`https://cdn.sensehq.com/genesis/hashed/static/images/personas/${option.key}.png`}\n />\n ) : (\n <Avatar iconName=\"user\" />\n )}\n\n <ButtonTextMedium className={css.text} color=\"secondary\">\n <Truncate>{option.label}</Truncate>\n </ButtonTextMedium>\n </div>\n );\n\n return <Menu {...args} resolveLabel={resolveLabel} />;\n};\n\nWithResolversStory.args = {\n isFluid: false,\n menuDisabled: false,\n size: 'medium',\n options: textOptions,\n testId: 'menu-with-resolvers-story',\n};\n\nconst composeMenuOptions: Array<Array<MenuOption>> = [\n [\n {\n key: '1',\n label: 'Option one',\n secondaryLabel: 'Sub Text',\n optionSize: 'small',\n },\n {\n key: '2',\n label: 'Option two',\n secondaryLabel: 'Sub Text',\n optionSize: 'small',\n },\n {\n key: '3',\n label: 'Option three',\n secondaryLabel: 'Sub Text',\n optionSize: 'small',\n },\n ],\n [\n {\n iconLeft: 'coffee',\n key: '4',\n label: 'Option one',\n optionSize: 'medium',\n },\n ],\n [\n {\n iconLeft: 'coffee',\n key: '5',\n label: 'Option one',\n optionSize: 'medium',\n },\n ],\n];\n\nexport const ComposeMenuStory = (args: MenuProps) => <Menu {...args} />;\n\nComposeMenuStory.args = {\n isFluid: false,\n menuDisabled: false,\n size: 'medium',\n composeOptions: composeMenuOptions,\n selectedKeys: ['2'],\n testId: 'menu-compose-story',\n};\n\nconst customComponentMenuOptions: Array<MenuOption> = [\n {\n key: '1',\n customComponent: (\n <div className={css.customComponent}>\n <Avatar\n color=\"blue\"\n imageSrc=\"https://cdn.sensehq.com/genesis/hashed/static/images/personas/1.png\"\n iconName=\"face-party\"\n iconType=\"regular\"\n size=\"medium\"\n />\n <ButtonTextMedium className={css.text}>Angelina White</ButtonTextMedium>\n </div>\n ),\n },\n {\n key: '2',\n customComponent: (\n <div className={css.customComponent}>\n <Avatar\n imageSrc=\"https://cdn.sensehq.com/genesis/hashed/static/images/personas/4.png\"\n color=\"green\"\n size=\"medium\"\n text=\"John\"\n />\n <ButtonTextMedium className={css.text}>John Smith</ButtonTextMedium>\n </div>\n ),\n },\n {\n key: '3',\n\n customComponent: (\n <div className={css.customComponent}>\n <Avatar\n imageSrc=\"https://cdn.sensehq.com/genesis/hashed/static/images/personas/6.png\"\n color=\"orange\"\n size=\"medium\"\n text=\"Jacobs Marley\"\n />\n <ButtonTextMedium className={css.text}>Sarah Parker</ButtonTextMedium>\n </div>\n ),\n },\n];\n\nexport const CustomComponentMenuStory = (args: MenuProps) => <Menu {...args} />;\n\nCustomComponentMenuStory.args = {\n isFluid: false,\n menuDisabled: false,\n classNames: {option: css.optionText},\n size: 'medium',\n options: customComponentMenuOptions,\n selectedKeys: ['2'],\n testId: 'menu-custom-component-story',\n};\n\nconst groupTitleComponentMenuOptions: Array<MenuGroupTitleOption> = [\n {\n groupTitle: (\n <div className={css.titleWrapper}>\n <FormLabelSmall color=\"tertiary\">Fast Food</FormLabelSmall>\n <div className={css.infoContainer}>\n <Tooltip title=\"Hello!\" elevation=\"menu\">\n <Icon name=\"circle-info\" size=\"small\" color=\"tertiary\" />\n </Tooltip>\n </div>\n </div>\n ),\n options: [\n {\n key: '1',\n iconLeft: 'burger',\n label: 'Burger',\n },\n {\n key: '2',\n iconLeft: 'pizza',\n label: 'Pizza',\n },\n {\n key: '3',\n iconLeft: 'french-fries',\n label: 'Fries',\n },\n ],\n },\n {\n showLineDivider: true,\n groupTitle: (\n <div className={css.titleWrapper}>\n <FormLabelSmall color=\"tertiary\">Fruits</FormLabelSmall>\n <div className={css.infoContainer}>\n <Tooltip title=\"Hello!\" elevation=\"menu\">\n <Icon name=\"circle-info\" size=\"small\" color=\"tertiary\" />\n </Tooltip>\n </div>\n </div>\n ),\n options: [\n {\n key: '7',\n iconLeft: 'banana',\n label: 'Banana',\n },\n {\n key: '8',\n iconLeft: 'pear',\n label: 'Pear',\n },\n {\n key: '9',\n iconLeft: 'lemon',\n label: 'Lemon',\n },\n ],\n },\n];\n\nexport const GroupTitleMenuStory = (args: MenuProps) => (\n <Menu {...args} classNames={{groupTitle: css.gpTitle}} />\n);\n\nGroupTitleMenuStory.args = {\n isFluid: false,\n menuDisabled: false,\n classNames: {option: css.optionText},\n size: 'medium',\n groupTitleOptions: groupTitleComponentMenuOptions,\n selectedKeys: ['2'],\n testId: 'menu-group-title-story',\n};\n\nconst GroupTitleActionMenu = (args: MenuProps) => {\n const [searchText, setSearchText] = React.useState<string>('');\n const [selectedKeys, setSelectedKeys] = React.useState(\n args.selectedKeys || [],\n );\n const optionsFiltered = getFilteredOptionsFromSearchText(\n textOptions,\n searchText,\n );\n const optionsFilteredKeys = optionsFiltered\n .filter(({disabled}) => !disabled)\n .map(({key}) => key);\n\n React.useEffect(() => {\n setSelectedKeys(args.selectedKeys || []);\n }, [args.selectedKeys]);\n\n const selectKeys = (option: MenuOption) => {\n if (selectedKeys.includes(option.key)) {\n setSelectedKeys(difference(selectedKeys, [option.key]));\n } else {\n setSelectedKeys(union(selectedKeys, [option.key]));\n }\n };\n\n const selectAllKeys: React.MouseEventHandler<HTMLButtonElement> = () => {\n setSelectedKeys(union(selectedKeys, optionsFilteredKeys));\n };\n\n const clearSelectedKeys: React.MouseEventHandler<HTMLButtonElement> = () => {\n setSelectedKeys(difference(selectedKeys, optionsFilteredKeys));\n };\n\n return (\n <Menu\n {...args}\n optionsVariant=\"checkbox\"\n selectedKeys={selectedKeys}\n onSelect={selectKeys}\n classNames={{\n header: css.groupTitleActionHeader,\n groupTitle: css.groupTitle,\n }}\n header={\n <SearchInput\n value={searchText}\n onChange={(e) => setSearchText(e.target.value)}\n onClear={() => setSearchText('')}\n size={args.size}\n />\n }\n groupTitleOptions={[\n {\n groupTitle: (\n <>\n <FormLabelMedium color=\"tertiary\">\n {`Values (${optionsFiltered.length})`}\n </FormLabelMedium>\n <div className={css.groupTitleActions}>\n <UnstyledButton onClick={clearSelectedKeys}>\n <ButtonTextSmallUnderline className={css.groupTitleAction}>\n Clear\n </ButtonTextSmallUnderline>\n </UnstyledButton>\n <UnstyledButton onClick={selectAllKeys}>\n <ButtonTextSmallUnderline\n color=\"clickable\"\n className={css.groupTitleAction}\n >\n Select All\n </ButtonTextSmallUnderline>\n </UnstyledButton>\n </div>\n </>\n ),\n options: optionsFiltered,\n },\n ]}\n />\n );\n};\n\nexport const GroupTitleActionMenuStory = (args: MenuProps) => (\n <GroupTitleActionMenu {...args} />\n);\n\nGroupTitleActionMenuStory.args = {\n isFluid: false,\n menuDisabled: false,\n classNames: {option: css.optionText},\n size: 'medium',\n selectedKeys: ['2'],\n testId: 'menu-group-title-action-story',\n};\n\nexport const CheckboxMenuStory = (args: MenuProps) => {\n const [selectedKeys, setSelectedKeys] = React.useState(\n args.selectedKeys || [],\n );\n\n React.useEffect(() => {\n setSelectedKeys(args.selectedKeys || []);\n }, [args.selectedKeys]);\n\n const selectKeys = (option: MenuOption) => {\n if (selectedKeys.includes(option.key)) {\n setSelectedKeys(selectedKeys.filter((item) => item !== option.key));\n } else {\n setSelectedKeys([...selectedKeys, option.key]);\n }\n };\n\n return <Menu {...args} selectedKeys={selectedKeys} onSelect={selectKeys} />;\n};\n\nCheckboxMenuStory.args = {\n isFluid: false,\n menuDisabled: false,\n classNames: {option: css.optionText},\n size: 'medium',\n options: checkboxOptions,\n optionsVariant: 'checkbox',\n selectedKeys: ['1', '4'],\n testId: 'menu-checkbox-story',\n};\n\nexport const RadioMenuStory = (args: MenuProps) => {\n const [selectedKeys, setSelectedKeys] = React.useState(\n args.selectedKeys || [],\n );\n\n React.useEffect(() => {\n setSelectedKeys(args.selectedKeys || []);\n }, [args.selectedKeys]);\n\n const selectKeys = (option: MenuOption) => {\n setSelectedKeys([option.key]);\n };\n\n return <Menu {...args} selectedKeys={selectedKeys} onSelect={selectKeys} />;\n};\n\nRadioMenuStory.args = {\n isFluid: false,\n menuDisabled: false,\n classNames: {option: css.optionText},\n size: 'medium',\n options: textOptions,\n optionsVariant: 'radio',\n selectedKeys: ['1'],\n testId: 'menu-radio-story',\n};\n\nexport const MenuHeader = () => (\n <div className={css.headerComponent}>\n <SubTitleSmall>Menu Header</SubTitleSmall>\n </div>\n);\n\nexport const MenuFooter = ({\n onSave,\n onCancel,\n}: {\n onSave?: () => void;\n onCancel?: () => void;\n}) => (\n <div className={css.footerComponent}>\n <Button size=\"small\" type=\"ghost\" onClick={onCancel}>\n Cancel\n </Button>\n <Button size=\"small\" type=\"secondary\" onClick={onSave}>\n Save\n </Button>\n </div>\n);\n\nexport const WithHeaderStory = (args: MenuProps) => (\n <Menu {...args} header={<MenuHeader />} />\n);\n\nWithHeaderStory.args = {\n isFluid: false,\n menuDisabled: false,\n size: 'small',\n options: textOptions,\n selectedKeys: ['2'],\n testId: 'menu-with-header-story',\n};\n\nexport const WithFooterStory = (args: MenuProps) => (\n <Menu {...args} footer={<MenuFooter />} />\n);\n\nWithFooterStory.args = {\n isFluid: false,\n menuDisabled: false,\n size: 'small',\n options: textOptions,\n selectedKeys: ['2'],\n testId: 'menu-with-footer-story',\n};\n\nexport const WithHeaderAndFooterStory = (args: MenuProps) => {\n const [selectedKeys, setSelectedKeys] = React.useState(\n args.selectedKeys || [],\n );\n\n React.useEffect(() => {\n setSelectedKeys(args.selectedKeys || []);\n }, [args.selectedKeys]);\n\n const selectKeys = (option: MenuOption) => {\n if (selectedKeys.includes(option.key)) {\n setSelectedKeys(selectedKeys.filter((item) => item !== option.key));\n } else {\n setSelectedKeys([...selectedKeys, option.key]);\n }\n };\n\n return (\n <Menu\n {...args}\n selectedKeys={selectedKeys}\n onSelect={selectKeys}\n header={<MenuHeader />}\n footer={<MenuFooter />}\n />\n );\n};\n\nWithHeaderAndFooterStory.args = {\n isFluid: false,\n menuDisabled: false,\n classNames: {option: css.optionText},\n options: textOptions,\n optionsVariant: 'checkbox',\n selectedKeys: ['1', '4'],\n testId: 'menu-with-header-and-footer-story',\n};\nTextOnlyStory.storyName = 'Text Only';\nTextWithTooltipStory.storyName = 'Text With Tooltip';\nMenuWithWrapStory.storyName = 'Menu With Wrap';\nWithSearchStory.storyName = 'With Search';\nTextAndIconLeftStory.storyName = 'Text And Icon Left';\nColoredOptionStory.storyName = 'Colored Option';\nTextAndIconRightStory.storyName = 'Text And Icon Right';\nTextAndDualIconStory.storyName = 'Text And Dual Icon';\nDualTextStory.storyName = 'Dual Text';\nWithResolversStory.storyName = 'With Resolvers';\nComposeMenuStory.storyName = 'Compose Menu';\nCustomComponentMenuStory.storyName = 'Custom Component Menu';\nGroupTitleMenuStory.storyName = 'Group Title Menu';\nGroupTitleActionMenuStory.storyName = 'Group Title Action Menu';\nCheckboxMenuStory.storyName = 'Checkbox Menu';\nRadioMenuStory.storyName = 'Radio Menu';\nWithHeaderStory.storyName = 'With Header';\nWithFooterStory.storyName = 'With Footer';\nWithHeaderAndFooterStory.storyName = 'With Header And Footer';\n"
1624
+ "content": "import * as React from 'react';\n\nimport {difference, union} from 'lodash';\n\nimport {getFilteredOptionsFromSearchText} from 'src/utils/menu';\n\nimport {Avatar} from 'src/components/Avatar';\nimport {Button, UnstyledButton} from 'src/components/Button';\nimport {Icon} from 'src/components/Icon';\nimport type {\n MenuGroupTitleOption,\n MenuOption,\n MenuProps,\n} from 'src/components/Menu/Menu';\nimport {Menu} from 'src/components/Menu/Menu';\nimport {SearchInput} from 'src/components/SearchInput';\nimport {\n ButtonTextMedium,\n ButtonTextSmallUnderline,\n FormLabelMedium,\n FormLabelSmall,\n SubTitleSmall,\n} from 'src/components/Text';\nimport {Tooltip} from 'src/components/Tooltip';\nimport {Truncate} from 'src/components/Truncate';\n\nimport css from 'src/components/Menu/Menu.stories.module.css';\n\n\nconst textOptions: MenuOption[] = [\n {key: '1', label: 'Option one'},\n {key: '2', label: 'Option two with some extra words to increase length'},\n {\n key: '3',\n label:\n 'Option three with more content to demonstrate truncation and test three lines wrap.',\n },\n {\n key: '4',\n label:\n 'Option four very long option that would truncate and now it is even longer than before to ensure truncation happens',\n },\n {key: '5', label: 'Option five(disabled)', disabled: true},\n {\n key: '6',\n label: 'Option six(disabled) very long option that would truncate',\n disabled: true,\n },\n {key: '7', label: 'Option Seven'},\n {key: '8', label: 'Option Eight'},\n {key: '9', label: 'Option Nine'},\n {key: '10', label: 'Option Ten'},\n {key: '11', label: 'Option Eleven'},\n {key: '12', label: 'Option Twelve'},\n {key: '13', label: 'Option Thirteen'},\n];\n\nconst checkboxOptions: MenuOption[] = [\n {key: '1', label: 'Option one', indeterminate: true},\n {key: '2', label: 'Option two'},\n {key: '3', label: 'Option three', indeterminate: true},\n {\n key: '4',\n label: 'Option four very long option that would truncate',\n },\n {key: '5', label: 'Option five(disabled)', disabled: true},\n {\n key: '6',\n label: 'Option six(disabled) very long option that would truncate',\n disabled: true,\n },\n {key: '7', label: 'Option Seven'},\n];\n\nconst textOptionsWithSecondaryLabel: MenuOption[] = [\n {key: '1', label: 'Option one', secondaryLabel: 'Sub Text'},\n {key: '2', label: 'Option two', secondaryLabel: 'Sub Text'},\n {key: '3', label: 'Option three', secondaryLabel: 'Sub Text'},\n {\n key: '4',\n label: 'Option four very long option that would truncate',\n secondaryLabel: 'Option four very long option that would truncate',\n },\n {\n key: '5',\n label: 'Option five(disabled)',\n disabled: true,\n secondaryLabel: 'Sub Text',\n },\n {\n key: '6',\n label: 'Option six(disabled) very long option that would truncate',\n disabled: true,\n secondaryLabel: 'Sub Text',\n },\n {key: '7', label: 'Option Seven', secondaryLabel: 'Sub Text'},\n {key: '8', label: 'Option Eight', secondaryLabel: 'Sub Text'},\n {key: '9', label: 'Option Nine', secondaryLabel: 'Sub Text'},\n];\n\nconst iconLeftOptions: MenuOption[] = [\n {\n key: '1',\n label: 'Option one',\n iconLeft: 'coffee',\n },\n {key: '2', label: 'Option two', iconLeft: 'user'},\n {\n key: '4',\n label: 'Option four very long option that would truncate',\n iconLeft: 'face-party',\n },\n {\n key: '5',\n label: 'Option five(disabled)',\n disabled: true,\n iconLeft: 'flag',\n },\n {\n key: '6',\n label: 'Option six(disabled) very long option that would truncate',\n disabled: true,\n iconLeft: 'camera',\n },\n];\n\nconst coloredOptions: MenuOption[] = [\n {\n key: '1',\n label: 'Edit',\n iconLeft: 'pen',\n },\n {\n key: '2',\n label: 'Delete',\n iconLeft: 'trash',\n classNames: {wrapper: css.dangerText},\n },\n];\n\nconst iconRightOptions: MenuOption[] = [\n {key: '1', label: 'Option one', iconRight: 'coffee'},\n {key: '2', label: 'Option two', iconRight: 'user'},\n {\n key: '4',\n label: 'Option four very long option that would truncate',\n iconRight: 'face-party',\n },\n {\n key: '5',\n label: 'Option five(disabled)',\n disabled: true,\n iconRight: 'flag',\n },\n {\n key: '6',\n label: 'Option six(disabled) very long option that would truncate',\n disabled: true,\n iconRight: 'camera',\n },\n];\n\nconst iconLeftRightOptions: MenuOption[] = [\n {key: '1', label: 'Option one', iconRight: 'coffee', iconLeft: 'coffee'},\n {key: '2', label: 'Option two', iconRight: 'user', iconLeft: 'coffee'},\n {\n key: '4',\n label: 'Option four very long option that would truncate',\n iconRight: 'face-party',\n iconLeft: 'coffee',\n },\n {\n key: '5',\n label: 'Option five(disabled)',\n disabled: true,\n iconRight: 'flag',\n iconLeft: 'coffee',\n },\n {\n key: '6',\n label: 'Option six(disabled) very long option that would truncate',\n disabled: true,\n iconRight: 'camera',\n iconLeft: 'coffee',\n },\n];\n\nexport default {\n tags: ['autodocs'],\n title: 'Components/Menu',\n argTypes: {\n resolveLabel: {\n description:\n 'A function that resolves the label for a MenuOption. This is useful when you want to customize the label of an option based on some condition',\n table: {\n type: {\n summary: '(option: MenuOption) => string | React.Node',\n },\n },\n },\n resolveSecondaryLabel: {\n description:\n 'A function that resolves the secondary label for a MenuOption. This is useful when you want to customize the secondary label of an option based on some condition',\n table: {\n type: {\n summary: '(option: MenuOption) => string | React.Node',\n },\n },\n },\n onSelect: {\n description:\n '**onSelect** handler is triggered when there is a change in selected option',\n action: 'clicked',\n table: {\n type: {\n summary:\n '(option: MenuOption, ?SyntheticEvent<HTMLElement>) => mixed',\n },\n },\n },\n onTabOut: {\n description:\n '**onTabOut** callback is triggered when the user navigates outside of the menu using the tab key',\n action: 'tabout',\n table: {\n type: {summary: '() => mixed'},\n },\n },\n options: {\n description: `list of Options to be displayed in the menu.\n Menu component accepts options / composeOptions / groupTitleOptions whatever is available first and not all.`,\n control: {\n type: 'object',\n },\n table: {\n type: {\n summary: `Array<{\n key?: string,\n label: React.Node,\n customComponent?: React.Node,\n secondaryLabel?: React.Node,\n iconLeft?: string,\n iconLeftType?: IconType,\n iconRight?: string,\n iconRightType?: IconType,\n disabled?: boolean,\n optionSize?: MenuSizeTypes,\n indeterminate?: boolean,\n }>`,\n },\n },\n },\n composeOptions: {\n description: `list of list of Options are grouped and displayed in the menu.\n Menu component accepts options / composeOptions / groupTitleOptions whatever is available first and not all.`,\n control: {\n type: 'object',\n },\n table: {\n type: {\n summary: `Array<Array<{\n key?: string,\n label: React.Node,\n customComponent?: React.Node,\n secondaryLabel?: React.Node,\n iconLeft?: string,\n iconLeftType?: IconType,\n iconRight?: string,\n iconRightType?: IconType,\n disabled?: boolean,\n optionSize?: MenuSizeTypes,\n indeterminate?: boolean,\n }>>`,\n },\n },\n },\n groupTitleOptions: {\n description: `List of Options are grouped with a title(each group contains a set of options) and displayed in the menu.\n Menu component accepts options / composeOptions / groupTitleOptions whatever is available first and not all.`,\n control: {\n type: 'object',\n },\n table: {\n type: {\n summary: `Array<{\n groupTitle?: React.Node,\n options?: Array<MenuOption>\n }>`,\n },\n },\n },\n selectedOption: {\n description: `Used in cases where you want to pre-select an option as soon as the component renders.\n Selection is triggered when the selected option exactly matches one of the passed option.\n **Note:** This prop would be deprecated soon. Please use \\`selectedKeys\\` instead\n `,\n control: {\n type: 'object',\n },\n table: {\n type: {summary: 'MenuOption'},\n },\n },\n optionsVariant: {\n description: 'Menu supports multiple options variant',\n control: {\n type: 'select',\n },\n options: ['checkbox', 'radio', 'normal'],\n table: {\n type: {summary: 'enum'},\n defaultValue: {summary: 'normal'},\n },\n },\n selectedKeys: {\n description:\n 'Selected keys corresponding to options. These options are shown as selected in UI',\n control: {\n type: 'array',\n },\n table: {\n type: {summary: 'Array<string>'},\n },\n },\n classNames: {\n description:\n 'External classnames to be applied. Wrapper gets applied to Menu wrapper and option gets applied to each option in the menu.',\n control: {\n type: 'object',\n },\n table: {\n type: {\n summary:\n '{wrapper?: string, option?: string, groupTitle?: string, optionTextContainer?: string, optionTextLabel?: string, header?: string, footer?: string}',\n },\n },\n },\n size: {\n description:\n 'Menu supports only two size variants namely small and medium. `small` < `medium`',\n control: {\n type: 'radio',\n },\n options: ['small', 'medium'],\n table: {\n type: {summary: 'enum'},\n defaultValue: {summary: 'medium'},\n },\n },\n width: {\n description:\n 'Manual width control. This overrides the default width considerations of Menu',\n control: {\n type: 'text',\n },\n table: {\n type: {summary: 'string'},\n },\n },\n menuDisabled: {\n description: 'if **true**, the option is disabled',\n table: {\n type: {summary: 'boolean'},\n defaultValue: {summary: false},\n },\n },\n isFluid: {\n description:\n 'if **true**, the component would take the width of the container it is placed in',\n control: {\n type: 'boolean',\n },\n table: {\n type: {summary: 'boolean'},\n defaultValue: {summary: false},\n },\n },\n allowSearch: {\n description: `if **true**, the component would show a search input on top of menu.\n This is a simple **static search**\n which filter options by **matching search text to option label**`,\n control: {\n type: 'boolean',\n },\n table: {\n type: {summary: 'boolean'},\n defaultValue: {summary: false},\n },\n },\n virtualization: {\n description: `If **Enabled**, the options will only be rendered when they are within the component's viewport. **menuHeight** accepts numerical input, interpreted as pixel values. **itemHeight** accepts a number (same height for every option row) or a function \\`(option, index) => number\\` for per-row heights — \\`index\\` is the option's position in the filtered option list (group titles are not counted). **groupTitleHeight** sizes group title rows when virtualizing \\`groupTitleOptions\\`; it accepts a number or a function \\`(groupTitle, groupIndex) => number\\`.`,\n control: {\n type: 'object',\n },\n table: {\n type: {\n summary:\n '{enable: boolean, menuHeight?: number, itemHeight?: number | ((option, index) => number), groupTitleHeight?: number | ((groupTitle, groupIndex) => number)}',\n },\n },\n },\n header: {\n description: 'Custom Header For the Menu',\n table: {\n type: {\n summary: 'React.Node',\n },\n },\n },\n footer: {\n description: 'Custom Header For the Menu',\n table: {\n type: {\n summary: 'React.Node',\n },\n },\n },\n showResultText: {\n description: `if **true**, shows result text below **search input**.\n **Note:** For the result text to be displayed below the search input, please ensure that allowSearch is set to true.`,\n control: {\n type: 'boolean',\n },\n table: {\n type: {summary: 'boolean'},\n defaultValue: {summary: true},\n },\n },\n staticLabels: {\n description:\n 'External labels to be assigned to static text displayed in the UI.',\n control: {\n type: 'object',\n },\n table: {\n type: {\n summary: '{[key: string]: string}',\n },\n defaultValue: {\n summary: `{RESULT: 'result', RESULTS: 'results', SEARCH_PLACEHOLDER: 'Search...'}`,\n },\n },\n },\n showLabelTooltip: {\n description: 'Show Tooltip on the label of the menu option if truncated',\n control: {\n type: 'object',\n },\n table: {\n type: {summary: 'MenuLabelTooltip'},\n },\n },\n allowWrap: {\n description:\n 'Allows the label to wrap up to 3 lines. If truncated, a tooltip will display up to 10 lines.',\n control: {\n type: 'boolean',\n },\n table: {\n type: {summary: 'boolean'},\n defaultValue: {summary: false},\n },\n },\n testId: {\n description: 'Test ID for the component',\n control: {\n type: 'text',\n },\n table: {\n type: {summary: 'string'},\n },\n },\n },\n parameters: {\n docs: {\n subtitle: 'Generates a Menu component',\n description: {\n component: `\n\\`\\`\\`js\nimport { Menu } from \"@spaced-out/ui-design-system/lib/components/Menu\";\n\\`\\`\\`\n**Menu** is a list of choices on a temporary surface. It appears on interacts with a button, or other control. This is a standalone Menu component,\nin real world use cases Menu component would be used in conjunction with other component like Button, Input, Typeahead, etc. Each option in the Menu\ncomponent is implemented as an \\`UnstyledButton\\`.\n- The component also supports custom **header** as \\`React Component\\` and this is then passed in\nthe **<a href=\"../?path=/docs/components-menu--docs\">Menu</a>** Component.\n- The component also supports custom **footer** as \\`React Component\\` and this is then passed in\nthe **<a href=\"../?path=/docs/components-menu--docs\">Menu</a>** Component. This might be useful when you need\nto save some state in the parent component.\n `,\n },\n },\n storySource: {\n componentPath: '/src/components/Menu/Menu',\n },\n },\n};\n\nexport const TextOnlyStory = (args: MenuProps) => <Menu {...args} />;\n\nTextOnlyStory.args = {\n isFluid: false,\n menuDisabled: false,\n size: 'medium',\n options: textOptions,\n selectedKeys: ['2'],\n testId: 'menu-text-only-story',\n};\nexport const TextWithTooltipStory = (args: MenuProps) => <Menu {...args} />;\n\nTextWithTooltipStory.args = {\n isFluid: false,\n menuDisabled: false,\n size: 'medium',\n options: textOptions,\n selectedKeys: ['2'],\n showLabelTooltip: {maxLines: 2},\n testId: 'menu-text-with-tooltip-story',\n};\n\nexport const MenuWithWrapStory = (args: MenuProps) => {\n const [selectedKeys, setSelectedKeys] = React.useState(\n args.selectedKeys || [],\n );\n const selectKeys = (option: MenuOption) => {\n setSelectedKeys([option.key]);\n };\n\n return <Menu {...args} selectedKeys={selectedKeys} onSelect={selectKeys} />;\n};\n\nMenuWithWrapStory.args = {\n isFluid: false,\n menuDisabled: false,\n size: 'medium',\n options: textOptions,\n selectedKeys: ['2'],\n allowWrap: true,\n testId: 'menu-with-wrap-story',\n};\n\nexport const WithSearchStory = (args: MenuProps) => <Menu {...args} />;\n\nWithSearchStory.args = {\n isFluid: false,\n menuDisabled: false,\n size: 'medium',\n options: textOptions,\n allowSearch: true,\n testId: 'menu-with-search-story',\n};\n\nexport const TextAndIconLeftStory = (args: MenuProps) => <Menu {...args} />;\n\nTextAndIconLeftStory.args = {\n isFluid: false,\n menuDisabled: false,\n size: 'medium',\n options: iconLeftOptions,\n selectedKeys: ['2'],\n testId: 'menu-text-and-icon-left-story',\n};\n\n/**\n * To make one of the options colored, you can set the classNames wrapper property in the option object.\n * Eg:\n * You would need to set the color of the option and manage the hover, focus and active state yourself.\n * ### JS\n * ```\n * const coloredOptions: MenuOption[] = [\n * {\n * key: '1',\n * label: 'Edit',\n * iconLeft: 'coffee',\n * },\n * {\n * key: '2',\n * label: 'Delete',\n * iconLeft: 'trash',\n * classNames: { wrapper: css.dangerText },\n * },\n * ];\n * ```\n * ### CSS\n * ```\n * .dangerText:hover,\n * .dangerText:active,\n * .dangerText:focus {\n * color: colorTextDanger;\n * }\n * ```\n */\nexport const ColoredOptionStory = (args: MenuProps) => <Menu {...args} />;\n\nColoredOptionStory.args = {\n isFluid: false,\n menuDisabled: false,\n size: 'medium',\n options: coloredOptions,\n testId: 'menu-colored-option-story',\n};\n\nexport const TextAndIconRightStory = (args: MenuProps) => <Menu {...args} />;\n\nTextAndIconRightStory.args = {\n isFluid: false,\n menuDisabled: false,\n size: 'medium',\n options: iconRightOptions,\n selectedKeys: ['2'],\n testId: 'menu-text-and-icon-right-story',\n};\n\nexport const TextAndDualIconStory = (args: MenuProps) => <Menu {...args} />;\n\nTextAndDualIconStory.args = {\n isFluid: false,\n menuDisabled: false,\n size: 'medium',\n options: iconLeftRightOptions,\n selectedKeys: ['2'],\n testId: 'menu-text-and-dual-icon-story',\n};\n\nexport const DualTextStory = (args: MenuProps) => <Menu {...args} />;\n\nDualTextStory.args = {\n isFluid: false,\n menuDisabled: false,\n size: 'medium',\n options: textOptionsWithSecondaryLabel,\n selectedKeys: ['2'],\n testId: 'menu-dual-text-story',\n};\n\nexport const WithResolversStory = (args: MenuProps) => {\n const resolveLabel = (option: MenuOption) => (\n <div className={css.customComponent}>\n {parseInt(option.key) < 6 ? (\n <Avatar\n imageSrc={`https://cdn.sensehq.com/genesis/hashed/static/images/personas/${option.key}.png`}\n />\n ) : (\n <Avatar iconName=\"user\" />\n )}\n\n <ButtonTextMedium className={css.text} color=\"secondary\">\n <Truncate>{option.label}</Truncate>\n </ButtonTextMedium>\n </div>\n );\n\n return <Menu {...args} resolveLabel={resolveLabel} />;\n};\n\nWithResolversStory.args = {\n isFluid: false,\n menuDisabled: false,\n size: 'medium',\n options: textOptions,\n testId: 'menu-with-resolvers-story',\n};\n\nconst composeMenuOptions: Array<Array<MenuOption>> = [\n [\n {\n key: '1',\n label: 'Option one',\n secondaryLabel: 'Sub Text',\n optionSize: 'small',\n },\n {\n key: '2',\n label: 'Option two',\n secondaryLabel: 'Sub Text',\n optionSize: 'small',\n },\n {\n key: '3',\n label: 'Option three',\n secondaryLabel: 'Sub Text',\n optionSize: 'small',\n },\n ],\n [\n {\n iconLeft: 'coffee',\n key: '4',\n label: 'Option one',\n optionSize: 'medium',\n },\n ],\n [\n {\n iconLeft: 'coffee',\n key: '5',\n label: 'Option one',\n optionSize: 'medium',\n },\n ],\n];\n\nexport const ComposeMenuStory = (args: MenuProps) => <Menu {...args} />;\n\nComposeMenuStory.args = {\n isFluid: false,\n menuDisabled: false,\n size: 'medium',\n composeOptions: composeMenuOptions,\n selectedKeys: ['2'],\n testId: 'menu-compose-story',\n};\n\nconst customComponentMenuOptions: Array<MenuOption> = [\n {\n key: '1',\n customComponent: (\n <div className={css.customComponent}>\n <Avatar\n color=\"blue\"\n imageSrc=\"https://cdn.sensehq.com/genesis/hashed/static/images/personas/1.png\"\n iconName=\"face-party\"\n iconType=\"regular\"\n size=\"medium\"\n />\n <ButtonTextMedium className={css.text}>Angelina White</ButtonTextMedium>\n </div>\n ),\n },\n {\n key: '2',\n customComponent: (\n <div className={css.customComponent}>\n <Avatar\n imageSrc=\"https://cdn.sensehq.com/genesis/hashed/static/images/personas/4.png\"\n color=\"green\"\n size=\"medium\"\n text=\"John\"\n />\n <ButtonTextMedium className={css.text}>John Smith</ButtonTextMedium>\n </div>\n ),\n },\n {\n key: '3',\n\n customComponent: (\n <div className={css.customComponent}>\n <Avatar\n imageSrc=\"https://cdn.sensehq.com/genesis/hashed/static/images/personas/6.png\"\n color=\"orange\"\n size=\"medium\"\n text=\"Jacobs Marley\"\n />\n <ButtonTextMedium className={css.text}>Sarah Parker</ButtonTextMedium>\n </div>\n ),\n },\n];\n\nexport const CustomComponentMenuStory = (args: MenuProps) => <Menu {...args} />;\n\nCustomComponentMenuStory.args = {\n isFluid: false,\n menuDisabled: false,\n classNames: {option: css.optionText},\n size: 'medium',\n options: customComponentMenuOptions,\n selectedKeys: ['2'],\n testId: 'menu-custom-component-story',\n};\n\nconst groupTitleComponentMenuOptions: Array<MenuGroupTitleOption> = [\n {\n groupTitle: (\n <div className={css.titleWrapper}>\n <FormLabelSmall color=\"tertiary\">Fast Food</FormLabelSmall>\n <div className={css.infoContainer}>\n <Tooltip title=\"Hello!\" elevation=\"menu\">\n <Icon name=\"circle-info\" size=\"small\" color=\"tertiary\" />\n </Tooltip>\n </div>\n </div>\n ),\n options: [\n {\n key: '1',\n iconLeft: 'burger',\n label: 'Burger',\n },\n {\n key: '2',\n iconLeft: 'pizza',\n label: 'Pizza',\n },\n {\n key: '3',\n iconLeft: 'french-fries',\n label: 'Fries',\n },\n ],\n },\n {\n showLineDivider: true,\n groupTitle: (\n <div className={css.titleWrapper}>\n <FormLabelSmall color=\"tertiary\">Fruits</FormLabelSmall>\n <div className={css.infoContainer}>\n <Tooltip title=\"Hello!\" elevation=\"menu\">\n <Icon name=\"circle-info\" size=\"small\" color=\"tertiary\" />\n </Tooltip>\n </div>\n </div>\n ),\n options: [\n {\n key: '7',\n iconLeft: 'banana',\n label: 'Banana',\n },\n {\n key: '8',\n iconLeft: 'pear',\n label: 'Pear',\n },\n {\n key: '9',\n iconLeft: 'lemon',\n label: 'Lemon',\n },\n ],\n },\n];\n\nexport const GroupTitleMenuStory = (args: MenuProps) => (\n <Menu {...args} classNames={{groupTitle: css.gpTitle}} />\n);\n\nGroupTitleMenuStory.args = {\n isFluid: false,\n menuDisabled: false,\n classNames: {option: css.optionText},\n size: 'medium',\n groupTitleOptions: groupTitleComponentMenuOptions,\n selectedKeys: ['2'],\n testId: 'menu-group-title-story',\n};\n\nexport const VirtualizedGroupTitleMenuStory = (args: MenuProps) => (\n <Menu {...args} classNames={{groupTitle: css.gpTitle}} />\n);\n\nVirtualizedGroupTitleMenuStory.args = {\n isFluid: false,\n menuDisabled: false,\n classNames: {option: css.optionText},\n size: 'medium',\n allowSearch: true,\n groupTitleOptions: Array.from({length: 20}, (_, groupIdx) => ({\n groupTitle: `Group ${groupIdx + 1}`,\n options: Array.from({length: 25}, (_, optionIdx) => ({\n key: `${groupIdx}-${optionIdx}`,\n label: `Group ${groupIdx + 1} option ${optionIdx + 1}`,\n // every 5th option carries a description, so it needs a taller row\n ...(optionIdx % 5 === 0\n ? {secondaryLabel: 'Option with a description'}\n : {}),\n })),\n })),\n virtualization: {\n enable: true,\n // size rows from the option itself, not its index\n itemHeight: (option: MenuOption) => (option.secondaryLabel ? 56 : 36),\n groupTitleHeight: 32,\n menuHeight: 320,\n },\n selectedKeys: ['0-1'],\n testId: 'menu-virtualized-group-title-story',\n};\n\nexport const VirtualizedCustomGroupTitleMenuStory = (args: MenuProps) => (\n <Menu {...args} classNames={{groupTitle: css.gpTitle}} />\n);\n\nVirtualizedCustomGroupTitleMenuStory.args = {\n isFluid: false,\n menuDisabled: false,\n classNames: {option: css.optionText},\n size: 'medium',\n allowSearch: true,\n groupTitleOptions: Array.from({length: 10}, (_, groupIdx) => ({\n // every other group has a two-line custom title node\n groupTitle:\n groupIdx % 2 === 0 ? (\n <div>\n <div>Group {groupIdx + 1}</div>\n <div style={{fontSize: '11px', fontWeight: 'normal'}}>\n Two-line custom header\n </div>\n </div>\n ) : (\n `Group ${groupIdx + 1}`\n ),\n options: Array.from({length: 15}, (_, optionIdx) => ({\n key: `${groupIdx}-${optionIdx}`,\n label: `Group ${groupIdx + 1} option ${optionIdx + 1}`,\n })),\n })),\n virtualization: {\n enable: true,\n itemHeight: 36,\n // tall rows for the two-line custom titles, default for the rest\n groupTitleHeight: (_groupTitle: React.ReactNode, groupIndex: number) =>\n groupIndex % 2 === 0 ? 56 : 32,\n menuHeight: 320,\n },\n testId: 'menu-virtualized-custom-group-title-story',\n};\n\nexport const VirtualizedVariableHeightMenuStory = (args: MenuProps) => (\n <Menu {...args} />\n);\n\nVirtualizedVariableHeightMenuStory.args = {\n isFluid: false,\n menuDisabled: false,\n classNames: {option: css.optionText},\n size: 'medium',\n allowSearch: true,\n options: Array.from({length: 200}, (_, optionIdx) => ({\n key: `${optionIdx}`,\n label: `Option ${optionIdx + 1}`,\n ...(optionIdx % 4 === 0\n ? {secondaryLabel: 'Option with a description'}\n : {}),\n })),\n virtualization: {\n enable: true,\n itemHeight: (option: MenuOption) => (option.secondaryLabel ? 56 : 36),\n menuHeight: 320,\n },\n selectedKeys: ['1'],\n testId: 'menu-virtualized-variable-height-story',\n};\n\nconst GroupTitleActionMenu = (args: MenuProps) => {\n const [searchText, setSearchText] = React.useState<string>('');\n const [selectedKeys, setSelectedKeys] = React.useState(\n args.selectedKeys || [],\n );\n const optionsFiltered = getFilteredOptionsFromSearchText(\n textOptions,\n searchText,\n );\n const optionsFilteredKeys = optionsFiltered\n .filter(({disabled}) => !disabled)\n .map(({key}) => key);\n\n React.useEffect(() => {\n setSelectedKeys(args.selectedKeys || []);\n }, [args.selectedKeys]);\n\n const selectKeys = (option: MenuOption) => {\n if (selectedKeys.includes(option.key)) {\n setSelectedKeys(difference(selectedKeys, [option.key]));\n } else {\n setSelectedKeys(union(selectedKeys, [option.key]));\n }\n };\n\n const selectAllKeys: React.MouseEventHandler<HTMLButtonElement> = () => {\n setSelectedKeys(union(selectedKeys, optionsFilteredKeys));\n };\n\n const clearSelectedKeys: React.MouseEventHandler<HTMLButtonElement> = () => {\n setSelectedKeys(difference(selectedKeys, optionsFilteredKeys));\n };\n\n return (\n <Menu\n {...args}\n optionsVariant=\"checkbox\"\n selectedKeys={selectedKeys}\n onSelect={selectKeys}\n classNames={{\n header: css.groupTitleActionHeader,\n groupTitle: css.groupTitle,\n }}\n header={\n <SearchInput\n value={searchText}\n onChange={(e) => setSearchText(e.target.value)}\n onClear={() => setSearchText('')}\n size={args.size}\n />\n }\n groupTitleOptions={[\n {\n groupTitle: (\n <>\n <FormLabelMedium color=\"tertiary\">\n {`Values (${optionsFiltered.length})`}\n </FormLabelMedium>\n <div className={css.groupTitleActions}>\n <UnstyledButton onClick={clearSelectedKeys}>\n <ButtonTextSmallUnderline className={css.groupTitleAction}>\n Clear\n </ButtonTextSmallUnderline>\n </UnstyledButton>\n <UnstyledButton onClick={selectAllKeys}>\n <ButtonTextSmallUnderline\n color=\"clickable\"\n className={css.groupTitleAction}\n >\n Select All\n </ButtonTextSmallUnderline>\n </UnstyledButton>\n </div>\n </>\n ),\n options: optionsFiltered,\n },\n ]}\n />\n );\n};\n\nexport const GroupTitleActionMenuStory = (args: MenuProps) => (\n <GroupTitleActionMenu {...args} />\n);\n\nGroupTitleActionMenuStory.args = {\n isFluid: false,\n menuDisabled: false,\n classNames: {option: css.optionText},\n size: 'medium',\n selectedKeys: ['2'],\n testId: 'menu-group-title-action-story',\n};\n\nexport const CheckboxMenuStory = (args: MenuProps) => {\n const [selectedKeys, setSelectedKeys] = React.useState(\n args.selectedKeys || [],\n );\n\n React.useEffect(() => {\n setSelectedKeys(args.selectedKeys || []);\n }, [args.selectedKeys]);\n\n const selectKeys = (option: MenuOption) => {\n if (selectedKeys.includes(option.key)) {\n setSelectedKeys(selectedKeys.filter((item) => item !== option.key));\n } else {\n setSelectedKeys([...selectedKeys, option.key]);\n }\n };\n\n return <Menu {...args} selectedKeys={selectedKeys} onSelect={selectKeys} />;\n};\n\nCheckboxMenuStory.args = {\n isFluid: false,\n menuDisabled: false,\n classNames: {option: css.optionText},\n size: 'medium',\n options: checkboxOptions,\n optionsVariant: 'checkbox',\n selectedKeys: ['1', '4'],\n testId: 'menu-checkbox-story',\n};\n\nexport const RadioMenuStory = (args: MenuProps) => {\n const [selectedKeys, setSelectedKeys] = React.useState(\n args.selectedKeys || [],\n );\n\n React.useEffect(() => {\n setSelectedKeys(args.selectedKeys || []);\n }, [args.selectedKeys]);\n\n const selectKeys = (option: MenuOption) => {\n setSelectedKeys([option.key]);\n };\n\n return <Menu {...args} selectedKeys={selectedKeys} onSelect={selectKeys} />;\n};\n\nRadioMenuStory.args = {\n isFluid: false,\n menuDisabled: false,\n classNames: {option: css.optionText},\n size: 'medium',\n options: textOptions,\n optionsVariant: 'radio',\n selectedKeys: ['1'],\n testId: 'menu-radio-story',\n};\n\nexport const MenuHeader = () => (\n <div className={css.headerComponent}>\n <SubTitleSmall>Menu Header</SubTitleSmall>\n </div>\n);\n\nexport const MenuFooter = ({\n onSave,\n onCancel,\n}: {\n onSave?: () => void;\n onCancel?: () => void;\n}) => (\n <div className={css.footerComponent}>\n <Button size=\"small\" type=\"ghost\" onClick={onCancel}>\n Cancel\n </Button>\n <Button size=\"small\" type=\"secondary\" onClick={onSave}>\n Save\n </Button>\n </div>\n);\n\nexport const WithHeaderStory = (args: MenuProps) => (\n <Menu {...args} header={<MenuHeader />} />\n);\n\nWithHeaderStory.args = {\n isFluid: false,\n menuDisabled: false,\n size: 'small',\n options: textOptions,\n selectedKeys: ['2'],\n testId: 'menu-with-header-story',\n};\n\nexport const WithFooterStory = (args: MenuProps) => (\n <Menu {...args} footer={<MenuFooter />} />\n);\n\nWithFooterStory.args = {\n isFluid: false,\n menuDisabled: false,\n size: 'small',\n options: textOptions,\n selectedKeys: ['2'],\n testId: 'menu-with-footer-story',\n};\n\nexport const WithHeaderAndFooterStory = (args: MenuProps) => {\n const [selectedKeys, setSelectedKeys] = React.useState(\n args.selectedKeys || [],\n );\n\n React.useEffect(() => {\n setSelectedKeys(args.selectedKeys || []);\n }, [args.selectedKeys]);\n\n const selectKeys = (option: MenuOption) => {\n if (selectedKeys.includes(option.key)) {\n setSelectedKeys(selectedKeys.filter((item) => item !== option.key));\n } else {\n setSelectedKeys([...selectedKeys, option.key]);\n }\n };\n\n return (\n <Menu\n {...args}\n selectedKeys={selectedKeys}\n onSelect={selectKeys}\n header={<MenuHeader />}\n footer={<MenuFooter />}\n />\n );\n};\n\nWithHeaderAndFooterStory.args = {\n isFluid: false,\n menuDisabled: false,\n classNames: {option: css.optionText},\n options: textOptions,\n optionsVariant: 'checkbox',\n selectedKeys: ['1', '4'],\n testId: 'menu-with-header-and-footer-story',\n};\nTextOnlyStory.storyName = 'Text Only';\nTextWithTooltipStory.storyName = 'Text With Tooltip';\nMenuWithWrapStory.storyName = 'Menu With Wrap';\nWithSearchStory.storyName = 'With Search';\nTextAndIconLeftStory.storyName = 'Text And Icon Left';\nColoredOptionStory.storyName = 'Colored Option';\nTextAndIconRightStory.storyName = 'Text And Icon Right';\nTextAndDualIconStory.storyName = 'Text And Dual Icon';\nDualTextStory.storyName = 'Dual Text';\nWithResolversStory.storyName = 'With Resolvers';\nComposeMenuStory.storyName = 'Compose Menu';\nCustomComponentMenuStory.storyName = 'Custom Component Menu';\nGroupTitleMenuStory.storyName = 'Group Title Menu';\nVirtualizedGroupTitleMenuStory.storyName = 'Virtualized Group Title Menu';\nVirtualizedCustomGroupTitleMenuStory.storyName =\n 'Virtualized Custom Group Title Menu';\nVirtualizedVariableHeightMenuStory.storyName =\n 'Virtualized Variable Height Menu';\nGroupTitleActionMenuStory.storyName = 'Group Title Action Menu';\nCheckboxMenuStory.storyName = 'Checkbox Menu';\nRadioMenuStory.storyName = 'Radio Menu';\nWithHeaderStory.storyName = 'With Header';\nWithFooterStory.storyName = 'With Footer';\nWithHeaderAndFooterStory.storyName = 'With Header And Footer';\n"
1625
1625
  },
1626
1626
  "css": {
1627
1627
  "path": "Menu.module.css",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spaced-out/genesis-mcp",
3
- "version": "1.0.43",
3
+ "version": "1.0.44",
4
4
  "description": "MCP server for Genesis UI Design System - provides AI assistants with access to components, hooks, and design tokens",
5
5
  "type": "module",
6
6
  "main": "index.js",