@olenbetong/appframe-ds 1.8.0 → 1.9.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 +8 -0
- package/package.json +2 -2
- package/src/AfLookup/Lookup.css +9 -1
- package/src/AfLookup/Lookup.tsx +8 -10
- package/src/AfLookup/LookupCombobox.tsx +1 -9
- package/src/AfLookup/LookupEdit.tsx +3 -3
- package/src/AfLookup/LookupGrid.css +3 -2
- package/src/AfLookup/LookupGrid.tsx +8 -4
- package/src/autocomplete/Autocomplete.css +6 -17
- package/src/binding/BoundNumberInput.tsx +42 -42
- 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.css +23 -23
- package/src/binding/DataEditToolbar.tsx +84 -84
- 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/binding/index.ts +20 -20
- package/src/container/Container.tsx +50 -50
- package/src/container/index.ts +1 -1
- package/src/cutoff/CutOffPicker.css +12 -12
- package/src/cutoff/CutOffPicker.tsx +165 -165
- package/src/cutoff/context.tsx +194 -194
- package/src/cutoff/dsCutOffDates.ts +21 -21
- package/src/cutoff/index.ts +4 -4
- package/src/cutoff/utils.test.ts +59 -59
- package/src/cutoff/utils.ts +276 -276
- package/src/filter/ChipListInput.tsx +79 -79
- package/src/filter/FieldFilterPanel.css +150 -150
- package/src/filter/FieldFilterPanel.tsx +303 -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 +285 -285
- package/src/filter/FilterEditor.tsx +75 -75
- 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 +195 -201
- package/src/filter/FilterStringField.tsx +72 -78
- package/src/filter/FilterValueEditor.tsx +239 -239
- package/src/filter/SavedFilterTree.css +107 -107
- package/src/filter/SavedFilterTree.test.tsx +94 -94
- package/src/filter/SavedFilterTree.tsx +218 -218
- 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 +4 -7
- package/src/grid/AfGridError.tsx +2 -2
- package/src/grid/AfHeaderFilterCell.tsx +0 -3
- package/src/grid/AfHeaderFilterPanel.tsx +1 -3
- package/src/grid/GridEditLookup.tsx +0 -2
- package/src/grid/Toolbar.tsx +9 -38
- package/src/grid/index.css +44 -0
- package/src/grid/index.tsx +12 -3
- package/src/grid/slots/index.tsx +15 -40
- package/src/grid/useAfGridApi.ts +0 -1
- package/src/grid/useAfPersistedState.ts +4 -8
- package/src/grid/useAfSizeMode.ts +19 -0
- package/src/i18n.ts +13 -13
- package/src/index.ts +14 -14
- package/src/input/NumberInput.tsx +0 -2
- package/src/layout/AppMenu.tsx +74 -74
- package/src/layout/AppMenuDrawer.css +71 -71
- package/src/layout/AppMenuDrawer.tsx +122 -122
- package/src/layout/DialogMotion.css +79 -79
- package/src/layout/ErrorBoundary.tsx +162 -162
- package/src/layout/PageContainer.css +48 -48
- package/src/layout/PageContainer.tsx +41 -41
- package/src/layout/ResponsiveDialog.css +86 -86
- package/src/layout/ResponsiveDialog.tsx +55 -55
- package/src/layout/index.tsx +70 -70
- package/src/link/Link.tsx +23 -23
- package/src/link/index.ts +1 -1
- package/src/list/ItemList.css +2 -1
- package/src/mdi/Mdi.test.tsx +91 -91
- package/src/mdi/Mdi.tsx +141 -141
- package/src/mdi/MdiConfirmCloseDialog.tsx +41 -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 -86
- 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 +57 -57
- package/src/page/PageBlock.css +64 -64
- package/src/page/PageBlock.tsx +93 -93
- package/src/page/PageHeader.css +88 -88
- package/src/page/PageHeader.tsx +115 -115
- package/src/page/PageTitle.css +5 -5
- package/src/page/PageTitle.tsx +21 -21
- package/src/page/index.ts +4 -4
- package/src/paper/Paper.tsx +52 -52
- package/src/progress/LinearProgress.css +121 -121
- package/src/progress/LinearProgress.tsx +106 -106
- package/src/progress/index.ts +1 -1
- package/src/report-downloader/ReportDownloader.css +67 -67
- package/src/report-downloader/components/StatusItem.tsx +87 -87
- 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 +27 -27
- package/src/theme/sizeMode.ts +62 -0
- package/src/theme/useDsColorScheme.ts +53 -53
package/src/grid/Toolbar.tsx
CHANGED
|
@@ -7,9 +7,6 @@ import {
|
|
|
7
7
|
} from "@mui/x-data-grid-premium";
|
|
8
8
|
import {
|
|
9
9
|
ArrowsCirclepathIcon,
|
|
10
|
-
Density1Icon,
|
|
11
|
-
Density2Icon,
|
|
12
|
-
Density3Icon,
|
|
13
10
|
FileCsvIcon,
|
|
14
11
|
FileExcelIcon,
|
|
15
12
|
FilterIcon,
|
|
@@ -30,7 +27,7 @@ import { getLocalizedString } from "../i18n.js";
|
|
|
30
27
|
|
|
31
28
|
export function GridToolbarAddButton({ onClick }: { onClick: () => void }) {
|
|
32
29
|
return (
|
|
33
|
-
<Button variant="tertiary"
|
|
30
|
+
<Button variant="tertiary" onClick={onClick}>
|
|
34
31
|
<PlusIcon aria-hidden />
|
|
35
32
|
{getLocalizedString("Add")}
|
|
36
33
|
</Button>
|
|
@@ -60,7 +57,6 @@ export function GridToolbarRefreshButton({ iconOnly }: GridToolbarIconOnlyProps
|
|
|
60
57
|
return (
|
|
61
58
|
<Button
|
|
62
59
|
variant="tertiary"
|
|
63
|
-
data-size="sm"
|
|
64
60
|
icon={iconOnly}
|
|
65
61
|
aria-label={iconOnly ? label : undefined}
|
|
66
62
|
title={iconOnly ? label : undefined}
|
|
@@ -79,7 +75,7 @@ export function GridToolbarColumnsButton() {
|
|
|
79
75
|
let { openColumnsPanel } = useAfGridContext();
|
|
80
76
|
|
|
81
77
|
return (
|
|
82
|
-
<Button variant="tertiary"
|
|
78
|
+
<Button variant="tertiary" onClick={openColumnsPanel}>
|
|
83
79
|
<TableIcon aria-hidden />
|
|
84
80
|
{getLocalizedString("Columns")}
|
|
85
81
|
</Button>
|
|
@@ -98,8 +94,8 @@ export function GridToolbarFilterButton({ iconOnly }: GridToolbarIconOnlyProps =
|
|
|
98
94
|
let name = filterCount > 0 ? `${label} (${filterCount})` : label;
|
|
99
95
|
return (
|
|
100
96
|
<BadgePosition placement="top-right" overlap="circle">
|
|
101
|
-
{filterCount > 0 && <Badge data-color="brand"
|
|
102
|
-
<Button variant="tertiary"
|
|
97
|
+
{filterCount > 0 && <Badge data-color="brand" count={filterCount} aria-hidden="true" />}
|
|
98
|
+
<Button variant="tertiary" icon aria-label={name} title={name} onClick={openFilterBuilder}>
|
|
103
99
|
<FilterIcon aria-hidden />
|
|
104
100
|
</Button>
|
|
105
101
|
</BadgePosition>
|
|
@@ -107,7 +103,7 @@ export function GridToolbarFilterButton({ iconOnly }: GridToolbarIconOnlyProps =
|
|
|
107
103
|
}
|
|
108
104
|
|
|
109
105
|
return (
|
|
110
|
-
<Button variant="tertiary"
|
|
106
|
+
<Button variant="tertiary" onClick={openFilterBuilder}>
|
|
111
107
|
<FilterIcon aria-hidden />
|
|
112
108
|
{label}
|
|
113
109
|
{filterCount > 0 ? ` (${filterCount})` : ""}
|
|
@@ -123,7 +119,7 @@ export function GridToolbarResetLayoutButton() {
|
|
|
123
119
|
}
|
|
124
120
|
|
|
125
121
|
return (
|
|
126
|
-
<Button variant="tertiary"
|
|
122
|
+
<Button variant="tertiary" onClick={handleReset}>
|
|
127
123
|
<ArrowsCirclepathIcon aria-hidden />
|
|
128
124
|
{getLocalizedString("Reset layout")}
|
|
129
125
|
</Button>
|
|
@@ -182,7 +178,7 @@ export function AfGridToolbarExcelExportButton({ fileName, title }: { fileName?:
|
|
|
182
178
|
let dataObject = useDataObject();
|
|
183
179
|
|
|
184
180
|
return (
|
|
185
|
-
<Button variant="tertiary"
|
|
181
|
+
<Button variant="tertiary" onClick={() => handleExportToExcel(apiRef, dataObject, fileName, title)}>
|
|
186
182
|
<FileExcelIcon aria-hidden />
|
|
187
183
|
{getLocalizedString("Export")}
|
|
188
184
|
</Button>
|
|
@@ -195,7 +191,7 @@ export type GridToolbarMoreOptionsButtonProps = React.PropsWithChildren<{
|
|
|
195
191
|
}>;
|
|
196
192
|
|
|
197
193
|
/**
|
|
198
|
-
* The toolbar's "more" menu: exports
|
|
194
|
+
* The toolbar's "more" menu: exports and layout reset. `children` are extra
|
|
199
195
|
* `Dropdown.Item`s placed first. The menu closes when an item is chosen.
|
|
200
196
|
*/
|
|
201
197
|
export function GridToolbarMoreOptionsButton({ showColumns, children }: GridToolbarMoreOptionsButtonProps = {}) {
|
|
@@ -208,19 +204,12 @@ export function GridToolbarMoreOptionsButton({ showColumns, children }: GridTool
|
|
|
208
204
|
<Dropdown.Trigger
|
|
209
205
|
variant="tertiary"
|
|
210
206
|
icon
|
|
211
|
-
data-size="sm"
|
|
212
207
|
aria-label={getLocalizedString("More options")}
|
|
213
208
|
title={getLocalizedString("More options")}
|
|
214
209
|
>
|
|
215
210
|
<MenuElipsisVerticalIcon aria-hidden />
|
|
216
211
|
</Dropdown.Trigger>
|
|
217
|
-
<Dropdown
|
|
218
|
-
open={open}
|
|
219
|
-
onOpen={() => setOpen(true)}
|
|
220
|
-
onClose={() => setOpen(false)}
|
|
221
|
-
placement="bottom-end"
|
|
222
|
-
data-size="sm"
|
|
223
|
-
>
|
|
212
|
+
<Dropdown open={open} onOpen={() => setOpen(true)} onClose={() => setOpen(false)} placement="bottom-end">
|
|
224
213
|
<Dropdown.List
|
|
225
214
|
onClick={(event) => {
|
|
226
215
|
if ((event.target as HTMLElement).closest("button, a")) setOpen(false);
|
|
@@ -242,24 +231,6 @@ export function GridToolbarMoreOptionsButton({ showColumns, children }: GridTool
|
|
|
242
231
|
</Dropdown.Button>
|
|
243
232
|
</Dropdown.Item>
|
|
244
233
|
<AfGridExcelExportMenuItem />
|
|
245
|
-
<Dropdown.Item>
|
|
246
|
-
<Dropdown.Button onClick={() => apiRef.current.setDensity("compact")}>
|
|
247
|
-
<Density1Icon aria-hidden />
|
|
248
|
-
{getLocalizedString("Compact")}
|
|
249
|
-
</Dropdown.Button>
|
|
250
|
-
</Dropdown.Item>
|
|
251
|
-
<Dropdown.Item>
|
|
252
|
-
<Dropdown.Button onClick={() => apiRef.current.setDensity("standard")}>
|
|
253
|
-
<Density2Icon aria-hidden />
|
|
254
|
-
{getLocalizedString("Standard")}
|
|
255
|
-
</Dropdown.Button>
|
|
256
|
-
</Dropdown.Item>
|
|
257
|
-
<Dropdown.Item>
|
|
258
|
-
<Dropdown.Button onClick={() => apiRef.current.setDensity("comfortable")}>
|
|
259
|
-
<Density3Icon aria-hidden />
|
|
260
|
-
{getLocalizedString("Comfortable")}
|
|
261
|
-
</Dropdown.Button>
|
|
262
|
-
</Dropdown.Item>
|
|
263
234
|
<Dropdown.Item>
|
|
264
235
|
<Dropdown.Button onClick={() => (apiRef.current as AfGridApi).resetLayout?.()}>
|
|
265
236
|
<ArrowsCirclepathIcon aria-hidden />
|
package/src/grid/index.css
CHANGED
|
@@ -2,6 +2,50 @@
|
|
|
2
2
|
height: 100%;
|
|
3
3
|
}
|
|
4
4
|
|
|
5
|
+
/*
|
|
6
|
+
* The grid follows the Designsystemet size mode it sits in instead of MUI's density and type
|
|
7
|
+
* scale: its text is the size mode's body text, like the inputs around it, and its row and header
|
|
8
|
+
* heights come from the same mode (`useAfSizeMode`).
|
|
9
|
+
*/
|
|
10
|
+
.AfGrid-root .MuiDataGrid-root {
|
|
11
|
+
font-size: inherit;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/*
|
|
15
|
+
* The footer is MUI's own, with a 52px minimum height and pagination text in MUI's type scale; it
|
|
16
|
+
* is as tall as a control and its text is the grid's.
|
|
17
|
+
*/
|
|
18
|
+
.AfGrid-root {
|
|
19
|
+
.MuiDataGrid-footerContainer,
|
|
20
|
+
.MuiTablePagination-toolbar {
|
|
21
|
+
min-height: var(--ds-size-12, 52px);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.MuiTablePagination-root,
|
|
25
|
+
.MuiTablePagination-selectLabel,
|
|
26
|
+
.MuiTablePagination-displayedRows {
|
|
27
|
+
font-size: inherit;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/*
|
|
32
|
+
* Controls in a data cell - editors, lookup buttons, the row's action buttons - fit the row, which
|
|
33
|
+
* is `--ds-size-10` tall, rather than taking Designsystemet's `--ds-size-12` control height, which
|
|
34
|
+
* would overflow it at every size. The custom properties are set on the components themselves,
|
|
35
|
+
* so they are overridden there and not on the cell.
|
|
36
|
+
*/
|
|
37
|
+
.AfGrid-root .MuiDataGrid-cell {
|
|
38
|
+
.ds-input {
|
|
39
|
+
--dsc-input-size: var(--ds-size-10, 2.25rem);
|
|
40
|
+
--dsc-input-padding-block: 0;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.ds-button {
|
|
44
|
+
--dsc-button-size: var(--ds-size-8, 2rem);
|
|
45
|
+
--dsc-button-padding: 0 var(--ds-size-2, 0.5rem);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
5
49
|
.AfGrid-loading {
|
|
6
50
|
display: flex;
|
|
7
51
|
align-items: center;
|
package/src/grid/index.tsx
CHANGED
|
@@ -20,7 +20,7 @@ import { type DataObject, uid } from "@olenbetong/appframe-data";
|
|
|
20
20
|
import { DataObjectProvider, useLoading } from "@olenbetong/appframe-react";
|
|
21
21
|
|
|
22
22
|
import clsx from "clsx";
|
|
23
|
-
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
23
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
24
24
|
import { FilterBuilder } from "../filter/index.js";
|
|
25
25
|
import { AfGridColumnsPanel } from "./AfGridColumnsPanel.js";
|
|
26
26
|
import { AfHeaderFilterCell } from "./AfHeaderFilterCell.js";
|
|
@@ -67,6 +67,7 @@ import { useAfKeepFocusAfterEdit } from "./useAfKeepFocusAfterEdit.js";
|
|
|
67
67
|
import { useAfRowEditModel } from "./useAfRowEditModel.js";
|
|
68
68
|
import { useAfRowGrouping } from "./useAfRowGrouping.js";
|
|
69
69
|
import { useAfServerAggregation } from "./useAfServerAggregation.js";
|
|
70
|
+
import { useAfSizeMode } from "./useAfSizeMode.js";
|
|
70
71
|
import { useAfSortModel } from "./useAfSortModel.js";
|
|
71
72
|
import { getLocalizedString } from "../i18n.js";
|
|
72
73
|
|
|
@@ -442,6 +443,8 @@ function AfGrid_(props: AfGridProps) {
|
|
|
442
443
|
let filterFields = useAfFilterFields(dataObject, columnsProp);
|
|
443
444
|
|
|
444
445
|
let keyEvents = useAfKeyBindings({ apiRef, dataObject });
|
|
446
|
+
let rootRef = useRef<HTMLDivElement>(null);
|
|
447
|
+
let sizeMode = useAfSizeMode(rootRef);
|
|
445
448
|
|
|
446
449
|
useEffect(() => {
|
|
447
450
|
if (!snackbar) return;
|
|
@@ -499,7 +502,7 @@ function AfGrid_(props: AfGridProps) {
|
|
|
499
502
|
return (
|
|
500
503
|
<DataObjectProvider dataObject={dataObject}>
|
|
501
504
|
<AfGridContextProvider value={gridContext}>
|
|
502
|
-
<div className="AfGrid-root" style={newItemRow.rootStyle} {...keyEvents} tabIndex={-1}>
|
|
505
|
+
<div ref={rootRef} className="AfGrid-root" style={newItemRow.rootStyle} {...keyEvents} tabIndex={-1}>
|
|
503
506
|
<DataGrid
|
|
504
507
|
apiRef={apiRef}
|
|
505
508
|
rows={data}
|
|
@@ -519,7 +522,13 @@ function AfGrid_(props: AfGridProps) {
|
|
|
519
522
|
headerFilters
|
|
520
523
|
{...paging}
|
|
521
524
|
showToolbar
|
|
525
|
+
{...sizeMode}
|
|
522
526
|
{...other}
|
|
527
|
+
/*
|
|
528
|
+
* The size mode sets the row height (`useAfSizeMode`), so MUI's density stays at the
|
|
529
|
+
* standard factor of 1 and is not offered or restored.
|
|
530
|
+
*/
|
|
531
|
+
density="standard"
|
|
523
532
|
{...grouping.gridProps}
|
|
524
533
|
/*
|
|
525
534
|
* Spread rather than passed, because the grid merges props over its defaults with
|
|
@@ -580,7 +589,7 @@ function AfGrid_(props: AfGridProps) {
|
|
|
580
589
|
/>
|
|
581
590
|
{!!snackbar && (
|
|
582
591
|
<div className="AfGrid-snackbar">
|
|
583
|
-
<Alert data-color="danger"
|
|
592
|
+
<Alert data-color="danger" onClick={() => setSnackbar(null)}>
|
|
584
593
|
{snackbar}
|
|
585
594
|
</Alert>
|
|
586
595
|
</div>
|
package/src/grid/slots/index.tsx
CHANGED
|
@@ -17,22 +17,11 @@ import type { GridSlotProps, GridSlotsComponent } from "@mui/x-data-grid-premium
|
|
|
17
17
|
import clsx from "clsx";
|
|
18
18
|
import { type Ref, useCallback } from "react";
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
*
|
|
24
|
-
* ever asks for `small` or `medium`, but the toolbar buttons also allow `large`.
|
|
20
|
+
/*
|
|
21
|
+
* The slots take no size from MUI's `size` prop: they inherit the Designsystemet size mode the
|
|
22
|
+
* grid sits in, like everything else on the page. Only the spinner's `data-size` is kept, which
|
|
23
|
+
* picks the spinner's own size rather than the mode.
|
|
25
24
|
*/
|
|
26
|
-
function dsSize(size?: "small" | "medium" | "large"): DsSize {
|
|
27
|
-
switch (size) {
|
|
28
|
-
case "small":
|
|
29
|
-
return "sm";
|
|
30
|
-
case "large":
|
|
31
|
-
return "lg";
|
|
32
|
-
default:
|
|
33
|
-
return "sm";
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
25
|
|
|
37
26
|
/**
|
|
38
27
|
* The grid passes `title` as a `ReactNode`, but Designsystemet's tooltip only
|
|
@@ -47,7 +36,7 @@ function BaseCheckbox(props: GridSlotProps["baseCheckbox"]) {
|
|
|
47
36
|
indeterminate,
|
|
48
37
|
inputRef,
|
|
49
38
|
label,
|
|
50
|
-
size,
|
|
39
|
+
size: _size,
|
|
51
40
|
density: _density,
|
|
52
41
|
fullWidth: _fullWidth,
|
|
53
42
|
touchRippleRef: _touchRippleRef,
|
|
@@ -79,7 +68,6 @@ function BaseCheckbox(props: GridSlotProps["baseCheckbox"]) {
|
|
|
79
68
|
{...slotProps?.htmlInput}
|
|
80
69
|
ref={ref}
|
|
81
70
|
className={clsx("AfGrid-checkbox", className)}
|
|
82
|
-
data-size={dsSize(size)}
|
|
83
71
|
label={label}
|
|
84
72
|
aria-label={label ? undefined : ((other as any)["aria-label"] ?? "")}
|
|
85
73
|
/>
|
|
@@ -87,10 +75,10 @@ function BaseCheckbox(props: GridSlotProps["baseCheckbox"]) {
|
|
|
87
75
|
}
|
|
88
76
|
|
|
89
77
|
function BaseButton(props: GridSlotProps["baseButton"]) {
|
|
90
|
-
let { children, size, startIcon, touchRippleRef: _touchRippleRef, ...other } = props;
|
|
78
|
+
let { children, size: _size, startIcon, touchRippleRef: _touchRippleRef, ...other } = props;
|
|
91
79
|
|
|
92
80
|
return (
|
|
93
|
-
<Button {...(other as any)} variant="tertiary"
|
|
81
|
+
<Button {...(other as any)} variant="tertiary">
|
|
94
82
|
{startIcon}
|
|
95
83
|
{children}
|
|
96
84
|
</Button>
|
|
@@ -98,17 +86,11 @@ function BaseButton(props: GridSlotProps["baseButton"]) {
|
|
|
98
86
|
}
|
|
99
87
|
|
|
100
88
|
function BaseIconButton(props: GridSlotProps["baseIconButton"]) {
|
|
101
|
-
let { children, size, label, color: _color, edge: _edge, touchRippleRef: _touchRippleRef, ...other } = props;
|
|
89
|
+
let { children, size: _size, label, color: _color, edge: _edge, touchRippleRef: _touchRippleRef, ...other } = props;
|
|
102
90
|
|
|
103
91
|
// `GridActionsCellItem` names its button through `aria-label`, not `label`; keep it
|
|
104
92
|
return (
|
|
105
|
-
<Button
|
|
106
|
-
{...(other as any)}
|
|
107
|
-
icon
|
|
108
|
-
variant="tertiary"
|
|
109
|
-
data-size={dsSize(size)}
|
|
110
|
-
aria-label={label ?? (other as any)["aria-label"]}
|
|
111
|
-
>
|
|
93
|
+
<Button {...(other as any)} icon variant="tertiary" aria-label={label ?? (other as any)["aria-label"]}>
|
|
112
94
|
{children}
|
|
113
95
|
</Button>
|
|
114
96
|
);
|
|
@@ -157,7 +139,7 @@ function BaseTooltip(props: GridSlotProps["baseTooltip"]) {
|
|
|
157
139
|
function BaseTextField(props: GridSlotProps["baseTextField"]) {
|
|
158
140
|
let {
|
|
159
141
|
inputRef,
|
|
160
|
-
size,
|
|
142
|
+
size: _size,
|
|
161
143
|
error,
|
|
162
144
|
helperText,
|
|
163
145
|
slotProps,
|
|
@@ -173,7 +155,6 @@ function BaseTextField(props: GridSlotProps["baseTextField"]) {
|
|
|
173
155
|
{...slotProps?.htmlInput}
|
|
174
156
|
ref={inputRef as Ref<HTMLInputElement>}
|
|
175
157
|
className={clsx("AfGrid-textField", className)}
|
|
176
|
-
data-size={dsSize(size)}
|
|
177
158
|
error={error ? helperText : undefined}
|
|
178
159
|
aria-label={other.label ? undefined : ((other as any)["aria-label"] ?? "")}
|
|
179
160
|
/>
|
|
@@ -183,7 +164,7 @@ function BaseTextField(props: GridSlotProps["baseTextField"]) {
|
|
|
183
164
|
function BaseSelect(props: GridSlotProps["baseSelect"]) {
|
|
184
165
|
let {
|
|
185
166
|
children,
|
|
186
|
-
size,
|
|
167
|
+
size: _size,
|
|
187
168
|
label,
|
|
188
169
|
labelId: _labelId,
|
|
189
170
|
native: _native,
|
|
@@ -201,7 +182,6 @@ function BaseSelect(props: GridSlotProps["baseSelect"]) {
|
|
|
201
182
|
{...(other as any)}
|
|
202
183
|
{...slotProps?.htmlInput}
|
|
203
184
|
className={clsx("AfGrid-select", className)}
|
|
204
|
-
data-size={dsSize(size)}
|
|
205
185
|
aria-label={typeof label === "string" ? label : ((other as any)["aria-label"] ?? "")}
|
|
206
186
|
>
|
|
207
187
|
{children}
|
|
@@ -216,23 +196,18 @@ function BaseSelectOption(props: GridSlotProps["baseSelectOption"]) {
|
|
|
216
196
|
}
|
|
217
197
|
|
|
218
198
|
function BaseSwitch(props: GridSlotProps["baseSwitch"]) {
|
|
219
|
-
let { size, label, ...other } = props;
|
|
199
|
+
let { size: _size, label, ...other } = props;
|
|
220
200
|
|
|
221
201
|
return (
|
|
222
|
-
<Switch
|
|
223
|
-
{...(other as any)}
|
|
224
|
-
data-size={dsSize(size)}
|
|
225
|
-
label={label}
|
|
226
|
-
aria-label={label ? undefined : ((other as any)["aria-label"] ?? "")}
|
|
227
|
-
/>
|
|
202
|
+
<Switch {...(other as any)} label={label} aria-label={label ? undefined : ((other as any)["aria-label"] ?? "")} />
|
|
228
203
|
);
|
|
229
204
|
}
|
|
230
205
|
|
|
231
206
|
function BaseChip(props: GridSlotProps["baseChip"]) {
|
|
232
|
-
let { label, size, icon, variant, children: _children, ...other } = props;
|
|
207
|
+
let { label, size: _size, icon, variant, children: _children, ...other } = props;
|
|
233
208
|
|
|
234
209
|
return (
|
|
235
|
-
<Tag {...(other as any)}
|
|
210
|
+
<Tag {...(other as any)} variant={variant === "outlined" ? "outline" : "default"}>
|
|
236
211
|
{icon}
|
|
237
212
|
{label}
|
|
238
213
|
</Tag>
|
package/src/grid/useAfGridApi.ts
CHANGED
|
@@ -52,7 +52,6 @@ export function useAfGridApi(props: Partial<AfGridProps>, apiRef: React.RefObjec
|
|
|
52
52
|
});
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
apiRef.current?.setDensity(propsRef.current.initialState?.density ?? "compact");
|
|
56
55
|
apiRef.current?.setPageSize(propsRef.current.dataObject.options.parameters.maxRecords ?? 100);
|
|
57
56
|
apiRef.current?.setSortModel(
|
|
58
57
|
afSortOrderToMuiSortModel(
|
|
@@ -47,10 +47,8 @@ function removeNonPersistedState(state: GridInitialState, config?: StatePersiste
|
|
|
47
47
|
let pagination: GridInitialState["pagination"] = { paginationModel: {} };
|
|
48
48
|
let sorting: GridInitialState["sorting"] = {};
|
|
49
49
|
|
|
50
|
-
// Persist the relevant state unless explicitly disabled in the config
|
|
51
|
-
|
|
52
|
-
newState.density = state.density;
|
|
53
|
-
}
|
|
50
|
+
// Persist the relevant state unless explicitly disabled in the config. Not the density: the
|
|
51
|
+
// size mode sets the row height, and the grid stays at the standard density.
|
|
54
52
|
if (config?.pinnedColumns !== false) {
|
|
55
53
|
newState.pinnedColumns = state.pinnedColumns;
|
|
56
54
|
}
|
|
@@ -108,10 +106,7 @@ function getInitialState({ columns, persistenceModel, id, initialState, layoutVe
|
|
|
108
106
|
let state: GridInitialState =
|
|
109
107
|
persistedState?.layoutVersion === layoutVersion
|
|
110
108
|
? removeNonPersistedState(persistedState.state, persistenceModel)
|
|
111
|
-
: {
|
|
112
|
-
density: "compact",
|
|
113
|
-
...initialState,
|
|
114
|
-
};
|
|
109
|
+
: { ...initialState };
|
|
115
110
|
|
|
116
111
|
if (!state.columns) {
|
|
117
112
|
state.columns = {};
|
|
@@ -173,6 +168,7 @@ setTimeout(() => {
|
|
|
173
168
|
|
|
174
169
|
export type StatePersistenceConfig = {
|
|
175
170
|
aggregation?: boolean;
|
|
171
|
+
/** @deprecated The size mode sets the row height, and the density is no longer persisted. */
|
|
176
172
|
density?: boolean;
|
|
177
173
|
pinnedColumns?: boolean;
|
|
178
174
|
rowGrouping?: boolean;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { RefObject } from "react";
|
|
2
|
+
|
|
3
|
+
import { useDsSize } from "../theme/sizeMode.js";
|
|
4
|
+
|
|
5
|
+
/** MUI's compact row height, the grid's look before it followed the size mode. */
|
|
6
|
+
const FALLBACK_HEIGHT = 36;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Row and header heights from the Designsystemet size mode the grid sits in, in place of MUI's
|
|
10
|
+
* density: `--ds-size-10`, which is 25px at xs, 31px at sm, 35px at md and 41px at lg with the
|
|
11
|
+
* site's 14px root. The grid renders at the standard density, so these are the heights as they
|
|
12
|
+
* are. The text, toolbar and footer follow the same size mode through the stylesheet and by
|
|
13
|
+
* inheriting `data-size`.
|
|
14
|
+
*/
|
|
15
|
+
export function useAfSizeMode(rootRef: RefObject<HTMLElement | null>) {
|
|
16
|
+
let height = useDsSize(rootRef, 10, FALLBACK_HEIGHT);
|
|
17
|
+
|
|
18
|
+
return { rowHeight: height, columnHeaderHeight: height };
|
|
19
|
+
}
|
package/src/i18n.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* The package's strings live in their own globalization project, `appframe-ds`, so one
|
|
3
|
-
* translation covers every app and the package can word a term its own way. The templates
|
|
4
|
-
* render the dictionary as af.i18n["appframe-ds"]; on a page that does not (a legacy
|
|
5
|
-
* template) the localizer falls back to the article's strings, exactly as before. See
|
|
6
|
-
* `createLocalizer` in @olenbetong/appframe-core.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { createLocalizer } from "@olenbetong/appframe-core";
|
|
10
|
-
|
|
11
|
-
export const PROJECT = "appframe-ds";
|
|
12
|
-
|
|
13
|
-
export const { getLocalizedString, localize } = createLocalizer(PROJECT);
|
|
1
|
+
/*
|
|
2
|
+
* The package's strings live in their own globalization project, `appframe-ds`, so one
|
|
3
|
+
* translation covers every app and the package can word a term its own way. The templates
|
|
4
|
+
* render the dictionary as af.i18n["appframe-ds"]; on a page that does not (a legacy
|
|
5
|
+
* template) the localizer falls back to the article's strings, exactly as before. See
|
|
6
|
+
* `createLocalizer` in @olenbetong/appframe-core.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { createLocalizer } from "@olenbetong/appframe-core";
|
|
10
|
+
|
|
11
|
+
export const PROJECT = "appframe-ds";
|
|
12
|
+
|
|
13
|
+
export const { getLocalizedString, localize } = createLocalizer(PROJECT);
|
package/src/index.ts
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
export * from "./AfLookup/index.js";
|
|
2
|
-
export * from "./autocomplete/index.js";
|
|
3
|
-
export * from "./binding/index.js";
|
|
4
|
-
export * from "./container/index.js";
|
|
5
|
-
export * from "./cutoff/index.js";
|
|
6
|
-
export * from "./filter/index.js";
|
|
7
|
-
export * from "./input/index.js";
|
|
8
|
-
export * from "./layout/index.js";
|
|
9
|
-
export * from "./link/index.js";
|
|
10
|
-
export * from "./list/index.js";
|
|
11
|
-
export * from "./page/index.js";
|
|
12
|
-
export * from "./paper/index.js";
|
|
13
|
-
export * from "./progress/index.js";
|
|
14
|
-
export * from "./sortable/index.js";
|
|
1
|
+
export * from "./AfLookup/index.js";
|
|
2
|
+
export * from "./autocomplete/index.js";
|
|
3
|
+
export * from "./binding/index.js";
|
|
4
|
+
export * from "./container/index.js";
|
|
5
|
+
export * from "./cutoff/index.js";
|
|
6
|
+
export * from "./filter/index.js";
|
|
7
|
+
export * from "./input/index.js";
|
|
8
|
+
export * from "./layout/index.js";
|
|
9
|
+
export * from "./link/index.js";
|
|
10
|
+
export * from "./list/index.js";
|
|
11
|
+
export * from "./page/index.js";
|
|
12
|
+
export * from "./paper/index.js";
|
|
13
|
+
export * from "./progress/index.js";
|
|
14
|
+
export * from "./sortable/index.js";
|
|
@@ -237,7 +237,6 @@ export function NumberInput({
|
|
|
237
237
|
icon
|
|
238
238
|
type="button"
|
|
239
239
|
variant="tertiary"
|
|
240
|
-
data-size="sm"
|
|
241
240
|
className="ObNumberInput-stepper"
|
|
242
241
|
tabIndex={-1}
|
|
243
242
|
disabled={atMin}
|
|
@@ -250,7 +249,6 @@ export function NumberInput({
|
|
|
250
249
|
icon
|
|
251
250
|
type="button"
|
|
252
251
|
variant="tertiary"
|
|
253
|
-
data-size="sm"
|
|
254
252
|
className="ObNumberInput-stepper"
|
|
255
253
|
tabIndex={-1}
|
|
256
254
|
disabled={atMax}
|
package/src/layout/AppMenu.tsx
CHANGED
|
@@ -1,74 +1,74 @@
|
|
|
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
|
+
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
|
+
}
|