@dbcdk/react-components 0.0.206 → 0.0.208

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.
Files changed (31) hide show
  1. package/dist/client.cjs +10 -0
  2. package/dist/client.d.ts +5 -0
  3. package/dist/client.js +2 -0
  4. package/dist/components/lightbox/Lightbox.cjs +2 -22
  5. package/dist/components/lightbox/Lightbox.d.ts +2 -8
  6. package/dist/components/lightbox/Lightbox.js +2 -22
  7. package/dist/components/listbox/Listbox.cjs +153 -0
  8. package/dist/components/listbox/Listbox.d.ts +22 -0
  9. package/dist/components/listbox/Listbox.js +146 -0
  10. package/dist/components/listbox/Listbox.module.css +97 -0
  11. package/dist/components/menu/Menu.cjs +7 -24
  12. package/dist/components/menu/Menu.js +7 -24
  13. package/dist/components/menu/Menu.types.cjs +2 -0
  14. package/dist/components/menu/Menu.types.d.ts +19 -0
  15. package/dist/components/menu/Menu.types.js +1 -0
  16. package/dist/components/menu/MenuAction.cjs +61 -0
  17. package/dist/components/menu/MenuAction.d.ts +7 -0
  18. package/dist/components/menu/MenuAction.js +59 -0
  19. package/dist/components/status-text/StatusText.cjs +27 -0
  20. package/dist/components/status-text/StatusText.d.ts +7 -0
  21. package/dist/components/status-text/StatusText.js +21 -0
  22. package/dist/components/status-text/StatusText.module.css +41 -0
  23. package/dist/components/table/Table.types.d.ts +6 -9
  24. package/dist/components/table/components/TableRow.cjs +5 -26
  25. package/dist/components/table/components/TableRow.js +5 -26
  26. package/dist/hooks/useRovingFocus.cjs +45 -0
  27. package/dist/hooks/useRovingFocus.d.ts +10 -0
  28. package/dist/hooks/useRovingFocus.js +43 -0
  29. package/dist/index.css +2 -0
  30. package/dist/index.d.ts +1 -0
  31. package/package.json +1 -1
package/dist/client.cjs CHANGED
@@ -8,6 +8,7 @@ var NavBar = require('./components/nav-bar/NavBar');
8
8
  var Avatar = require('./components/avatar/Avatar');
9
9
  var Popover = require('./components/popover/Popover');
10
10
  var Menu = require('./components/menu/Menu');
11
+ var Listbox = require('./components/listbox/Listbox');
11
12
  var Icon = require('./components/icon/Icon');
12
13
  var UserDisplay = require('./components/user-display/UserDisplay');
13
14
  var Tabs = require('./components/tabs/Tabs');
@@ -24,6 +25,7 @@ var useTheme = require('./hooks/useTheme');
24
25
  var usePersistentState = require('./hooks/usePersistentState');
25
26
  var useTreeViewSettings = require('./hooks/useTreeViewSettings');
26
27
  var Chip = require('./components/chip/Chip');
28
+ var StatusText = require('./components/status-text/StatusText');
27
29
  var Panel = require('./components/panel/Panel');
28
30
  var Card = require('./components/card/Card');
29
31
  var PrintFriendlyCard = require('./components/print-friendly/PrintFriendlyCard');
@@ -136,6 +138,10 @@ Object.defineProperty(exports, "Menu", {
136
138
  enumerable: true,
137
139
  get: function () { return Menu.Menu; }
138
140
  });
141
+ Object.defineProperty(exports, "Listbox", {
142
+ enumerable: true,
143
+ get: function () { return Listbox.Listbox; }
144
+ });
139
145
  Object.defineProperty(exports, "Icon", {
140
146
  enumerable: true,
141
147
  get: function () { return Icon.Icon; }
@@ -208,6 +214,10 @@ Object.defineProperty(exports, "Chip", {
208
214
  enumerable: true,
209
215
  get: function () { return Chip.Chip; }
210
216
  });
217
+ Object.defineProperty(exports, "StatusText", {
218
+ enumerable: true,
219
+ get: function () { return StatusText.StatusText; }
220
+ });
211
221
  Object.defineProperty(exports, "Panel", {
212
222
  enumerable: true,
213
223
  get: function () { return Panel.Panel; }
package/dist/client.d.ts CHANGED
@@ -11,6 +11,8 @@ export { Popover } from './components/popover/Popover';
11
11
  export type { PopoverProps, PopoverHandle } from './components/popover/Popover';
12
12
  export { Menu } from './components/menu/Menu';
13
13
  export type { MenuProps, MenuLabelProps, MenuSeparatorProps, MenuHeaderProps, MenuDescriptionProps, MenuItemProps, MenuCheckItemProps, MenuRadioItemProps, MenuSwitchItemProps, } from './components/menu/Menu';
14
+ export { Listbox } from './components/listbox/Listbox';
15
+ export type { ListboxOption, ListboxProps } from './components/listbox/Listbox';
14
16
  export { Icon, SeverityIcon } from './components/icon/Icon';
15
17
  export { UserDisplay } from './components/user-display/UserDisplay';
16
18
  export { Tabs } from './components/tabs/Tabs';
@@ -40,6 +42,8 @@ export type { PersistentStateStorage, PersistentStateHydration, UsePersistentSta
40
42
  export { useTreeViewSettings, localStorageTreeViewSettingsStorage, } from './hooks/useTreeViewSettings';
41
43
  export type { TreeViewSettingsStorage, UseTreeViewSettingsOptions, UseTreeViewSettingsResult, } from './hooks/useTreeViewSettings';
42
44
  export { Chip } from './components/chip/Chip';
45
+ export { StatusText } from './components/status-text/StatusText';
46
+ export type { StatusTextProps } from './components/status-text/StatusText';
43
47
  export { Panel } from './components/panel/Panel';
44
48
  export { Card } from './components/card/Card';
45
49
  export type { CardProps } from './components/card/Card';
@@ -177,3 +181,4 @@ export { FileUpload } from './components/forms/file-upload/FileUpload';
177
181
  export type { FileUploadProps, FileUploadHandle } from './components/forms/file-upload/FileUpload';
178
182
  export { HighlightedText } from './components/highlighted-text/HighlightedText';
179
183
  export type { HighlightedTextProps } from './components/highlighted-text/HighlightedText';
184
+ export type { MenuActionItem, MenuButtonItem, MenuLinkItem } from './components/menu/Menu.types';
package/dist/client.js CHANGED
@@ -6,6 +6,7 @@ export { NavBar } from './components/nav-bar/NavBar';
6
6
  export { Avatar } from './components/avatar/Avatar';
7
7
  export { Popover } from './components/popover/Popover';
8
8
  export { Menu } from './components/menu/Menu';
9
+ export { Listbox } from './components/listbox/Listbox';
9
10
  export { Icon, SeverityIcon } from './components/icon/Icon';
10
11
  export { UserDisplay } from './components/user-display/UserDisplay';
11
12
  export { Tabs } from './components/tabs/Tabs';
@@ -22,6 +23,7 @@ export { useTheme } from './hooks/useTheme';
22
23
  export { usePersistentState } from './hooks/usePersistentState';
23
24
  export { localStorageTreeViewSettingsStorage, useTreeViewSettings } from './hooks/useTreeViewSettings';
24
25
  export { Chip } from './components/chip/Chip';
26
+ export { StatusText } from './components/status-text/StatusText';
25
27
  export { Panel } from './components/panel/Panel';
26
28
  export { Card } from './components/card/Card';
27
29
  export { PrintFriendlyCard } from './components/print-friendly/PrintFriendlyCard';
@@ -9,6 +9,7 @@ var Popover = require('../../components/popover/Popover');
9
9
  var styles = require('./Lightbox.module.css');
10
10
  var Button = require('../button/Button');
11
11
  var Menu = require('../menu/Menu');
12
+ var MenuAction = require('../menu/MenuAction');
12
13
 
13
14
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
14
15
 
@@ -262,28 +263,7 @@ function ThumbnailContextMenu({
262
263
  }
263
264
  );
264
265
  },
265
- children: (close) => /* @__PURE__ */ jsxRuntime.jsx(Menu.Menu, { children: menuItems.map((menuItem, menuItemIndex) => /* @__PURE__ */ jsxRuntime.jsx(
266
- Menu.Menu.Item,
267
- {
268
- active: menuItem.active,
269
- selected: menuItem.selected,
270
- disabled: menuItem.disabled,
271
- variant: menuItem.variant,
272
- children: /* @__PURE__ */ jsxRuntime.jsx(
273
- "button",
274
- {
275
- type: "button",
276
- onClick: (e) => {
277
- e.stopPropagation();
278
- menuItem.onClick();
279
- close();
280
- },
281
- children: menuItem.label
282
- }
283
- )
284
- },
285
- menuItemIndex
286
- )) })
266
+ children: (close) => /* @__PURE__ */ jsxRuntime.jsx(Menu.Menu, { children: menuItems.map((menuItem, menuItemIndex) => /* @__PURE__ */ jsxRuntime.jsx(MenuAction.MenuAction, { item: menuItem, onActivate: close }, menuItemIndex)) })
287
267
  }
288
268
  );
289
269
  }
@@ -1,5 +1,6 @@
1
1
  import type { ReactElement, ReactNode } from 'react';
2
2
  import { type MetaItem } from '../../components/meta-bar/MetaBar';
3
+ import type { MenuActionItem } from '../menu/Menu.types';
3
4
  export type LightboxItem = {
4
5
  thumbnail: ReactNode;
5
6
  preview?: ReactNode;
@@ -10,14 +11,7 @@ export type LightboxItem = {
10
11
  height?: number;
11
12
  metaAddition?: MetaItem[];
12
13
  };
13
- export type LightboxContextMenuItem = {
14
- label: ReactNode;
15
- onClick: () => void;
16
- active?: boolean;
17
- selected?: boolean;
18
- disabled?: boolean;
19
- variant?: 'default' | 'danger';
20
- };
14
+ export type LightboxContextMenuItem = MenuActionItem;
21
15
  export interface LightboxProps {
22
16
  items: LightboxItem[];
23
17
  title?: string;
@@ -7,6 +7,7 @@ import { Popover } from '../../components/popover/Popover';
7
7
  import styles from './Lightbox.module.css';
8
8
  import { Button } from '../button/Button';
9
9
  import { Menu } from '../menu/Menu';
10
+ import { MenuAction } from '../menu/MenuAction';
10
11
 
11
12
  function extFromMime(mime) {
12
13
  var _a;
@@ -256,28 +257,7 @@ function ThumbnailContextMenu({
256
257
  }
257
258
  );
258
259
  },
259
- children: (close) => /* @__PURE__ */ jsx(Menu, { children: menuItems.map((menuItem, menuItemIndex) => /* @__PURE__ */ jsx(
260
- Menu.Item,
261
- {
262
- active: menuItem.active,
263
- selected: menuItem.selected,
264
- disabled: menuItem.disabled,
265
- variant: menuItem.variant,
266
- children: /* @__PURE__ */ jsx(
267
- "button",
268
- {
269
- type: "button",
270
- onClick: (e) => {
271
- e.stopPropagation();
272
- menuItem.onClick();
273
- close();
274
- },
275
- children: menuItem.label
276
- }
277
- )
278
- },
279
- menuItemIndex
280
- )) })
260
+ children: (close) => /* @__PURE__ */ jsx(Menu, { children: menuItems.map((menuItem, menuItemIndex) => /* @__PURE__ */ jsx(MenuAction, { item: menuItem, onActivate: close }, menuItemIndex)) })
281
261
  }
282
262
  );
283
263
  }
@@ -0,0 +1,153 @@
1
+ 'use client';
2
+ 'use strict';
3
+
4
+ var jsxRuntime = require('react/jsx-runtime');
5
+ var react = require('react');
6
+ var useRovingFocus = require('../../hooks/useRovingFocus');
7
+ var styles = require('./Listbox.module.css');
8
+ var menuStyles = require('../menu/Menu.module.css');
9
+
10
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
11
+
12
+ var styles__default = /*#__PURE__*/_interopDefault(styles);
13
+ var menuStyles__default = /*#__PURE__*/_interopDefault(menuStyles);
14
+
15
+ function Listbox({
16
+ options,
17
+ selectedValues,
18
+ selectionMode = "single",
19
+ autoFocusValue,
20
+ onOptionSelect,
21
+ onActiveChange,
22
+ renderAction,
23
+ actionLabel,
24
+ className,
25
+ style,
26
+ "aria-label": ariaLabel,
27
+ ...rootProps
28
+ }) {
29
+ const rootRef = react.useRef(null);
30
+ const optionRefs = react.useRef(/* @__PURE__ */ new Map());
31
+ const [activeValue, setActiveValue] = react.useState(() => {
32
+ var _a, _b;
33
+ const activeOption = (_b = (_a = options.find((option) => option.value === autoFocusValue && !option.disabled)) != null ? _a : options.find((option) => selectedValues.includes(option.value) && !option.disabled)) != null ? _b : options.find((option) => !option.disabled);
34
+ return activeOption == null ? void 0 : activeOption.value;
35
+ });
36
+ react.useEffect(() => {
37
+ var _a;
38
+ if (autoFocusValue !== void 0) (_a = optionRefs.current.get(autoFocusValue)) == null ? void 0 : _a.focus();
39
+ }, [autoFocusValue]);
40
+ const handleNavigate = react.useCallback(
41
+ (item) => {
42
+ var _a;
43
+ const value = (_a = options.find(
44
+ (option) => String(option.value) === item.dataset.listboxValue
45
+ )) == null ? void 0 : _a.value;
46
+ if (value === void 0) return;
47
+ setActiveValue(value);
48
+ onActiveChange == null ? void 0 : onActiveChange(value);
49
+ },
50
+ [onActiveChange, options]
51
+ );
52
+ const handleKeyDown = useRovingFocus.useRovingFocus({
53
+ containerRef: rootRef,
54
+ itemSelector: '[role="option"]:not([aria-disabled="true"])',
55
+ loop: true,
56
+ resolveCurrentItem: (focused, items) => {
57
+ var _a;
58
+ const value = (_a = focused.closest("[data-listbox-value]")) == null ? void 0 : _a.dataset.listboxValue;
59
+ return items.find((item) => item.dataset.listboxValue === value);
60
+ },
61
+ onNavigate: handleNavigate
62
+ });
63
+ const rowCount = options.length;
64
+ const rowStyle = rowCount ? `repeat(${rowCount}, auto)` : "none";
65
+ return /* @__PURE__ */ jsxRuntime.jsxs(
66
+ "div",
67
+ {
68
+ ...rootProps,
69
+ ref: rootRef,
70
+ className: [styles__default.default.root, className].filter(Boolean).join(" "),
71
+ style: { ...style, gridTemplateRows: rowStyle },
72
+ onKeyDown: (event) => {
73
+ var _a;
74
+ handleKeyDown(event);
75
+ (_a = rootProps.onKeyDown) == null ? void 0 : _a.call(rootProps, event);
76
+ },
77
+ children: [
78
+ /* @__PURE__ */ jsxRuntime.jsx(
79
+ "div",
80
+ {
81
+ className: styles__default.default.options,
82
+ role: "listbox",
83
+ "aria-label": ariaLabel,
84
+ "aria-multiselectable": selectionMode === "multiple" || void 0,
85
+ children: options.map((option) => {
86
+ const isSelected = selectedValues.includes(option.value);
87
+ const isActive = activeValue === option.value;
88
+ const value = String(option.value);
89
+ return /* @__PURE__ */ jsxRuntime.jsx(
90
+ "button",
91
+ {
92
+ ref: (element) => {
93
+ if (element) optionRefs.current.set(option.value, element);
94
+ else optionRefs.current.delete(option.value);
95
+ },
96
+ type: "button",
97
+ role: "option",
98
+ "aria-selected": isSelected,
99
+ "aria-disabled": option.disabled || void 0,
100
+ tabIndex: !option.disabled && isActive ? 0 : -1,
101
+ "data-listbox-value": value,
102
+ className: [
103
+ menuStyles__default.default.interactive,
104
+ menuStyles__default.default.item,
105
+ isSelected ? menuStyles__default.default.selected : "",
106
+ isActive ? menuStyles__default.default.active : "",
107
+ styles__default.default.option,
108
+ option.status === "success" ? styles__default.default.optionSuccess : "",
109
+ option.status === "info" ? styles__default.default.optionInfo : "",
110
+ option.className
111
+ ].filter(Boolean).join(" "),
112
+ disabled: option.disabled,
113
+ onFocus: () => setActiveValue(option.value),
114
+ onClick: () => {
115
+ setActiveValue(option.value);
116
+ onOptionSelect(option.value);
117
+ },
118
+ children: /* @__PURE__ */ jsxRuntime.jsxs("span", { className: styles__default.default.optionContent, children: [
119
+ option.icon ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: styles__default.default.icon, "aria-hidden": "true", children: option.icon }) : null,
120
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: styles__default.default.text, children: [
121
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: styles__default.default.label, children: option.label }),
122
+ option.description ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: styles__default.default.description, children: option.description }) : null
123
+ ] })
124
+ ] })
125
+ },
126
+ value
127
+ );
128
+ })
129
+ }
130
+ ),
131
+ renderAction ? /* @__PURE__ */ jsxRuntime.jsx(
132
+ "div",
133
+ {
134
+ className: styles__default.default.actions,
135
+ role: "group",
136
+ "aria-label": actionLabel != null ? actionLabel : `${ariaLabel} actions`,
137
+ children: options.map((option) => /* @__PURE__ */ jsxRuntime.jsx(
138
+ "div",
139
+ {
140
+ className: styles__default.default.actionRow,
141
+ "data-listbox-value": String(option.value),
142
+ children: renderAction(option, selectedValues.includes(option.value))
143
+ },
144
+ String(option.value)
145
+ ))
146
+ }
147
+ ) : null
148
+ ]
149
+ }
150
+ );
151
+ }
152
+
153
+ exports.Listbox = Listbox;
@@ -0,0 +1,22 @@
1
+ import { type HTMLAttributes, type ReactNode } from 'react';
2
+ export type ListboxOption<T extends string | number = string> = {
3
+ value: T;
4
+ label: ReactNode;
5
+ description?: ReactNode;
6
+ icon?: ReactNode;
7
+ className?: string;
8
+ status?: 'info' | 'success';
9
+ disabled?: boolean;
10
+ };
11
+ export type ListboxProps<T extends string | number = string> = Omit<HTMLAttributes<HTMLDivElement>, 'children' | 'onSelect' | 'aria-label'> & {
12
+ 'aria-label': string;
13
+ actionLabel?: string;
14
+ options: readonly ListboxOption<T>[];
15
+ selectedValues: readonly T[];
16
+ selectionMode?: 'single' | 'multiple';
17
+ autoFocusValue?: T;
18
+ onOptionSelect: (value: T) => void;
19
+ onActiveChange?: (value: T) => void;
20
+ renderAction?: (option: ListboxOption<T>, selected: boolean) => ReactNode;
21
+ };
22
+ export declare function Listbox<T extends string | number = string>({ options, selectedValues, selectionMode, autoFocusValue, onOptionSelect, onActiveChange, renderAction, actionLabel, className, style, 'aria-label': ariaLabel, ...rootProps }: Readonly<ListboxProps<T>>): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,146 @@
1
+ 'use client';
2
+ import { jsxs, jsx } from 'react/jsx-runtime';
3
+ import { useRef, useState, useEffect, useCallback } from 'react';
4
+ import { useRovingFocus } from '../../hooks/useRovingFocus';
5
+ import styles from './Listbox.module.css';
6
+ import menuStyles from '../menu/Menu.module.css';
7
+
8
+ function Listbox({
9
+ options,
10
+ selectedValues,
11
+ selectionMode = "single",
12
+ autoFocusValue,
13
+ onOptionSelect,
14
+ onActiveChange,
15
+ renderAction,
16
+ actionLabel,
17
+ className,
18
+ style,
19
+ "aria-label": ariaLabel,
20
+ ...rootProps
21
+ }) {
22
+ const rootRef = useRef(null);
23
+ const optionRefs = useRef(/* @__PURE__ */ new Map());
24
+ const [activeValue, setActiveValue] = useState(() => {
25
+ var _a, _b;
26
+ const activeOption = (_b = (_a = options.find((option) => option.value === autoFocusValue && !option.disabled)) != null ? _a : options.find((option) => selectedValues.includes(option.value) && !option.disabled)) != null ? _b : options.find((option) => !option.disabled);
27
+ return activeOption == null ? void 0 : activeOption.value;
28
+ });
29
+ useEffect(() => {
30
+ var _a;
31
+ if (autoFocusValue !== void 0) (_a = optionRefs.current.get(autoFocusValue)) == null ? void 0 : _a.focus();
32
+ }, [autoFocusValue]);
33
+ const handleNavigate = useCallback(
34
+ (item) => {
35
+ var _a;
36
+ const value = (_a = options.find(
37
+ (option) => String(option.value) === item.dataset.listboxValue
38
+ )) == null ? void 0 : _a.value;
39
+ if (value === void 0) return;
40
+ setActiveValue(value);
41
+ onActiveChange == null ? void 0 : onActiveChange(value);
42
+ },
43
+ [onActiveChange, options]
44
+ );
45
+ const handleKeyDown = useRovingFocus({
46
+ containerRef: rootRef,
47
+ itemSelector: '[role="option"]:not([aria-disabled="true"])',
48
+ loop: true,
49
+ resolveCurrentItem: (focused, items) => {
50
+ var _a;
51
+ const value = (_a = focused.closest("[data-listbox-value]")) == null ? void 0 : _a.dataset.listboxValue;
52
+ return items.find((item) => item.dataset.listboxValue === value);
53
+ },
54
+ onNavigate: handleNavigate
55
+ });
56
+ const rowCount = options.length;
57
+ const rowStyle = rowCount ? `repeat(${rowCount}, auto)` : "none";
58
+ return /* @__PURE__ */ jsxs(
59
+ "div",
60
+ {
61
+ ...rootProps,
62
+ ref: rootRef,
63
+ className: [styles.root, className].filter(Boolean).join(" "),
64
+ style: { ...style, gridTemplateRows: rowStyle },
65
+ onKeyDown: (event) => {
66
+ var _a;
67
+ handleKeyDown(event);
68
+ (_a = rootProps.onKeyDown) == null ? void 0 : _a.call(rootProps, event);
69
+ },
70
+ children: [
71
+ /* @__PURE__ */ jsx(
72
+ "div",
73
+ {
74
+ className: styles.options,
75
+ role: "listbox",
76
+ "aria-label": ariaLabel,
77
+ "aria-multiselectable": selectionMode === "multiple" || void 0,
78
+ children: options.map((option) => {
79
+ const isSelected = selectedValues.includes(option.value);
80
+ const isActive = activeValue === option.value;
81
+ const value = String(option.value);
82
+ return /* @__PURE__ */ jsx(
83
+ "button",
84
+ {
85
+ ref: (element) => {
86
+ if (element) optionRefs.current.set(option.value, element);
87
+ else optionRefs.current.delete(option.value);
88
+ },
89
+ type: "button",
90
+ role: "option",
91
+ "aria-selected": isSelected,
92
+ "aria-disabled": option.disabled || void 0,
93
+ tabIndex: !option.disabled && isActive ? 0 : -1,
94
+ "data-listbox-value": value,
95
+ className: [
96
+ menuStyles.interactive,
97
+ menuStyles.item,
98
+ isSelected ? menuStyles.selected : "",
99
+ isActive ? menuStyles.active : "",
100
+ styles.option,
101
+ option.status === "success" ? styles.optionSuccess : "",
102
+ option.status === "info" ? styles.optionInfo : "",
103
+ option.className
104
+ ].filter(Boolean).join(" "),
105
+ disabled: option.disabled,
106
+ onFocus: () => setActiveValue(option.value),
107
+ onClick: () => {
108
+ setActiveValue(option.value);
109
+ onOptionSelect(option.value);
110
+ },
111
+ children: /* @__PURE__ */ jsxs("span", { className: styles.optionContent, children: [
112
+ option.icon ? /* @__PURE__ */ jsx("span", { className: styles.icon, "aria-hidden": "true", children: option.icon }) : null,
113
+ /* @__PURE__ */ jsxs("span", { className: styles.text, children: [
114
+ /* @__PURE__ */ jsx("span", { className: styles.label, children: option.label }),
115
+ option.description ? /* @__PURE__ */ jsx("span", { className: styles.description, children: option.description }) : null
116
+ ] })
117
+ ] })
118
+ },
119
+ value
120
+ );
121
+ })
122
+ }
123
+ ),
124
+ renderAction ? /* @__PURE__ */ jsx(
125
+ "div",
126
+ {
127
+ className: styles.actions,
128
+ role: "group",
129
+ "aria-label": actionLabel != null ? actionLabel : `${ariaLabel} actions`,
130
+ children: options.map((option) => /* @__PURE__ */ jsx(
131
+ "div",
132
+ {
133
+ className: styles.actionRow,
134
+ "data-listbox-value": String(option.value),
135
+ children: renderAction(option, selectedValues.includes(option.value))
136
+ },
137
+ String(option.value)
138
+ ))
139
+ }
140
+ ) : null
141
+ ]
142
+ }
143
+ );
144
+ }
145
+
146
+ export { Listbox };
@@ -0,0 +1,97 @@
1
+ .root {
2
+ display: grid;
3
+ grid-template-columns: minmax(0, 1fr) auto;
4
+ column-gap: var(--spacing-xxs);
5
+ }
6
+
7
+ .options,
8
+ .actions {
9
+ display: grid;
10
+ grid-template-rows: subgrid;
11
+ grid-row: 1 / -1;
12
+ }
13
+
14
+ .options {
15
+ grid-column: 1;
16
+ min-width: 0;
17
+ }
18
+
19
+ .actions {
20
+ grid-column: 2;
21
+ }
22
+
23
+ .option {
24
+ align-items: center;
25
+ min-width: 0;
26
+ }
27
+
28
+ .optionContent {
29
+ display: flex;
30
+ align-items: center;
31
+ gap: var(--spacing-sm);
32
+ width: 100%;
33
+ min-width: 0;
34
+ }
35
+
36
+ .icon {
37
+ display: inline-flex;
38
+ flex: 0 0 auto;
39
+ align-items: center;
40
+ justify-content: center;
41
+ color: var(--color-fg-muted);
42
+ }
43
+
44
+ .icon svg {
45
+ width: var(--icon-size-md);
46
+ height: var(--icon-size-md);
47
+ margin: 0;
48
+ }
49
+
50
+ .text {
51
+ display: flex;
52
+ flex: 1 1 auto;
53
+ flex-direction: column;
54
+ min-width: 0;
55
+ }
56
+
57
+ .label,
58
+ .description {
59
+ min-width: 0;
60
+ overflow-wrap: anywhere;
61
+ }
62
+
63
+ .description {
64
+ color: var(--color-fg-muted);
65
+ font-size: var(--font-size-sm);
66
+ }
67
+
68
+ .option.optionSuccess[aria-selected='true'],
69
+ .option.optionSuccess.active {
70
+ background: var(--color-status-success-bg);
71
+ color: var(--color-status-success-fg);
72
+ box-shadow: inset 0 0 0 1px var(--color-status-success-border);
73
+ }
74
+
75
+ .option.optionSuccess .icon,
76
+ .option.optionSuccess .description {
77
+ color: var(--color-status-success-fg);
78
+ }
79
+
80
+ .option.optionInfo[aria-selected='true'],
81
+ .option.optionInfo.active {
82
+ background: var(--color-status-info-bg);
83
+ color: var(--color-status-info-fg);
84
+ box-shadow: inset 0 0 0 1px var(--color-status-info-border);
85
+ }
86
+
87
+ .option.optionInfo .icon,
88
+ .option.optionInfo .description {
89
+ color: var(--color-status-info-fg);
90
+ }
91
+
92
+ .actionRow {
93
+ display: flex;
94
+ align-items: center;
95
+ justify-content: center;
96
+ padding-inline-start: var(--spacing-xxs);
97
+ }
@@ -3,6 +3,7 @@
3
3
 
4
4
  var jsxRuntime = require('react/jsx-runtime');
5
5
  var React = require('react');
6
+ var useRovingFocus = require('../../hooks/useRovingFocus');
6
7
  var styles = require('./Menu.module.css');
7
8
  var Checkbox = require('../forms/checkbox/Checkbox');
8
9
  var RadioButton = require('../forms/radio-buttons/RadioButton');
@@ -31,34 +32,16 @@ function _interopNamespace(e) {
31
32
  var React__namespace = /*#__PURE__*/_interopNamespace(React);
32
33
  var styles__default = /*#__PURE__*/_interopDefault(styles);
33
34
 
34
- const INTERACTIVE_SELECTOR = 'a:not([disabled]), button:not([disabled]), [tabindex]:not([tabindex="-1"]):not([aria-disabled="true"]), [role="menuitem"]:not([aria-disabled="true"]), [role="menuitemradio"]:not([aria-disabled="true"]), [role="menuitemcheckbox"]:not([aria-disabled="true"]), [role="option"]:not([aria-disabled="true"])';
35
- function getMenuItems(el) {
36
- return Array.from(el.querySelectorAll(INTERACTIVE_SELECTOR));
37
- }
38
35
  const MenuBase = React__namespace.forwardRef(
39
36
  ({ children, className, itemRole = "menuitem", gap, onKeyDown, ...props }, ref) => {
40
37
  const internalRef = React__namespace.useRef(null);
38
+ const handleRovingFocus = useRovingFocus.useRovingFocus({
39
+ containerRef: internalRef,
40
+ itemSelector: 'a[href]:not([aria-disabled="true"]), button:not([disabled]), [tabindex]:not([tabindex="-1"]):not([aria-disabled="true"]), [role="menuitem"]:not([aria-disabled="true"]), [role="menuitemradio"]:not([aria-disabled="true"]), [role="menuitemcheckbox"]:not([aria-disabled="true"]), [role="option"]:not([aria-disabled="true"])',
41
+ loop: true
42
+ });
41
43
  const handleKeyDown = (e) => {
42
- const ul = internalRef.current;
43
- if (!ul) return;
44
- const items = getMenuItems(ul);
45
- if (items.length === 0) return;
46
- const focused = document.activeElement;
47
- const currentIndex = items.indexOf(focused);
48
- let nextIndex = null;
49
- if (e.key === "ArrowDown") {
50
- nextIndex = currentIndex < items.length - 1 ? currentIndex + 1 : 0;
51
- } else if (e.key === "ArrowUp") {
52
- nextIndex = currentIndex > 0 ? currentIndex - 1 : items.length - 1;
53
- } else if (e.key === "Home") {
54
- nextIndex = 0;
55
- } else if (e.key === "End") {
56
- nextIndex = items.length - 1;
57
- }
58
- if (nextIndex !== null) {
59
- e.preventDefault();
60
- items[nextIndex].focus();
61
- }
44
+ handleRovingFocus(e);
62
45
  onKeyDown == null ? void 0 : onKeyDown(e);
63
46
  };
64
47
  return /* @__PURE__ */ jsxRuntime.jsx(
@@ -1,39 +1,22 @@
1
1
  'use client';
2
2
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
3
3
  import * as React from 'react';
4
+ import { useRovingFocus } from '../../hooks/useRovingFocus';
4
5
  import styles from './Menu.module.css';
5
6
  import { Checkbox } from '../forms/checkbox/Checkbox';
6
7
  import { RadioButton } from '../forms/radio-buttons/RadioButton';
7
8
  import { Switch } from '../forms/switch/Switch';
8
9
 
9
- const INTERACTIVE_SELECTOR = 'a:not([disabled]), button:not([disabled]), [tabindex]:not([tabindex="-1"]):not([aria-disabled="true"]), [role="menuitem"]:not([aria-disabled="true"]), [role="menuitemradio"]:not([aria-disabled="true"]), [role="menuitemcheckbox"]:not([aria-disabled="true"]), [role="option"]:not([aria-disabled="true"])';
10
- function getMenuItems(el) {
11
- return Array.from(el.querySelectorAll(INTERACTIVE_SELECTOR));
12
- }
13
10
  const MenuBase = React.forwardRef(
14
11
  ({ children, className, itemRole = "menuitem", gap, onKeyDown, ...props }, ref) => {
15
12
  const internalRef = React.useRef(null);
13
+ const handleRovingFocus = useRovingFocus({
14
+ containerRef: internalRef,
15
+ itemSelector: 'a[href]:not([aria-disabled="true"]), button:not([disabled]), [tabindex]:not([tabindex="-1"]):not([aria-disabled="true"]), [role="menuitem"]:not([aria-disabled="true"]), [role="menuitemradio"]:not([aria-disabled="true"]), [role="menuitemcheckbox"]:not([aria-disabled="true"]), [role="option"]:not([aria-disabled="true"])',
16
+ loop: true
17
+ });
16
18
  const handleKeyDown = (e) => {
17
- const ul = internalRef.current;
18
- if (!ul) return;
19
- const items = getMenuItems(ul);
20
- if (items.length === 0) return;
21
- const focused = document.activeElement;
22
- const currentIndex = items.indexOf(focused);
23
- let nextIndex = null;
24
- if (e.key === "ArrowDown") {
25
- nextIndex = currentIndex < items.length - 1 ? currentIndex + 1 : 0;
26
- } else if (e.key === "ArrowUp") {
27
- nextIndex = currentIndex > 0 ? currentIndex - 1 : items.length - 1;
28
- } else if (e.key === "Home") {
29
- nextIndex = 0;
30
- } else if (e.key === "End") {
31
- nextIndex = items.length - 1;
32
- }
33
- if (nextIndex !== null) {
34
- e.preventDefault();
35
- items[nextIndex].focus();
36
- }
19
+ handleRovingFocus(e);
37
20
  onKeyDown == null ? void 0 : onKeyDown(e);
38
21
  };
39
22
  return /* @__PURE__ */ jsx(
@@ -0,0 +1,2 @@
1
+ 'use strict';
2
+
@@ -0,0 +1,19 @@
1
+ import type { AnchorHTMLAttributes, ReactNode } from 'react';
2
+ type MenuItemBase = {
3
+ label: ReactNode;
4
+ icon?: ReactNode;
5
+ active?: boolean;
6
+ selected?: boolean;
7
+ disabled?: boolean;
8
+ variant?: 'default' | 'danger';
9
+ };
10
+ export type MenuButtonItem = MenuItemBase & {
11
+ href?: undefined;
12
+ onClick: () => void;
13
+ };
14
+ export type MenuLinkItem = MenuItemBase & Pick<AnchorHTMLAttributes<HTMLAnchorElement>, 'download' | 'target' | 'rel'> & {
15
+ href: string;
16
+ onClick?: () => void;
17
+ };
18
+ export type MenuActionItem = MenuButtonItem | MenuLinkItem;
19
+ export {};
@@ -0,0 +1 @@
1
+
@@ -0,0 +1,61 @@
1
+ 'use client';
2
+ 'use strict';
3
+
4
+ var jsxRuntime = require('react/jsx-runtime');
5
+ var Menu = require('./Menu');
6
+
7
+ function MenuAction({
8
+ item,
9
+ onActivate,
10
+ children
11
+ }) {
12
+ var _a;
13
+ function handleClick(event) {
14
+ var _a2;
15
+ event.stopPropagation();
16
+ if (item.disabled) {
17
+ event.preventDefault();
18
+ return;
19
+ }
20
+ (_a2 = item.onClick) == null ? void 0 : _a2.call(item);
21
+ onActivate();
22
+ }
23
+ function handleKeyDown(event) {
24
+ if (event.key !== "Enter" && event.key !== " ") return;
25
+ event.stopPropagation();
26
+ if (item.href !== void 0) {
27
+ if (event.key === "Enter" && (event.ctrlKey || event.metaKey || event.shiftKey || event.altKey))
28
+ return;
29
+ event.preventDefault();
30
+ if (!item.disabled && !event.repeat) event.currentTarget.click();
31
+ }
32
+ }
33
+ const content = children != null ? children : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
34
+ item.icon,
35
+ item.label
36
+ ] });
37
+ return /* @__PURE__ */ jsxRuntime.jsx(
38
+ Menu.Menu.Item,
39
+ {
40
+ active: item.active,
41
+ selected: item.selected,
42
+ disabled: item.disabled,
43
+ variant: item.variant,
44
+ children: item.href !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx(
45
+ "a",
46
+ {
47
+ href: item.disabled ? void 0 : item.href,
48
+ download: item.download,
49
+ target: item.target,
50
+ rel: (_a = item.rel) != null ? _a : item.target === "_blank" ? "noopener noreferrer" : void 0,
51
+ tabIndex: item.disabled ? -1 : 0,
52
+ onClick: handleClick,
53
+ onKeyDown: handleKeyDown,
54
+ children: content
55
+ }
56
+ ) : /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", onClick: handleClick, onKeyDown: handleKeyDown, children: content })
57
+ }
58
+ );
59
+ }
60
+
61
+ exports.MenuAction = MenuAction;
@@ -0,0 +1,7 @@
1
+ import type { ReactElement, ReactNode } from 'react';
2
+ import type { MenuActionItem } from './Menu.types';
3
+ export declare function MenuAction({ item, onActivate, children, }: {
4
+ item: MenuActionItem;
5
+ onActivate: () => void;
6
+ children?: ReactNode;
7
+ }): ReactElement;
@@ -0,0 +1,59 @@
1
+ 'use client';
2
+ import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
3
+ import { Menu } from './Menu';
4
+
5
+ function MenuAction({
6
+ item,
7
+ onActivate,
8
+ children
9
+ }) {
10
+ var _a;
11
+ function handleClick(event) {
12
+ var _a2;
13
+ event.stopPropagation();
14
+ if (item.disabled) {
15
+ event.preventDefault();
16
+ return;
17
+ }
18
+ (_a2 = item.onClick) == null ? void 0 : _a2.call(item);
19
+ onActivate();
20
+ }
21
+ function handleKeyDown(event) {
22
+ if (event.key !== "Enter" && event.key !== " ") return;
23
+ event.stopPropagation();
24
+ if (item.href !== void 0) {
25
+ if (event.key === "Enter" && (event.ctrlKey || event.metaKey || event.shiftKey || event.altKey))
26
+ return;
27
+ event.preventDefault();
28
+ if (!item.disabled && !event.repeat) event.currentTarget.click();
29
+ }
30
+ }
31
+ const content = children != null ? children : /* @__PURE__ */ jsxs(Fragment, { children: [
32
+ item.icon,
33
+ item.label
34
+ ] });
35
+ return /* @__PURE__ */ jsx(
36
+ Menu.Item,
37
+ {
38
+ active: item.active,
39
+ selected: item.selected,
40
+ disabled: item.disabled,
41
+ variant: item.variant,
42
+ children: item.href !== void 0 ? /* @__PURE__ */ jsx(
43
+ "a",
44
+ {
45
+ href: item.disabled ? void 0 : item.href,
46
+ download: item.download,
47
+ target: item.target,
48
+ rel: (_a = item.rel) != null ? _a : item.target === "_blank" ? "noopener noreferrer" : void 0,
49
+ tabIndex: item.disabled ? -1 : 0,
50
+ onClick: handleClick,
51
+ onKeyDown: handleKeyDown,
52
+ children: content
53
+ }
54
+ ) : /* @__PURE__ */ jsx("button", { type: "button", onClick: handleClick, onKeyDown: handleKeyDown, children: content })
55
+ }
56
+ );
57
+ }
58
+
59
+ export { MenuAction };
@@ -0,0 +1,27 @@
1
+ 'use client';
2
+ 'use strict';
3
+
4
+ var jsxRuntime = require('react/jsx-runtime');
5
+ var styles = require('./StatusText.module.css');
6
+
7
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
8
+
9
+ var styles__default = /*#__PURE__*/_interopDefault(styles);
10
+
11
+ function StatusText({
12
+ children,
13
+ severity = "neutral",
14
+ className,
15
+ ...props
16
+ }) {
17
+ return /* @__PURE__ */ jsxRuntime.jsx(
18
+ "span",
19
+ {
20
+ ...props,
21
+ className: [styles__default.default.statusText, styles__default.default[severity], className].filter(Boolean).join(" "),
22
+ children
23
+ }
24
+ );
25
+ }
26
+
27
+ exports.StatusText = StatusText;
@@ -0,0 +1,7 @@
1
+ import type { HTMLAttributes, ReactNode } from 'react';
2
+ import type { Severity } from '../../constants/severity.types';
3
+ export interface StatusTextProps extends HTMLAttributes<HTMLSpanElement> {
4
+ children: ReactNode;
5
+ severity?: Severity;
6
+ }
7
+ export declare function StatusText({ children, severity, className, ...props }: Readonly<StatusTextProps>): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,21 @@
1
+ 'use client';
2
+ import { jsx } from 'react/jsx-runtime';
3
+ import styles from './StatusText.module.css';
4
+
5
+ function StatusText({
6
+ children,
7
+ severity = "neutral",
8
+ className,
9
+ ...props
10
+ }) {
11
+ return /* @__PURE__ */ jsx(
12
+ "span",
13
+ {
14
+ ...props,
15
+ className: [styles.statusText, styles[severity], className].filter(Boolean).join(" "),
16
+ children
17
+ }
18
+ );
19
+ }
20
+
21
+ export { StatusText };
@@ -0,0 +1,41 @@
1
+ .statusText {
2
+ background: var(--status-text-bg);
3
+ color: var(--status-text-fg);
4
+ -webkit-box-decoration-break: clone;
5
+ box-decoration-break: clone;
6
+ }
7
+
8
+ .neutral {
9
+ --status-text-bg: var(--color-bg-surface-subtle);
10
+ --status-text-fg: var(--color-fg-default);
11
+ }
12
+
13
+ .brand {
14
+ --status-text-bg: color-mix(in srgb, var(--color-brand) 12%, var(--color-bg-surface));
15
+ --status-text-fg: var(--color-brand);
16
+ }
17
+
18
+ .accent {
19
+ --status-text-bg: var(--color-accent-bg-soft);
20
+ --status-text-fg: var(--color-accent-fg-soft);
21
+ }
22
+
23
+ .success {
24
+ --status-text-bg: var(--color-status-success-bg);
25
+ --status-text-fg: var(--color-status-success-fg);
26
+ }
27
+
28
+ .error {
29
+ --status-text-bg: var(--color-status-error-bg);
30
+ --status-text-fg: var(--color-status-error-fg);
31
+ }
32
+
33
+ .info {
34
+ --status-text-bg: var(--color-status-info-bg);
35
+ --status-text-fg: var(--color-status-info-fg);
36
+ }
37
+
38
+ .warning {
39
+ --status-text-bg: var(--color-status-warning-bg);
40
+ --status-text-fg: var(--color-status-warning-fg);
41
+ }
@@ -1,4 +1,5 @@
1
1
  import type { HTMLAttributes, ReactNode, Ref } from 'react';
2
+ import type { MenuButtonItem, MenuLinkItem } from '../../components/menu/Menu.types';
2
3
  import type { PageChangeEvent } from '../../components/pagination/Pagination';
3
4
  import type { Severity } from '../../constants/severity.types';
4
5
  export type ViewMode = 'compact' | 'wrapped';
@@ -69,21 +70,17 @@ export type HeaderExtrasArgs<T> = {
69
70
  column: ColumnItem<T>;
70
71
  index: number;
71
72
  };
72
- export type TableContextMenuActionItem = {
73
+ export type TableContextMenuActionItem = MenuButtonItem & {
74
+ type?: 'item';
75
+ };
76
+ export type TableContextMenuLinkItem = MenuLinkItem & {
73
77
  type?: 'item';
74
- label: ReactNode;
75
- icon?: ReactNode;
76
- onClick: () => void;
77
- active?: boolean;
78
- selected?: boolean;
79
- disabled?: boolean;
80
- variant?: 'default' | 'danger';
81
78
  };
82
79
  export type TableContextMenuHeaderItem = {
83
80
  type: 'header';
84
81
  label: ReactNode;
85
82
  };
86
- export type TableContextMenuItem = TableContextMenuActionItem | TableContextMenuHeaderItem;
83
+ export type TableContextMenuItem = TableContextMenuActionItem | TableContextMenuLinkItem | TableContextMenuHeaderItem;
87
84
  export type StickyHeader = false | 'app-header' | number;
88
85
  export type TableVariant = 'primary' | 'embedded';
89
86
  export type SortDirection = 'asc' | 'desc' | null;
@@ -9,6 +9,7 @@ var Popover = require('../../../components/popover/Popover');
9
9
  var severity = require('../../../constants/severity');
10
10
  var Button = require('../../button/Button');
11
11
  var Menu = require('../../menu/Menu');
12
+ var MenuAction = require('../../menu/MenuAction');
12
13
  var useTableRowInteractions = require('../hooks/useTableRowInteractions');
13
14
  var table_classes = require('../table.classes');
14
15
  var styles = require('../Table.module.css');
@@ -252,32 +253,10 @@ function RowActions({
252
253
  );
253
254
  },
254
255
  children: (close) => /* @__PURE__ */ jsxRuntime.jsx(Menu.Menu, { children: menuItems.map(
255
- (item, index) => item.type === "header" ? /* @__PURE__ */ jsxRuntime.jsx(Menu.Menu.Header, { children: item.label }, index) : /* @__PURE__ */ jsxRuntime.jsx(
256
- Menu.Menu.Item,
257
- {
258
- active: item.active,
259
- selected: item.selected,
260
- disabled: item.disabled,
261
- variant: item.variant,
262
- children: /* @__PURE__ */ jsxRuntime.jsx(
263
- "button",
264
- {
265
- type: "button",
266
- "data-row-context-menu": "true",
267
- onClick: (e) => {
268
- e.stopPropagation();
269
- item.onClick();
270
- close();
271
- },
272
- children: /* @__PURE__ */ jsxRuntime.jsxs("span", { className: styles__default.default.contextMenuItemLabel, children: [
273
- item.icon,
274
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: styles__default.default.contextMenuItemText, children: item.label })
275
- ] })
276
- }
277
- )
278
- },
279
- index
280
- )
256
+ (item, index) => item.type === "header" ? /* @__PURE__ */ jsxRuntime.jsx(Menu.Menu.Header, { children: item.label }, index) : /* @__PURE__ */ jsxRuntime.jsx(MenuAction.MenuAction, { item, onActivate: close, children: /* @__PURE__ */ jsxRuntime.jsxs("span", { className: styles__default.default.contextMenuItemLabel, children: [
257
+ item.icon,
258
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: styles__default.default.contextMenuItemText, children: item.label })
259
+ ] }) }, index)
281
260
  ) })
282
261
  }
283
262
  ) : null
@@ -7,6 +7,7 @@ import { Popover } from '../../../components/popover/Popover';
7
7
  import { SeverityBgColor } from '../../../constants/severity';
8
8
  import { Button } from '../../button/Button';
9
9
  import { Menu } from '../../menu/Menu';
10
+ import { MenuAction } from '../../menu/MenuAction';
10
11
  import { useTableRowInteractions } from '../hooks/useTableRowInteractions';
11
12
  import { cx } from '../table.classes';
12
13
  import styles from '../Table.module.css';
@@ -246,32 +247,10 @@ function RowActions({
246
247
  );
247
248
  },
248
249
  children: (close) => /* @__PURE__ */ jsx(Menu, { children: menuItems.map(
249
- (item, index) => item.type === "header" ? /* @__PURE__ */ jsx(Menu.Header, { children: item.label }, index) : /* @__PURE__ */ jsx(
250
- Menu.Item,
251
- {
252
- active: item.active,
253
- selected: item.selected,
254
- disabled: item.disabled,
255
- variant: item.variant,
256
- children: /* @__PURE__ */ jsx(
257
- "button",
258
- {
259
- type: "button",
260
- "data-row-context-menu": "true",
261
- onClick: (e) => {
262
- e.stopPropagation();
263
- item.onClick();
264
- close();
265
- },
266
- children: /* @__PURE__ */ jsxs("span", { className: styles.contextMenuItemLabel, children: [
267
- item.icon,
268
- /* @__PURE__ */ jsx("span", { className: styles.contextMenuItemText, children: item.label })
269
- ] })
270
- }
271
- )
272
- },
273
- index
274
- )
250
+ (item, index) => item.type === "header" ? /* @__PURE__ */ jsx(Menu.Header, { children: item.label }, index) : /* @__PURE__ */ jsx(MenuAction, { item, onActivate: close, children: /* @__PURE__ */ jsxs("span", { className: styles.contextMenuItemLabel, children: [
251
+ item.icon,
252
+ /* @__PURE__ */ jsx("span", { className: styles.contextMenuItemText, children: item.label })
253
+ ] }) }, index)
275
254
  ) })
276
255
  }
277
256
  ) : null
@@ -0,0 +1,45 @@
1
+ 'use strict';
2
+
3
+ var react = require('react');
4
+
5
+ function useRovingFocus({
6
+ containerRef,
7
+ itemSelector,
8
+ loop = false,
9
+ resolveCurrentItem,
10
+ onNavigate
11
+ }) {
12
+ return react.useCallback(
13
+ (event) => {
14
+ const container = containerRef.current;
15
+ if (!container) return;
16
+ const items = Array.from(container.querySelectorAll(itemSelector));
17
+ if (items.length === 0) return;
18
+ const focused = document.activeElement;
19
+ const currentItem = items.includes(focused) ? focused : resolveCurrentItem == null ? void 0 : resolveCurrentItem(focused, items);
20
+ const currentIndex = currentItem ? items.indexOf(currentItem) : -1;
21
+ let nextIndex = null;
22
+ if (event.key === "ArrowDown") {
23
+ nextIndex = currentIndex < 0 ? 0 : currentIndex + 1;
24
+ if (loop && nextIndex >= items.length) nextIndex = 0;
25
+ else if (nextIndex >= items.length) nextIndex = items.length - 1;
26
+ } else if (event.key === "ArrowUp") {
27
+ nextIndex = currentIndex < 0 ? items.length - 1 : currentIndex - 1;
28
+ if (loop && nextIndex < 0) nextIndex = items.length - 1;
29
+ else if (nextIndex < 0) nextIndex = 0;
30
+ } else if (event.key === "Home") {
31
+ nextIndex = 0;
32
+ } else if (event.key === "End") {
33
+ nextIndex = items.length - 1;
34
+ }
35
+ if (nextIndex === null) return;
36
+ event.preventDefault();
37
+ const nextItem = items[nextIndex];
38
+ nextItem.focus();
39
+ onNavigate == null ? void 0 : onNavigate(nextItem);
40
+ },
41
+ [containerRef, itemSelector, loop, onNavigate, resolveCurrentItem]
42
+ );
43
+ }
44
+
45
+ exports.useRovingFocus = useRovingFocus;
@@ -0,0 +1,10 @@
1
+ import { type KeyboardEvent, type RefObject } from 'react';
2
+ type UseRovingFocusOptions<T extends HTMLElement> = {
3
+ containerRef: RefObject<T | null>;
4
+ itemSelector: string;
5
+ loop?: boolean;
6
+ resolveCurrentItem?: (focused: HTMLElement, items: HTMLElement[]) => HTMLElement | undefined;
7
+ onNavigate?: (item: HTMLElement) => void;
8
+ };
9
+ export declare function useRovingFocus<T extends HTMLElement>({ containerRef, itemSelector, loop, resolveCurrentItem, onNavigate, }: UseRovingFocusOptions<T>): (event: KeyboardEvent) => void;
10
+ export {};
@@ -0,0 +1,43 @@
1
+ import { useCallback } from 'react';
2
+
3
+ function useRovingFocus({
4
+ containerRef,
5
+ itemSelector,
6
+ loop = false,
7
+ resolveCurrentItem,
8
+ onNavigate
9
+ }) {
10
+ return useCallback(
11
+ (event) => {
12
+ const container = containerRef.current;
13
+ if (!container) return;
14
+ const items = Array.from(container.querySelectorAll(itemSelector));
15
+ if (items.length === 0) return;
16
+ const focused = document.activeElement;
17
+ const currentItem = items.includes(focused) ? focused : resolveCurrentItem == null ? void 0 : resolveCurrentItem(focused, items);
18
+ const currentIndex = currentItem ? items.indexOf(currentItem) : -1;
19
+ let nextIndex = null;
20
+ if (event.key === "ArrowDown") {
21
+ nextIndex = currentIndex < 0 ? 0 : currentIndex + 1;
22
+ if (loop && nextIndex >= items.length) nextIndex = 0;
23
+ else if (nextIndex >= items.length) nextIndex = items.length - 1;
24
+ } else if (event.key === "ArrowUp") {
25
+ nextIndex = currentIndex < 0 ? items.length - 1 : currentIndex - 1;
26
+ if (loop && nextIndex < 0) nextIndex = items.length - 1;
27
+ else if (nextIndex < 0) nextIndex = 0;
28
+ } else if (event.key === "Home") {
29
+ nextIndex = 0;
30
+ } else if (event.key === "End") {
31
+ nextIndex = items.length - 1;
32
+ }
33
+ if (nextIndex === null) return;
34
+ event.preventDefault();
35
+ const nextItem = items[nextIndex];
36
+ nextItem.focus();
37
+ onNavigate == null ? void 0 : onNavigate(nextItem);
38
+ },
39
+ [containerRef, itemSelector, loop, onNavigate, resolveCurrentItem]
40
+ );
41
+ }
42
+
43
+ export { useRovingFocus };
package/dist/index.css CHANGED
@@ -42,6 +42,7 @@
42
42
  @import './components/json-viewer/JsonViewer.module.css';
43
43
  @import './components/lightbox/Lightbox.module.css';
44
44
  @import './components/line-viewer/LineViewer.module.css';
45
+ @import './components/listbox/Listbox.module.css';
45
46
  @import './components/media-card/MediaCard.module.css';
46
47
  @import './components/menu/Menu.module.css';
47
48
  @import './components/meta-bar/MetaBar.module.css';
@@ -71,6 +72,7 @@
71
72
  @import './components/split-pane/SplitPane.module.css';
72
73
  @import './components/stack/Stack.module.css';
73
74
  @import './components/state-page/StatePage.module.css';
75
+ @import './components/status-text/StatusText.module.css';
74
76
  @import './components/table/Table.module.css';
75
77
  @import './components/table/components/column-resizer/ColumnResizer.module.css';
76
78
  @import './components/table/components/empty-state/EmptyState.module.css';
package/dist/index.d.ts CHANGED
@@ -51,3 +51,4 @@ export * from './components/tree-view/TreeView';
51
51
  export * from './components/split-button/SplitButton';
52
52
  export * from './components/filtering/chip-multi-toggle/ChipMultiToggle';
53
53
  export * from './components/forms/checkbox-group/CheckboxGroup';
54
+ export type { MenuActionItem, MenuButtonItem, MenuLinkItem } from './components/menu/Menu.types';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dbcdk/react-components",
3
- "version": "0.0.206",
3
+ "version": "0.0.208",
4
4
  "description": "Reusable React components for DBC projects",
5
5
  "license": "ISC",
6
6
  "author": "",