UNPKG

@atlaskit/dropdown-menu

Version:

A dropdown menu displays a list of actions or options to a user.

402 lines (401 loc) 16.3 kB
import { type KeyboardEvent, type MouseEvent, type ReactElement, type ReactNode, type Ref, type RefObject } from 'react'; import type { CustomItemComponentProps, CustomItemProps, MenuGroupProps, SectionProps } from '@atlaskit/menu/types'; import type { ContentProps, TriggerProps } from '@atlaskit/popup/types'; export type FocusableElementRef = RefObject<HTMLAnchorElement | HTMLButtonElement>; export type Action = 'next' | 'prev' | 'first' | 'last' | 'tab'; export type Placement = 'auto-start' | 'auto' | 'auto-end' | 'top-start' | 'top' | 'top-end' | 'right-start' | 'right' | 'right-end' | 'bottom-end' | 'bottom' | 'bottom-start' | 'left-end' | 'left' | 'left-start'; export type ItemId = string; export type GroupId = string; export type CachedItem = { id: ItemId; groupId: GroupId; }; export type FocusItem = { itemId: ItemId; itemNode: HTMLElement; }; export type Behaviors = 'checkbox' | 'radio' | 'menuitemcheckbox' | 'menuitemradio'; export interface CustomTriggerProps<TriggerElement extends HTMLElement = any> extends Omit<TriggerProps, 'ref'> { /** * Ref that should be applied to the trigger. This is used to calculate the menu position. */ triggerRef: Ref<TriggerElement>; /** * Makes the trigger appear selected. */ isSelected?: boolean; /** * Event that is triggered when the element is clicked. */ onClick?: (e: MouseEvent | KeyboardEvent) => void; /** * onFocus handler that should be applied to the trigger. Used to bind onKeyDown listener * to the window object to track ArrowDown events. */ onFocus?: () => void; /** * onBlur handler that should be applied to the trigger. Used to unbind onKeyDown listener * from the window object. */ onBlur?: () => void; /** * A `testId` prop is provided for specified elements, which is a unique * string that appears as a data attribute `data-testid` in the rendered code, * serving as a hook for automated tests. * * As dropdown-menu is composed of different components, we passed down the testId to the sub component you want to test: * - `testId--trigger` to get the menu trigger. * - `testId--content` to get the dropdown content trigger. */ testId?: string; } export interface OnOpenChangeArgs { isOpen: boolean; /** * The event that triggered the close. * The value will be `null` when the dropdown is closed programatically and has no corresponding event. */ event: Event | MouseEvent | KeyboardEvent | null; } export interface MenuWrapperProps extends MenuGroupProps { setInitialFocusRef?: ContentProps['setInitialFocusRef']; onClose?: ContentProps['onClose']; onUpdate: ContentProps['update']; isLoading?: InternalDropdownMenuProps['isLoading']; statusLabel?: InternalDropdownMenuProps['statusLabel']; shouldRenderToParent?: boolean; isTriggeredUsingKeyboard?: boolean; autoFocus?: boolean; } export interface DropdownMenuGroupProps extends SectionProps { } interface InternalDropdownMenuProps<TriggerElement extends HTMLElement = any> { /** * Controls the appearance of the menu. * The default menu will scroll after its height exceeds the pre-defined amount. * The tall menu won't scroll until the height exceeds the height of the viewport. */ appearance?: 'default' | 'tall'; /** * Controls if the first menu item receives focus when menu is opened. Note that the menu has a focus lock * which traps the focus within the menu. The first item gets focus automatically * if the menu is triggered using the keyboard. * */ autoFocus?: boolean; /** * Content that will be rendered inside the layer element. Should typically be * `DropdownItemGroup` or `DropdownItem`, or the checkbox and radio variants of those. */ children?: ReactNode; /** * If true, a spinner is rendered instead of the items. */ isLoading?: boolean; /** * Text to be used as status for assistive technologies. Defaults to "Loading". */ statusLabel?: string; /** * Controls the open state of the dropdown. */ isOpen?: boolean; /** * Position of the menu. */ placement?: Placement; /** * This fits the dropdown menu width to its parent's width. * When set to `true`, the trigger and dropdown menu elements will be wrapped in a `div` with `position: relative`. * The dropdown menu will be rendered as a sibling to the trigger element, and will be full width. * The default is `false`. */ shouldFitContainer?: boolean; /** * Allows the dropdown menu to be placed on the opposite side of its trigger if it does not * fit in the viewport. */ shouldFlip?: boolean; /** * The root element where the dropdown menu content should be rendered. * `true` renders the dropdown menu in the DOM node closest to the trigger and avoids focus trap with tab keys. * `false` renders the dropdown menu in React.Portal. * Defaults to `false`. */ shouldRenderToParent?: boolean; /** * Controls the spacing density of the menu. */ spacing?: Extract<MenuGroupProps['spacing'], 'cozy' | 'compact'>; /** * Content that triggers the dropdown menu to open and close. Use with * `triggerType` to get a button trigger. To customize the trigger element, * provide a function to this prop. You can find * [examples for custom triggers](https://atlassian.design/components/dropdown-menu/examples#custom-triggers) * in our documentation. */ trigger?: string | ((triggerButtonProps: CustomTriggerProps<TriggerElement>) => ReactElement); /** * A `testId` prop is provided for specified elements, which is a unique * string that appears as a data attribute `data-testid` in the rendered code, * serving as a hook for automated tests. * * As dropdown-menu is composed of different components, we passed down the testId to the sub component you want to test: * - `testId--trigger` to get the menu trigger. * - `testId--content` to get the dropdown content trigger. */ testId?: string; /** * Controls the initial open state of the dropdown. If provided, the component is considered to be controlled * which means that the user is responsible for managing the open and close state of the menu. * Using `defaultOpen` may cause accessiblity issues because it will automatically shift focus to the first menu item; which can be disorienting. * Only use this if action on the menu is required to proceed. */ defaultOpen?: boolean; /** * Called when the menu should be open/closed. Receives an object with `isOpen` state. * * If the dropdown was closed programatically, the `event` parameter will be `null`. */ onOpenChange?: (args: OnOpenChangeArgs) => void; /** * Z-index that the popup should be displayed in. * This is passed to the portal component. * Defaults to `layers.modal()` from `@atlaskit/theme` which is 510. */ zIndex?: number; /** * Provide an accessible label via `aria-label` for assistive technology. */ label?: string; /** * If ref is passed, focus returns to that specific ref element after dropdown dismissed. */ returnFocusRef?: RefObject<HTMLElement>; /** * An optional name used to identify events for [React UFO (Unified Frontend Observability) press interactions](https://developer.atlassian.com/platform/ufo/react-ufo/react-ufo/getting-started/#quick-start--press-interactions). For more information, see [React UFO integration into Design System components](https://go.atlassian.com/react-ufo-dst-integration). */ interactionName?: string; /** * This controls the positioning strategy to use. Can vary between `absolute` and `fixed`. * The default is `fixed`. */ strategy?: 'absolute' | 'fixed'; } type StandardDropdownMenuProps<TriggerElement extends HTMLElement = any> = InternalDropdownMenuProps<TriggerElement> & { shouldFitContainer?: false; }; type ShouldFitContainerDropdownMenuProps<TriggerElement extends HTMLElement = any> = InternalDropdownMenuProps<TriggerElement> & { shouldFitContainer: true; shouldRenderToParent?: true; strategy?: 'absolute'; }; export type DropdownMenuProps<TriggerElement extends HTMLElement = any> = StandardDropdownMenuProps<TriggerElement> | ShouldFitContainerDropdownMenuProps<TriggerElement>; export interface DropdownItemProps { /** * Primary content for the item. */ children: React.ReactNode; /** * Custom component to render as an item. * Should be wrapped in `forwardRef` to avoid accessibility issues when controlling keyboard focus. */ component?: CustomItemProps['component']; /** * Description of the item. * This will render smaller text below the primary text of the item as well as slightly increasing the height of the item. */ description?: string | JSX.Element; /** * Makes the element appear disabled. This will remove interactivity and the item won't appear in the focus order. */ isDisabled?: boolean; /** * When `true` the title of the item will wrap multiple lines if it exceeds the width of the dropdown menu. */ shouldTitleWrap?: boolean; /** * When `true` the description of the item will wrap multiple lines if it exceeds the width of the dropdown menu. */ shouldDescriptionWrap?: boolean; /** * Event that is triggered when the element is clicked. */ onClick?: (e: MouseEvent | KeyboardEvent) => void; /** * Makes the element appear selected. */ isSelected?: boolean; /** * Link to another page. */ href?: string; /** * Where to display the linked URL, * see [anchor information](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a) on mdn for more information. */ target?: string; /** * Adds a title attribute to the root item element. */ title?: string; /** * Element to render before the item text. * Generally should be an [icon](https://atlaskit.atlassian.com/packages/design-system/icon) component. */ elemBefore?: React.ReactNode; /** * Element to render after the item text. * Generally should be an [icon](https://atlaskit.atlassian.com/packages/design-system/icon) component. */ elemAfter?: React.ReactNode; /** * The relationship of the linked URL as space-separated link types. * Generally you'll want to set this to "noopener noreferrer" when `target` is "_blank". */ rel?: string; /** * A `testId` prop is provided for specified elements, * which is a unique string that appears as a data attribute `data-testid` in the rendered code, * serving as a hook for automated tests. */ testId?: string; /** * Use this to opt out of using a router link and instead use a regular anchor element when * using the `href` prop. * Marked as "unsafe" because ideally, router links should be used for all internal links. */ UNSAFE_shouldDisableRouterLink?: boolean; /** * If ref is passed, focus returns to that specific ref element after dropdown item clicked. */ returnFocusRef?: RefObject<HTMLElement>; /** * An optional name used to identify events for [React UFO (Unified Frontend Observability) press interactions](https://developer.atlassian.com/platform/ufo/react-ufo/react-ufo/getting-started/#quick-start--press-interactions). For more information, see [React UFO integration into Design System components](https://go.atlassian.com/react-ufo-dst-integration). */ interactionName?: string; /** * An optional boolean value used to indicate if the dropdown item has popup or not. */ 'aria-haspopup'?: boolean | 'dialog'; /** * An optional string value that specifies the role of the dropdown item. * Use this to indicate whether the item is * or presentational (e.g., 'presentation') for accessibility purposes. * If not specified, it defaults to role="menuitem". */ role?: string; } export interface DropdownItemCheckboxProps { /** * Primary content for the item. */ children: React.ReactNode; /** * Description of the item. * This will render smaller text below the primary text of the item as well as slightly increasing the height of the item. */ description?: string | JSX.Element; /** * Makes the checkbox appear disabled as well as removing interactivity. */ isDisabled?: boolean; /** * When `true` the title of the item will wrap multiple lines if it's long enough. */ shouldTitleWrap?: boolean; /** * When `true` the description of the item will wrap multiple lines if it's long enough. */ shouldDescriptionWrap?: boolean; /** * Event that is triggered when the checkbox is clicked. */ onClick?: (e: MouseEvent | KeyboardEvent) => void; /** * Sets whether the checkbox is checked or unchecked. */ isSelected?: boolean; /** * Sets whether the checkbox begins selected. */ defaultSelected?: boolean; /** * Unique id of a checkbox. */ id: string; /** * Adds a title attribute to the root item element. */ title?: string; /** * A `testId` prop is provided for specified elements, * which is a unique string that appears as a data attribute `data-testid` in the rendered code, * serving as a hook for automated tests. */ testId?: string; /** * An optional name used to identify events for [React UFO (Unified Frontend Observability) press interactions](https://developer.atlassian.com/platform/ufo/react-ufo/react-ufo/getting-started/#quick-start--press-interactions). For more information, see [React UFO integration into Design System components](https://go.atlassian.com/react-ufo-dst-integration). */ interactionName?: string; } export interface DropdownItemRadioProps { /** * Primary content for the item. */ children: React.ReactNode; /** * Description of the item. * This will render smaller text below the primary text of the item as well as slightly increasing the height of the item. */ description?: string | JSX.Element; /** * Makes the checkbox appear disabled as well as removing interactivity. */ isDisabled?: boolean; /** * When `true` the title of the item will wrap multiple lines if it's long enough. */ shouldTitleWrap?: boolean; /** * When `true` the description of the item will wrap multiple lines if it's long enough. */ shouldDescriptionWrap?: boolean; /** * Event that is triggered when the checkbox is clicked. */ onClick?: (e: MouseEvent | KeyboardEvent) => void; /** * Sets whether the checkbox is checked or unchecked. */ isSelected?: boolean; /** * Sets whether the checkbox begins selected. */ defaultSelected?: boolean; /** * Unique ID of the checkbox. */ id: string; /** * Adds a title attribute to the root item element. */ title?: string; /** * A `testId` prop is provided for specified elements, * which is a unique string that appears as a data attribute `data-testid` in the rendered code, * serving as a hook for automated tests. */ testId?: string; /** * An optional name used to identify events for [React UFO (Unified Frontend Observability) press interactions](https://developer.atlassian.com/platform/ufo/react-ufo/react-ufo/getting-started/#quick-start--press-interactions). For more information, see [React UFO integration into Design System components](https://go.atlassian.com/react-ufo-dst-integration). */ interactionName?: string; } export interface CustomItemHtmlProps extends CustomItemComponentProps { /** * Link to another page. */ href?: string; } export {};