@olenbetong/appframe-ds 1.0.3 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/package.json +8 -7
- package/scripts/copyAssets.mjs +27 -55
- package/src/AfLookup/Lookup.css +129 -125
- package/src/AfLookup/Lookup.tsx +184 -162
- package/src/AfLookup/LookupCombobox.css +78 -78
- package/src/AfLookup/LookupCombobox.tsx +237 -237
- package/src/AfLookup/LookupEdit.tsx +130 -130
- package/src/AfLookup/LookupGrid.tsx +124 -124
- package/src/AfLookup/index.ts +5 -5
- package/src/autocomplete/Autocomplete.css +256 -256
- package/src/autocomplete/Autocomplete.tsx +320 -320
- package/src/autocomplete/AutocompleteFrame.tsx +168 -168
- package/src/autocomplete/Combobox.tsx +286 -286
- package/src/autocomplete/index.ts +4 -4
- package/src/autocomplete/types.ts +204 -204
- package/src/autocomplete/utils.ts +183 -183
- package/src/binding/BoundNumericTextField.tsx +102 -102
- package/src/binding/BoundTextField.tsx +49 -49
- package/src/binding/CancelButton.tsx +30 -30
- package/src/binding/CancelIconButton.tsx +30 -30
- package/src/binding/DataEditToolbar.tsx +92 -92
- package/src/binding/DeleteButton.tsx +35 -35
- package/src/binding/DeleteIconButton.tsx +35 -35
- package/src/binding/RefreshButton.tsx +31 -31
- package/src/binding/RefreshIconButton.tsx +33 -33
- package/src/binding/RefreshRowIconButton.tsx +33 -33
- package/src/binding/SaveButton.tsx +37 -37
- package/src/binding/SaveIconButton.tsx +38 -38
- package/src/container/Container.tsx +49 -49
- package/src/container/index.ts +1 -1
- package/src/cutoff/CutOffPicker.css +12 -0
- package/src/cutoff/CutOffPicker.tsx +165 -0
- package/src/cutoff/context.tsx +194 -0
- package/src/cutoff/dsCutOffDates.ts +21 -0
- package/src/cutoff/index.ts +4 -0
- package/src/cutoff/utils.test.ts +59 -0
- package/src/cutoff/utils.ts +276 -0
- package/src/filter/ChipListInput.tsx +79 -79
- package/src/filter/FieldFilterPanel.css +150 -150
- package/src/filter/FieldFilterPanel.tsx +309 -309
- package/src/filter/FilterBuilder.css +152 -152
- package/src/filter/FilterBuilder.test.tsx +153 -153
- package/src/filter/FilterBuilder.tsx +435 -435
- package/src/filter/FilterEditor.css +231 -231
- package/src/filter/FilterEditor.test.tsx +259 -259
- package/src/filter/FilterEditor.tsx +75 -74
- package/src/filter/FilterGroupEditor.tsx +160 -160
- package/src/filter/FilterNameDialog.css +22 -22
- package/src/filter/FilterNameDialog.tsx +81 -81
- package/src/filter/FilterRow.tsx +117 -117
- package/src/filter/FilterShareDialog.css +28 -28
- package/src/filter/FilterShareDialog.tsx +201 -201
- package/src/filter/FilterStringField.tsx +78 -77
- package/src/filter/FilterValueEditor.tsx +220 -220
- package/src/filter/SavedFilterTree.css +107 -107
- package/src/filter/SavedFilterTree.test.tsx +94 -94
- package/src/filter/SavedFilterTree.tsx +218 -222
- package/src/filter/fieldFilter.test.ts +158 -158
- package/src/filter/fieldFilter.ts +139 -139
- package/src/filter/index.ts +27 -27
- package/src/filter/types.ts +38 -38
- package/src/filter/useDistinctValues.test.ts +102 -102
- package/src/filter/useDistinctValues.ts +230 -230
- package/src/global.d.ts +11 -11
- package/src/grid/AfGridColumnsPanel.tsx +118 -118
- package/src/grid/AfGridContext.tsx +51 -51
- package/src/grid/AfGridError.tsx +46 -46
- package/src/grid/AfHeaderFilterCell.tsx +147 -151
- package/src/grid/AfHeaderFilterPanel.tsx +111 -111
- package/src/grid/GridEditLookup.css +12 -0
- package/src/grid/GridEditLookup.tsx +121 -0
- package/src/grid/Toolbar.tsx +233 -233
- package/src/grid/editing.ts +100 -100
- package/src/grid/filter.ts +78 -78
- package/src/grid/formatters.ts +48 -48
- package/src/grid/index.css +245 -245
- package/src/grid/index.tsx +489 -484
- package/src/grid/license.ts +48 -48
- package/src/grid/localization.ts +369 -369
- package/src/grid/slots/index.tsx +307 -307
- package/src/grid/slots/slots.css +64 -64
- package/src/grid/theme.tsx +138 -138
- package/src/grid/useAfColumns.tsx +371 -371
- package/src/grid/useAfCurrentIndex.ts +63 -63
- package/src/grid/useAfData.ts +20 -20
- package/src/grid/useAfFilter.ts +160 -160
- package/src/grid/useAfFilterFields.ts +55 -55
- package/src/grid/useAfGridApi.ts +71 -71
- package/src/grid/useAfKeyBindings.ts +36 -36
- package/src/grid/useAfNewItemRow.ts +258 -258
- package/src/grid/useAfPagination.ts +53 -53
- package/src/grid/useAfPersistedState.ts +230 -230
- package/src/grid/useAfRowEditModel.ts +145 -145
- package/src/grid/useAfRowGrouping.ts +47 -47
- package/src/grid/useAfServerAggregation.ts +145 -145
- package/src/grid/useAfSortModel.ts +84 -84
- package/src/i18n.ts +13 -0
- package/src/index.ts +14 -12
- package/src/input/InputAdornments.css +75 -75
- package/src/input/InputAdornments.tsx +102 -102
- package/src/input/index.ts +1 -1
- package/src/layout/AppMenu.tsx +74 -75
- package/src/layout/AppMenuDrawer.css +70 -70
- package/src/layout/AppMenuDrawer.tsx +122 -122
- package/src/layout/ErrorBoundary.tsx +162 -162
- package/src/layout/ResponsiveDialog.css +86 -0
- package/src/layout/ResponsiveDialog.tsx +55 -0
- package/src/layout/index.tsx +69 -68
- package/src/link/Link.tsx +23 -0
- package/src/link/index.ts +1 -0
- package/src/mdi/Mdi.css +138 -138
- package/src/mdi/Mdi.test.tsx +91 -87
- package/src/mdi/Mdi.tsx +141 -141
- package/src/mdi/MdiConfirmCloseDialog.tsx +47 -47
- package/src/mdi/MdiContext.tsx +59 -59
- package/src/mdi/MdiDocumentContext.tsx +60 -60
- package/src/mdi/MdiPanel.tsx +61 -61
- package/src/mdi/MdiRestore.test.tsx +86 -82
- package/src/mdi/MdiTab.tsx +30 -30
- package/src/mdi/controller.test.ts +382 -382
- package/src/mdi/controller.ts +656 -656
- package/src/mdi/dockviewHost.ts +132 -132
- package/src/mdi/fakeHost.ts +200 -200
- package/src/mdi/host.ts +44 -44
- package/src/mdi/ids.test.ts +20 -20
- package/src/mdi/ids.ts +18 -18
- package/src/mdi/index.ts +31 -31
- package/src/mdi/layoutRestore.test.ts +156 -156
- package/src/mdi/layoutRestore.ts +311 -311
- package/src/mdi/layoutSpec.test.ts +143 -143
- package/src/mdi/layoutSpec.ts +214 -214
- package/src/mdi/layoutStore.test.ts +93 -93
- package/src/mdi/layoutStore.ts +99 -99
- package/src/mdi/memorySettingsStore.ts +21 -21
- package/src/mdi/registry.ts +34 -34
- package/src/mdi/testTypes.ts +37 -37
- package/src/mdi/theme.ts +22 -22
- package/src/mdi/types.ts +214 -214
- package/src/page/Page.css +53 -53
- package/src/page/PageBlock.css +64 -64
- package/src/page/PageBlock.tsx +92 -92
- package/src/page/PageHeader.css +88 -88
- package/src/page/PageHeader.tsx +115 -116
- package/src/page/PageTitle.css +5 -0
- package/src/page/PageTitle.tsx +21 -0
- package/src/page/index.ts +4 -3
- package/src/paper/Paper.tsx +52 -52
- package/src/progress/LinearProgress.css +93 -93
- package/src/progress/LinearProgress.tsx +104 -104
- package/src/progress/index.ts +1 -1
- package/src/report-downloader/ReportDownloader.css +67 -67
- package/src/report-downloader/components/StatusItem.tsx +87 -86
- package/src/report-downloader/components/StatusList.tsx +28 -28
- package/src/report-downloader/index.ts +41 -41
- package/src/report-downloader/status.tsx +92 -92
- package/src/sortable/DropIndicator.css +51 -51
- package/src/sortable/DropIndicator.tsx +27 -27
- package/src/sortable/SortableItemList.css +21 -21
- package/src/sortable/SortableItemList.tsx +264 -264
- package/src/sortable/index.ts +8 -8
- package/src/sortable/reorderSortOrder.ts +126 -126
- package/src/sortable/sortOrder.ts +101 -101
- package/src/sortable/types.ts +9 -9
- package/src/sortable/useSortableContainer.ts +53 -53
- package/src/sortable/useSortableItem.ts +95 -95
- package/src/sortable/useSortableList.ts +117 -117
- package/src/test/happy-dom-document-class.ts +24 -24
- package/src/test/setup.ts +12 -12
- package/src/theme/useDsColorScheme.ts +53 -53
- package/tsconfig.build.json +9 -9
- package/tsconfig.json +8 -8
- package/vitest.config.ts +10 -10
|
@@ -1,102 +1,102 @@
|
|
|
1
|
-
import "./InputAdornments.css";
|
|
2
|
-
|
|
3
|
-
import { Input, type InputProps } from "@digdir/designsystemet-react";
|
|
4
|
-
import clsx from "clsx";
|
|
5
|
-
import { Children, isValidElement, type ReactElement, type ReactNode, type Ref } from "react";
|
|
6
|
-
|
|
7
|
-
export type InputSlotProps = {
|
|
8
|
-
/**
|
|
9
|
-
* Extra classes for the input rendered in this slot
|
|
10
|
-
*/
|
|
11
|
-
className?: string;
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Marks where the input should be rendered among the children of
|
|
16
|
-
* `InputAdornments`. Renders nothing on its own; `InputAdornments` swaps it
|
|
17
|
-
* for the actual input. When it is left out, the input is rendered before the
|
|
18
|
-
* adornments.
|
|
19
|
-
*
|
|
20
|
-
* @example
|
|
21
|
-
* ```jsx
|
|
22
|
-
* <InputAdornments value={value} onChange={handleChange}>
|
|
23
|
-
* <SearchIcon aria-hidden />
|
|
24
|
-
* <InputSlot />
|
|
25
|
-
* <Button icon variant="tertiary"><XMarkIcon aria-hidden /></Button>
|
|
26
|
-
* </InputAdornments>
|
|
27
|
-
* ```
|
|
28
|
-
*/
|
|
29
|
-
export function InputSlot(_props: InputSlotProps) {
|
|
30
|
-
return null;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export type InputAdornmentsProps = Omit<InputProps, "children"> & {
|
|
34
|
-
/**
|
|
35
|
-
* Adornments rendered next to the input, e.g. icons or buttons. Include an
|
|
36
|
-
* `InputSlot` to control where the input itself ends up.
|
|
37
|
-
*/
|
|
38
|
-
children?: ReactNode;
|
|
39
|
-
/**
|
|
40
|
-
* Classes for the wrapper that carries the input styling
|
|
41
|
-
*/
|
|
42
|
-
className?: string;
|
|
43
|
-
/**
|
|
44
|
-
* Classes for the bare input element itself
|
|
45
|
-
*/
|
|
46
|
-
inputClassName?: string;
|
|
47
|
-
/**
|
|
48
|
-
* Ref to the input element. The wrapper is an implementation detail, so the
|
|
49
|
-
* ref points at the input rather than at the element carrying the styling.
|
|
50
|
-
*/
|
|
51
|
-
ref?: Ref<HTMLInputElement>;
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Input with adornments, equivalent to a MUI `TextField` with
|
|
56
|
-
* `InputProps.startAdornment` / `endAdornment`. Designsystemet has no
|
|
57
|
-
* equivalent, so the border, background, hover and focus styling is moved from
|
|
58
|
-
* the input onto a wrapper element, making the input and its adornments read
|
|
59
|
-
* as a single control.
|
|
60
|
-
*
|
|
61
|
-
* All other props are forwarded to the Designsystemet `Input`, so `ref`,
|
|
62
|
-
* `value`, `onChange`, `data-size` and `data-color` work as usual.
|
|
63
|
-
*
|
|
64
|
-
* @example
|
|
65
|
-
* ```jsx
|
|
66
|
-
* // Input first (default), adornments after
|
|
67
|
-
* <InputAdornments aria-label="Search" value={value} onChange={handleChange}>
|
|
68
|
-
* <Button icon variant="tertiary"><MagnifyingGlassIcon aria-hidden /></Button>
|
|
69
|
-
* </InputAdornments>
|
|
70
|
-
*
|
|
71
|
-
* // Explicit placement with InputSlot
|
|
72
|
-
* <InputAdornments aria-label="Amount" data-size="lg">
|
|
73
|
-
* <span aria-hidden>kr</span>
|
|
74
|
-
* <InputSlot />
|
|
75
|
-
* <span aria-hidden>,-</span>
|
|
76
|
-
* </InputAdornments>
|
|
77
|
-
* ```
|
|
78
|
-
*/
|
|
79
|
-
export function InputAdornments({ children, className, inputClassName, ...props }: InputAdornmentsProps) {
|
|
80
|
-
let size = props["data-size"];
|
|
81
|
-
let color = props["data-color"];
|
|
82
|
-
let childArray = Children.toArray(children);
|
|
83
|
-
let slot = childArray.find((child) => isValidElement(child) && child.type === InputSlot) as
|
|
84
|
-
| ReactElement<InputSlotProps>
|
|
85
|
-
| undefined;
|
|
86
|
-
|
|
87
|
-
let input = (
|
|
88
|
-
<Input
|
|
89
|
-
key="ObInputAdornments-input"
|
|
90
|
-
{...props}
|
|
91
|
-
className={clsx("ObInputAdornments-field", inputClassName, slot?.props.className)}
|
|
92
|
-
/>
|
|
93
|
-
);
|
|
94
|
-
|
|
95
|
-
let content = slot ? childArray.map((child) => (child === slot ? input : child)) : [input, ...childArray];
|
|
96
|
-
|
|
97
|
-
return (
|
|
98
|
-
<div className={clsx("ObInputAdornments-root", className)} data-size={size} data-color={color}>
|
|
99
|
-
{content}
|
|
100
|
-
</div>
|
|
101
|
-
);
|
|
102
|
-
}
|
|
1
|
+
import "./InputAdornments.css";
|
|
2
|
+
|
|
3
|
+
import { Input, type InputProps } from "@digdir/designsystemet-react";
|
|
4
|
+
import clsx from "clsx";
|
|
5
|
+
import { Children, isValidElement, type ReactElement, type ReactNode, type Ref } from "react";
|
|
6
|
+
|
|
7
|
+
export type InputSlotProps = {
|
|
8
|
+
/**
|
|
9
|
+
* Extra classes for the input rendered in this slot
|
|
10
|
+
*/
|
|
11
|
+
className?: string;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Marks where the input should be rendered among the children of
|
|
16
|
+
* `InputAdornments`. Renders nothing on its own; `InputAdornments` swaps it
|
|
17
|
+
* for the actual input. When it is left out, the input is rendered before the
|
|
18
|
+
* adornments.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```jsx
|
|
22
|
+
* <InputAdornments value={value} onChange={handleChange}>
|
|
23
|
+
* <SearchIcon aria-hidden />
|
|
24
|
+
* <InputSlot />
|
|
25
|
+
* <Button icon variant="tertiary"><XMarkIcon aria-hidden /></Button>
|
|
26
|
+
* </InputAdornments>
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export function InputSlot(_props: InputSlotProps) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export type InputAdornmentsProps = Omit<InputProps, "children"> & {
|
|
34
|
+
/**
|
|
35
|
+
* Adornments rendered next to the input, e.g. icons or buttons. Include an
|
|
36
|
+
* `InputSlot` to control where the input itself ends up.
|
|
37
|
+
*/
|
|
38
|
+
children?: ReactNode;
|
|
39
|
+
/**
|
|
40
|
+
* Classes for the wrapper that carries the input styling
|
|
41
|
+
*/
|
|
42
|
+
className?: string;
|
|
43
|
+
/**
|
|
44
|
+
* Classes for the bare input element itself
|
|
45
|
+
*/
|
|
46
|
+
inputClassName?: string;
|
|
47
|
+
/**
|
|
48
|
+
* Ref to the input element. The wrapper is an implementation detail, so the
|
|
49
|
+
* ref points at the input rather than at the element carrying the styling.
|
|
50
|
+
*/
|
|
51
|
+
ref?: Ref<HTMLInputElement>;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Input with adornments, equivalent to a MUI `TextField` with
|
|
56
|
+
* `InputProps.startAdornment` / `endAdornment`. Designsystemet has no
|
|
57
|
+
* equivalent, so the border, background, hover and focus styling is moved from
|
|
58
|
+
* the input onto a wrapper element, making the input and its adornments read
|
|
59
|
+
* as a single control.
|
|
60
|
+
*
|
|
61
|
+
* All other props are forwarded to the Designsystemet `Input`, so `ref`,
|
|
62
|
+
* `value`, `onChange`, `data-size` and `data-color` work as usual.
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* ```jsx
|
|
66
|
+
* // Input first (default), adornments after
|
|
67
|
+
* <InputAdornments aria-label="Search" value={value} onChange={handleChange}>
|
|
68
|
+
* <Button icon variant="tertiary"><MagnifyingGlassIcon aria-hidden /></Button>
|
|
69
|
+
* </InputAdornments>
|
|
70
|
+
*
|
|
71
|
+
* // Explicit placement with InputSlot
|
|
72
|
+
* <InputAdornments aria-label="Amount" data-size="lg">
|
|
73
|
+
* <span aria-hidden>kr</span>
|
|
74
|
+
* <InputSlot />
|
|
75
|
+
* <span aria-hidden>,-</span>
|
|
76
|
+
* </InputAdornments>
|
|
77
|
+
* ```
|
|
78
|
+
*/
|
|
79
|
+
export function InputAdornments({ children, className, inputClassName, ...props }: InputAdornmentsProps) {
|
|
80
|
+
let size = props["data-size"];
|
|
81
|
+
let color = props["data-color"];
|
|
82
|
+
let childArray = Children.toArray(children);
|
|
83
|
+
let slot = childArray.find((child) => isValidElement(child) && child.type === InputSlot) as
|
|
84
|
+
| ReactElement<InputSlotProps>
|
|
85
|
+
| undefined;
|
|
86
|
+
|
|
87
|
+
let input = (
|
|
88
|
+
<Input
|
|
89
|
+
key="ObInputAdornments-input"
|
|
90
|
+
{...props}
|
|
91
|
+
className={clsx("ObInputAdornments-field", inputClassName, slot?.props.className)}
|
|
92
|
+
/>
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
let content = slot ? childArray.map((child) => (child === slot ? input : child)) : [input, ...childArray];
|
|
96
|
+
|
|
97
|
+
return (
|
|
98
|
+
<div className={clsx("ObInputAdornments-root", className)} data-size={size} data-color={color}>
|
|
99
|
+
{content}
|
|
100
|
+
</div>
|
|
101
|
+
);
|
|
102
|
+
}
|
package/src/input/index.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export * from "./InputAdornments.js";
|
|
1
|
+
export * from "./InputAdornments.js";
|
package/src/layout/AppMenu.tsx
CHANGED
|
@@ -1,75 +1,74 @@
|
|
|
1
|
-
import "./AppMenu.css";
|
|
2
|
-
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
import
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
</
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
let
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
let
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
nextElement.
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
aria-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
{
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
}
|
|
1
|
+
import "./AppMenu.css";
|
|
2
|
+
|
|
3
|
+
import clsx from "clsx";
|
|
4
|
+
import type React from "react";
|
|
5
|
+
import { getLocalizedString } from "../i18n.js";
|
|
6
|
+
|
|
7
|
+
type MenuHeaderProps = {
|
|
8
|
+
children?: React.ReactNode[] | React.ReactNode;
|
|
9
|
+
primary: string;
|
|
10
|
+
secondary?: string;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
function MenuHeader({ primary, secondary }: MenuHeaderProps) {
|
|
14
|
+
return (
|
|
15
|
+
<>
|
|
16
|
+
<div className="AppMenu-header">
|
|
17
|
+
<h1 className="AppMenu-title" id="NavigationHeader">
|
|
18
|
+
{primary}
|
|
19
|
+
</h1>
|
|
20
|
+
{secondary && <p className="AppMenu-subTitle">{secondary}</p>}
|
|
21
|
+
</div>
|
|
22
|
+
<hr className="AppMenu-divider" />
|
|
23
|
+
</>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function handleKeyDown(evt: React.KeyboardEvent) {
|
|
28
|
+
let nextElement: Element | null | undefined = null;
|
|
29
|
+
let appMenu = evt.currentTarget;
|
|
30
|
+
|
|
31
|
+
if (!document.activeElement || !(appMenu instanceof HTMLElement) || !appMenu.contains(document.activeElement)) return;
|
|
32
|
+
|
|
33
|
+
let links = Array.from(appMenu.querySelectorAll<HTMLElement>("a, button"));
|
|
34
|
+
let currentIndex = links.indexOf(document.activeElement as HTMLElement);
|
|
35
|
+
|
|
36
|
+
if (evt.key === "ArrowDown" || evt.key === "ArrowRight") {
|
|
37
|
+
if (currentIndex < links.length) {
|
|
38
|
+
nextElement = links[currentIndex + 1];
|
|
39
|
+
}
|
|
40
|
+
} else if (evt.key === "ArrowUp" || evt.key === "ArrowLeft") {
|
|
41
|
+
if (currentIndex > 0) {
|
|
42
|
+
nextElement = links[currentIndex - 1];
|
|
43
|
+
}
|
|
44
|
+
} else if (evt.key === "Home" || evt.key === "End") {
|
|
45
|
+
nextElement = links.at(evt.key === "Home" ? 0 : -1);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (nextElement instanceof HTMLElement) {
|
|
49
|
+
evt.preventDefault();
|
|
50
|
+
nextElement.scrollIntoView({ block: "nearest" });
|
|
51
|
+
nextElement.focus();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export type AppMenuProps = Pick<React.HTMLProps<HTMLElement>, "aria-label" | "aria-labelledby" | "className" | "id"> & {
|
|
56
|
+
children?: React.ReactNode[] | React.ReactNode;
|
|
57
|
+
headerPrimary?: string;
|
|
58
|
+
headerSecondary?: string;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export function AppMenu({ children, className, headerPrimary, headerSecondary, ...props }: AppMenuProps) {
|
|
62
|
+
return (
|
|
63
|
+
<nav
|
|
64
|
+
aria-labelledby={headerPrimary ? "NavigationHeader" : undefined}
|
|
65
|
+
aria-label={headerPrimary ? undefined : getLocalizedString("Application pages")}
|
|
66
|
+
className={clsx(className, "MenuItems-root")}
|
|
67
|
+
onKeyDown={handleKeyDown}
|
|
68
|
+
{...props}
|
|
69
|
+
>
|
|
70
|
+
{headerPrimary && <MenuHeader primary={headerPrimary} secondary={headerSecondary} />}
|
|
71
|
+
{children}
|
|
72
|
+
</nav>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
@@ -1,70 +1,70 @@
|
|
|
1
|
-
/* --- drawer --- */
|
|
2
|
-
|
|
3
|
-
@media screen {
|
|
4
|
-
.ObPageHeader-drawer {
|
|
5
|
-
/*
|
|
6
|
-
* Absolute, not fixed: the drawer belongs to the page, so it stays clear of any site
|
|
7
|
-
* chrome rendered above the app. Raising z-index instead would put the menu over that
|
|
8
|
-
* chrome and hide it, and #root often creates its own stacking context anyway.
|
|
9
|
-
* Requires Page (which is position: relative); use layout="app" so it has a height.
|
|
10
|
-
*/
|
|
11
|
-
position: absolute;
|
|
12
|
-
top: 0;
|
|
13
|
-
left: 0;
|
|
14
|
-
bottom: 0;
|
|
15
|
-
width: var(--ob-page-drawer-width, 20rem);
|
|
16
|
-
max-width: 90%;
|
|
17
|
-
box-sizing: border-box;
|
|
18
|
-
background-color: var(--ds-color-neutral-background-default, #fff);
|
|
19
|
-
color: var(--ds-color-neutral-text-default, #1e2b3c);
|
|
20
|
-
transform: translateX(-100%);
|
|
21
|
-
transition: transform 225ms cubic-bezier(0, 0, 0.2, 1);
|
|
22
|
-
overflow: hidden;
|
|
23
|
-
display: flex;
|
|
24
|
-
flex-direction: column;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
.ObPageHeader-drawer > .MenuItems-root {
|
|
28
|
-
flex: 1 1 auto;
|
|
29
|
-
min-height: 0;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
.ObPageHeader-drawer.ObPageHeader-drawerOpen {
|
|
33
|
-
transform: translateX(0);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
.ObPageHeader-drawerDocked {
|
|
37
|
-
border-right: 1px solid var(--ds-color-neutral-border-subtle, #dfdfdf);
|
|
38
|
-
z-index: 1100;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
.ObPageHeader-drawerOverlayMode {
|
|
42
|
-
z-index: 1200;
|
|
43
|
-
box-shadow:
|
|
44
|
-
rgb(0 0 0 / 20%) 0px 8px 10px -5px,
|
|
45
|
-
rgb(0 0 0 / 14%) 0px 16px 24px 2px,
|
|
46
|
-
rgb(0 0 0 / 12%) 0px 6px 30px 5px;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
.ObPageHeader-drawerOverlay {
|
|
50
|
-
position: absolute;
|
|
51
|
-
inset: 0;
|
|
52
|
-
background-color: rgb(0 0 0 / 50%);
|
|
53
|
-
z-index: 1199;
|
|
54
|
-
opacity: 0;
|
|
55
|
-
pointer-events: none;
|
|
56
|
-
transition: opacity 225ms cubic-bezier(0.4, 0, 0.2, 1);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
.ObPageHeader-drawerOverlay.ObPageHeader-drawerOpen {
|
|
60
|
-
opacity: 1;
|
|
61
|
-
pointer-events: auto;
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
@media print {
|
|
66
|
-
.ObPageHeader-drawer,
|
|
67
|
-
.ObPageHeader-drawerOverlay {
|
|
68
|
-
display: none !important;
|
|
69
|
-
}
|
|
70
|
-
}
|
|
1
|
+
/* --- drawer --- */
|
|
2
|
+
|
|
3
|
+
@media screen {
|
|
4
|
+
.ObPageHeader-drawer {
|
|
5
|
+
/*
|
|
6
|
+
* Absolute, not fixed: the drawer belongs to the page, so it stays clear of any site
|
|
7
|
+
* chrome rendered above the app. Raising z-index instead would put the menu over that
|
|
8
|
+
* chrome and hide it, and #root often creates its own stacking context anyway.
|
|
9
|
+
* Requires Page (which is position: relative); use layout="app" so it has a height.
|
|
10
|
+
*/
|
|
11
|
+
position: absolute;
|
|
12
|
+
top: 0;
|
|
13
|
+
left: 0;
|
|
14
|
+
bottom: 0;
|
|
15
|
+
width: var(--ob-page-drawer-width, 20rem);
|
|
16
|
+
max-width: 90%;
|
|
17
|
+
box-sizing: border-box;
|
|
18
|
+
background-color: var(--ds-color-neutral-background-default, #fff);
|
|
19
|
+
color: var(--ds-color-neutral-text-default, #1e2b3c);
|
|
20
|
+
transform: translateX(-100%);
|
|
21
|
+
transition: transform 225ms cubic-bezier(0, 0, 0.2, 1);
|
|
22
|
+
overflow: hidden;
|
|
23
|
+
display: flex;
|
|
24
|
+
flex-direction: column;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.ObPageHeader-drawer > .MenuItems-root {
|
|
28
|
+
flex: 1 1 auto;
|
|
29
|
+
min-height: 0;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.ObPageHeader-drawer.ObPageHeader-drawerOpen {
|
|
33
|
+
transform: translateX(0);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.ObPageHeader-drawerDocked {
|
|
37
|
+
border-right: 1px solid var(--ds-color-neutral-border-subtle, #dfdfdf);
|
|
38
|
+
z-index: 1100;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.ObPageHeader-drawerOverlayMode {
|
|
42
|
+
z-index: 1200;
|
|
43
|
+
box-shadow:
|
|
44
|
+
rgb(0 0 0 / 20%) 0px 8px 10px -5px,
|
|
45
|
+
rgb(0 0 0 / 14%) 0px 16px 24px 2px,
|
|
46
|
+
rgb(0 0 0 / 12%) 0px 6px 30px 5px;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.ObPageHeader-drawerOverlay {
|
|
50
|
+
position: absolute;
|
|
51
|
+
inset: 0;
|
|
52
|
+
background-color: rgb(0 0 0 / 50%);
|
|
53
|
+
z-index: 1199;
|
|
54
|
+
opacity: 0;
|
|
55
|
+
pointer-events: none;
|
|
56
|
+
transition: opacity 225ms cubic-bezier(0.4, 0, 0.2, 1);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.ObPageHeader-drawerOverlay.ObPageHeader-drawerOpen {
|
|
60
|
+
opacity: 1;
|
|
61
|
+
pointer-events: auto;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
@media print {
|
|
66
|
+
.ObPageHeader-drawer,
|
|
67
|
+
.ObPageHeader-drawerOverlay {
|
|
68
|
+
display: none !important;
|
|
69
|
+
}
|
|
70
|
+
}
|