@dbcdk/react-components 0.0.10 → 0.0.13
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/components/accordion/Accordion.d.ts +2 -2
- package/dist/components/accordion/Accordion.js +34 -41
- package/dist/components/accordion/Accordion.module.css +13 -72
- package/dist/components/accordion/components/AccordionRow.d.ts +10 -0
- package/dist/components/accordion/components/AccordionRow.js +51 -0
- package/dist/components/accordion/components/AccordionRow.module.css +82 -0
- package/dist/components/breadcrumbs/Breadcrumbs.module.css +0 -1
- package/dist/components/button/Button.module.css +7 -7
- package/dist/components/card/Card.d.ts +15 -6
- package/dist/components/card/Card.js +39 -13
- package/dist/components/card/Card.module.css +22 -28
- package/dist/components/card/components/CardMeta.d.ts +15 -0
- package/dist/components/card/components/CardMeta.js +20 -0
- package/dist/components/card/components/CardMeta.module.css +51 -0
- package/dist/components/card-container/CardContainer.js +1 -1
- package/dist/components/card-container/CardContainer.module.css +3 -1
- package/dist/components/chip/Chip.module.css +7 -2
- package/dist/components/circle/Circle.d.ts +2 -1
- package/dist/components/circle/Circle.js +2 -2
- package/dist/components/circle/Circle.module.css +6 -2
- package/dist/components/code-block/CodeBlock.js +1 -1
- package/dist/components/code-block/CodeBlock.module.css +30 -17
- package/dist/components/copy-button/CopyButton.d.ts +1 -0
- package/dist/components/copy-button/CopyButton.js +10 -2
- package/dist/components/datetime-picker/DateTimePicker.d.ts +33 -8
- package/dist/components/datetime-picker/DateTimePicker.js +119 -78
- package/dist/components/datetime-picker/DateTimePicker.module.css +2 -0
- package/dist/components/datetime-picker/dateTimeHelpers.d.ts +15 -3
- package/dist/components/datetime-picker/dateTimeHelpers.js +137 -23
- package/dist/components/filter-field/FilterField.js +16 -11
- package/dist/components/filter-field/FilterField.module.css +137 -16
- package/dist/components/forms/checkbox/Checkbox.d.ts +2 -2
- package/dist/components/forms/checkbox-group/CheckboxGroup.js +1 -1
- package/dist/components/forms/checkbox-group/CheckboxGroup.module.css +1 -1
- package/dist/components/forms/form-select/FormSelect.d.ts +35 -0
- package/dist/components/forms/form-select/FormSelect.js +86 -0
- package/dist/components/forms/form-select/FormSelect.module.css +236 -0
- package/dist/components/forms/input/Input.d.ts +0 -3
- package/dist/components/forms/input/Input.js +1 -4
- package/dist/components/forms/input/Input.module.css +8 -7
- package/dist/components/forms/input-container/InputContainer.module.css +1 -1
- package/dist/components/forms/radio-buttons/RadioButtons.module.css +1 -0
- package/dist/components/forms/select/Select.js +55 -16
- package/dist/components/hyperlink/Hyperlink.d.ts +19 -7
- package/dist/components/hyperlink/Hyperlink.js +35 -11
- package/dist/components/hyperlink/Hyperlink.module.css +50 -2
- package/dist/components/interval-select/IntervalSelect.d.ts +9 -2
- package/dist/components/interval-select/IntervalSelect.js +21 -6
- package/dist/components/menu/Menu.d.ts +29 -0
- package/dist/components/menu/Menu.js +61 -16
- package/dist/components/menu/Menu.module.css +73 -5
- package/dist/components/overlay/modal/Modal.module.css +4 -3
- package/dist/components/overlay/modal/provider/ModalProvider.js +1 -3
- package/dist/components/overlay/side-panel/SidePanel.js +18 -1
- package/dist/components/overlay/side-panel/SidePanel.module.css +1 -3
- package/dist/components/overlay/tooltip/useTooltipTrigger.js +4 -2
- package/dist/components/page-layout/PageLayout.d.ts +16 -4
- package/dist/components/page-layout/PageLayout.js +57 -28
- package/dist/components/page-layout/PageLayout.module.css +153 -33
- package/dist/components/popover/Popover.d.ts +17 -4
- package/dist/components/popover/Popover.js +147 -65
- package/dist/components/popover/Popover.module.css +5 -0
- package/dist/components/sidebar/components/expandable-sidebar-item/ExpandableSidebarItem.js +22 -18
- package/dist/components/sidebar/providers/SidebarProvider.d.ts +4 -1
- package/dist/components/sidebar/providers/SidebarProvider.js +66 -18
- package/dist/components/split-button/SplitButton.d.ts +1 -1
- package/dist/components/split-button/SplitButton.js +3 -1
- package/dist/components/split-button/SplitButton.module.css +4 -4
- package/dist/components/split-pane/SplitPane.d.ts +10 -24
- package/dist/components/split-pane/SplitPane.js +83 -54
- package/dist/components/split-pane/SplitPane.module.css +11 -6
- package/dist/components/split-pane/provider/SplitPaneContext.js +5 -11
- package/dist/components/state-page/StatePage.module.css +1 -1
- package/dist/components/sticky-footer-layout/StickyFooterLayout.d.ts +3 -8
- package/dist/components/sticky-footer-layout/StickyFooterLayout.js +57 -20
- package/dist/components/table/Table.d.ts +8 -8
- package/dist/components/table/Table.js +37 -79
- package/dist/components/table/Table.module.css +62 -46
- package/dist/components/table/{tanstack.d.ts → TanstackTable.d.ts} +7 -3
- package/dist/components/table/TanstackTable.js +84 -0
- package/dist/components/table/components/column-resizer/ColumnResizer.js +1 -1
- package/dist/components/table/components/column-resizer/ColumnResizer.module.css +17 -7
- package/dist/components/table/components/table-settings/TableSettings.d.ts +13 -3
- package/dist/components/table/components/table-settings/TableSettings.js +55 -4
- package/dist/components/table/table.utils.d.ts +17 -0
- package/dist/components/table/table.utils.js +61 -0
- package/dist/components/table/tanstackTable.utils.d.ts +22 -0
- package/dist/components/table/tanstackTable.utils.js +104 -0
- package/dist/components/tabs/Tabs.d.ts +35 -12
- package/dist/components/tabs/Tabs.js +114 -26
- package/dist/components/tabs/Tabs.module.css +158 -71
- package/dist/hooks/useTableSettings.d.ts +23 -4
- package/dist/hooks/useTableSettings.js +64 -17
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/src/styles/styles.css +38 -23
- package/dist/styles/animation.d.ts +5 -0
- package/dist/styles/animation.js +5 -0
- package/dist/styles/styles.css +38 -23
- package/dist/styles/themes/dbc/base.css +136 -0
- package/dist/styles/themes/dbc/dark.css +39 -202
- package/dist/styles/themes/dbc/light.css +17 -174
- package/dist/utils/localStorage.utils.d.ts +19 -0
- package/dist/utils/localStorage.utils.js +78 -0
- package/package.json +4 -4
- package/dist/components/table/tanstack.js +0 -162
|
@@ -17,7 +17,4 @@ export type InputProps = Omit<React.InputHTMLAttributes<HTMLInputElement>, 'size
|
|
|
17
17
|
tooltipPlacement?: 'top' | 'right' | 'bottom' | 'left';
|
|
18
18
|
modified?: boolean;
|
|
19
19
|
};
|
|
20
|
-
/**
|
|
21
|
-
* Explicit exported type annotation is required with --isolatedDeclarations.
|
|
22
|
-
*/
|
|
23
20
|
export declare const Input: React.ForwardRefExoticComponent<React.PropsWithoutRef<InputProps> & React.RefAttributes<HTMLInputElement>>;
|
|
@@ -18,12 +18,9 @@ function mergeRefs(...refs) {
|
|
|
18
18
|
}
|
|
19
19
|
};
|
|
20
20
|
}
|
|
21
|
-
/**
|
|
22
|
-
* Explicit exported type annotation is required with --isolatedDeclarations.
|
|
23
|
-
*/
|
|
24
21
|
export const Input = forwardRef(function Input({
|
|
25
22
|
// InputContainer props
|
|
26
|
-
label, error, helpText, orientation = '
|
|
23
|
+
label, error, helpText, orientation = 'vertical', labelWidth = '160px', fullWidth = false, required, tooltip, tooltipPlacement = 'right', modified,
|
|
27
24
|
// Input-only props
|
|
28
25
|
icon, autoFocus, minWidth, width, inputSize = 'md', variant = 'outlined', onClear, onButtonClick, buttonLabel, buttonIcon,
|
|
29
26
|
// Native input props
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
/* inline-flex makes width behave weird in parents */
|
|
4
4
|
display: inline-flex;
|
|
5
5
|
align-items: stretch;
|
|
6
|
+
flex-grow: 1;
|
|
6
7
|
gap: 0;
|
|
7
8
|
/* width control */
|
|
8
9
|
inline-size: var(--input-width, auto);
|
|
@@ -42,8 +43,8 @@
|
|
|
42
43
|
border: var(--border-width-thin) solid var(--color-border-default);
|
|
43
44
|
border-radius: var(--border-radius-default);
|
|
44
45
|
|
|
45
|
-
padding-inline: var(--
|
|
46
|
-
padding-block:
|
|
46
|
+
padding-inline: var(--spacing-sm);
|
|
47
|
+
padding-block: var(--spacing-xs);
|
|
47
48
|
|
|
48
49
|
transition:
|
|
49
50
|
background-color var(--transition-fast) var(--ease-standard),
|
|
@@ -66,7 +67,7 @@
|
|
|
66
67
|
|
|
67
68
|
/* Optional: if ClearButton is absolutely positioned, reserve space */
|
|
68
69
|
.withClear .input {
|
|
69
|
-
padding-inline-end: calc(var(--
|
|
70
|
+
padding-inline-end: calc(var(--spacing-md) + 28px);
|
|
70
71
|
}
|
|
71
72
|
|
|
72
73
|
/* Placeholder */
|
|
@@ -99,20 +100,20 @@
|
|
|
99
100
|
|
|
100
101
|
/* Sizes */
|
|
101
102
|
.xs {
|
|
102
|
-
block-size:
|
|
103
|
+
block-size: var(--component-size-xs);
|
|
103
104
|
font-size: var(--font-size-sm);
|
|
104
105
|
padding: 0 var(--spacing-xxs);
|
|
105
106
|
}
|
|
106
107
|
.sm {
|
|
107
|
-
block-size:
|
|
108
|
+
block-size: var(--component-size-sm);
|
|
108
109
|
font-size: var(--font-size-sm);
|
|
109
110
|
}
|
|
110
111
|
.md {
|
|
111
|
-
block-size:
|
|
112
|
+
block-size: var(--component-size-md);
|
|
112
113
|
font-size: var(--font-size-sm);
|
|
113
114
|
}
|
|
114
115
|
.lg {
|
|
115
|
-
block-size:
|
|
116
|
+
block-size: var(--component-size-lg);
|
|
116
117
|
font-size: var(--font-size-lg);
|
|
117
118
|
}
|
|
118
119
|
|
|
@@ -80,7 +80,7 @@
|
|
|
80
80
|
*/
|
|
81
81
|
.inputContainer[data-modified] label:not(.label) {
|
|
82
82
|
background-color: color-mix(in srgb, var(--color-status-warning-bg) 35%, transparent);
|
|
83
|
-
border-radius: var(--border-radius-
|
|
83
|
+
border-radius: var(--border-radius-default);
|
|
84
84
|
padding: 2px 6px;
|
|
85
85
|
}
|
|
86
86
|
|
|
@@ -1,45 +1,61 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { Check } from 'lucide-react';
|
|
4
|
-
import { useEffect, useId, useRef, useState } from 'react';
|
|
4
|
+
import { useEffect, useId, useMemo, useRef, useState } from 'react';
|
|
5
5
|
import { useTooltipTrigger } from '../../../components/overlay/tooltip/useTooltipTrigger';
|
|
6
6
|
import { Button } from '../../button/Button';
|
|
7
7
|
import { ClearButton } from '../../clear-button/ClearButton';
|
|
8
8
|
import { Menu } from '../../menu/Menu';
|
|
9
9
|
import { Popover } from '../../popover/Popover';
|
|
10
10
|
import { InputContainer } from '../input-container/InputContainer';
|
|
11
|
-
export function Select({
|
|
12
|
-
// InputContainer props
|
|
13
|
-
label, error, helpText, orientation = 'vertical', labelWidth = '160px', fullWidth = true, required, tooltip, tooltipPlacement = 'right', modified = false,
|
|
14
|
-
// Select props
|
|
15
|
-
id, options, selectedValue, onChange, placeholder = 'Vælg', size, variant = 'outlined', onClear, datakey, dataCy, disabled, }) {
|
|
11
|
+
export function Select({ label, error, helpText, orientation = 'vertical', labelWidth = '160px', fullWidth = true, required, tooltip, tooltipPlacement = 'right', modified = false, id, options, selectedValue, onChange, placeholder = 'Vælg', size, variant = 'outlined', onClear, datakey, dataCy, disabled, }) {
|
|
16
12
|
const generatedId = useId();
|
|
17
13
|
const controlId = id !== null && id !== void 0 ? id : `select-${generatedId}`;
|
|
18
14
|
const describedById = `${controlId}-desc`;
|
|
15
|
+
const listboxId = `${controlId}-listbox`;
|
|
19
16
|
const popoverRef = useRef(null);
|
|
20
17
|
const optionRefs = useRef([]);
|
|
21
|
-
const selectedIndex = options.findIndex(o => o.value === selectedValue);
|
|
18
|
+
const selectedIndex = useMemo(() => options.findIndex(o => o.value === selectedValue), [options, selectedValue]);
|
|
22
19
|
const [activeIndex, setActiveIndex] = useState(selectedIndex >= 0 ? selectedIndex : 0);
|
|
20
|
+
const [open, setOpen] = useState(false);
|
|
21
|
+
// Only move focus when open
|
|
23
22
|
useEffect(() => {
|
|
24
23
|
var _a;
|
|
24
|
+
if (!open)
|
|
25
|
+
return;
|
|
25
26
|
(_a = optionRefs.current[activeIndex]) === null || _a === void 0 ? void 0 : _a.focus();
|
|
26
|
-
}, [activeIndex]);
|
|
27
|
+
}, [activeIndex, open]);
|
|
28
|
+
// keep activeIndex aligned when opening
|
|
29
|
+
const resetActiveToSelected = () => setActiveIndex(selectedIndex >= 0 ? selectedIndex : 0);
|
|
27
30
|
const handleKeyDown = (e) => {
|
|
28
31
|
var _a, _b;
|
|
29
32
|
switch (e.key) {
|
|
30
33
|
case 'ArrowDown': {
|
|
31
34
|
e.preventDefault();
|
|
35
|
+
if (!open) {
|
|
36
|
+
setOpen(true);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
32
39
|
setActiveIndex(i => Math.min(i + 1, options.length - 1));
|
|
33
40
|
break;
|
|
34
41
|
}
|
|
35
42
|
case 'ArrowUp': {
|
|
36
43
|
e.preventDefault();
|
|
44
|
+
if (!open) {
|
|
45
|
+
setOpen(true);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
37
48
|
setActiveIndex(i => Math.max(i - 1, 0));
|
|
38
49
|
break;
|
|
39
50
|
}
|
|
40
51
|
case 'Enter':
|
|
41
52
|
case ' ': {
|
|
53
|
+
// Space on a button should open/select depending on state
|
|
42
54
|
e.preventDefault();
|
|
55
|
+
if (!open) {
|
|
56
|
+
setOpen(true);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
43
59
|
const opt = options[activeIndex];
|
|
44
60
|
if (opt) {
|
|
45
61
|
onChange(opt.value);
|
|
@@ -48,22 +64,35 @@ id, options, selectedValue, onChange, placeholder = 'Vælg', size, variant = 'ou
|
|
|
48
64
|
break;
|
|
49
65
|
}
|
|
50
66
|
case 'Escape': {
|
|
67
|
+
if (!open)
|
|
68
|
+
return;
|
|
51
69
|
e.preventDefault();
|
|
52
70
|
(_b = popoverRef.current) === null || _b === void 0 ? void 0 : _b.close();
|
|
53
71
|
break;
|
|
54
72
|
}
|
|
73
|
+
case 'Home': {
|
|
74
|
+
if (!open)
|
|
75
|
+
return;
|
|
76
|
+
e.preventDefault();
|
|
77
|
+
setActiveIndex(0);
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
case 'End': {
|
|
81
|
+
if (!open)
|
|
82
|
+
return;
|
|
83
|
+
e.preventDefault();
|
|
84
|
+
setActiveIndex(options.length - 1);
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
55
87
|
}
|
|
56
88
|
};
|
|
57
89
|
const selected = options.find(o => o.value === selectedValue);
|
|
58
|
-
// Tooltip trigger props (anchor to the Button)
|
|
59
90
|
const tooltipEnabled = Boolean(tooltip);
|
|
60
91
|
const { triggerProps, id: tooltipId } = useTooltipTrigger({
|
|
61
92
|
content: tooltipEnabled ? tooltip : null,
|
|
62
93
|
placement: tooltipPlacement,
|
|
63
94
|
offset: 8,
|
|
64
95
|
});
|
|
65
|
-
// If you want BOTH tooltip + error/helpText describedby:
|
|
66
|
-
// merge describedby ids (keeping existing describedById)
|
|
67
96
|
const describedBy = (() => {
|
|
68
97
|
const ids = [];
|
|
69
98
|
if (error || helpText)
|
|
@@ -72,15 +101,25 @@ id, options, selectedValue, onChange, placeholder = 'Vælg', size, variant = 'ou
|
|
|
72
101
|
ids.push(tooltipId);
|
|
73
102
|
return ids.length ? ids.join(' ') : undefined;
|
|
74
103
|
})();
|
|
75
|
-
return (_jsxs(InputContainer, { label: label, htmlFor: controlId, fullWidth: fullWidth, error: error, helpText: helpText, orientation: orientation, labelWidth: labelWidth, required: required, modified: modified, children: [_jsx(Popover, { ref: popoverRef,
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
104
|
+
return (_jsxs(InputContainer, { label: label, htmlFor: controlId, fullWidth: fullWidth, error: error, helpText: helpText, orientation: orientation, labelWidth: labelWidth, required: required, modified: modified, children: [_jsx(Popover, { ref: popoverRef, open: open, onOpenChange: next => {
|
|
105
|
+
setOpen(next);
|
|
106
|
+
if (next)
|
|
107
|
+
resetActiveToSelected();
|
|
108
|
+
}, contentId: listboxId,
|
|
109
|
+
// Select manages roving focus; don't auto-focus content wrapper
|
|
110
|
+
autoFocusContent: false,
|
|
111
|
+
// keep focus on trigger until you move it yourself (your ArrowDown opens then focuses option)
|
|
112
|
+
returnFocus: true, trigger: (toggle, icon, isOpen) => (_jsx(Button, { disabled: disabled, ...(tooltipEnabled ? triggerProps : {}), id: controlId, "data-cy": dataCy !== null && dataCy !== void 0 ? dataCy : 'select-button', onKeyDown: handleKeyDown, fullWidth: fullWidth, variant: variant, onClick: e => {
|
|
113
|
+
resetActiveToSelected();
|
|
114
|
+
toggle(e);
|
|
115
|
+
}, size: size, type: "button", "data-forminput": true, "aria-haspopup": "listbox", "aria-expanded": !!isOpen, "aria-controls": listboxId, "aria-invalid": Boolean(error) || undefined, "aria-describedby": describedBy, children: _jsxs("span", { className: "dbc-flex dbc-justify-between dbc-items-center dbc-gap-xxs", style: { width: '100%' }, children: [_jsx("span", { children: selected ? selected.label : placeholder }), onClear && selected && _jsx(ClearButton, { onClick: onClear }), icon] }) })), children: _jsx(Menu, { onKeyDown: handleKeyDown, role: "listbox", children: options.map((opt, index) => {
|
|
79
116
|
const isSelected = typeof opt.value === 'object' && typeof selectedValue === 'object' && datakey
|
|
80
117
|
? (selectedValue === null || selectedValue === void 0 ? void 0 : selectedValue[datakey]) === opt.value[datakey]
|
|
81
118
|
: opt.value === selectedValue;
|
|
82
119
|
const isActive = index === activeIndex;
|
|
83
|
-
return (_jsx(Menu.Item, { active: isActive,
|
|
120
|
+
return (_jsx(Menu.Item, { active: isActive,
|
|
121
|
+
// IMPORTANT: listbox uses role="option"
|
|
122
|
+
itemRole: "option", "aria-selected": isSelected, children: _jsxs("button", { ref: el => (optionRefs.current[index] = el), type: "button", tabIndex: isActive ? 0 : -1, onClick: () => {
|
|
84
123
|
var _a;
|
|
85
124
|
onChange(opt.value);
|
|
86
125
|
(_a = popoverRef.current) === null || _a === void 0 ? void 0 : _a.close();
|
|
@@ -1,10 +1,22 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
className?: string;
|
|
2
|
+
type BaseProps = {
|
|
3
|
+
children: React.ReactNode;
|
|
5
4
|
icon?: React.ReactNode;
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
className?: string;
|
|
6
|
+
/**
|
|
7
|
+
* If true, Hyperlink will NOT render <a>.
|
|
8
|
+
* Instead, it will clone its only child (e.g. Next <Link>) and apply styling/props to it.
|
|
9
|
+
*/
|
|
10
|
+
asChild?: boolean;
|
|
11
|
+
variant?: 'primary' | 'secondary';
|
|
12
|
+
inline?: boolean;
|
|
13
|
+
};
|
|
14
|
+
type AnchorProps = BaseProps & React.AnchorHTMLAttributes<HTMLAnchorElement> & {
|
|
15
|
+
as?: 'a';
|
|
16
|
+
};
|
|
17
|
+
type ButtonProps = BaseProps & React.ButtonHTMLAttributes<HTMLButtonElement> & {
|
|
18
|
+
as: 'button';
|
|
19
|
+
};
|
|
20
|
+
type HyperlinkProps = AnchorProps | ButtonProps;
|
|
21
|
+
export declare function Hyperlink(props: HyperlinkProps): React.ReactElement;
|
|
10
22
|
export {};
|
|
@@ -1,15 +1,39 @@
|
|
|
1
1
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import * as React from 'react';
|
|
3
3
|
import styles from './Hyperlink.module.css';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
4
|
+
function cx(...parts) {
|
|
5
|
+
return parts.filter(Boolean).join(' ');
|
|
6
|
+
}
|
|
7
|
+
function renderInner(children, icon) {
|
|
8
|
+
return (_jsxs(_Fragment, { children: [_jsx("span", { className: styles.content, children: children }), icon && _jsx("span", { className: styles.icon, children: icon })] }));
|
|
9
|
+
}
|
|
10
|
+
export function Hyperlink(props) {
|
|
11
|
+
var _a;
|
|
12
|
+
const { children, icon, className, asChild, as = 'a', variant = 'primary', inline = true, ...rest } = props;
|
|
13
|
+
const linkClassName = cx(styles.link, className, variant === 'secondary' ? styles.secondary : styles.primary, inline ? '' : styles.block);
|
|
14
|
+
if (asChild) {
|
|
15
|
+
const child = React.Children.only(children);
|
|
16
|
+
if (!React.isValidElement(child)) {
|
|
17
|
+
throw new Error('Hyperlink with asChild expects a single valid React element as its child.');
|
|
18
|
+
}
|
|
19
|
+
const childProps = (_a = child.props) !== null && _a !== void 0 ? _a : {};
|
|
20
|
+
return React.cloneElement(child, {
|
|
21
|
+
...childProps,
|
|
22
|
+
...rest,
|
|
23
|
+
className: cx(childProps.className, linkClassName),
|
|
24
|
+
children: renderInner(childProps.children, icon),
|
|
25
|
+
onClick: (e) => {
|
|
26
|
+
e.stopPropagation();
|
|
27
|
+
if (childProps.onClick) {
|
|
28
|
+
childProps.onClick(e);
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
if (as === 'button') {
|
|
34
|
+
// (Optional) guardrail: avoid accidentally passing href to a button
|
|
35
|
+
// const { href, ...buttonRest } = rest as any
|
|
36
|
+
return (_jsx("button", { type: "button", className: linkClassName, ...rest, children: renderInner(children, icon) }));
|
|
37
|
+
}
|
|
38
|
+
return (_jsx("a", { onClick: e => e.stopPropagation(), className: linkClassName, ...rest, children: renderInner(children, icon) }));
|
|
15
39
|
}
|
|
@@ -2,13 +2,61 @@
|
|
|
2
2
|
display: inline-flex;
|
|
3
3
|
gap: var(--spacing-xs);
|
|
4
4
|
position: relative;
|
|
5
|
+
font-weight: normal;
|
|
6
|
+
background: none;
|
|
7
|
+
border: none;
|
|
8
|
+
padding: 0;
|
|
5
9
|
text-decoration: none;
|
|
6
|
-
color: var(--color-brand);
|
|
7
10
|
font-size: inherit;
|
|
11
|
+
font-family: inherit;
|
|
12
|
+
cursor: pointer;
|
|
13
|
+
color: var(--color-brand);
|
|
14
|
+
line-height: inherit;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.link.secondary {
|
|
18
|
+
color: var(--color-fg-default);
|
|
8
19
|
}
|
|
9
20
|
|
|
10
21
|
.link:hover {
|
|
11
|
-
|
|
22
|
+
color: var(--color-brand);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.link.primary {
|
|
26
|
+
position: relative;
|
|
27
|
+
color: var(--color-brand);
|
|
28
|
+
text-decoration: none;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.link.block {
|
|
32
|
+
background: var(--color-bg-contextual-subtle);
|
|
33
|
+
display: inline-block;
|
|
34
|
+
padding: var(--spacing-xs);
|
|
35
|
+
&:hover {
|
|
36
|
+
background-color: var(--color-bg-contextual);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.link::after {
|
|
41
|
+
content: '';
|
|
42
|
+
position: absolute;
|
|
43
|
+
left: 0;
|
|
44
|
+
bottom: -2px;
|
|
45
|
+
width: 100%;
|
|
46
|
+
height: 1px;
|
|
47
|
+
background-color: currentColor;
|
|
48
|
+
transform: scaleX(0);
|
|
49
|
+
transform-origin: left;
|
|
50
|
+
transition: transform 100ms ease;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.link:hover::after {
|
|
54
|
+
transform: scaleX(1);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.link:focus-visible {
|
|
58
|
+
outline: 2px solid var(--color-brand);
|
|
59
|
+
outline-offset: 2px;
|
|
12
60
|
}
|
|
13
61
|
|
|
14
62
|
.icon {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { ButtonVariant } from '../../components/button/Button';
|
|
3
3
|
import { InputContainer } from '../../components/forms/input-container/InputContainer';
|
|
4
|
+
import { UtcIsoString } from '../datetime-picker/dateTimeHelpers';
|
|
4
5
|
type InputContainerProps = React.ComponentProps<typeof InputContainer>;
|
|
5
6
|
export type IntervalOption = {
|
|
6
7
|
label: string;
|
|
@@ -12,9 +13,14 @@ export type IntervalSelectProps = Omit<InputContainerProps, 'children' | 'htmlFo
|
|
|
12
13
|
id?: string;
|
|
13
14
|
options: IntervalOption[];
|
|
14
15
|
selectedValue: IntervalSelectValue;
|
|
15
|
-
|
|
16
|
+
/**
|
|
17
|
+
* Emits a UTC instant as ISO string (Z), consistent with DateTimePicker when enableTime=true.
|
|
18
|
+
* Also includes the computed local Date for convenience.
|
|
19
|
+
*/
|
|
20
|
+
onChange: (isoUtc: UtcIsoString, meta: {
|
|
16
21
|
minutesAgo: number;
|
|
17
22
|
option: IntervalOption;
|
|
23
|
+
dateLocal: Date;
|
|
18
24
|
}) => void;
|
|
19
25
|
/** Base date for the calculation; defaults to "now". */
|
|
20
26
|
baseDate?: Date;
|
|
@@ -23,8 +29,9 @@ export type IntervalSelectProps = Omit<InputContainerProps, 'children' | 'htmlFo
|
|
|
23
29
|
variant?: ButtonVariant;
|
|
24
30
|
onClear?: () => void;
|
|
25
31
|
dataCy?: string;
|
|
32
|
+
disabled?: boolean;
|
|
26
33
|
tooltip?: React.ReactNode;
|
|
27
34
|
tooltipPlacement?: 'top' | 'right' | 'bottom' | 'left';
|
|
28
35
|
};
|
|
29
|
-
export declare function IntervalSelect({ label, error, helpText, orientation, labelWidth, fullWidth, required, tooltip, tooltipPlacement, id, options, selectedValue, onChange, baseDate, placeholder, size, variant, onClear, dataCy, }: IntervalSelectProps): React.ReactNode;
|
|
36
|
+
export declare function IntervalSelect({ label, error, helpText, orientation, labelWidth, fullWidth, required, tooltip, tooltipPlacement, id, options, selectedValue, onChange, baseDate, placeholder, size, variant, onClear, dataCy, disabled, }: IntervalSelectProps): React.ReactNode;
|
|
30
37
|
export {};
|
|
@@ -8,20 +8,23 @@ import { InputContainer } from '../../components/forms/input-container/InputCont
|
|
|
8
8
|
import { Menu } from '../../components/menu/Menu';
|
|
9
9
|
import { useTooltipTrigger } from '../../components/overlay/tooltip/useTooltipTrigger';
|
|
10
10
|
import { Popover } from '../../components/popover/Popover';
|
|
11
|
+
import { isoFromLocalDate } from '../datetime-picker/dateTimeHelpers';
|
|
11
12
|
export function IntervalSelect({
|
|
12
13
|
// InputContainer props
|
|
13
14
|
label, error, helpText, orientation = 'vertical', labelWidth = '160px', fullWidth = true, required,
|
|
14
15
|
// tooltip
|
|
15
16
|
tooltip, tooltipPlacement = 'right',
|
|
16
17
|
// IntervalSelect props
|
|
17
|
-
id, options, selectedValue, onChange, baseDate, placeholder = 'Vælg interval', size, variant = 'outlined', onClear, dataCy, }) {
|
|
18
|
+
id, options, selectedValue, onChange, baseDate, placeholder = 'Vælg interval', size, variant = 'outlined', onClear, dataCy, disabled, }) {
|
|
18
19
|
const generatedId = useId();
|
|
19
20
|
const controlId = id !== null && id !== void 0 ? id : `interval-select-${generatedId}`;
|
|
20
21
|
const describedById = `${controlId}-desc`;
|
|
22
|
+
const listboxId = `${controlId}-listbox`;
|
|
21
23
|
const popoverRef = useRef(null);
|
|
22
24
|
const optionRefs = useRef([]);
|
|
23
25
|
const selectedIndex = useMemo(() => options.findIndex(o => o.minutesAgo === selectedValue), [options, selectedValue]);
|
|
24
26
|
const [activeIndex, setActiveIndex] = useState(selectedIndex >= 0 ? selectedIndex : 0);
|
|
27
|
+
// Focus active option when opened (simple version: always focus when index changes)
|
|
25
28
|
useEffect(() => {
|
|
26
29
|
var _a;
|
|
27
30
|
(_a = optionRefs.current[activeIndex]) === null || _a === void 0 ? void 0 : _a.focus();
|
|
@@ -44,8 +47,9 @@ id, options, selectedValue, onChange, baseDate, placeholder = 'Vælg interval',
|
|
|
44
47
|
const handleCommit = (opt) => {
|
|
45
48
|
var _a;
|
|
46
49
|
const base = baseDate !== null && baseDate !== void 0 ? baseDate : new Date();
|
|
47
|
-
const
|
|
48
|
-
|
|
50
|
+
const dateLocal = new Date(base.getTime() - opt.minutesAgo * 60000);
|
|
51
|
+
const isoUtc = isoFromLocalDate(dateLocal);
|
|
52
|
+
onChange(isoUtc, { minutesAgo: opt.minutesAgo, option: opt, dateLocal });
|
|
49
53
|
(_a = popoverRef.current) === null || _a === void 0 ? void 0 : _a.close();
|
|
50
54
|
};
|
|
51
55
|
const handleKeyDown = (e) => {
|
|
@@ -59,6 +63,14 @@ id, options, selectedValue, onChange, baseDate, placeholder = 'Vælg interval',
|
|
|
59
63
|
e.preventDefault();
|
|
60
64
|
setActiveIndex(i => Math.max(i - 1, 0));
|
|
61
65
|
break;
|
|
66
|
+
case 'Home':
|
|
67
|
+
e.preventDefault();
|
|
68
|
+
setActiveIndex(0);
|
|
69
|
+
break;
|
|
70
|
+
case 'End':
|
|
71
|
+
e.preventDefault();
|
|
72
|
+
setActiveIndex(options.length - 1);
|
|
73
|
+
break;
|
|
62
74
|
case 'Enter':
|
|
63
75
|
case ' ':
|
|
64
76
|
e.preventDefault();
|
|
@@ -71,12 +83,15 @@ id, options, selectedValue, onChange, baseDate, placeholder = 'Vælg interval',
|
|
|
71
83
|
break;
|
|
72
84
|
}
|
|
73
85
|
};
|
|
74
|
-
return (_jsxs(InputContainer, { label: label, htmlFor: controlId, fullWidth: fullWidth, error: error, helpText: helpText, orientation: orientation, labelWidth: labelWidth, required: required, children: [_jsx(Popover, { ref: popoverRef, trigger: (onClick, icon) => (_jsx(Button, { ...(tooltipEnabled ? triggerProps : {}), id: controlId, "data-cy": dataCy !== null && dataCy !== void 0 ? dataCy : 'interval-select-button', onKeyDown: handleKeyDown, fullWidth: fullWidth, variant: variant, onClick: e => {
|
|
86
|
+
return (_jsxs(InputContainer, { label: label, htmlFor: controlId, fullWidth: fullWidth, error: error, helpText: helpText, orientation: orientation, labelWidth: labelWidth, required: required, children: [_jsx(Popover, { ref: popoverRef, contentId: listboxId, trigger: (onClick, icon, isOpen) => (_jsx(Button, { disabled: disabled, ...(tooltipEnabled ? triggerProps : {}), id: controlId, "data-cy": dataCy !== null && dataCy !== void 0 ? dataCy : 'interval-select-button', onKeyDown: handleKeyDown, fullWidth: fullWidth, variant: variant, onClick: e => {
|
|
87
|
+
// Reset active to selected when opening
|
|
75
88
|
setActiveIndex(selectedIndex >= 0 ? selectedIndex : 0);
|
|
76
89
|
onClick(e);
|
|
77
|
-
}, size: size, type: "button", "aria-haspopup": "listbox", "aria-invalid": Boolean(error) || undefined, "aria-describedby": describedBy, children: _jsxs("span", { className: "dbc-flex dbc-justify-between dbc-items-center dbc-gap-xxs", style: { width: '100%' }, children: [_jsxs("span", { className: "dbc-flex dbc-items-center dbc-gap-xxs", children: [_jsx(Clock, { size: 14 }), selected ? selected.label : placeholder] }), onClear && selected && _jsx(ClearButton, { onClick: onClear }), icon] }) })), children: _jsx(Menu, { onKeyDown: handleKeyDown, role: "listbox", children: options.map((opt, index) => {
|
|
90
|
+
}, size: size, type: "button", "aria-haspopup": "listbox", "aria-expanded": !!isOpen, "aria-controls": listboxId, "aria-invalid": Boolean(error) || undefined, "aria-describedby": describedBy, children: _jsxs("span", { className: "dbc-flex dbc-justify-between dbc-items-center dbc-gap-xxs", style: { width: '100%' }, children: [_jsxs("span", { className: "dbc-flex dbc-items-center dbc-gap-xxs", children: [_jsx(Clock, { size: 14 }), selected ? selected.label : placeholder] }), onClear && selected && _jsx(ClearButton, { onClick: onClear }), icon] }) })), children: _jsx(Menu, { id: listboxId, onKeyDown: handleKeyDown, role: "listbox", children: options.map((opt, index) => {
|
|
78
91
|
const isSelected = opt.minutesAgo === selectedValue;
|
|
79
92
|
const isActive = index === activeIndex;
|
|
80
|
-
return (_jsx(Menu.Item, { active: isActive,
|
|
93
|
+
return (_jsx(Menu.Item, { active: isActive,
|
|
94
|
+
// IMPORTANT: listbox uses role="option"
|
|
95
|
+
itemRole: "option", "aria-selected": isSelected, children: _jsxs("button", { ref: el => (optionRefs.current[index] = el), type: "button", tabIndex: isActive ? 0 : -1, onClick: () => handleCommit(opt), onFocus: () => setActiveIndex(index), style: { display: 'flex', alignItems: 'center', width: '100%' }, children: [_jsx("span", { style: { width: 16, display: 'inline-flex', justifyContent: 'center' }, children: isSelected ? _jsx(Check, {}) : null }), opt.label] }) }, opt.minutesAgo));
|
|
81
96
|
}) }) }), (error || helpText) && (_jsx("span", { id: describedById, style: { display: 'none' }, children: error !== null && error !== void 0 ? error : helpText }))] }));
|
|
82
97
|
}
|
|
@@ -1,12 +1,41 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
export interface MenuProps extends React.HTMLAttributes<HTMLUListElement> {
|
|
3
3
|
children: React.ReactNode;
|
|
4
|
+
/**
|
|
5
|
+
* Default role for items rendered by Menu.Item when it clones/wraps content.
|
|
6
|
+
* - default: "menuitem" (for role="menu")
|
|
7
|
+
* - for Select/listbox usage, pass itemRole="option" and role="listbox" on Menu.
|
|
8
|
+
*/
|
|
9
|
+
itemRole?: 'menuitem' | 'option';
|
|
4
10
|
}
|
|
11
|
+
export type MenuSeparatorProps = React.LiHTMLAttributes<HTMLLIElement>;
|
|
5
12
|
export interface MenuItemProps extends React.LiHTMLAttributes<HTMLLIElement> {
|
|
6
13
|
children: React.ReactNode;
|
|
7
14
|
active?: boolean;
|
|
8
15
|
disabled?: boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Override the role applied to the interactive element for this item only.
|
|
18
|
+
* If not set, Menu's `itemRole` is used.
|
|
19
|
+
*/
|
|
20
|
+
itemRole?: 'menuitem' | 'option';
|
|
21
|
+
}
|
|
22
|
+
export interface MenuCheckItemProps extends Omit<React.LiHTMLAttributes<HTMLLIElement>, 'onChange'> {
|
|
23
|
+
label: React.ReactNode;
|
|
24
|
+
checked: boolean;
|
|
25
|
+
disabled?: boolean;
|
|
26
|
+
onCheckedChange?: (checked: boolean) => void;
|
|
27
|
+
}
|
|
28
|
+
export interface MenuRadioItemProps extends Omit<React.LiHTMLAttributes<HTMLLIElement>, 'onChange'> {
|
|
29
|
+
name: string;
|
|
30
|
+
value: string;
|
|
31
|
+
checked: boolean;
|
|
32
|
+
disabled?: boolean;
|
|
33
|
+
label: string;
|
|
34
|
+
onValueChange?: (value: string) => void;
|
|
9
35
|
}
|
|
10
36
|
export declare const Menu: React.FC<MenuProps> & {
|
|
11
37
|
Item: React.FC<MenuItemProps>;
|
|
38
|
+
CheckItem: React.FC<MenuCheckItemProps>;
|
|
39
|
+
RadioItem: React.FC<MenuRadioItemProps>;
|
|
40
|
+
Separator: React.FC<MenuSeparatorProps>;
|
|
12
41
|
};
|
|
@@ -2,28 +2,73 @@
|
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
3
|
import * as React from 'react';
|
|
4
4
|
import styles from './Menu.module.css';
|
|
5
|
-
|
|
5
|
+
import { Checkbox } from '../forms/checkbox/Checkbox';
|
|
6
|
+
import { RadioButton } from '../forms/radio-buttons/RadioButton';
|
|
7
|
+
const MenuBase = React.forwardRef(({ children, className, itemRole = 'menuitem', ...props }, ref) => (_jsx("ul", { ref: ref, role: "menu", "data-itemrole": itemRole, className: [styles.container, className].filter(Boolean).join(' '), ...props, children: children })));
|
|
6
8
|
MenuBase.displayName = 'Menu';
|
|
7
9
|
const isInteractiveEl = (el) => React.isValidElement(el) &&
|
|
8
|
-
(typeof el.type === 'string' ? el.type === 'a' || el.type === 'button' : true);
|
|
9
|
-
|
|
10
|
-
|
|
10
|
+
(typeof el.type === 'string' ? el.type === 'a' || el.type === 'button' : true);
|
|
11
|
+
function applyMenuItemPropsToElement(child, opts) {
|
|
12
|
+
var _a, _b, _c, _d;
|
|
13
|
+
const { active, disabled, role, tabIndex = -1, className } = opts;
|
|
14
|
+
const childClass = [styles.item, active ? styles.active : ''].filter(Boolean).join(' ');
|
|
15
|
+
const nextImmediate = React.cloneElement(child, {
|
|
16
|
+
className: [child.props.className, styles.interactiveChild, className]
|
|
17
|
+
.filter(Boolean)
|
|
18
|
+
.join(' '),
|
|
19
|
+
});
|
|
20
|
+
// If immediate child is interactive, apply a11y+styles there
|
|
21
|
+
if (typeof child.type === 'string' && (child.type === 'a' || child.type === 'button')) {
|
|
22
|
+
return React.cloneElement(child, {
|
|
23
|
+
role: (_a = child.props.role) !== null && _a !== void 0 ? _a : role,
|
|
24
|
+
tabIndex: (_b = child.props.tabIndex) !== null && _b !== void 0 ? _b : tabIndex,
|
|
25
|
+
'aria-selected': active || undefined,
|
|
26
|
+
'aria-disabled': disabled || undefined,
|
|
27
|
+
className: [child.props.className, styles.interactive, childClass, className]
|
|
28
|
+
.filter(Boolean)
|
|
29
|
+
.join(' '),
|
|
30
|
+
...(child.type === 'button' ? { disabled } : {}),
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
// For custom components, assume they forward props
|
|
34
|
+
return React.cloneElement(nextImmediate, {
|
|
35
|
+
role: (_c = nextImmediate.props.role) !== null && _c !== void 0 ? _c : role,
|
|
36
|
+
tabIndex: (_d = nextImmediate.props.tabIndex) !== null && _d !== void 0 ? _d : tabIndex,
|
|
37
|
+
'aria-selected': active || undefined,
|
|
38
|
+
'aria-disabled': disabled || undefined,
|
|
39
|
+
className: [nextImmediate.props.className, styles.interactive, childClass]
|
|
40
|
+
.filter(Boolean)
|
|
41
|
+
.join(' '),
|
|
42
|
+
disabled,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
const MenuItem = React.forwardRef(({ children, active, disabled, className, itemRole, ...liProps }, ref) => {
|
|
46
|
+
// If caller sets itemRole prop, use it; otherwise attempt to inherit from parent Menu via data attr.
|
|
47
|
+
// (We can’t reliably read parent props here without context; simplest is: caller passes itemRole on Menu.Item when needed.)
|
|
48
|
+
const resolvedRole = itemRole !== null && itemRole !== void 0 ? itemRole : 'menuitem';
|
|
11
49
|
if (isInteractiveEl(children)) {
|
|
12
50
|
const child = children;
|
|
13
|
-
|
|
14
|
-
return (_jsx("li", { ref: ref, role: "none", className: [styles.row, className].filter(Boolean).join(' '), ...liProps, children: React.cloneElement(child, {
|
|
15
|
-
role: 'menuitem',
|
|
16
|
-
tabIndex: -1,
|
|
17
|
-
'aria-selected': active || undefined,
|
|
18
|
-
'aria-disabled': disabled || undefined,
|
|
19
|
-
className: [child.props.className, styles.interactive, childClass]
|
|
20
|
-
.filter(Boolean)
|
|
21
|
-
.join(' '),
|
|
22
|
-
}) }));
|
|
51
|
+
return (_jsx("li", { ref: ref, role: "none", className: [styles.row, className].filter(Boolean).join(' '), ...liProps, children: applyMenuItemPropsToElement(child, { active, disabled, role: resolvedRole }) }));
|
|
23
52
|
}
|
|
24
|
-
|
|
53
|
+
// Fallback: wrap non-interactive children in a <button>
|
|
54
|
+
return (_jsx("li", { ref: ref, role: "none", className: [styles.row, className].filter(Boolean).join(' '), ...liProps, children: _jsx("button", { role: resolvedRole, tabIndex: -1, "aria-selected": active || undefined, "aria-disabled": disabled || undefined, className: [styles.interactive, styles.item, active ? styles.active : '']
|
|
25
55
|
.filter(Boolean)
|
|
26
56
|
.join(' '), type: "button", disabled: disabled, children: children }) }));
|
|
27
57
|
});
|
|
28
58
|
MenuItem.displayName = 'Menu.Item';
|
|
29
|
-
|
|
59
|
+
const MenuCheckItem = React.forwardRef(({ label, checked, disabled, onCheckedChange, className, ...liProps }, ref) => {
|
|
60
|
+
return (_jsx("li", { ref: ref, role: "none", className: [styles.row, className].filter(Boolean).join(' '), ...liProps, children: _jsx("div", { className: styles.interactiveChild, children: _jsx(Checkbox, { noContainer: true, checked: checked, disabled: disabled, label: label, onChange: (next, _e) => onCheckedChange === null || onCheckedChange === void 0 ? void 0 : onCheckedChange(next) }) }) }));
|
|
61
|
+
});
|
|
62
|
+
MenuCheckItem.displayName = 'Menu.CheckItem';
|
|
63
|
+
const MenuRadioItem = React.forwardRef(({ name, value, checked, disabled, label, onValueChange, className, ...liProps }, ref) => {
|
|
64
|
+
return (_jsx("li", { ref: ref, role: "none", className: [styles.row, className].filter(Boolean).join(' '), ...liProps, children: _jsx("div", { className: styles.interactiveChild, children: _jsx(RadioButton, { noContainer: true, name: name, value: value, checked: checked, disabled: disabled, label: label, onChange: (v, _e) => onValueChange === null || onValueChange === void 0 ? void 0 : onValueChange(v) }) }) }));
|
|
65
|
+
});
|
|
66
|
+
MenuRadioItem.displayName = 'Menu.RadioItem';
|
|
67
|
+
const MenuSeparator = React.forwardRef(({ className, ...props }, ref) => (_jsx("li", { ref: ref, role: "separator", className: [styles.separator, className].filter(Boolean).join(' '), ...props })));
|
|
68
|
+
MenuSeparator.displayName = 'Menu.Separator';
|
|
69
|
+
export const Menu = Object.assign(MenuBase, {
|
|
70
|
+
Item: MenuItem,
|
|
71
|
+
CheckItem: MenuCheckItem,
|
|
72
|
+
RadioItem: MenuRadioItem,
|
|
73
|
+
Separator: MenuSeparator,
|
|
74
|
+
});
|