@lumx/core 4.16.1-alpha.2 → 4.17.0-next.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/components-and-utils.css +10 -0
- package/js/components/Menu/MenuButton.d.ts +15 -0
- package/js/components/Menu/MenuButtonStories.d.ts +41 -0
- package/js/components/Menu/MenuButtonTestStories.d.ts +31 -0
- package/js/components/Menu/MenuButtonTests.d.ts +22 -0
- package/js/components/Menu/MenuItem.d.ts +26 -0
- package/js/components/Menu/MenuItemTests.d.ts +11 -0
- package/js/components/Menu/MenuList.d.ts +16 -0
- package/js/components/Menu/MenuPopover.d.ts +34 -0
- package/js/components/Menu/MenuTrigger.d.ts +21 -0
- package/js/components/Menu/setupMenu.d.ts +18 -0
- package/js/components/Menu/types.d.ts +40 -0
- package/js/components/Menu/utils.d.ts +9 -0
- package/js/utils/browser/isPrintableKey.d.ts +7 -0
- package/lumx.css +10 -0
- package/package.json +2 -2
- package/scss/_components_classes.scss +1 -0
- package/scss/components/menu/_index.scss +11 -0
package/components-and-utils.css
CHANGED
|
@@ -8302,6 +8302,16 @@ table {
|
|
|
8302
8302
|
background-color: var(--lumx-color-dark-L5);
|
|
8303
8303
|
}
|
|
8304
8304
|
|
|
8305
|
+
/* ==========================================================================
|
|
8306
|
+
Menu
|
|
8307
|
+
========================================================================== */
|
|
8308
|
+
.lumx-menu-popover {
|
|
8309
|
+
max-height: 50vh;
|
|
8310
|
+
}
|
|
8311
|
+
.lumx-menu-popover__scroll {
|
|
8312
|
+
overflow-y: auto;
|
|
8313
|
+
}
|
|
8314
|
+
|
|
8305
8315
|
/* ==========================================================================
|
|
8306
8316
|
Message
|
|
8307
8317
|
========================================================================== */
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { JSXElement, LumxClassName } from '../../types';
|
|
2
|
+
export interface MenuButtonProps {
|
|
3
|
+
label?: JSXElement;
|
|
4
|
+
children?: JSXElement;
|
|
5
|
+
triggerProps?: Record<string, any>;
|
|
6
|
+
popoverProps?: Record<string, any>;
|
|
7
|
+
onOpen?: (isOpen: boolean) => void;
|
|
8
|
+
}
|
|
9
|
+
export interface MenuButtonComponents {
|
|
10
|
+
Menu: any;
|
|
11
|
+
}
|
|
12
|
+
export declare const COMPONENT_NAME = "MenuButton";
|
|
13
|
+
export declare const CLASSNAME: LumxClassName<typeof COMPONENT_NAME>;
|
|
14
|
+
/** Menu button core template (composition of menu provider, trigger, popover and list) */
|
|
15
|
+
export declare const MenuButton: (props: MenuButtonProps, { Menu }: MenuButtonComponents) => import("react").JSX.Element;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { SetupStoriesOptions } from '@lumx/core/stories/types';
|
|
2
|
+
export declare function setup({ components: { MenuButton, MenuItem, MenuDivider }, }: SetupStoriesOptions<{
|
|
3
|
+
components: {
|
|
4
|
+
MenuButton: any;
|
|
5
|
+
MenuItem: any;
|
|
6
|
+
MenuDivider: any;
|
|
7
|
+
};
|
|
8
|
+
}>): {
|
|
9
|
+
meta: {
|
|
10
|
+
component: any;
|
|
11
|
+
argTypes: {
|
|
12
|
+
onOpen: {
|
|
13
|
+
action: boolean;
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
args: {
|
|
17
|
+
label: string;
|
|
18
|
+
};
|
|
19
|
+
play(): Promise<void>;
|
|
20
|
+
};
|
|
21
|
+
Default: {
|
|
22
|
+
argTypes: {
|
|
23
|
+
onProfile: {
|
|
24
|
+
action: boolean;
|
|
25
|
+
};
|
|
26
|
+
onSettings: {
|
|
27
|
+
action: boolean;
|
|
28
|
+
};
|
|
29
|
+
onHelp: {
|
|
30
|
+
action: boolean;
|
|
31
|
+
};
|
|
32
|
+
onLogout: {
|
|
33
|
+
action: boolean;
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
args: {
|
|
37
|
+
emphasis: string;
|
|
38
|
+
};
|
|
39
|
+
render: ({ onProfile, onSettings, onHelp, onLogout, ...args }: any) => import("react").JSX.Element;
|
|
40
|
+
};
|
|
41
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export declare function setup({ components: { MenuButton, MenuItem } }: {
|
|
2
|
+
components: {
|
|
3
|
+
MenuButton: any;
|
|
4
|
+
MenuItem: any;
|
|
5
|
+
};
|
|
6
|
+
}): {
|
|
7
|
+
meta: {
|
|
8
|
+
tags: string[];
|
|
9
|
+
args: {
|
|
10
|
+
onOne: import("storybook/test").Mock<(...args: any[]) => any>;
|
|
11
|
+
onTwo: import("storybook/test").Mock<(...args: any[]) => any>;
|
|
12
|
+
onThree: import("storybook/test").Mock<(...args: any[]) => any>;
|
|
13
|
+
};
|
|
14
|
+
render: (args: any) => import("react").JSX.Element;
|
|
15
|
+
};
|
|
16
|
+
TabFromLastItemClosesAndMovesFocus: {
|
|
17
|
+
play: ({ canvasElement }: any) => Promise<void>;
|
|
18
|
+
};
|
|
19
|
+
ShiftTabFromFirstItemClosesAndReturnsToTrigger: {
|
|
20
|
+
play: ({ canvasElement }: any) => Promise<void>;
|
|
21
|
+
};
|
|
22
|
+
EscapeRestoresFocusToTrigger: {
|
|
23
|
+
play: ({ canvasElement }: any) => Promise<void>;
|
|
24
|
+
};
|
|
25
|
+
ItemActivationRestoresFocusToTrigger: {
|
|
26
|
+
play: ({ canvasElement, args }: any) => Promise<void>;
|
|
27
|
+
};
|
|
28
|
+
ClickAwayClosesMenu: {
|
|
29
|
+
play: ({ canvasElement }: any) => Promise<void>;
|
|
30
|
+
};
|
|
31
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
type RenderResult = {
|
|
2
|
+
unmount: () => void;
|
|
3
|
+
container: HTMLElement;
|
|
4
|
+
};
|
|
5
|
+
/**
|
|
6
|
+
* Options to set up the MenuButton test suite.
|
|
7
|
+
* Injected by the framework-specific test file (React or Vue).
|
|
8
|
+
*/
|
|
9
|
+
export interface MenuButtonTestSetup {
|
|
10
|
+
components: {
|
|
11
|
+
MenuButton: any;
|
|
12
|
+
MenuItem: any;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Render a MenuButton template.
|
|
16
|
+
*
|
|
17
|
+
* @param template JSX render function returning the element to render.
|
|
18
|
+
*/
|
|
19
|
+
render: (template: () => any) => RenderResult;
|
|
20
|
+
}
|
|
21
|
+
export default function menuButtonTests({ components, render }: MenuButtonTestSetup): void;
|
|
22
|
+
export {};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { CommonRef, HasClassName, JSXElement, LumxClassName } from '../../types';
|
|
2
|
+
/** MenuItem props. */
|
|
3
|
+
export interface MenuItemProps extends HasClassName {
|
|
4
|
+
/** Item label content. */
|
|
5
|
+
children?: JSXElement;
|
|
6
|
+
/** Content rendered before the label (e.g. an icon). */
|
|
7
|
+
before?: JSXElement;
|
|
8
|
+
/** Content rendered after the label (e.g. a shortcut hint). */
|
|
9
|
+
after?: JSXElement;
|
|
10
|
+
/** Whether the item is disabled. Skipped by Tab and arrow nav. */
|
|
11
|
+
isDisabled?: boolean;
|
|
12
|
+
/** Click handler for the action element. */
|
|
13
|
+
handleClick?(event: any): void;
|
|
14
|
+
/** Extra props forwarded to the inner action element (e.g. link props when `as="a"`). */
|
|
15
|
+
actionProps?: Record<string, any>;
|
|
16
|
+
/** ref to the inner action element (the focusable `<button>` / `<a>`). */
|
|
17
|
+
actionRef?: CommonRef;
|
|
18
|
+
/** ref to the root `<li>` element. */
|
|
19
|
+
ref?: CommonRef;
|
|
20
|
+
}
|
|
21
|
+
/** Props overridden by framework wrappers. */
|
|
22
|
+
export type MenuItemPropsToOverride = 'before' | 'after' | 'children' | 'handleClick' | 'actionProps' | 'actionRef';
|
|
23
|
+
export declare const COMPONENT_NAME = "MenuItem";
|
|
24
|
+
export declare const CLASSNAME: LumxClassName<typeof COMPONENT_NAME>;
|
|
25
|
+
/** MenuItem core template. Renders a `ListItem` with a `ListItemAction`. */
|
|
26
|
+
export declare const MenuItem: (props: MenuItemProps) => import("react").JSX.Element;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { SetupOptions } from '../../../testing';
|
|
2
|
+
/**
|
|
3
|
+
* Mounts the component and returns common DOM elements / data needed in multiple tests further down.
|
|
4
|
+
*/
|
|
5
|
+
export declare const setup: (propsOverride: any | undefined, { render, screen, ...options }: SetupOptions<any>) => {
|
|
6
|
+
props: any;
|
|
7
|
+
menuItem: HTMLElement;
|
|
8
|
+
action: HTMLElement;
|
|
9
|
+
screen: import("@testing-library/dom").Screen;
|
|
10
|
+
};
|
|
11
|
+
export declare function menuItemTests(renderOptions: SetupOptions<any>): void;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { CommonRef, HasClassName, JSXElement, LumxClassName } from '../../types';
|
|
2
|
+
/** Menu list props. */
|
|
3
|
+
export type MenuListProps = HasClassName & {
|
|
4
|
+
/** Menu content (MenuItem, ListDivider, ListSection…). */
|
|
5
|
+
children?: JSXElement;
|
|
6
|
+
/** The id of the menu container element (matches `aria-controls` on the trigger). */
|
|
7
|
+
id?: string;
|
|
8
|
+
/** The id of the element that labels the menu (auto-set to the trigger id by wrappers). */
|
|
9
|
+
'aria-labelledby'?: string;
|
|
10
|
+
/** ref to the root `<ul>` element. */
|
|
11
|
+
ref?: CommonRef;
|
|
12
|
+
};
|
|
13
|
+
export declare const COMPONENT_NAME = "Menu";
|
|
14
|
+
export declare const CLASSNAME: LumxClassName<typeof COMPONENT_NAME>;
|
|
15
|
+
/** MenuList core template. Wraps `List` with menu-specific class and ARIA labelling. */
|
|
16
|
+
export declare const MenuList: (props: MenuListProps) => import("react").JSX.Element;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { CommonRef, HasClassName, JSXElement, LumxClassName } from '../../types';
|
|
2
|
+
import type { Placement } from '../Popover/constants';
|
|
3
|
+
/** MenuPopover props. */
|
|
4
|
+
export interface MenuPopoverProps extends HasClassName {
|
|
5
|
+
/** Popover content (a `Menu`). */
|
|
6
|
+
children?: JSXElement;
|
|
7
|
+
/** Whether the popover is open. */
|
|
8
|
+
isOpen?: boolean;
|
|
9
|
+
/** Placement relative to the anchor. Defaults to `'bottom-start'`. */
|
|
10
|
+
placement?: Placement;
|
|
11
|
+
/** Reference to the anchor element. */
|
|
12
|
+
anchorRef?: CommonRef;
|
|
13
|
+
/** Callback invoked when the popover requests to close (click away, escape). */
|
|
14
|
+
handleClose?(): void;
|
|
15
|
+
/** Whether the popover should close when clicking outside. Default: true. */
|
|
16
|
+
closeOnClickAway?: boolean;
|
|
17
|
+
/** Whether the popover should close on Escape. Default: true. */
|
|
18
|
+
closeOnEscape?: boolean;
|
|
19
|
+
/** Whether to render in a portal. Default: false (avoid stacking-context surprises). */
|
|
20
|
+
usePortal?: boolean;
|
|
21
|
+
/** Whether to focus the anchor on close. Default: true. */
|
|
22
|
+
focusAnchorOnClose?: boolean;
|
|
23
|
+
}
|
|
24
|
+
/** Framework components injected by wrappers. */
|
|
25
|
+
export interface MenuPopoverComponents {
|
|
26
|
+
/** Popover component (framework-specific). */
|
|
27
|
+
Popover: any;
|
|
28
|
+
/** FlexBox component (framework-specific). */
|
|
29
|
+
FlexBox: any;
|
|
30
|
+
}
|
|
31
|
+
export declare const COMPONENT_NAME = "MenuPopover";
|
|
32
|
+
export declare const CLASSNAME: LumxClassName<typeof COMPONENT_NAME>;
|
|
33
|
+
/** MenuPopover core template. Renders a `Popover` with menu-friendly defaults. */
|
|
34
|
+
export declare const MenuPopover: (props: MenuPopoverProps, { Popover, FlexBox }: MenuPopoverComponents) => import("react").JSX.Element;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { CommonRef, HasClassName, JSXElement, LumxClassName } from '../../types';
|
|
2
|
+
/** MenuTrigger props. */
|
|
3
|
+
export interface MenuTriggerProps extends HasClassName {
|
|
4
|
+
/** Content of the trigger (label / icon / etc.). */
|
|
5
|
+
children?: JSXElement;
|
|
6
|
+
/** The id of the menu container (for `aria-controls`). */
|
|
7
|
+
menuId?: string;
|
|
8
|
+
/** Whether the menu is currently open. */
|
|
9
|
+
isOpen?: boolean;
|
|
10
|
+
/** ref to the trigger element. */
|
|
11
|
+
ref?: CommonRef;
|
|
12
|
+
}
|
|
13
|
+
/** Framework components injected by wrappers. */
|
|
14
|
+
export interface MenuTriggerComponents {
|
|
15
|
+
/** Component to render as the trigger (e.g. Button, IconButton, Chip, link). */
|
|
16
|
+
Trigger: any;
|
|
17
|
+
}
|
|
18
|
+
export declare const COMPONENT_NAME = "MenuTrigger";
|
|
19
|
+
export declare const CLASSNAME: LumxClassName<typeof COMPONENT_NAME>;
|
|
20
|
+
/** MenuTrigger core template. Renders `Trigger` with disclosure ARIA attributes. */
|
|
21
|
+
export declare const MenuTrigger: (props: MenuTriggerProps, { Trigger }: MenuTriggerComponents) => import("react").JSX.Element;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { MenuHandle, SetupMenuOptions } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Set up a disclosure-pattern menu handle.
|
|
4
|
+
*
|
|
5
|
+
* The trigger is registered separately via {@link MenuHandle.registerTrigger}
|
|
6
|
+
* once the trigger mounts. The menu container is registered separately via
|
|
7
|
+
* {@link MenuHandle.registerMenu} once on mount — the popover uses
|
|
8
|
+
* `closeMode="hide"` so the `<ul>` stays in the DOM at all times.
|
|
9
|
+
*
|
|
10
|
+
* IMPORTANT: this is the **disclosure widget** pattern, NOT WAI-ARIA's
|
|
11
|
+
* `role="menu"`. Items are native `<button>` / `<a>` elements with `tabindex="0"`,
|
|
12
|
+
* so screen readers announce them naturally and Tab navigates between them in
|
|
13
|
+
* DOM order. There is no focus trap.
|
|
14
|
+
*
|
|
15
|
+
* @param options Options (menuId for `aria-controls`).
|
|
16
|
+
* @returns A {@link MenuHandle} for interacting with the menu.
|
|
17
|
+
*/
|
|
18
|
+
export declare function setupMenu(options?: SetupMenuOptions): MenuHandle;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/** Map of menu event names to their payload types. */
|
|
2
|
+
export interface MenuEventMap {
|
|
3
|
+
/** Fired when the menu open state changes. Payload: whether the menu is open. */
|
|
4
|
+
open: boolean;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Handle returned by `setupMenu`. Used by framework wrappers to plug DOM elements
|
|
8
|
+
* into the menu state machine.
|
|
9
|
+
*/
|
|
10
|
+
export interface MenuHandle {
|
|
11
|
+
/** The trigger element, registered via `registerTrigger`. Null before registration. */
|
|
12
|
+
readonly trigger: HTMLElement | null;
|
|
13
|
+
/** Set the open state, update ARIA, fire `open` event. */
|
|
14
|
+
setOpen(open: boolean): void;
|
|
15
|
+
/**
|
|
16
|
+
* Register the trigger element. Wires click toggle, keyboard handlers (ArrowUp/Down
|
|
17
|
+
* to open with focus, trigger typeahead) and ARIA attributes. Returns a cleanup fn.
|
|
18
|
+
*/
|
|
19
|
+
registerTrigger(el: HTMLElement): () => void;
|
|
20
|
+
/**
|
|
21
|
+
* Register the menu container (`<ul>`). Wires keyboard handlers (arrow nav, typeahead,
|
|
22
|
+
* Tab-edge close, Escape). Always mounted (`closeMode="hide"`), so runs once.
|
|
23
|
+
* Returns a cleanup fn.
|
|
24
|
+
*/
|
|
25
|
+
registerMenu(el: HTMLElement): () => void;
|
|
26
|
+
/**
|
|
27
|
+
* Apply a focus request deferred while the menu was closed (trigger ArrowDown/Up,
|
|
28
|
+
* typeahead, or click-open). The wrapper must call this once items commit to the DOM.
|
|
29
|
+
*/
|
|
30
|
+
flushPendingNavigation(): void;
|
|
31
|
+
/** Subscribe to a menu event. Returns an unsubscribe function. */
|
|
32
|
+
subscribe<E extends keyof MenuEventMap>(event: E, listener: (value: MenuEventMap[E]) => void): () => void;
|
|
33
|
+
/** Tear down — removes all event listeners and clears state. */
|
|
34
|
+
destroy(): void;
|
|
35
|
+
}
|
|
36
|
+
/** Options for `setupMenu`. The wrapper sets the matching `id` on the `<ul>` itself. */
|
|
37
|
+
export interface SetupMenuOptions {
|
|
38
|
+
/** The id to advertise via `aria-controls` on the trigger. */
|
|
39
|
+
menuId?: string;
|
|
40
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CSS selector for menu items (inner focusable element). Includes disabled items: per
|
|
3
|
+
* WAI-ARIA APG they stay focusable so AT announces them; click is no-op'd via `aria-disabled`.
|
|
4
|
+
*/
|
|
5
|
+
export declare const MENU_ITEM_SELECTOR = "[data-menu-item]";
|
|
6
|
+
/** Get an item's text label (trimmed `textContent`) for typeahead matching. */
|
|
7
|
+
export declare function getItemText(item: HTMLElement): string;
|
|
8
|
+
/** Get all item elements within a menu container, in DOM order (includes disabled). */
|
|
9
|
+
export declare function getItems(menu: HTMLElement): HTMLElement[];
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Is the key a single printable character (not Space, no modifier keys)?
|
|
3
|
+
*
|
|
4
|
+
* Used by typeahead-style keyboard handlers (menu, combobox) to detect when a
|
|
5
|
+
* keypress should start/continue a type-to-search rather than trigger navigation.
|
|
6
|
+
*/
|
|
7
|
+
export declare function isPrintableKey({ key, altKey, ctrlKey, metaKey }: KeyboardEvent): boolean;
|
package/lumx.css
CHANGED
|
@@ -8983,6 +8983,16 @@ table {
|
|
|
8983
8983
|
background-color: var(--lumx-color-dark-L5);
|
|
8984
8984
|
}
|
|
8985
8985
|
|
|
8986
|
+
/* ==========================================================================
|
|
8987
|
+
Menu
|
|
8988
|
+
========================================================================== */
|
|
8989
|
+
.lumx-menu-popover {
|
|
8990
|
+
max-height: 50vh;
|
|
8991
|
+
}
|
|
8992
|
+
.lumx-menu-popover__scroll {
|
|
8993
|
+
overflow-y: auto;
|
|
8994
|
+
}
|
|
8995
|
+
|
|
8986
8996
|
/* ==========================================================================
|
|
8987
8997
|
Message
|
|
8988
8998
|
========================================================================== */
|
package/package.json
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
},
|
|
8
8
|
"dependencies": {
|
|
9
9
|
"@floating-ui/dom": "^1.7.5",
|
|
10
|
-
"@lumx/icons": "^4.
|
|
10
|
+
"@lumx/icons": "^4.17.0-next.0",
|
|
11
11
|
"classnames": "^2.3.2",
|
|
12
12
|
"focus-visible": "^5.0.2",
|
|
13
13
|
"lodash": "4.18.1",
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
"update-version-changelog": "yarn version-changelog ../../CHANGELOG.md"
|
|
67
67
|
},
|
|
68
68
|
"sideEffects": false,
|
|
69
|
-
"version": "4.
|
|
69
|
+
"version": "4.17.0-next.0",
|
|
70
70
|
"devDependencies": {
|
|
71
71
|
"@rollup/plugin-typescript": "^12.3.0",
|
|
72
72
|
"@testing-library/dom": "^10.4.1",
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
@import "./components/link/index";
|
|
27
27
|
@import "./components/link-preview/index";
|
|
28
28
|
@import "./components/list/index";
|
|
29
|
+
@import "./components/menu/index";
|
|
29
30
|
@import "./components/message/index";
|
|
30
31
|
@import "./components/mosaic/index";
|
|
31
32
|
@import "./components/navigation/index";
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/* ==========================================================================
|
|
2
|
+
Menu
|
|
3
|
+
========================================================================== */
|
|
4
|
+
|
|
5
|
+
.#{$lumx-base-prefix}-menu-popover {
|
|
6
|
+
max-height: 50vh;
|
|
7
|
+
|
|
8
|
+
&__scroll {
|
|
9
|
+
overflow-y: auto;
|
|
10
|
+
}
|
|
11
|
+
}
|