@spaced-out/genesis-mcp 1.0.51 → 1.0.52
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.
- package/data/design-system.json +70 -31
- package/package.json +1 -1
package/data/design-system.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"metadata": {
|
|
3
|
-
"buildDate": "2026-09-
|
|
4
|
-
"version": "0.6.
|
|
3
|
+
"buildDate": "2026-09-29T09:04:13.213Z",
|
|
4
|
+
"version": "0.6.37-beta.0",
|
|
5
5
|
"designSystemPath": "/home/runner/work/ui-design-system/ui-design-system"
|
|
6
6
|
},
|
|
7
7
|
"components": {
|
|
@@ -290,11 +290,11 @@
|
|
|
290
290
|
"files": {
|
|
291
291
|
"main": {
|
|
292
292
|
"path": "ButtonDropdown.d.ts",
|
|
293
|
-
"content": "import * as React from 'react';\nimport type { Flow } from 'flow-to-typescript-codemod';\nimport type { ClickAwayRefType } from '../../utils/click-away';\nimport type { ButtonProps } from '../../components/Button';\nimport type { MenuOption, MenuProps } from '../../components/Menu';\nimport type { BaseTooltipProps, ElevationType } from '../../components/Tooltip';\nexport declare const ANCHOR_POSITION_TYPE: Readonly<{\n top: \"top\";\n topStart: \"top-start\";\n topEnd: \"top-end\";\n bottom: \"bottom\";\n bottomStart: \"bottom-start\";\n bottomEnd: \"bottom-end\";\n}>;\nexport declare const STRATEGY_TYPE: Readonly<{\n absolute: \"absolute\";\n fixed: \"fixed\";\n}>;\nexport type AnchorType = (typeof ANCHOR_POSITION_TYPE)[keyof typeof ANCHOR_POSITION_TYPE];\nexport type Strategy = (typeof STRATEGY_TYPE)[keyof typeof STRATEGY_TYPE];\ntype ClassNames = Readonly<{\n buttonWrapper?: string;\n dropdownContainer?: string;\n buttonIcon?: string;\n}>;\nexport interface ButtonDropdownProps extends Omit<ButtonProps, 'classNames' | 'menu' | 'positionStrategy' | 'anchorPosition' | 'onOptionSelect' | 'onMenuOpen' | 'onMenuClose' | 'tooltip' | 'elevation' | 'clickAwayRef'> {\n classNames?: ClassNames;\n menu?: MenuProps;\n positionStrategy?: Strategy;\n anchorPosition?: AnchorType;\n onOptionSelect?: (option: MenuOption, arg2?: React.SyntheticEvent<HTMLElement> | null | undefined) => unknown;\n onMenuOpen?: () => unknown;\n onMenuClose?: () => unknown;\n tooltip?: BaseTooltipProps;\n elevation?: ElevationType;\n clickAwayRef?: React.RefObject<ClickAwayRefType | null>;\n testId?: string;\n}\nexport declare const ButtonDropdown: Flow.AbstractComponent<ButtonDropdownProps, HTMLDivElement>;\nexport {};\n//# sourceMappingURL=ButtonDropdown.d.ts.map"
|
|
293
|
+
"content": "import * as React from 'react';\nimport type { Flow } from 'flow-to-typescript-codemod';\nimport type { ClickAwayRefType } from '../../utils/click-away';\nimport type { ButtonProps } from '../../components/Button';\nimport type { MenuOption, MenuProps } from '../../components/Menu';\nimport type { BaseTooltipProps, ElevationType } from '../../components/Tooltip';\nexport declare const ANCHOR_POSITION_TYPE: Readonly<{\n top: \"top\";\n topStart: \"top-start\";\n topEnd: \"top-end\";\n bottom: \"bottom\";\n bottomStart: \"bottom-start\";\n bottomEnd: \"bottom-end\";\n}>;\nexport declare const STRATEGY_TYPE: Readonly<{\n absolute: \"absolute\";\n fixed: \"fixed\";\n}>;\nexport type AnchorType = (typeof ANCHOR_POSITION_TYPE)[keyof typeof ANCHOR_POSITION_TYPE];\nexport type Strategy = (typeof STRATEGY_TYPE)[keyof typeof STRATEGY_TYPE];\ntype ClassNames = Readonly<{\n buttonWrapper?: string;\n dropdownContainer?: string;\n buttonIcon?: string;\n}>;\nexport interface ButtonDropdownProps extends Omit<ButtonProps, 'classNames' | 'menu' | 'positionStrategy' | 'anchorPosition' | 'onOptionSelect' | 'onMenuOpen' | 'onMenuClose' | 'tooltip' | 'elevation' | 'clickAwayRef'> {\n classNames?: ClassNames;\n menu?: MenuProps;\n positionStrategy?: Strategy;\n anchorPosition?: AnchorType;\n onOptionSelect?: (option: MenuOption, arg2?: React.SyntheticEvent<HTMLElement> | null | undefined) => unknown;\n onMenuOpen?: () => unknown;\n onMenuClose?: () => unknown;\n tooltip?: BaseTooltipProps;\n elevation?: ElevationType;\n /**\n * Opt in to the accessibility behavior: `aria-haspopup` / `aria-expanded` /\n * `aria-controls` on the trigger, the arrow key that opens the popup, focus\n * moving into the menu and back to the trigger, and the roving tab stop\n * inside it. Forwarded to the `Menu` this opens.\n *\n * Off by default, and off is this component exactly as it behaved before any\n * of it existed.\n */\n enableA11y?: boolean;\n clickAwayRef?: React.RefObject<ClickAwayRefType | null>;\n testId?: string;\n}\nexport declare const ButtonDropdown: Flow.AbstractComponent<ButtonDropdownProps, HTMLDivElement>;\nexport {};\n//# sourceMappingURL=ButtonDropdown.d.ts.map"
|
|
294
294
|
},
|
|
295
295
|
"story": {
|
|
296
296
|
"path": "ButtonDropdown.stories.tsx",
|
|
297
|
-
"content": "import * as React from 'react';\n\nimport type {ClickAwayRefType} from 'src/utils/click-away';\nimport {\n getButtonLabelFromSelectedKeys,\n getSelectedKeysFromSelectedOption,\n} from 'src/utils/menu';\n\nimport type {ButtonProps} from 'src/components/Button';\nimport {BUTTON_ACTION_TYPE} from 'src/components/Button';\nimport type {ButtonDropdownProps} from 'src/components/ButtonDropdown/ButtonDropdown';\nimport {\n ANCHOR_POSITION_TYPE,\n ButtonDropdown,\n STRATEGY_TYPE,\n} from 'src/components/ButtonDropdown/ButtonDropdown';\nimport {Icon} from 'src/components/Icon';\nimport type {\n MenuGroupTitleOption,\n MenuOption,\n MenuProps,\n} from 'src/components/Menu';\nimport {MenuFooter, MenuHeader} from 'src/components/Menu/Menu.stories';\nimport {FormLabelSmall} from 'src/components/Text';\nimport {ELEVATION_TYPES, Tooltip} from 'src/components/Tooltip';\n\nimport css from 'src/components/ButtonDropdown/ButtonDropdown.stories.module.css';\n\n\nconst anchorPositionOptions: Array<unknown> = [\n ...Object.values(ANCHOR_POSITION_TYPE),\n];\n\nconst positionStrategyOptions: Array<unknown> = [\n ...Object.values(STRATEGY_TYPE),\n];\n\nconst actionTypeOptions: Array<unknown> = [\n ...Object.values(BUTTON_ACTION_TYPE),\n];\n\nconst elevationOptions: Array<unknown> = [...Object.values(ELEVATION_TYPES)];\n\nconst textOptions: MenuOption[] = [\n {key: '1', label: 'Option one'},\n {key: '2', label: 'Option two'},\n {key: '3', label: 'Option three'},\n {\n key: '4',\n label:\n 'Option three this is a very long option that would truncate, and this is even longer text to further test the truncation behavior in the dropdown component, ensuring that extremely long labels are handled gracefully and do not break the layout or overflow in an undesirable way, especially when the text is much longer than what would typically be expected in a dropdown option.',\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 Night'},\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 body=\"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 groupTitle: (\n <div className={css.titleWrapper}>\n <FormLabelSmall color=\"tertiary\">Fruits</FormLabelSmall>\n <div className={css.infoContainer}>\n <Tooltip body=\"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\nconst menuProps: MenuProps = {\n isFluid: false,\n menuDisabled: false,\n size: 'medium',\n options: textOptions,\n};\n\nconst textOnlyButtonProps: ButtonProps = {\n children: 'Button Text',\n};\n\nconst textAndIconButtonProps: ButtonProps = {\n children: 'Long Button Text',\n iconLeftName: 'face-party',\n iconRightName: 'face-party',\n};\n\nconst iconOnlyButtonProps: ButtonProps = {\n iconLeftName: 'face-party',\n ariaLabel: 'Icon Button Dropdown',\n};\n\nexport default {\n tags: ['autodocs'],\n title: 'Components/Button Dropdown/Generic Button Dropdown',\n component: ButtonDropdown,\n argTypes: {\n onClick: {\n description:\n '**onClick** handler that is applied to the root `<button>` element',\n action: 'clicked',\n table: {\n type: {summary: '(SyntheticEvent<HTMLElement>) => mixed'},\n },\n },\n classNames: {\n description: 'External classNames to be applied',\n control: {\n type: 'object',\n },\n table: {\n type: {\n summary:\n '{buttonWrapper?: string, dropdownContainer?: string, buttonIcon?: string}',\n },\n },\n },\n tooltip: {\n control: {\n type: 'object',\n },\n description: 'Field to set the **Tooltip** for Button Dropdown',\n table: {\n type: {summary: 'BaseTooltipProps'},\n },\n },\n menu: {\n description: 'Menu props to use',\n control: {\n type: 'object',\n },\n table: {\n type: {summary: 'MenuProps'},\n },\n },\n elevation: {\n description:\n 'Elevation level for the dropdown menu. This controls the z-index stacking order. Default is set to \"modal\" elevation. Use higher elevation levels when rendering the dropdown above other elevated elements like Modal, Toast, etc.',\n control: {\n type: 'select',\n },\n options: elevationOptions,\n table: {\n type: {summary: 'enum'},\n defaultValue: {summary: 'modal'},\n },\n },\n iconLeftName: {\n description:\n 'Left icon to be applied to the button, it internally uses the `Icon` component to render icons',\n control: {\n type: 'text',\n },\n table: {\n type: {summary: 'string'},\n },\n },\n iconLeftType: {\n description:\n 'Type of Left Icon. Type represents 4 unique icon styles :- Solid, Regular, Duotone and Brands',\n options: ['regular', 'solid', 'duotone', 'brands'],\n control: {type: 'select'},\n table: {\n type: {summary: 'string'},\n defaultValue: {summary: 'regular'},\n },\n },\n iconRightName: {\n description:\n 'Right icon to be applied to the button, it internally uses the `Icon` component to render icons',\n control: {\n type: 'text',\n },\n table: {\n type: {summary: 'string'},\n },\n },\n iconRightType: {\n description:\n 'Type of Right Icon. Type represents 4 unique icon styles :- Solid, Regular, Duotone and Brands',\n options: ['regular', 'solid', 'duotone', 'brands'],\n control: {type: 'select'},\n table: {\n type: {summary: 'string'},\n defaultValue: {summary: 'regular'},\n },\n },\n type: {\n options: ['primary', 'secondary', 'tertiary', 'ghost', 'danger'],\n description: 'Type of Button. Supported types are listed on the dropdown',\n control: {\n type: 'select',\n },\n table: {\n type: {summary: 'enum'},\n defaultValue: {summary: 'primary'},\n },\n },\n actionType: {\n options: actionTypeOptions,\n description:\n 'Action type of Button. Supported types are listed on the dropdown',\n control: {\n type: 'select',\n },\n table: {\n type: {summary: 'enum'},\n defaultValue: {summary: 'submit'},\n },\n },\n size: {\n description:\n 'Button 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 disabled: {\n description: 'If **true**, the component is disabled',\n control: {\n type: 'boolean',\n },\n table: {\n type: {summary: 'boolean'},\n defaultValue: {summary: 'false'},\n },\n },\n isLoading: {\n description:\n 'If **true**, the component would show a circular loader and also preserve the current size of button so that there are no jumps',\n control: {\n type: 'boolean',\n },\n table: {\n type: {summary: 'boolean'},\n defaultValue: {summary: 'false'},\n },\n },\n isFluid: {\n description:\n 'If **true**, the component would take the container\\'s `width`',\n control: {\n type: 'boolean',\n },\n table: {\n type: {summary: 'boolean'},\n defaultValue: {summary: 'false'},\n },\n },\n children: {\n description: 'The content of the Button',\n table: {\n type: {summary: 'React.Component | string'},\n },\n },\n ariaLabel: {\n control: {\n type: 'text',\n },\n description:\n '**aria-label** should be used where Button do not have have discernible text. This would avoid accessibility violations in screen readers',\n table: {\n type: {summary: 'string'},\n },\n },\n onOptionSelect: {\n description:\n '**onOptionSelect** 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 onMenuOpen: {\n description:\n '**onMenuOpen** handler is triggered when menu is opened. This should be helpful in cases where you want to change Button icon / text on this trigger.',\n action: 'clicked',\n table: {\n type: {summary: '() => mixed'},\n },\n },\n onMenuClose: {\n description:\n '**onMenuClose** handler is triggered when menu is closed. This should be helpful in cases where you want to change Button icon / text on this trigger.',\n action: 'clicked',\n table: {\n type: {summary: '() => mixed'},\n },\n },\n anchorPosition: {\n options: anchorPositionOptions,\n description:\n 'Position of Menu relative to its reference element. It can be any one value from the dropdown',\n control: {\n type: 'select',\n },\n table: {\n type: {summary: 'enum'},\n defaultValue: {summary: 'bottom-start'},\n },\n },\n positionStrategy: {\n options: positionStrategyOptions,\n description:\n 'Position of Menu relative to either its nearest positioned ancestor (absolute) or its nearest containing block- usually the viewport (fixed). It can be any one value from the dropdown',\n control: {\n type: 'select',\n },\n table: {\n type: {summary: 'enum'},\n defaultValue: {summary: 'absolute'},\n },\n },\n clickAwayRef: {\n type: {optional: true},\n description: 'ref that contains the functions to open or close the child',\n table: {\n type: {\n summary: 'ClickAwayRefType',\n },\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 ButtonDropdown component',\n description: {\n component: `\n\\`\\`\\`js\nimport { ButtonDropdown } from \"@spaced-out/ui-design-system/lib/components/ButtonDropdown\";\n\\`\\`\\`\nButton Dropdown is a composite component that internally implements **<a href=\"../?path=/docs/components-menu--docs\">Menu</a>** with\n**<a href=\"../?path=/docs/components-button--docs\">Button</a>** component. It also uses **Floating UI** to position the\nmenu component relative to empty space available, taking viewport and component position into account. This makes sure that menu is always visible\nirrespective of the component positioning. The component has a callback to notify the parent on option selection. Button Dropdown is implemented inside a\n\\`ClickAway\\` listener and thus Menu is closed automatically on outside click.\n\nIf you want to use Icon only button for button dropdown. Use IconLeftName and IconLeftType.\n\n- Supports passing a \\`clickAwayRef\\` prop to the component.\n- You can always force Open the instance from ref by using \\`clickAwayRef.current?.forceOpen()\\`. This might be useful when want to force Open as per you needs.\n- You can always force Close the instance from ref by using \\`clickAwayRef.current?.forceClose()\\`. This might be useful when want to force Close as per you needs.\n\n\n**Note: ** Always use \\`ariaLabel\\` with Icon only button for accessibility\n\n**Note: ** For checkbox type option set you can make use of \\`getSelectedKeysFromSelectedOption\\` and \\`getButtonLabelFromSelectedKeys\\` from \\`utils/menu\\`\nto populate \\`selectedKeys\\` and \\`buttonText\\` respectively.\n `,\n },\n },\n storySource: {\n componentPath: '/src/components/ButtonDropdown/ButtonDropdown',\n },\n },\n};\n\nexport const TextOnlyStory = (args: ButtonDropdownProps) => (\n <div className={css.menuBtnWrapper}>\n <ButtonDropdown {...args} />\n </div>\n);\n\nTextOnlyStory.args = {\n ...textOnlyButtonProps,\n menu: menuProps,\n size: 'medium',\n testId: 'button-dropdown-text-only-story',\n};\n\nexport const TextWithTooltipStory = (args: ButtonDropdownProps) => (\n <div className={css.menuBtnWrapper}>\n <ButtonDropdown {...args} />\n </div>\n);\n\nTextWithTooltipStory.args = {\n ...textOnlyButtonProps,\n menu: {...menuProps, showLabelTooltip: {maxLines: 2}},\n size: 'medium',\n testId: 'button-dropdown-text-with-tooltip-story',\n};\n\nexport const WithSearchStory = (args: ButtonDropdownProps) => (\n <div className={css.menuBtnWrapper}>\n <ButtonDropdown {...args} />\n </div>\n);\n\nWithSearchStory.args = {\n ...textOnlyButtonProps,\n menu: {...menuProps, allowSearch: true},\n size: 'medium',\n testId: 'button-dropdown-with-search-story',\n};\n\nexport const TextAndIconStory = (args: ButtonDropdownProps) => (\n <div className={css.menuBtnWrapper}>\n <ButtonDropdown {...args} />\n </div>\n);\n\nTextAndIconStory.args = {\n ...textAndIconButtonProps,\n menu: menuProps,\n size: 'medium',\n testId: 'button-dropdown-text-and-icon-story',\n};\n\nexport const IconOnlyStory = (args: ButtonDropdownProps) => (\n <div className={css.menuBtnWrapper}>\n <ButtonDropdown {...args} />\n </div>\n);\n\nIconOnlyStory.args = {\n ...iconOnlyButtonProps,\n menu: menuProps,\n size: 'medium',\n testId: 'button-dropdown-icon-only-story',\n};\n\nexport const DynamicIconStory = (args: ButtonDropdownProps) => {\n const [iconName, setIconName] = React.useState('chevron-down');\n return (\n <div className={css.menuBtnWrapper}>\n <ButtonDropdown\n {...args}\n iconLeftName={iconName}\n onMenuOpen={() => setIconName('chevron-up')}\n onMenuClose={() => setIconName('chevron-down')}\n tooltip={{body: iconName}}\n />\n </div>\n );\n};\n\nDynamicIconStory.args = {\n iconLeftName: 'chevron-down',\n ariaLabel: 'Icon Button Dropdown',\n menu: menuProps,\n size: 'medium',\n testId: 'button-dropdown-dynamic-icon-story',\n};\n\nconst complicatedMenuProps: MenuProps = {\n isFluid: false,\n menuDisabled: false,\n size: 'medium',\n groupTitleOptions: groupTitleComponentMenuOptions,\n};\n\nexport const TextAndDynamicIconStory = (args: ButtonDropdownProps) => {\n const [iconName, setIconName] = React.useState('chevron-down');\n return (\n <div className={css.menuBtnWrapper}>\n <ButtonDropdown\n {...args}\n iconRightName={iconName}\n onMenuOpen={() => setIconName('chevron-up')}\n onMenuClose={() => setIconName('chevron-down')}\n />\n </div>\n );\n};\n\nTextAndDynamicIconStory.args = {\n children: 'Long Button Text',\n iconRightName: 'chevron-down',\n iconRightType: 'regular',\n menu: complicatedMenuProps,\n size: 'medium',\n type: 'ghost',\n testId: 'button-dropdown-text-and-dynamic-icon-story',\n};\n\nconst checkboxMenuProps: MenuProps = {\n options: textOptions,\n optionsVariant: 'checkbox',\n selectedKeys: ['4'],\n isFluid: false,\n};\n\nexport const WithCheckboxMenuStory = (args: ButtonDropdownProps) => {\n const [btnText, setBtnText] = React.useState<React.ReactNode>('');\n const [selectedKeys, setSelectedKeys] = React.useState(\n args.menu?.selectedKeys || [],\n );\n\n React.useEffect(() => {\n setSelectedKeys(args.menu?.selectedKeys || []);\n const newBtnText = getButtonLabelFromSelectedKeys(\n args.menu?.selectedKeys,\n args.children,\n );\n setBtnText(newBtnText);\n }, [args.menu?.selectedKeys]);\n\n const handleChange = (option: MenuOption) => {\n const newSelectedKeys = getSelectedKeysFromSelectedOption(\n option,\n selectedKeys,\n );\n const newBtnText = getButtonLabelFromSelectedKeys(\n newSelectedKeys,\n args.children,\n );\n setSelectedKeys(newSelectedKeys);\n setBtnText(newBtnText);\n };\n\n return (\n <div className={css.menuBtnWrapper}>\n <ButtonDropdown\n {...args}\n menu={{...args.menu, selectedKeys}}\n onOptionSelect={handleChange}\n >\n {btnText}\n </ButtonDropdown>\n </div>\n );\n};\n\nWithCheckboxMenuStory.args = {\n children: 'Long Button Text',\n menu: checkboxMenuProps,\n size: 'medium',\n testId: 'button-dropdown-with-checkbox-menu-story',\n};\n\nconst radioMenuProps: MenuProps = {\n options: textOptions,\n optionsVariant: 'radio',\n selectedKeys: ['4'],\n isFluid: false,\n};\n\nexport const WithRadioMenuStory = (args: ButtonDropdownProps) => {\n const [selectedKeys, setSelectedKeys] = React.useState(\n args.menu?.selectedKeys || [],\n );\n\n React.useEffect(() => {\n setSelectedKeys(args.menu?.selectedKeys || []);\n }, [args.menu?.selectedKeys]);\n\n const handleChange = (option: MenuOption) => {\n setSelectedKeys([option.key]);\n };\n\n return (\n <div className={css.menuBtnWrapper}>\n <ButtonDropdown\n {...args}\n menu={{...args.menu, selectedKeys}}\n onOptionSelect={handleChange}\n >\n {args.children}\n </ButtonDropdown>\n </div>\n );\n};\n\nWithRadioMenuStory.args = {\n children: 'Long Button Text',\n menu: radioMenuProps,\n size: 'medium',\n testId: 'button-dropdown-with-radio-menu-story',\n};\nexport const WithHeaderAndFooterStory = (args: ButtonDropdownProps) => (\n <WithHeaderAndFooter {...args} />\n);\n\nexport const WithHeaderAndFooter = (args: ButtonDropdownProps) => {\n const clickAwayRef = React.useRef<ClickAwayRefType | null>(null);\n const [btnText, setBtnText] = React.useState<React.ReactNode>('');\n const [selectedKeys, setSelectedKeys] = React.useState(\n args.menu?.selectedKeys || [],\n );\n\n React.useEffect(() => {\n setSelectedKeys(args.menu?.selectedKeys || []);\n const newBtnText = getButtonLabelFromSelectedKeys(\n args.menu?.selectedKeys,\n args.children,\n );\n setBtnText(newBtnText);\n }, [args.menu?.selectedKeys]);\n\n const handleChange = (option: MenuOption) => {\n const newSelectedKeys = getSelectedKeysFromSelectedOption(\n option,\n selectedKeys,\n );\n const newBtnText = getButtonLabelFromSelectedKeys(\n newSelectedKeys,\n args.children,\n );\n setSelectedKeys(newSelectedKeys);\n setBtnText(newBtnText);\n };\n\n const onSave = () => {\n if (clickAwayRef.current) {\n clickAwayRef.current?.forceClose();\n }\n };\n\n return (\n <div className={css.menuBtnWrapper}>\n <ButtonDropdown\n {...args}\n menu={{\n ...args.menu,\n selectedKeys,\n header: <MenuHeader />,\n footer: <MenuFooter onSave={onSave} />,\n }}\n onOptionSelect={handleChange}\n clickAwayRef={clickAwayRef}\n >\n {btnText}\n </ButtonDropdown>\n </div>\n );\n};\n\nWithHeaderAndFooterStory.args = {\n children: 'Long Button Text',\n menu: {\n ...menuProps,\n header: <MenuHeader />,\n footer: <MenuFooter />,\n },\n size: 'medium',\n testId: 'button-dropdown-with-header-and-footer-story',\n};\nTextOnlyStory.storyName = 'Text Only';\nTextWithTooltipStory.storyName = 'Text With Tooltip';\nWithSearchStory.storyName = 'With Search';\nTextAndIconStory.storyName = 'Text And Icon';\nIconOnlyStory.storyName = 'Icon Only';\nDynamicIconStory.storyName = 'Dynamic Icon';\nTextAndDynamicIconStory.storyName = 'Text And Dynamic Icon';\nWithCheckboxMenuStory.storyName = 'With Checkbox Menu';\nWithRadioMenuStory.storyName = 'With Radio Menu';\nWithHeaderAndFooterStory.storyName = 'With Header And Footer';\n"
|
|
297
|
+
"content": "import * as React from 'react';\n\nimport type {ClickAwayRefType} from 'src/utils/click-away';\nimport {\n getButtonLabelFromSelectedKeys,\n getSelectedKeysFromSelectedOption,\n} from 'src/utils/menu';\n\nimport type {ButtonProps} from 'src/components/Button';\nimport {BUTTON_ACTION_TYPE} from 'src/components/Button';\nimport type {ButtonDropdownProps} from 'src/components/ButtonDropdown/ButtonDropdown';\nimport {\n ANCHOR_POSITION_TYPE,\n ButtonDropdown,\n STRATEGY_TYPE,\n} from 'src/components/ButtonDropdown/ButtonDropdown';\nimport {Icon} from 'src/components/Icon';\nimport type {\n MenuGroupTitleOption,\n MenuOption,\n MenuProps,\n} from 'src/components/Menu';\nimport {MenuFooter, MenuHeader} from 'src/components/Menu/Menu.stories';\nimport {FormLabelSmall} from 'src/components/Text';\nimport {ELEVATION_TYPES, Tooltip} from 'src/components/Tooltip';\n\nimport css from 'src/components/ButtonDropdown/ButtonDropdown.stories.module.css';\n\n\nconst anchorPositionOptions: Array<unknown> = [\n ...Object.values(ANCHOR_POSITION_TYPE),\n];\n\nconst positionStrategyOptions: Array<unknown> = [\n ...Object.values(STRATEGY_TYPE),\n];\n\nconst actionTypeOptions: Array<unknown> = [\n ...Object.values(BUTTON_ACTION_TYPE),\n];\n\nconst elevationOptions: Array<unknown> = [...Object.values(ELEVATION_TYPES)];\n\nconst textOptions: MenuOption[] = [\n {key: '1', label: 'Option one'},\n {key: '2', label: 'Option two'},\n {key: '3', label: 'Option three'},\n {\n key: '4',\n label:\n 'Option three this is a very long option that would truncate, and this is even longer text to further test the truncation behavior in the dropdown component, ensuring that extremely long labels are handled gracefully and do not break the layout or overflow in an undesirable way, especially when the text is much longer than what would typically be expected in a dropdown option.',\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 Night'},\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 body=\"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 groupTitle: (\n <div className={css.titleWrapper}>\n <FormLabelSmall color=\"tertiary\">Fruits</FormLabelSmall>\n <div className={css.infoContainer}>\n <Tooltip body=\"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\nconst menuProps: MenuProps = {\n isFluid: false,\n menuDisabled: false,\n size: 'medium',\n options: textOptions,\n};\n\nconst textOnlyButtonProps: ButtonProps = {\n children: 'Button Text',\n};\n\nconst textAndIconButtonProps: ButtonProps = {\n children: 'Long Button Text',\n iconLeftName: 'face-party',\n iconRightName: 'face-party',\n};\n\nconst iconOnlyButtonProps: ButtonProps = {\n iconLeftName: 'face-party',\n ariaLabel: 'Icon Button Dropdown',\n};\n\nexport default {\n tags: ['autodocs'],\n title: 'Components/Button Dropdown/Generic Button Dropdown',\n component: ButtonDropdown,\n argTypes: {\n enableA11y: {\n description:\n '**enableA11y** opts into the accessibility behavior: `aria-haspopup` / `aria-expanded` / `aria-controls` on the trigger, the arrow key that opens the popup, and focus moving into the menu and back. Off by default, where `ButtonDropdown` behaves exactly as it did before any of it existed. Off here too, as everywhere: turn the control on to see the accessible behavior.',\n control: {type: 'boolean'},\n table: {\n type: {summary: 'boolean'},\n defaultValue: {summary: 'false'},\n },\n },\n onClick: {\n description:\n '**onClick** handler that is applied to the root `<button>` element',\n action: 'clicked',\n table: {\n type: {summary: '(SyntheticEvent<HTMLElement>) => mixed'},\n },\n },\n classNames: {\n description: 'External classNames to be applied',\n control: {\n type: 'object',\n },\n table: {\n type: {\n summary:\n '{buttonWrapper?: string, dropdownContainer?: string, buttonIcon?: string}',\n },\n },\n },\n tooltip: {\n control: {\n type: 'object',\n },\n description: 'Field to set the **Tooltip** for Button Dropdown',\n table: {\n type: {summary: 'BaseTooltipProps'},\n },\n },\n menu: {\n description: 'Menu props to use',\n control: {\n type: 'object',\n },\n table: {\n type: {summary: 'MenuProps'},\n },\n },\n elevation: {\n description:\n 'Elevation level for the dropdown menu. This controls the z-index stacking order. Default is set to \"modal\" elevation. Use higher elevation levels when rendering the dropdown above other elevated elements like Modal, Toast, etc.',\n control: {\n type: 'select',\n },\n options: elevationOptions,\n table: {\n type: {summary: 'enum'},\n defaultValue: {summary: 'modal'},\n },\n },\n iconLeftName: {\n description:\n 'Left icon to be applied to the button, it internally uses the `Icon` component to render icons',\n control: {\n type: 'text',\n },\n table: {\n type: {summary: 'string'},\n },\n },\n iconLeftType: {\n description:\n 'Type of Left Icon. Type represents 4 unique icon styles :- Solid, Regular, Duotone and Brands',\n options: ['regular', 'solid', 'duotone', 'brands'],\n control: {type: 'select'},\n table: {\n type: {summary: 'string'},\n defaultValue: {summary: 'regular'},\n },\n },\n iconRightName: {\n description:\n 'Right icon to be applied to the button, it internally uses the `Icon` component to render icons',\n control: {\n type: 'text',\n },\n table: {\n type: {summary: 'string'},\n },\n },\n iconRightType: {\n description:\n 'Type of Right Icon. Type represents 4 unique icon styles :- Solid, Regular, Duotone and Brands',\n options: ['regular', 'solid', 'duotone', 'brands'],\n control: {type: 'select'},\n table: {\n type: {summary: 'string'},\n defaultValue: {summary: 'regular'},\n },\n },\n type: {\n options: ['primary', 'secondary', 'tertiary', 'ghost', 'danger'],\n description: 'Type of Button. Supported types are listed on the dropdown',\n control: {\n type: 'select',\n },\n table: {\n type: {summary: 'enum'},\n defaultValue: {summary: 'primary'},\n },\n },\n actionType: {\n options: actionTypeOptions,\n description:\n 'Action type of Button. Supported types are listed on the dropdown',\n control: {\n type: 'select',\n },\n table: {\n type: {summary: 'enum'},\n defaultValue: {summary: 'submit'},\n },\n },\n size: {\n description:\n 'Button 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 disabled: {\n description: 'If **true**, the component is disabled',\n control: {\n type: 'boolean',\n },\n table: {\n type: {summary: 'boolean'},\n defaultValue: {summary: 'false'},\n },\n },\n isLoading: {\n description:\n 'If **true**, the component would show a circular loader and also preserve the current size of button so that there are no jumps',\n control: {\n type: 'boolean',\n },\n table: {\n type: {summary: 'boolean'},\n defaultValue: {summary: 'false'},\n },\n },\n isFluid: {\n description:\n 'If **true**, the component would take the container\\'s `width`',\n control: {\n type: 'boolean',\n },\n table: {\n type: {summary: 'boolean'},\n defaultValue: {summary: 'false'},\n },\n },\n children: {\n description: 'The content of the Button',\n table: {\n type: {summary: 'React.Component | string'},\n },\n },\n ariaLabel: {\n control: {\n type: 'text',\n },\n description:\n '**aria-label** should be used where Button do not have have discernible text. This would avoid accessibility violations in screen readers',\n table: {\n type: {summary: 'string'},\n },\n },\n onOptionSelect: {\n description:\n '**onOptionSelect** 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 onMenuOpen: {\n description:\n '**onMenuOpen** handler is triggered when menu is opened. This should be helpful in cases where you want to change Button icon / text on this trigger.',\n action: 'clicked',\n table: {\n type: {summary: '() => mixed'},\n },\n },\n onMenuClose: {\n description:\n '**onMenuClose** handler is triggered when menu is closed. This should be helpful in cases where you want to change Button icon / text on this trigger.',\n action: 'clicked',\n table: {\n type: {summary: '() => mixed'},\n },\n },\n anchorPosition: {\n options: anchorPositionOptions,\n description:\n 'Position of Menu relative to its reference element. It can be any one value from the dropdown',\n control: {\n type: 'select',\n },\n table: {\n type: {summary: 'enum'},\n defaultValue: {summary: 'bottom-start'},\n },\n },\n positionStrategy: {\n options: positionStrategyOptions,\n description:\n 'Position of Menu relative to either its nearest positioned ancestor (absolute) or its nearest containing block- usually the viewport (fixed). It can be any one value from the dropdown',\n control: {\n type: 'select',\n },\n table: {\n type: {summary: 'enum'},\n defaultValue: {summary: 'absolute'},\n },\n },\n clickAwayRef: {\n type: {optional: true},\n description: 'ref that contains the functions to open or close the child',\n table: {\n type: {\n summary: 'ClickAwayRefType',\n },\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 ButtonDropdown component',\n description: {\n component: `\n\\`\\`\\`js\nimport { ButtonDropdown } from \"@spaced-out/ui-design-system/lib/components/ButtonDropdown\";\n\\`\\`\\`\nButton Dropdown is a composite component that internally implements **<a href=\"../?path=/docs/components-menu--docs\">Menu</a>** with\n**<a href=\"../?path=/docs/components-button--docs\">Button</a>** component. It also uses **Floating UI** to position the\nmenu component relative to empty space available, taking viewport and component position into account. This makes sure that menu is always visible\nirrespective of the component positioning. The component has a callback to notify the parent on option selection. Button Dropdown is implemented inside a\n\\`ClickAway\\` listener and thus Menu is closed automatically on outside click.\n\nIf you want to use Icon only button for button dropdown. Use IconLeftName and IconLeftType.\n\n- Supports passing a \\`clickAwayRef\\` prop to the component.\n- You can always force Open the instance from ref by using \\`clickAwayRef.current?.forceOpen()\\`. This might be useful when want to force Open as per you needs.\n- You can always force Close the instance from ref by using \\`clickAwayRef.current?.forceClose()\\`. This might be useful when want to force Close as per you needs.\n\n\n**Note: ** Always use \\`ariaLabel\\` with Icon only button for accessibility\n\n**Note: ** For checkbox type option set you can make use of \\`getSelectedKeysFromSelectedOption\\` and \\`getButtonLabelFromSelectedKeys\\` from \\`utils/menu\\`\nto populate \\`selectedKeys\\` and \\`buttonText\\` respectively.\n `,\n },\n },\n storySource: {\n componentPath: '/src/components/ButtonDropdown/ButtonDropdown',\n },\n },\n};\n\nexport const TextOnlyStory = (args: ButtonDropdownProps) => (\n <div className={css.menuBtnWrapper}>\n <ButtonDropdown {...args} />\n </div>\n);\n\nTextOnlyStory.args = {\n ...textOnlyButtonProps,\n menu: menuProps,\n size: 'medium',\n testId: 'button-dropdown-text-only-story',\n};\n\n/*\n * The same trigger as `Text Only`, with the accessibility opt-in on.\n *\n * With `enableA11y` the button advertises its popup — `aria-haspopup`, and\n * `aria-expanded` / `aria-controls` that only point at the listbox while it is\n * mounted — and ArrowDown opens it. These options are real buttons, so this is\n * the roving tab stop pattern: arrows move actual DOM focus between them and\n * Space/Enter activate natively. Escape closes and returns focus to the button.\n *\n * Every other story here leaves the prop off, which is the default and is this\n * component exactly as it behaved before any of this existed.\n */\nexport const AccessibleButtonDropdownStory = (args: ButtonDropdownProps) => (\n <div className={css.menuBtnWrapper}>\n <ButtonDropdown {...args} />\n </div>\n);\n\nAccessibleButtonDropdownStory.args = {\n ...textOnlyButtonProps,\n enableA11y: true,\n menu: menuProps,\n size: 'medium',\n testId: 'button-dropdown-accessible-story',\n};\n\nexport const TextWithTooltipStory = (args: ButtonDropdownProps) => (\n <div className={css.menuBtnWrapper}>\n <ButtonDropdown {...args} />\n </div>\n);\n\nTextWithTooltipStory.args = {\n ...textOnlyButtonProps,\n menu: {...menuProps, showLabelTooltip: {maxLines: 2}},\n size: 'medium',\n testId: 'button-dropdown-text-with-tooltip-story',\n};\n\nexport const WithSearchStory = (args: ButtonDropdownProps) => (\n <div className={css.menuBtnWrapper}>\n <ButtonDropdown {...args} />\n </div>\n);\n\nWithSearchStory.args = {\n ...textOnlyButtonProps,\n menu: {...menuProps, allowSearch: true},\n size: 'medium',\n testId: 'button-dropdown-with-search-story',\n};\n\nexport const TextAndIconStory = (args: ButtonDropdownProps) => (\n <div className={css.menuBtnWrapper}>\n <ButtonDropdown {...args} />\n </div>\n);\n\nTextAndIconStory.args = {\n ...textAndIconButtonProps,\n menu: menuProps,\n size: 'medium',\n testId: 'button-dropdown-text-and-icon-story',\n};\n\nexport const IconOnlyStory = (args: ButtonDropdownProps) => (\n <div className={css.menuBtnWrapper}>\n <ButtonDropdown {...args} />\n </div>\n);\n\nIconOnlyStory.args = {\n ...iconOnlyButtonProps,\n menu: menuProps,\n size: 'medium',\n testId: 'button-dropdown-icon-only-story',\n};\n\nexport const DynamicIconStory = (args: ButtonDropdownProps) => {\n const [iconName, setIconName] = React.useState('chevron-down');\n return (\n <div className={css.menuBtnWrapper}>\n <ButtonDropdown\n {...args}\n iconLeftName={iconName}\n onMenuOpen={() => setIconName('chevron-up')}\n onMenuClose={() => setIconName('chevron-down')}\n tooltip={{body: iconName}}\n />\n </div>\n );\n};\n\nDynamicIconStory.args = {\n iconLeftName: 'chevron-down',\n ariaLabel: 'Icon Button Dropdown',\n menu: menuProps,\n size: 'medium',\n testId: 'button-dropdown-dynamic-icon-story',\n};\n\nconst complicatedMenuProps: MenuProps = {\n isFluid: false,\n menuDisabled: false,\n size: 'medium',\n groupTitleOptions: groupTitleComponentMenuOptions,\n};\n\nexport const TextAndDynamicIconStory = (args: ButtonDropdownProps) => {\n const [iconName, setIconName] = React.useState('chevron-down');\n return (\n <div className={css.menuBtnWrapper}>\n <ButtonDropdown\n {...args}\n iconRightName={iconName}\n onMenuOpen={() => setIconName('chevron-up')}\n onMenuClose={() => setIconName('chevron-down')}\n />\n </div>\n );\n};\n\nTextAndDynamicIconStory.args = {\n children: 'Long Button Text',\n iconRightName: 'chevron-down',\n iconRightType: 'regular',\n menu: complicatedMenuProps,\n size: 'medium',\n type: 'ghost',\n testId: 'button-dropdown-text-and-dynamic-icon-story',\n};\n\nconst checkboxMenuProps: MenuProps = {\n options: textOptions,\n optionsVariant: 'checkbox',\n selectedKeys: ['4'],\n isFluid: false,\n};\n\nexport const WithCheckboxMenuStory = (args: ButtonDropdownProps) => {\n const [btnText, setBtnText] = React.useState<React.ReactNode>('');\n const [selectedKeys, setSelectedKeys] = React.useState(\n args.menu?.selectedKeys || [],\n );\n\n React.useEffect(() => {\n setSelectedKeys(args.menu?.selectedKeys || []);\n const newBtnText = getButtonLabelFromSelectedKeys(\n args.menu?.selectedKeys,\n args.children,\n );\n setBtnText(newBtnText);\n }, [args.menu?.selectedKeys]);\n\n const handleChange = (option: MenuOption) => {\n const newSelectedKeys = getSelectedKeysFromSelectedOption(\n option,\n selectedKeys,\n );\n const newBtnText = getButtonLabelFromSelectedKeys(\n newSelectedKeys,\n args.children,\n );\n setSelectedKeys(newSelectedKeys);\n setBtnText(newBtnText);\n };\n\n return (\n <div className={css.menuBtnWrapper}>\n <ButtonDropdown\n {...args}\n menu={{...args.menu, selectedKeys}}\n onOptionSelect={handleChange}\n >\n {btnText}\n </ButtonDropdown>\n </div>\n );\n};\n\nWithCheckboxMenuStory.args = {\n children: 'Long Button Text',\n menu: checkboxMenuProps,\n size: 'medium',\n testId: 'button-dropdown-with-checkbox-menu-story',\n};\n\nconst radioMenuProps: MenuProps = {\n options: textOptions,\n optionsVariant: 'radio',\n selectedKeys: ['4'],\n isFluid: false,\n};\n\nexport const WithRadioMenuStory = (args: ButtonDropdownProps) => {\n const [selectedKeys, setSelectedKeys] = React.useState(\n args.menu?.selectedKeys || [],\n );\n\n React.useEffect(() => {\n setSelectedKeys(args.menu?.selectedKeys || []);\n }, [args.menu?.selectedKeys]);\n\n const handleChange = (option: MenuOption) => {\n setSelectedKeys([option.key]);\n };\n\n return (\n <div className={css.menuBtnWrapper}>\n <ButtonDropdown\n {...args}\n menu={{...args.menu, selectedKeys}}\n onOptionSelect={handleChange}\n >\n {args.children}\n </ButtonDropdown>\n </div>\n );\n};\n\nWithRadioMenuStory.args = {\n children: 'Long Button Text',\n menu: radioMenuProps,\n size: 'medium',\n testId: 'button-dropdown-with-radio-menu-story',\n};\nexport const WithHeaderAndFooterStory = (args: ButtonDropdownProps) => (\n <WithHeaderAndFooter {...args} />\n);\n\nexport const WithHeaderAndFooter = (args: ButtonDropdownProps) => {\n const clickAwayRef = React.useRef<ClickAwayRefType | null>(null);\n const [btnText, setBtnText] = React.useState<React.ReactNode>('');\n const [selectedKeys, setSelectedKeys] = React.useState(\n args.menu?.selectedKeys || [],\n );\n\n React.useEffect(() => {\n setSelectedKeys(args.menu?.selectedKeys || []);\n const newBtnText = getButtonLabelFromSelectedKeys(\n args.menu?.selectedKeys,\n args.children,\n );\n setBtnText(newBtnText);\n }, [args.menu?.selectedKeys]);\n\n const handleChange = (option: MenuOption) => {\n const newSelectedKeys = getSelectedKeysFromSelectedOption(\n option,\n selectedKeys,\n );\n const newBtnText = getButtonLabelFromSelectedKeys(\n newSelectedKeys,\n args.children,\n );\n setSelectedKeys(newSelectedKeys);\n setBtnText(newBtnText);\n };\n\n const onSave = () => {\n if (clickAwayRef.current) {\n clickAwayRef.current?.forceClose();\n }\n };\n\n return (\n <div className={css.menuBtnWrapper}>\n <ButtonDropdown\n {...args}\n menu={{\n ...args.menu,\n selectedKeys,\n header: <MenuHeader />,\n footer: <MenuFooter onSave={onSave} />,\n }}\n onOptionSelect={handleChange}\n clickAwayRef={clickAwayRef}\n >\n {btnText}\n </ButtonDropdown>\n </div>\n );\n};\n\nWithHeaderAndFooterStory.args = {\n children: 'Long Button Text',\n menu: {\n ...menuProps,\n header: <MenuHeader />,\n footer: <MenuFooter />,\n },\n size: 'medium',\n testId: 'button-dropdown-with-header-and-footer-story',\n};\nTextOnlyStory.storyName = 'Text Only';\nAccessibleButtonDropdownStory.storyName = 'Accessible (enableA11y)';\nTextWithTooltipStory.storyName = 'Text With Tooltip';\nWithSearchStory.storyName = 'With Search';\nTextAndIconStory.storyName = 'Text And Icon';\nIconOnlyStory.storyName = 'Icon Only';\nDynamicIconStory.storyName = 'Dynamic Icon';\nTextAndDynamicIconStory.storyName = 'Text And Dynamic Icon';\nWithCheckboxMenuStory.storyName = 'With Checkbox Menu';\nWithRadioMenuStory.storyName = 'With Radio Menu';\nWithHeaderAndFooterStory.storyName = 'With Header And Footer';\n"
|
|
298
298
|
},
|
|
299
299
|
"css": {
|
|
300
300
|
"path": "ButtonDropdown.module.css",
|
|
@@ -327,11 +327,11 @@
|
|
|
327
327
|
"files": {
|
|
328
328
|
"main": {
|
|
329
329
|
"path": "ButtonTabs.d.ts",
|
|
330
|
-
"content": "import * as React from 'react';\nimport type { Flow } from 'flow-to-typescript-codemod';\nimport type { AnchorType } from '../../components/ButtonDropdown';\nimport type { ElevationType } from '../../components/Tooltip';\ntype ClassNames = Readonly<{\n wrapper?: string;\n buttonTabDropdownWrapper?: string;\n}>;\nexport interface ButtonTabsProps {\n classNames?: ClassNames;\n children: React.ReactNode;\n isFluid?: boolean;\n size?: 'medium' | 'small';\n disabled?: boolean;\n selectedButtonTabId?: string;\n onButtonTabSelect?: ((id: string, e?: React.SyntheticEvent<HTMLElement> | null | undefined) => unknown) | null | undefined;\n wrapAfter?: number;\n elevation?: ElevationType;\n wrapTabTitle?: string;\n anchorPosition?: AnchorType;\n testId?: string;\n}\nexport declare const ButtonTabs: Flow.AbstractComponent<ButtonTabsProps, HTMLDivElement>;\nexport {};\n//# sourceMappingURL=ButtonTabs.d.ts.map"
|
|
330
|
+
"content": "import * as React from 'react';\nimport type { Flow } from 'flow-to-typescript-codemod';\nimport type { AnchorType } from '../../components/ButtonDropdown';\nimport type { ElevationType } from '../../components/Tooltip';\ntype ClassNames = Readonly<{\n wrapper?: string;\n buttonTabDropdownWrapper?: string;\n}>;\nexport interface ButtonTabsProps {\n classNames?: ClassNames;\n children: React.ReactNode;\n isFluid?: boolean;\n size?: 'medium' | 'small';\n disabled?: boolean;\n selectedButtonTabId?: string;\n onButtonTabSelect?: ((id: string, e?: React.SyntheticEvent<HTMLElement> | null | undefined) => unknown) | null | undefined;\n wrapAfter?: number;\n elevation?: ElevationType;\n wrapTabTitle?: string;\n /**\n * Opt in to the accessibility behavior for the overflow tab and the menu it\n * opens. Off by default, and off is this component exactly as it behaved\n * before any of it existed.\n */\n enableA11y?: boolean;\n anchorPosition?: AnchorType;\n testId?: string;\n}\nexport declare const ButtonTabs: Flow.AbstractComponent<ButtonTabsProps, HTMLDivElement>;\nexport {};\n//# sourceMappingURL=ButtonTabs.d.ts.map"
|
|
331
331
|
},
|
|
332
332
|
"story": {
|
|
333
333
|
"path": "ButtonTabs.stories.tsx",
|
|
334
|
-
"content": "import * as React from 'react';\n\nimport {ANCHOR_POSITION_TYPE} from 'src/components/ButtonDropdown';\nimport {ButtonTab} from 'src/components/ButtonTabs/ButtonTab';\nimport type {ButtonTabsProps} from 'src/components/ButtonTabs/ButtonTabs';\nimport {ButtonTabs} from 'src/components/ButtonTabs/ButtonTabs';\nimport {ELEVATION_TYPES} from 'src/components/Tooltip';\n\nimport css from 'src/components/ButtonTabs/ButtonTabs.stories.module.css';\n\n\nconst anchorPositionOptions: Array<unknown> = [\n ...Object.values(ANCHOR_POSITION_TYPE),\n];\nconst elevationOptions: Array<unknown> = [...Object.values(ELEVATION_TYPES)];\n\nexport default {\n tags: ['autodocs'],\n title: 'Components/Button Tabs/Button Tabs',\n component: ButtonTabs,\n argTypes: {\n classNames: {\n description: 'External classNames to be applied',\n control: {\n type: 'object',\n },\n table: {\n type: {summary: '{wrapper?: string}'},\n },\n },\n selectedButtonTabId: {\n description:\n 'Selected Id of the Button Tab, when the selectedId is same as id, Button Tab is updated to the selected state',\n control: {\n type: 'text',\n },\n table: {\n type: {summary: 'string'},\n },\n },\n onButtonTabSelect: {\n description:\n '**onClick** handler that is applied to the Button Tab. This is internally passed to the `ButtonTab` component',\n action: 'clicked',\n table: {\n type: {\n summary: '(id: string, e?: SyntheticEvent<HTMLElement>) => mixed',\n },\n },\n },\n elevation: {\n description:\n 'This is passed to ButtonTabDropdown. Elevation level for the dropdown menu controls the z-index stacking order. Default is set to \"modal\" elevation. Use higher elevation levels when rendering the dropdown above other elevated elements like Modal, Toast, etc.',\n control: {\n type: 'select',\n },\n options: elevationOptions,\n table: {\n type: {summary: 'enum'},\n defaultValue: {summary: 'modal'},\n },\n },\n size: {\n description:\n 'ButtonTabs support only two size variants namely small and medium. `small` < `medium`. This is internally passed to the `ButtonTab` component',\n control: {\n type: 'radio',\n },\n options: ['small', 'medium'],\n table: {\n type: {summary: 'enum'},\n defaultValue: {summary: 'medium'},\n },\n },\n disabled: {\n description:\n 'If **true**, the component is disabled. This is internally passed to the `ButtonTab` component',\n control: {\n type: 'boolean',\n },\n table: {\n type: {summary: 'boolean'},\n defaultValue: {summary: 'false'},\n },\n },\n isFluid: {\n description:\n 'If **true**, the component would take the container\\'s `width`. This is internally passed to the `ButtonTab` component',\n control: {\n type: 'boolean',\n },\n table: {\n type: {summary: 'boolean'},\n defaultValue: {summary: 'false'},\n },\n },\n children: {\n type: {required: true},\n description:\n 'The content of the ButtonTabs. This should always have list of `ButtonTab` as children',\n table: {\n type: {summary: 'React.Component | string'},\n },\n },\n wrapAfter: {\n description: 'Number of tabs to display before wrapping with a dropdown',\n control: {\n type: 'number',\n min: 0,\n },\n table: {\n type: {summary: 'number'},\n },\n },\n wrapTabTitle: {\n description: 'Title of the button tab that opens the wrapped dropdown',\n control: {\n type: 'text',\n },\n table: {\n type: {summary: 'string'},\n defaultValue: {summary: 'more'},\n },\n },\n anchorPosition: {\n options: anchorPositionOptions,\n description:\n 'Position of Menu relative to its reference element. It can be any one value from the dropdown',\n control: {\n type: 'select',\n },\n table: {\n type: {summary: 'enum'},\n defaultValue: {summary: 'bottom-end'},\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 ButtonTabs component.',\n description: {\n component: `\n\\`\\`\\`js\nimport { ButtonTabs, ButtonTab } from \"@spaced-out/ui-design-system/lib/components/ButtonTabs\";\n\\`\\`\\`\n**ButtonTabs** accepts multiple **ButtonTab** elements as children. \\`ButtonTab\\` make it easy to switch between different views.\nA selection of tab can be done by passing selectedId to the \\`ButtonTab\\` component. Some props like **size**, **isFluid**\nand **disabled** are propagated as is to the children \\`ButtonTab\\`\n `,\n },\n },\n storySource: {\n componentPath: '/src/components/ButtonTabs/ButtonTabs',\n },\n },\n};\n\nexport const TextOnlyStory = (args: ButtonTabsProps) => {\n const [selectedId, setSelectedId] = React.useState(args.selectedButtonTabId);\n\n return (\n <div className={css.container}>\n <ButtonTabs\n {...args}\n selectedButtonTabId={selectedId}\n classNames={{wrapper: css.wrapper}}\n onButtonTabSelect={(id) => setSelectedId(id)}\n >\n <ButtonTab id=\"1\">School Bus</ButtonTab>\n <ButtonTab id=\"2\">Tram Train</ButtonTab>\n <ButtonTab id=\"3\">Airplane Arrival</ButtonTab>\n <ButtonTab id=\"4\">Space Shuttle</ButtonTab>\n <ButtonTab id=\"5\">Fighter Jet</ButtonTab>\n </ButtonTabs>\n </div>\n );\n};\n\nTextOnlyStory.args = {\n selectedButtonTabId: '4',\n testId: 'button-tabs-text-only-story',\n};\n\nexport const TextAndIconStory = (args: ButtonTabsProps) => {\n const [selectedId, setSelectedId] = React.useState(args.selectedButtonTabId);\n\n return (\n <div className={css.container}>\n <ButtonTabs\n {...args}\n selectedButtonTabId={selectedId}\n classNames={{wrapper: css.wrapper}}\n onButtonTabSelect={(id) => setSelectedId(id)}\n >\n <ButtonTab id=\"1\" iconType=\"duotone\" iconName=\"bus-school\">\n School Bus\n </ButtonTab>\n <ButtonTab id=\"2\" iconType=\"duotone\" iconName=\"train-tram\">\n Tram Train\n </ButtonTab>\n <ButtonTab id=\"3\" iconType=\"duotone\" iconName=\"plane-arrival\">\n Airplane Arrival\n </ButtonTab>\n <ButtonTab id=\"4\" iconType=\"duotone\" iconName=\"shuttle-space\">\n Space Shuttle\n </ButtonTab>\n <ButtonTab id=\"5\" iconType=\"duotone\" iconName=\"jet-fighter\">\n Fighter Jet\n </ButtonTab>\n </ButtonTabs>\n </div>\n );\n};\n\nTextAndIconStory.args = {\n selectedButtonTabId: '2',\n testId: 'button-tabs-text-and-icon-story',\n};\n\nexport const IconOnlyStory = (args: ButtonTabsProps) => {\n const [selectedId, setSelectedId] = React.useState(args.selectedButtonTabId);\n\n return (\n <div className={css.container}>\n <ButtonTabs\n {...args}\n selectedButtonTabId={selectedId}\n classNames={{wrapper: css.wrapper}}\n onButtonTabSelect={(id) => setSelectedId(id)}\n >\n <ButtonTab id=\"1\" iconType=\"duotone\" iconName=\"bus-school\"></ButtonTab>\n <ButtonTab id=\"2\" iconType=\"duotone\" iconName=\"train-tram\"></ButtonTab>\n <ButtonTab\n id=\"3\"\n iconType=\"duotone\"\n iconName=\"plane-arrival\"\n ></ButtonTab>\n <ButtonTab\n id=\"4\"\n iconType=\"duotone\"\n iconName=\"shuttle-space\"\n ></ButtonTab>\n <ButtonTab id=\"5\" iconType=\"duotone\" iconName=\"jet-fighter\"></ButtonTab>\n <ButtonTab id=\"6\" iconType=\"duotone\" iconName=\"banana\"></ButtonTab>\n <ButtonTab id=\"7\" iconType=\"duotone\" iconName=\"citrus\"></ButtonTab>\n <ButtonTab id=\"8\" iconType=\"duotone\" iconName=\"pineapple\"></ButtonTab>\n <ButtonTab id=\"9\" iconType=\"duotone\" iconName=\"lemon\"></ButtonTab>\n <ButtonTab id=\"10\" iconType=\"duotone\" iconName=\"pie\"></ButtonTab>\n <ButtonTab id=\"11\" iconType=\"duotone\" iconName=\"pear\"></ButtonTab>\n <ButtonTab id=\"12\" iconType=\"duotone\" iconName=\"grapes\"></ButtonTab>\n <ButtonTab id=\"13\" iconType=\"duotone\" iconName=\"mango\"></ButtonTab>\n <ButtonTab id=\"14\" iconType=\"duotone\" iconName=\"avocado\"></ButtonTab>\n <ButtonTab id=\"15\" iconType=\"duotone\" iconName=\"leaf\"></ButtonTab>\n </ButtonTabs>\n </div>\n );\n};\n\nIconOnlyStory.args = {\n selectedButtonTabId: '3',\n testId: 'button-tabs-icon-only-story',\n};\n\nexport const IconOnlyWrappedStory = (args: ButtonTabsProps) => {\n const [selectedId, setSelectedId] = React.useState(args.selectedButtonTabId);\n\n return (\n <div className={css.container}>\n <ButtonTabs\n {...args}\n selectedButtonTabId={selectedId}\n classNames={{wrapper: css.wrapper}}\n onButtonTabSelect={(id) => setSelectedId(id)}\n >\n <ButtonTab id=\"1\" iconType=\"duotone\" iconName=\"bus-school\"></ButtonTab>\n <ButtonTab id=\"2\" iconType=\"duotone\" iconName=\"train-tram\"></ButtonTab>\n <ButtonTab\n id=\"3\"\n iconType=\"duotone\"\n iconName=\"plane-arrival\"\n ></ButtonTab>\n <ButtonTab\n id=\"4\"\n iconType=\"duotone\"\n iconName=\"shuttle-space\"\n ></ButtonTab>\n <ButtonTab id=\"5\" iconType=\"duotone\" iconName=\"jet-fighter\"></ButtonTab>\n <ButtonTab\n id=\"6\"\n iconType=\"duotone\"\n iconName=\"banana\"\n disabled\n ></ButtonTab>\n <ButtonTab\n id=\"7\"\n iconType=\"duotone\"\n iconName=\"citrus\"\n disabled\n ></ButtonTab>\n <ButtonTab id=\"8\" iconType=\"duotone\" iconName=\"pineapple\"></ButtonTab>\n <ButtonTab id=\"9\" iconType=\"duotone\" iconName=\"lemon\"></ButtonTab>\n <ButtonTab id=\"10\" iconType=\"duotone\" iconName=\"pie\"></ButtonTab>\n <ButtonTab id=\"11\" iconType=\"duotone\" iconName=\"pear\"></ButtonTab>\n <ButtonTab id=\"12\" iconType=\"duotone\" iconName=\"grapes\"></ButtonTab>\n <ButtonTab\n id=\"13\"\n iconType=\"duotone\"\n iconName=\"mango\"\n disabled\n ></ButtonTab>\n <ButtonTab\n id=\"14\"\n iconType=\"duotone\"\n iconName=\"avocado\"\n disabled\n ></ButtonTab>\n <ButtonTab id=\"15\" iconType=\"duotone\" iconName=\"leaf\"></ButtonTab>\n </ButtonTabs>\n </div>\n );\n};\n\nIconOnlyWrappedStory.args = {\n selectedButtonTabId: '1',\n wrapAfter: 3,\n testId: 'button-tabs-icon-only-wrapped-story',\n};\n\nexport const WrappedStory = (args: ButtonTabsProps) => {\n const [selectedId, setSelectedId] = React.useState(args.selectedButtonTabId);\n const onButtonTabSelectHandler = (\n id: string,\n e: React.SyntheticEvent<HTMLElement> | null | undefined,\n ): void => {\n args.onButtonTabSelect?.(id, e);\n setSelectedId(id);\n };\n\n return (\n <div className={css.container}>\n <ButtonTabs\n {...args}\n classNames={{wrapper: css.wrapper}}\n onButtonTabSelect={onButtonTabSelectHandler}\n selectedButtonTabId={selectedId}\n >\n <ButtonTab id=\"1\" iconType=\"duotone\" iconName=\"bus-school\">\n School Bus\n </ButtonTab>\n <ButtonTab id=\"2\" iconType=\"duotone\" iconName=\"train-tram\">\n Tram Train\n </ButtonTab>\n <ButtonTab id=\"3\" iconType=\"duotone\" iconName=\"plane-arrival\">\n Airplane Arrival\n </ButtonTab>\n <ButtonTab id=\"4\" iconType=\"duotone\" iconName=\"shuttle-space\">\n Space Shuttle\n </ButtonTab>\n <ButtonTab id=\"5\" iconType=\"duotone\" iconName=\"jet-fighter\">\n Fighter Jet\n </ButtonTab>\n </ButtonTabs>\n </div>\n );\n};\n\nWrappedStory.args = {\n selectedButtonTabId: '1',\n wrapAfter: 3,\n testId: 'button-tabs-wrapped-story',\n};\n\nexport const DisabledButtonsStory = (args: ButtonTabsProps) => {\n const [selectedId, setSelectedId] = React.useState(args.selectedButtonTabId);\n\n return (\n <div className={css.container}>\n <ButtonTabs\n {...args}\n selectedButtonTabId={selectedId}\n classNames={{wrapper: css.wrapper}}\n onButtonTabSelect={(id) => setSelectedId(id)}\n >\n <ButtonTab id=\"1\">School Bus</ButtonTab>\n <ButtonTab id=\"2\" disabled>\n Tram Train\n </ButtonTab>\n <ButtonTab id=\"3\" disabled>\n Airplane Arrival\n </ButtonTab>\n <ButtonTab id=\"4\">Space Shuttle</ButtonTab>\n <ButtonTab id=\"5\">Fighter Jet</ButtonTab>\n </ButtonTabs>\n </div>\n );\n};\n\nDisabledButtonsStory.args = {\n selectedButtonTabId: '1',\n disabled: true,\n testId: 'button-tabs-disabled-buttons-story',\n};\nTextOnlyStory.storyName = 'Text Only';\nTextAndIconStory.storyName = 'Text And Icon';\nIconOnlyStory.storyName = 'Icon Only';\nIconOnlyWrappedStory.storyName = 'Icon Only Wrapped';\nWrappedStory.storyName = 'Wrapped';\nDisabledButtonsStory.storyName = 'Disabled Buttons';\n"
|
|
334
|
+
"content": "import * as React from 'react';\n\nimport {ANCHOR_POSITION_TYPE} from 'src/components/ButtonDropdown';\nimport {ButtonTab} from 'src/components/ButtonTabs/ButtonTab';\nimport type {ButtonTabsProps} from 'src/components/ButtonTabs/ButtonTabs';\nimport {ButtonTabs} from 'src/components/ButtonTabs/ButtonTabs';\nimport {ELEVATION_TYPES} from 'src/components/Tooltip';\n\nimport css from 'src/components/ButtonTabs/ButtonTabs.stories.module.css';\n\n\nconst anchorPositionOptions: Array<unknown> = [\n ...Object.values(ANCHOR_POSITION_TYPE),\n];\nconst elevationOptions: Array<unknown> = [...Object.values(ELEVATION_TYPES)];\n\nexport default {\n tags: ['autodocs'],\n title: 'Components/Button Tabs/Button Tabs',\n component: ButtonTabs,\n argTypes: {\n enableA11y: {\n description:\n '**enableA11y** opts into the accessibility behavior: `aria-haspopup` / `aria-expanded` / `aria-controls` on the trigger, the arrow key that opens the popup, and focus moving into the menu and back. Off by default, where `ButtonTabs` behaves exactly as it did before any of it existed. Off here too, as everywhere: turn the control on to see the accessible behavior.',\n control: {type: 'boolean'},\n table: {\n type: {summary: 'boolean'},\n defaultValue: {summary: 'false'},\n },\n },\n classNames: {\n description: 'External classNames to be applied',\n control: {\n type: 'object',\n },\n table: {\n type: {summary: '{wrapper?: string}'},\n },\n },\n selectedButtonTabId: {\n description:\n 'Selected Id of the Button Tab, when the selectedId is same as id, Button Tab is updated to the selected state',\n control: {\n type: 'text',\n },\n table: {\n type: {summary: 'string'},\n },\n },\n onButtonTabSelect: {\n description:\n '**onClick** handler that is applied to the Button Tab. This is internally passed to the `ButtonTab` component',\n action: 'clicked',\n table: {\n type: {\n summary: '(id: string, e?: SyntheticEvent<HTMLElement>) => mixed',\n },\n },\n },\n elevation: {\n description:\n 'This is passed to ButtonTabDropdown. Elevation level for the dropdown menu controls the z-index stacking order. Default is set to \"modal\" elevation. Use higher elevation levels when rendering the dropdown above other elevated elements like Modal, Toast, etc.',\n control: {\n type: 'select',\n },\n options: elevationOptions,\n table: {\n type: {summary: 'enum'},\n defaultValue: {summary: 'modal'},\n },\n },\n size: {\n description:\n 'ButtonTabs support only two size variants namely small and medium. `small` < `medium`. This is internally passed to the `ButtonTab` component',\n control: {\n type: 'radio',\n },\n options: ['small', 'medium'],\n table: {\n type: {summary: 'enum'},\n defaultValue: {summary: 'medium'},\n },\n },\n disabled: {\n description:\n 'If **true**, the component is disabled. This is internally passed to the `ButtonTab` component',\n control: {\n type: 'boolean',\n },\n table: {\n type: {summary: 'boolean'},\n defaultValue: {summary: 'false'},\n },\n },\n isFluid: {\n description:\n 'If **true**, the component would take the container\\'s `width`. This is internally passed to the `ButtonTab` component',\n control: {\n type: 'boolean',\n },\n table: {\n type: {summary: 'boolean'},\n defaultValue: {summary: 'false'},\n },\n },\n children: {\n type: {required: true},\n description:\n 'The content of the ButtonTabs. This should always have list of `ButtonTab` as children',\n table: {\n type: {summary: 'React.Component | string'},\n },\n },\n wrapAfter: {\n description: 'Number of tabs to display before wrapping with a dropdown',\n control: {\n type: 'number',\n min: 0,\n },\n table: {\n type: {summary: 'number'},\n },\n },\n wrapTabTitle: {\n description: 'Title of the button tab that opens the wrapped dropdown',\n control: {\n type: 'text',\n },\n table: {\n type: {summary: 'string'},\n defaultValue: {summary: 'more'},\n },\n },\n anchorPosition: {\n options: anchorPositionOptions,\n description:\n 'Position of Menu relative to its reference element. It can be any one value from the dropdown',\n control: {\n type: 'select',\n },\n table: {\n type: {summary: 'enum'},\n defaultValue: {summary: 'bottom-end'},\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 ButtonTabs component.',\n description: {\n component: `\n\\`\\`\\`js\nimport { ButtonTabs, ButtonTab } from \"@spaced-out/ui-design-system/lib/components/ButtonTabs\";\n\\`\\`\\`\n**ButtonTabs** accepts multiple **ButtonTab** elements as children. \\`ButtonTab\\` make it easy to switch between different views.\nA selection of tab can be done by passing selectedId to the \\`ButtonTab\\` component. Some props like **size**, **isFluid**\nand **disabled** are propagated as is to the children \\`ButtonTab\\`\n `,\n },\n },\n storySource: {\n componentPath: '/src/components/ButtonTabs/ButtonTabs',\n },\n },\n};\n\nexport const TextOnlyStory = (args: ButtonTabsProps) => {\n const [selectedId, setSelectedId] = React.useState(args.selectedButtonTabId);\n\n return (\n <div className={css.container}>\n <ButtonTabs\n {...args}\n selectedButtonTabId={selectedId}\n classNames={{wrapper: css.wrapper}}\n onButtonTabSelect={(id) => setSelectedId(id)}\n >\n <ButtonTab id=\"1\">School Bus</ButtonTab>\n <ButtonTab id=\"2\">Tram Train</ButtonTab>\n <ButtonTab id=\"3\">Airplane Arrival</ButtonTab>\n <ButtonTab id=\"4\">Space Shuttle</ButtonTab>\n <ButtonTab id=\"5\">Fighter Jet</ButtonTab>\n </ButtonTabs>\n </div>\n );\n};\n\nTextOnlyStory.args = {\n selectedButtonTabId: '4',\n testId: 'button-tabs-text-only-story',\n};\n\nexport const TextAndIconStory = (args: ButtonTabsProps) => {\n const [selectedId, setSelectedId] = React.useState(args.selectedButtonTabId);\n\n return (\n <div className={css.container}>\n <ButtonTabs\n {...args}\n selectedButtonTabId={selectedId}\n classNames={{wrapper: css.wrapper}}\n onButtonTabSelect={(id) => setSelectedId(id)}\n >\n <ButtonTab id=\"1\" iconType=\"duotone\" iconName=\"bus-school\">\n School Bus\n </ButtonTab>\n <ButtonTab id=\"2\" iconType=\"duotone\" iconName=\"train-tram\">\n Tram Train\n </ButtonTab>\n <ButtonTab id=\"3\" iconType=\"duotone\" iconName=\"plane-arrival\">\n Airplane Arrival\n </ButtonTab>\n <ButtonTab id=\"4\" iconType=\"duotone\" iconName=\"shuttle-space\">\n Space Shuttle\n </ButtonTab>\n <ButtonTab id=\"5\" iconType=\"duotone\" iconName=\"jet-fighter\">\n Fighter Jet\n </ButtonTab>\n </ButtonTabs>\n </div>\n );\n};\n\nTextAndIconStory.args = {\n selectedButtonTabId: '2',\n testId: 'button-tabs-text-and-icon-story',\n};\n\nexport const IconOnlyStory = (args: ButtonTabsProps) => {\n const [selectedId, setSelectedId] = React.useState(args.selectedButtonTabId);\n\n return (\n <div className={css.container}>\n <ButtonTabs\n {...args}\n selectedButtonTabId={selectedId}\n classNames={{wrapper: css.wrapper}}\n onButtonTabSelect={(id) => setSelectedId(id)}\n >\n <ButtonTab id=\"1\" iconType=\"duotone\" iconName=\"bus-school\"></ButtonTab>\n <ButtonTab id=\"2\" iconType=\"duotone\" iconName=\"train-tram\"></ButtonTab>\n <ButtonTab\n id=\"3\"\n iconType=\"duotone\"\n iconName=\"plane-arrival\"\n ></ButtonTab>\n <ButtonTab\n id=\"4\"\n iconType=\"duotone\"\n iconName=\"shuttle-space\"\n ></ButtonTab>\n <ButtonTab id=\"5\" iconType=\"duotone\" iconName=\"jet-fighter\"></ButtonTab>\n <ButtonTab id=\"6\" iconType=\"duotone\" iconName=\"banana\"></ButtonTab>\n <ButtonTab id=\"7\" iconType=\"duotone\" iconName=\"citrus\"></ButtonTab>\n <ButtonTab id=\"8\" iconType=\"duotone\" iconName=\"pineapple\"></ButtonTab>\n <ButtonTab id=\"9\" iconType=\"duotone\" iconName=\"lemon\"></ButtonTab>\n <ButtonTab id=\"10\" iconType=\"duotone\" iconName=\"pie\"></ButtonTab>\n <ButtonTab id=\"11\" iconType=\"duotone\" iconName=\"pear\"></ButtonTab>\n <ButtonTab id=\"12\" iconType=\"duotone\" iconName=\"grapes\"></ButtonTab>\n <ButtonTab id=\"13\" iconType=\"duotone\" iconName=\"mango\"></ButtonTab>\n <ButtonTab id=\"14\" iconType=\"duotone\" iconName=\"avocado\"></ButtonTab>\n <ButtonTab id=\"15\" iconType=\"duotone\" iconName=\"leaf\"></ButtonTab>\n </ButtonTabs>\n </div>\n );\n};\n\nIconOnlyStory.args = {\n selectedButtonTabId: '3',\n testId: 'button-tabs-icon-only-story',\n};\n\nexport const IconOnlyWrappedStory = (args: ButtonTabsProps) => {\n const [selectedId, setSelectedId] = React.useState(args.selectedButtonTabId);\n\n return (\n <div className={css.container}>\n <ButtonTabs\n {...args}\n selectedButtonTabId={selectedId}\n classNames={{wrapper: css.wrapper}}\n onButtonTabSelect={(id) => setSelectedId(id)}\n >\n <ButtonTab id=\"1\" iconType=\"duotone\" iconName=\"bus-school\"></ButtonTab>\n <ButtonTab id=\"2\" iconType=\"duotone\" iconName=\"train-tram\"></ButtonTab>\n <ButtonTab\n id=\"3\"\n iconType=\"duotone\"\n iconName=\"plane-arrival\"\n ></ButtonTab>\n <ButtonTab\n id=\"4\"\n iconType=\"duotone\"\n iconName=\"shuttle-space\"\n ></ButtonTab>\n <ButtonTab id=\"5\" iconType=\"duotone\" iconName=\"jet-fighter\"></ButtonTab>\n <ButtonTab\n id=\"6\"\n iconType=\"duotone\"\n iconName=\"banana\"\n disabled\n ></ButtonTab>\n <ButtonTab\n id=\"7\"\n iconType=\"duotone\"\n iconName=\"citrus\"\n disabled\n ></ButtonTab>\n <ButtonTab id=\"8\" iconType=\"duotone\" iconName=\"pineapple\"></ButtonTab>\n <ButtonTab id=\"9\" iconType=\"duotone\" iconName=\"lemon\"></ButtonTab>\n <ButtonTab id=\"10\" iconType=\"duotone\" iconName=\"pie\"></ButtonTab>\n <ButtonTab id=\"11\" iconType=\"duotone\" iconName=\"pear\"></ButtonTab>\n <ButtonTab id=\"12\" iconType=\"duotone\" iconName=\"grapes\"></ButtonTab>\n <ButtonTab\n id=\"13\"\n iconType=\"duotone\"\n iconName=\"mango\"\n disabled\n ></ButtonTab>\n <ButtonTab\n id=\"14\"\n iconType=\"duotone\"\n iconName=\"avocado\"\n disabled\n ></ButtonTab>\n <ButtonTab id=\"15\" iconType=\"duotone\" iconName=\"leaf\"></ButtonTab>\n </ButtonTabs>\n </div>\n );\n};\n\nIconOnlyWrappedStory.args = {\n selectedButtonTabId: '1',\n wrapAfter: 3,\n testId: 'button-tabs-icon-only-wrapped-story',\n};\n\nexport const WrappedStory = (args: ButtonTabsProps) => {\n const [selectedId, setSelectedId] = React.useState(args.selectedButtonTabId);\n const onButtonTabSelectHandler = (\n id: string,\n e: React.SyntheticEvent<HTMLElement> | null | undefined,\n ): void => {\n args.onButtonTabSelect?.(id, e);\n setSelectedId(id);\n };\n\n return (\n <div className={css.container}>\n <ButtonTabs\n {...args}\n classNames={{wrapper: css.wrapper}}\n onButtonTabSelect={onButtonTabSelectHandler}\n selectedButtonTabId={selectedId}\n >\n <ButtonTab id=\"1\" iconType=\"duotone\" iconName=\"bus-school\">\n School Bus\n </ButtonTab>\n <ButtonTab id=\"2\" iconType=\"duotone\" iconName=\"train-tram\">\n Tram Train\n </ButtonTab>\n <ButtonTab id=\"3\" iconType=\"duotone\" iconName=\"plane-arrival\">\n Airplane Arrival\n </ButtonTab>\n <ButtonTab id=\"4\" iconType=\"duotone\" iconName=\"shuttle-space\">\n Space Shuttle\n </ButtonTab>\n <ButtonTab id=\"5\" iconType=\"duotone\" iconName=\"jet-fighter\">\n Fighter Jet\n </ButtonTab>\n </ButtonTabs>\n </div>\n );\n};\n\nWrappedStory.args = {\n selectedButtonTabId: '1',\n wrapAfter: 3,\n testId: 'button-tabs-wrapped-story',\n};\n\nexport const DisabledButtonsStory = (args: ButtonTabsProps) => {\n const [selectedId, setSelectedId] = React.useState(args.selectedButtonTabId);\n\n return (\n <div className={css.container}>\n <ButtonTabs\n {...args}\n selectedButtonTabId={selectedId}\n classNames={{wrapper: css.wrapper}}\n onButtonTabSelect={(id) => setSelectedId(id)}\n >\n <ButtonTab id=\"1\">School Bus</ButtonTab>\n <ButtonTab id=\"2\" disabled>\n Tram Train\n </ButtonTab>\n <ButtonTab id=\"3\" disabled>\n Airplane Arrival\n </ButtonTab>\n <ButtonTab id=\"4\">Space Shuttle</ButtonTab>\n <ButtonTab id=\"5\">Fighter Jet</ButtonTab>\n </ButtonTabs>\n </div>\n );\n};\n\nDisabledButtonsStory.args = {\n selectedButtonTabId: '1',\n disabled: true,\n testId: 'button-tabs-disabled-buttons-story',\n};\nTextOnlyStory.storyName = 'Text Only';\nTextAndIconStory.storyName = 'Text And Icon';\nIconOnlyStory.storyName = 'Icon Only';\nIconOnlyWrappedStory.storyName = 'Icon Only Wrapped';\nWrappedStory.storyName = 'Wrapped';\nDisabledButtonsStory.storyName = 'Disabled Buttons';\n"
|
|
335
335
|
},
|
|
336
336
|
"css": {
|
|
337
337
|
"path": "ButtonTabs.module.css",
|
|
@@ -344,7 +344,7 @@
|
|
|
344
344
|
"additional": {
|
|
345
345
|
"ButtonTab/ButtonTab.d.ts": {
|
|
346
346
|
"path": "ButtonTab/ButtonTab.d.ts",
|
|
347
|
-
"content": "import * as React from 'react';\nimport type { Flow } from 'flow-to-typescript-codemod';\nimport type { IconType } from '../../../components/Icon';\ntype ClassNames = Readonly<{\n wrapper?: string;\n}>;\nexport interface ButtonTabProps {\n classNames?: ClassNames;\n children?: React.ReactNode;\n disabled?: boolean;\n onButtonTabSelect?: ((id: string, e?: React.SyntheticEvent<HTMLElement> | null | undefined) => unknown) | null | undefined;\n iconName?: string;\n iconType?: IconType;\n size?: 'medium' | 'small';\n isFluid?: boolean;\n ariaLabel?: string;\n id: string;\n selectedButtonTabId?: string;\n isLoading?: boolean;\n isLeftCurved?: boolean;\n isRightCurved?: boolean;\n testId?: string;\n}\nexport declare const ButtonTab: Flow.AbstractComponent<ButtonTabProps, HTMLButtonElement>;\nexport {};\n//# sourceMappingURL=ButtonTab.d.ts.map"
|
|
347
|
+
"content": "import * as React from 'react';\nimport type { Flow } from 'flow-to-typescript-codemod';\nimport type { IconType } from '../../../components/Icon';\ntype ClassNames = Readonly<{\n wrapper?: string;\n}>;\n/**\n * Attributes forwarded straight to the underlying `<button>`. Declared rather\n * than relied on: `ButtonTab` spreads its rest props, so these reached the DOM\n * whether or not the type mentioned them. `id` is NOT among them — this\n * component takes `id` as the tab's own identity, which is why a menu trigger\n * names its listbox with `ariaLabel` instead.\n */\ntype ForwardedButtonProps = Pick<React.ButtonHTMLAttributes<HTMLButtonElement>, 'aria-haspopup' | 'aria-expanded' | 'aria-controls' | 'onKeyDown'>;\nexport interface ButtonTabProps extends ForwardedButtonProps {\n classNames?: ClassNames;\n children?: React.ReactNode;\n disabled?: boolean;\n onButtonTabSelect?: ((id: string, e?: React.SyntheticEvent<HTMLElement> | null | undefined) => unknown) | null | undefined;\n iconName?: string;\n iconType?: IconType;\n size?: 'medium' | 'small';\n isFluid?: boolean;\n ariaLabel?: string;\n id: string;\n selectedButtonTabId?: string;\n isLoading?: boolean;\n isLeftCurved?: boolean;\n isRightCurved?: boolean;\n testId?: string;\n}\nexport declare const ButtonTab: Flow.AbstractComponent<ButtonTabProps, HTMLButtonElement>;\nexport {};\n//# sourceMappingURL=ButtonTab.d.ts.map"
|
|
348
348
|
},
|
|
349
349
|
"ButtonTab/index.d.ts": {
|
|
350
350
|
"path": "ButtonTab/index.d.ts",
|
|
@@ -352,7 +352,7 @@
|
|
|
352
352
|
},
|
|
353
353
|
"ButtonTabDropdown.d.ts": {
|
|
354
354
|
"path": "ButtonTabDropdown.d.ts",
|
|
355
|
-
"content": "import * as React from 'react';\nimport type { AnchorType } from '../../components/ButtonDropdown';\nimport type { ButtonTabProps } from '../../components/ButtonTabs/ButtonTab';\nimport type { ElevationType } from '../../components/Tooltip';\nexport interface ButtonTabDropdownProps extends Omit<ButtonTabProps, 'title' | 'elevation' | 'dropdownClass' | 'anchorPosition'> {\n title: string;\n elevation?: ElevationType;\n dropdownClass?: string;\n anchorPosition?: AnchorType;\n testId?: string;\n}\nexport declare const ButtonTabDropdown: ({ title, elevation, anchorPosition, testId, ...buttonTabProps }: ButtonTabDropdownProps) => React.JSX.Element;\n//# sourceMappingURL=ButtonTabDropdown.d.ts.map"
|
|
355
|
+
"content": "import * as React from 'react';\nimport type { AnchorType } from '../../components/ButtonDropdown';\nimport type { ButtonTabProps } from '../../components/ButtonTabs/ButtonTab';\nimport type { ElevationType } from '../../components/Tooltip';\nexport interface ButtonTabDropdownProps extends Omit<ButtonTabProps, 'title' | 'elevation' | 'dropdownClass' | 'anchorPosition'> {\n title: string;\n elevation?: ElevationType;\n dropdownClass?: string;\n /**\n * Opt in to the accessibility behavior: `aria-haspopup` / `aria-expanded` /\n * `aria-controls` on the tab, the arrow key that opens the popup, and focus\n * moving into the menu and back. Forwarded to the `Menu` this opens.\n *\n * Off by default, and off is this component exactly as it behaved before any\n * of it existed.\n */\n enableA11y?: boolean;\n anchorPosition?: AnchorType;\n testId?: string;\n}\nexport declare const ButtonTabDropdown: ({ title, elevation, anchorPosition, enableA11y, testId, ...buttonTabProps }: ButtonTabDropdownProps) => React.JSX.Element;\n//# sourceMappingURL=ButtonTabDropdown.d.ts.map"
|
|
356
356
|
}
|
|
357
357
|
}
|
|
358
358
|
},
|
|
@@ -760,11 +760,11 @@
|
|
|
760
760
|
"files": {
|
|
761
761
|
"main": {
|
|
762
762
|
"path": "Combobox.d.ts",
|
|
763
|
-
"content": "import * as React from 'react';\nimport type { Flow } from 'flow-to-typescript-codemod';\nimport type { IconType } from '../../components/Icon';\nimport type { InputOnChangeParamsType } from '../../components/Input';\nimport type { MenuOption, MenuProps } from '../../components/Menu';\ntype InputClassNames = Readonly<{\n box?: string;\n iconLeft?: string;\n iconRight?: string;\n wrapper?: string;\n}>;\ntype DropdownClassNames = Readonly<{\n wrapper?: string;\n box?: string;\n iconRight?: string;\n}>;\ntype ClassNames = Readonly<{\n wrapper?: string;\n box?: string;\n input?: InputClassNames;\n dropdown?: DropdownClassNames;\n}>;\nexport interface ComboboxProps {\n classNames?: ClassNames;\n disabled?: boolean;\n type?: 'text' | 'tel';\n label?: string | React.ReactNode;\n size?: 'medium' | 'small';\n onContainerClick?: React.MouseEventHandler<HTMLDivElement>;\n locked?: boolean;\n error?: boolean;\n errorText?: string;\n helperText?: string | React.ReactNode;\n required?: boolean;\n readOnly?: boolean;\n boxRef?: React.Ref<HTMLDivElement>;\n value: {\n dropdown?: string;\n input?: string;\n };\n onChange: (arg1: {\n input: string;\n dropdown: string;\n inputChange?: InputOnChangeParamsType;\n dropdownOption?: MenuOption;\n }) => unknown;\n inputPlaceholder?: string;\n onInputFocus?: React.FocusEventHandler<HTMLInputElement>;\n onInputBlur?: React.FocusEventHandler<HTMLInputElement>;\n onInputKeyDown?: React.KeyboardEventHandler<HTMLInputElement>;\n onInputContainerClick?: React.MouseEventHandler<HTMLDivElement>;\n inputName?: string;\n iconLeftName?: string;\n iconLeftType?: IconType;\n iconRightName?: string;\n iconRightType?: IconType;\n onIconRightClick?: ((arg1: React.SyntheticEvent<HTMLElement>) => unknown) | null | undefined;\n inputBoxRef?: React.Ref<HTMLInputElement>;\n minLength?: number;\n maxLength?: number;\n pattern?: string;\n min?: string;\n max?: string;\n menu?: MenuProps;\n onMenuOpen?: () => unknown;\n onMenuClose?: () => unknown;\n scrollMenuToBottom?: boolean;\n onDropdownFocus?: React.FocusEventHandler<HTMLInputElement>;\n onDropdownBlur?: React.FocusEventHandler<HTMLInputElement>;\n onDropdownKeyDown?: React.KeyboardEventHandler<HTMLInputElement>;\n onDropdownContainerClick?: React.MouseEventHandler<HTMLDivElement>;\n dropdownName?: string;\n dropdownPlaceholder?: string;\n dropdownBoxRef?: React.Ref<HTMLDivElement>;\n testId?: string;\n}\nexport declare const Combobox: Flow.AbstractComponent<ComboboxProps, HTMLDivElement>;\nexport {};\n//# sourceMappingURL=Combobox.d.ts.map"
|
|
763
|
+
"content": "import * as React from 'react';\nimport type { Flow } from 'flow-to-typescript-codemod';\nimport type { IconType } from '../../components/Icon';\nimport type { InputOnChangeParamsType } from '../../components/Input';\nimport type { MenuOption, MenuProps } from '../../components/Menu';\ntype InputClassNames = Readonly<{\n box?: string;\n iconLeft?: string;\n iconRight?: string;\n wrapper?: string;\n}>;\ntype DropdownClassNames = Readonly<{\n wrapper?: string;\n box?: string;\n iconRight?: string;\n}>;\ntype ClassNames = Readonly<{\n wrapper?: string;\n box?: string;\n input?: InputClassNames;\n dropdown?: DropdownClassNames;\n}>;\nexport interface ComboboxProps {\n classNames?: ClassNames;\n disabled?: boolean;\n type?: 'text' | 'tel';\n label?: string | React.ReactNode;\n size?: 'medium' | 'small';\n onContainerClick?: React.MouseEventHandler<HTMLDivElement>;\n locked?: boolean;\n error?: boolean;\n errorText?: string;\n helperText?: string | React.ReactNode;\n required?: boolean;\n readOnly?: boolean;\n boxRef?: React.Ref<HTMLDivElement>;\n value: {\n dropdown?: string;\n input?: string;\n };\n onChange: (arg1: {\n input: string;\n dropdown: string;\n inputChange?: InputOnChangeParamsType;\n dropdownOption?: MenuOption;\n }) => unknown;\n inputPlaceholder?: string;\n onInputFocus?: React.FocusEventHandler<HTMLInputElement>;\n onInputBlur?: React.FocusEventHandler<HTMLInputElement>;\n onInputKeyDown?: React.KeyboardEventHandler<HTMLInputElement>;\n onInputContainerClick?: React.MouseEventHandler<HTMLDivElement>;\n inputName?: string;\n iconLeftName?: string;\n iconLeftType?: IconType;\n iconRightName?: string;\n iconRightType?: IconType;\n onIconRightClick?: ((arg1: React.SyntheticEvent<HTMLElement>) => unknown) | null | undefined;\n inputBoxRef?: React.Ref<HTMLInputElement>;\n minLength?: number;\n maxLength?: number;\n pattern?: string;\n min?: string;\n max?: string;\n menu?: MenuProps;\n onMenuOpen?: () => unknown;\n onMenuClose?: () => unknown;\n scrollMenuToBottom?: boolean;\n /**\n * Opt in to the accessibility behavior: the label and helper/error text\n * programmatically associated with both fields, and the `combobox` wiring in\n * the `Dropdown` this renders.\n *\n * Off by default, and off is this component exactly as it behaved before any\n * of it existed.\n */\n enableA11y?: boolean;\n onDropdownFocus?: React.FocusEventHandler<HTMLInputElement>;\n onDropdownBlur?: React.FocusEventHandler<HTMLInputElement>;\n onDropdownKeyDown?: React.KeyboardEventHandler<HTMLInputElement>;\n onDropdownContainerClick?: React.MouseEventHandler<HTMLDivElement>;\n dropdownName?: string;\n dropdownPlaceholder?: string;\n dropdownBoxRef?: React.Ref<HTMLDivElement>;\n testId?: string;\n}\nexport declare const Combobox: Flow.AbstractComponent<ComboboxProps, HTMLDivElement>;\nexport {};\n//# sourceMappingURL=Combobox.d.ts.map"
|
|
764
764
|
},
|
|
765
765
|
"story": {
|
|
766
766
|
"path": "Combobox.stories.tsx",
|
|
767
|
-
"content": "import * as React from 'react';\n\nimport type {ComboboxProps} from 'src/components/Combobox/Combobox';\nimport {Combobox} from 'src/components/Combobox/Combobox';\nimport {\n currencyOptions,\n defaultOptions,\n formatPhoneNumber,\n phoneNumberOptions,\n} from 'src/components/Combobox/helper';\nimport type {InputOnChangeParamsType} from 'src/components/Input';\nimport type {MenuOption} from 'src/components/Menu';\n\nimport css from 'src/components/Combobox/Combobox.stories.module.css';\n\n\nexport default {\n tags: ['autodocs'],\n title: 'Form Components/Combobox',\n component: Combobox,\n argTypes: {\n value: {\n type: {required: true},\n description: 'The controlled value to input',\n control: {\n type: 'object',\n },\n table: {\n type: {summary: '{dropdown?: string, input?: string}'},\n },\n },\n onChange: {\n type: {required: true},\n description:\n '**onChange** handler is triggered when there is a selection of an option from dropdown or a change in input value. If input is changed onChange will have `dropdownOption` as undefined and on a dropdown change `inputChange` will be undefined',\n table: {\n type: {\n summary:\n 'onChange: ({input: string, dropdown: string, inputChange?: InputChange, dropdownOption?: MenuOption}) => mixed',\n },\n },\n },\n menu: {\n description: 'Menu props to use',\n control: {\n type: 'object',\n },\n table: {\n type: {summary: 'MenuProps'},\n },\n },\n type: {\n description: 'Type of Input. Supported types are listed on the dropdown',\n options: ['text', 'tel'],\n control: {\n type: 'select',\n },\n table: {\n type: {summary: 'enum'},\n defaultValue: {summary: 'text'},\n },\n },\n size: {\n options: ['small', 'medium'],\n description:\n 'Combobox supports only two size variants namely small and medium. `small` < `medium`',\n control: {\n type: 'select',\n },\n table: {\n type: {summary: 'enum'},\n defaultValue: {summary: 'medium'},\n },\n },\n classNames: {\n description: 'External classnames to be applied',\n control: {\n type: 'object',\n },\n },\n helperText: {\n description:\n 'Secondary label of the combobox. This appears on bottom of combobox, if the input is in `error` state the priority is given to `errorText`, in such cases secondary label wont be displayed',\n control: {\n type: 'text',\n },\n table: {\n type: {summary: 'string'},\n },\n },\n error: {\n description:\n 'if **true**, the component is moved to error state, Error state displays a red border surrounding combobox',\n control: {\n type: 'boolean',\n },\n table: {\n type: {summary: 'boolean'},\n defaultValue: {summary: 'false'},\n },\n },\n errorText: {\n description:\n 'Error text to be displayed on the bottom of the combobox. This uses `colorTextDanger`',\n control: {\n type: 'text',\n },\n table: {\n type: {summary: 'string'},\n },\n },\n label: {\n description:\n 'Primary label of the combobox. This appears on top of combobox',\n control: {\n type: 'text',\n },\n table: {\n type: {summary: 'string'},\n },\n },\n disabled: {\n description: 'If **true**, the component is disabled',\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 Combobox component.',\n description: {\n component: `\n\\`\\`\\`js\nimport { Combobox } from \"@spaced-out/ui-design-system/lib/components/Combobox\";\n\\`\\`\\`\n**Combobox** is implemented as a combination of **<a href=\"..?path=/docs/form-components-dropdown-generic-dropdown--docs\">Generic Dropdown</a>** and **<a href=\"..?path=/docs/form-components-input--docs\">Input</a>** components.\nIt allows user to input free text and select from the user defined options in the dropdown.\nIt is a controlled component, hence **value** and **onChange** props are mandatory.\nIt accepts all the props that **Input** and **Generic Dropdown** provides.\n\nMandatory props:\n* \\`value\\` accepts an object \\`{ dropdown: string, input: string }\\` which should have the display strings for both the dropdown and input components. To show the placeholder pass an empty string \\`' '\\`\n* \\`onChange\\` callback provides an object with four values in its parameter\n * \\`input\\`: the string which is inputted\n * \\`dropdown\\`: the \\`label\\` of the option which is selected\n * \\`inputChange\\`: the onChange parameters of Input component\n * \\`dropdownOption\\`: the onChange parameters of Dropdown component\n `,\n },\n },\n storySource: {\n componentPath: '/src/components/Combobox',\n },\n },\n};\n\nexport const DefaultStory = (args: ComboboxProps) => {\n const [selectedKeys, setSelectedKeys] = React.useState(\n args.menu?.selectedKeys || [],\n );\n const [dropDownText, setDropdownText] = React.useState('');\n const [inputText, setInputText] = React.useState('');\n\n const handleChange: (arg1: {\n input: string;\n dropdown: string;\n inputChange?: InputOnChangeParamsType;\n dropdownOption?: MenuOption;\n }) => unknown = ({input, dropdown, dropdownOption}) => {\n setInputText(input);\n setDropdownText(dropdown);\n if (dropdownOption?.key) {\n setSelectedKeys([dropdownOption.key]);\n }\n };\n\n return (\n <div className={css.container}>\n <Combobox\n {...args}\n menu={{...args.menu, selectedKeys}}\n value={{dropdown: dropDownText, input: inputText}}\n onChange={handleChange}\n />\n </div>\n );\n};\n\nDefaultStory.args = {\n label: 'Form Label',\n inputPlaceholder: 'Enter text...',\n dropdownPlaceholder: 'Select...',\n menu: {options: defaultOptions},\n testId: 'combobox-default-story',\n};\n\nexport const CurrencyInputStory = (args: ComboboxProps) => {\n const [comboboxData, setComboboxData] = React.useState({\n selectedKeys: args.menu?.selectedKeys || [],\n selectedValue: args.value.dropdown || '',\n inputValue: args.value.input || '',\n });\n const [error, setError] = React.useState({\n state: false,\n text: '',\n });\n\n const handleChange: (arg1: {\n input: string;\n dropdown: string;\n inputChange?: InputOnChangeParamsType;\n dropdownOption?: MenuOption;\n }) => unknown = ({input, dropdown, dropdownOption}) => {\n const withoutCommasInput = input.replace(/,/g, '');\n\n const allDigits = /^\\d+$/.test(withoutCommasInput);\n if (withoutCommasInput.length > 0 && !allDigits) {\n setError({\n state: true,\n text: 'Only digits are allowed',\n });\n } else {\n setError({\n state: false,\n text: '',\n });\n }\n\n setComboboxData((_prev) => ({\n ..._prev,\n inputValue: allDigits\n ? new Intl.NumberFormat().format(+withoutCommasInput)\n : input,\n selectedValue: dropdown,\n selectedKeys: dropdownOption?.key\n ? [dropdownOption.key]\n : _prev.selectedKeys,\n }));\n };\n\n return (\n <div className={css.container}>\n <Combobox\n {...args}\n error={error.state}\n errorText={error.text}\n menu={{...args.menu, selectedKeys: comboboxData.selectedKeys}}\n value={{\n dropdown: comboboxData.selectedValue,\n input: comboboxData.inputValue,\n }}\n onChange={handleChange}\n />\n </div>\n );\n};\n\nCurrencyInputStory.args = {\n label: 'Amount',\n inputPlaceholder: 'Enter amount...',\n helperText: 'Select the currency and input the price',\n value: {dropdown: '', input: ''},\n menu: {options: currencyOptions},\n testId: 'combobox-currency-input-story',\n};\n\nexport const PhoneNumberInputStory = (args: ComboboxProps) => {\n const [comboboxData, setComboboxData] = React.useState({\n selectedKeys: args.menu?.selectedKeys || [],\n selectedValue: args.value.dropdown || '',\n inputValue: args.value.input || '',\n });\n\n const handleChange: (arg1: {\n input: string;\n dropdown: string;\n inputChange?: InputOnChangeParamsType;\n dropdownOption?: MenuOption;\n }) => unknown = ({input, dropdown, dropdownOption}) => {\n setComboboxData((_prev) => ({\n ..._prev,\n inputValue: formatPhoneNumber(\n input,\n dropdownOption?.key || _prev.selectedKeys[0],\n ),\n selectedValue: dropdown.split(' ')[0],\n selectedKeys: dropdownOption?.key\n ? [dropdownOption.key]\n : _prev.selectedKeys,\n }));\n };\n\n return (\n <div className={css.container}>\n <Combobox\n {...args}\n menu={{...args.menu, selectedKeys: comboboxData.selectedKeys}}\n value={{\n dropdown: comboboxData.selectedValue,\n input: comboboxData.inputValue,\n }}\n onChange={handleChange}\n />\n </div>\n );\n};\n\nPhoneNumberInputStory.args = {\n label: 'Call Number',\n inputPlaceholder: 'Number',\n helperText: 'Select the country code and the number to call',\n value: {dropdown: '+44', input: '90 1574 7289'},\n menu: {options: phoneNumberOptions, allowSearch: true},\n testId: 'combobox-phone-number-input-story',\n};\nDefaultStory.storyName = 'Default';\nCurrencyInputStory.storyName = 'Currency Input';\nPhoneNumberInputStory.storyName = 'Phone Number Input';\n"
|
|
767
|
+
"content": "import * as React from 'react';\n\nimport type {ComboboxProps} from 'src/components/Combobox/Combobox';\nimport {Combobox} from 'src/components/Combobox/Combobox';\nimport {\n currencyOptions,\n defaultOptions,\n formatPhoneNumber,\n phoneNumberOptions,\n} from 'src/components/Combobox/helper';\nimport type {InputOnChangeParamsType} from 'src/components/Input';\nimport type {MenuOption} from 'src/components/Menu';\n\nimport css from 'src/components/Combobox/Combobox.stories.module.css';\n\n\nexport default {\n tags: ['autodocs'],\n title: 'Form Components/Combobox',\n component: Combobox,\n argTypes: {\n enableA11y: {\n description:\n '**enableA11y** opts into the accessibility behavior: the label and helper/error text programmatically associated with both fields, and the `combobox` wiring in the `Dropdown` it renders. Off by default, where the component behaves exactly as it did before any of it existed. Off here too, as everywhere: turn the control on to see the accessible behavior.',\n control: {type: 'boolean'},\n table: {\n type: {summary: 'boolean'},\n defaultValue: {summary: 'false'},\n },\n },\n value: {\n type: {required: true},\n description: 'The controlled value to input',\n control: {\n type: 'object',\n },\n table: {\n type: {summary: '{dropdown?: string, input?: string}'},\n },\n },\n onChange: {\n type: {required: true},\n description:\n '**onChange** handler is triggered when there is a selection of an option from dropdown or a change in input value. If input is changed onChange will have `dropdownOption` as undefined and on a dropdown change `inputChange` will be undefined',\n table: {\n type: {\n summary:\n 'onChange: ({input: string, dropdown: string, inputChange?: InputChange, dropdownOption?: MenuOption}) => mixed',\n },\n },\n },\n menu: {\n description: 'Menu props to use',\n control: {\n type: 'object',\n },\n table: {\n type: {summary: 'MenuProps'},\n },\n },\n type: {\n description: 'Type of Input. Supported types are listed on the dropdown',\n options: ['text', 'tel'],\n control: {\n type: 'select',\n },\n table: {\n type: {summary: 'enum'},\n defaultValue: {summary: 'text'},\n },\n },\n size: {\n options: ['small', 'medium'],\n description:\n 'Combobox supports only two size variants namely small and medium. `small` < `medium`',\n control: {\n type: 'select',\n },\n table: {\n type: {summary: 'enum'},\n defaultValue: {summary: 'medium'},\n },\n },\n classNames: {\n description: 'External classnames to be applied',\n control: {\n type: 'object',\n },\n },\n helperText: {\n description:\n 'Secondary label of the combobox. This appears on bottom of combobox, if the input is in `error` state the priority is given to `errorText`, in such cases secondary label wont be displayed',\n control: {\n type: 'text',\n },\n table: {\n type: {summary: 'string'},\n },\n },\n error: {\n description:\n 'if **true**, the component is moved to error state, Error state displays a red border surrounding combobox',\n control: {\n type: 'boolean',\n },\n table: {\n type: {summary: 'boolean'},\n defaultValue: {summary: 'false'},\n },\n },\n errorText: {\n description:\n 'Error text to be displayed on the bottom of the combobox. This uses `colorTextDanger`',\n control: {\n type: 'text',\n },\n table: {\n type: {summary: 'string'},\n },\n },\n label: {\n description:\n 'Primary label of the combobox. This appears on top of combobox',\n control: {\n type: 'text',\n },\n table: {\n type: {summary: 'string'},\n },\n },\n disabled: {\n description: 'If **true**, the component is disabled',\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 Combobox component.',\n description: {\n component: `\n\\`\\`\\`js\nimport { Combobox } from \"@spaced-out/ui-design-system/lib/components/Combobox\";\n\\`\\`\\`\n**Combobox** is implemented as a combination of **<a href=\"..?path=/docs/form-components-dropdown-generic-dropdown--docs\">Generic Dropdown</a>** and **<a href=\"..?path=/docs/form-components-input--docs\">Input</a>** components.\nIt allows user to input free text and select from the user defined options in the dropdown.\nIt is a controlled component, hence **value** and **onChange** props are mandatory.\nIt accepts all the props that **Input** and **Generic Dropdown** provides.\n\nMandatory props:\n* \\`value\\` accepts an object \\`{ dropdown: string, input: string }\\` which should have the display strings for both the dropdown and input components. To show the placeholder pass an empty string \\`' '\\`\n* \\`onChange\\` callback provides an object with four values in its parameter\n * \\`input\\`: the string which is inputted\n * \\`dropdown\\`: the \\`label\\` of the option which is selected\n * \\`inputChange\\`: the onChange parameters of Input component\n * \\`dropdownOption\\`: the onChange parameters of Dropdown component\n `,\n },\n },\n storySource: {\n componentPath: '/src/components/Combobox',\n },\n },\n};\n\nexport const DefaultStory = (args: ComboboxProps) => {\n const [selectedKeys, setSelectedKeys] = React.useState(\n args.menu?.selectedKeys || [],\n );\n const [dropDownText, setDropdownText] = React.useState('');\n const [inputText, setInputText] = React.useState('');\n\n const handleChange: (arg1: {\n input: string;\n dropdown: string;\n inputChange?: InputOnChangeParamsType;\n dropdownOption?: MenuOption;\n }) => unknown = ({input, dropdown, dropdownOption}) => {\n setInputText(input);\n setDropdownText(dropdown);\n if (dropdownOption?.key) {\n setSelectedKeys([dropdownOption.key]);\n }\n };\n\n return (\n <div className={css.container}>\n <Combobox\n {...args}\n menu={{...args.menu, selectedKeys}}\n value={{dropdown: dropDownText, input: inputText}}\n onChange={handleChange}\n />\n </div>\n );\n};\n\nDefaultStory.args = {\n label: 'Form Label',\n inputPlaceholder: 'Enter text...',\n dropdownPlaceholder: 'Select...',\n menu: {options: defaultOptions},\n testId: 'combobox-default-story',\n};\n\nexport const CurrencyInputStory = (args: ComboboxProps) => {\n const [comboboxData, setComboboxData] = React.useState({\n selectedKeys: args.menu?.selectedKeys || [],\n selectedValue: args.value.dropdown || '',\n inputValue: args.value.input || '',\n });\n const [error, setError] = React.useState({\n state: false,\n text: '',\n });\n\n const handleChange: (arg1: {\n input: string;\n dropdown: string;\n inputChange?: InputOnChangeParamsType;\n dropdownOption?: MenuOption;\n }) => unknown = ({input, dropdown, dropdownOption}) => {\n const withoutCommasInput = input.replace(/,/g, '');\n\n const allDigits = /^\\d+$/.test(withoutCommasInput);\n if (withoutCommasInput.length > 0 && !allDigits) {\n setError({\n state: true,\n text: 'Only digits are allowed',\n });\n } else {\n setError({\n state: false,\n text: '',\n });\n }\n\n setComboboxData((_prev) => ({\n ..._prev,\n inputValue: allDigits\n ? new Intl.NumberFormat().format(+withoutCommasInput)\n : input,\n selectedValue: dropdown,\n selectedKeys: dropdownOption?.key\n ? [dropdownOption.key]\n : _prev.selectedKeys,\n }));\n };\n\n return (\n <div className={css.container}>\n <Combobox\n {...args}\n error={error.state}\n errorText={error.text}\n menu={{...args.menu, selectedKeys: comboboxData.selectedKeys}}\n value={{\n dropdown: comboboxData.selectedValue,\n input: comboboxData.inputValue,\n }}\n onChange={handleChange}\n />\n </div>\n );\n};\n\nCurrencyInputStory.args = {\n label: 'Amount',\n inputPlaceholder: 'Enter amount...',\n helperText: 'Select the currency and input the price',\n value: {dropdown: '', input: ''},\n menu: {options: currencyOptions},\n testId: 'combobox-currency-input-story',\n};\n\nexport const PhoneNumberInputStory = (args: ComboboxProps) => {\n const [comboboxData, setComboboxData] = React.useState({\n selectedKeys: args.menu?.selectedKeys || [],\n selectedValue: args.value.dropdown || '',\n inputValue: args.value.input || '',\n });\n\n const handleChange: (arg1: {\n input: string;\n dropdown: string;\n inputChange?: InputOnChangeParamsType;\n dropdownOption?: MenuOption;\n }) => unknown = ({input, dropdown, dropdownOption}) => {\n setComboboxData((_prev) => ({\n ..._prev,\n inputValue: formatPhoneNumber(\n input,\n dropdownOption?.key || _prev.selectedKeys[0],\n ),\n selectedValue: dropdown.split(' ')[0],\n selectedKeys: dropdownOption?.key\n ? [dropdownOption.key]\n : _prev.selectedKeys,\n }));\n };\n\n return (\n <div className={css.container}>\n <Combobox\n {...args}\n menu={{...args.menu, selectedKeys: comboboxData.selectedKeys}}\n value={{\n dropdown: comboboxData.selectedValue,\n input: comboboxData.inputValue,\n }}\n onChange={handleChange}\n />\n </div>\n );\n};\n\nPhoneNumberInputStory.args = {\n label: 'Call Number',\n inputPlaceholder: 'Number',\n helperText: 'Select the country code and the number to call',\n value: {dropdown: '+44', input: '90 1574 7289'},\n menu: {options: phoneNumberOptions, allowSearch: true},\n testId: 'combobox-phone-number-input-story',\n};\nDefaultStory.storyName = 'Default';\nCurrencyInputStory.storyName = 'Currency Input';\nPhoneNumberInputStory.storyName = 'Phone Number Input';\n"
|
|
768
768
|
},
|
|
769
769
|
"css": {
|
|
770
770
|
"path": "Combobox.module.css",
|
|
@@ -960,15 +960,15 @@
|
|
|
960
960
|
"files": {
|
|
961
961
|
"main": {
|
|
962
962
|
"path": "Dropdown.d.ts",
|
|
963
|
-
"content": "import * as React from 'react';\nimport type { Flow } from 'flow-to-typescript-codemod';\nimport type { ClickAwayRefType } from '../../utils/click-away';\nimport type { InputProps } from '../../components/Input';\nimport type { MenuOption, MenuProps } from '../../components/Menu';\nimport type { ElevationType } from '../../components/Tooltip';\ntype ClassNames = Readonly<{\n wrapper?: string;\n box?: string;\n iconRight?: string;\n}>;\nexport interface DropdownProps extends Omit<InputProps, 'classNames' | 'onChange' | 'onMenuOpen' | 'onMenuClose' | 'scrollMenuToBottom' | 'dropdownInputText' | 'menu' | 'elevation' | 'clickAwayRef'> {\n classNames?: ClassNames;\n onChange?: (option: MenuOption, arg2?: React.SyntheticEvent<HTMLElement> | null | undefined) => unknown;\n onMenuOpen?: () => unknown;\n onMenuClose?: () => unknown;\n scrollMenuToBottom?: boolean;\n dropdownInputText?: string;\n menu?: MenuProps;\n elevation?: ElevationType;\n clickAwayRef?: React.RefObject<ClickAwayRefType | null>;\n testId?: string;\n}\nexport declare const Dropdown: Flow.AbstractComponent<DropdownProps, HTMLInputElement>;\nexport {};\n//# sourceMappingURL=Dropdown.d.ts.map"
|
|
963
|
+
"content": "import * as React from 'react';\nimport type { Flow } from 'flow-to-typescript-codemod';\nimport type { ClickAwayRefType } from '../../utils/click-away';\nimport type { InputProps } from '../../components/Input';\nimport type { MenuOption, MenuProps } from '../../components/Menu';\nimport type { ElevationType } from '../../components/Tooltip';\ntype ClassNames = Readonly<{\n wrapper?: string;\n box?: string;\n iconRight?: string;\n}>;\nexport interface DropdownProps extends Omit<InputProps, 'classNames' | 'onChange' | 'onMenuOpen' | 'onMenuClose' | 'scrollMenuToBottom' | 'dropdownInputText' | 'menu' | 'elevation' | 'clickAwayRef'> {\n classNames?: ClassNames;\n onChange?: (option: MenuOption, arg2?: React.SyntheticEvent<HTMLElement> | null | undefined) => unknown;\n onMenuOpen?: () => unknown;\n onMenuClose?: () => unknown;\n scrollMenuToBottom?: boolean;\n dropdownInputText?: string;\n menu?: MenuProps;\n elevation?: ElevationType;\n clickAwayRef?: React.RefObject<ClickAwayRefType | null>;\n /**\n * Opt in to the accessibility behavior: the `combobox` role and its ARIA,\n * keyboard navigation of the options from the input, and focus returning to\n * the input on close. Forwarded to the `Menu` this opens.\n *\n * Off by default, and off is this component exactly as it behaved before any\n * of it existed.\n */\n enableA11y?: boolean;\n testId?: string;\n}\nexport declare const Dropdown: Flow.AbstractComponent<DropdownProps, HTMLInputElement>;\nexport {};\n//# sourceMappingURL=Dropdown.d.ts.map"
|
|
964
964
|
},
|
|
965
965
|
"story": {
|
|
966
966
|
"path": "Dropdown.stories.tsx",
|
|
967
|
-
"content": "import * as React from 'react';\n\nimport type {ClickAwayRefType} from 'src/utils/click-away';\nimport {\n getSelectedKeysFromSelectedOption,\n getTextLabelFromSelectedKeys,\n} from 'src/utils/menu';\n\nimport {Button} from 'src/components/Button';\nimport type {DropdownProps} from 'src/components/Dropdown/Dropdown';\nimport {Dropdown} from 'src/components/Dropdown/Dropdown';\nimport {Icon} from 'src/components/Icon';\nimport type {\n MenuGroupTitleOption,\n MenuOption,\n MenuProps,\n} from 'src/components/Menu';\nimport {MenuFooter, MenuHeader} from 'src/components/Menu/Menu.stories';\nimport {FormLabelSmall} from 'src/components/Text';\nimport {ELEVATION_TYPES, Tooltip} from 'src/components/Tooltip';\n\nimport css from 'src/components/Dropdown/Dropdown.stories.module.css';\n\n\nconst options: 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:\n 'Option three is a very long option that would truncate and keep going to make it even longer than before. You can hover over the label to see the full text. ShowLabelTooltip maxLines is used to define the number of lines to show in the tooltip.',\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 key: '7',\n label: 'Option seven',\n iconLeft: 'coffee',\n },\n {key: '8', label: 'Option eight', iconLeft: 'user'},\n {\n key: '9',\n label: 'Option nine',\n iconLeft: 'face-party',\n },\n];\n\nconst elevationOptions: Array<unknown> = [...Object.values(ELEVATION_TYPES)];\n\nexport default {\n tags: ['autodocs'],\n title: 'Form Components/Dropdown/Generic Dropdown',\n component: Dropdown,\n argTypes: {\n size: {\n description:\n 'Dropdown supports only two size variants namely small and medium. `small` < `medium`',\n options: ['medium', 'small'],\n control: {\n type: 'select',\n },\n table: {\n type: {summary: 'enum'},\n defaultValue: {summary: 'medium'},\n },\n },\n menu: {\n description: 'Menu props to use',\n control: {\n type: 'object',\n },\n table: {\n type: {summary: 'MenuProps'},\n },\n },\n name: {\n description: 'Name of the Dropdown',\n control: {\n type: 'text',\n },\n table: {\n type: {summary: 'string'},\n },\n },\n elevation: {\n description:\n 'Elevation level for the dropdown menu. This controls the z-index stacking order. Default is set to \"modal\" elevation. Use higher elevation levels when rendering the dropdown above other elevated elements like Modal, Toast, etc.',\n control: {\n type: 'select',\n },\n options: elevationOptions,\n table: {\n type: {summary: 'enum'},\n defaultValue: {summary: 'modal'},\n },\n },\n disabled: {\n description: 'if **true**, the component is disabled',\n control: {\n type: 'boolean',\n },\n table: {\n type: {summary: 'boolean'},\n defaultValue: {summary: 'false'},\n },\n },\n placeholder: {\n description: 'Placeholder of dropdown. This appears inside the input box',\n control: {\n type: 'text',\n },\n table: {\n type: {summary: 'string'},\n },\n },\n locked: {\n description:\n 'if **true**, the component is locked, Locked state is the readonly state of the dropdown',\n control: {\n type: 'boolean',\n },\n table: {\n type: {summary: 'boolean'},\n defaultValue: {summary: 'false'},\n },\n },\n error: {\n description:\n 'if **true**, the component is moved to error state, Error state displays a red border surrounding input',\n control: {\n type: 'boolean',\n },\n table: {\n type: {summary: 'boolean'},\n defaultValue: {summary: 'false'},\n },\n },\n errorText: {\n description:\n 'Error text to be displayed on the bottom of the input. This uses `colorTextDanger`',\n control: {\n type: 'text',\n },\n table: {\n type: {summary: 'string'},\n },\n },\n label: {\n description:\n 'Primary label of the dropdown. This appears on top of dropdown input',\n control: {\n type: 'text',\n },\n table: {\n type: {summary: 'string'},\n },\n },\n helperText: {\n description:\n 'Secondary label of the dropdown. This appears on bottom of dropdown input, if the dropdown is in `error` state the priority is given to `errorText`, in such cases secondary label wont be displayed',\n control: {\n type: 'text',\n },\n table: {\n type: {summary: 'string'},\n },\n },\n dropdownInputText: {\n description:\n 'Text that appears inside the top input. Since Dropdown is a controlled component, this has to be set by caller',\n table: {\n type: {summary: 'string'},\n },\n },\n classNames: {\n description: 'External classnames to be applied',\n control: {\n type: 'object',\n },\n table: {\n type: {summary: '{wrapper?: string, box?: string}'},\n },\n },\n required: {\n description: 'if **true**, a red star would be added to the label',\n control: {\n type: 'boolean',\n },\n table: {\n type: {summary: 'boolean'},\n defaultValue: {summary: 'false'},\n },\n },\n onChange: {\n description:\n '**onChange** handler is triggered when there is a selection of an option from dropdown',\n action: 'changed',\n table: {\n type: {\n summary:\n '(option: MenuOption, ?SyntheticEvent<HTMLElement>) => mixed',\n },\n },\n },\n onMenuOpen: {\n description:\n '**onMenuOpen** handler is triggered when menu is opened. This should be helpful in cases where you want to change Button icon / text on this trigger.',\n action: 'clicked',\n table: {\n type: {summary: '() => mixed'},\n },\n },\n onMenuClose: {\n description:\n '**onMenuClose** handler is triggered when menu is closed. This should be helpful in cases where you want to change Button icon / text on this trigger.',\n action: 'clicked',\n table: {\n type: {summary: '() => mixed'},\n },\n },\n onFocus: {\n description: '**onFocus** handler is triggered when dropdown is focussed',\n action: 'focussed',\n table: {\n type: {summary: '(e: SyntheticInputEvent<HTMLInputElement>) => mixed'},\n },\n },\n onBlur: {\n description: '**onBlur** handler is triggered when dropdown is blurred',\n action: 'blurred',\n table: {\n type: {summary: '(e: SyntheticInputEvent<HTMLInputElement>) => mixed'},\n },\n },\n clickAwayRef: {\n type: {optional: true},\n description: 'ref that contains the functions to open or close the child',\n table: {\n type: {\n summary: 'ClickAwayRefType',\n },\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 Dropdown component',\n description: {\n component: `\n\\`\\`\\`js\nimport { Dropdown } from \"@spaced-out/ui-design-system/lib/components/Dropdown\";\n\\`\\`\\`\n**Dropdown** is implemented as a combination of **<a href=\"../?path=/docs/form-components-input--docs\">Input</a>** and **<a href=\"../?path=/docs/components-menu--docs\">Menu</a>**\ncomponents. It internally uses **Floating UI** to\nposition itself in context of DOM. Supports a bunch of callbacks listed below.\nThis is a controlled component. You would need to pass and set \\`selectedKeys\\` and \\`dropdownInputText\\` by handling \\`onChange\\` in order to set/reset the selection state\nof menu item and input value respectively.\n\n- Supports passing a \\`clickAwayRef\\` prop to the component.\n- You can always force Open the instance from ref by using \\`clickAwayRef.current?.forceOpen()\\`. This might be useful when want to force Open as per you needs.\n- You can always force Close the instance from ref by using \\`clickAwayRef.current?.forceClose()\\`. This might be useful when want to force Close as per you needs.\n\n**Note: ** For **checkbox** type option set you can make use of \\`getSelectedKeysFromSelectedOption\\` and \\`getTextLabelFromSelectedKeys\\` from \\`utils/menu\\`\nto populate \\`selectedKeys\\` and \\`dropdownInputText\\` respectively.\n `,\n },\n },\n storySource: {\n componentPath: '/src/components/Dropdown/Dropdown',\n },\n },\n};\n\nconst menuProps: MenuProps = {\n options,\n};\n\nexport const WithSimpleMenuStory = (args: DropdownProps) => {\n const [selectedKeys, setSelectedKeys] = React.useState(\n args.menu?.selectedKeys || [],\n );\n const [dropDownText, setDropdownText] = React.useState(\n args.dropdownInputText,\n );\n\n const handleChange = (\n option: MenuOption,\n _event?: React.SyntheticEvent<HTMLElement> | null | undefined,\n ) => {\n setSelectedKeys([option.key]);\n setDropdownText(option.label);\n };\n\n const onClear = () => {\n setSelectedKeys([]);\n setDropdownText('');\n };\n return (\n <div className={css.dropdownWrapper}>\n <div className={css.content}>\n <Dropdown\n {...args}\n dropdownInputText={dropDownText}\n menu={{...args.menu, selectedKeys}}\n onChange={handleChange}\n />\n <Button type=\"secondary\" onClick={onClear}>\n Clear\n </Button>\n </div>\n </div>\n );\n};\n\nWithSimpleMenuStory.args = {\n label: 'Form Label',\n menu: menuProps,\n testId: 'dropdown-with-simple-menu-story',\n};\n\nexport const WithSimpleMenuTooltipStory = (args: DropdownProps) => {\n const [selectedKeys, setSelectedKeys] = React.useState(\n args.menu?.selectedKeys || [],\n );\n const [dropDownText, setDropdownText] = React.useState(\n args.dropdownInputText,\n );\n\n const handleChange = (option: MenuOption) => {\n setSelectedKeys([option.key]);\n setDropdownText(option.label);\n };\n\n const onClear = () => {\n setSelectedKeys([]);\n setDropdownText('');\n };\n return (\n <div className={css.dropdownWrapper}>\n <div className={css.content}>\n <Dropdown\n {...args}\n dropdownInputText={dropDownText}\n menu={{...args.menu, selectedKeys}}\n onChange={handleChange}\n />\n <Button type=\"secondary\" onClick={onClear}>\n Clear\n </Button>\n </div>\n </div>\n );\n};\n\nWithSimpleMenuTooltipStory.args = {\n label: 'Form Label',\n menu: {...menuProps, showLabelTooltip: {maxLines: 6}},\n testId: 'dropdown-with-simple-menu-tooltip-story',\n};\n\nexport const WithSearchStory = (args: DropdownProps) => {\n const [selectedKeys, setSelectedKeys] = React.useState(\n args.menu?.selectedKeys || [],\n );\n const [dropDownText, setDropdownText] = React.useState(\n args.dropdownInputText,\n );\n\n const handleChange = (option: MenuOption) => {\n setSelectedKeys([option.key]);\n setDropdownText(option.label);\n };\n\n const onClear = () => {\n setSelectedKeys([]);\n setDropdownText('');\n };\n return (\n <div className={css.dropdownWrapper}>\n <div className={css.content}>\n <Dropdown\n {...args}\n dropdownInputText={dropDownText}\n menu={{...args.menu, selectedKeys}}\n onChange={handleChange}\n />\n <Button type=\"secondary\" onClick={onClear}>\n Clear\n </Button>\n </div>\n </div>\n );\n};\n\nWithSearchStory.args = {\n label: 'Form Label',\n\n menu: {...menuProps, allowSearch: true},\n testId: 'dropdown-with-search-story',\n};\n\nconst checkboxMenuProps: MenuProps = {\n options,\n optionsVariant: 'checkbox',\n selectedKeys: ['4'],\n};\n\nexport const WithCheckboxMenuStory = (args: DropdownProps) => {\n const [dropDownText, setDropdownText] = React.useState(\n args.dropdownInputText,\n );\n const [selectedKeys, setSelectedKeys] = React.useState(\n args.menu?.selectedKeys || [],\n );\n\n React.useEffect(() => {\n setSelectedKeys(args.menu?.selectedKeys || []);\n const newDropdownText = getTextLabelFromSelectedKeys(\n args.menu?.selectedKeys,\n args.menu?.options,\n );\n setDropdownText(newDropdownText);\n }, [args.menu?.selectedKeys]);\n\n const handleChange = (option: MenuOption) => {\n const newSelectedKeys = getSelectedKeysFromSelectedOption(\n option,\n selectedKeys,\n );\n const newDropdownText = getTextLabelFromSelectedKeys(\n newSelectedKeys,\n args.menu?.options,\n );\n setSelectedKeys(newSelectedKeys);\n setDropdownText(newDropdownText);\n };\n\n const onClear = () => {\n setSelectedKeys([]);\n setDropdownText('');\n };\n\n return (\n <div className={css.dropdownWrapper}>\n <div className={css.content}>\n <Dropdown\n {...args}\n dropdownInputText={dropDownText}\n menu={{...args.menu, selectedKeys}}\n onChange={handleChange}\n />\n <Button type=\"secondary\" onClick={onClear}>\n Clear\n </Button>\n </div>\n </div>\n );\n};\n\nWithCheckboxMenuStory.args = {\n label: 'Form Label',\n\n menu: checkboxMenuProps,\n testId: 'dropdown-with-checkbox-menu-story',\n};\n\nconst radioMenuProps: MenuProps = {\n options,\n optionsVariant: 'radio',\n selectedKeys: ['2'],\n};\n\nexport const WithRadioMenuStory = (args: DropdownProps) => {\n const [dropDownText, setDropdownText] = React.useState(\n args.dropdownInputText,\n );\n const [selectedKeys, setSelectedKeys] = React.useState(\n args.menu?.selectedKeys || [],\n );\n\n React.useEffect(() => {\n setSelectedKeys(args.menu?.selectedKeys || []);\n }, [args.menu?.selectedKeys]);\n\n const handleChange = (option: MenuOption) => {\n setSelectedKeys([option.key]);\n setDropdownText(option.label);\n };\n\n const onClear = () => {\n setSelectedKeys([]);\n setDropdownText('');\n };\n return (\n <div className={css.dropdownWrapper}>\n <div className={css.content}>\n <Dropdown\n {...args}\n dropdownInputText={dropDownText}\n menu={{...args.menu, selectedKeys}}\n onChange={handleChange}\n />\n <Button type=\"secondary\" onClick={onClear}>\n Clear\n </Button>\n </div>\n </div>\n );\n};\n\nWithRadioMenuStory.args = {\n label: 'Form Label',\n menu: radioMenuProps,\n testId: 'dropdown-with-radio-menu-story',\n};\n\nconst composeOptions: Array<Array<MenuOption>> = [\n [\n {\n key: '1',\n label: 'Option one',\n secondaryLabel: 'Sub Text',\n },\n {\n key: '2',\n label: 'Option two',\n secondaryLabel: 'Sub Text',\n },\n {\n key: '3',\n label: 'Option three',\n secondaryLabel: 'Sub Text',\n },\n ],\n [\n {\n iconLeft: 'coffee',\n key: '4',\n label: 'Option one',\n },\n ],\n [\n {\n iconLeft: 'coffee',\n key: '5',\n label: 'Option one',\n },\n ],\n];\n\nconst composeMenuProps: MenuProps = {\n composeOptions,\n};\n\nexport const WithComposeMenuStory = (args: DropdownProps) => {\n const [selectedKeys, setSelectedKeys] = React.useState(\n args.menu?.selectedKeys || [],\n );\n const [dropDownText, setDropdownText] = React.useState(\n args.dropdownInputText,\n );\n\n const handleChange = (option: MenuOption) => {\n setSelectedKeys([option.key]);\n setDropdownText(option.label);\n };\n\n const onClear = () => {\n setSelectedKeys([]);\n setDropdownText('');\n };\n return (\n <div className={css.dropdownWrapper}>\n <div className={css.content}>\n <Dropdown\n {...args}\n dropdownInputText={dropDownText}\n menu={{...args.menu, selectedKeys}}\n onChange={handleChange}\n />\n <Button type=\"secondary\" onClick={onClear}>\n Clear\n </Button>\n </div>\n </div>\n );\n};\n\nWithComposeMenuStory.args = {\n label: 'Form Label',\n menu: composeMenuProps,\n testId: 'dropdown-with-compose-menu-story',\n};\n\nconst groupTitleOptions: 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 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\nconst groupTitleMenuProps: MenuProps = {\n groupTitleOptions,\n};\n\nexport const WithGroupTitleMenuStory = (args: DropdownProps) => {\n const [selectedKeys, setSelectedKeys] = React.useState(\n args.menu?.selectedKeys || [],\n );\n const [dropDownText, setDropdownText] = React.useState(\n args.dropdownInputText,\n );\n\n const handleChange = (option: MenuOption) => {\n setSelectedKeys([option.key]);\n setDropdownText(option.label);\n };\n\n const onClear = () => {\n setSelectedKeys([]);\n setDropdownText('');\n };\n return (\n <div className={css.dropdownWrapper}>\n <div className={css.content}>\n <Dropdown\n {...args}\n dropdownInputText={dropDownText}\n menu={{...args.menu, selectedKeys}}\n onChange={handleChange}\n />\n <Button type=\"secondary\" onClick={onClear}>\n Clear\n </Button>\n </div>\n </div>\n );\n};\n\nWithGroupTitleMenuStory.args = {\n label: 'Form Label',\n\n menu: {...groupTitleMenuProps},\n testId: 'dropdown-with-group-title-menu-story',\n};\n\nexport const WithHeaderAndFooterStory = (args: DropdownProps) => (\n <WithHeaderAndFooter {...args} />\n);\n\nexport const WithHeaderAndFooter = (args: DropdownProps) => {\n const clickAwayRef = React.useRef<ClickAwayRefType | null>(null);\n const [dropDownText, setDropdownText] = React.useState(\n args.dropdownInputText,\n );\n const [selectedKeys, setSelectedKeys] = React.useState(\n args.menu?.selectedKeys || [],\n );\n\n React.useEffect(() => {\n setSelectedKeys(args.menu?.selectedKeys || []);\n const newDropdownText = getTextLabelFromSelectedKeys(\n args.menu?.selectedKeys,\n args.menu?.options,\n );\n setDropdownText(newDropdownText);\n }, [args.menu?.selectedKeys]);\n\n const handleChange = (option: MenuOption) => {\n const newSelectedKeys = getSelectedKeysFromSelectedOption(\n option,\n selectedKeys,\n );\n const newDropdownText = getTextLabelFromSelectedKeys(\n newSelectedKeys,\n args.menu?.options,\n );\n setSelectedKeys(newSelectedKeys);\n setDropdownText(newDropdownText);\n };\n\n const onClear = () => {\n setSelectedKeys([]);\n setDropdownText('');\n };\n\n const onSave = () => {\n if (clickAwayRef.current) {\n clickAwayRef.current?.forceClose();\n }\n };\n\n return (\n <div className={css.dropdownWrapper}>\n <div className={css.content}>\n <Dropdown\n {...args}\n dropdownInputText={dropDownText}\n menu={{\n ...args.menu,\n selectedKeys,\n header: <MenuHeader />,\n footer: <MenuFooter onSave={onSave} />,\n }}\n onChange={handleChange}\n clickAwayRef={clickAwayRef}\n />\n <Button type=\"secondary\" onClick={onClear}>\n Clear\n </Button>\n </div>\n </div>\n );\n};\n\nconst menuPropsWithHeaderAndFooter: MenuProps = {\n options,\n optionsVariant: 'checkbox',\n};\n\nWithHeaderAndFooterStory.args = {\n label: 'Form Label',\n menu: {\n ...menuPropsWithHeaderAndFooter,\n header: <MenuHeader />,\n footer: <MenuFooter />,\n },\n testId: 'dropdown-with-header-and-footer-story',\n};\nWithSimpleMenuStory.storyName = 'With Simple Menu';\nWithSimpleMenuTooltipStory.storyName = 'With Simple Menu Tooltip';\nWithSearchStory.storyName = 'With Search';\nWithCheckboxMenuStory.storyName = 'With Checkbox Menu';\nWithRadioMenuStory.storyName = 'With Radio Menu';\nWithComposeMenuStory.storyName = 'With Compose Menu';\nWithGroupTitleMenuStory.storyName = 'With Group Title Menu';\nWithHeaderAndFooterStory.storyName = 'With Header And Footer';\n"
|
|
967
|
+
"content": "import * as React from 'react';\n\nimport type {ClickAwayRefType} from 'src/utils/click-away';\nimport {\n getSelectedKeysFromSelectedOption,\n getTextLabelFromSelectedKeys,\n} from 'src/utils/menu';\n\nimport {Button} from 'src/components/Button';\nimport type {DropdownProps} from 'src/components/Dropdown/Dropdown';\nimport {Dropdown} from 'src/components/Dropdown/Dropdown';\nimport {Icon} from 'src/components/Icon';\nimport type {\n MenuGroupTitleOption,\n MenuOption,\n MenuProps,\n} from 'src/components/Menu';\nimport {MenuFooter, MenuHeader} from 'src/components/Menu/Menu.stories';\nimport {FormLabelSmall} from 'src/components/Text';\nimport {ELEVATION_TYPES, Tooltip} from 'src/components/Tooltip';\n\nimport css from 'src/components/Dropdown/Dropdown.stories.module.css';\n\n\nconst options: 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:\n 'Option three is a very long option that would truncate and keep going to make it even longer than before. You can hover over the label to see the full text. ShowLabelTooltip maxLines is used to define the number of lines to show in the tooltip.',\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 key: '7',\n label: 'Option seven',\n iconLeft: 'coffee',\n },\n {key: '8', label: 'Option eight', iconLeft: 'user'},\n {\n key: '9',\n label: 'Option nine',\n iconLeft: 'face-party',\n },\n];\n\nconst elevationOptions: Array<unknown> = [...Object.values(ELEVATION_TYPES)];\n\nexport default {\n tags: ['autodocs'],\n title: 'Form Components/Dropdown/Generic Dropdown',\n component: Dropdown,\n argTypes: {\n enableA11y: {\n description:\n '**enableA11y** opts into the accessibility behavior: the `combobox` role and its ARIA, keyboard navigation of the options from the input, and focus returning to the input on close. Off by default, where the component behaves exactly as it did before any of it existed. Off here too, as everywhere: turn the control on to see the accessible behavior.',\n control: {type: 'boolean'},\n table: {\n type: {summary: 'boolean'},\n defaultValue: {summary: 'false'},\n },\n },\n size: {\n description:\n 'Dropdown supports only two size variants namely small and medium. `small` < `medium`',\n options: ['medium', 'small'],\n control: {\n type: 'select',\n },\n table: {\n type: {summary: 'enum'},\n defaultValue: {summary: 'medium'},\n },\n },\n menu: {\n description: 'Menu props to use',\n control: {\n type: 'object',\n },\n table: {\n type: {summary: 'MenuProps'},\n },\n },\n name: {\n description: 'Name of the Dropdown',\n control: {\n type: 'text',\n },\n table: {\n type: {summary: 'string'},\n },\n },\n elevation: {\n description:\n 'Elevation level for the dropdown menu. This controls the z-index stacking order. Default is set to \"modal\" elevation. Use higher elevation levels when rendering the dropdown above other elevated elements like Modal, Toast, etc.',\n control: {\n type: 'select',\n },\n options: elevationOptions,\n table: {\n type: {summary: 'enum'},\n defaultValue: {summary: 'modal'},\n },\n },\n disabled: {\n description: 'if **true**, the component is disabled',\n control: {\n type: 'boolean',\n },\n table: {\n type: {summary: 'boolean'},\n defaultValue: {summary: 'false'},\n },\n },\n placeholder: {\n description: 'Placeholder of dropdown. This appears inside the input box',\n control: {\n type: 'text',\n },\n table: {\n type: {summary: 'string'},\n },\n },\n locked: {\n description:\n 'if **true**, the component is locked, Locked state is the readonly state of the dropdown',\n control: {\n type: 'boolean',\n },\n table: {\n type: {summary: 'boolean'},\n defaultValue: {summary: 'false'},\n },\n },\n error: {\n description:\n 'if **true**, the component is moved to error state, Error state displays a red border surrounding input',\n control: {\n type: 'boolean',\n },\n table: {\n type: {summary: 'boolean'},\n defaultValue: {summary: 'false'},\n },\n },\n errorText: {\n description:\n 'Error text to be displayed on the bottom of the input. This uses `colorTextDanger`',\n control: {\n type: 'text',\n },\n table: {\n type: {summary: 'string'},\n },\n },\n label: {\n description:\n 'Primary label of the dropdown. This appears on top of dropdown input',\n control: {\n type: 'text',\n },\n table: {\n type: {summary: 'string'},\n },\n },\n helperText: {\n description:\n 'Secondary label of the dropdown. This appears on bottom of dropdown input, if the dropdown is in `error` state the priority is given to `errorText`, in such cases secondary label wont be displayed',\n control: {\n type: 'text',\n },\n table: {\n type: {summary: 'string'},\n },\n },\n dropdownInputText: {\n description:\n 'Text that appears inside the top input. Since Dropdown is a controlled component, this has to be set by caller',\n table: {\n type: {summary: 'string'},\n },\n },\n classNames: {\n description: 'External classnames to be applied',\n control: {\n type: 'object',\n },\n table: {\n type: {summary: '{wrapper?: string, box?: string}'},\n },\n },\n required: {\n description: 'if **true**, a red star would be added to the label',\n control: {\n type: 'boolean',\n },\n table: {\n type: {summary: 'boolean'},\n defaultValue: {summary: 'false'},\n },\n },\n onChange: {\n description:\n '**onChange** handler is triggered when there is a selection of an option from dropdown',\n action: 'changed',\n table: {\n type: {\n summary:\n '(option: MenuOption, ?SyntheticEvent<HTMLElement>) => mixed',\n },\n },\n },\n onMenuOpen: {\n description:\n '**onMenuOpen** handler is triggered when menu is opened. This should be helpful in cases where you want to change Button icon / text on this trigger.',\n action: 'clicked',\n table: {\n type: {summary: '() => mixed'},\n },\n },\n onMenuClose: {\n description:\n '**onMenuClose** handler is triggered when menu is closed. This should be helpful in cases where you want to change Button icon / text on this trigger.',\n action: 'clicked',\n table: {\n type: {summary: '() => mixed'},\n },\n },\n onFocus: {\n description: '**onFocus** handler is triggered when dropdown is focussed',\n action: 'focussed',\n table: {\n type: {summary: '(e: SyntheticInputEvent<HTMLInputElement>) => mixed'},\n },\n },\n onBlur: {\n description: '**onBlur** handler is triggered when dropdown is blurred',\n action: 'blurred',\n table: {\n type: {summary: '(e: SyntheticInputEvent<HTMLInputElement>) => mixed'},\n },\n },\n clickAwayRef: {\n type: {optional: true},\n description: 'ref that contains the functions to open or close the child',\n table: {\n type: {\n summary: 'ClickAwayRefType',\n },\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 Dropdown component',\n description: {\n component: `\n\\`\\`\\`js\nimport { Dropdown } from \"@spaced-out/ui-design-system/lib/components/Dropdown\";\n\\`\\`\\`\n**Dropdown** is implemented as a combination of **<a href=\"../?path=/docs/form-components-input--docs\">Input</a>** and **<a href=\"../?path=/docs/components-menu--docs\">Menu</a>**\ncomponents. It internally uses **Floating UI** to\nposition itself in context of DOM. Supports a bunch of callbacks listed below.\nThis is a controlled component. You would need to pass and set \\`selectedKeys\\` and \\`dropdownInputText\\` by handling \\`onChange\\` in order to set/reset the selection state\nof menu item and input value respectively.\n\n- Supports passing a \\`clickAwayRef\\` prop to the component.\n- You can always force Open the instance from ref by using \\`clickAwayRef.current?.forceOpen()\\`. This might be useful when want to force Open as per you needs.\n- You can always force Close the instance from ref by using \\`clickAwayRef.current?.forceClose()\\`. This might be useful when want to force Close as per you needs.\n\n**Note: ** For **checkbox** type option set you can make use of \\`getSelectedKeysFromSelectedOption\\` and \\`getTextLabelFromSelectedKeys\\` from \\`utils/menu\\`\nto populate \\`selectedKeys\\` and \\`dropdownInputText\\` respectively.\n `,\n },\n },\n storySource: {\n componentPath: '/src/components/Dropdown/Dropdown',\n },\n },\n};\n\nconst menuProps: MenuProps = {\n options,\n};\n\nexport const WithSimpleMenuStory = (args: DropdownProps) => {\n const [selectedKeys, setSelectedKeys] = React.useState(\n args.menu?.selectedKeys || [],\n );\n const [dropDownText, setDropdownText] = React.useState(\n args.dropdownInputText,\n );\n\n const handleChange = (\n option: MenuOption,\n _event?: React.SyntheticEvent<HTMLElement> | null | undefined,\n ) => {\n setSelectedKeys([option.key]);\n setDropdownText(option.label);\n };\n\n const onClear = () => {\n setSelectedKeys([]);\n setDropdownText('');\n };\n return (\n <div className={css.dropdownWrapper}>\n <div className={css.content}>\n <Dropdown\n {...args}\n dropdownInputText={dropDownText}\n menu={{...args.menu, selectedKeys}}\n onChange={handleChange}\n />\n <Button type=\"secondary\" onClick={onClear}>\n Clear\n </Button>\n </div>\n </div>\n );\n};\n\nWithSimpleMenuStory.args = {\n label: 'Form Label',\n menu: menuProps,\n testId: 'dropdown-with-simple-menu-story',\n};\n\n/*\n * The same dropdown as `With Simple Menu`, with the accessibility opt-in on.\n *\n * With `enableA11y` the field becomes a `combobox` that owns the listbox it\n * opens: it reports `aria-expanded` / `aria-controls` while the list is\n * rendered, focus stays in the field so typing still reaches it, and the arrow\n * keys move a virtual highlight reported through `aria-activedescendant`.\n * Escape closes and returns focus to the field.\n *\n * Every other story here leaves the prop off, which is the default and is this\n * component exactly as it behaved before any of this existed.\n */\nexport const AccessibleDropdownStory = (args: DropdownProps) => {\n const [selectedKeys, setSelectedKeys] = React.useState(\n args.menu?.selectedKeys || [],\n );\n const [dropDownText, setDropdownText] = React.useState(\n args.dropdownInputText,\n );\n\n const handleChange = (\n option: MenuOption,\n _event?: React.SyntheticEvent<HTMLElement> | null | undefined,\n ) => {\n setSelectedKeys([option.key]);\n setDropdownText(option.label);\n };\n\n return (\n <div className={css.dropdownWrapper}>\n <div className={css.content}>\n <Dropdown\n {...args}\n dropdownInputText={dropDownText}\n onChange={handleChange}\n menu={{...args.menu, selectedKeys}}\n />\n </div>\n </div>\n );\n};\n\nAccessibleDropdownStory.args = {\n enableA11y: true,\n label: 'Form Label',\n menu: menuProps,\n testId: 'dropdown-accessible-story',\n};\n\nexport const WithSimpleMenuTooltipStory = (args: DropdownProps) => {\n const [selectedKeys, setSelectedKeys] = React.useState(\n args.menu?.selectedKeys || [],\n );\n const [dropDownText, setDropdownText] = React.useState(\n args.dropdownInputText,\n );\n\n const handleChange = (option: MenuOption) => {\n setSelectedKeys([option.key]);\n setDropdownText(option.label);\n };\n\n const onClear = () => {\n setSelectedKeys([]);\n setDropdownText('');\n };\n return (\n <div className={css.dropdownWrapper}>\n <div className={css.content}>\n <Dropdown\n {...args}\n dropdownInputText={dropDownText}\n menu={{...args.menu, selectedKeys}}\n onChange={handleChange}\n />\n <Button type=\"secondary\" onClick={onClear}>\n Clear\n </Button>\n </div>\n </div>\n );\n};\n\nWithSimpleMenuTooltipStory.args = {\n label: 'Form Label',\n menu: {...menuProps, showLabelTooltip: {maxLines: 6}},\n testId: 'dropdown-with-simple-menu-tooltip-story',\n};\n\nexport const WithSearchStory = (args: DropdownProps) => {\n const [selectedKeys, setSelectedKeys] = React.useState(\n args.menu?.selectedKeys || [],\n );\n const [dropDownText, setDropdownText] = React.useState(\n args.dropdownInputText,\n );\n\n const handleChange = (option: MenuOption) => {\n setSelectedKeys([option.key]);\n setDropdownText(option.label);\n };\n\n const onClear = () => {\n setSelectedKeys([]);\n setDropdownText('');\n };\n return (\n <div className={css.dropdownWrapper}>\n <div className={css.content}>\n <Dropdown\n {...args}\n dropdownInputText={dropDownText}\n menu={{...args.menu, selectedKeys}}\n onChange={handleChange}\n />\n <Button type=\"secondary\" onClick={onClear}>\n Clear\n </Button>\n </div>\n </div>\n );\n};\n\nWithSearchStory.args = {\n label: 'Form Label',\n\n menu: {...menuProps, allowSearch: true},\n testId: 'dropdown-with-search-story',\n};\n\nconst checkboxMenuProps: MenuProps = {\n options,\n optionsVariant: 'checkbox',\n selectedKeys: ['4'],\n};\n\nexport const WithCheckboxMenuStory = (args: DropdownProps) => {\n const [dropDownText, setDropdownText] = React.useState(\n args.dropdownInputText,\n );\n const [selectedKeys, setSelectedKeys] = React.useState(\n args.menu?.selectedKeys || [],\n );\n\n React.useEffect(() => {\n setSelectedKeys(args.menu?.selectedKeys || []);\n const newDropdownText = getTextLabelFromSelectedKeys(\n args.menu?.selectedKeys,\n args.menu?.options,\n );\n setDropdownText(newDropdownText);\n }, [args.menu?.selectedKeys]);\n\n const handleChange = (option: MenuOption) => {\n const newSelectedKeys = getSelectedKeysFromSelectedOption(\n option,\n selectedKeys,\n );\n const newDropdownText = getTextLabelFromSelectedKeys(\n newSelectedKeys,\n args.menu?.options,\n );\n setSelectedKeys(newSelectedKeys);\n setDropdownText(newDropdownText);\n };\n\n const onClear = () => {\n setSelectedKeys([]);\n setDropdownText('');\n };\n\n return (\n <div className={css.dropdownWrapper}>\n <div className={css.content}>\n <Dropdown\n {...args}\n dropdownInputText={dropDownText}\n menu={{...args.menu, selectedKeys}}\n onChange={handleChange}\n />\n <Button type=\"secondary\" onClick={onClear}>\n Clear\n </Button>\n </div>\n </div>\n );\n};\n\nWithCheckboxMenuStory.args = {\n label: 'Form Label',\n\n menu: checkboxMenuProps,\n testId: 'dropdown-with-checkbox-menu-story',\n};\n\nconst radioMenuProps: MenuProps = {\n options,\n optionsVariant: 'radio',\n selectedKeys: ['2'],\n};\n\nexport const WithRadioMenuStory = (args: DropdownProps) => {\n const [dropDownText, setDropdownText] = React.useState(\n args.dropdownInputText,\n );\n const [selectedKeys, setSelectedKeys] = React.useState(\n args.menu?.selectedKeys || [],\n );\n\n React.useEffect(() => {\n setSelectedKeys(args.menu?.selectedKeys || []);\n }, [args.menu?.selectedKeys]);\n\n const handleChange = (option: MenuOption) => {\n setSelectedKeys([option.key]);\n setDropdownText(option.label);\n };\n\n const onClear = () => {\n setSelectedKeys([]);\n setDropdownText('');\n };\n return (\n <div className={css.dropdownWrapper}>\n <div className={css.content}>\n <Dropdown\n {...args}\n dropdownInputText={dropDownText}\n menu={{...args.menu, selectedKeys}}\n onChange={handleChange}\n />\n <Button type=\"secondary\" onClick={onClear}>\n Clear\n </Button>\n </div>\n </div>\n );\n};\n\nWithRadioMenuStory.args = {\n label: 'Form Label',\n menu: radioMenuProps,\n testId: 'dropdown-with-radio-menu-story',\n};\n\nconst composeOptions: Array<Array<MenuOption>> = [\n [\n {\n key: '1',\n label: 'Option one',\n secondaryLabel: 'Sub Text',\n },\n {\n key: '2',\n label: 'Option two',\n secondaryLabel: 'Sub Text',\n },\n {\n key: '3',\n label: 'Option three',\n secondaryLabel: 'Sub Text',\n },\n ],\n [\n {\n iconLeft: 'coffee',\n key: '4',\n label: 'Option one',\n },\n ],\n [\n {\n iconLeft: 'coffee',\n key: '5',\n label: 'Option one',\n },\n ],\n];\n\nconst composeMenuProps: MenuProps = {\n composeOptions,\n};\n\nexport const WithComposeMenuStory = (args: DropdownProps) => {\n const [selectedKeys, setSelectedKeys] = React.useState(\n args.menu?.selectedKeys || [],\n );\n const [dropDownText, setDropdownText] = React.useState(\n args.dropdownInputText,\n );\n\n const handleChange = (option: MenuOption) => {\n setSelectedKeys([option.key]);\n setDropdownText(option.label);\n };\n\n const onClear = () => {\n setSelectedKeys([]);\n setDropdownText('');\n };\n return (\n <div className={css.dropdownWrapper}>\n <div className={css.content}>\n <Dropdown\n {...args}\n dropdownInputText={dropDownText}\n menu={{...args.menu, selectedKeys}}\n onChange={handleChange}\n />\n <Button type=\"secondary\" onClick={onClear}>\n Clear\n </Button>\n </div>\n </div>\n );\n};\n\nWithComposeMenuStory.args = {\n label: 'Form Label',\n menu: composeMenuProps,\n testId: 'dropdown-with-compose-menu-story',\n};\n\nconst groupTitleOptions: 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 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\nconst groupTitleMenuProps: MenuProps = {\n groupTitleOptions,\n};\n\nexport const WithGroupTitleMenuStory = (args: DropdownProps) => {\n const [selectedKeys, setSelectedKeys] = React.useState(\n args.menu?.selectedKeys || [],\n );\n const [dropDownText, setDropdownText] = React.useState(\n args.dropdownInputText,\n );\n\n const handleChange = (option: MenuOption) => {\n setSelectedKeys([option.key]);\n setDropdownText(option.label);\n };\n\n const onClear = () => {\n setSelectedKeys([]);\n setDropdownText('');\n };\n return (\n <div className={css.dropdownWrapper}>\n <div className={css.content}>\n <Dropdown\n {...args}\n dropdownInputText={dropDownText}\n menu={{...args.menu, selectedKeys}}\n onChange={handleChange}\n />\n <Button type=\"secondary\" onClick={onClear}>\n Clear\n </Button>\n </div>\n </div>\n );\n};\n\nWithGroupTitleMenuStory.args = {\n label: 'Form Label',\n\n menu: {...groupTitleMenuProps},\n testId: 'dropdown-with-group-title-menu-story',\n};\n\nexport const WithHeaderAndFooterStory = (args: DropdownProps) => (\n <WithHeaderAndFooter {...args} />\n);\n\nexport const WithHeaderAndFooter = (args: DropdownProps) => {\n const clickAwayRef = React.useRef<ClickAwayRefType | null>(null);\n const [dropDownText, setDropdownText] = React.useState(\n args.dropdownInputText,\n );\n const [selectedKeys, setSelectedKeys] = React.useState(\n args.menu?.selectedKeys || [],\n );\n\n React.useEffect(() => {\n setSelectedKeys(args.menu?.selectedKeys || []);\n const newDropdownText = getTextLabelFromSelectedKeys(\n args.menu?.selectedKeys,\n args.menu?.options,\n );\n setDropdownText(newDropdownText);\n }, [args.menu?.selectedKeys]);\n\n const handleChange = (option: MenuOption) => {\n const newSelectedKeys = getSelectedKeysFromSelectedOption(\n option,\n selectedKeys,\n );\n const newDropdownText = getTextLabelFromSelectedKeys(\n newSelectedKeys,\n args.menu?.options,\n );\n setSelectedKeys(newSelectedKeys);\n setDropdownText(newDropdownText);\n };\n\n const onClear = () => {\n setSelectedKeys([]);\n setDropdownText('');\n };\n\n const onSave = () => {\n if (clickAwayRef.current) {\n clickAwayRef.current?.forceClose();\n }\n };\n\n return (\n <div className={css.dropdownWrapper}>\n <div className={css.content}>\n <Dropdown\n {...args}\n dropdownInputText={dropDownText}\n menu={{\n ...args.menu,\n selectedKeys,\n header: <MenuHeader />,\n footer: <MenuFooter onSave={onSave} />,\n }}\n onChange={handleChange}\n clickAwayRef={clickAwayRef}\n />\n <Button type=\"secondary\" onClick={onClear}>\n Clear\n </Button>\n </div>\n </div>\n );\n};\n\nconst menuPropsWithHeaderAndFooter: MenuProps = {\n options,\n optionsVariant: 'checkbox',\n};\n\nWithHeaderAndFooterStory.args = {\n label: 'Form Label',\n menu: {\n ...menuPropsWithHeaderAndFooter,\n header: <MenuHeader />,\n footer: <MenuFooter />,\n },\n testId: 'dropdown-with-header-and-footer-story',\n};\nWithSimpleMenuStory.storyName = 'With Simple Menu';\nAccessibleDropdownStory.storyName = 'Accessible (enableA11y)';\nWithSimpleMenuTooltipStory.storyName = 'With Simple Menu Tooltip';\nWithSearchStory.storyName = 'With Search';\nWithCheckboxMenuStory.storyName = 'With Checkbox Menu';\nWithRadioMenuStory.storyName = 'With Radio Menu';\nWithComposeMenuStory.storyName = 'With Compose Menu';\nWithGroupTitleMenuStory.storyName = 'With Group Title Menu';\nWithHeaderAndFooterStory.storyName = 'With Header And Footer';\n"
|
|
968
968
|
},
|
|
969
969
|
"css": {
|
|
970
970
|
"path": "Dropdown.module.css",
|
|
971
|
-
"content": "@value (\n sizeFluid\n) from '../../styles/variables/_size.css';\n\n.dropdownContainer {\n display: flex;\n position: relative;\n flex-flow: column;\n width: sizeFluid;\n}\n\n.inputBox {\n cursor: pointer;\n}\n\n.menuWrapper {\n width: var(--dropdown-width);\n z-index: var(--menu-elevation);\n}\n"
|
|
971
|
+
"content": "@value (\n sizeFluid\n) from '../../styles/variables/_size.css';\n\n@value (\n borderWidthTertiary\n) from '../../styles/variables/_border.css';\n\n.dropdownContainer {\n display: flex;\n position: relative;\n flex-flow: column;\n width: sizeFluid;\n}\n\n.inputBox {\n cursor: pointer;\n}\n\n/*\n * Keeping the trigger visually active while its menu is open (DOM focus has\n * moved into the listbox) is done in Input.module.css, keyed off the trigger's\n * own `aria-expanded`. It has to live there: the rules it must beat are scoped\n * to Input's `.wrapper` / `.box` / `.withError` class names, which this module\n * cannot name and therefore cannot reliably out-specify.\n */\n\n/*\n * forced-colors mode drops the box-shadow open-emphasis ring; the base box\n * border survives, but add a system-color outline so the open state stays\n * perceivable in Windows High Contrast.\n */\n@media (forced-colors: active) {\n .inputBox.inputBoxOpen {\n outline: borderWidthTertiary solid Highlight;\n outline-offset: borderWidthTertiary;\n }\n}\n\n.menuWrapper {\n width: var(--dropdown-width);\n z-index: var(--menu-elevation);\n}\n"
|
|
972
972
|
},
|
|
973
973
|
"index": {
|
|
974
974
|
"path": "index.d.ts",
|
|
@@ -1462,11 +1462,11 @@
|
|
|
1462
1462
|
"files": {
|
|
1463
1463
|
"main": {
|
|
1464
1464
|
"path": "InlineDropdown.d.ts",
|
|
1465
|
-
"content": "import * as React from 'react';\nimport type { Flow } from 'flow-to-typescript-codemod';\nimport type { ClickAwayRefType } from '../../utils/click-away';\nimport type { UnstyledButtonProps } from '../../components/Button';\nimport type { AnchorType } from '../../components/ButtonDropdown';\nimport type { MenuOption, MenuProps } from '../../components/Menu';\nimport type { ElevationType } from '../../components/Tooltip';\ntype ClassNames = Readonly<{\n buttonWrapper?: string;\n dropdownContainer?: string;\n}>;\nexport interface InlineDropdownProps extends Omit<UnstyledButtonProps, 'classNames' | 'menu' | 'anchorPosition' | 'onOptionSelect' | 'onMenuOpen' | 'onMenuClose' | 'size' | 'elevation' | 'clickAwayRef'> {\n classNames?: ClassNames;\n menu?: MenuProps;\n anchorPosition?: AnchorType;\n onOptionSelect?: (option: MenuOption, arg2?: React.SyntheticEvent<HTMLElement> | null | undefined) => unknown;\n onMenuOpen?: () => unknown;\n onMenuClose?: () => unknown;\n size?: 'medium' | 'small' | 'extraSmall';\n elevation?: ElevationType;\n clickAwayRef?: React.RefObject<ClickAwayRefType | null>;\n testId?: string;\n}\nexport declare const InlineDropdown: Flow.AbstractComponent<InlineDropdownProps, HTMLDivElement>;\nexport {};\n//# sourceMappingURL=InlineDropdown.d.ts.map"
|
|
1465
|
+
"content": "import * as React from 'react';\nimport type { Flow } from 'flow-to-typescript-codemod';\nimport type { ClickAwayRefType } from '../../utils/click-away';\nimport type { UnstyledButtonProps } from '../../components/Button';\nimport type { AnchorType } from '../../components/ButtonDropdown';\nimport type { MenuOption, MenuProps } from '../../components/Menu';\nimport type { ElevationType } from '../../components/Tooltip';\ntype ClassNames = Readonly<{\n buttonWrapper?: string;\n dropdownContainer?: string;\n}>;\nexport interface InlineDropdownProps extends Omit<UnstyledButtonProps, 'classNames' | 'menu' | 'anchorPosition' | 'onOptionSelect' | 'onMenuOpen' | 'onMenuClose' | 'size' | 'elevation' | 'clickAwayRef'> {\n classNames?: ClassNames;\n menu?: MenuProps;\n anchorPosition?: AnchorType;\n onOptionSelect?: (option: MenuOption, arg2?: React.SyntheticEvent<HTMLElement> | null | undefined) => unknown;\n onMenuOpen?: () => unknown;\n onMenuClose?: () => unknown;\n size?: 'medium' | 'small' | 'extraSmall';\n elevation?: ElevationType;\n /**\n * Opt in to the accessibility behavior: `aria-haspopup` / `aria-expanded` /\n * `aria-controls` on the trigger, the arrow key that opens the popup, focus\n * moving into the menu and back to the trigger, and the roving tab stop\n * inside it. Forwarded to the `Menu` this opens.\n *\n * Off by default, and off is this component exactly as it behaved before any\n * of it existed.\n */\n enableA11y?: boolean;\n clickAwayRef?: React.RefObject<ClickAwayRefType | null>;\n testId?: string;\n}\nexport declare const InlineDropdown: Flow.AbstractComponent<InlineDropdownProps, HTMLDivElement>;\nexport {};\n//# sourceMappingURL=InlineDropdown.d.ts.map"
|
|
1466
1466
|
},
|
|
1467
1467
|
"story": {
|
|
1468
1468
|
"path": "InlineDropdown.stories.tsx",
|
|
1469
|
-
"content": "import * as React from 'react';\n\nimport {\n getButtonLabelFromSelectedKeys,\n getSelectedKeysFromSelectedOption,\n} from 'src/utils/menu';\n\nimport type {ButtonProps} from 'src/components/Button';\nimport {ANCHOR_POSITION_TYPE} from 'src/components/ButtonDropdown';\nimport type {InlineDropdownProps} from 'src/components/InlineDropdown/InlineDropdown';\nimport {InlineDropdown} from 'src/components/InlineDropdown/InlineDropdown';\nimport type {MenuOption, MenuProps} from 'src/components/Menu';\nimport {MenuFooter, MenuHeader} from 'src/components/Menu/Menu.stories';\nimport {ELEVATION_TYPES} from 'src/components/Tooltip';\n\nimport css from 'src/components/InlineDropdown/InlineDropdown.stories.module.css';\n\n\nconst anchorPositionOptions: Array<unknown> = [\n ...Object.values(ANCHOR_POSITION_TYPE),\n];\n\nimport type {ClickAwayRefType} from 'src/utils/click-away';\n\nimport {BUTTON_ACTION_TYPE} from 'src/components/Button';\n\n\nconst elevationOptions: Array<unknown> = [...Object.values(ELEVATION_TYPES)];\nconst actionTypeOptions: Array<unknown> = [\n ...Object.values(BUTTON_ACTION_TYPE),\n];\n\nconst textOptions: MenuOption[] = [\n {key: '1', label: 'Option one'},\n {key: '2', label: 'Option two'},\n {\n key: '3',\n label:\n 'Option three this is a very long option that would truncate, and this is even longer text to further test the truncation behavior in the dropdown component, ensuring that extremely long labels are handled gracefully and do not break the layout or overflow in an undesirable way, especially when the text is much longer than what would typically be expected in a dropdown option.',\n },\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 {key: '8', label: 'Option Eight'},\n {key: '9', label: 'Option Night'},\n];\n\nconst menuProps: MenuProps = {\n isFluid: false,\n menuDisabled: false,\n size: 'medium',\n options: textOptions,\n};\n\nconst textOnlyInlineDropdownProps: ButtonProps = {\n children: 'Button Text',\n};\n\nexport default {\n tags: ['autodocs'],\n title: 'Components/Inline Dropdown/Generic Inline Dropdown',\n component: InlineDropdown,\n argTypes: {\n onClick: {\n description:\n '**onClick** handler that is applied to the root `<UnstyledButton>` element',\n action: 'clicked',\n table: {\n type: {summary: '(SyntheticEvent<HTMLElement>) => mixed'},\n },\n },\n menu: {\n description: 'Menu props to use',\n control: {\n type: 'object',\n },\n table: {\n type: {summary: 'MenuProps'},\n },\n },\n classNames: {\n description: 'External classNames to be applied',\n control: {\n type: 'object',\n },\n table: {\n type: {summary: '{buttonWrapper?: string, dropdownContainer?: string}'},\n },\n },\n elevation: {\n description:\n 'Elevation level for the dropdown menu. This controls the z-index stacking order. Default is set to \"modal\" elevation. Use higher elevation levels when rendering the dropdown above other elevated elements like Modal, Toast, etc.',\n control: {\n type: 'select',\n },\n options: elevationOptions,\n table: {\n type: {summary: 'enum'},\n defaultValue: {summary: 'modal'},\n },\n },\n actionType: {\n options: actionTypeOptions,\n description:\n 'Action type of Button. Supported types are listed on the dropdown',\n control: {\n type: 'select',\n },\n table: {\n type: {summary: 'enum'},\n defaultValue: {summary: 'submit'},\n },\n },\n size: {\n description:\n 'Inline dropdown supports only three size variants namely small and medium and extra small. `extraSmall` < `small` < `medium`',\n control: {\n type: 'radio',\n },\n options: ['medium', 'small', 'extraSmall'],\n table: {\n type: {summary: 'enum'},\n defaultValue: {summary: 'medium'},\n },\n },\n disabled: {\n description: 'If **true**, the component is disabled',\n control: {\n type: 'boolean',\n },\n table: {\n type: {summary: 'boolean'},\n defaultValue: {summary: 'false'},\n },\n },\n children: {\n description: 'The content of the inline dropdown',\n table: {\n type: {summary: 'React.Component | string'},\n },\n },\n onOptionSelect: {\n description:\n '**onOptionSelect** 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 onMenuOpen: {\n description: '**onMenuOpen** handler is triggered when menu is opened.',\n action: 'clicked',\n table: {\n type: {summary: '() => mixed'},\n },\n },\n onMenuClose: {\n description: '**onMenuClose** handler is triggered when menu is closed.',\n action: 'clicked',\n table: {\n type: {summary: '() => mixed'},\n },\n },\n anchorPosition: {\n options: anchorPositionOptions,\n description:\n 'Position of Menu. It can be any one value from the dropdown',\n control: {\n type: 'select',\n },\n table: {\n type: {summary: 'enum'},\n defaultValue: {summary: 'bottom-start'},\n },\n },\n clickAwayRef: {\n type: {optional: true},\n description: 'ref that contains the functions to open or close the child',\n table: {\n type: {\n summary: 'ClickAwayRefType',\n },\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 InlineDropdown component',\n description: {\n component: `\n\\`\\`\\`js\nimport { InlineDropdown } from \"@spaced-out/ui-design-system/lib/components/InlineDropdown\";\n\\`\\`\\`\nInline Dropdown is a composite component that internally implements **<a href=\"../?path=/docs-components-menu--docs\">Menu</a>** with\n**Unstyled Button** component. It also uses **Floating UI** to position the\nmenu component relative to empty space available, taking viewport and component position into account. This makes sure that menu is always visible\nirrespective of the component positioning. The component has a callback to notify the parent on option selection. Inline Dropdown is implemented inside a\n\\`ClickAway\\` listener and thus Menu is closed automatically on outside click.\n\n- Supports passing a \\`clickAwayRef\\` prop to the component.\n- You can always force Open the dropdown from ref by using \\`clickAwayRef.current?.forceOpen()\\`. This might be useful when want to force Open as per you needs.\n- You can always force Close the dropdown from ref by using \\`clickAwayRef.current?.forceClose()\\`. This might be useful when want to force Close as per you needs.\n\n\n**Note: ** For checkbox type option set you can make use of \\`getSelectedKeysFromSelectedOption\\` and \\`getButtonLabelFromSelectedKeys\\` from \\`utils/menu\\`\nto populate \\`selectedKeys\\` and \\`buttonText\\` respectively.\n `,\n },\n },\n storySource: {\n componentPath: '/src/components/InlineDropdown/InlineDropdown',\n },\n },\n};\n\nexport const TextOnlyStory = (args: InlineDropdownProps) => (\n <div className={css.menuBtnWrapper}>\n <InlineDropdown {...args} />\n </div>\n);\n\nTextOnlyStory.args = {\n ...textOnlyInlineDropdownProps,\n menu: menuProps,\n size: 'medium',\n testId: 'inline-dropdown-text-only-story',\n};\n\nexport const TextWithTooltipStory = (args: InlineDropdownProps) => (\n <div className={css.menuBtnWrapper}>\n <InlineDropdown {...args} />\n </div>\n);\n\nTextWithTooltipStory.args = {\n ...textOnlyInlineDropdownProps,\n menu: {...menuProps, showLabelTooltip: {maxLines: 2}},\n size: 'medium',\n testId: 'inline-dropdown-text-with-tooltip-story',\n};\n\nexport const WithSearchStory = (args: InlineDropdownProps) => (\n <div className={css.menuBtnWrapper}>\n <InlineDropdown {...args} />\n </div>\n);\n\nWithSearchStory.args = {\n ...textOnlyInlineDropdownProps,\n menu: {...menuProps, allowSearch: true},\n size: 'medium',\n testId: 'inline-dropdown-with-search-story',\n};\n\nconst checkboxMenuProps: MenuProps = {\n options: textOptions,\n optionsVariant: 'checkbox',\n selectedKeys: ['4'],\n isFluid: false,\n};\n\nexport const WithCheckboxMenuStory = (args: InlineDropdownProps) => {\n const [btnText, setBtnText] = React.useState<React.ReactNode>('');\n const [selectedKeys, setSelectedKeys] = React.useState(\n args.menu?.selectedKeys || [],\n );\n\n React.useEffect(() => {\n setSelectedKeys(args.menu?.selectedKeys || []);\n const newBtnText = getButtonLabelFromSelectedKeys(\n args.menu?.selectedKeys,\n args.children,\n );\n setBtnText(newBtnText);\n }, [args.menu?.selectedKeys]);\n\n const handleChange = (option: MenuOption) => {\n const newSelectedKeys = getSelectedKeysFromSelectedOption(\n option,\n selectedKeys,\n );\n const newBtnText = getButtonLabelFromSelectedKeys(\n newSelectedKeys,\n args.children,\n );\n setSelectedKeys(newSelectedKeys);\n setBtnText(newBtnText);\n };\n\n return (\n <div className={css.menuBtnWrapper}>\n <InlineDropdown\n {...args}\n menu={{...args.menu, selectedKeys}}\n onOptionSelect={handleChange}\n >\n {btnText}\n </InlineDropdown>\n </div>\n );\n};\n\nWithCheckboxMenuStory.args = {\n children: 'Long Button Text',\n menu: checkboxMenuProps,\n size: 'medium',\n testId: 'inline-dropdown-with-checkbox-menu-story',\n};\n\nconst radioMenuProps: MenuProps = {\n options: textOptions,\n optionsVariant: 'radio',\n selectedKeys: ['4'],\n isFluid: false,\n};\n\nexport const WithRadioMenuStory = (args: InlineDropdownProps) => {\n const [selectedKeys, setSelectedKeys] = React.useState(\n args.menu?.selectedKeys || [],\n );\n\n React.useEffect(() => {\n setSelectedKeys(args.menu?.selectedKeys || []);\n }, [args.menu?.selectedKeys]);\n\n const handleChange = (option: MenuOption) => {\n setSelectedKeys([option.key]);\n };\n\n return (\n <div className={css.menuBtnWrapper}>\n <InlineDropdown\n {...args}\n menu={{...args.menu, selectedKeys}}\n onOptionSelect={handleChange}\n >\n {args.children}\n </InlineDropdown>\n </div>\n );\n};\n\nWithRadioMenuStory.args = {\n children: 'Long Button Text',\n menu: radioMenuProps,\n testId: 'inline-dropdown-with-radio-menu-story',\n};\n\nexport const WithHeaderAndFooterStory = (args: InlineDropdownProps) => (\n <WithHeaderAndFooter {...args} />\n);\n\nexport const WithHeaderAndFooter = (args: InlineDropdownProps) => {\n const clickAwayRef = React.useRef<ClickAwayRefType | null>(null);\n const [btnText, setBtnText] = React.useState<React.ReactNode>('');\n const [selectedKeys, setSelectedKeys] = React.useState(\n args.menu?.selectedKeys || [],\n );\n\n React.useEffect(() => {\n setSelectedKeys(args.menu?.selectedKeys || []);\n const newBtnText = getButtonLabelFromSelectedKeys(\n args.menu?.selectedKeys,\n args.children,\n );\n setBtnText(newBtnText);\n }, [args.menu?.selectedKeys]);\n\n const handleChange = (option: MenuOption) => {\n const newSelectedKeys = getSelectedKeysFromSelectedOption(\n option,\n selectedKeys,\n );\n const newBtnText = getButtonLabelFromSelectedKeys(\n newSelectedKeys,\n args.children,\n );\n setSelectedKeys(newSelectedKeys);\n setBtnText(newBtnText);\n };\n\n const onSave = () => {\n if (clickAwayRef.current) {\n clickAwayRef.current?.forceClose();\n }\n };\n\n return (\n <div className={css.menuBtnWrapper}>\n <InlineDropdown\n {...args}\n menu={{\n ...args.menu,\n selectedKeys,\n header: <MenuHeader />,\n footer: <MenuFooter onSave={onSave} />,\n }}\n onOptionSelect={handleChange}\n clickAwayRef={clickAwayRef}\n >\n {btnText}\n </InlineDropdown>\n </div>\n );\n};\n\nconst menuPropsWithHeaderAndFooter: MenuProps = {\n options: textOptions,\n optionsVariant: 'checkbox',\n};\n\nWithHeaderAndFooterStory.args = {\n children: 'Long Button Text',\n menu: menuPropsWithHeaderAndFooter,\n size: 'medium',\n testId: 'inline-dropdown-with-header-and-footer-story',\n};\nTextOnlyStory.storyName = 'Text Only';\nTextWithTooltipStory.storyName = 'Text With Tooltip';\nWithSearchStory.storyName = 'With Search';\nWithCheckboxMenuStory.storyName = 'With Checkbox Menu';\nWithRadioMenuStory.storyName = 'With Radio Menu';\nWithHeaderAndFooterStory.storyName = 'With Header And Footer';\n"
|
|
1469
|
+
"content": "import * as React from 'react';\n\nimport {\n getButtonLabelFromSelectedKeys,\n getSelectedKeysFromSelectedOption,\n} from 'src/utils/menu';\n\nimport type {ButtonProps} from 'src/components/Button';\nimport {ANCHOR_POSITION_TYPE} from 'src/components/ButtonDropdown';\nimport type {InlineDropdownProps} from 'src/components/InlineDropdown/InlineDropdown';\nimport {InlineDropdown} from 'src/components/InlineDropdown/InlineDropdown';\nimport type {MenuOption, MenuProps} from 'src/components/Menu';\nimport {MenuFooter, MenuHeader} from 'src/components/Menu/Menu.stories';\nimport {ELEVATION_TYPES} from 'src/components/Tooltip';\n\nimport css from 'src/components/InlineDropdown/InlineDropdown.stories.module.css';\n\n\nconst anchorPositionOptions: Array<unknown> = [\n ...Object.values(ANCHOR_POSITION_TYPE),\n];\n\nimport type {ClickAwayRefType} from 'src/utils/click-away';\n\nimport {BUTTON_ACTION_TYPE} from 'src/components/Button';\n\n\nconst elevationOptions: Array<unknown> = [...Object.values(ELEVATION_TYPES)];\nconst actionTypeOptions: Array<unknown> = [\n ...Object.values(BUTTON_ACTION_TYPE),\n];\n\nconst textOptions: MenuOption[] = [\n {key: '1', label: 'Option one'},\n {key: '2', label: 'Option two'},\n {\n key: '3',\n label:\n 'Option three this is a very long option that would truncate, and this is even longer text to further test the truncation behavior in the dropdown component, ensuring that extremely long labels are handled gracefully and do not break the layout or overflow in an undesirable way, especially when the text is much longer than what would typically be expected in a dropdown option.',\n },\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 {key: '8', label: 'Option Eight'},\n {key: '9', label: 'Option Night'},\n];\n\nconst menuProps: MenuProps = {\n isFluid: false,\n menuDisabled: false,\n size: 'medium',\n options: textOptions,\n};\n\nconst textOnlyInlineDropdownProps: ButtonProps = {\n children: 'Button Text',\n};\n\nexport default {\n tags: ['autodocs'],\n title: 'Components/Inline Dropdown/Generic Inline Dropdown',\n component: InlineDropdown,\n argTypes: {\n enableA11y: {\n description:\n '**enableA11y** opts into the accessibility behavior: `aria-haspopup` / `aria-expanded` / `aria-controls` on the trigger, the arrow key that opens the popup, and focus moving into the menu and back. Off by default, where `InlineDropdown` behaves exactly as it did before any of it existed. Off here too, as everywhere: turn the control on to see the accessible behavior.',\n control: {type: 'boolean'},\n table: {\n type: {summary: 'boolean'},\n defaultValue: {summary: 'false'},\n },\n },\n onClick: {\n description:\n '**onClick** handler that is applied to the root `<UnstyledButton>` element',\n action: 'clicked',\n table: {\n type: {summary: '(SyntheticEvent<HTMLElement>) => mixed'},\n },\n },\n menu: {\n description: 'Menu props to use',\n control: {\n type: 'object',\n },\n table: {\n type: {summary: 'MenuProps'},\n },\n },\n classNames: {\n description: 'External classNames to be applied',\n control: {\n type: 'object',\n },\n table: {\n type: {summary: '{buttonWrapper?: string, dropdownContainer?: string}'},\n },\n },\n elevation: {\n description:\n 'Elevation level for the dropdown menu. This controls the z-index stacking order. Default is set to \"modal\" elevation. Use higher elevation levels when rendering the dropdown above other elevated elements like Modal, Toast, etc.',\n control: {\n type: 'select',\n },\n options: elevationOptions,\n table: {\n type: {summary: 'enum'},\n defaultValue: {summary: 'modal'},\n },\n },\n actionType: {\n options: actionTypeOptions,\n description:\n 'Action type of Button. Supported types are listed on the dropdown',\n control: {\n type: 'select',\n },\n table: {\n type: {summary: 'enum'},\n defaultValue: {summary: 'submit'},\n },\n },\n size: {\n description:\n 'Inline dropdown supports only three size variants namely small and medium and extra small. `extraSmall` < `small` < `medium`',\n control: {\n type: 'radio',\n },\n options: ['medium', 'small', 'extraSmall'],\n table: {\n type: {summary: 'enum'},\n defaultValue: {summary: 'medium'},\n },\n },\n disabled: {\n description: 'If **true**, the component is disabled',\n control: {\n type: 'boolean',\n },\n table: {\n type: {summary: 'boolean'},\n defaultValue: {summary: 'false'},\n },\n },\n children: {\n description: 'The content of the inline dropdown',\n table: {\n type: {summary: 'React.Component | string'},\n },\n },\n onOptionSelect: {\n description:\n '**onOptionSelect** 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 onMenuOpen: {\n description: '**onMenuOpen** handler is triggered when menu is opened.',\n action: 'clicked',\n table: {\n type: {summary: '() => mixed'},\n },\n },\n onMenuClose: {\n description: '**onMenuClose** handler is triggered when menu is closed.',\n action: 'clicked',\n table: {\n type: {summary: '() => mixed'},\n },\n },\n anchorPosition: {\n options: anchorPositionOptions,\n description:\n 'Position of Menu. It can be any one value from the dropdown',\n control: {\n type: 'select',\n },\n table: {\n type: {summary: 'enum'},\n defaultValue: {summary: 'bottom-start'},\n },\n },\n clickAwayRef: {\n type: {optional: true},\n description: 'ref that contains the functions to open or close the child',\n table: {\n type: {\n summary: 'ClickAwayRefType',\n },\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 InlineDropdown component',\n description: {\n component: `\n\\`\\`\\`js\nimport { InlineDropdown } from \"@spaced-out/ui-design-system/lib/components/InlineDropdown\";\n\\`\\`\\`\nInline Dropdown is a composite component that internally implements **<a href=\"../?path=/docs-components-menu--docs\">Menu</a>** with\n**Unstyled Button** component. It also uses **Floating UI** to position the\nmenu component relative to empty space available, taking viewport and component position into account. This makes sure that menu is always visible\nirrespective of the component positioning. The component has a callback to notify the parent on option selection. Inline Dropdown is implemented inside a\n\\`ClickAway\\` listener and thus Menu is closed automatically on outside click.\n\n- Supports passing a \\`clickAwayRef\\` prop to the component.\n- You can always force Open the dropdown from ref by using \\`clickAwayRef.current?.forceOpen()\\`. This might be useful when want to force Open as per you needs.\n- You can always force Close the dropdown from ref by using \\`clickAwayRef.current?.forceClose()\\`. This might be useful when want to force Close as per you needs.\n\n\n**Note: ** For checkbox type option set you can make use of \\`getSelectedKeysFromSelectedOption\\` and \\`getButtonLabelFromSelectedKeys\\` from \\`utils/menu\\`\nto populate \\`selectedKeys\\` and \\`buttonText\\` respectively.\n `,\n },\n },\n storySource: {\n componentPath: '/src/components/InlineDropdown/InlineDropdown',\n },\n },\n};\n\nexport const TextOnlyStory = (args: InlineDropdownProps) => (\n <div className={css.menuBtnWrapper}>\n <InlineDropdown {...args} />\n </div>\n);\n\nTextOnlyStory.args = {\n ...textOnlyInlineDropdownProps,\n menu: menuProps,\n size: 'medium',\n testId: 'inline-dropdown-text-only-story',\n};\n\nexport const TextWithTooltipStory = (args: InlineDropdownProps) => (\n <div className={css.menuBtnWrapper}>\n <InlineDropdown {...args} />\n </div>\n);\n\nTextWithTooltipStory.args = {\n ...textOnlyInlineDropdownProps,\n menu: {...menuProps, showLabelTooltip: {maxLines: 2}},\n size: 'medium',\n testId: 'inline-dropdown-text-with-tooltip-story',\n};\n\nexport const WithSearchStory = (args: InlineDropdownProps) => (\n <div className={css.menuBtnWrapper}>\n <InlineDropdown {...args} />\n </div>\n);\n\nWithSearchStory.args = {\n ...textOnlyInlineDropdownProps,\n menu: {...menuProps, allowSearch: true},\n size: 'medium',\n testId: 'inline-dropdown-with-search-story',\n};\n\nconst checkboxMenuProps: MenuProps = {\n options: textOptions,\n optionsVariant: 'checkbox',\n selectedKeys: ['4'],\n isFluid: false,\n};\n\nexport const WithCheckboxMenuStory = (args: InlineDropdownProps) => {\n const [btnText, setBtnText] = React.useState<React.ReactNode>('');\n const [selectedKeys, setSelectedKeys] = React.useState(\n args.menu?.selectedKeys || [],\n );\n\n React.useEffect(() => {\n setSelectedKeys(args.menu?.selectedKeys || []);\n const newBtnText = getButtonLabelFromSelectedKeys(\n args.menu?.selectedKeys,\n args.children,\n );\n setBtnText(newBtnText);\n }, [args.menu?.selectedKeys]);\n\n const handleChange = (option: MenuOption) => {\n const newSelectedKeys = getSelectedKeysFromSelectedOption(\n option,\n selectedKeys,\n );\n const newBtnText = getButtonLabelFromSelectedKeys(\n newSelectedKeys,\n args.children,\n );\n setSelectedKeys(newSelectedKeys);\n setBtnText(newBtnText);\n };\n\n return (\n <div className={css.menuBtnWrapper}>\n <InlineDropdown\n {...args}\n menu={{...args.menu, selectedKeys}}\n onOptionSelect={handleChange}\n >\n {btnText}\n </InlineDropdown>\n </div>\n );\n};\n\nWithCheckboxMenuStory.args = {\n children: 'Long Button Text',\n menu: checkboxMenuProps,\n size: 'medium',\n testId: 'inline-dropdown-with-checkbox-menu-story',\n};\n\nconst radioMenuProps: MenuProps = {\n options: textOptions,\n optionsVariant: 'radio',\n selectedKeys: ['4'],\n isFluid: false,\n};\n\nexport const WithRadioMenuStory = (args: InlineDropdownProps) => {\n const [selectedKeys, setSelectedKeys] = React.useState(\n args.menu?.selectedKeys || [],\n );\n\n React.useEffect(() => {\n setSelectedKeys(args.menu?.selectedKeys || []);\n }, [args.menu?.selectedKeys]);\n\n const handleChange = (option: MenuOption) => {\n setSelectedKeys([option.key]);\n };\n\n return (\n <div className={css.menuBtnWrapper}>\n <InlineDropdown\n {...args}\n menu={{...args.menu, selectedKeys}}\n onOptionSelect={handleChange}\n >\n {args.children}\n </InlineDropdown>\n </div>\n );\n};\n\nWithRadioMenuStory.args = {\n children: 'Long Button Text',\n menu: radioMenuProps,\n testId: 'inline-dropdown-with-radio-menu-story',\n};\n\nexport const WithHeaderAndFooterStory = (args: InlineDropdownProps) => (\n <WithHeaderAndFooter {...args} />\n);\n\nexport const WithHeaderAndFooter = (args: InlineDropdownProps) => {\n const clickAwayRef = React.useRef<ClickAwayRefType | null>(null);\n const [btnText, setBtnText] = React.useState<React.ReactNode>('');\n const [selectedKeys, setSelectedKeys] = React.useState(\n args.menu?.selectedKeys || [],\n );\n\n React.useEffect(() => {\n setSelectedKeys(args.menu?.selectedKeys || []);\n const newBtnText = getButtonLabelFromSelectedKeys(\n args.menu?.selectedKeys,\n args.children,\n );\n setBtnText(newBtnText);\n }, [args.menu?.selectedKeys]);\n\n const handleChange = (option: MenuOption) => {\n const newSelectedKeys = getSelectedKeysFromSelectedOption(\n option,\n selectedKeys,\n );\n const newBtnText = getButtonLabelFromSelectedKeys(\n newSelectedKeys,\n args.children,\n );\n setSelectedKeys(newSelectedKeys);\n setBtnText(newBtnText);\n };\n\n const onSave = () => {\n if (clickAwayRef.current) {\n clickAwayRef.current?.forceClose();\n }\n };\n\n return (\n <div className={css.menuBtnWrapper}>\n <InlineDropdown\n {...args}\n menu={{\n ...args.menu,\n selectedKeys,\n header: <MenuHeader />,\n footer: <MenuFooter onSave={onSave} />,\n }}\n onOptionSelect={handleChange}\n clickAwayRef={clickAwayRef}\n >\n {btnText}\n </InlineDropdown>\n </div>\n );\n};\n\nconst menuPropsWithHeaderAndFooter: MenuProps = {\n options: textOptions,\n optionsVariant: 'checkbox',\n};\n\nWithHeaderAndFooterStory.args = {\n children: 'Long Button Text',\n menu: menuPropsWithHeaderAndFooter,\n size: 'medium',\n testId: 'inline-dropdown-with-header-and-footer-story',\n};\nTextOnlyStory.storyName = 'Text Only';\nTextWithTooltipStory.storyName = 'Text With Tooltip';\nWithSearchStory.storyName = 'With Search';\nWithCheckboxMenuStory.storyName = 'With Checkbox Menu';\nWithRadioMenuStory.storyName = 'With Radio Menu';\nWithHeaderAndFooterStory.storyName = 'With Header And Footer';\n"
|
|
1470
1470
|
},
|
|
1471
1471
|
"css": {
|
|
1472
1472
|
"path": "InlineDropdown.module.css",
|
|
@@ -1507,7 +1507,7 @@
|
|
|
1507
1507
|
},
|
|
1508
1508
|
"css": {
|
|
1509
1509
|
"path": "Input.module.css",
|
|
1510
|
-
"content": "@value (\n size2,\n size18,\n size24,\n size42,\n size30,\n size34,\n sizeFluid\n) from '../../styles/variables/_size.css';\n\n@value (\n spaceNone,\n spaceXSmall,\n spaceSmall,\n spaceMedium,\n spaceXXSmall\n) from '../../styles/variables/_space.css';\n\n@value (\n borderRadiusMedium,\n borderRadiusSmall,\n borderWidthPrimary,\n borderWidthTertiary,\n borderWidthNone,\n borderRadiusXSmall\n) from '../../styles/variables/_border.css';\n\n@value (\n colorTextPrimary,\n colorTextSecondary,\n colorTextTertiary,\n colorBorderPrimary,\n colorFillPrimary,\n colorFocusPrimary,\n colorFocusDanger,\n colorTextDisabled,\n colorFillDisabled,\n colorTextDanger,\n colorBackgroundTertiary,\n colorBorderTertiary\n) from '../../styles/variables/_color.css';\n\n.container {\n color: colorTextPrimary;\n width: sizeFluid;\n}\n\n.box {\n display: flex;\n align-items: center;\n color: colorTextPrimary;\n gap: spaceXSmall;\n height: size42;\n padding: spaceNone spaceSmall;\n border: borderWidthPrimary solid colorBorderPrimary;\n background-color: colorBackgroundTertiary;\n}\n\n.wrapper:not(.withError) .box:not(.inputDisabled):not(.locked):hover {\n border-color: colorBorderTertiary;\n}\n\n.wrapper:not(.withError):focus-within\n .box:not(.inputDisabled):not(.locked):hover {\n border-color: colorFillPrimary;\n}\n\n.locked {\n border-style: dashed;\n}\n\n.medium {\n composes: formInputMedium from '../../styles/typography.module.css';\n height: size42;\n border-radius: borderRadiusMedium;\n}\n\n.colorText {\n composes: formInputMedium from '../../styles/typography.module.css';\n color: colorTextSecondary;\n}\n\n.hasValue {\n color: colorTextPrimary;\n}\n\n.small {\n composes: formInputSmall from '../../styles/typography.module.css';\n height: size34;\n border-radius: borderRadiusSmall;\n}\n\n.wrapper.withError .box {\n border-color: colorTextDanger;\n}\n\n.wrapper.withError:focus-within .box {\n box-shadow: borderWidthNone borderWidthNone borderWidthNone\n borderWidthTertiary colorFocusDanger;\n}\n\n.box input {\n width: sizeFluid;\n height: sizeFluid;\n cursor: inherit;\n}\n\n.wrapper:not(.withError):focus-within .box:not(.inputDisabled) {\n border-color: colorFillPrimary;\n box-shadow: borderWidthNone borderWidthNone borderWidthNone\n borderWidthTertiary colorFocusPrimary;\n}\n\n.inputDisabled {\n color: colorTextDisabled;\n border-color: colorBorderPrimary;\n background-color: colorFillDisabled;\n}\n\ninput::placeholder {\n color: colorTextTertiary;\n}\n\n.inputDisabled input::placeholder {\n color: inherit;\n}\n\n.wrapper {\n display: flex;\n flex-direction: column;\n width: sizeFluid;\n gap: spaceXXSmall;\n}\n\n.info {\n display: flex;\n justify-content: space-between;\n}\n\n.infoContent {\n display: flex;\n}\n\n.rightClickableIcon {\n cursor: pointer;\n}\n\n.rightClickableIcon.disabled {\n pointer-events: none;\n}\n\n.box.color {\n padding-right: spaceMedium;\n}\n\n.box > input[type='color'] {\n display: flex;\n cursor: pointer;\n align-items: center;\n width: fit-content;\n gap: spaceXSmall;\n padding: spaceNone;\n width: size18;\n height: size18;\n margin: spaceNone;\n}\n\n.box > input[type='color']::-webkit-color-swatch {\n display: flex;\n padding: spaceNone;\n align-self: center;\n border: none;\n min-width: size18;\n min-height: size18;\n border-radius: borderRadiusXSmall;\n}\n\n.hideNumberSpinner input[type='number']::-webkit-outer-spin-button,\n.hideNumberSpinner input[type='number']::-webkit-inner-spin-button {\n -webkit-appearance: none;\n margin: 0;\n}\n\n/* Hide spinner in Firefox */\n.hideNumberSpinner input[type='number'] {\n appearance: textfield;\n -moz-appearance: textfield;\n}\n\n.prefixContainer {\n display: flex;\n width: sizeFluid;\n cursor: inherit;\n padding-left: size2;\n overflow-y: auto;\n\n & .prefixText {\n color: colorTextTertiary;\n white-space: nowrap;\n }\n\n & input {\n padding-left: spaceNone;\n min-width: size18;\n }\n}\n\n.rightSlotContainer {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: spaceXSmall;\n width: sizeFluid;\n cursor: inherit;\n padding-left: size2;\n overflow-y: auto;\n\n & .rightSlotContent {\n color: colorTextTertiary;\n padding: 0;\n width: max-content;\n min-width: max-content;\n }\n\n & input {\n padding-left: spaceNone;\n min-width: size18;\n }\n}\n\n.inputRightSlotSeparator {\n width: size2;\n height: size24;\n}\n\n.inputRightSlotSeparator.small {\n height: size18;\n}\n"
|
|
1510
|
+
"content": "@value (\n size2,\n size18,\n size24,\n size42,\n size30,\n size34,\n sizeFluid\n) from '../../styles/variables/_size.css';\n\n@value (\n spaceNone,\n spaceXSmall,\n spaceSmall,\n spaceMedium,\n spaceXXSmall\n) from '../../styles/variables/_space.css';\n\n@value (\n borderRadiusMedium,\n borderRadiusSmall,\n borderWidthPrimary,\n borderWidthTertiary,\n borderWidthNone,\n borderRadiusXSmall\n) from '../../styles/variables/_border.css';\n\n@value (\n colorTextPrimary,\n colorTextSecondary,\n colorTextTertiary,\n colorBorderPrimary,\n colorFillPrimary,\n colorFocusPrimary,\n colorFocusDanger,\n colorTextDisabled,\n colorFillDisabled,\n colorTextDanger,\n colorBackgroundTertiary,\n colorBorderTertiary\n) from '../../styles/variables/_color.css';\n\n.container {\n color: colorTextPrimary;\n width: sizeFluid;\n}\n\n.box {\n display: flex;\n align-items: center;\n color: colorTextPrimary;\n gap: spaceXSmall;\n height: size42;\n padding: spaceNone spaceSmall;\n border: borderWidthPrimary solid colorBorderPrimary;\n background-color: colorBackgroundTertiary;\n}\n\n.wrapper:not(.withError) .box:not(.inputDisabled):not(.locked):hover {\n border-color: colorBorderTertiary;\n}\n\n.wrapper:not(.withError):focus-within\n .box:not(.inputDisabled):not(.locked):hover {\n border-color: colorFillPrimary;\n}\n\n.locked {\n border-style: dashed;\n}\n\n.medium {\n composes: formInputMedium from '../../styles/typography.module.css';\n height: size42;\n border-radius: borderRadiusMedium;\n}\n\n.colorText {\n composes: formInputMedium from '../../styles/typography.module.css';\n color: colorTextSecondary;\n}\n\n.hasValue {\n color: colorTextPrimary;\n}\n\n.small {\n composes: formInputSmall from '../../styles/typography.module.css';\n height: size34;\n border-radius: borderRadiusSmall;\n}\n\n.wrapper.withError .box {\n border-color: colorTextDanger;\n}\n\n.wrapper.withError:focus-within .box {\n box-shadow: borderWidthNone borderWidthNone borderWidthNone\n borderWidthTertiary colorFocusDanger;\n}\n\n.box input {\n width: sizeFluid;\n height: sizeFluid;\n cursor: inherit;\n}\n\n.wrapper:not(.withError):focus-within .box:not(.inputDisabled) {\n border-color: colorFillPrimary;\n box-shadow: borderWidthNone borderWidthNone borderWidthNone\n borderWidthTertiary colorFocusPrimary;\n}\n\n/*\n * The field is a popup trigger whose popup is open, so DOM focus has moved into a\n * portal and `:focus-within` above no longer matches. Keep the emphasis anyway,\n * including while hovered — otherwise the hover rule repaints the border grey and\n * leaves the popup's halo behind it, a two-tone broken state.\n *\n * These live here rather than in Dropdown/Typeahead's own module because the\n * rules being overridden are scoped to THIS module's `.wrapper` / `.box` /\n * `.withError` class names, which a consumer stylesheet cannot name and so cannot\n * reliably out-specify.\n *\n * Both the popup attribute AND the expanded state are required so this only ever\n * hits a TRIGGER. On `aria-expanded` alone it also matches any other expanded\n * input inside the field — such as a filter field rendered in the popup — and\n * paints a focus ring on something that never has focus. `:not(.locked)` matches\n * the adjacent hover rules: keying off ARIA loses the protection `:focus-within`\n * had for free, since a locked field's input is natively disabled and so could\n * never have been focused.\n */\n.wrapper:not(.withError)\n .box:not(.inputDisabled):not(.locked):has(\n input[aria-haspopup][aria-expanded='true']\n ) {\n border-color: colorFillPrimary;\n box-shadow: borderWidthNone borderWidthNone borderWidthNone\n borderWidthTertiary colorFocusPrimary;\n}\n\n/* An errored trigger keeps the danger colors when its popup is open. */\n.wrapper.withError\n .box:not(.inputDisabled):not(.locked):has(\n input[aria-haspopup][aria-expanded='true']\n ) {\n border-color: colorTextDanger;\n box-shadow: borderWidthNone borderWidthNone borderWidthNone\n borderWidthTertiary colorFocusDanger;\n}\n\n.inputDisabled {\n color: colorTextDisabled;\n border-color: colorBorderPrimary;\n background-color: colorFillDisabled;\n}\n\ninput::placeholder {\n color: colorTextTertiary;\n}\n\n.inputDisabled input::placeholder {\n color: inherit;\n}\n\n.wrapper {\n display: flex;\n flex-direction: column;\n width: sizeFluid;\n gap: spaceXXSmall;\n}\n\n.info {\n display: flex;\n justify-content: space-between;\n}\n\n.infoContent {\n display: flex;\n}\n\n.rightClickableIcon {\n cursor: pointer;\n}\n\n.rightClickableIcon.disabled {\n pointer-events: none;\n}\n\n.box.color {\n padding-right: spaceMedium;\n}\n\n.box > input[type='color'] {\n display: flex;\n cursor: pointer;\n align-items: center;\n width: fit-content;\n gap: spaceXSmall;\n padding: spaceNone;\n width: size18;\n height: size18;\n margin: spaceNone;\n}\n\n.box > input[type='color']::-webkit-color-swatch {\n display: flex;\n padding: spaceNone;\n align-self: center;\n border: none;\n min-width: size18;\n min-height: size18;\n border-radius: borderRadiusXSmall;\n}\n\n.hideNumberSpinner input[type='number']::-webkit-outer-spin-button,\n.hideNumberSpinner input[type='number']::-webkit-inner-spin-button {\n -webkit-appearance: none;\n margin: 0;\n}\n\n/* Hide spinner in Firefox */\n.hideNumberSpinner input[type='number'] {\n appearance: textfield;\n -moz-appearance: textfield;\n}\n\n.prefixContainer {\n display: flex;\n width: sizeFluid;\n cursor: inherit;\n padding-left: size2;\n overflow-y: auto;\n\n & .prefixText {\n color: colorTextTertiary;\n white-space: nowrap;\n }\n\n & input {\n padding-left: spaceNone;\n min-width: size18;\n }\n}\n\n.rightSlotContainer {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: spaceXSmall;\n width: sizeFluid;\n cursor: inherit;\n padding-left: size2;\n overflow-y: auto;\n\n & .rightSlotContent {\n color: colorTextTertiary;\n padding: 0;\n width: max-content;\n min-width: max-content;\n }\n\n & input {\n padding-left: spaceNone;\n min-width: size18;\n }\n}\n\n.inputRightSlotSeparator {\n width: size2;\n height: size24;\n}\n\n.inputRightSlotSeparator.small {\n height: size18;\n}\n"
|
|
1511
1511
|
},
|
|
1512
1512
|
"index": {
|
|
1513
1513
|
"path": "index.d.ts",
|
|
@@ -1617,24 +1617,40 @@
|
|
|
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 /**\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 /** Max height of the whole card in px, search/header/footer included; never below the first row. */\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 { FixedSizeList, VariableSizeList } from 'react-window';\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 /** Max height of the whole card in px, search/header/footer included; never below the first row. */\n menuHeight?: number;\n}>;\n/**\n * Either react-window list Menu may render. Both expose the `scrollToItem`\n * used to keep the keyboard's active option mounted.\n */\ntype VirtualList = FixedSizeList | VariableSizeList;\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 /**\n * Opt in to the accessibility behavior: keyboard navigation, the\n * `listbox` / `option` roles and their states, focus management on open and\n * close, and the scroll container moving onto the listbox.\n *\n * Off by default, and off is not a degraded mode — it is this component\n * exactly as it behaved before any of it existed: no roles, no ARIA, no key\n * handling, no focus moved, the card scrolling as it always did. Every prop\n * below that exists to serve the accessible behavior (`onTabOut`,\n * `onReturnFocusToInput`, `menuId`, `ariaLabelledBy`, `ariaLabel`,\n * `initialFocusRef`, `activeOptionKey`, `externalKeyboardNav`,\n * `onActiveOptionKeyChange`) is inert while this is unset.\n *\n * It is opt-in per call site so a surface can turn it on, verify it, and\n * leave the rest of the app untouched — the behavior changes focus and DOM\n * structure, and those are the things an app is most likely to have built\n * around.\n */\n enableA11y?: boolean;\n onTabOut?: () => unknown;\n /**\n * Combobox consumers only: return focus to the trigger input (menu stays open)\n * when Tab/Shift+Tab moves off a header/footer control back toward the input.\n * The input lives outside the menu DOM, so it is the centre of the tab order.\n */\n onReturnFocusToInput?: () => void;\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 * Announced by a `Typeahead`'s live region while it is loading, and when a\n * search returns nothing. Here rather than on `Typeahead` so every string a\n * menu can speak is overridden in one place.\n */\n LOADING_RESULTS?: string;\n NO_RESULTS?: string;\n };\n showLabelTooltip?: MenuLabelTooltip;\n allowWrap?: boolean;\n /**\n * id applied to the option `listbox` — the element a trigger's `aria-controls`\n * should reference — and the prefix for the per-option ids that\n * `aria-activedescendant` points at. Falls back to a generated id. It is not\n * on the outer card.\n */\n menuId?: string;\n /**\n * Accessible name for the popup, linked to the trigger that opens it.\n */\n ariaLabelledBy?: string;\n /**\n * Literal accessible name for the listbox, used only when `ariaLabelledBy` is\n * not supplied. Defaults to `'Options'` so the listbox is never unnamed — pass\n * something specific to the content wherever you can.\n */\n ariaLabel?: string;\n /**\n * Marks the Menu as a popup that should auto-focus on open. When provided,\n * Menu moves focus to the search input (or the active option) once it mounts\n * and writes that element into the ref. Omit it for inline Menus (and for\n * combobox triggers using `externalKeyboardNav`) so focus is never stolen.\n */\n initialFocusRef?: React.RefObject<HTMLElement | null>;\n /**\n * Controlled active option, honoured only with `externalKeyboardNav`: the\n * trigger outside the menu owns the highlight and reports it here. Without\n * `externalKeyboardNav` Menu manages its own active option (the roving tab\n * stop, or the search field's active descendant) and this prop is ignored.\n */\n activeOptionKey?: string | null;\n /**\n * When true the Menu is driven by an external combobox input (e.g. Dropdown /\n * Typeahead): focus stays on that input, the Menu does not run its own\n * keyboard navigation or move focus into itself, and `activeOptionKey` is a\n * controlled prop reflecting the input's aria-activedescendant.\n */\n externalKeyboardNav?: boolean;\n /** Report active-option changes when `externalKeyboardNav` is set. */\n onActiveOptionKeyChange?: (key: string | null) => void;\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 /** Pointer-activation handler threaded down to each MenuOptionButton. */\n onPointerActivate?: (key: string, el: HTMLElement, clientX: number, clientY: number) => void;\n /** Handle on the virtual list, so Menu can scroll the active option into view. */\n listRef?: React.RefCallback<VirtualList>;\n /** Key the pointer most recently highlighted — keeps the focus ring off it. */\n pointerActiveKey?: string | null;\n /** Withhold the opening highlight until the user drives the menu themselves. */\n suppressInitialHighlight?: boolean;\n /** Whether focus is inside the menu — a roving highlight is only drawn then. */\n menuHasFocus?: boolean;\n}\n/**\n * Flatten the active option source (options | compose | groups) into one list.\n * Exported because a combobox trigger drives the highlight from outside Menu\n * and needs the same list Menu itself navigates — see `useMenuTrigger`.\n */\nexport declare const getFlatOptions: (props: MenuProps) => MenuOption[];\n/**\n * Key of the option that should own the roving tab stop when the menu opens:\n * the selected option if present, otherwise the first enabled option.\n */\nexport declare const getInitialActiveOptionKey: (props: MenuProps) => string | null;\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** 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"
|
|
1624
|
+
"content": "import * as React from 'react';\n\nimport {difference, union} from 'lodash';\n\nimport {ClickAway} from 'src/utils/click-away';\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 {\n useComboboxMenuTrigger,\n useMenuTrigger,\n} from 'src/components/Menu/useMenuTrigger';\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 enableA11y: {\n description:\n '**enableA11y** opts into the accessibility behavior: keyboard navigation, the `listbox` / `option` roles and their states, focus management on open and close, and the scroll container moving onto the listbox. Off by default, where the Menu behaves exactly as it did before any of that existed. Off here too, as everywhere: turn the control on to see the accessible behavior.',\n control: {type: 'boolean'},\n table: {\n type: {summary: 'boolean'},\n defaultValue: {summary: 'false'},\n },\n },\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** is called when Tab (or Shift+Tab) would take focus out of the menu entirely. Supplying it means the menu takes over that key: the default is prevented and the consumer is expected to close the menu and put focus somewhere sensible — normally back on the trigger. Omit it for an inline menu, so Tab simply moves on to the next thing on the page.',\n table: {\n type: {summary: '() => mixed'},\n },\n // Deliberately NOT an `action`: an injected spy is truthy, so the menu\n // would take over Tab and then nothing would close it or move focus,\n // making every inline story look like a keyboard trap.\n },\n onReturnFocusToInput: {\n description:\n 'Combobox consumers only. Called when Tab/Shift+Tab moves off a header/footer control back toward the trigger input, which lives outside the menu DOM and is therefore the centre of the tab order. Return focus to it and keep the menu open. Omit it for menus whose input is inside the menu.',\n table: {\n type: {summary: '() => void'},\n },\n },\n menuId: {\n description:\n 'DOM id applied to the option `listbox`. Wire it to the trigger\\'s `aria-controls` and it is also the prefix for the per-option ids used by `aria-activedescendant`. Falls back to a generated id.',\n table: {\n type: {summary: 'string'},\n },\n },\n ariaLabelledBy: {\n description:\n 'Id of the element that names the popup — normally the trigger, or the field label. `listbox` requires an accessible name, so supply this or `ariaLabel`.',\n table: {\n type: {summary: 'string'},\n },\n },\n ariaLabel: {\n description:\n 'Literal accessible name for the listbox, used only when `ariaLabelledBy` is absent. Prefer naming it after the field rather than echoing a placeholder.',\n table: {\n type: {summary: 'string'},\n },\n },\n initialFocusRef: {\n description:\n 'Marks the Menu as a popup that should take focus when it opens: focus moves to the search field, or the active option, and the resolved element is written back into the ref. Omit it for inline menus, and for combobox triggers using `externalKeyboardNav`, so focus is never stolen.',\n table: {\n type: {summary: 'RefObject<HTMLElement | null>'},\n },\n },\n activeOptionKey: {\n description:\n 'Key of the option that owns the roving tab stop, or — with `externalKeyboardNav` — the controlled active descendant.',\n table: {\n type: {summary: 'string | null'},\n },\n },\n externalKeyboardNav: {\n description:\n 'Set when an outside combobox input drives navigation (Dropdown / Typeahead): focus stays on that input, Menu does no option navigation of its own (Tab between its header, options and footer still works) and never moves focus into itself, and `activeOptionKey` becomes a controlled prop mirroring the input\\'s `aria-activedescendant`.',\n table: {\n type: {summary: 'boolean'},\n defaultValue: {summary: 'false'},\n },\n },\n onActiveOptionKeyChange: {\n description:\n 'Reports active-option changes back to the parent. Required when `externalKeyboardNav` is set, since the parent owns that state.',\n table: {\n type: {summary: '(key: string | null) => void'},\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\n/*\n * Roving focus over a virtualized list: `allowSearch` is off, so the options\n * themselves own focus rather than a search field, and arrow keys / Home / End\n * / type-ahead move real DOM focus between rows that mount and unmount as the\n * list scrolls. Kept as its own story because that pairing is what breaks when\n * a virtualized row is re-created underneath the focused element.\n */\nexport const VirtualizedRovingFocusMenuStory = (args: MenuProps) => (\n <Menu {...args} classNames={{groupTitle: css.gpTitle}} />\n);\n\nVirtualizedRovingFocusMenuStory.args = {\n // This story exists to demonstrate the accessible behavior, so it opts in.\n enableA11y: true,\n isFluid: false,\n menuDisabled: false,\n classNames: {option: css.optionText},\n size: 'medium',\n allowSearch: false,\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 })),\n })),\n virtualization: {\n enable: true,\n itemHeight: 36,\n groupTitleHeight: 32,\n menuHeight: 320,\n },\n selectedKeys: ['0-1'],\n testId: 'menu-virtualized-roving-focus-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\n/*\n * Bulk actions over a filtered checkbox list.\n *\n * The actions live in the menu `header`, NOT in a group title: a group title is\n * rendered inside the `role=\"listbox\"`, which may only own `option` and `group`\n * children, so interactive controls in there are invalid (axe reports\n * `aria-required-children`, and AT announces a button where it expects an\n * option). The header sits outside the listbox, which is where controls that\n * act ON the list belong. The label naming the list is handed to Menu as\n * `ariaLabelledBy`, so the listbox is named \"Values (n)\" rather than the\n * generic default.\n */\nconst GroupTitleActionMenu = (args: MenuProps) => {\n const valuesLabelId = 'menu-group-title-action-values';\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 ariaLabelledBy={valuesLabelId}\n classNames={{\n header: css.groupTitleActionHeader,\n }}\n header={\n <>\n <SearchInput\n value={searchText}\n onChange={(e) => setSearchText(e.target.value)}\n onClear={() => setSearchText('')}\n size={args.size}\n />\n <div className={css.groupTitleActionRow}>\n <span id={valuesLabelId}>\n <FormLabelMedium color=\"tertiary\">\n {`Values (${optionsFiltered.length})`}\n </FormLabelMedium>\n </span>\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 </div>\n </>\n }\n options={optionsFiltered}\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};\n/*\n * The two trigger hooks, wired the way a consumer would wire them. These are the\n * only stories that mount `useMenuTrigger` / `useComboboxMenuTrigger` directly,\n * so a consumer-side change to either shows up here first: the trigger's ARIA\n * before and after opening, ArrowDown opening the popup, focus moving in (or\n * staying on the input), and Tab/Escape closing it and returning focus.\n */\nconst ButtonTriggerMenu = (args: MenuProps) => {\n // A trigger that already has an id of its own: hand it to the hook rather\n // than setting it on the element as well, and the hook keeps it — a generated\n // one would break whatever the consumer points at it — and names the listbox\n // through it unless `ariaLabel` says otherwise. Setting it in both places\n // would be a duplicate prop, which is what `getTriggerProps` returning the\n // resolved id exists to avoid.\n const menuTrigger = useMenuTrigger({id: 'menu-trigger-demo'});\n return (\n <ClickAway restoreFocusOnClose>\n {({isOpen, onOpen, clickAway, boundaryRef, triggerRef}) => (\n <div className={css.triggerDemo}>\n <Button\n ref={triggerRef as React.RefObject<HTMLButtonElement>}\n {...menuTrigger.getTriggerProps({isOpen, onOpen})}\n onClick={(e) => {\n e.stopPropagation();\n onOpen();\n }}\n >\n Actions\n </Button>\n {isOpen && (\n <div\n ref={boundaryRef as React.RefObject<HTMLDivElement>}\n className={css.triggerDemoPopup}\n >\n <Menu\n {...args}\n {...menuTrigger.menuProps}\n onSelect={() => clickAway()}\n onTabOut={clickAway}\n />\n </div>\n )}\n </div>\n )}\n </ClickAway>\n );\n};\n\nexport const WithButtonTriggerStory = (args: MenuProps) => (\n <ButtonTriggerMenu {...args} />\n);\n\nWithButtonTriggerStory.args = {\n // This story exists to demonstrate the accessible behavior, so it opts in.\n enableA11y: true,\n isFluid: false,\n menuDisabled: false,\n classNames: {option: css.optionText},\n size: 'medium',\n options: textOptions,\n selectedKeys: ['2'],\n header: <MenuHeader />,\n footer: <MenuFooter />,\n testId: 'menu-with-button-trigger-story',\n};\n\nconst ComboboxTriggerMenu = (args: MenuProps) => {\n const menuRef = React.useRef<HTMLDivElement>(null);\n const triggerWrapRef = React.useRef<HTMLDivElement>(null);\n const clickAwayFnRef = React.useRef<(() => void) | null>(null);\n const [isMenuOpen, setIsMenuOpen] = React.useState(false);\n const [selected, setSelected] = React.useState<MenuOption | null>(\n args.selectedOption ?? null,\n );\n const menu: MenuProps = {...args, selectedOption: selected};\n const focusInput = () =>\n triggerWrapRef.current?.querySelector('input')?.focus();\n const select = (option: MenuOption) => {\n setSelected(option);\n clickAwayFnRef.current?.();\n focusInput();\n };\n const combobox = useComboboxMenuTrigger({\n menu,\n menuRef,\n isOpen: isMenuOpen,\n onSelectOption: select,\n onClose: () => clickAwayFnRef.current?.(),\n focusTrigger: focusInput,\n triggerAriaLabel: 'Fruit',\n });\n return (\n <ClickAway<HTMLDivElement, HTMLDivElement>\n onChange={setIsMenuOpen}\n restoreFocusOnClose\n >\n {({isOpen, onOpen, clickAway, boundaryRef, triggerRef}) => {\n clickAwayFnRef.current = clickAway;\n return (\n <div className={css.triggerDemo}>\n <div ref={triggerWrapRef} className={css.triggerDemoInput}>\n <SearchInput\n {...combobox.getTriggerProps({isOpen, onOpen})}\n boxRef={triggerRef}\n readOnly\n value={selected?.label ?? ''}\n placeholder=\"Select a fruit\"\n onContainerClick={(e) => {\n e.stopPropagation();\n onOpen();\n }}\n onChange={() => undefined}\n />\n </div>\n {isOpen && (\n <div\n ref={boundaryRef as React.RefObject<HTMLDivElement>}\n className={css.triggerDemoPopup}\n >\n <Menu\n {...menu}\n ref={menuRef}\n {...combobox.menuProps}\n onSelect={select}\n onTabOut={clickAway}\n />\n </div>\n )}\n </div>\n );\n }}\n </ClickAway>\n );\n};\n\nexport const WithComboboxTriggerStory = (args: MenuProps) => (\n <ComboboxTriggerMenu {...args} />\n);\n\nWithComboboxTriggerStory.args = {\n // This story exists to demonstrate the accessible behavior, so it opts in.\n enableA11y: true,\n isFluid: false,\n menuDisabled: false,\n classNames: {option: css.optionText},\n size: 'medium',\n options: textOptions,\n footer: <MenuFooter />,\n testId: 'menu-with-combobox-trigger-story',\n};\n\nTextOnlyStory.storyName = 'Text Only';\nWithButtonTriggerStory.storyName = 'With Button Trigger (useMenuTrigger)';\nWithComboboxTriggerStory.storyName =\n 'With Combobox Trigger (useComboboxMenuTrigger)';\nTextWithTooltipStory.storyName = 'Text With Tooltip';\nMenuWithWrapStory.storyName = 'Menu With Wrap';\nWithSearchStory.storyName = 'With Search';\n/*\n * The same menu as `Text Only`, with the accessibility opt-in turned on, so the\n * two can be compared side by side.\n *\n * With `enableA11y` the options become a named `listbox` of `option`s, one of\n * them holds the roving tab stop, and the arrow keys, Home/End, PageUp/PageDown\n * and type-ahead drive it. The highlight is only drawn while focus is inside\n * the menu, because under roving focus the highlight IS the focus.\n *\n * Every other story here leaves the prop off, which is the default and is this\n * component exactly as it behaved before any of this existed. Flip the control\n * on any of them to see the difference.\n */\nexport const AccessibleMenuStory = (args: MenuProps) => <Menu {...args} />;\n\nAccessibleMenuStory.args = {\n enableA11y: true,\n isFluid: false,\n menuDisabled: false,\n size: 'medium',\n options: textOptions,\n selectedKeys: ['2'],\n ariaLabel: 'Fruit',\n testId: 'menu-accessible-story',\n};\n\n/*\n * The accessible menu with its own filter field. The field is a `searchbox`,\n * not a second combobox, and the list it filters is a focusable `listbox` that\n * reports the active option through `aria-activedescendant` — so the field\n * keeps the keys it needs while the arrow keys still move the highlight.\n */\nexport const AccessibleSearchMenuStory = (args: MenuProps) => (\n <Menu {...args} />\n);\n\nAccessibleSearchMenuStory.args = {\n enableA11y: true,\n isFluid: false,\n menuDisabled: false,\n size: 'medium',\n allowSearch: true,\n options: textOptions,\n selectedKeys: ['2'],\n ariaLabel: 'Fruit',\n testId: 'menu-accessible-search-story',\n};\n\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';\nVirtualizedRovingFocusMenuStory.storyName =\n 'Virtualized Menu With Roving Focus';\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';\nAccessibleMenuStory.storyName = 'Accessible (enableA11y)';\nAccessibleSearchMenuStory.storyName = 'Accessible With Search (enableA11y)';\n"
|
|
1625
1625
|
},
|
|
1626
1626
|
"css": {
|
|
1627
1627
|
"path": "Menu.module.css",
|
|
1628
|
-
"content": "@value (\n borderWidthPrimary,\n borderWidthNone,\n borderWidthTertiary,\n borderRadiusMedium,\n borderRadiusSmall\n) from '../../styles/variables/_border.css';\n\n@value (\n colorBackgroundSecondary,\n colorFillSecondary,\n colorTextDisabled,\n colorTextPrimary,\n colorTextSecondary,\n colorTextTertiary,\n colorBackgroundTertiary,\n colorFocusSecondary\n) from '../../styles/variables/_color.css';\n\n@value (\n colorBorderPrimary\n) from '../../styles/variables/_color.css';\n\n@value (\n size160,\n size42,\n size34,\n size50,\n size300,\n size240,\n size228,\n size276,\n sizeFluid\n) from '../../styles/variables/_size.css';\n\n@value (\n spaceNone,\n spaceSmall,\n spaceXSmall,\n spaceXXSmall,\n spaceMedium\n) from '../../styles/variables/_space.css';\n\n@value (\n elevationMenu,\n elevationCard\n) from '../../styles/variables/_elevation.css';\n\n.menuCard {\n position: relative;\n composes: boxShadow2 from '../../styles/shadow.module.css';\n composes: borderPrimary from '../../styles/border.module.css';\n background-color: colorBackgroundTertiary;\n display: flex;\n flex-flow: column;\n padding: spaceXSmall;\n min-width: size160;\n border-radius: borderRadiusMedium;\n overflow: auto;\n z-index: elevationMenu;\n}\n\n.menuCardTopPaddingZero {\n padding-top: spaceNone;\n}\n\n.menuCardBottomPaddingZero {\n padding-bottom: spaceNone;\n}\n\n.menuCard:empty {\n padding: spaceNone;\n border: none;\n}\n\n.medium {\n width: size300;\n min-width: size300;\n max-height: size276;\n}\n\n.mediumWithHeader,\n.mediumWithFooter {\n max-height: calc(size276 + size50);\n}\n\n.mediumWithHeaderAndFooter {\n max-height: calc(size276 + size50 + size50);\n}\n\n.small {\n width: size240;\n min-width: size240;\n max-height: size228;\n}\n\n.smallWithHeader,\n.smallWithFooter {\n max-height: calc(size228 + size50);\n}\n\n.smallWithHeaderAndFooter {\n max-height: calc(size228 + size50 + size50);\n}\n\n.fluid {\n width: sizeFluid;\n min-width: size160;\n}\n\n.option {\n composes: motionEaseInEaseOut from '../../styles/animation.module.css';\n flex: 0 0 auto;\n width: sizeFluid;\n display: flex;\n border-radius: borderRadiusSmall;\n align-items: center;\n cursor: pointer;\n column-gap: spaceSmall;\n color: colorTextSecondary;\n}\n\n.optionTextContainer {\n display: flex;\n flex-flow: column;\n gap: spaceXXSmall;\n}\n\n.optionTextLabel,\n.optionTextSecondaryLabel {\n display: flex;\n flex-flow: column;\n align-items: flex-start;\n text-align: left;\n}\n\n.withIconLeft {\n padding-left: spaceXSmall;\n}\n\n.withIconRight {\n padding-right: spaceXSmall;\n}\n\n.icon {\n color: inherit;\n}\n\n.rightIcon {\n margin-left: auto;\n}\n\n.optionSmall {\n composes: menuTextSmall from '../../styles/typography.module.css';\n padding: spaceXSmall spaceSmall;\n}\n\n.optionMedium {\n composes: menuTextMedium from '../../styles/typography.module.css';\n padding: spaceSmall spaceSmall;\n}\n\n.optionTextSecondaryLabel {\n composes: bodySmall from '../../styles/typography.module.css';\n color: colorTextTertiary;\n}\n\n.option:hover,\n.option:active {\n background: colorBackgroundSecondary;\n color: colorTextPrimary;\n}\n\n.option:hover .optionTextSecondaryLabel {\n color: colorTextSecondary;\n}\n\n.option:active .optionTextSecondaryLabel {\n color: colorTextSecondary;\n}\n\n.option:focus {\n outline: none;\n color: colorTextPrimary;\n}\n\n.option:focus-visible {\n outline: none;\n color: colorTextPrimary;\n box-shadow: inset borderWidthNone borderWidthNone borderWidthNone\n borderWidthTertiary colorFocusSecondary;\n}\n\n.option:focus-within {\n outline: none;\n color: colorTextPrimary;\n /* box-shadow: none; */\n}\n\n.option.selected {\n background: colorFillSecondary;\n outline: none;\n color: colorTextPrimary;\n}\n\n.option:focus .optionTextSecondaryLabel {\n color: colorTextSecondary;\n}\n\n.disabled {\n color: colorTextDisabled;\n pointer-events: none;\n}\n\n.disabled .optionTextSecondaryLabel {\n color: colorTextDisabled;\n}\n\n.menuDivider {\n padding-top: spaceXSmall;\n padding-bottom: spaceXSmall;\n}\n\n.menuDivider:not(:first-of-type) {\n border-top: borderWidthPrimary solid colorBorderPrimary;\n padding-top: spaceXSmall;\n padding-bottom: spaceXSmall;\n}\n\n.hideDivider {\n border-top: none;\n}\n\n.menuDivider:first-child {\n padding-top: spaceNone;\n}\n\n.menuDivider:last-child {\n padding-bottom: spaceNone;\n}\n\n.optionsWrapper {\n display: flex;\n flex-flow: column;\n}\n\n.groupTitleWrapper {\n composes: formLabelSmall from '../../styles/typography.module.css';\n display: flex;\n padding-bottom: spaceXSmall;\n padding-left: spaceSmall;\n padding-right: calc(spaceXSmall / 2);\n color: colorTextTertiary;\n padding-top: spaceSmall;\n}\n\n.filterOptionsResultText {\n margin-top: calc(spaceXSmall * 2);\n margin-bottom: spaceXSmall;\n margin-left: spaceSmall;\n}\n\n.menuHeader {\n position: sticky;\n background-color: colorBackgroundTertiary;\n padding: spaceXSmall spaceNone;\n top: spaceNone;\n margin-bottom: spaceXSmall;\n z-index: calc(elevationCard/4);\n min-height: size50;\n max-height: size50;\n}\n\n.menuHeader::after {\n content: '';\n position: absolute;\n left: calc(spaceXSmall * -1);\n right: calc(spaceXSmall * -1);\n bottom: spaceNone;\n border-bottom: borderWidthPrimary solid colorBorderPrimary;\n}\n\n.menuFooter {\n position: sticky;\n background-color: colorBackgroundTertiary;\n padding: spaceXSmall spaceNone;\n bottom: spaceNone;\n margin-top: spaceXSmall;\n min-height: size50;\n max-height: size50;\n}\n\n.menuFooter::before {\n content: '';\n position: absolute;\n left: calc(spaceXSmall * -1);\n right: calc(spaceXSmall * -1);\n top: spaceNone;\n border-top: borderWidthPrimary solid colorBorderPrimary;\n}\n"
|
|
1628
|
+
"content": "@value (\n borderWidthPrimary,\n borderWidthNone,\n borderWidthTertiary,\n borderRadiusMedium,\n borderRadiusSmall\n) from '../../styles/variables/_border.css';\n\n@value (\n colorBackgroundSecondary,\n colorFillSecondary,\n colorFillPrimary,\n colorTextDisabled,\n colorTextPrimary,\n colorTextSecondary,\n colorTextTertiary,\n colorBackgroundTertiary,\n colorFocusSecondary\n) from '../../styles/variables/_color.css';\n\n@value (\n colorBorderPrimary\n) from '../../styles/variables/_color.css';\n\n@value (\n size160,\n size42,\n size34,\n size50,\n size300,\n size240,\n size228,\n size276,\n sizeFluid\n) from '../../styles/variables/_size.css';\n\n@value (\n spaceNone,\n spaceSmall,\n spaceXSmall,\n spaceXXSmall,\n spaceMedium\n) from '../../styles/variables/_space.css';\n\n@value (\n elevationMenu,\n elevationCard\n) from '../../styles/variables/_elevation.css';\n\n.menuCard {\n position: relative;\n composes: boxShadow2 from '../../styles/shadow.module.css';\n composes: borderPrimary from '../../styles/border.module.css';\n background-color: colorBackgroundTertiary;\n display: flex;\n flex-flow: column;\n padding: spaceXSmall;\n min-width: size160;\n border-radius: borderRadiusMedium;\n overflow: auto;\n z-index: elevationMenu;\n}\n\n/*\n * The options carry their own scroll container, so the card must not scroll as\n * well — otherwise both would show a scrollbar. `hidden` also keeps a pinned\n * header / footer inside the border radius.\n */\n.menuCardA11y.menuCardTopPaddingZero,\n.menuCardA11y.menuCardBottomPaddingZero,\n.menuCardNoScroll {\n overflow: hidden;\n}\n\n.menuCardTopPaddingZero {\n padding-top: spaceNone;\n}\n\n.menuCardBottomPaddingZero {\n padding-bottom: spaceNone;\n}\n\n/*\n * Scrolls the options only. A header / footer is a flex sibling of this element\n * rather than something sticky inside it, so the scrollbar spans just the option\n * list — it no longer runs the whole height of the card behind the bars — and no\n * option can be clipped under them. `min-height: 0` is what lets a flex item\n * actually shrink and scroll instead of growing to fit its content.\n */\n.optionsScroll {\n display: flex;\n flex-flow: column;\n flex: 1 1 auto;\n min-height: 0;\n overflow-y: auto;\n}\n\n/*\n * Collapse a menu that renders nothing. `:empty` alone no longer suffices: the\n * card always holds the (possibly empty) role=\"listbox\" element so the trigger's\n * aria-controls resolves, so Menu also sets `.menuCardEmpty` explicitly.\n */\n.menuCard:empty,\n.menuCard.menuCardEmpty {\n padding: spaceNone;\n border: none;\n}\n\n.medium {\n width: size300;\n min-width: size300;\n max-height: size276;\n}\n\n.mediumWithHeader,\n.mediumWithFooter {\n max-height: calc(size276 + size50);\n}\n\n.mediumWithHeaderAndFooter {\n max-height: calc(size276 + size50 + size50);\n}\n\n.small {\n width: size240;\n min-width: size240;\n max-height: size228;\n}\n\n.smallWithHeader,\n.smallWithFooter {\n max-height: calc(size228 + size50);\n}\n\n.smallWithHeaderAndFooter {\n max-height: calc(size228 + size50 + size50);\n}\n\n.fluid {\n width: sizeFluid;\n min-width: size160;\n}\n\n.option {\n composes: motionEaseInEaseOut from '../../styles/animation.module.css';\n flex: 0 0 auto;\n width: sizeFluid;\n display: flex;\n border-radius: borderRadiusSmall;\n align-items: center;\n cursor: pointer;\n column-gap: spaceSmall;\n color: colorTextSecondary;\n}\n\n.optionTextContainer {\n display: flex;\n flex-flow: column;\n gap: spaceXXSmall;\n}\n\n.optionTextLabel,\n.optionTextSecondaryLabel {\n display: flex;\n flex-flow: column;\n align-items: flex-start;\n text-align: left;\n}\n\n.withIconLeft {\n padding-left: spaceXSmall;\n}\n\n.withIconRight {\n padding-right: spaceXSmall;\n}\n\n.icon {\n color: inherit;\n}\n\n.rightIcon {\n margin-left: auto;\n}\n\n.optionSmall {\n composes: menuTextSmall from '../../styles/typography.module.css';\n padding: spaceXSmall spaceSmall;\n}\n\n.optionMedium {\n composes: menuTextMedium from '../../styles/typography.module.css';\n padding: spaceSmall spaceSmall;\n}\n\n.optionTextSecondaryLabel {\n composes: bodySmall from '../../styles/typography.module.css';\n color: colorTextTertiary;\n}\n\n.option:hover,\n.option:active {\n background: colorBackgroundSecondary;\n color: colorTextPrimary;\n}\n\n.option:hover .optionTextSecondaryLabel {\n color: colorTextSecondary;\n}\n\n.option:active .optionTextSecondaryLabel {\n color: colorTextSecondary;\n}\n\n/*\n * These two suppress the browser's own focus ring, which a CLICK produces:\n * clicking an option focuses it, and `:focus-visible` deliberately does not\n * match pointer-driven focus, so without them a clicked option grows a native\n * outline. Unscoped, because that is true whether or not a consumer opted in.\n */\n.option:focus {\n outline: none;\n color: colorTextPrimary;\n}\n\n.option:focus-within {\n outline: none;\n color: colorTextPrimary;\n}\n\n.option:focus .optionTextSecondaryLabel {\n color: colorTextSecondary;\n}\n\n/*\n * Selected value (committed), shown as a stronger fill than the active highlight.\n *\n * Deliberately no left accent bar: a 2px inset box-shadow gets tapered by the\n * option's 8px border-radius, so it renders as a small curved sliver at the edge\n * that reads as a stray focus artefact rather than an intentional indicator. The\n * checkbox / radio glyph carries the state in those variants, and forced-colors\n * mode below re-expresses it as a full outline, which the radius follows cleanly.\n */\n.option.selected {\n background: colorFillSecondary;\n outline: none;\n color: colorTextPrimary;\n}\n\n/*\n * Focus ring — KEYBOARD ONLY. A focus indicator has to mean \"focus is here\", so\n * it must not appear under the mouse pointer:\n *\n * - `.activeRing` is set by Menu for the combobox modes (Dropdown / Typeahead),\n * where DOM focus stays on the input and the active option is virtual. Menu\n * withholds it when the highlight came from the pointer.\n * - `:focus-visible` covers the roving menus, which move real DOM focus. It is\n * exactly the right selector here: arrow-key navigation calls .focus() after a\n * key press so the ring shows, while hover-driven .focus() follows a pointer\n * interaction so it does not.\n *\n * Declared after `.selected`, which sets no box-shadow of its own, so a selected\n * option that also has focus shows the ring without anything to fight over.\n */\n.option:focus-visible {\n outline: none;\n color: colorTextPrimary;\n box-shadow: inset borderWidthNone borderWidthNone borderWidthNone\n borderWidthTertiary colorFocusSecondary;\n}\n\n/*\n * The opt-in's own indicators, scoped to it so a consumer who has not opted in\n * keeps exactly the option styling they had. `.active` is the highlight the\n * keyboard moves; `.activeRing` is the focus ring for the combobox modes, where\n * DOM focus stays on the input and `:focus-visible` can never match the option.\n */\n.menuCardA11y .option.active {\n outline: none;\n color: colorTextPrimary;\n background: colorBackgroundSecondary;\n}\n\n.menuCardA11y .option.active .optionTextSecondaryLabel {\n color: colorTextSecondary;\n}\n\n.menuCardA11y .option.activeRing {\n outline: none;\n color: colorTextPrimary;\n box-shadow: inset borderWidthNone borderWidthNone borderWidthNone\n borderWidthTertiary colorFocusSecondary;\n}\n\n/*\n * Windows High Contrast / forced-colors mode drops box-shadow and flattens\n * background, so the bar + tint indicators above vanish. Re-express the active\n * highlight and selected state with system-color outlines (honored in HCM).\n */\n@media (forced-colors: active) {\n .menuCardA11y .option:hover,\n .menuCardA11y .option.active {\n outline: borderWidthTertiary solid Highlight;\n outline-offset: calc(-1 * borderWidthTertiary);\n }\n\n .menuCardA11y .option.selected {\n outline: borderWidthTertiary solid SelectedItem;\n outline-offset: calc(-1 * borderWidthTertiary);\n }\n\n /*\n * Keyboard focus must stay distinguishable from BOTH the hover highlight and\n * the selected state, and Highlight / SelectedItem commonly resolve to the same\n * system color — so change the outline STYLE rather than add a third color.\n * Declared last so a selected option that also has focus still shows it.\n */\n .menuCardA11y .option.activeRing,\n .menuCardA11y .option:focus-visible {\n outline: borderWidthTertiary dashed Highlight;\n outline-offset: calc(-1 * borderWidthTertiary);\n }\n}\n\n.disabled {\n color: colorTextDisabled;\n pointer-events: none;\n}\n\n.disabled .optionTextSecondaryLabel {\n color: colorTextDisabled;\n}\n\n.menuDivider {\n padding-top: spaceXSmall;\n padding-bottom: spaceXSmall;\n}\n\n.menuDivider:not(:first-of-type) {\n border-top: borderWidthPrimary solid colorBorderPrimary;\n padding-top: spaceXSmall;\n padding-bottom: spaceXSmall;\n}\n\n.hideDivider {\n border-top: none;\n}\n\n.menuDivider:first-child {\n padding-top: spaceNone;\n}\n\n.menuDivider:last-child {\n padding-bottom: spaceNone;\n}\n\n.optionsWrapper {\n display: flex;\n flex-flow: column;\n}\n\n/*\n * Wraps only the option list so the listbox role contains option children\n * exclusively (search/header/footer stay outside it). display:contents keeps\n * the existing flex layout of .menuCard unchanged.\n */\n.optionsListbox {\n display: contents;\n}\n\n/*\n * The listbox as the element that actually scrolls. Under virtualization\n * react-window's own scroll box carries the role (see ListboxOuter); this is the\n * same arrangement for a plain option list, and it matters beyond tidiness: the\n * options are not focusable themselves whenever a combobox trigger drives the\n * menu, so a\n * scroll container that is NOT the combobox's popup reads as an unreachable\n * scroll area (axe `scrollable-region-focusable`). Making the listbox the\n * scroller means the region IS the popup, which that rule exempts.\n */\n.optionsListboxScroll {\n display: flex;\n flex-flow: column;\n flex: 1 1 auto;\n min-height: 0;\n overflow-y: auto;\n}\n\n/*\n * The listbox takes a tab stop only where the menu owns its filter field, and in\n * that shape it reports the cursor through `aria-activedescendant` — the ring is\n * drawn on the active OPTION, which is what APG prescribes for that pattern.\n * Leaving the browser's own ring on the container as well shows two focus\n * indicators at once, the outer one around a box the user cannot move, which\n * reads as a rendering fault rather than as focus. Applied to whichever element\n * holds the role: this one, or react-window's scroll box when virtualized.\n */\n.listboxFocusContainer:focus,\n.listboxFocusContainer:focus-visible {\n outline: none;\n}\n\n/*\n * role=\"group\" wrapper around each titled option group. display:contents keeps\n * the existing flex layout (title + options) unchanged.\n */\n.optionGroup {\n display: contents;\n}\n\n.groupTitleWrapper {\n composes: formLabelSmall from '../../styles/typography.module.css';\n display: flex;\n padding-bottom: spaceXSmall;\n padding-left: spaceSmall;\n padding-right: calc(spaceXSmall / 2);\n color: colorTextTertiary;\n padding-top: spaceSmall;\n}\n\n.filterOptionsResultText {\n margin-top: calc(spaceXSmall * 2);\n margin-bottom: spaceXSmall;\n margin-left: spaceSmall;\n}\n\n/*\n * Pinned by living OUTSIDE the options scroll container rather than by sticking\n * inside it, so it never has the option list's scrollbar running behind it and\n * never has an option tucked under it. `relative` positions the divider below.\n */\n.menuHeader {\n position: sticky;\n background-color: colorBackgroundTertiary;\n padding: spaceXSmall spaceNone;\n top: spaceNone;\n margin-bottom: spaceXSmall;\n z-index: calc(elevationCard/4);\n min-height: size50;\n max-height: size50;\n}\n\n/*\n * With the opt-in the options carry their own scroll container, so the bars are\n * pinned by living OUTSIDE it rather than by sticking inside it: the option\n * list's scrollbar never runs behind them and no option is ever tucked under\n * them. `relative` positions the divider below.\n */\n.menuCardA11y .menuHeader,\n.menuCardA11y .menuFooter {\n position: relative;\n flex: 0 0 auto;\n top: auto;\n bottom: auto;\n}\n\n.menuHeader::after {\n content: '';\n position: absolute;\n left: calc(spaceXSmall * -1);\n right: calc(spaceXSmall * -1);\n bottom: spaceNone;\n border-bottom: borderWidthPrimary solid colorBorderPrimary;\n}\n\n/* As `.menuHeader`, pinned by sitting outside the options scroll container. */\n.menuFooter {\n position: sticky;\n background-color: colorBackgroundTertiary;\n padding: spaceXSmall spaceNone;\n bottom: spaceNone;\n margin-top: spaceXSmall;\n min-height: size50;\n max-height: size50;\n}\n\n.menuFooter::before {\n content: '';\n position: absolute;\n left: calc(spaceXSmall * -1);\n right: calc(spaceXSmall * -1);\n top: spaceNone;\n border-top: borderWidthPrimary solid colorBorderPrimary;\n}\n"
|
|
1629
1629
|
},
|
|
1630
1630
|
"index": {
|
|
1631
1631
|
"path": "index.d.ts",
|
|
1632
|
-
"content": "export * from '../../components/Menu/Menu';\nexport * from '../../components/Menu/MenuOptionButton';\n//# sourceMappingURL=index.d.ts.map"
|
|
1632
|
+
"content": "export * from '../../components/Menu/Menu';\nexport * from '../../components/Menu/MenuOptionButton';\nexport * from '../../components/Menu/useMenuTrigger';\nexport * from '../../components/Menu/useMenuKeyboardNavigation';\n//# sourceMappingURL=index.d.ts.map"
|
|
1633
1633
|
},
|
|
1634
1634
|
"additional": {
|
|
1635
|
+
"MenuFocusScope.d.ts": {
|
|
1636
|
+
"path": "MenuFocusScope.d.ts",
|
|
1637
|
+
"content": "import * as React from 'react';\nimport { FloatingFocusManager } from '@floating-ui/react';\n/**\n * floating-ui's `FloatingFocusManager`, or nothing at all.\n *\n * Mounting it is what manages focus, so there is no `disabled` prop to pass —\n * the choice has to be whether the element exists. Every popup trigger in the\n * dropdown family needs that choice: without `enableA11y` focus is managed by\n * floating-ui exactly as it always was, and with it, `Menu` takes over, because\n * the one thing `FloatingFocusManager` cannot express is returning focus to a\n * trigger that lives OUTSIDE the popup.\n */\nexport declare const MenuFocusScope: ({ enabled, context, initialFocus, returnFocus, children, }: {\n enabled: boolean;\n context: React.ComponentProps<typeof FloatingFocusManager>[\"context\"];\n initialFocus: React.RefObject<HTMLElement | null>;\n /** Passed through; `Typeahead` opts out, as it did before. */\n returnFocus?: boolean;\n children: React.ReactElement;\n}) => React.JSX.Element;\n//# sourceMappingURL=MenuFocusScope.d.ts.map"
|
|
1638
|
+
},
|
|
1635
1639
|
"MenuOptionButton.d.ts": {
|
|
1636
1640
|
"path": "MenuOptionButton.d.ts",
|
|
1637
|
-
"content": "import * as React from 'react';\nimport type { CSSProperties } from 'react';\nimport type { BaseMenuProps, MenuOption } from '../../components/Menu/Menu';\nexport interface MenuOptionProps extends Omit<BaseMenuProps, 'option' | 'isLastItem' | 'style'> {\n option: MenuOption;\n isLastItem?: boolean;\n style?: CSSProperties | undefined;\n testId?: string;\n}\nexport declare const MenuOptionButton: (props: MenuOptionProps) => React.JSX.Element;\n//# sourceMappingURL=MenuOptionButton.d.ts.map"
|
|
1641
|
+
"content": "import * as React from 'react';\nimport type { CSSProperties } from 'react';\nimport type { BaseMenuProps, MenuOption } from '../../components/Menu/Menu';\nexport interface MenuOptionProps extends Omit<BaseMenuProps, 'option' | 'isLastItem' | 'style'> {\n option: MenuOption;\n /**\n * Last option in the list. Without `enableA11y` this is how the menu knows\n * where Tab leaves it: the last option carries a keydown listener that calls\n * `onTabOut`. With the opt-in, the keyboard hook owns Tab for the whole menu\n * and this is ignored.\n */\n isLastItem?: boolean;\n style?: CSSProperties | undefined;\n /** Total option count — set for virtualized lists so AT announces \"N of total\". */\n setSize?: number;\n /** 1-based position — set for virtualized lists (windowed DOM hides the rest). */\n posInSet?: number;\n /**\n * Make this option the active highlight when the pointer moves over it, so\n * mouse and keyboard share one cursor (and roving focus follows the pointer).\n * `clientX`/`clientY` let Menu drop no-op moves (scroll under a still cursor).\n */\n onPointerActivate?: (key: string, el: HTMLElement, clientX: number, clientY: number) => void;\n /**\n * Key of the option the POINTER most recently highlighted. Used to keep the\n * focus ring off a highlight the mouse created, while the background tint still\n * follows the pointer.\n */\n pointerActiveKey?: string | null;\n /**\n * Withhold the highlight the menu opened with. Set when the menu was opened by\n * mouse, so it does not appear to have a cursor the user never placed; cleared\n * as soon as they move the active option themselves.\n */\n suppressInitialHighlight?: boolean;\n /**\n * Whether focus is inside the menu. A roving menu's highlight IS its DOM\n * focus, so it must not be drawn once focus has moved elsewhere on the page —\n * the active option is still remembered, it is just no longer shown. The\n * combobox modes ignore this: there the trigger input outside the menu holds\n * focus by design and this highlight is the user's only cursor.\n */\n menuHasFocus?: boolean;\n testId?: string;\n}\nexport declare const MenuOptionButton: (props: MenuOptionProps) => React.JSX.Element;\n//# sourceMappingURL=MenuOptionButton.d.ts.map"
|
|
1642
|
+
},
|
|
1643
|
+
"menuOptionId.d.ts": {
|
|
1644
|
+
"path": "menuOptionId.d.ts",
|
|
1645
|
+
"content": "/**\n * The DOM id of an option row — the one thing the row itself, the listbox's\n * `aria-activedescendant`, and a combobox trigger's `aria-activedescendant`\n * must all agree on, so it is built in exactly one place.\n *\n * Option keys are consumer data and may contain whitespace (\"New York\"), which\n * an id can hold but an IDREF cannot: `aria-activedescendant=\"m-option-New\n * York\"` is a list of two references, neither of which resolves. Encoding the\n * key keeps the id a single token.\n */\nexport declare const menuOptionId: (menuId: string, key: string) => string;\n//# sourceMappingURL=menuOptionId.d.ts.map"
|
|
1646
|
+
},
|
|
1647
|
+
"useMenuKeyboardNavigation.d.ts": {
|
|
1648
|
+
"path": "useMenuKeyboardNavigation.d.ts",
|
|
1649
|
+
"content": "import * as React from 'react';\n/**\n * Keyboard navigation for the Menu option list. Three interaction models,\n * selected via `mode`:\n *\n * 1. `'roving'` — ROVING FOCUS (default; menu-button popups). Options are native\n * <button>s, so ArrowUp/ArrowDown move real DOM focus between the enabled\n * options and Space/Enter activate the focused option natively. Home/End jump\n * to the first/last option, PageUp/PageDown move by a page, and typing a\n * printable character jumps to the next matching label (type-ahead).\n *\n * 2. `'activedescendant'` — SELECT-ONLY COMBOBOX. DOM focus stays on the\n * combobox input; the \"active\" option is a virtual highlight reported via\n * `setActiveOptionKey` (surfaced as the input's aria-activedescendant) and\n * NEVER takes real focus. Arrows/Home/End/PageUp/PageDown move the highlight,\n * printable characters type-ahead, and Enter selects the active option.\n *\n * 3. `'search'` — EDITABLE COMBOBOX. Like `'activedescendant'`, but the input is\n * a free-text field: printable keys, Left/Right and Home/End are left to the\n * input for text editing (no type-ahead). Arrows/PageUp/PageDown move the\n * highlight and Enter selects.\n *\n * Navigation wraps around at the ends. Options are discovered from the DOM\n * (`[data-menu-option-key]`) so it always matches what is rendered after search\n * filtering / grouping.\n *\n * Tab / Shift+Tab keep focus moving through the menu's own focus zones in DOM\n * order — header → search → options → footer — and only close the menu (via\n * `onTabOut`) when focus would leave the menu entirely (Tab past the last\n * focusable, Shift+Tab before the first). This keeps focus from silently\n * dropping to <body> when the popup renders in a portal with nothing after it.\n */\nexport type MenuNavigationMode = 'roving' | 'activedescendant' | 'search';\n/** One navigable option, for menus whose option DOM is windowed. */\nexport interface MenuNavigationOption {\n key: string;\n label: string;\n}\ninterface UseMenuKeyboardNavigationArgs {\n /** Ref to the element that contains the option buttons (the Menu listbox). */\n containerRef: React.RefObject<HTMLElement | null>;\n /** Disable all handling (e.g. when the menu is disabled or closed). */\n enabled?: boolean;\n /** Interaction model — see the file header. Defaults to `'roving'`. */\n mode?: MenuNavigationMode;\n /** Key of the option currently highlighted (needed for the non-roving modes). */\n activeOptionKey?: string | null;\n /** Select the option with the given key (used for Enter in the combobox modes). */\n onSelectKey?: (key: string) => void;\n /** Report the option key that owns the roving tab stop / active descendant. */\n setActiveOptionKey: (key: string | null) => void;\n /** Close the menu when Tab / Shift+Tab would move focus out of it. */\n onTabOut?: () => void;\n /**\n * Combobox menus only: return focus to the trigger input (keeping the menu\n * open) when Tab / Shift+Tab moves off a header/footer control back toward the\n * input — which sits OUTSIDE the menu DOM, so the input is the logical centre\n * of the tab order (header ← input → footer), not an edge. When omitted (the\n * roving menus) the input lives inside the menu, so the simple boundary close\n * applies instead.\n */\n onReturnFocusToInput?: () => void;\n /**\n * Enabled options in render order. Supply this when the option DOM is WINDOWED\n * (react-window virtualization): only a slice of the rows exists at any moment,\n * so DOM discovery would confine Home/End, PageUp/PageDown, wrap-around and\n * type-ahead to that slice — e.g. End on a 100-page Pagination would stop at\n * page 10. When supplied, all index maths resolve against this list and the\n * consumer is responsible for scrolling the active option into view (Menu does\n * this from `activeOptionKey`). Omit it for plain menus: the DOM is then the\n * source of truth, which keeps grouped/compose layouts and `resolveLabel`\n * type-ahead working off the text actually rendered.\n */\n options?: MenuNavigationOption[];\n}\n/**\n * Focusable controls inside the menu's sticky header / footer region, in DOM\n * order. Used by combobox triggers (Dropdown / Typeahead) so Tab / Shift+Tab can\n * move focus into the footer / header instead of closing the menu when one\n * holds focusable controls (e.g. Cancel / Save buttons).\n */\nexport declare const getMenuRegionFocusables: (menu: HTMLElement | null, region: \"header\" | \"footer\") => HTMLElement[];\n/**\n * Move focus into the menu's footer (Tab) or header (Shift+Tab) edge if that\n * region has focusable controls. Returns true when it moved focus, so a combobox\n * trigger can close the menu only when there was no chrome to land on.\n */\nexport declare const moveFocusToMenuChrome: (menu: HTMLElement | null, shiftKey: boolean) => boolean;\nexport declare const useMenuKeyboardNavigation: ({ containerRef, enabled, mode, activeOptionKey, onSelectKey, setActiveOptionKey, onTabOut, onReturnFocusToInput, options, }: UseMenuKeyboardNavigationArgs) => {\n onKeyDown: (event: React.KeyboardEvent<HTMLElement>) => void;\n};\nexport {};\n//# sourceMappingURL=useMenuKeyboardNavigation.d.ts.map"
|
|
1650
|
+
},
|
|
1651
|
+
"useMenuTrigger.d.ts": {
|
|
1652
|
+
"path": "useMenuTrigger.d.ts",
|
|
1653
|
+
"content": "import * as React from 'react';\nimport type { MenuOption, MenuProps } from '../../components/Menu/Menu';\n/**\n * Wires a trigger to the Menu it opens.\n *\n * Three things have to live on the trigger rather than in Menu, because they\n * exist while the menu does NOT: `aria-haspopup`, `aria-expanded=\"false\"`, and\n * the ArrowDown/ArrowUp that opens the popup in the first place. That is the\n * floor — no Menu prop can supply them — so these hooks hand them over as props\n * to spread instead, and own everything else: the ids, the tie between the\n * trigger's name and the listbox, and the ref Menu focuses into on open.\n *\n * `useMenuTrigger` is for triggers whose popup options own focus — a button, a\n * tab overflow chevron, an inline label — so arrows move real DOM focus.\n * `useComboboxMenuTrigger` is for triggers that are text inputs, where focus\n * stays put and the highlight is virtual (`aria-activedescendant`).\n *\n * `getTriggerProps` is a getter rather than a static object because open state\n * belongs to the consumer's `ClickAway`, which exposes it through a render\n * callback — where a hook cannot be called.\n *\n * const menuTrigger = useMenuTrigger({id: props.id, ariaLabel: menu?.ariaLabel});\n * <ClickAway restoreFocusOnClose>\n * {({isOpen, onOpen, clickAway}) => (\n * <>\n * <Button {...props} {...menuTrigger.getTriggerProps({isOpen, onOpen})} />\n * {isOpen && (\n * <Menu\n * {...menu}\n * {...menuTrigger.menuProps}\n * enableA11y\n * onTabOut={clickAway}\n * />\n * )}\n * </>\n * )}\n * </ClickAway>\n *\n * `restoreFocusOnClose` is what returns focus to the trigger when the menu\n * closes. Menu moves focus INTO the popup on open, so without it Escape, a\n * selection or Tab leaves focus on `document.body` — the components in this\n * package pass it alongside their own `enableA11y`.\n */\nexport interface UseMenuTriggerArgs {\n /**\n * Produce the wiring at all. False returns empty props and an inert handler,\n * so a consumer can call the hook unconditionally (hooks cannot be\n * conditional) while its own accessibility opt-in is off.\n */\n enabled?: boolean;\n /**\n * The trigger's own `id`, when the consumer supplied one. It is kept — the\n * hook only mints an id when there is none, since the listbox is named after\n * the trigger through it.\n */\n id?: string;\n /** The listbox id, when the consumer set `menu.menuId`. Minted otherwise. */\n menuId?: string;\n /** Literal name for the listbox. Wins over naming it after the trigger. */\n ariaLabel?: string;\n /** Id of the element naming the listbox. Defaults to the trigger itself. */\n ariaLabelledBy?: string;\n}\n/**\n * Generic in the trigger element so the returned handler matches whatever it is\n * spread onto — inferred from the trigger's own `onKeyDown` where it has one.\n */\nexport interface GetTriggerPropsArgs<T extends HTMLElement = HTMLElement> {\n /** The consumer's popup state — what ClickAway reports. */\n isOpen: boolean;\n onOpen: () => void;\n /** The trigger's own key handler, if it has one. Runs first. */\n onKeyDown?: React.KeyboardEventHandler<T>;\n}\nexport interface MenuTriggerProps<T extends HTMLElement = HTMLElement> {\n id: string;\n 'aria-haspopup': 'listbox';\n 'aria-expanded': boolean;\n 'aria-controls': string | undefined;\n onKeyDown: React.KeyboardEventHandler<T>;\n}\nexport interface MenuTriggerMenuProps {\n menuId: string;\n ariaLabelledBy?: string;\n ariaLabel?: string;\n initialFocusRef: React.RefObject<HTMLElement | null>;\n}\nexport declare const useMenuTrigger: ({ enabled, id, menuId: consumerMenuId, ariaLabel, ariaLabelledBy, }?: UseMenuTriggerArgs) => {\n getTriggerProps: <T extends HTMLElement = HTMLElement>(args: GetTriggerPropsArgs<T>) => MenuTriggerProps<T>;\n menuProps: MenuTriggerMenuProps;\n};\n/**\n * Which of ARIA's two combobox patterns the trigger implements.\n *\n * - `select` — a read-only field standing in for a `<select>`. An option is\n * highlighted as soon as the list opens, Space selects it (the field has no\n * typing role), and Enter or Space opens the list in the first place.\n * - `autocomplete` — an editable field that filters the list as you type.\n * Nothing is highlighted until the user arrows to it (APG calls this manual\n * selection: auto-highlighting would let Enter select something they never\n * chose), Space types a space, and only the arrow keys open the list.\n */\nexport type ComboboxPattern = 'select' | 'autocomplete';\nexport interface UseComboboxMenuTriggerArgs {\n /**\n * Produce the wiring at all. False returns empty props and an inert handler,\n * so a consumer can call the hook unconditionally while its own accessibility\n * opt-in is off.\n */\n enabled?: boolean;\n /** Which pattern this trigger implements. Defaults to `select`. */\n pattern?: ComboboxPattern;\n /**\n * The menu this trigger opens. Read for the option source (unless `options`\n * is given), the selection, `virtualization`, `menuDisabled`, `resolveLabel`,\n * `allowSearch` and the consumer's own `menuId` / `ariaLabel` /\n * `ariaLabelledBy`, all of which win over what the hook would derive.\n */\n menu?: MenuProps;\n /**\n * Option source, when it is not `menu` — a consumer that owns filtering and\n * hands the already-filtered options to Menu supplies them here instead. Every\n * derivation (navigation list, highlight seed, selection lookup) uses it.\n */\n options?: Array<MenuOption>;\n /**\n * Refuse to open, e.g. until enough has been typed. Consulted only for the\n * keys that would otherwise open the list.\n */\n canOpen?: () => boolean;\n /** Ref on the rendered Menu, for reaching its header/footer controls. */\n menuRef: React.RefObject<HTMLElement | null>;\n /**\n * The consumer's popup state — what ClickAway reports. Drives which keys the\n * trigger handles (open-state navigation vs. closed-state opening) and the\n * highlight's lifecycle. If the listbox can be WITHHELD while the popup is\n * open (still loading, no matches), keep passing the open state here and hand\n * `isExpanded` to `getTriggerProps` for the ARIA — routing keys off the\n * rendered state instead would make an arrow key call `onOpen` on an\n * already-open popup, and `onOpen` toggles.\n */\n isOpen: boolean;\n /** Apply a selection. Called with the option the keyboard acted on. */\n onSelectOption: (option: MenuOption) => void;\n /** Close the popup — used when Tab leaves a menu with no chrome to land on. */\n onClose: () => void;\n /** Put focus back on the trigger input, keeping the menu open. */\n focusTrigger: () => void;\n /** What already labels the trigger, used to name the listbox after it. */\n triggerAriaLabelledBy?: string;\n triggerAriaLabel?: string;\n /**\n * Literal name for the listbox when nothing else supplies one. Defaults to\n * `'Options'`; a suggestion list wants something truer to what it holds.\n */\n defaultAriaLabel?: string;\n}\nexport interface GetComboboxTriggerPropsArgs<T extends HTMLElement = HTMLElement> extends GetTriggerPropsArgs<T> {\n /**\n * Whether the listbox is actually rendered right now, when that can differ\n * from `isOpen` (a popup withheld while loading or with no matches). Drives\n * `aria-expanded`, `aria-controls` and `aria-activedescendant`, so the trigger\n * never claims an expanded listbox that is not in the DOM. Defaults to\n * `isOpen`.\n */\n isExpanded?: boolean;\n}\nexport interface ComboboxTriggerProps<T extends HTMLElement = HTMLElement> {\n role: 'combobox';\n 'aria-haspopup': 'listbox';\n 'aria-expanded': boolean;\n 'aria-controls': string | undefined;\n 'aria-autocomplete': 'none' | 'list';\n 'aria-activedescendant': string | undefined;\n onKeyDown: React.KeyboardEventHandler<T>;\n}\nexport interface ComboboxMenuProps {\n menuId: string;\n ariaLabelledBy?: string;\n ariaLabel?: string;\n externalKeyboardNav: boolean;\n activeOptionKey?: string | null;\n onActiveOptionKeyChange?: (key: string | null) => void;\n onReturnFocusToInput?: () => void;\n initialFocusRef?: React.RefObject<HTMLElement | null>;\n}\n/**\n * Wires a COMBOBOX trigger — a text input — to the Menu it opens. Focus stays\n * on the input and the highlight is virtual, reported through\n * `aria-activedescendant`, because the input has to keep receiving the keys it\n * filters with.\n *\n * Driving the highlight from outside Menu means owning the active option as\n * state, and owning it means deriving the same option list, initial highlight\n * and navigation list that Menu derives internally. Both combobox consumers had\n * grown their own copies of all three; they come from Menu itself now.\n *\n * A menu that carries its own filter field (`menu.allowSearch`) inverts the\n * model: navigation lives inside Menu, so the trigger stops driving it and\n * hands over an `initialFocusRef` instead. Both shapes come out of this hook.\n */\nexport declare const useComboboxMenuTrigger: ({ enabled, pattern, menu, options, canOpen, menuRef, isOpen, onSelectOption, onClose, focusTrigger, triggerAriaLabelledBy, triggerAriaLabel, defaultAriaLabel, }: UseComboboxMenuTriggerArgs) => {\n getTriggerProps: <T extends HTMLElement = HTMLElement>(args: GetComboboxTriggerPropsArgs<T>) => ComboboxTriggerProps<T>;\n menuProps: ComboboxMenuProps;\n activeOptionKey: string | null;\n};\n//# sourceMappingURL=useMenuTrigger.d.ts.map"
|
|
1638
1654
|
}
|
|
1639
1655
|
}
|
|
1640
1656
|
},
|
|
@@ -1643,8 +1659,12 @@
|
|
|
1643
1659
|
"Menu.stories.module.css",
|
|
1644
1660
|
"Menu.stories.tsx",
|
|
1645
1661
|
"Menu.tsx",
|
|
1662
|
+
"MenuFocusScope.tsx",
|
|
1646
1663
|
"MenuOptionButton.tsx",
|
|
1647
|
-
"index.ts"
|
|
1664
|
+
"index.ts",
|
|
1665
|
+
"menuOptionId.ts",
|
|
1666
|
+
"useMenuKeyboardNavigation.ts",
|
|
1667
|
+
"useMenuTrigger.ts"
|
|
1648
1668
|
]
|
|
1649
1669
|
},
|
|
1650
1670
|
"Modal": {
|
|
@@ -1712,11 +1732,11 @@
|
|
|
1712
1732
|
"files": {
|
|
1713
1733
|
"main": {
|
|
1714
1734
|
"path": "OptionButton.d.ts",
|
|
1715
|
-
"content": "import * as React from 'react';\nimport type { Flow } from 'flow-to-typescript-codemod';\nimport type { ClickAwayRefType } from '../../utils/click-away';\nimport type { ButtonProps } from '../../components/Button';\nimport type { AnchorType } from '../../components/ButtonDropdown';\nimport type { MenuOption, MenuProps } from '../../components/Menu';\nimport type { BaseTooltipProps } from '../../components/Tooltip';\ntype ClassNames = Readonly<{\n wrapper?: string;\n baseButtonWrapper?: string;\n baseButtonIcon?: string;\n optionsButtonWrapper?: string;\n optionsButtonIcon?: string;\n dropdownContainer?: string;\n}>;\nexport interface OptionButtonTooltipProps {\n baseButton?: BaseTooltipProps;\n optionsButton?: BaseTooltipProps;\n}\nexport interface OptionButtonProps extends Omit<ButtonProps, 'menu' | 'anchorPosition' | 'onOptionSelect' | 'onButtonClick' | 'onMenuOpen' | 'onMenuClose' | 'tooltip' | 'classNames' | 'clickAwayRef'> {\n menu?: MenuProps;\n anchorPosition?: AnchorType;\n onOptionSelect?: (option: MenuOption, arg2?: React.SyntheticEvent<HTMLElement> | null | undefined) => unknown;\n onButtonClick?: React.MouseEventHandler<HTMLButtonElement>;\n onMenuOpen?: () => unknown;\n onMenuClose?: () => unknown;\n tooltip?: OptionButtonTooltipProps;\n classNames?: ClassNames;\n clickAwayRef?: React.RefObject<ClickAwayRefType | null>;\n testId?: string;\n}\nexport declare const OptionButton: Flow.AbstractComponent<OptionButtonProps, HTMLDivElement>;\nexport {};\n//# sourceMappingURL=OptionButton.d.ts.map"
|
|
1735
|
+
"content": "import * as React from 'react';\nimport type { Flow } from 'flow-to-typescript-codemod';\nimport type { ClickAwayRefType } from '../../utils/click-away';\nimport type { ButtonProps } from '../../components/Button';\nimport type { AnchorType } from '../../components/ButtonDropdown';\nimport type { MenuOption, MenuProps } from '../../components/Menu';\nimport type { BaseTooltipProps } from '../../components/Tooltip';\ntype ClassNames = Readonly<{\n wrapper?: string;\n baseButtonWrapper?: string;\n baseButtonIcon?: string;\n optionsButtonWrapper?: string;\n optionsButtonIcon?: string;\n dropdownContainer?: string;\n}>;\nexport interface OptionButtonTooltipProps {\n baseButton?: BaseTooltipProps;\n optionsButton?: BaseTooltipProps;\n}\nexport interface OptionButtonProps extends Omit<ButtonProps, 'menu' | 'anchorPosition' | 'onOptionSelect' | 'onButtonClick' | 'onMenuOpen' | 'onMenuClose' | 'tooltip' | 'classNames' | 'clickAwayRef'> {\n menu?: MenuProps;\n anchorPosition?: AnchorType;\n onOptionSelect?: (option: MenuOption, arg2?: React.SyntheticEvent<HTMLElement> | null | undefined) => unknown;\n onButtonClick?: React.MouseEventHandler<HTMLButtonElement>;\n onMenuOpen?: () => unknown;\n onMenuClose?: () => unknown;\n tooltip?: OptionButtonTooltipProps;\n classNames?: ClassNames;\n clickAwayRef?: React.RefObject<ClickAwayRefType | null>;\n /**\n * Opt in to the accessibility behavior. Forwarded to the `ButtonDropdown`\n * this renders, which owns the trigger and the menu.\n *\n * Off by default, and off is this component exactly as it behaved before any\n * of it existed.\n */\n enableA11y?: boolean;\n /**\n * Accessible name for the options button — the icon-only half of the split\n * button, which has no text of its own. It also names the listbox that button\n * opens, so without it the listbox ends up unnamed. Override it for another\n * language, as `TabList` does with `overflowTabLabel`. A consumer's own\n * `ariaLabel` still wins.\n */\n optionsLabel?: string;\n testId?: string;\n}\nexport declare const OptionButton: Flow.AbstractComponent<OptionButtonProps, HTMLDivElement>;\nexport {};\n//# sourceMappingURL=OptionButton.d.ts.map"
|
|
1716
1736
|
},
|
|
1717
1737
|
"story": {
|
|
1718
1738
|
"path": "OptionButton.stories.tsx",
|
|
1719
|
-
"content": "import * as React from 'react';\n\nimport type {ClickAwayRefType} from 'src/utils/click-away';\n\nimport {BUTTON_ACTION_TYPE} from 'src/components/Button';\nimport {ANCHOR_POSITION_TYPE} from 'src/components/ButtonDropdown';\nimport type {MenuOption, MenuProps} from 'src/components/Menu';\nimport {MenuFooter, MenuHeader} from 'src/components/Menu/Menu.stories';\nimport type {OptionButtonProps} from 'src/components/OptionButton/OptionButton';\nimport {OptionButton} from 'src/components/OptionButton/OptionButton';\n\nimport css from 'src/components/OptionButton/OptionButton.stories.module.css';\n\n\nconst anchorPositionOptions: Array<unknown> = [\n ...Object.values(ANCHOR_POSITION_TYPE),\n];\n\nconst actionTypeOptions: Array<unknown> = [\n ...Object.values(BUTTON_ACTION_TYPE),\n];\nconst textOptions: MenuOption[] = [\n {key: '1', label: 'Option one'},\n {key: '2', label: 'Option two'},\n {\n key: '3',\n label: 'Option three this is a very long option that would truncate',\n },\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 {key: '8', label: 'Option Eight'},\n {key: '9', label: 'Option Night'},\n];\n\nconst menuProps: MenuProps = {\n isFluid: false,\n menuDisabled: false,\n options: textOptions,\n};\n\nexport default {\n tags: ['autodocs'],\n title: 'Components/Option Button/Generic Option Button',\n component: OptionButton,\n argTypes: {\n onButtonClick: {\n description:\n '**onButtonClick** handler that is applied to the root `<button>` element',\n action: 'clicked',\n table: {\n type: {summary: '(SyntheticEvent<HTMLElement>) => mixed'},\n },\n },\n menu: {\n description:\n '**menu** contains a list of choices as options on a temporary surface. It appears on interacts with option button.',\n defaultValue: menuProps,\n control: {\n type: 'object',\n },\n table: {\n type: {summary: 'MenuProps'},\n },\n },\n iconLeftName: {\n description:\n 'Left icon to be applied to the button, it internally uses the `Icon` component to render icons',\n control: {\n type: 'text',\n },\n table: {\n type: {summary: 'string'},\n },\n },\n iconLeftType: {\n description:\n 'Type of Left Icon. Type represents 4 unique icon styles :- Solid, Regular, Duotone and Brands',\n options: ['regular', 'solid', 'duotone', 'brands'],\n control: {type: 'select'},\n table: {\n type: {summary: 'string'},\n defaultValue: {summary: 'regular'},\n },\n },\n type: {\n options: ['primary', 'secondary', 'tertiary', 'ghost', 'danger'],\n description: 'Type of Button. Supported types are listed on the dropdown',\n control: {\n type: 'select',\n },\n table: {\n type: {summary: 'enum'},\n defaultValue: {summary: 'primary'},\n },\n },\n actionType: {\n options: actionTypeOptions,\n description:\n 'Action type of Button. Supported types are listed on the dropdown',\n control: {\n type: 'select',\n },\n table: {\n type: {summary: 'enum'},\n defaultValue: {summary: 'submit'},\n },\n },\n size: {\n description:\n 'Option Button 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 disabled: {\n description: 'If **true**, the component is disabled',\n control: {\n type: 'boolean',\n },\n table: {\n type: {summary: 'boolean'},\n defaultValue: {summary: 'false'},\n },\n },\n isLoading: {\n description:\n 'If **true**, the component would show a circular loader and also preserve the current size of button so that there are no jumps',\n control: {\n type: 'boolean',\n },\n table: {\n type: {summary: 'boolean'},\n defaultValue: {summary: 'false'},\n },\n },\n isFluid: {\n description:\n 'If **true**, the component would take the container\\'s `width`',\n control: {\n type: 'boolean',\n },\n table: {\n type: {summary: 'boolean'},\n defaultValue: {summary: 'false'},\n },\n },\n children: {\n description: 'The content of the Button',\n table: {\n type: {summary: 'React.Component | string'},\n },\n },\n ariaLabel: {\n control: {\n type: 'text',\n },\n description:\n '**aria-label** should be used where Option Button do not have have discernible text. This would avoid accessibility violations in screen readers',\n table: {\n type: {summary: 'string'},\n },\n },\n onOptionSelect: {\n description:\n '**onOptionSelect** 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 onMenuOpen: {\n description:\n '**onMenuOpen** handler is triggered when menu is opened. This should be helpful in cases where you want to change Button icon / text on this trigger.',\n action: 'clicked',\n table: {\n type: {summary: '() => mixed'},\n },\n },\n onMenuClose: {\n description:\n '**onMenuClose** handler is triggered when menu is closed. This should be helpful in cases where you want to change Button icon / text on this trigger.',\n action: 'clicked',\n table: {\n type: {summary: '() => mixed'},\n },\n },\n anchorPosition: {\n options: anchorPositionOptions,\n description:\n 'Position of Menu. It can be any one value from the dropdown',\n control: {\n type: 'select',\n },\n table: {\n type: {summary: 'enum'},\n defaultValue: {summary: 'bottom-start'},\n },\n },\n classNames: {\n description: 'External classNames to be applied',\n control: {\n type: 'object',\n },\n table: {\n type: {\n summary:\n '{wrapper?: string, baseButtonWrapper?: string, baseButtonIcon?: string, optionsButtonWrapper?: string, optionsButtonIcon?: string, dropdownContainer?: string,}',\n },\n },\n },\n tooltip: {\n control: {\n type: 'object',\n },\n description:\n 'Field to set the **Tooltip** for Base Button and Options Button',\n table: {\n type: {summary: 'OptionButtonTooltipProps'},\n },\n },\n clickAwayRef: {\n type: {optional: true},\n description: 'ref that contains the functions to open or close the child',\n table: {\n type: {\n summary: 'ClickAwayRefType',\n },\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 OptionButton component',\n description: {\n component: `\n\\`\\`\\`js\nimport { OptionButton } from \"@spaced-out/ui-design-system/lib/components/OptionButton\";\n\\`\\`\\`\nOption Button allow users to take the main action, and also provides with multiple options related to the main action. OptionButton comes with multiple variants and has various states(hover, focus, etc). There are two parts of OptionButton, left button to perform main action, right button to select any further options related to main action.\n\n- You can always force Open the options instance from ref by using \\`clickAwayRef.current?.forceOpen\\`. This might be useful when want to force Open as per you needs.\n- You can always force Close the options instance from ref by using \\`clickAwayRef.current?.forceClose\\`. This might be useful when want to force Close as per you needs.\n\n\n**Note: ** Always use \\`ariaLabel\\` with Icon only option button to avoid accessibility violation\n `,\n },\n },\n storySource: {\n componentPath: '/src/components/OptionButton/OptionButton',\n },\n },\n};\n\nexport const DefaultStory = (args: OptionButtonProps) => (\n <div className={css.optionBtnWrapper}>\n <OptionButton {...args} />\n </div>\n);\n\nDefaultStory.args = {\n children: 'Send',\n type: 'primary',\n size: 'small',\n iconLeftName: 'paper-plane-top',\n iconRightName: 'chevron-down',\n ariaLabel: 'Icon Button',\n isFluid: false,\n actionType: 'submit',\n menu: menuProps,\n testId: 'option-button-default-story',\n};\n\nexport const WithTruncateOptionsTooltipStory = (args: OptionButtonProps) => (\n <div className={css.optionBtnWrapper}>\n <OptionButton {...args} />\n </div>\n);\n\nWithTruncateOptionsTooltipStory.args = {\n children: 'Send',\n type: 'primary',\n size: 'small',\n iconLeftName: 'paper-plane-top',\n iconRightName: 'chevron-down',\n ariaLabel: 'Icon Button',\n isFluid: false,\n actionType: 'submit',\n menu: {...menuProps, showLabelTooltip: {maxLines: 2}},\n testId: 'option-button-with-truncate-options-tooltip-story',\n};\n\nexport const WithTooltipStory = (args: OptionButtonProps) => (\n <div className={css.optionBtnWrapper}>\n <OptionButton {...args} />\n </div>\n);\n\nWithTooltipStory.args = {\n children: 'Send',\n type: 'primary',\n size: 'small',\n iconLeftName: 'paper-plane-top',\n iconRightName: 'chevron-down',\n ariaLabel: 'Icon Button',\n isFluid: false,\n actionType: 'submit',\n menu: menuProps,\n tooltip: {\n baseButton: {\n body: 'Send now',\n placement: 'top-start',\n },\n optionsButton: {\n body: 'Schedule for later',\n placement: 'top-end',\n },\n },\n testId: 'option-button-with-tooltip-story',\n};\n\nexport const WithHeaderAndFooterStory = (args: OptionButtonProps) => (\n <WithHeaderAndFooter {...args} />\n);\n\nexport const WithHeaderAndFooter = (args: OptionButtonProps) => {\n const clickAwayRef = React.useRef<ClickAwayRefType | null>(null);\n const [selectedKeys, setSelectedKeys] = React.useState<string[]>([]);\n\n const handleOptionSelect = ({key}: MenuOption) => {\n if (selectedKeys.includes(key)) {\n setSelectedKeys((currentKeys) =>\n currentKeys.filter((currKey) => currKey !== key),\n );\n } else {\n setSelectedKeys((currentKeys) => [...currentKeys, key]);\n }\n };\n\n const onSave = () => {\n if (clickAwayRef.current) {\n clickAwayRef.current?.forceClose();\n }\n };\n\n return (\n <div className={css.optionBtnWrapper}>\n <OptionButton\n {...args}\n menu={{\n ...args.menu,\n selectedKeys,\n header: <MenuHeader />,\n footer: <MenuFooter onSave={onSave} />,\n }}\n onOptionSelect={handleOptionSelect}\n clickAwayRef={clickAwayRef}\n />\n </div>\n );\n};\n\nconst menuPropsWithHeaderAndFooter: MenuProps = {\n options: textOptions,\n isFluid: false,\n menuDisabled: false,\n optionsVariant: 'checkbox',\n};\n\nWithHeaderAndFooterStory.args = {\n children: 'Send',\n type: 'primary',\n size: 'small',\n iconLeftName: 'paper-plane-top',\n iconRightName: 'chevron-down',\n ariaLabel: 'Icon Button',\n isFluid: false,\n actionType: 'submit',\n menu: menuPropsWithHeaderAndFooter,\n tooltip: {\n baseButton: {\n body: 'Send now',\n placement: 'top-start',\n },\n optionsButton: {\n body: 'Schedule for later',\n placement: 'top-end',\n },\n },\n testId: 'option-button-with-header-and-footer-story',\n};\nDefaultStory.storyName = 'Default';\nWithTruncateOptionsTooltipStory.storyName = 'With Truncate Options Tooltip';\nWithTooltipStory.storyName = 'With Tooltip';\nWithHeaderAndFooterStory.storyName = 'With Header And Footer';\n"
|
|
1739
|
+
"content": "import * as React from 'react';\n\nimport type {ClickAwayRefType} from 'src/utils/click-away';\n\nimport {BUTTON_ACTION_TYPE} from 'src/components/Button';\nimport {ANCHOR_POSITION_TYPE} from 'src/components/ButtonDropdown';\nimport type {MenuOption, MenuProps} from 'src/components/Menu';\nimport {MenuFooter, MenuHeader} from 'src/components/Menu/Menu.stories';\nimport type {OptionButtonProps} from 'src/components/OptionButton/OptionButton';\nimport {OptionButton} from 'src/components/OptionButton/OptionButton';\n\nimport css from 'src/components/OptionButton/OptionButton.stories.module.css';\n\n\nconst anchorPositionOptions: Array<unknown> = [\n ...Object.values(ANCHOR_POSITION_TYPE),\n];\n\nconst actionTypeOptions: Array<unknown> = [\n ...Object.values(BUTTON_ACTION_TYPE),\n];\nconst textOptions: MenuOption[] = [\n {key: '1', label: 'Option one'},\n {key: '2', label: 'Option two'},\n {\n key: '3',\n label: 'Option three this is a very long option that would truncate',\n },\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 {key: '8', label: 'Option Eight'},\n {key: '9', label: 'Option Night'},\n];\n\nconst menuProps: MenuProps = {\n isFluid: false,\n menuDisabled: false,\n options: textOptions,\n};\n\nexport default {\n tags: ['autodocs'],\n title: 'Components/Option Button/Generic Option Button',\n component: OptionButton,\n argTypes: {\n enableA11y: {\n description:\n '**enableA11y** opts into the accessibility behavior: `aria-haspopup` / `aria-expanded` / `aria-controls` on the trigger, the arrow key that opens the popup, and focus moving into the menu and back. Off by default, where `OptionButton` behaves exactly as it did before any of it existed. Off here too, as everywhere: turn the control on to see the accessible behavior.',\n control: {type: 'boolean'},\n table: {\n type: {summary: 'boolean'},\n defaultValue: {summary: 'false'},\n },\n },\n onButtonClick: {\n description:\n '**onButtonClick** handler that is applied to the root `<button>` element',\n action: 'clicked',\n table: {\n type: {summary: '(SyntheticEvent<HTMLElement>) => mixed'},\n },\n },\n menu: {\n description:\n '**menu** contains a list of choices as options on a temporary surface. It appears on interacts with option button.',\n defaultValue: menuProps,\n control: {\n type: 'object',\n },\n table: {\n type: {summary: 'MenuProps'},\n },\n },\n iconLeftName: {\n description:\n 'Left icon to be applied to the button, it internally uses the `Icon` component to render icons',\n control: {\n type: 'text',\n },\n table: {\n type: {summary: 'string'},\n },\n },\n iconLeftType: {\n description:\n 'Type of Left Icon. Type represents 4 unique icon styles :- Solid, Regular, Duotone and Brands',\n options: ['regular', 'solid', 'duotone', 'brands'],\n control: {type: 'select'},\n table: {\n type: {summary: 'string'},\n defaultValue: {summary: 'regular'},\n },\n },\n type: {\n options: ['primary', 'secondary', 'tertiary', 'ghost', 'danger'],\n description: 'Type of Button. Supported types are listed on the dropdown',\n control: {\n type: 'select',\n },\n table: {\n type: {summary: 'enum'},\n defaultValue: {summary: 'primary'},\n },\n },\n actionType: {\n options: actionTypeOptions,\n description:\n 'Action type of Button. Supported types are listed on the dropdown',\n control: {\n type: 'select',\n },\n table: {\n type: {summary: 'enum'},\n defaultValue: {summary: 'submit'},\n },\n },\n size: {\n description:\n 'Option Button 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 disabled: {\n description: 'If **true**, the component is disabled',\n control: {\n type: 'boolean',\n },\n table: {\n type: {summary: 'boolean'},\n defaultValue: {summary: 'false'},\n },\n },\n isLoading: {\n description:\n 'If **true**, the component would show a circular loader and also preserve the current size of button so that there are no jumps',\n control: {\n type: 'boolean',\n },\n table: {\n type: {summary: 'boolean'},\n defaultValue: {summary: 'false'},\n },\n },\n isFluid: {\n description:\n 'If **true**, the component would take the container\\'s `width`',\n control: {\n type: 'boolean',\n },\n table: {\n type: {summary: 'boolean'},\n defaultValue: {summary: 'false'},\n },\n },\n children: {\n description: 'The content of the Button',\n table: {\n type: {summary: 'React.Component | string'},\n },\n },\n ariaLabel: {\n control: {\n type: 'text',\n },\n description:\n '**aria-label** should be used where Option Button do not have have discernible text. This would avoid accessibility violations in screen readers',\n table: {\n type: {summary: 'string'},\n },\n },\n onOptionSelect: {\n description:\n '**onOptionSelect** 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 onMenuOpen: {\n description:\n '**onMenuOpen** handler is triggered when menu is opened. This should be helpful in cases where you want to change Button icon / text on this trigger.',\n action: 'clicked',\n table: {\n type: {summary: '() => mixed'},\n },\n },\n onMenuClose: {\n description:\n '**onMenuClose** handler is triggered when menu is closed. This should be helpful in cases where you want to change Button icon / text on this trigger.',\n action: 'clicked',\n table: {\n type: {summary: '() => mixed'},\n },\n },\n anchorPosition: {\n options: anchorPositionOptions,\n description:\n 'Position of Menu. It can be any one value from the dropdown',\n control: {\n type: 'select',\n },\n table: {\n type: {summary: 'enum'},\n defaultValue: {summary: 'bottom-start'},\n },\n },\n classNames: {\n description: 'External classNames to be applied',\n control: {\n type: 'object',\n },\n table: {\n type: {\n summary:\n '{wrapper?: string, baseButtonWrapper?: string, baseButtonIcon?: string, optionsButtonWrapper?: string, optionsButtonIcon?: string, dropdownContainer?: string,}',\n },\n },\n },\n tooltip: {\n control: {\n type: 'object',\n },\n description:\n 'Field to set the **Tooltip** for Base Button and Options Button',\n table: {\n type: {summary: 'OptionButtonTooltipProps'},\n },\n },\n clickAwayRef: {\n type: {optional: true},\n description: 'ref that contains the functions to open or close the child',\n table: {\n type: {\n summary: 'ClickAwayRefType',\n },\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 OptionButton component',\n description: {\n component: `\n\\`\\`\\`js\nimport { OptionButton } from \"@spaced-out/ui-design-system/lib/components/OptionButton\";\n\\`\\`\\`\nOption Button allow users to take the main action, and also provides with multiple options related to the main action. OptionButton comes with multiple variants and has various states(hover, focus, etc). There are two parts of OptionButton, left button to perform main action, right button to select any further options related to main action.\n\n- You can always force Open the options instance from ref by using \\`clickAwayRef.current?.forceOpen\\`. This might be useful when want to force Open as per you needs.\n- You can always force Close the options instance from ref by using \\`clickAwayRef.current?.forceClose\\`. This might be useful when want to force Close as per you needs.\n\n\n**Note: ** Always use \\`ariaLabel\\` with Icon only option button to avoid accessibility violation\n `,\n },\n },\n storySource: {\n componentPath: '/src/components/OptionButton/OptionButton',\n },\n },\n};\n\nexport const DefaultStory = (args: OptionButtonProps) => (\n <div className={css.optionBtnWrapper}>\n <OptionButton {...args} />\n </div>\n);\n\nDefaultStory.args = {\n children: 'Send',\n type: 'primary',\n size: 'small',\n iconLeftName: 'paper-plane-top',\n iconRightName: 'chevron-down',\n ariaLabel: 'Icon Button',\n isFluid: false,\n actionType: 'submit',\n menu: menuProps,\n testId: 'option-button-default-story',\n};\n\nexport const WithTruncateOptionsTooltipStory = (args: OptionButtonProps) => (\n <div className={css.optionBtnWrapper}>\n <OptionButton {...args} />\n </div>\n);\n\nWithTruncateOptionsTooltipStory.args = {\n children: 'Send',\n type: 'primary',\n size: 'small',\n iconLeftName: 'paper-plane-top',\n iconRightName: 'chevron-down',\n ariaLabel: 'Icon Button',\n isFluid: false,\n actionType: 'submit',\n menu: {...menuProps, showLabelTooltip: {maxLines: 2}},\n testId: 'option-button-with-truncate-options-tooltip-story',\n};\n\nexport const WithTooltipStory = (args: OptionButtonProps) => (\n <div className={css.optionBtnWrapper}>\n <OptionButton {...args} />\n </div>\n);\n\nWithTooltipStory.args = {\n children: 'Send',\n type: 'primary',\n size: 'small',\n iconLeftName: 'paper-plane-top',\n iconRightName: 'chevron-down',\n ariaLabel: 'Icon Button',\n isFluid: false,\n actionType: 'submit',\n menu: menuProps,\n tooltip: {\n baseButton: {\n body: 'Send now',\n placement: 'top-start',\n },\n optionsButton: {\n body: 'Schedule for later',\n placement: 'top-end',\n },\n },\n testId: 'option-button-with-tooltip-story',\n};\n\nexport const WithHeaderAndFooterStory = (args: OptionButtonProps) => (\n <WithHeaderAndFooter {...args} />\n);\n\nexport const WithHeaderAndFooter = (args: OptionButtonProps) => {\n const clickAwayRef = React.useRef<ClickAwayRefType | null>(null);\n const [selectedKeys, setSelectedKeys] = React.useState<string[]>([]);\n\n const handleOptionSelect = ({key}: MenuOption) => {\n if (selectedKeys.includes(key)) {\n setSelectedKeys((currentKeys) =>\n currentKeys.filter((currKey) => currKey !== key),\n );\n } else {\n setSelectedKeys((currentKeys) => [...currentKeys, key]);\n }\n };\n\n const onSave = () => {\n if (clickAwayRef.current) {\n clickAwayRef.current?.forceClose();\n }\n };\n\n return (\n <div className={css.optionBtnWrapper}>\n <OptionButton\n {...args}\n menu={{\n ...args.menu,\n selectedKeys,\n header: <MenuHeader />,\n footer: <MenuFooter onSave={onSave} />,\n }}\n onOptionSelect={handleOptionSelect}\n clickAwayRef={clickAwayRef}\n />\n </div>\n );\n};\n\nconst menuPropsWithHeaderAndFooter: MenuProps = {\n options: textOptions,\n isFluid: false,\n menuDisabled: false,\n optionsVariant: 'checkbox',\n};\n\nWithHeaderAndFooterStory.args = {\n children: 'Send',\n type: 'primary',\n size: 'small',\n iconLeftName: 'paper-plane-top',\n iconRightName: 'chevron-down',\n ariaLabel: 'Icon Button',\n isFluid: false,\n actionType: 'submit',\n menu: menuPropsWithHeaderAndFooter,\n tooltip: {\n baseButton: {\n body: 'Send now',\n placement: 'top-start',\n },\n optionsButton: {\n body: 'Schedule for later',\n placement: 'top-end',\n },\n },\n testId: 'option-button-with-header-and-footer-story',\n};\nDefaultStory.storyName = 'Default';\nWithTruncateOptionsTooltipStory.storyName = 'With Truncate Options Tooltip';\nWithTooltipStory.storyName = 'With Tooltip';\nWithHeaderAndFooterStory.storyName = 'With Header And Footer';\n"
|
|
1720
1740
|
},
|
|
1721
1741
|
"css": {
|
|
1722
1742
|
"path": "OptionButton.module.css",
|
|
@@ -2622,7 +2642,7 @@
|
|
|
2622
2642
|
"additional": {
|
|
2623
2643
|
"Tab/Tab.d.ts": {
|
|
2624
2644
|
"path": "Tab/Tab.d.ts",
|
|
2625
|
-
"content": "import * as React from 'react';\nimport type { Flow } from 'flow-to-typescript-codemod';\nimport type { IconType } from '../../../components/Icon';\nimport type { ElevationType } from '../../../components/Tooltip';\ntype ClassNames = Readonly<{\n wrapper?: string;\n iconTextWrap?: string;\n}>;\nexport declare const TAB_SIZE: Readonly<{\n small: \"small\";\n medium: \"medium\";\n}>;\nexport declare const tabSizeOptions: Array<unknown>;\nexport type TabSize = keyof typeof TAB_SIZE;\nexport interface TabProps {\n classNames?: ClassNames;\n onSelect?: (arg1: {\n tabId?: string;\n label?: string;\n }) => unknown;\n size?: TabSize;\n selectedTab?: {\n tabId?: string;\n label?: string;\n };\n disabled?: boolean;\n tabId?: string;\n label?: string;\n iconName?: string;\n iconType?: IconType;\n maxWidth?: string;\n elevation?: ElevationType;\n onClick?: (e: React.SyntheticEvent<HTMLElement>) => unknown;\n testId?: string;\n}\nexport declare const Tab: Flow.AbstractComponent<TabProps, HTMLButtonElement>;\nexport {};\n//# sourceMappingURL=Tab.d.ts.map"
|
|
2645
|
+
"content": "import * as React from 'react';\nimport type { Flow } from 'flow-to-typescript-codemod';\nimport type { IconType } from '../../../components/Icon';\nimport type { ElevationType } from '../../../components/Tooltip';\ntype ClassNames = Readonly<{\n wrapper?: string;\n iconTextWrap?: string;\n}>;\nexport declare const TAB_SIZE: Readonly<{\n small: \"small\";\n medium: \"medium\";\n}>;\nexport declare const tabSizeOptions: Array<unknown>;\nexport type TabSize = keyof typeof TAB_SIZE;\n/**\n * Attributes forwarded straight to the underlying `<button>`. Declared rather\n * than relied on: `Tab` spreads its rest props, so these reached the DOM\n * whether or not the type mentioned them — which meant a future refactor could\n * drop a popup trigger's ARIA with no type error anywhere.\n */\ntype ForwardedButtonProps = Pick<React.ButtonHTMLAttributes<HTMLButtonElement>, 'id' | 'aria-label' | 'aria-haspopup' | 'aria-expanded' | 'aria-controls' | 'onKeyDown'>;\nexport interface TabProps extends ForwardedButtonProps {\n classNames?: ClassNames;\n onSelect?: (arg1: {\n tabId?: string;\n label?: string;\n }) => unknown;\n size?: TabSize;\n selectedTab?: {\n tabId?: string;\n label?: string;\n };\n disabled?: boolean;\n tabId?: string;\n label?: string;\n iconName?: string;\n iconType?: IconType;\n maxWidth?: string;\n elevation?: ElevationType;\n onClick?: (e: React.SyntheticEvent<HTMLElement>) => unknown;\n testId?: string;\n}\nexport declare const Tab: Flow.AbstractComponent<TabProps, HTMLButtonElement>;\nexport {};\n//# sourceMappingURL=Tab.d.ts.map"
|
|
2626
2646
|
},
|
|
2627
2647
|
"Tab/index.d.ts": {
|
|
2628
2648
|
"path": "Tab/index.d.ts",
|
|
@@ -2630,11 +2650,11 @@
|
|
|
2630
2650
|
},
|
|
2631
2651
|
"TabList/TabDropdown.d.ts": {
|
|
2632
2652
|
"path": "TabList/TabDropdown.d.ts",
|
|
2633
|
-
"content": "import * as React from 'react';\nimport type { AnchorType } from '../../../components/ButtonDropdown';\nimport type { MenuOption, MenuProps } from '../../../components/Menu';\nimport type { TabProps } from '../../../components/Tabs/Tab';\nimport type { ElevationType } from '../../../components/Tooltip';\ntype ClassNames = Readonly<{\n wrapper?: string;\n}>;\nexport interface TabDropdownProps {\n anchorPosition?: AnchorType;\n size?: 'medium' | 'small';\n props?: {\n tab: TabProps;\n menu: MenuProps;\n };\n onOptionSelect?: (option: MenuOption, arg2?: React.SyntheticEvent<HTMLElement> | null | undefined) => unknown;\n classNames?: ClassNames;\n selected?: boolean;\n disabled?: boolean;\n elevation?: ElevationType;\n testId?: string;\n}\nexport declare const TabDropdown: ({ anchorPosition, size, onOptionSelect, props, elevation, classNames, testId, }: TabDropdownProps) => React.JSX.Element;\nexport {};\n//# sourceMappingURL=TabDropdown.d.ts.map"
|
|
2653
|
+
"content": "import * as React from 'react';\nimport type { AnchorType } from '../../../components/ButtonDropdown';\nimport type { MenuOption, MenuProps } from '../../../components/Menu';\nimport type { TabProps } from '../../../components/Tabs/Tab';\nimport type { ElevationType } from '../../../components/Tooltip';\ntype ClassNames = Readonly<{\n wrapper?: string;\n}>;\nexport interface TabDropdownProps {\n anchorPosition?: AnchorType;\n size?: 'medium' | 'small';\n props?: {\n tab: TabProps;\n menu: MenuProps;\n };\n onOptionSelect?: (option: MenuOption, arg2?: React.SyntheticEvent<HTMLElement> | null | undefined) => unknown;\n classNames?: ClassNames;\n selected?: boolean;\n disabled?: boolean;\n /**\n * Opt in to the accessibility behavior: `aria-haspopup` / `aria-expanded` /\n * `aria-controls` on the tab, the arrow key that opens the popup, and focus\n * moving into the menu and back. Forwarded to the `Menu` this opens.\n *\n * Off by default, and off is this component exactly as it behaved before any\n * of it existed.\n */\n enableA11y?: boolean;\n elevation?: ElevationType;\n testId?: string;\n}\nexport declare const TabDropdown: ({ anchorPosition, size, onOptionSelect, props, elevation, classNames, enableA11y, testId, }: TabDropdownProps) => React.JSX.Element;\nexport {};\n//# sourceMappingURL=TabDropdown.d.ts.map"
|
|
2634
2654
|
},
|
|
2635
2655
|
"TabList/TabList.d.ts": {
|
|
2636
2656
|
"path": "TabList/TabList.d.ts",
|
|
2637
|
-
"content": "import * as React from 'react';\nimport type { Flow } from 'flow-to-typescript-codemod';\nimport type { ElevationType } from '../../../components/Tooltip';\ntype ClassNames = Readonly<{\n wrapper?: string;\n}>;\nexport interface TabListProps {\n classNames?: ClassNames;\n onSelect?: (arg1: {\n tabId: string;\n label?: string;\n }) => unknown;\n children?: React.ReactNode;\n size?: 'medium' | 'small';\n selectedTab?: {\n tabId: string;\n label?: string;\n };\n menuWidth?: string;\n elevation?: ElevationType;\n maxTabWidth?: string;\n testId?: string;\n}\nexport declare const TabList: Flow.AbstractComponent<TabListProps, HTMLDivElement>;\nexport {};\n//# sourceMappingURL=TabList.d.ts.map"
|
|
2657
|
+
"content": "import * as React from 'react';\nimport type { Flow } from 'flow-to-typescript-codemod';\nimport type { ElevationType } from '../../../components/Tooltip';\ntype ClassNames = Readonly<{\n wrapper?: string;\n}>;\nexport interface TabListProps {\n classNames?: ClassNames;\n onSelect?: (arg1: {\n tabId: string;\n label?: string;\n }) => unknown;\n children?: React.ReactNode;\n size?: 'medium' | 'small';\n selectedTab?: {\n tabId: string;\n label?: string;\n };\n menuWidth?: string;\n elevation?: ElevationType;\n maxTabWidth?: string;\n /**\n * Accessible name for the overflow tab, which is icon-only and therefore has\n * no text of its own — it also names the listbox that tab opens. Override it\n * for another language, as `ButtonTabs` does with `wrapTabTitle`.\n */\n overflowTabLabel?: string;\n /**\n * Opt in to the accessibility behavior for the overflow tab and the menu it\n * opens, and give that icon-only tab an accessible name.\n *\n * Off by default, and off is this component exactly as it behaved before any\n * of it existed.\n */\n enableA11y?: boolean;\n testId?: string;\n}\nexport declare const TabList: Flow.AbstractComponent<TabListProps, HTMLDivElement>;\nexport {};\n//# sourceMappingURL=TabList.d.ts.map"
|
|
2638
2658
|
},
|
|
2639
2659
|
"TabList/index.d.ts": {
|
|
2640
2660
|
"path": "TabList/index.d.ts",
|
|
@@ -3019,15 +3039,15 @@
|
|
|
3019
3039
|
"files": {
|
|
3020
3040
|
"main": {
|
|
3021
3041
|
"path": "Typeahead.d.ts",
|
|
3022
|
-
"content": "import * as React from 'react';\nimport type { Flow } from 'flow-to-typescript-codemod';\nimport type { ClickAwayRefType } from '../../utils/click-away';\nimport type { InputProps } from '../../components/Input';\nimport type { MenuOption, MenuProps } from '../../components/Menu';\nimport type { ElevationType } from '../../components/Tooltip';\ntype ClassNames = Readonly<{\n wrapper?: string;\n box?: string;\n}>;\ninterface BaseTypeaheadProps extends Omit<InputProps, 'classNames' | 'onSelect' | 'onSearch' | 'onMenuOpen' | 'onMenuClose' | 'typeaheadInputText' | 'menu' | 'onClear' | 'isLoading' | 'menuOpenOffset' | 'clickAwayRef' | 'elevation'> {\n classNames?: ClassNames;\n onSelect?: (option: MenuOption, arg2?: React.SyntheticEvent<HTMLElement> | null | undefined) => unknown;\n onSearch?: (evt: React.ChangeEvent<HTMLInputElement>) => unknown;\n onMenuOpen?: () => unknown;\n onMenuClose?: () => unknown;\n typeaheadInputText?: string;\n menu?: MenuProps;\n onClear?: () => void;\n isLoading?: boolean;\n menuOpenOffset?: number;\n clickAwayRef?: React.RefObject<ClickAwayRefType | null>;\n elevation?: ElevationType;\n testId?: string;\n rightSlot?: React.ReactNode;\n}\nexport interface TypeaheadProps extends Omit<BaseTypeaheadProps, 'allowInternalFilter'> {\n allowInternalFilter?: boolean;\n}\nexport declare const Typeahead: Flow.AbstractComponent<TypeaheadProps, HTMLInputElement>;\nexport {};\n//# sourceMappingURL=Typeahead.d.ts.map"
|
|
3042
|
+
"content": "import * as React from 'react';\nimport type { Flow } from 'flow-to-typescript-codemod';\nimport type { ClickAwayRefType } from '../../utils/click-away';\nimport type { InputProps } from '../../components/Input';\nimport type { MenuOption, MenuProps } from '../../components/Menu';\nimport type { ElevationType } from '../../components/Tooltip';\ntype ClassNames = Readonly<{\n wrapper?: string;\n box?: string;\n}>;\ninterface BaseTypeaheadProps extends Omit<InputProps, 'classNames' | 'onSelect' | 'onSearch' | 'onMenuOpen' | 'onMenuClose' | 'typeaheadInputText' | 'menu' | 'onClear' | 'isLoading' | 'menuOpenOffset' | 'clickAwayRef' | 'elevation'> {\n classNames?: ClassNames;\n onSelect?: (option: MenuOption, arg2?: React.SyntheticEvent<HTMLElement> | null | undefined) => unknown;\n onSearch?: (evt: React.ChangeEvent<HTMLInputElement>) => unknown;\n onMenuOpen?: () => unknown;\n onMenuClose?: () => unknown;\n typeaheadInputText?: string;\n menu?: MenuProps;\n onClear?: () => void;\n isLoading?: boolean;\n menuOpenOffset?: number;\n clickAwayRef?: React.RefObject<ClickAwayRefType | null>;\n elevation?: ElevationType;\n /**\n * Opt in to the accessibility behavior: the `combobox` role and its ARIA,\n * arrow-key navigation of the suggestions from the input, and focus handling\n * on close. Forwarded to the `Menu` this opens.\n *\n * Off by default, and off is this component exactly as it behaved before any\n * of it existed.\n */\n enableA11y?: boolean;\n testId?: string;\n rightSlot?: React.ReactNode;\n}\nexport interface TypeaheadProps extends Omit<BaseTypeaheadProps, 'allowInternalFilter'> {\n allowInternalFilter?: boolean;\n}\nexport declare const Typeahead: Flow.AbstractComponent<TypeaheadProps, HTMLInputElement>;\nexport {};\n//# sourceMappingURL=Typeahead.d.ts.map"
|
|
3023
3043
|
},
|
|
3024
3044
|
"story": {
|
|
3025
3045
|
"path": "Typeahead.stories.tsx",
|
|
3026
|
-
"content": "import * as React from 'react';\n\nimport {useDebounce} from 'src/hooks/useDebounce';\nimport type {ClickAwayRefType} from 'src/utils/click-away';\n\nimport type {MenuOption, MenuProps} from 'src/components/Menu';\nimport {MenuFooter, MenuHeader} from 'src/components/Menu/Menu.stories';\nimport {Toggle} from 'src/components/Toggle';\nimport {ELEVATION_TYPES, Tooltip} from 'src/components/Tooltip';\nimport type {TypeaheadProps} from 'src/components/Typeahead/Typeahead';\nimport {Typeahead} from 'src/components/Typeahead/Typeahead';\n\nimport css from 'src/components/Typeahead/Typeahead.stories.module.css';\n\n\nconst DEFAULT_LIMIT = 10;\nconst DEFAULT_OFFSET = 0;\nconst elevationOptions: Array<unknown> = [...Object.values(ELEVATION_TYPES)];\n\nconst options: MenuOption[] = [\n {key: '1', label: 'Eden Porter', iconLeft: 'user'},\n {key: '2', label: 'Verda Rodgers', iconLeft: 'user'},\n {\n key: '3',\n label:\n 'Option three this is a very long option that would truncate, and it continues even further to demonstrate how the text will behave when it exceeds the typical display width of the dropdown or typeahead component, ensuring that truncation, ellipsis, or wrapping is handled gracefully by the UI.',\n iconLeft: 'user',\n },\n {key: '4', label: 'Rolf Kim', iconLeft: 'user'},\n {key: '5', label: 'Ava Clark', iconLeft: 'user'},\n {key: '6', label: 'Geneva Rhodes', iconLeft: 'user'},\n {key: '7', label: 'Miranda Black', iconLeft: 'user'},\n {key: '8', label: 'Marsh Saunders', iconLeft: 'user'},\n {key: '9', label: 'Prunella Webb', iconLeft: 'user'},\n {key: '10', label: 'Andy Mullins', iconLeft: 'user'},\n {key: '11', label: 'Jade Watkins', iconLeft: 'user'},\n {key: '12', label: 'Wally Wolfe', iconLeft: 'user'},\n {key: '13', label: 'Misty Lipsey', iconLeft: 'user'},\n {key: '14', label: 'Dwight Gibbs', iconLeft: 'user'},\n {key: '15', label: 'Ronald Rhodes', iconLeft: 'user'},\n {key: '16', label: 'Sophia Evans', iconLeft: 'user'},\n {key: '17', label: 'Liam Harris', iconLeft: 'user'},\n {key: '18', label: 'Olivia Grant', iconLeft: 'user'},\n {key: '19', label: 'Ethan Daniels', iconLeft: 'user'},\n {key: '20', label: 'Chloe Thomas', iconLeft: 'user'},\n {key: '21', label: 'Lucas Martinez', iconLeft: 'user'},\n {key: '22', label: 'Benjamin Wright', iconLeft: 'user'},\n {key: '23', label: 'Amelia King', iconLeft: 'user'},\n {key: '24', label: 'James Lewis', iconLeft: 'user'},\n {key: '25', label: 'Pamela Fennimore', iconLeft: 'user'},\n];\n\nexport default {\n tags: ['autodocs'],\n title: 'Form Components/Typeahead/Generic Typeahead',\n component: Typeahead,\n argTypes: {\n size: {\n description:\n 'Typeahead supports only two size variants namely small and medium. `small` < `medium`',\n options: ['medium', 'small'],\n control: {\n type: 'select',\n },\n table: {\n type: {summary: 'enum'},\n defaultValue: {summary: 'medium'},\n },\n },\n menuSize: {\n description:\n 'Size of the menu.If not specified menu would take the size prop of typeahead. `small` < `medium`',\n options: ['medium', 'small'],\n control: {\n type: 'select',\n },\n table: {\n type: {summary: 'enum'},\n },\n },\n elevation: {\n description:\n 'This is passed to Typeahead. Elevation level for the dropdown menu controls the z-index stacking order. Default is set to \"modal\" elevation. Use higher elevation levels when rendering the dropdown above other elevated elements like Modal, Toast, etc.',\n control: {\n type: 'select',\n },\n options: elevationOptions,\n table: {\n type: {summary: 'enum'},\n defaultValue: {summary: 'modal'},\n },\n },\n name: {\n description: 'Name of the typeahead',\n control: {\n type: 'text',\n },\n table: {\n type: {summary: 'string'},\n },\n },\n options: {\n description:\n 'Typeahead options to be displayed. Each option should atleast have `{key: string, label: string}` Each option additionally supports `iconLeft` and `iconRight`',\n control: {\n type: 'object',\n },\n table: {\n type: {summary: 'MenuOption[]'},\n },\n },\n disabled: {\n description: 'if **true**, the component is disabled',\n control: {\n type: 'boolean',\n },\n table: {\n type: {summary: 'boolean'},\n defaultValue: {summary: 'false'},\n },\n },\n isLoading: {\n description:\n 'if **true**, the component would add a loader. The loader occupies space outside the container.',\n control: {\n type: 'boolean',\n },\n table: {\n type: {summary: 'boolean'},\n defaultValue: {summary: 'false'},\n },\n },\n menuOpenOffset: {\n description:\n 'A number indicating how many characters are needed to open menu',\n control: {\n type: 'number',\n },\n table: {\n type: {summary: 'number'},\n defaultValue: {summary: 1},\n },\n },\n placeholder: {\n description:\n 'Placeholder of typeahead. This appears inside the input box',\n control: {\n type: 'text',\n },\n table: {\n type: {summary: 'string'},\n },\n },\n locked: {\n description:\n 'if **true**, the component is locked, Locked state is the readonly state of the typeahead',\n control: {\n type: 'boolean',\n },\n table: {\n type: {summary: 'boolean'},\n defaultValue: {summary: 'false'},\n },\n },\n error: {\n description:\n 'if **true**, the component is moved to error state, Error state displays a red border surrounding input',\n control: {\n type: 'boolean',\n },\n table: {\n type: {summary: 'boolean'},\n defaultValue: {summary: 'false'},\n },\n },\n errorText: {\n description:\n 'Error text to be displayed on the bottom of the input. This uses `colorTextDanger`',\n control: {\n type: 'text',\n },\n table: {\n type: {summary: 'string'},\n },\n },\n label: {\n description:\n 'Primary label of the typeahead. This appears on top of typeahead input',\n control: {\n type: 'text',\n },\n table: {\n type: {summary: 'string'},\n },\n },\n helperText: {\n description:\n 'Secondary label of the typeahead. This appears on bottom of typeahead input, if the typeahead is in `error` state the priority is given to `errorText`, in such cases secondary label wont be displayed',\n control: {\n type: 'text',\n },\n table: {\n type: {summary: 'string'},\n },\n },\n rightSlot: {\n description: 'Right slot to be applied to the typeahead',\n control: {\n type: 'object',\n },\n table: {\n type: {summary: 'React.ReactNode'},\n },\n },\n classNames: {\n description: 'External classnames to be applied',\n control: {\n type: 'object',\n },\n table: {\n type: {summary: '{wrapper?: string, box?: string}'},\n },\n },\n onSelect: {\n description:\n '**onSelect** handler is triggered when there is a selection of an option from Menu',\n action: 'Option Selected',\n table: {\n type: {\n summary:\n '(option: MenuOption, ?SyntheticEvent<HTMLElement>) => mixed',\n },\n },\n },\n iconLeftName: {\n description:\n 'Left icon to be applied to the button, it internally uses the `Icon` component to render icons. By default Typeahead has magnifying-glass icon',\n control: {\n type: 'text',\n },\n table: {\n type: {summary: 'string'},\n defaultValue: {summary: 'magnifying-glass'},\n },\n },\n onClear: {\n description:\n '**onClear** handler is triggered when clear button is clicked.',\n action: 'cleared',\n table: {\n type: {summary: '() => void'},\n },\n },\n onSearch: {\n description:\n '**onSearch** handler is triggered when there change in the input value',\n action: 'Searched',\n table: {\n type: {\n summary: '(evt: SyntheticInputEvent<HTMLInputElement>) => mixed',\n },\n },\n },\n onFocus: {\n description:\n '**onFocus** handler is triggered when typeahead is focussed',\n action: 'focussed',\n table: {\n type: {summary: '(e: SyntheticInputEvent<HTMLInputElement>) => mixed'},\n },\n },\n onBlur: {\n description: '**onBlur** handler is triggered when typeahead is blurred',\n action: 'blurred',\n table: {\n type: {summary: '(e: SyntheticInputEvent<HTMLInputElement>) => mixed'},\n },\n },\n typeaheadInputText: {\n description:\n 'Text that appears inside the top input. Since Typeahead is a controlled component, this has to be set by caller',\n table: {\n type: {summary: 'string'},\n },\n },\n menu: {\n description: 'Menu props to use',\n control: {\n type: 'object',\n },\n table: {\n type: {summary: 'MenuProps'},\n },\n },\n onMenuOpen: {\n description:\n '**onMenuOpen** handler is triggered when menu is opened. This should be helpful in cases where you want to change Button icon / text on this trigger.',\n action: 'clicked',\n table: {\n type: {summary: '() => mixed'},\n },\n },\n onMenuClose: {\n description:\n '**onMenuClose** handler is triggered when menu is closed. This should be helpful in cases where you want to change Button icon / text on this trigger.',\n action: 'clicked',\n table: {\n type: {summary: '() => mixed'},\n },\n },\n clickAwayRef: {\n type: {optional: true},\n description: 'ref that contains the functions to open or close the child',\n table: {\n type: {\n summary: 'ClickAwayRefType',\n },\n },\n },\n allowInternalFilter: {\n description:\n 'Allows the **MenuOptions** to be filtered internally based on the **label** of each option. When this is set to false, the internal filtering mechanism that matches options against the search text is skipped. This means it assumes that the **MenuOptions** has already been filtered externally and will display the provided options as they are without any further processing.',\n control: {\n type: 'boolean',\n },\n table: {\n type: {summary: 'boolean'},\n defaultValue: {summary: true},\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 Typeahead component',\n description: {\n component: `\n\\`\\`\\`js\nimport { Typeahead } from \"@spaced-out/ui-design-system/lib/components/Typeahead\";\n\\`\\`\\`\n**Typeahead** is implemented as a combination of **<a href=\"../?path=/docs/form-components-input--docs\">Input</a>** and **<a href=\"../?path=/docs/components-menu--docs\">Menu</a>**\ncomponents. It internally uses **Floating UI** to\nposition itself in context of DOM. Supports a bunch of callbacks listed below. Typeahead automatically filters options when a\nsubstring match is found in option(s). This is done using \\`label\\` prop of option.\n\n- Supports passing a \\`clickAwayRef\\` prop to the component.\n- You can always force Open the dropdown from ref by using \\`clickAwayRef.current?.forceOpen()\\`. This might be useful when want to force Open as per you needs.\n- You can always force Close the dropdown from ref by using \\`clickAwayRef.current?.forceClose()\\`. This might be useful when want to force Close as per you needs.\n\n**Purpose:** The Typeahead component is designed to enhance performance and user experience by initially displaying a limited set of menu options (e.g., most popular or recently used items). Once the user begins typing into the input field, the component dynamically queries the API and displays all matching results based on the input text. This approach minimizes initial load time while ensuring that users can still access the full range of options through search-driven filtering.\n\nCurrently typeahead only supports simple menu options.\n\n\\`\\`\\`jsx\nexport const _BasicTypeahead = (args: TypeaheadProps): React.Node => {\n const clickAwayRef = React.useRef(null);\n const [inputText, setInputText] = React.useState('');\n const [isLoading, setIsLoading] = React.useState(false);\n const [menuOptions, setMenuOptions] = React.useState([]);\n\n const debouncedInputText = useDebounce(inputText, 500);\n\n const fetchData = async () => {\n setIsLoading(true);\n const newOptions = await getPaginatedData(\n debouncedInputText,\n DEFAULT_OFFSET,\n DEFAULT_LIMIT,\n );\n setMenuOptions(newOptions);\n setIsLoading(false);\n };\n\n React.useEffect(() => {\n fetchData();\n }, [debouncedInputText]);\n\n return (\n <div className={css.typeaheadWrapper}>\n <Typeahead\n {...args}\n menu={{\n options: menuOptions,\n virtualization: {enable: true},\n }}\n onClear={() => setInputText('')}\n onSearch={(e) => setInputText(e.target.value)}\n onSelect={({label}) => setInputText(label || '')}\n isLoading={isLoading}\n clickAwayRef={clickAwayRef}\n typeaheadInputText={inputText}\n allowInternalFilter={false}\n />\n </div>\n );\n}\n\\`\\`\\`\n `,\n },\n },\n storySource: {\n componentPath: '/src/components/Typeahead/Typeahead',\n },\n },\n};\n\nconst menuProps: MenuProps = {\n options,\n};\n\nexport const TypeaheadStory = (args: TypeaheadProps) => {\n const [inputText, setInputText] = React.useState<string | undefined>('');\n return (\n <div className={css.typeaheadWrapper}>\n <Typeahead\n {...args}\n typeaheadInputText={inputText}\n onSearch={(e) => setInputText(e.target.value)}\n onSelect={(option) => {\n setInputText(option.label);\n }}\n onClear={() => setInputText('')}\n />\n </div>\n );\n};\n\nTypeaheadStory.args = {\n label: 'Search a name',\n menu: menuProps,\n testId: 'typeahead-story',\n};\n\nexport const TypeaheadWithTooltipOptionsStory = (args: TypeaheadProps) => {\n const [inputText, setInputText] = React.useState<string | undefined>('');\n return (\n <div className={css.typeaheadWrapper}>\n <Typeahead\n {...args}\n typeaheadInputText={inputText}\n onSearch={(e) => setInputText(e.target.value)}\n onSelect={(option) => {\n setInputText(option.label);\n }}\n onClear={() => setInputText('')}\n />\n </div>\n );\n};\n\nTypeaheadWithTooltipOptionsStory.args = {\n label: 'Search a name',\n menu: {...menuProps, showLabelTooltip: {maxLines: 10}},\n testId: 'typeahead-with-tooltip-options-story',\n};\n\nexport const WithHeaderAndFooterStory = (args: TypeaheadProps) => (\n <WithHeaderAndFooter {...args} />\n);\n\nexport const WithHeaderAndFooter = (args: TypeaheadProps) => {\n const clickAwayRef = React.useRef<ClickAwayRefType | null>(null);\n const [inputText, setInputText] = React.useState<string | undefined>('');\n\n const onSave = () => {\n if (clickAwayRef.current) {\n clickAwayRef.current?.forceClose();\n }\n };\n\n return (\n <div className={css.typeaheadWrapper}>\n <Typeahead\n {...args}\n typeaheadInputText={inputText}\n onSearch={(e) => setInputText(e.target.value)}\n onSelect={(option) => {\n setInputText(option.label);\n }}\n menu={{\n options,\n header: <MenuHeader />,\n footer: <MenuFooter onSave={onSave} />,\n }}\n onClear={() => setInputText('')}\n clickAwayRef={clickAwayRef}\n />\n </div>\n );\n};\n\nWithHeaderAndFooterStory.args = {\n label: 'Search a name',\n testId: 'typeahead-with-header-and-footer-story',\n};\n\nconst getPaginatedData = ({\n limit,\n offset,\n searchText,\n}: {\n limit: number;\n offset: number;\n searchText: string;\n}) =>\n new Promise(\n (\n resolve: (result: Promise<Array<MenuOption>> | Array<MenuOption>) => void,\n ) =>\n setTimeout(() => {\n const filteredOptions = options.filter((option) =>\n option.label\n ? option.label.toLowerCase().includes(searchText.toLowerCase())\n : true,\n );\n\n resolve(filteredOptions.slice(offset, offset + limit));\n }, 1000),\n );\n\nexport const WithPaginationAndVirtualizationStory = (args: TypeaheadProps) => {\n const clickAwayRef = React.useRef(null);\n const [inputText, setInputText] = React.useState('');\n const [isLoading, setIsLoading] = React.useState(false);\n const [menuOptions, setMenuOptions] = React.useState<MenuOption[]>([]);\n\n const debouncedInputText = useDebounce(inputText, 500);\n\n const fetchData = async () => {\n setIsLoading(true);\n const newOptions = await getPaginatedData({\n limit: DEFAULT_LIMIT,\n offset: DEFAULT_OFFSET,\n searchText: debouncedInputText,\n });\n setMenuOptions(newOptions);\n setIsLoading(false);\n };\n\n React.useEffect(() => {\n fetchData();\n }, [debouncedInputText]);\n\n return (\n <div className={css.typeaheadWrapper}>\n <Typeahead\n {...args}\n menu={{\n options: menuOptions,\n virtualization: {enable: true},\n }}\n onClear={() => setInputText('')}\n onSearch={(e) => setInputText(e.target.value)}\n onSelect={({label}) => setInputText(label || '')}\n isLoading={isLoading}\n clickAwayRef={clickAwayRef}\n typeaheadInputText={inputText}\n allowInternalFilter={false}\n />\n </div>\n );\n};\n\nWithPaginationAndVirtualizationStory.args = {\n label: 'Search a name',\n menuOpenOffset: 0,\n testId: 'typeahead-with-pagination-and-virtualization-story',\n};\n\nexport const TypeaheadWithRightSlotStory = (args: TypeaheadProps) => {\n const [inputText, setInputText] = React.useState<string | undefined>('');\n const [checked, setChecked] = React.useState(false);\n return (\n <div className={css.typeaheadWrapper}>\n <Typeahead\n {...args}\n typeaheadInputText={inputText}\n onSearch={(e) => setInputText(e.target.value)}\n onSelect={(option) => {\n setInputText(option.label);\n }}\n onClear={() => setInputText('')}\n rightSlot={\n <Tooltip title={checked ? 'Disable AI Search' : 'Enable AI Search'}>\n <div className={css.toggleContainer}>\n <Toggle\n checked={checked}\n onChange={() => {\n setChecked(!checked);\n }}\n />\n </div>\n </Tooltip>\n }\n />\n </div>\n );\n};\n\nTypeaheadWithRightSlotStory.args = {\n label: 'Search a name',\n menu: menuProps,\n testId: 'typeahead-story',\n};\n\nTypeaheadStory.storyName = 'Typeahead';\nTypeaheadWithTooltipOptionsStory.storyName = 'Typeahead With Tooltip Options';\nWithHeaderAndFooterStory.storyName = 'With Header And Footer';\nWithPaginationAndVirtualizationStory.storyName =\n 'With Pagination And Virtualization';\nTypeaheadWithRightSlotStory.storyName = 'Typeahead With Right Slot';\n"
|
|
3046
|
+
"content": "import * as React from 'react';\n\nimport {useDebounce} from 'src/hooks/useDebounce';\nimport type {ClickAwayRefType} from 'src/utils/click-away';\n\nimport type {MenuOption, MenuProps} from 'src/components/Menu';\nimport {MenuFooter, MenuHeader} from 'src/components/Menu/Menu.stories';\nimport {Toggle} from 'src/components/Toggle';\nimport {ELEVATION_TYPES, Tooltip} from 'src/components/Tooltip';\nimport type {TypeaheadProps} from 'src/components/Typeahead/Typeahead';\nimport {Typeahead} from 'src/components/Typeahead/Typeahead';\n\nimport css from 'src/components/Typeahead/Typeahead.stories.module.css';\n\n\nconst DEFAULT_LIMIT = 10;\nconst DEFAULT_OFFSET = 0;\nconst elevationOptions: Array<unknown> = [...Object.values(ELEVATION_TYPES)];\n\nconst options: MenuOption[] = [\n {key: '1', label: 'Eden Porter', iconLeft: 'user'},\n {key: '2', label: 'Verda Rodgers', iconLeft: 'user'},\n {\n key: '3',\n label:\n 'Option three this is a very long option that would truncate, and it continues even further to demonstrate how the text will behave when it exceeds the typical display width of the dropdown or typeahead component, ensuring that truncation, ellipsis, or wrapping is handled gracefully by the UI.',\n iconLeft: 'user',\n },\n {key: '4', label: 'Rolf Kim', iconLeft: 'user'},\n {key: '5', label: 'Ava Clark', iconLeft: 'user'},\n {key: '6', label: 'Geneva Rhodes', iconLeft: 'user'},\n {key: '7', label: 'Miranda Black', iconLeft: 'user'},\n {key: '8', label: 'Marsh Saunders', iconLeft: 'user'},\n {key: '9', label: 'Prunella Webb', iconLeft: 'user'},\n {key: '10', label: 'Andy Mullins', iconLeft: 'user'},\n {key: '11', label: 'Jade Watkins', iconLeft: 'user'},\n {key: '12', label: 'Wally Wolfe', iconLeft: 'user'},\n {key: '13', label: 'Misty Lipsey', iconLeft: 'user'},\n {key: '14', label: 'Dwight Gibbs', iconLeft: 'user'},\n {key: '15', label: 'Ronald Rhodes', iconLeft: 'user'},\n {key: '16', label: 'Sophia Evans', iconLeft: 'user'},\n {key: '17', label: 'Liam Harris', iconLeft: 'user'},\n {key: '18', label: 'Olivia Grant', iconLeft: 'user'},\n {key: '19', label: 'Ethan Daniels', iconLeft: 'user'},\n {key: '20', label: 'Chloe Thomas', iconLeft: 'user'},\n {key: '21', label: 'Lucas Martinez', iconLeft: 'user'},\n {key: '22', label: 'Benjamin Wright', iconLeft: 'user'},\n {key: '23', label: 'Amelia King', iconLeft: 'user'},\n {key: '24', label: 'James Lewis', iconLeft: 'user'},\n {key: '25', label: 'Pamela Fennimore', iconLeft: 'user'},\n];\n\nexport default {\n tags: ['autodocs'],\n title: 'Form Components/Typeahead/Generic Typeahead',\n component: Typeahead,\n argTypes: {\n enableA11y: {\n description:\n '**enableA11y** opts into the accessibility behavior: the `combobox` role and its ARIA, arrow-key navigation of the suggestions from the input, and focus handling on close. Off by default, where the component behaves exactly as it did before any of it existed. Off here too, as everywhere: turn the control on to see the accessible behavior.',\n control: {type: 'boolean'},\n table: {\n type: {summary: 'boolean'},\n defaultValue: {summary: 'false'},\n },\n },\n size: {\n description:\n 'Typeahead supports only two size variants namely small and medium. `small` < `medium`',\n options: ['medium', 'small'],\n control: {\n type: 'select',\n },\n table: {\n type: {summary: 'enum'},\n defaultValue: {summary: 'medium'},\n },\n },\n menuSize: {\n description:\n 'Size of the menu.If not specified menu would take the size prop of typeahead. `small` < `medium`',\n options: ['medium', 'small'],\n control: {\n type: 'select',\n },\n table: {\n type: {summary: 'enum'},\n },\n },\n elevation: {\n description:\n 'This is passed to Typeahead. Elevation level for the dropdown menu controls the z-index stacking order. Default is set to \"modal\" elevation. Use higher elevation levels when rendering the dropdown above other elevated elements like Modal, Toast, etc.',\n control: {\n type: 'select',\n },\n options: elevationOptions,\n table: {\n type: {summary: 'enum'},\n defaultValue: {summary: 'modal'},\n },\n },\n name: {\n description: 'Name of the typeahead',\n control: {\n type: 'text',\n },\n table: {\n type: {summary: 'string'},\n },\n },\n options: {\n description:\n 'Typeahead options to be displayed. Each option should atleast have `{key: string, label: string}` Each option additionally supports `iconLeft` and `iconRight`',\n control: {\n type: 'object',\n },\n table: {\n type: {summary: 'MenuOption[]'},\n },\n },\n disabled: {\n description: 'if **true**, the component is disabled',\n control: {\n type: 'boolean',\n },\n table: {\n type: {summary: 'boolean'},\n defaultValue: {summary: 'false'},\n },\n },\n isLoading: {\n description:\n 'if **true**, the component would add a loader. The loader occupies space outside the container.',\n control: {\n type: 'boolean',\n },\n table: {\n type: {summary: 'boolean'},\n defaultValue: {summary: 'false'},\n },\n },\n menuOpenOffset: {\n description:\n 'A number indicating how many characters are needed to open menu',\n control: {\n type: 'number',\n },\n table: {\n type: {summary: 'number'},\n defaultValue: {summary: 1},\n },\n },\n placeholder: {\n description:\n 'Placeholder of typeahead. This appears inside the input box',\n control: {\n type: 'text',\n },\n table: {\n type: {summary: 'string'},\n },\n },\n locked: {\n description:\n 'if **true**, the component is locked, Locked state is the readonly state of the typeahead',\n control: {\n type: 'boolean',\n },\n table: {\n type: {summary: 'boolean'},\n defaultValue: {summary: 'false'},\n },\n },\n error: {\n description:\n 'if **true**, the component is moved to error state, Error state displays a red border surrounding input',\n control: {\n type: 'boolean',\n },\n table: {\n type: {summary: 'boolean'},\n defaultValue: {summary: 'false'},\n },\n },\n errorText: {\n description:\n 'Error text to be displayed on the bottom of the input. This uses `colorTextDanger`',\n control: {\n type: 'text',\n },\n table: {\n type: {summary: 'string'},\n },\n },\n label: {\n description:\n 'Primary label of the typeahead. This appears on top of typeahead input',\n control: {\n type: 'text',\n },\n table: {\n type: {summary: 'string'},\n },\n },\n helperText: {\n description:\n 'Secondary label of the typeahead. This appears on bottom of typeahead input, if the typeahead is in `error` state the priority is given to `errorText`, in such cases secondary label wont be displayed',\n control: {\n type: 'text',\n },\n table: {\n type: {summary: 'string'},\n },\n },\n rightSlot: {\n description: 'Right slot to be applied to the typeahead',\n control: {\n type: 'object',\n },\n table: {\n type: {summary: 'React.ReactNode'},\n },\n },\n classNames: {\n description: 'External classnames to be applied',\n control: {\n type: 'object',\n },\n table: {\n type: {summary: '{wrapper?: string, box?: string}'},\n },\n },\n onSelect: {\n description:\n '**onSelect** handler is triggered when there is a selection of an option from Menu',\n action: 'Option Selected',\n table: {\n type: {\n summary:\n '(option: MenuOption, ?SyntheticEvent<HTMLElement>) => mixed',\n },\n },\n },\n iconLeftName: {\n description:\n 'Left icon to be applied to the button, it internally uses the `Icon` component to render icons. By default Typeahead has magnifying-glass icon',\n control: {\n type: 'text',\n },\n table: {\n type: {summary: 'string'},\n defaultValue: {summary: 'magnifying-glass'},\n },\n },\n onClear: {\n description:\n '**onClear** handler is triggered when clear button is clicked.',\n action: 'cleared',\n table: {\n type: {summary: '() => void'},\n },\n },\n onSearch: {\n description:\n '**onSearch** handler is triggered when there change in the input value',\n action: 'Searched',\n table: {\n type: {\n summary: '(evt: SyntheticInputEvent<HTMLInputElement>) => mixed',\n },\n },\n },\n onFocus: {\n description:\n '**onFocus** handler is triggered when typeahead is focussed',\n action: 'focussed',\n table: {\n type: {summary: '(e: SyntheticInputEvent<HTMLInputElement>) => mixed'},\n },\n },\n onBlur: {\n description: '**onBlur** handler is triggered when typeahead is blurred',\n action: 'blurred',\n table: {\n type: {summary: '(e: SyntheticInputEvent<HTMLInputElement>) => mixed'},\n },\n },\n typeaheadInputText: {\n description:\n 'Text that appears inside the top input. Since Typeahead is a controlled component, this has to be set by caller',\n table: {\n type: {summary: 'string'},\n },\n },\n menu: {\n description: 'Menu props to use',\n control: {\n type: 'object',\n },\n table: {\n type: {summary: 'MenuProps'},\n },\n },\n onMenuOpen: {\n description:\n '**onMenuOpen** handler is triggered when menu is opened. This should be helpful in cases where you want to change Button icon / text on this trigger.',\n action: 'clicked',\n table: {\n type: {summary: '() => mixed'},\n },\n },\n onMenuClose: {\n description:\n '**onMenuClose** handler is triggered when menu is closed. This should be helpful in cases where you want to change Button icon / text on this trigger.',\n action: 'clicked',\n table: {\n type: {summary: '() => mixed'},\n },\n },\n clickAwayRef: {\n type: {optional: true},\n description: 'ref that contains the functions to open or close the child',\n table: {\n type: {\n summary: 'ClickAwayRefType',\n },\n },\n },\n allowInternalFilter: {\n description:\n 'Allows the **MenuOptions** to be filtered internally based on the **label** of each option. When this is set to false, the internal filtering mechanism that matches options against the search text is skipped. This means it assumes that the **MenuOptions** has already been filtered externally and will display the provided options as they are without any further processing.',\n control: {\n type: 'boolean',\n },\n table: {\n type: {summary: 'boolean'},\n defaultValue: {summary: true},\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 Typeahead component',\n description: {\n component: `\n\\`\\`\\`js\nimport { Typeahead } from \"@spaced-out/ui-design-system/lib/components/Typeahead\";\n\\`\\`\\`\n**Typeahead** is implemented as a combination of **<a href=\"../?path=/docs/form-components-input--docs\">Input</a>** and **<a href=\"../?path=/docs/components-menu--docs\">Menu</a>**\ncomponents. It internally uses **Floating UI** to\nposition itself in context of DOM. Supports a bunch of callbacks listed below. Typeahead automatically filters options when a\nsubstring match is found in option(s). This is done using \\`label\\` prop of option.\n\n- Supports passing a \\`clickAwayRef\\` prop to the component.\n- You can always force Open the dropdown from ref by using \\`clickAwayRef.current?.forceOpen()\\`. This might be useful when want to force Open as per you needs.\n- You can always force Close the dropdown from ref by using \\`clickAwayRef.current?.forceClose()\\`. This might be useful when want to force Close as per you needs.\n\n**Purpose:** The Typeahead component is designed to enhance performance and user experience by initially displaying a limited set of menu options (e.g., most popular or recently used items). Once the user begins typing into the input field, the component dynamically queries the API and displays all matching results based on the input text. This approach minimizes initial load time while ensuring that users can still access the full range of options through search-driven filtering.\n\nCurrently typeahead only supports simple menu options.\n\n\\`\\`\\`jsx\nexport const _BasicTypeahead = (args: TypeaheadProps): React.Node => {\n const clickAwayRef = React.useRef(null);\n const [inputText, setInputText] = React.useState('');\n const [isLoading, setIsLoading] = React.useState(false);\n const [menuOptions, setMenuOptions] = React.useState([]);\n\n const debouncedInputText = useDebounce(inputText, 500);\n\n const fetchData = async () => {\n setIsLoading(true);\n const newOptions = await getPaginatedData(\n debouncedInputText,\n DEFAULT_OFFSET,\n DEFAULT_LIMIT,\n );\n setMenuOptions(newOptions);\n setIsLoading(false);\n };\n\n React.useEffect(() => {\n fetchData();\n }, [debouncedInputText]);\n\n return (\n <div className={css.typeaheadWrapper}>\n <Typeahead\n {...args}\n menu={{\n options: menuOptions,\n virtualization: {enable: true},\n }}\n onClear={() => setInputText('')}\n onSearch={(e) => setInputText(e.target.value)}\n onSelect={({label}) => setInputText(label || '')}\n isLoading={isLoading}\n clickAwayRef={clickAwayRef}\n typeaheadInputText={inputText}\n allowInternalFilter={false}\n />\n </div>\n );\n}\n\\`\\`\\`\n `,\n },\n },\n storySource: {\n componentPath: '/src/components/Typeahead/Typeahead',\n },\n },\n};\n\nconst menuProps: MenuProps = {\n options,\n};\n\nexport const TypeaheadStory = (args: TypeaheadProps) => {\n const [inputText, setInputText] = React.useState<string | undefined>('');\n return (\n <div className={css.typeaheadWrapper}>\n <Typeahead\n {...args}\n typeaheadInputText={inputText}\n onSearch={(e) => setInputText(e.target.value)}\n onSelect={(option) => {\n setInputText(option.label);\n }}\n onClear={() => setInputText('')}\n />\n </div>\n );\n};\n\nTypeaheadStory.args = {\n label: 'Search a name',\n menu: menuProps,\n testId: 'typeahead-story',\n};\n\n/*\n * The same typeahead as `Typeahead`, with the accessibility opt-in on.\n *\n * With `enableA11y` the field is an `autocomplete` combobox: nothing is\n * highlighted until you arrow to it, since auto-highlighting would let Enter\n * select a suggestion you never chose. Typing keeps reaching the field, the\n * arrow keys move `aria-activedescendant`, and a polite live region announces\n * the result count and the loading state. The field only claims to be expanded\n * while a listbox is actually rendered.\n *\n * Every other story here leaves the prop off, which is the default and is this\n * component exactly as it behaved before any of this existed.\n */\nexport const AccessibleTypeaheadStory = (args: TypeaheadProps) => {\n const [inputText, setInputText] = React.useState<string | undefined>('');\n return (\n <div className={css.typeaheadWrapper}>\n <Typeahead\n {...args}\n typeaheadInputText={inputText}\n onSearch={(e) => setInputText(e.target.value)}\n onSelect={(option) => {\n setInputText(option.label);\n }}\n onClear={() => setInputText('')}\n />\n </div>\n );\n};\n\nAccessibleTypeaheadStory.args = {\n enableA11y: true,\n label: 'Search a name',\n menu: menuProps,\n testId: 'typeahead-accessible-story',\n};\n\nexport const TypeaheadWithTooltipOptionsStory = (args: TypeaheadProps) => {\n const [inputText, setInputText] = React.useState<string | undefined>('');\n return (\n <div className={css.typeaheadWrapper}>\n <Typeahead\n {...args}\n typeaheadInputText={inputText}\n onSearch={(e) => setInputText(e.target.value)}\n onSelect={(option) => {\n setInputText(option.label);\n }}\n onClear={() => setInputText('')}\n />\n </div>\n );\n};\n\nTypeaheadWithTooltipOptionsStory.args = {\n label: 'Search a name',\n menu: {...menuProps, showLabelTooltip: {maxLines: 10}},\n testId: 'typeahead-with-tooltip-options-story',\n};\n\nexport const WithHeaderAndFooterStory = (args: TypeaheadProps) => (\n <WithHeaderAndFooter {...args} />\n);\n\nexport const WithHeaderAndFooter = (args: TypeaheadProps) => {\n const clickAwayRef = React.useRef<ClickAwayRefType | null>(null);\n const [inputText, setInputText] = React.useState<string | undefined>('');\n\n const onSave = () => {\n if (clickAwayRef.current) {\n clickAwayRef.current?.forceClose();\n }\n };\n\n return (\n <div className={css.typeaheadWrapper}>\n <Typeahead\n {...args}\n typeaheadInputText={inputText}\n onSearch={(e) => setInputText(e.target.value)}\n onSelect={(option) => {\n setInputText(option.label);\n }}\n menu={{\n options,\n header: <MenuHeader />,\n footer: <MenuFooter onSave={onSave} />,\n }}\n onClear={() => setInputText('')}\n clickAwayRef={clickAwayRef}\n />\n </div>\n );\n};\n\nWithHeaderAndFooterStory.args = {\n label: 'Search a name',\n testId: 'typeahead-with-header-and-footer-story',\n};\n\nconst getPaginatedData = ({\n limit,\n offset,\n searchText,\n}: {\n limit: number;\n offset: number;\n searchText: string;\n}) =>\n new Promise(\n (\n resolve: (result: Promise<Array<MenuOption>> | Array<MenuOption>) => void,\n ) =>\n setTimeout(() => {\n const filteredOptions = options.filter((option) =>\n option.label\n ? option.label.toLowerCase().includes(searchText.toLowerCase())\n : true,\n );\n\n resolve(filteredOptions.slice(offset, offset + limit));\n }, 1000),\n );\n\nexport const WithPaginationAndVirtualizationStory = (args: TypeaheadProps) => {\n const clickAwayRef = React.useRef(null);\n const [inputText, setInputText] = React.useState('');\n const [isLoading, setIsLoading] = React.useState(false);\n const [menuOptions, setMenuOptions] = React.useState<MenuOption[]>([]);\n\n const debouncedInputText = useDebounce(inputText, 500);\n\n const fetchData = async () => {\n setIsLoading(true);\n const newOptions = await getPaginatedData({\n limit: DEFAULT_LIMIT,\n offset: DEFAULT_OFFSET,\n searchText: debouncedInputText,\n });\n setMenuOptions(newOptions);\n setIsLoading(false);\n };\n\n React.useEffect(() => {\n fetchData();\n }, [debouncedInputText]);\n\n return (\n <div className={css.typeaheadWrapper}>\n <Typeahead\n {...args}\n menu={{\n options: menuOptions,\n virtualization: {enable: true},\n }}\n onClear={() => setInputText('')}\n onSearch={(e) => setInputText(e.target.value)}\n onSelect={({label}) => setInputText(label || '')}\n isLoading={isLoading}\n clickAwayRef={clickAwayRef}\n typeaheadInputText={inputText}\n allowInternalFilter={false}\n />\n </div>\n );\n};\n\nWithPaginationAndVirtualizationStory.args = {\n label: 'Search a name',\n menuOpenOffset: 0,\n testId: 'typeahead-with-pagination-and-virtualization-story',\n};\n\nexport const TypeaheadWithRightSlotStory = (args: TypeaheadProps) => {\n const [inputText, setInputText] = React.useState<string | undefined>('');\n const [checked, setChecked] = React.useState(false);\n return (\n <div className={css.typeaheadWrapper}>\n <Typeahead\n {...args}\n typeaheadInputText={inputText}\n onSearch={(e) => setInputText(e.target.value)}\n onSelect={(option) => {\n setInputText(option.label);\n }}\n onClear={() => setInputText('')}\n rightSlot={\n <Tooltip title={checked ? 'Disable AI Search' : 'Enable AI Search'}>\n <div className={css.toggleContainer}>\n <Toggle\n checked={checked}\n onChange={() => {\n setChecked(!checked);\n }}\n />\n </div>\n </Tooltip>\n }\n />\n </div>\n );\n};\n\nTypeaheadWithRightSlotStory.args = {\n label: 'Search a name',\n menu: menuProps,\n testId: 'typeahead-story',\n};\n\nTypeaheadStory.storyName = 'Typeahead';\nAccessibleTypeaheadStory.storyName = 'Accessible (enableA11y)';\nTypeaheadWithTooltipOptionsStory.storyName = 'Typeahead With Tooltip Options';\nWithHeaderAndFooterStory.storyName = 'With Header And Footer';\nWithPaginationAndVirtualizationStory.storyName =\n 'With Pagination And Virtualization';\nTypeaheadWithRightSlotStory.storyName = 'Typeahead With Right Slot';\n"
|
|
3027
3047
|
},
|
|
3028
3048
|
"css": {
|
|
3029
3049
|
"path": "Typeahead.module.css",
|
|
3030
|
-
"content": "@value (\n sizeFluid\n) from '../../styles/variables/_size.css';\n\n.typeaheadContainer {\n display: flex;\n position: relative;\n flex-flow: column;\n width: sizeFluid;\n height: fit-content;\n}\n\n.menuWrapper {\n width: var(--dropdown-width);\n z-index: var(--menu-elevation);\n}\n"
|
|
3050
|
+
"content": "@value (\n sizeFluid\n) from '../../styles/variables/_size.css';\n\n.typeaheadContainer {\n display: flex;\n position: relative;\n flex-flow: column;\n width: sizeFluid;\n height: fit-content;\n}\n\n.menuWrapper {\n width: var(--dropdown-width);\n z-index: var(--menu-elevation);\n}\n\n/* Announced by screen readers (loading / result count) but visually hidden. */\n.visuallyHidden {\n position: absolute;\n width: 1px;\n height: 1px;\n margin: -1px;\n padding: 0;\n border: 0;\n overflow: hidden;\n clip: rect(0, 0, 0, 0);\n white-space: nowrap;\n}\n"
|
|
3031
3051
|
},
|
|
3032
3052
|
"index": {
|
|
3033
3053
|
"path": "index.d.ts",
|
|
@@ -3323,6 +3343,25 @@
|
|
|
3323
3343
|
"useInputState.ts"
|
|
3324
3344
|
]
|
|
3325
3345
|
},
|
|
3346
|
+
"useInteractionModality": {
|
|
3347
|
+
"name": "useInteractionModality",
|
|
3348
|
+
"path": "src/hooks/useInteractionModality",
|
|
3349
|
+
"files": {
|
|
3350
|
+
"main": {
|
|
3351
|
+
"path": "useInteractionModality.d.ts",
|
|
3352
|
+
"content": "export type InteractionModality = 'keyboard' | 'pointer';\n/**\n * Begin tracking. Idempotent, and session-scoped by design: the listeners live\n * for the life of the page, so there is nothing to tear down.\n *\n * Tracking has to start BEFORE the thing that wants the answer exists. The\n * interesting question is \"how was the popup that just mounted opened?\", and the\n * keypress that opened it happened while the popup did not yet exist — so a\n * component reading this on mount is always too late to have attached the\n * listener itself. Whatever renders earlier and knows a popup may follow — a\n * menu trigger — primes it.\n *\n * It is not primed on import. Attaching document listeners is a side effect, and\n * a consumer that has not opted into any of this should get none: an app merely\n * importing a component from this package must not start listening to its\n * keyboard. Capture phase, so the modality is recorded before any component\n * handler reacts to the same event.\n */\nexport declare const startInteractionModalityTracking: () => void;\n/**\n * How the user last interacted with the page, read imperatively.\n *\n * Use this when the answer is needed during render or in an effect rather than\n * as reactive state — for example a popup deciding on mount whether it was opened\n * by keyboard, where re-rendering on later modality changes would be wrong.\n */\nexport declare const getInteractionModality: () => InteractionModality;\n/**\n * How the user last interacted with the page, as reactive state. Re-renders the\n * caller when the user switches between keyboard and pointer.\n */\nexport declare const useInteractionModality: (\n/**\n * Subscribe at all. False leaves the caller on the default and attaches\n * nothing — for a component whose consumer has not opted into the behavior\n * this modality serves.\n */\nenabled?: boolean) => InteractionModality;\n//# sourceMappingURL=useInteractionModality.d.ts.map"
|
|
3353
|
+
},
|
|
3354
|
+
"story": null,
|
|
3355
|
+
"index": {
|
|
3356
|
+
"path": "index.d.ts",
|
|
3357
|
+
"content": "export * from '../../hooks/useInteractionModality/useInteractionModality';\n//# sourceMappingURL=index.d.ts.map"
|
|
3358
|
+
}
|
|
3359
|
+
},
|
|
3360
|
+
"allFiles": [
|
|
3361
|
+
"index.ts",
|
|
3362
|
+
"useInteractionModality.ts"
|
|
3363
|
+
]
|
|
3364
|
+
},
|
|
3326
3365
|
"useLockedBody": {
|
|
3327
3366
|
"name": "useLockedBody",
|
|
3328
3367
|
"path": "src/hooks/useLockedBody",
|
|
@@ -5131,7 +5170,7 @@
|
|
|
5131
5170
|
"files": {
|
|
5132
5171
|
"click-away.d.ts": {
|
|
5133
5172
|
"path": "click-away.d.ts",
|
|
5134
|
-
"content": "import * as React from 'react';\ntype BoundaryRefType<T extends HTMLElement> = React.RefObject<T | null>;\ntype TriggerRefType<T extends HTMLElement> = React.RefObject<T | null>;\nexport interface ChildProps<TTrigger extends HTMLElement = HTMLButtonElement, TBoundary extends HTMLElement = HTMLDivElement> {\n onOpen: () => void;\n isOpen: boolean;\n height: number | null | undefined;\n pageBottom: number | null | undefined;\n clickAway: () => void;\n boundaryRef: BoundaryRefType<TBoundary>;\n triggerRef: TriggerRefType<TTrigger>;\n}\nexport interface ClickAwayRefType {\n forceClose: () => void;\n forceOpen: () => void;\n}\nexport interface ClickAwayProps<TTrigger extends HTMLElement = HTMLButtonElement, TBoundary extends HTMLElement = HTMLDivElement> {\n closeOnEscapeKeypress?: boolean;\n children: (props: ChildProps<TTrigger, TBoundary>) => React.ReactNode;\n onChange?: (isOpen: boolean) => unknown;\n clickAwayRef?: React.RefObject<ClickAwayRefType | null>;\n /**\n * When containsNestedFloatingPortals is true, prevents ClickAway from closing the dropdown if the click target\n * is inside a floating portal root element.\n *\n * This is necessary for nested dropdowns rendered using floating portals,\n * such as via `@floating-ui/react`. Floating portals render elements outside\n * the normal DOM tree (often directly into `document.body` or another container)\n * using `ReactDOM.createPortal`. As a result, these nested dropdowns do not\n * appear inside the parent dropdown's `boundaryRef` or `triggerRef` subtree.\n *\n * To address this, the floating portal ensures that all nested dropdowns\n * are mounted into a **shared root element** — effectively maintaining a\n * common DOM ancestry. This allows parent dropdowns to detect when clicks\n * occur within any of their nested children, even if rendered via portals.\n * The shared root element is the closest parent element that has a `data-floating-ui-portal` attribute.\n * Because of this, the ClickAway logic will not close the parent menu if the click target is inside boundaryRef's parentElement.\n *\n * Enabling `containsNestedFloatingPortals` allows the ClickAway logic to account\n * for this shared root and avoids incorrectly closing the parent menu when\n * interacting with nested dropdowns.\n */\n containsNestedFloatingPortals?: boolean;\n}\ninterface ClickAwayState {\n isOpen: boolean;\n height: number | null | undefined;\n pageBottom: number | null | undefined;\n}\nexport declare class ClickAway<TTrigger extends HTMLElement = HTMLButtonElement, TBoundary extends HTMLElement = HTMLDivElement> extends React.Component<ClickAwayProps<TTrigger, TBoundary>, ClickAwayState> {\n static defaultProps: {\n containsNestedFloatingPortals?: boolean;\n closeOnEscapeKeypress?: boolean;\n };\n state: ClickAwayState;\n el: HTMLElement | null | undefined;\n boundaryRef: BoundaryRefType<TBoundary>;\n triggerRef: TriggerRefType<TTrigger>;\n componentDidMount(): void;\n componentDidUpdate(_prevProps: Readonly<ClickAwayProps<TTrigger, TBoundary>>, prevState: Readonly<ClickAwayState>): void;\n componentWillUnmount(): void;\n render(): React.ReactNode;\n handleOpenClick: () => void;\n handleCloseClick: (evt: MouseEvent) => void;\n handleCloseOnEscapeKeypress: (evt?: KeyboardEvent) => void;\n forceClose: () => void;\n handleOnChange: () => unknown;\n pageBottom(): number;\n}\nexport {};\n//# sourceMappingURL=click-away.d.ts.map"
|
|
5173
|
+
"content": "import * as React from 'react';\ntype BoundaryRefType<T extends HTMLElement> = React.RefObject<T | null>;\ntype TriggerRefType<T extends HTMLElement> = React.RefObject<T | null>;\nexport interface ChildProps<TTrigger extends HTMLElement = HTMLButtonElement, TBoundary extends HTMLElement = HTMLDivElement> {\n onOpen: () => void;\n isOpen: boolean;\n height: number | null | undefined;\n pageBottom: number | null | undefined;\n clickAway: () => void;\n boundaryRef: BoundaryRefType<TBoundary>;\n triggerRef: TriggerRefType<TTrigger>;\n}\nexport interface ClickAwayRefType {\n forceClose: () => void;\n forceOpen: () => void;\n}\nexport interface ClickAwayProps<TTrigger extends HTMLElement = HTMLButtonElement, TBoundary extends HTMLElement = HTMLDivElement> {\n closeOnEscapeKeypress?: boolean;\n /**\n * Return focus to whatever had it before the popup opened, when the popup\n * closes with focus still inside it (Escape, a selection, Tab past the edge).\n * A click elsewhere on the page is exempt: focus belongs where the user\n * clicked.\n *\n * Off by default. It is only wanted where something moves focus INTO the\n * popup — otherwise focus never left the trigger and there is nothing to\n * restore, and calling `focus()` on a trigger that already has it can be\n * enough to disturb a consumer (a field that opens its own menu on focus\n * would reopen the popup that just closed).\n */\n restoreFocusOnClose?: boolean;\n children: (props: ChildProps<TTrigger, TBoundary>) => React.ReactNode;\n onChange?: (isOpen: boolean) => unknown;\n clickAwayRef?: React.RefObject<ClickAwayRefType | null>;\n /**\n * When containsNestedFloatingPortals is true, prevents ClickAway from closing the dropdown if the click target\n * is inside a floating portal root element.\n *\n * This is necessary for nested dropdowns rendered using floating portals,\n * such as via `@floating-ui/react`. Floating portals render elements outside\n * the normal DOM tree (often directly into `document.body` or another container)\n * using `ReactDOM.createPortal`. As a result, these nested dropdowns do not\n * appear inside the parent dropdown's `boundaryRef` or `triggerRef` subtree.\n *\n * To address this, the floating portal ensures that all nested dropdowns\n * are mounted into a **shared root element** — effectively maintaining a\n * common DOM ancestry. This allows parent dropdowns to detect when clicks\n * occur within any of their nested children, even if rendered via portals.\n * The shared root element is the closest parent element that has a `data-floating-ui-portal` attribute.\n * Because of this, the ClickAway logic will not close the parent menu if the click target is inside boundaryRef's parentElement.\n *\n * Enabling `containsNestedFloatingPortals` allows the ClickAway logic to account\n * for this shared root and avoids incorrectly closing the parent menu when\n * interacting with nested dropdowns.\n */\n containsNestedFloatingPortals?: boolean;\n}\ninterface ClickAwayState {\n isOpen: boolean;\n height: number | null | undefined;\n pageBottom: number | null | undefined;\n}\nexport declare class ClickAway<TTrigger extends HTMLElement = HTMLButtonElement, TBoundary extends HTMLElement = HTMLDivElement> extends React.Component<ClickAwayProps<TTrigger, TBoundary>, ClickAwayState> {\n static defaultProps: {\n containsNestedFloatingPortals?: boolean;\n closeOnEscapeKeypress?: boolean;\n };\n state: ClickAwayState;\n el: HTMLElement | null | undefined;\n boundaryRef: BoundaryRefType<TBoundary>;\n triggerRef: TriggerRefType<TTrigger>;\n previouslyFocused: HTMLElement | null;\n closedByOutsideClick: boolean;\n componentDidMount(): void;\n componentDidUpdate(_prevProps: Readonly<ClickAwayProps<TTrigger, TBoundary>>, prevState: Readonly<ClickAwayState>): void;\n componentWillUnmount(): void;\n render(): React.ReactNode;\n handleOpenClick: () => void;\n restoreFocus: () => void;\n handleCloseClick: (evt: MouseEvent) => void;\n handleCloseOnEscapeKeypress: (evt?: KeyboardEvent) => void;\n forceClose: () => void;\n handleOnChange: () => unknown;\n pageBottom(): number;\n}\nexport {};\n//# sourceMappingURL=click-away.d.ts.map"
|
|
5135
5174
|
},
|
|
5136
5175
|
"index.d.ts": {
|
|
5137
5176
|
"path": "index.d.ts",
|