@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.
- package/index.cjs.js +1237 -133
- package/index.esm.js +1237 -136
- package/migrations/v3-0-0/menulist-rename-to-menucontent.js +105 -0
- package/migrations.json +5 -0
- package/package.json +6 -6
- package/src/components/Menu/MenuContent/MenuContent.d.ts +96 -0
- package/src/components/Menu/MenuDivider/MenuDivider.d.ts +5 -5
- package/src/components/Menu/MenuItem/MenuItem.d.ts +57 -10
- package/src/components/Menu/MoreMenu/MoreMenu.d.ts +7 -7
- package/src/components/Popover/MenuTree.d.ts +130 -0
- package/src/components/Popover/Popover.d.ts +8 -0
- package/src/components/Popover/PopoverContent.d.ts +13 -8
- package/src/components/Popover/PopoverNestedClearance.d.ts +56 -0
- package/src/components/Popover/PopoverSizing.d.ts +54 -2
- package/src/components/Popover/PopoverTypes.d.ts +21 -0
- package/src/components/Popover/hoverScrollGuard.d.ts +85 -0
- package/src/components/Popover/isElementVisible.d.ts +15 -0
- package/src/components/Popover/useHoverScrollGuard.d.ts +54 -0
- package/src/components/Popover/usePopover.d.ts +1 -1
- package/src/components/Sidebar/Sidebar.d.ts +3 -3
- package/src/hooks/getScrollbarWidth.d.ts +13 -0
- package/src/index.d.ts +4 -2
- package/src/components/Menu/MenuList/MenuList.d.ts +0 -83
- /package/src/components/Menu/{MenuList/MenuList.variants.d.ts → MenuContent/MenuContent.variants.d.ts} +0 -0
|
@@ -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, }:
|
|
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 {
|
|
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
|
|
45
|
+
* Pass custom props to the MenuContent component used to render overflow items
|
|
46
46
|
*/
|
|
47
|
-
menuListProps?: Omit<ComponentProps<typeof
|
|
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;
|
|
File without changes
|