@olenbetong/appframe-ds 1.7.2 → 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 +14 -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 +72 -36
- package/src/grid/index.css +44 -0
- package/src/grid/index.tsx +16 -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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Button, Dropdown } from "@digdir/designsystemet-react";
|
|
1
|
+
import { Badge, BadgePosition, Button, Dropdown } from "@digdir/designsystemet-react";
|
|
2
2
|
import {
|
|
3
3
|
type GridApi,
|
|
4
4
|
type GridExportMenuItemProps,
|
|
@@ -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,
|
|
@@ -20,6 +17,8 @@ import {
|
|
|
20
17
|
import { type DataObject, exportOptionsFromDataObject } from "@olenbetong/appframe-data";
|
|
21
18
|
import { useDataObject, useLoading, usePermissions } from "@olenbetong/appframe-react";
|
|
22
19
|
|
|
20
|
+
import { useState } from "react";
|
|
21
|
+
|
|
23
22
|
import { openDataObjectReportWithProgress } from "../report-downloader/index.js";
|
|
24
23
|
import { useAfGridContext } from "./AfGridContext.js";
|
|
25
24
|
import { addRow } from "./editing.js";
|
|
@@ -28,7 +27,7 @@ import { getLocalizedString } from "../i18n.js";
|
|
|
28
27
|
|
|
29
28
|
export function GridToolbarAddButton({ onClick }: { onClick: () => void }) {
|
|
30
29
|
return (
|
|
31
|
-
<Button variant="tertiary"
|
|
30
|
+
<Button variant="tertiary" onClick={onClick}>
|
|
32
31
|
<PlusIcon aria-hidden />
|
|
33
32
|
{getLocalizedString("Add")}
|
|
34
33
|
</Button>
|
|
@@ -45,16 +44,29 @@ export function GridToolbarAddIfAllowedButton({ disableInsert }: { disableInsert
|
|
|
45
44
|
) : null;
|
|
46
45
|
}
|
|
47
46
|
|
|
48
|
-
export
|
|
47
|
+
export type GridToolbarIconOnlyProps = {
|
|
48
|
+
/** Show the icon alone, with the label as its accessible name and tooltip. */
|
|
49
|
+
iconOnly?: boolean;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export function GridToolbarRefreshButton({ iconOnly }: GridToolbarIconOnlyProps = {}) {
|
|
49
53
|
let dataObject = useDataObject();
|
|
50
54
|
let isLoading = useLoading(dataObject);
|
|
55
|
+
let label = getLocalizedString("Refresh");
|
|
51
56
|
|
|
52
57
|
return (
|
|
53
|
-
<Button
|
|
58
|
+
<Button
|
|
59
|
+
variant="tertiary"
|
|
60
|
+
icon={iconOnly}
|
|
61
|
+
aria-label={iconOnly ? label : undefined}
|
|
62
|
+
title={iconOnly ? label : undefined}
|
|
63
|
+
loading={isLoading}
|
|
64
|
+
onClick={() => dataObject.refreshDataSource()}
|
|
65
|
+
>
|
|
54
66
|
{/* Designsystemet prepends the spinner rather than replacing the children, so
|
|
55
67
|
the icon is hidden while loading to keep the button from changing width */}
|
|
56
68
|
{!isLoading && <ArrowsCirclepathIcon aria-hidden />}
|
|
57
|
-
{
|
|
69
|
+
{!iconOnly && label}
|
|
58
70
|
</Button>
|
|
59
71
|
);
|
|
60
72
|
}
|
|
@@ -63,7 +75,7 @@ export function GridToolbarColumnsButton() {
|
|
|
63
75
|
let { openColumnsPanel } = useAfGridContext();
|
|
64
76
|
|
|
65
77
|
return (
|
|
66
|
-
<Button variant="tertiary"
|
|
78
|
+
<Button variant="tertiary" onClick={openColumnsPanel}>
|
|
67
79
|
<TableIcon aria-hidden />
|
|
68
80
|
{getLocalizedString("Columns")}
|
|
69
81
|
</Button>
|
|
@@ -74,13 +86,26 @@ export function GridToolbarColumnsButton() {
|
|
|
74
86
|
* Opens the filter builder instead of the grid's own filter panel. The count
|
|
75
87
|
* reflects the conditions in the builder's filter, not the header filters.
|
|
76
88
|
*/
|
|
77
|
-
export function GridToolbarFilterButton() {
|
|
89
|
+
export function GridToolbarFilterButton({ iconOnly }: GridToolbarIconOnlyProps = {}) {
|
|
78
90
|
let { openFilterBuilder, filterCount } = useAfGridContext();
|
|
91
|
+
let label = getLocalizedString("Filter");
|
|
92
|
+
|
|
93
|
+
if (iconOnly) {
|
|
94
|
+
let name = filterCount > 0 ? `${label} (${filterCount})` : label;
|
|
95
|
+
return (
|
|
96
|
+
<BadgePosition placement="top-right" overlap="circle">
|
|
97
|
+
{filterCount > 0 && <Badge data-color="brand" count={filterCount} aria-hidden="true" />}
|
|
98
|
+
<Button variant="tertiary" icon aria-label={name} title={name} onClick={openFilterBuilder}>
|
|
99
|
+
<FilterIcon aria-hidden />
|
|
100
|
+
</Button>
|
|
101
|
+
</BadgePosition>
|
|
102
|
+
);
|
|
103
|
+
}
|
|
79
104
|
|
|
80
105
|
return (
|
|
81
|
-
<Button variant="tertiary"
|
|
106
|
+
<Button variant="tertiary" onClick={openFilterBuilder}>
|
|
82
107
|
<FilterIcon aria-hidden />
|
|
83
|
-
{
|
|
108
|
+
{label}
|
|
84
109
|
{filterCount > 0 ? ` (${filterCount})` : ""}
|
|
85
110
|
</Button>
|
|
86
111
|
);
|
|
@@ -94,7 +119,7 @@ export function GridToolbarResetLayoutButton() {
|
|
|
94
119
|
}
|
|
95
120
|
|
|
96
121
|
return (
|
|
97
|
-
<Button variant="tertiary"
|
|
122
|
+
<Button variant="tertiary" onClick={handleReset}>
|
|
98
123
|
<ArrowsCirclepathIcon aria-hidden />
|
|
99
124
|
{getLocalizedString("Reset layout")}
|
|
100
125
|
</Button>
|
|
@@ -153,23 +178,52 @@ export function AfGridToolbarExcelExportButton({ fileName, title }: { fileName?:
|
|
|
153
178
|
let dataObject = useDataObject();
|
|
154
179
|
|
|
155
180
|
return (
|
|
156
|
-
<Button variant="tertiary"
|
|
181
|
+
<Button variant="tertiary" onClick={() => handleExportToExcel(apiRef, dataObject, fileName, title)}>
|
|
157
182
|
<FileExcelIcon aria-hidden />
|
|
158
183
|
{getLocalizedString("Export")}
|
|
159
184
|
</Button>
|
|
160
185
|
);
|
|
161
186
|
}
|
|
162
187
|
|
|
163
|
-
export
|
|
188
|
+
export type GridToolbarMoreOptionsButtonProps = React.PropsWithChildren<{
|
|
189
|
+
/** Offer the column panel in the menu, for toolbars without a Columns button. */
|
|
190
|
+
showColumns?: boolean;
|
|
191
|
+
}>;
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* The toolbar's "more" menu: exports and layout reset. `children` are extra
|
|
195
|
+
* `Dropdown.Item`s placed first. The menu closes when an item is chosen.
|
|
196
|
+
*/
|
|
197
|
+
export function GridToolbarMoreOptionsButton({ showColumns, children }: GridToolbarMoreOptionsButtonProps = {}) {
|
|
164
198
|
let apiRef = useGridApiContext();
|
|
199
|
+
let { openColumnsPanel } = useAfGridContext();
|
|
200
|
+
let [open, setOpen] = useState(false);
|
|
165
201
|
|
|
166
202
|
return (
|
|
167
203
|
<Dropdown.TriggerContext>
|
|
168
|
-
<Dropdown.Trigger
|
|
204
|
+
<Dropdown.Trigger
|
|
205
|
+
variant="tertiary"
|
|
206
|
+
icon
|
|
207
|
+
aria-label={getLocalizedString("More options")}
|
|
208
|
+
title={getLocalizedString("More options")}
|
|
209
|
+
>
|
|
169
210
|
<MenuElipsisVerticalIcon aria-hidden />
|
|
170
211
|
</Dropdown.Trigger>
|
|
171
|
-
<Dropdown placement="bottom-end"
|
|
172
|
-
<Dropdown.List
|
|
212
|
+
<Dropdown open={open} onOpen={() => setOpen(true)} onClose={() => setOpen(false)} placement="bottom-end">
|
|
213
|
+
<Dropdown.List
|
|
214
|
+
onClick={(event) => {
|
|
215
|
+
if ((event.target as HTMLElement).closest("button, a")) setOpen(false);
|
|
216
|
+
}}
|
|
217
|
+
>
|
|
218
|
+
{children}
|
|
219
|
+
{showColumns && (
|
|
220
|
+
<Dropdown.Item>
|
|
221
|
+
<Dropdown.Button onClick={openColumnsPanel}>
|
|
222
|
+
<TableIcon aria-hidden />
|
|
223
|
+
{getLocalizedString("Columns")}
|
|
224
|
+
</Dropdown.Button>
|
|
225
|
+
</Dropdown.Item>
|
|
226
|
+
)}
|
|
173
227
|
<Dropdown.Item>
|
|
174
228
|
<Dropdown.Button onClick={() => apiRef.current.exportDataAsCsv()}>
|
|
175
229
|
<FileCsvIcon aria-hidden />
|
|
@@ -177,24 +231,6 @@ export function GridToolbarMoreOptionsButton() {
|
|
|
177
231
|
</Dropdown.Button>
|
|
178
232
|
</Dropdown.Item>
|
|
179
233
|
<AfGridExcelExportMenuItem />
|
|
180
|
-
<Dropdown.Item>
|
|
181
|
-
<Dropdown.Button onClick={() => apiRef.current.setDensity("compact")}>
|
|
182
|
-
<Density1Icon aria-hidden />
|
|
183
|
-
{getLocalizedString("Compact")}
|
|
184
|
-
</Dropdown.Button>
|
|
185
|
-
</Dropdown.Item>
|
|
186
|
-
<Dropdown.Item>
|
|
187
|
-
<Dropdown.Button onClick={() => apiRef.current.setDensity("standard")}>
|
|
188
|
-
<Density2Icon aria-hidden />
|
|
189
|
-
{getLocalizedString("Standard")}
|
|
190
|
-
</Dropdown.Button>
|
|
191
|
-
</Dropdown.Item>
|
|
192
|
-
<Dropdown.Item>
|
|
193
|
-
<Dropdown.Button onClick={() => apiRef.current.setDensity("comfortable")}>
|
|
194
|
-
<Density3Icon aria-hidden />
|
|
195
|
-
{getLocalizedString("Comfortable")}
|
|
196
|
-
</Dropdown.Button>
|
|
197
|
-
</Dropdown.Item>
|
|
198
234
|
<Dropdown.Item>
|
|
199
235
|
<Dropdown.Button onClick={() => (apiRef.current as AfGridApi).resetLayout?.()}>
|
|
200
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";
|
|
@@ -41,7 +41,9 @@ import {
|
|
|
41
41
|
GridToolbarColumnsButton,
|
|
42
42
|
GridToolbarContainer,
|
|
43
43
|
GridToolbarFilterButton,
|
|
44
|
+
type GridToolbarIconOnlyProps,
|
|
44
45
|
GridToolbarMoreOptionsButton,
|
|
46
|
+
type GridToolbarMoreOptionsButtonProps,
|
|
45
47
|
GridToolbarRefreshButton,
|
|
46
48
|
GridToolbarResetLayoutButton,
|
|
47
49
|
} from "./Toolbar.js";
|
|
@@ -65,6 +67,7 @@ import { useAfKeepFocusAfterEdit } from "./useAfKeepFocusAfterEdit.js";
|
|
|
65
67
|
import { useAfRowEditModel } from "./useAfRowEditModel.js";
|
|
66
68
|
import { useAfRowGrouping } from "./useAfRowGrouping.js";
|
|
67
69
|
import { useAfServerAggregation } from "./useAfServerAggregation.js";
|
|
70
|
+
import { useAfSizeMode } from "./useAfSizeMode.js";
|
|
68
71
|
import { useAfSortModel } from "./useAfSortModel.js";
|
|
69
72
|
import { getLocalizedString } from "../i18n.js";
|
|
70
73
|
|
|
@@ -109,6 +112,8 @@ export type {
|
|
|
109
112
|
AfGridColumn,
|
|
110
113
|
AfGridExcelExportOptions,
|
|
111
114
|
AfGridNewItemRowPosition,
|
|
115
|
+
GridToolbarIconOnlyProps,
|
|
116
|
+
GridToolbarMoreOptionsButtonProps,
|
|
112
117
|
AfGridRowAction,
|
|
113
118
|
};
|
|
114
119
|
export type { AfGridGrouping, AfGridGroupingProps } from "./useAfRowGrouping.js";
|
|
@@ -438,6 +443,8 @@ function AfGrid_(props: AfGridProps) {
|
|
|
438
443
|
let filterFields = useAfFilterFields(dataObject, columnsProp);
|
|
439
444
|
|
|
440
445
|
let keyEvents = useAfKeyBindings({ apiRef, dataObject });
|
|
446
|
+
let rootRef = useRef<HTMLDivElement>(null);
|
|
447
|
+
let sizeMode = useAfSizeMode(rootRef);
|
|
441
448
|
|
|
442
449
|
useEffect(() => {
|
|
443
450
|
if (!snackbar) return;
|
|
@@ -495,7 +502,7 @@ function AfGrid_(props: AfGridProps) {
|
|
|
495
502
|
return (
|
|
496
503
|
<DataObjectProvider dataObject={dataObject}>
|
|
497
504
|
<AfGridContextProvider value={gridContext}>
|
|
498
|
-
<div className="AfGrid-root" style={newItemRow.rootStyle} {...keyEvents} tabIndex={-1}>
|
|
505
|
+
<div ref={rootRef} className="AfGrid-root" style={newItemRow.rootStyle} {...keyEvents} tabIndex={-1}>
|
|
499
506
|
<DataGrid
|
|
500
507
|
apiRef={apiRef}
|
|
501
508
|
rows={data}
|
|
@@ -515,7 +522,13 @@ function AfGrid_(props: AfGridProps) {
|
|
|
515
522
|
headerFilters
|
|
516
523
|
{...paging}
|
|
517
524
|
showToolbar
|
|
525
|
+
{...sizeMode}
|
|
518
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"
|
|
519
532
|
{...grouping.gridProps}
|
|
520
533
|
/*
|
|
521
534
|
* Spread rather than passed, because the grid merges props over its defaults with
|
|
@@ -576,7 +589,7 @@ function AfGrid_(props: AfGridProps) {
|
|
|
576
589
|
/>
|
|
577
590
|
{!!snackbar && (
|
|
578
591
|
<div className="AfGrid-snackbar">
|
|
579
|
-
<Alert data-color="danger"
|
|
592
|
+
<Alert data-color="danger" onClick={() => setSnackbar(null)}>
|
|
580
593
|
{snackbar}
|
|
581
594
|
</Alert>
|
|
582
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}
|