@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.
Files changed (147) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/package.json +2 -2
  3. package/src/AfLookup/Lookup.css +9 -1
  4. package/src/AfLookup/Lookup.tsx +8 -10
  5. package/src/AfLookup/LookupCombobox.tsx +1 -9
  6. package/src/AfLookup/LookupEdit.tsx +3 -3
  7. package/src/AfLookup/LookupGrid.css +3 -2
  8. package/src/AfLookup/LookupGrid.tsx +8 -4
  9. package/src/autocomplete/Autocomplete.css +6 -17
  10. package/src/binding/BoundNumberInput.tsx +42 -42
  11. package/src/binding/BoundNumericTextField.tsx +102 -102
  12. package/src/binding/BoundTextField.tsx +49 -49
  13. package/src/binding/CancelButton.tsx +30 -30
  14. package/src/binding/CancelIconButton.tsx +30 -30
  15. package/src/binding/DataEditToolbar.css +23 -23
  16. package/src/binding/DataEditToolbar.tsx +84 -84
  17. package/src/binding/DeleteButton.tsx +35 -35
  18. package/src/binding/DeleteIconButton.tsx +35 -35
  19. package/src/binding/RefreshButton.tsx +31 -31
  20. package/src/binding/RefreshIconButton.tsx +33 -33
  21. package/src/binding/RefreshRowIconButton.tsx +33 -33
  22. package/src/binding/SaveButton.tsx +37 -37
  23. package/src/binding/SaveIconButton.tsx +38 -38
  24. package/src/binding/index.ts +20 -20
  25. package/src/container/Container.tsx +50 -50
  26. package/src/container/index.ts +1 -1
  27. package/src/cutoff/CutOffPicker.css +12 -12
  28. package/src/cutoff/CutOffPicker.tsx +165 -165
  29. package/src/cutoff/context.tsx +194 -194
  30. package/src/cutoff/dsCutOffDates.ts +21 -21
  31. package/src/cutoff/index.ts +4 -4
  32. package/src/cutoff/utils.test.ts +59 -59
  33. package/src/cutoff/utils.ts +276 -276
  34. package/src/filter/ChipListInput.tsx +79 -79
  35. package/src/filter/FieldFilterPanel.css +150 -150
  36. package/src/filter/FieldFilterPanel.tsx +303 -309
  37. package/src/filter/FilterBuilder.css +152 -152
  38. package/src/filter/FilterBuilder.test.tsx +153 -153
  39. package/src/filter/FilterBuilder.tsx +435 -435
  40. package/src/filter/FilterEditor.css +231 -231
  41. package/src/filter/FilterEditor.test.tsx +285 -285
  42. package/src/filter/FilterEditor.tsx +75 -75
  43. package/src/filter/FilterGroupEditor.tsx +160 -160
  44. package/src/filter/FilterNameDialog.css +22 -22
  45. package/src/filter/FilterNameDialog.tsx +81 -81
  46. package/src/filter/FilterRow.tsx +117 -117
  47. package/src/filter/FilterShareDialog.css +28 -28
  48. package/src/filter/FilterShareDialog.tsx +195 -201
  49. package/src/filter/FilterStringField.tsx +72 -78
  50. package/src/filter/FilterValueEditor.tsx +239 -239
  51. package/src/filter/SavedFilterTree.css +107 -107
  52. package/src/filter/SavedFilterTree.test.tsx +94 -94
  53. package/src/filter/SavedFilterTree.tsx +218 -218
  54. package/src/filter/fieldFilter.test.ts +158 -158
  55. package/src/filter/fieldFilter.ts +139 -139
  56. package/src/filter/index.ts +27 -27
  57. package/src/filter/types.ts +38 -38
  58. package/src/filter/useDistinctValues.test.ts +102 -102
  59. package/src/filter/useDistinctValues.ts +230 -230
  60. package/src/global.d.ts +11 -11
  61. package/src/grid/AfGridColumnsPanel.tsx +4 -7
  62. package/src/grid/AfGridError.tsx +2 -2
  63. package/src/grid/AfHeaderFilterCell.tsx +0 -3
  64. package/src/grid/AfHeaderFilterPanel.tsx +1 -3
  65. package/src/grid/GridEditLookup.tsx +0 -2
  66. package/src/grid/Toolbar.tsx +72 -36
  67. package/src/grid/index.css +44 -0
  68. package/src/grid/index.tsx +16 -3
  69. package/src/grid/slots/index.tsx +15 -40
  70. package/src/grid/useAfGridApi.ts +0 -1
  71. package/src/grid/useAfPersistedState.ts +4 -8
  72. package/src/grid/useAfSizeMode.ts +19 -0
  73. package/src/i18n.ts +13 -13
  74. package/src/index.ts +14 -14
  75. package/src/input/NumberInput.tsx +0 -2
  76. package/src/layout/AppMenu.tsx +74 -74
  77. package/src/layout/AppMenuDrawer.css +71 -71
  78. package/src/layout/AppMenuDrawer.tsx +122 -122
  79. package/src/layout/DialogMotion.css +79 -79
  80. package/src/layout/ErrorBoundary.tsx +162 -162
  81. package/src/layout/PageContainer.css +48 -48
  82. package/src/layout/PageContainer.tsx +41 -41
  83. package/src/layout/ResponsiveDialog.css +86 -86
  84. package/src/layout/ResponsiveDialog.tsx +55 -55
  85. package/src/layout/index.tsx +70 -70
  86. package/src/link/Link.tsx +23 -23
  87. package/src/link/index.ts +1 -1
  88. package/src/list/ItemList.css +2 -1
  89. package/src/mdi/Mdi.test.tsx +91 -91
  90. package/src/mdi/Mdi.tsx +141 -141
  91. package/src/mdi/MdiConfirmCloseDialog.tsx +41 -47
  92. package/src/mdi/MdiContext.tsx +59 -59
  93. package/src/mdi/MdiDocumentContext.tsx +60 -60
  94. package/src/mdi/MdiPanel.tsx +61 -61
  95. package/src/mdi/MdiRestore.test.tsx +86 -86
  96. package/src/mdi/MdiTab.tsx +30 -30
  97. package/src/mdi/controller.test.ts +382 -382
  98. package/src/mdi/controller.ts +656 -656
  99. package/src/mdi/dockviewHost.ts +132 -132
  100. package/src/mdi/fakeHost.ts +200 -200
  101. package/src/mdi/host.ts +44 -44
  102. package/src/mdi/ids.test.ts +20 -20
  103. package/src/mdi/ids.ts +18 -18
  104. package/src/mdi/index.ts +31 -31
  105. package/src/mdi/layoutRestore.test.ts +156 -156
  106. package/src/mdi/layoutRestore.ts +311 -311
  107. package/src/mdi/layoutSpec.test.ts +143 -143
  108. package/src/mdi/layoutSpec.ts +214 -214
  109. package/src/mdi/layoutStore.test.ts +93 -93
  110. package/src/mdi/layoutStore.ts +99 -99
  111. package/src/mdi/memorySettingsStore.ts +21 -21
  112. package/src/mdi/registry.ts +34 -34
  113. package/src/mdi/testTypes.ts +37 -37
  114. package/src/mdi/theme.ts +22 -22
  115. package/src/mdi/types.ts +214 -214
  116. package/src/page/Page.css +57 -57
  117. package/src/page/PageBlock.css +64 -64
  118. package/src/page/PageBlock.tsx +93 -93
  119. package/src/page/PageHeader.css +88 -88
  120. package/src/page/PageHeader.tsx +115 -115
  121. package/src/page/PageTitle.css +5 -5
  122. package/src/page/PageTitle.tsx +21 -21
  123. package/src/page/index.ts +4 -4
  124. package/src/paper/Paper.tsx +52 -52
  125. package/src/progress/LinearProgress.css +121 -121
  126. package/src/progress/LinearProgress.tsx +106 -106
  127. package/src/progress/index.ts +1 -1
  128. package/src/report-downloader/ReportDownloader.css +67 -67
  129. package/src/report-downloader/components/StatusItem.tsx +87 -87
  130. package/src/report-downloader/components/StatusList.tsx +28 -28
  131. package/src/report-downloader/index.ts +41 -41
  132. package/src/report-downloader/status.tsx +92 -92
  133. package/src/sortable/DropIndicator.css +51 -51
  134. package/src/sortable/DropIndicator.tsx +27 -27
  135. package/src/sortable/SortableItemList.css +21 -21
  136. package/src/sortable/SortableItemList.tsx +264 -264
  137. package/src/sortable/index.ts +8 -8
  138. package/src/sortable/reorderSortOrder.ts +126 -126
  139. package/src/sortable/sortOrder.ts +101 -101
  140. package/src/sortable/types.ts +9 -9
  141. package/src/sortable/useSortableContainer.ts +53 -53
  142. package/src/sortable/useSortableItem.ts +95 -95
  143. package/src/sortable/useSortableList.ts +117 -117
  144. package/src/test/happy-dom-document-class.ts +24 -24
  145. package/src/test/setup.ts +27 -27
  146. package/src/theme/sizeMode.ts +62 -0
  147. package/src/theme/useDsColorScheme.ts +53 -53
@@ -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" data-size="sm" onClick={onClick}>
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 function GridToolbarRefreshButton() {
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 variant="tertiary" data-size="sm" loading={isLoading} onClick={() => dataObject.refreshDataSource()}>
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
- {getLocalizedString("Refresh")}
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" data-size="sm" onClick={openColumnsPanel}>
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" data-size="sm" onClick={openFilterBuilder}>
106
+ <Button variant="tertiary" onClick={openFilterBuilder}>
82
107
  <FilterIcon aria-hidden />
83
- {getLocalizedString("Filter")}
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" data-size="sm" onClick={handleReset}>
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" data-size="sm" onClick={() => handleExportToExcel(apiRef, dataObject, fileName, title)}>
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 function GridToolbarMoreOptionsButton() {
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 variant="tertiary" icon data-size="sm" aria-label={getLocalizedString("More options")}>
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" data-size="sm">
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 />
@@ -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;
@@ -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" data-size="sm" onClick={() => setSnackbar(null)}>
592
+ <Alert data-color="danger" onClick={() => setSnackbar(null)}>
580
593
  {snackbar}
581
594
  </Alert>
582
595
  </div>
@@ -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
- type DsSize = "2xs" | "xs" | "sm" | "md" | "lg";
21
-
22
- /**
23
- * Maps the MUI slot sizes onto the Designsystemet size tokens. The grid only
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" data-size={dsSize(size)}>
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)} data-size={dsSize(size)} variant={variant === "outlined" ? "outline" : "default"}>
210
+ <Tag {...(other as any)} variant={variant === "outlined" ? "outline" : "default"}>
236
211
  {icon}
237
212
  {label}
238
213
  </Tag>
@@ -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
- if (config?.density !== false) {
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}