@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.
- package/dist/client.cjs +10 -0
- package/dist/client.d.ts +5 -0
- package/dist/client.js +2 -0
- package/dist/components/lightbox/Lightbox.cjs +2 -22
- package/dist/components/lightbox/Lightbox.d.ts +2 -8
- package/dist/components/lightbox/Lightbox.js +2 -22
- package/dist/components/listbox/Listbox.cjs +153 -0
- package/dist/components/listbox/Listbox.d.ts +22 -0
- package/dist/components/listbox/Listbox.js +146 -0
- package/dist/components/listbox/Listbox.module.css +97 -0
- package/dist/components/menu/Menu.cjs +7 -24
- package/dist/components/menu/Menu.js +7 -24
- package/dist/components/menu/Menu.types.cjs +2 -0
- package/dist/components/menu/Menu.types.d.ts +19 -0
- package/dist/components/menu/Menu.types.js +1 -0
- package/dist/components/menu/MenuAction.cjs +61 -0
- package/dist/components/menu/MenuAction.d.ts +7 -0
- package/dist/components/menu/MenuAction.js +59 -0
- package/dist/components/status-text/StatusText.cjs +27 -0
- package/dist/components/status-text/StatusText.d.ts +7 -0
- package/dist/components/status-text/StatusText.js +21 -0
- package/dist/components/status-text/StatusText.module.css +41 -0
- package/dist/components/table/Table.types.d.ts +6 -9
- package/dist/components/table/components/TableRow.cjs +5 -26
- package/dist/components/table/components/TableRow.js +5 -26
- package/dist/hooks/useRovingFocus.cjs +45 -0
- package/dist/hooks/useRovingFocus.d.ts +10 -0
- package/dist/hooks/useRovingFocus.js +43 -0
- package/dist/index.css +2 -0
- package/dist/index.d.ts +1 -0
- 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
|
-
|
|
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
|
-
|
|
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,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
|
-
|
|
257
|
-
{
|
|
258
|
-
|
|
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
|
-
|
|
251
|
-
{
|
|
252
|
-
|
|
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';
|