@trackunit/react-components 2.5.4 → 2.6.2-alpha-770d9994af7.0

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.
@@ -9,6 +9,51 @@ type HeightValueParams = {
9
9
  value: HeightSizeLimit | undefined;
10
10
  availableHeight: number;
11
11
  };
12
+ type FloatingWidthBudgetParams = {
13
+ /**
14
+ * Floating UI `size()`'s side-relative available width at the current placement.
15
+ */
16
+ availableWidth: number;
17
+ /**
18
+ * The viewport's width in pixels (`window.innerWidth`).
19
+ */
20
+ viewportWidth: number;
21
+ /**
22
+ * When true (nested horizontal submenu), prefer the viewport so `shift` can cascade with
23
+ * overlap instead of `size` shrinking the panel into the remaining side-slot.
24
+ */
25
+ preferViewportBudget: boolean;
26
+ };
27
+ /**
28
+ * Width budget passed into `getMaxWidthValue` / `getMinWidthValue` from `size()`'s `apply()`.
29
+ *
30
+ * For nested horizontal submenus, Floating UI's side-relative `availableWidth` is only the
31
+ * remaining strip beside the parent panel. Capping to that shrinks the panel until `shift` no
32
+ * longer sees overflow -- so the intentional "cascade with overlap" path never runs, and any
33
+ * CSS min-width on the content (e.g. `MenuContent`'s `min-w-[200px]`) then overflows the
34
+ * wrapper and the page.
35
+ *
36
+ * Using the viewport width as the budget keeps the panel at its preferred size; `flip` still
37
+ * tries both sides, and `shift` slides it over the parent when neither side fits.
38
+ */
39
+ export declare const getFloatingWidthBudget: ({ availableWidth, viewportWidth, preferViewportBudget, }: FloatingWidthBudgetParams) => number;
40
+ type ShiftOptionsParams = {
41
+ /**
42
+ * True for a nested submenu whose requested placement is horizontal (`right*` / `left*`).
43
+ */
44
+ isNestedHorizontal: boolean;
45
+ };
46
+ /**
47
+ * Options for Floating UI's `shift()` middleware.
48
+ *
49
+ * For `right`/`left` placement, `shift`'s default only moves along the alignment (vertical) axis.
50
+ * Nested horizontal submenus need `crossAxis: true` so they can slide horizontally over the parent
51
+ * when neither side has room -- the "cascade with overlap" path paired with `getFloatingWidthBudget`.
52
+ */
53
+ export declare const getShiftOptions: ({ isNestedHorizontal, }: ShiftOptionsParams) => {
54
+ padding: number;
55
+ crossAxis?: boolean;
56
+ };
12
57
  /**
13
58
  * Converts a width size value into a CSS dimension value for max constraints
14
59
  *
@@ -19,13 +64,20 @@ type HeightValueParams = {
19
64
  */
20
65
  export declare const getMaxWidthValue: ({ value, referenceWidth, availableWidth }: WidthValueParams) => string;
21
66
  /**
22
- * Converts a width size value into a CSS dimension value for min constraints
67
+ * Converts a width size value into a CSS dimension value for min constraints.
68
+ *
69
+ * A numeric `value` is clamped to `availableWidth` so a caller's desired minimum can never force the
70
+ * floating panel wider than the viewport space `size()` already determined was safe -- without this, a
71
+ * fixed minWidth would win a losing fight against `getMaxWidthValue`'s cap (CSS resolves `min-width` over
72
+ * `max-width` on conflict) and the panel would overflow past its container/viewport edge instead of
73
+ * shrinking to fit.
23
74
  *
24
75
  * @param params - The parameters object
25
76
  * @param params.value - The size value: number for pixels, "trigger-width" to match trigger, "none" for no constraint
26
77
  * @param params.referenceWidth - The width of the trigger element in pixels
78
+ * @param params.availableWidth - The available width in the viewport
27
79
  */
28
- export declare const getMinWidthValue: ({ value, referenceWidth, }: Omit<WidthValueParams, "availableWidth">) => string | undefined;
80
+ export declare const getMinWidthValue: ({ value, referenceWidth, availableWidth }: WidthValueParams) => string | undefined;
29
81
  /**
30
82
  * Converts a height size value into a CSS dimension value for max constraints
31
83
  *
@@ -38,6 +38,7 @@ export type PopoverSizing = {
38
38
  minHeight?: HeightSizeLimit;
39
39
  maxHeight?: HeightSizeLimit;
40
40
  };
41
+ export type PopoverRole = "dialog" | "menu" | "listbox" | "grid" | "tree";
41
42
  export interface PopoverProps extends CommonProps {
42
43
  /**
43
44
  * Size constraints for the popover
@@ -75,6 +76,12 @@ export interface PopoverProps extends CommonProps {
75
76
  * Callback to be called when the popover open state changes
76
77
  */
77
78
  onOpenStateChange?: (open: boolean) => void;
79
+ /**
80
+ * ARIA role for the floating element, forwarded to Floating UI's `useRole`.
81
+ *
82
+ * @default "dialog"
83
+ */
84
+ role?: PopoverRole;
78
85
  /**
79
86
  * The id of the html element
80
87
  */
@@ -86,6 +93,20 @@ export type UsePopoverType = {
86
93
  isOpen: boolean;
87
94
  setIsOpen: Dispatch<SetStateAction<boolean>>;
88
95
  isModal: boolean | undefined;
96
+ /**
97
+ * This popover's id within its `MenuTree`, if it is rendered inside one. Used to register it
98
+ * with the tree via `FloatingNode` regardless of whether a tree exists.
99
+ */
100
+ nodeId: string;
101
+ /**
102
+ * Whether this popover itself has an ancestor node in its `MenuTree`, i.e. it is not the root of
103
+ * that tree (always `false` outside any `MenuTree`). Read from `useMenuTree()` before this
104
+ * popover wraps its own children in `<FloatingNode>` -- consumers rendered *inside* this popover's
105
+ * content (e.g. `MenuContent`) sit beneath that `FloatingNode` and would otherwise see their own
106
+ * ambient parent id resolve to this popover's id and misreport themselves as nested, so they
107
+ * should read this flag instead of calling `useMenuTree()` a second time from within.
108
+ */
109
+ isNested: boolean;
89
110
  labelId?: string;
90
111
  descriptionId?: string;
91
112
  setLabelId: (id: string) => void;
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Module-level (not React state) tracking of whether hover-driven `Popover` opening should be
3
+ * held back right now, because scrolling is either happening or has not yet settled.
4
+ *
5
+ * ### Root cause this guards against
6
+ * Scrolling any container recomputes the browser's pointer hit-test target on every frame, which
7
+ * fires real `pointerenter`/`pointerleave` pairs on whichever element now sits under an
8
+ * otherwise-stationary cursor -- indistinguishable, at the DOM level, from the user actually
9
+ * hovering that element. A hover-activated `Popover` reacting to those phantom events opens every
10
+ * row a scrolling list happens to carry under the cursor (most visible with `MenuItem`'s nested
11
+ * submenus in a scrolling list, but the same browser behavior affects any hover-activated
12
+ * `Popover`, e.g. a `Tooltip` over a scrolling table row).
13
+ *
14
+ * ### Why "settled", not "next pointer move"
15
+ * An earlier version of this guard cleared suppression on the next genuine `pointermove`, so a
16
+ * still-scrolling list would keep flipping back and forth between "suppressed" and "open the row
17
+ * the cursor happens to be over this frame" the moment the user so much as twitched the mouse
18
+ * mid-scroll. That's backwards from how this is meant to feel: while the list is moving, whatever
19
+ * was hovered before the scroll started should just stay put, and only once the scroll actually
20
+ * comes to rest should the row the cursor now stably rests over take over. Debouncing off the
21
+ * `scroll` event itself -- clearing suppression only after a lull longer than
22
+ * {@link SCROLL_SETTLE_DELAY_MS} with no further scroll events -- gets exactly that: suppression
23
+ * spans the whole scroll gesture (however bumpy), not just individual frames of it.
24
+ *
25
+ * ### Why not intercept/cancel the phantom event instead
26
+ * `mouseenter`/`pointerenter` don't natively bubble, so React simulates their capture/bubble
27
+ * dispatch internally -- not a stable surface to hook into from outside, and Floating UI's
28
+ * `useHover` owns that event handling anyway (it also drives `handleClose`/`safePolygon`, which
29
+ * still needs to work normally). Gating hover on "no scroll event anywhere in the last
30
+ * {@link SCROLL_SETTLE_DELAY_MS}ms" gets the same real-world result without any of that.
31
+ *
32
+ * ### Why page-wide listeners, not scoped to a particular scroll container
33
+ * A `scroll` event reaches a capturing-phase listener on `document` regardless of which element
34
+ * scrolled or whether the event itself bubbles, so a single pair of listeners here -- shared by
35
+ * every *opted-in* hover instance on the page, rather than each one attaching its own -- is both
36
+ * simpler and cheaper than resolving each `Popover`'s own scrollable ancestors. Subscription is
37
+ * gated in `usePopover` to delayed-hover nested `MenuTree` members only (ADR-0002), so plain
38
+ * Popovers/tooltips never attach the listeners or inherit suppression. The remaining trade-off is
39
+ * that scrolling *anywhere* briefly holds back hover-opens for those opted-in menu rows, not just
40
+ * near the scrolling container; acceptable since the guard clears itself shortly after scrolling
41
+ * settles, wherever the cursor then happens to rest.
42
+ *
43
+ * ### Visual hover highlight (CSS)
44
+ * The same scroll-induced `:hover` flips that would open phantom submenus also restyle every row
45
+ * the cursor passes over via CSS `:hover` backgrounds -- even while this guard holds the *open*
46
+ * state still. Consumers that want the highlight to freeze with the open state (notably
47
+ * `MenuItem`) key off {@link HOVER_SCROLL_SUPPRESSED_ATTR} on `<html>`, which this module mirrors
48
+ * onto the document in lockstep with suppression, so the highlight can follow open-state rules
49
+ * without re-rendering every row on each scroll tick.
50
+ *
51
+ * ### Why this also tracks the last real pointer position
52
+ * Once scrolling settles, `useHoverScrollGuard` needs to know which element the cursor now rests
53
+ * over so it can open that one. The obvious answer -- ask the browser via `:hover` -- turns out
54
+ * not to work: browsers only recompute an element's cached `:hover` state in response to a new
55
+ * real pointer event, not just because a re-render or attribute change made it hit-testable again.
56
+ * Since scrolling itself is exactly what carried the cursor's hit-test target across a run of rows
57
+ * without the physical mouse ever moving, there's typically no fresh pointer event left to trigger
58
+ * that recomputation right when settling happens -- so `:hover` stays stuck reporting whatever it
59
+ * last had, easily confirmed by comparing it against `document.elementFromPoint` at the same
60
+ * coordinates, which performs a fresh, on-demand hit-test instead of reading a cached flag. Tracking
61
+ * real client coordinates here (via a `pointermove` listener, sharing this module's lifecycle) lets
62
+ * `useHoverScrollGuard` run that same fresh `elementFromPoint` check itself once settled, rather
63
+ * than trusting a pseudo-class the browser hasn't gotten around to updating yet.
64
+ */
65
+ export declare const HOVER_SCROLL_SUPPRESSED_ATTR = "data-hover-scroll-suppressed";
66
+ export declare const hoverScrollGuard: {
67
+ isSuppressed: () => boolean;
68
+ /**
69
+ * The last real pointer position seen anywhere on the page, or `null` if no `pointermove` has
70
+ * happened yet (e.g. scrolling driven by keyboard/touch before any mouse input). See this
71
+ * module's doc comment for why consumers should use this over reading `:hover` directly.
72
+ */
73
+ getLastPointerPosition: () => {
74
+ x: number;
75
+ y: number;
76
+ } | null;
77
+ /**
78
+ * Subscribes to changes in suppression state, lazily attaching the shared `scroll` listener on
79
+ * first subscriber and tearing it down once the last one unsubscribes.
80
+ *
81
+ * @param listener Called whenever suppression state flips
82
+ * @returns {() => void} Unsubscribe function
83
+ */
84
+ subscribe: (listener: () => void) => (() => void);
85
+ };
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Whether `element` is currently visible to the user -- not clipped out by any scrollable/clipping
3
+ * ancestor (the browser fact `PopoverContent`'s `returnFocus` needs to respect: focusing a clipped
4
+ * element still succeeds, and browsers then auto-scroll it into view, fighting whatever caused it
5
+ * to be clipped in the first place -- see that module's doc comment) and not clipped by the
6
+ * viewport itself.
7
+ *
8
+ * Deliberately synchronous (plain `getBoundingClientRect`/`getComputedStyle` reads) rather than an
9
+ * `IntersectionObserver`, which only reports asynchronously on its own schedule and can't answer
10
+ * "is it visible right now" at the exact moment a popover is closing.
11
+ *
12
+ * @param element The element to check
13
+ * @returns {boolean} `false` if `element` (or any ancestor up to the viewport) clips it out
14
+ */
15
+ export declare const isElementVisible: (element: HTMLElement) => boolean;
@@ -0,0 +1,54 @@
1
+ import { FloatingContext, ReferenceType } from "@floating-ui/react";
2
+ import { MutableRefObject } from "react";
3
+ export interface UseHoverScrollGuardOptions {
4
+ /**
5
+ * Whether this consumer should participate in the shared page-wide scroll guard at all.
6
+ * When false, no document listeners are attached on behalf of this instance (ADR-0002 opt-in).
7
+ */
8
+ enabled: boolean;
9
+ /** Whether this `Popover`'s own `activation.hover` is enabled at all (before this guard). */
10
+ hoverEnabled: boolean;
11
+ /** See `usePopover`'s own `openSiblingPinned` -- a click-pinned sibling should still win. */
12
+ openSiblingPinned: boolean;
13
+ /** Whether this `Popover` is already open (nothing to do if so). */
14
+ isOpen: boolean;
15
+ /** This `Popover`'s own Floating UI context, used to imperatively open it. See below. */
16
+ popoverContext: FloatingContext;
17
+ /** This `Popover`'s reference element ref, used to check whether the cursor rests on it. See below. */
18
+ referenceRef: MutableRefObject<ReferenceType | null>;
19
+ }
20
+ /**
21
+ * Wires a single `Popover`'s hover interaction up to the shared `hoverScrollGuard`: holds its
22
+ * hover-opens back for as long as scrolling anywhere on the page hasn't yet settled (see
23
+ * `hoverScrollGuard`'s own doc comment for the full root-cause rationale), and -- once it settles
24
+ * -- imperatively opens this `Popover` if the cursor turns out to be resting on it, bypassing any
25
+ * open delay.
26
+ *
27
+ * ### Why the open needs its own imperative check
28
+ * Scrolling can carry the cursor's hit-test target across a whole run of rows without the physical
29
+ * cursor ever moving, so by the time scrolling settles there is no fresh `pointerenter` left for
30
+ * `useHover` to react to on whichever row the cursor ends up over: entering already happened
31
+ * (suppressed) while the list was still moving, not as a new enter.
32
+ *
33
+ * ### Why `elementFromPoint`, not `:hover`
34
+ * The obvious way to ask "is the cursor over this element right now" is `element.matches(":hover")`
35
+ * -- but browsers only recompute that cached flag in response to a *new* real pointer event, not
36
+ * just because the element became hit-testable again. Scrolling is exactly what leaves no such event
37
+ * behind at the moment settling happens, so `:hover` reports stale information right when it's
38
+ * needed most. `document.elementFromPoint` performs a fresh, on-demand hit-test instead of reading a
39
+ * cached flag, so it agrees with reality even when `:hover` hasn't caught up yet. It's checked
40
+ * against the last real pointer position `hoverScrollGuard` tracked (see that module's doc comment)
41
+ * rather than this reference's own bounding box, so it naturally returns nothing (and this correctly
42
+ * no-ops) if another element -- e.g. a still-open popover -- now sits on top of it.
43
+ *
44
+ * @param options See `UseHoverScrollGuardOptions`
45
+ * @param options.enabled Whether this consumer should subscribe to the shared scroll guard
46
+ * @param options.hoverEnabled Whether this `Popover`'s own `activation.hover` is enabled at all
47
+ * @param options.openSiblingPinned See `usePopover`'s own `openSiblingPinned`
48
+ * @param options.isOpen Whether this `Popover` is already open
49
+ * @param options.popoverContext This `Popover`'s own Floating UI context
50
+ * @param options.referenceRef This `Popover`'s reference element ref
51
+ * @returns {boolean} Whether hover-opens should be held back right now -- fold into `useHover`'s
52
+ * own `enabled` option alongside this `Popover`'s other activation conditions
53
+ */
54
+ export declare const useHoverScrollGuard: ({ enabled, hoverEnabled, openSiblingPinned, isOpen, popoverContext, referenceRef, }: UseHoverScrollGuardOptions) => boolean;
@@ -6,4 +6,4 @@ import { PopoverProps, UsePopoverType } from "./PopoverTypes";
6
6
  * @param {PopoverProps} options The options for the popover
7
7
  * @returns {UsePopoverType} The data for the popover
8
8
  */
9
- export declare const usePopover: ({ initialOpen, placement, isModal, isOpen: controlledIsOpen, activation, dismissal, sizing, onOpenStateChange, id, className, "data-testid": dataTestId, }: PopoverProps) => UsePopoverType;
9
+ export declare const usePopover: ({ initialOpen, placement, isModal, isOpen: controlledIsOpen, activation, dismissal, sizing, onOpenStateChange, role, id, className, "data-testid": dataTestId, }: PopoverProps) => UsePopoverType;
@@ -2,7 +2,7 @@ import { ComponentProps, MouseEventHandler, ReactElement } from "react";
2
2
  import { CommonProps } from "../../common/CommonProps";
3
3
  import { Refable } from "../../common/Refable";
4
4
  import type { Styleable } from "../../common/Styleable";
5
- import { MenuList } from "../Menu/MenuList/MenuList";
5
+ import { MenuContent } from "../Menu/MenuContent/MenuContent";
6
6
  import { MoreMenu } from "../Menu/MoreMenu/MoreMenu";
7
7
  export interface SidebarItemProps extends CommonProps, Styleable {
8
8
  /**
@@ -42,9 +42,9 @@ export interface SidebarProps extends CommonProps, Styleable, Refable<HTMLDivEle
42
42
  */
43
43
  moreMenuProps?: Omit<ComponentProps<typeof MoreMenu>, "children">;
44
44
  /**
45
- * Pass custom props to the MenuList component used to render overflow items
45
+ * Pass custom props to the MenuContent component used to render overflow items
46
46
  */
47
- menuListProps?: Omit<ComponentProps<typeof MenuList>, "children">;
47
+ menuListProps?: Omit<ComponentProps<typeof MenuContent>, "children">;
48
48
  }
49
49
  /**
50
50
  * Sidebar renders a responsive horizontal/vertical navigation bar that automatically collapses overflowing items into a MoreMenu.
@@ -0,0 +1,13 @@
1
+ /**
2
+ * The layout-reserved width of `element`'s own vertical scrollbar (in pixels), or `0` when it
3
+ * isn't currently reserving any (no overflow, or an overlay-style scrollbar that doesn't consume
4
+ * layout space, e.g. macOS's default).
5
+ *
6
+ * `offsetWidth` includes the element's border and any reserved scrollbar; `clientWidth` excludes
7
+ * both. Subtracting the (computed) border widths from that difference isolates the scrollbar
8
+ * itself -- a naive `offsetWidth - clientWidth` would wrongly fold border width into the result.
9
+ *
10
+ * @param element The element to measure
11
+ * @returns {number} The scrollbar's reserved width in pixels, or `0`
12
+ */
13
+ export declare const getScrollbarWidth: (element: HTMLElement) => number;
package/src/index.d.ts CHANGED
@@ -51,11 +51,11 @@ export * from "./components/List/List.variants";
51
51
  export * from "./components/List/useList";
52
52
  export * from "./components/ListItem/ListItem";
53
53
  export * from "./components/ListItem/useListItemHeight";
54
+ export * from "./components/Menu/MenuContent/MenuContent";
55
+ export * from "./components/Menu/MenuContent/MenuContent.variants";
54
56
  export * from "./components/Menu/MenuDivider/MenuDivider";
55
57
  export * from "./components/Menu/MenuItem/MenuItem";
56
58
  export * from "./components/Menu/MenuItem/MenuItem.variants";
57
- export * from "./components/Menu/MenuList/MenuList";
58
- export * from "./components/Menu/MenuList/MenuList.variants";
59
59
  export * from "./components/Menu/MoreMenu/MoreMenu";
60
60
  export * from "./components/Notice/Notice";
61
61
  export * from "./components/Page/Page";
@@ -68,6 +68,8 @@ export * from "./components/PageHeader/PageHeader.variants";
68
68
  export * from "./components/PageHeader/types";
69
69
  export * from "./components/Pagination/Pagination";
70
70
  export * from "./components/Polygon/Polygon";
71
+ export type { MenuTreeCloseEvent, MenuTreeOpenEvent, UseMenuTreeType } from "./components/Popover/MenuTree";
72
+ export { MenuTree, useMenuTree } from "./components/Popover/MenuTree";
71
73
  export * from "./components/Popover/Popover";
72
74
  export * from "./components/Popover/PopoverContent";
73
75
  export * from "./components/Popover/PopoverTitle";
@@ -1,83 +0,0 @@
1
- import { MouseEventHandler, ReactElement, ReactNode } from "react";
2
- import { CommonProps } from "../../../common/CommonProps";
3
- import type { Styleable } from "../../../common/Styleable";
4
- import { Refable } from "../../../common/Refable";
5
- export interface MenuListProps extends CommonProps, Styleable, Refable<HTMLDivElement> {
6
- /**
7
- * List of menu items to be rendered.
8
- */
9
- children: ReactNode;
10
- /** Click handler - typically used to trigger close action. when propagation is NOT prevented on menuItem */
11
- onClick?: MouseEventHandler<HTMLDivElement>;
12
- /**
13
- * Enable multi-selection in the menu list.
14
- *
15
- * @default false
16
- */
17
- isMulti?: boolean;
18
- /**
19
- * Array of IDs representing the currently selected items.
20
- */
21
- selectedItems?: Array<string>;
22
- /**
23
- * Callback triggered when selected items change.
24
- */
25
- onSelectionChange?: (selected: Array<string>) => void;
26
- }
27
- /**
28
- * The MenuList is a popover menu that appears above all other content on the page. The menu offers a list of actions or functions that a user can access by clicking on a trigger.
29
- *
30
- * **When to use**
31
- * - Use the MenuList if you have limited space and need to display overflow actions in a list.
32
- * - Use the MenuList for actions that are not essential to completing workflows.
33
- * - Don't use the MenuList to display single or multi-select items within form components. For dropdowns within select components, use SelectDropdown (component not available yet).
34
- *
35
- * @example MenuList with action items
36
- * ```tsx
37
- * import { MenuList, MenuItem, MoreMenu, Icon } from "@trackunit/react-components";
38
- *
39
- * const ActionsMenu = () => (
40
- * <MoreMenu>
41
- * {(close) => (
42
- * <MenuList onClick={close}>
43
- * <MenuItem id="edit" prefix={<Icon name="PencilSquare" size="small" />}>
44
- * Edit
45
- * </MenuItem>
46
- * <MenuItem id="duplicate" prefix={<Icon name="DocumentDuplicate" size="small" />}>
47
- * Duplicate
48
- * </MenuItem>
49
- * <MenuItem id="delete" prefix={<Icon name="Trash" size="small" />} destructive>
50
- * Delete
51
- * </MenuItem>
52
- * </MenuList>
53
- * )}
54
- * </MoreMenu>
55
- * );
56
- * ```
57
- * @example Multi-select MenuList
58
- * ```tsx
59
- * import { MenuList, MenuItem, MoreMenu } from "@trackunit/react-components";
60
- * import { useState } from "react";
61
- *
62
- * const FilterMenu = () => {
63
- * const [selected, setSelected] = useState<string[]>(["active"]);
64
- *
65
- * return (
66
- * <MoreMenu label="Filter by status">
67
- * <MenuList
68
- * isMulti
69
- * selectedItems={selected}
70
- * onSelectionChange={setSelected}
71
- * >
72
- * <MenuItem id="active">Active</MenuItem>
73
- * <MenuItem id="idle">Idle</MenuItem>
74
- * <MenuItem id="offline">Offline</MenuItem>
75
- * </MenuList>
76
- * </MoreMenu>
77
- * );
78
- * };
79
- * ```
80
- * @param {MenuListProps} props - The props for the MenuList component
81
- * @returns {ReactElement} MenuList component
82
- */
83
- export declare const MenuList: ({ "data-testid": dataTestId, className, children, isMulti, selectedItems: controlledSelectedItems, onSelectionChange, style, ref, ...args }: MenuListProps) => ReactElement;