@olenbetong/appframe-ds 0.8.0 → 0.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 (67) hide show
  1. package/CHANGELOG.md +70 -0
  2. package/package.json +50 -6
  3. package/scripts/copyAssets.mjs +13 -0
  4. package/src/AfLookup/LookupCombobox.tsx +3 -1
  5. package/src/filter/ChipListInput.tsx +79 -0
  6. package/src/filter/FieldFilterPanel.css +127 -0
  7. package/src/filter/FieldFilterPanel.tsx +309 -0
  8. package/src/filter/FilterBuilder.css +152 -0
  9. package/src/filter/FilterBuilder.test.tsx +153 -0
  10. package/src/filter/FilterBuilder.tsx +435 -0
  11. package/src/filter/FilterEditor.css +231 -0
  12. package/src/filter/FilterEditor.test.tsx +259 -0
  13. package/src/filter/FilterEditor.tsx +74 -0
  14. package/src/filter/FilterGroupEditor.tsx +160 -0
  15. package/src/filter/FilterNameDialog.css +22 -0
  16. package/src/filter/FilterNameDialog.tsx +81 -0
  17. package/src/filter/FilterRow.tsx +117 -0
  18. package/src/filter/FilterShareDialog.css +28 -0
  19. package/src/filter/FilterShareDialog.tsx +201 -0
  20. package/src/filter/FilterStringField.tsx +77 -0
  21. package/src/filter/FilterValueEditor.tsx +220 -0
  22. package/src/filter/SavedFilterTree.css +107 -0
  23. package/src/filter/SavedFilterTree.test.tsx +94 -0
  24. package/src/filter/SavedFilterTree.tsx +222 -0
  25. package/src/filter/fieldFilter.test.ts +158 -0
  26. package/src/filter/fieldFilter.ts +139 -0
  27. package/src/filter/index.ts +27 -0
  28. package/src/filter/types.ts +38 -0
  29. package/src/filter/useDistinctValues.test.ts +102 -0
  30. package/src/filter/useDistinctValues.ts +230 -0
  31. package/src/global.d.ts +11 -0
  32. package/src/grid/AfGridColumnsPanel.tsx +118 -0
  33. package/src/grid/AfGridContext.tsx +51 -0
  34. package/src/grid/AfGridError.tsx +46 -0
  35. package/src/grid/AfHeaderFilterCell.tsx +151 -0
  36. package/src/grid/AfHeaderFilterPanel.tsx +111 -0
  37. package/src/grid/Toolbar.tsx +233 -0
  38. package/src/grid/editing.ts +90 -0
  39. package/src/grid/filter.ts +78 -0
  40. package/src/grid/formatters.ts +48 -0
  41. package/src/grid/index.css +187 -0
  42. package/src/grid/index.tsx +376 -0
  43. package/src/grid/license.ts +48 -0
  44. package/src/grid/localization.ts +369 -0
  45. package/src/grid/slots/index.tsx +275 -0
  46. package/src/grid/slots/slots.css +53 -0
  47. package/src/grid/theme.tsx +185 -0
  48. package/src/grid/useAfColumns.tsx +313 -0
  49. package/src/grid/useAfCurrentIndex.ts +55 -0
  50. package/src/grid/useAfData.ts +20 -0
  51. package/src/grid/useAfFilter.ts +160 -0
  52. package/src/grid/useAfFilterFields.ts +55 -0
  53. package/src/grid/useAfGridApi.ts +66 -0
  54. package/src/grid/useAfKeyBindings.ts +36 -0
  55. package/src/grid/useAfPagination.ts +53 -0
  56. package/src/grid/useAfPersistedState.ts +222 -0
  57. package/src/grid/useAfRowEditModel.ts +137 -0
  58. package/src/grid/useAfSortModel.ts +72 -0
  59. package/src/index.ts +1 -0
  60. package/src/input/InputAdornments.tsx +6 -1
  61. package/src/report-downloader/ReportDownloader.css +67 -0
  62. package/src/report-downloader/components/StatusItem.tsx +86 -0
  63. package/src/report-downloader/components/StatusList.tsx +28 -0
  64. package/src/report-downloader/index.ts +41 -0
  65. package/src/report-downloader/status.tsx +92 -0
  66. package/src/test/setup.ts +8 -0
  67. package/vitest.config.ts +10 -0
@@ -0,0 +1,48 @@
1
+ import { LicenseInfo } from "@mui/x-license";
2
+
3
+ let explicitKey: string | undefined;
4
+ let registered = false;
5
+
6
+ /**
7
+ * Sets the MUI X license key used by the grid. Only needed when the key isn't
8
+ * injected at build time — call it before the first grid renders.
9
+ */
10
+ export function setAfGridLicenseKey(key: string) {
11
+ explicitKey = key;
12
+ registered = false;
13
+ registerLicense();
14
+ }
15
+
16
+ function resolveLicenseKey() {
17
+ if (explicitKey) {
18
+ return explicitKey;
19
+ }
20
+
21
+ // Replaced at build time by the `appframe()` Vite plugin. Guarded with
22
+ // `typeof`, because this package is compiled with `tsc` and consumers are
23
+ // free to bundle it without the define.
24
+ if (typeof __MUI_X_LICENSE_KEY__ === "string" && __MUI_X_LICENSE_KEY__) {
25
+ return __MUI_X_LICENSE_KEY__;
26
+ }
27
+
28
+ return import.meta.env?.VITE_MUI_X_LICENSE_KEY;
29
+ }
30
+
31
+ /**
32
+ * Registers the MUI X license key with MUI. Called when the grid module is
33
+ * loaded, so apps don't have to do it themselves.
34
+ */
35
+ export function registerLicense() {
36
+ if (registered) return;
37
+
38
+ registered = true;
39
+ let key = resolveLicenseKey();
40
+
41
+ if (key) {
42
+ LicenseInfo.setLicenseKey(key);
43
+ } else if (import.meta.env?.DEV) {
44
+ console.warn(
45
+ "[appframe-ds] No MUI X license key found. Set the MUI_X_LICENSE_KEY environment variable, or call setAfGridLicenseKey() before rendering a grid.",
46
+ );
47
+ }
48
+ }
@@ -0,0 +1,369 @@
1
+ import type { GridLocaleText } from "@mui/x-data-grid-pro";
2
+ import { getLocalizedString, localize } from "@olenbetong/appframe-core";
3
+
4
+ export type AfGridLocaleTextExt = GridLocaleText & {
5
+ filterOperatorNotContains: string;
6
+ filterOperatorNotEquals: string;
7
+ filterOperatorLike: string;
8
+ filterOperatorNotLike: string;
9
+ filterOperatorIsNoneOf: string;
10
+ headerFilterOperatorNotContains: string;
11
+ headerFilterOperatorNotEquals: string;
12
+ headerFilterOperatorLike: string;
13
+ headerFilterOperatorNotLike: string;
14
+ headerFilterOperatorIsNoneOf: string;
15
+ };
16
+
17
+ export const AF_GRID_LOCALE_TEXT: AfGridLocaleTextExt = {
18
+ // Root
19
+ noRowsLabel: getLocalizedString("No rows"),
20
+ noResultsOverlayLabel: getLocalizedString("No results found."),
21
+ noColumnsOverlayLabel: getLocalizedString("No columns"),
22
+ noColumnsOverlayManageColumns: getLocalizedString("Manage columns"),
23
+ emptyPivotOverlayLabel: getLocalizedString("Add fields to rows, columns, and values to create a pivot table"),
24
+
25
+ // Density selector toolbar button text
26
+ toolbarDensity: getLocalizedString("Density"),
27
+ toolbarDensityLabel: getLocalizedString("Density"),
28
+ toolbarDensityCompact: getLocalizedString("Compact"),
29
+ toolbarDensityStandard: getLocalizedString("Standard"),
30
+ toolbarDensityComfortable: getLocalizedString("Comfortable"),
31
+
32
+ // Undo/redo toolbar button text
33
+ toolbarUndo: getLocalizedString("Undo"),
34
+ toolbarRedo: getLocalizedString("Redo"),
35
+
36
+ // Columns selector toolbar button text
37
+ toolbarColumns: getLocalizedString("Columns"),
38
+ toolbarColumnsLabel: getLocalizedString("Select columns"),
39
+
40
+ // Filters toolbar button text
41
+ toolbarFilters: getLocalizedString("Filters"),
42
+ toolbarFiltersLabel: getLocalizedString("Show filters"),
43
+ toolbarFiltersTooltipHide: getLocalizedString("Hide filters"),
44
+ toolbarFiltersTooltipShow: getLocalizedString("Show filters"),
45
+ toolbarFiltersTooltipActive: (count) =>
46
+ count !== 1
47
+ ? getLocalizedString(`$count active filters`).replace("$count", String(count))
48
+ : getLocalizedString(`$count active filter`).replace("$count", String(count)),
49
+
50
+ // Quick filter toolbar field
51
+ toolbarQuickFilterPlaceholder: getLocalizedString("Search…"),
52
+ toolbarQuickFilterLabel: getLocalizedString("Search"),
53
+ toolbarQuickFilterDeleteIconLabel: getLocalizedString("Clear"),
54
+
55
+ // Export selector toolbar button text
56
+ toolbarExport: getLocalizedString("Export"),
57
+ toolbarExportLabel: getLocalizedString("Export"),
58
+ toolbarExportCSV: getLocalizedString("Download as CSV"),
59
+ toolbarExportPrint: getLocalizedString("Print"),
60
+ toolbarExportExcel: getLocalizedString("Download as Excel"),
61
+
62
+ // Toolbar pivot button
63
+ toolbarPivot: getLocalizedString("Pivot"),
64
+
65
+ // Toolbar charts button
66
+ toolbarCharts: getLocalizedString("Charts"),
67
+
68
+ // Toolbar AI Assistant button
69
+ toolbarAssistant: getLocalizedString("AI Assistant"),
70
+
71
+ // Columns management text
72
+ columnsManagementSearchTitle: getLocalizedString("Search"),
73
+ columnsManagementNoColumns: getLocalizedString("No columns"),
74
+ columnsManagementShowHideAllText: getLocalizedString("Show/Hide All"),
75
+ columnsManagementReset: getLocalizedString("Reset"),
76
+ columnsManagementDeleteIconLabel: getLocalizedString("Clear"),
77
+
78
+ // Filter panel text
79
+ filterPanelAddFilter: getLocalizedString("Add filter"),
80
+ filterPanelDeleteIconLabel: getLocalizedString("Delete"),
81
+ filterPanelLogicOperator: getLocalizedString("Logic operator"),
82
+ filterPanelOperator: getLocalizedString("Operator"),
83
+ filterPanelOperatorAnd: getLocalizedString("And"),
84
+ filterPanelOperatorOr: getLocalizedString("Or"),
85
+ filterPanelColumns: getLocalizedString("Columns"),
86
+ filterPanelInputLabel: getLocalizedString("Value"),
87
+ filterPanelInputPlaceholder: getLocalizedString("Filter value"),
88
+ filterPanelRemoveAll: getLocalizedString("Remove all"),
89
+
90
+ // Filter operators text
91
+ filterOperatorContains: getLocalizedString("contains"),
92
+ filterOperatorDoesNotContain: getLocalizedString("does not contain"),
93
+ filterOperatorEquals: getLocalizedString("equals"),
94
+ filterOperatorDoesNotEqual: getLocalizedString("does not equal"),
95
+ filterOperatorStartsWith: getLocalizedString("starts with"),
96
+ filterOperatorEndsWith: getLocalizedString("ends with"),
97
+ filterOperatorIs: getLocalizedString("is"),
98
+ filterOperatorNot: getLocalizedString("is not"),
99
+ filterOperatorAfter: getLocalizedString("is after"),
100
+ filterOperatorOnOrAfter: getLocalizedString("is on or after"),
101
+ filterOperatorBefore: getLocalizedString("is before"),
102
+ filterOperatorOnOrBefore: getLocalizedString("is on or before"),
103
+ filterOperatorIsEmpty: getLocalizedString("is empty"),
104
+ filterOperatorIsNotEmpty: getLocalizedString("is not empty"),
105
+ filterOperatorIsAnyOf: getLocalizedString("is any of"),
106
+ "filterOperator=": "=",
107
+ "filterOperator!=": "!=",
108
+ "filterOperator>": ">",
109
+ "filterOperator>=": ">=",
110
+ "filterOperator<": "<",
111
+ "filterOperator<=": "<=",
112
+
113
+ // Header filter operators text
114
+ headerFilterOperatorContains: getLocalizedString("Contains"),
115
+ headerFilterOperatorDoesNotContain: getLocalizedString("Does not contain"),
116
+ headerFilterOperatorEquals: getLocalizedString("Equals"),
117
+ headerFilterOperatorDoesNotEqual: getLocalizedString("Does not equal"),
118
+ headerFilterOperatorStartsWith: getLocalizedString("Starts with"),
119
+ headerFilterOperatorEndsWith: getLocalizedString("Ends with"),
120
+ headerFilterOperatorIs: getLocalizedString("Is"),
121
+ headerFilterOperatorNot: getLocalizedString("Is not"),
122
+ headerFilterOperatorAfter: getLocalizedString("Is after"),
123
+ headerFilterOperatorOnOrAfter: getLocalizedString("Is on or after"),
124
+ headerFilterOperatorBefore: getLocalizedString("Is before"),
125
+ headerFilterOperatorOnOrBefore: getLocalizedString("Is on or before"),
126
+ headerFilterOperatorIsEmpty: getLocalizedString("Is empty"),
127
+ headerFilterOperatorIsNotEmpty: getLocalizedString("Is not empty"),
128
+ headerFilterOperatorIsAnyOf: getLocalizedString("Is any of"),
129
+ "headerFilterOperator=": getLocalizedString("Equals"),
130
+ "headerFilterOperator!=": getLocalizedString("Not equals"),
131
+ "headerFilterOperator>": getLocalizedString("greater than"),
132
+ "headerFilterOperator>=": getLocalizedString("greater than or equal to"),
133
+ "headerFilterOperator<": getLocalizedString("less than"),
134
+ "headerFilterOperator<=": getLocalizedString("less than or equal to"),
135
+ headerFilterClear: getLocalizedString("Clear filter"),
136
+
137
+ // Filter values text
138
+ filterValueAny: getLocalizedString("any"),
139
+ filterValueTrue: getLocalizedString("true"),
140
+ filterValueFalse: getLocalizedString("false"),
141
+
142
+ // Column menu text
143
+ columnMenuLabel: getLocalizedString("Menu"),
144
+ columnMenuAriaLabel: (columnName: string) => localize`${columnName} column menu`,
145
+ columnMenuShowColumns: getLocalizedString("Show columns"),
146
+ columnMenuFilter: getLocalizedString("Filter"),
147
+ columnMenuHideColumn: getLocalizedString("Hide"),
148
+ columnMenuUnsort: getLocalizedString("Unsort"),
149
+ columnMenuSortAsc: getLocalizedString("Sort by ASC"),
150
+ columnMenuSortDesc: getLocalizedString("Sort by DESC"),
151
+ columnMenuManageColumns: getLocalizedString("Manage columns"),
152
+ columnMenuManagePivot: getLocalizedString("Manage pivot"),
153
+ columnMenuManageCharts: getLocalizedString("Manage charts"),
154
+
155
+ // Column header text
156
+ columnHeaderFiltersTooltipActive: (count) =>
157
+ count !== 1
158
+ ? getLocalizedString(`$count active filters`).replace("$count", String(count))
159
+ : getLocalizedString(`$count active filter`).replace("$count", String(count)),
160
+ columnHeaderFiltersLabel: getLocalizedString("Show filters"),
161
+ columnHeaderSortIconLabel: getLocalizedString("Sort"),
162
+
163
+ // Rows selected footer text
164
+ footerRowSelected: (count) =>
165
+ count !== 1
166
+ ? getLocalizedString(`$count rows selected`).replace("$count", count.toLocaleString())
167
+ : getLocalizedString(`$count row selected`).replace("$count", count.toLocaleString()),
168
+
169
+ // Total row amount footer text
170
+ footerTotalRows: getLocalizedString("Total Rows:"),
171
+
172
+ // Total visible row amount footer text
173
+ footerTotalVisibleRows: (visibleCount, totalCount) =>
174
+ getLocalizedString(`$count of $total`)
175
+ .replace("$count", visibleCount.toLocaleString())
176
+ .replace("$total", totalCount.toLocaleString()),
177
+
178
+ // Checkbox selection text
179
+ checkboxSelectionHeaderName: getLocalizedString("Checkbox selection"),
180
+ checkboxSelectionSelectAllRows: getLocalizedString("Select all rows"),
181
+ checkboxSelectionUnselectAllRows: getLocalizedString("Unselect all rows"),
182
+ checkboxSelectionSelectRow: getLocalizedString("Select row"),
183
+ checkboxSelectionUnselectRow: getLocalizedString("Unselect row"),
184
+
185
+ // Boolean cell text
186
+ booleanCellTrueLabel: getLocalizedString("yes"),
187
+ booleanCellFalseLabel: getLocalizedString("no"),
188
+
189
+ // Long text cell
190
+ longTextCellExpandLabel: getLocalizedString("Expand"),
191
+ longTextCellCollapseLabel: getLocalizedString("Collapse"),
192
+
193
+ // Actions cell more text
194
+ actionsCellMore: getLocalizedString("more"),
195
+
196
+ // Column pinning text
197
+ pinToLeft: getLocalizedString("Pin to left"),
198
+ pinToRight: getLocalizedString("Pin to right"),
199
+ unpin: getLocalizedString("Unpin"),
200
+
201
+ // Tree Data
202
+ treeDataGroupingHeaderName: getLocalizedString("Group"),
203
+ treeDataExpand: getLocalizedString("see children"),
204
+ treeDataCollapse: getLocalizedString("hide children"),
205
+
206
+ // Grouping columns
207
+ groupingColumnHeaderName: getLocalizedString("Group"),
208
+ groupColumn: (name) => getLocalizedString("Group by $name").replace("$name", name),
209
+ unGroupColumn: (name) => getLocalizedString("Stop grouping by $name").replace("$name", name),
210
+
211
+ // Master/detail
212
+ detailPanelToggle: getLocalizedString("Detail panel toggle"),
213
+ expandDetailPanel: getLocalizedString("Expand"),
214
+ collapseDetailPanel: getLocalizedString("Collapse"),
215
+
216
+ // Pagination
217
+ paginationRowsPerPage: getLocalizedString("Rows per page:"),
218
+ paginationDisplayedRows: ({ from, to, count, estimated }) => {
219
+ if (!estimated) {
220
+ return localize`${from}–${to} of ${count !== -1 ? count : `more than ${to}`}`;
221
+ }
222
+ const estimatedLabel = estimated && estimated > to ? localize`around ${estimated}` : `more than ${to}`;
223
+ return localize`${from}–${to} of ${count !== -1 ? count : estimatedLabel}`;
224
+ },
225
+ paginationItemAriaLabel: (type) => {
226
+ if (type === "first") {
227
+ return getLocalizedString("Go to first page");
228
+ }
229
+ if (type === "last") {
230
+ return getLocalizedString("Go to last page");
231
+ }
232
+ if (type === "next") {
233
+ return getLocalizedString("Go to next page");
234
+ }
235
+ // if (type === 'previous') {
236
+ return getLocalizedString("Go to previous page");
237
+ },
238
+
239
+ // Row reordering text
240
+ rowReorderingHeaderName: getLocalizedString("Row reordering"),
241
+
242
+ // Aggregation
243
+ aggregationMenuItemHeader: getLocalizedString("Aggregation"),
244
+ aggregationFunctionLabelNone: getLocalizedString("none"),
245
+ aggregationFunctionLabelSum: getLocalizedString("sum"),
246
+ aggregationFunctionLabelAvg: getLocalizedString("avg"),
247
+ aggregationFunctionLabelMin: getLocalizedString("min"),
248
+ aggregationFunctionLabelMax: getLocalizedString("max"),
249
+ aggregationFunctionLabelSize: getLocalizedString("size"),
250
+
251
+ // Pivot panel
252
+ pivotToggleLabel: getLocalizedString("Pivot"),
253
+ pivotRows: getLocalizedString("Rows"),
254
+ pivotColumns: getLocalizedString("Columns"),
255
+ pivotValues: getLocalizedString("Values"),
256
+ pivotCloseButton: getLocalizedString("Close pivot settings"),
257
+ pivotSearchButton: getLocalizedString("Search fields"),
258
+ pivotSearchControlPlaceholder: getLocalizedString("Search fields"),
259
+ pivotSearchControlLabel: getLocalizedString("Search fields"),
260
+ pivotSearchControlClear: getLocalizedString("Clear search"),
261
+ pivotNoFields: getLocalizedString("No fields"),
262
+ pivotMenuMoveUp: getLocalizedString("Move up"),
263
+ pivotMenuMoveDown: getLocalizedString("Move down"),
264
+ pivotMenuMoveToTop: getLocalizedString("Move to top"),
265
+ pivotMenuMoveToBottom: getLocalizedString("Move to bottom"),
266
+ pivotMenuRows: getLocalizedString("Rows"),
267
+ pivotMenuColumns: getLocalizedString("Columns"),
268
+ pivotMenuValues: getLocalizedString("Values"),
269
+ pivotMenuOptions: getLocalizedString("Field options"),
270
+ pivotMenuAddToRows: getLocalizedString("Add to Rows"),
271
+ pivotMenuAddToColumns: getLocalizedString("Add to Columns"),
272
+ pivotMenuAddToValues: getLocalizedString("Add to Values"),
273
+ pivotMenuRemove: getLocalizedString("Remove"),
274
+ pivotDragToRows: getLocalizedString("Drag here to create rows"),
275
+ pivotDragToColumns: getLocalizedString("Drag here to create columns"),
276
+ pivotDragToValues: getLocalizedString("Drag here to create values"),
277
+ pivotYearColumnHeaderName: getLocalizedString("(Year)"),
278
+ pivotQuarterColumnHeaderName: getLocalizedString("(Quarter)"),
279
+
280
+ // Charts configuration panel
281
+ chartsNoCharts: getLocalizedString("There are no charts available"),
282
+ chartsChartNotSelected: getLocalizedString("Select a chart type to configure its options"),
283
+ chartsTabChart: getLocalizedString("Chart"),
284
+ chartsTabFields: getLocalizedString("Fields"),
285
+ chartsTabCustomize: getLocalizedString("Customize"),
286
+ chartsCloseButton: getLocalizedString("Close charts configuration"),
287
+ chartsSyncButtonLabel: getLocalizedString("Sync chart"),
288
+ chartsSearchPlaceholder: getLocalizedString("Search fields"),
289
+ chartsSearchLabel: getLocalizedString("Search fields"),
290
+ chartsSearchClear: getLocalizedString("Clear search"),
291
+ chartsNoFields: getLocalizedString("No fields"),
292
+ chartsFieldBlocked: getLocalizedString("This field cannot be added to any section"),
293
+ chartsCategories: getLocalizedString("Categories"),
294
+ chartsSeries: getLocalizedString("Series"),
295
+ chartsMenuAddToDimensions: (dimensionLabel: string) => localize`Add to ${dimensionLabel}`,
296
+ chartsMenuAddToValues: (valuesLabel: string) => localize`Add to ${valuesLabel}`,
297
+ chartsMenuMoveUp: getLocalizedString("Move up"),
298
+ chartsMenuMoveDown: getLocalizedString("Move down"),
299
+ chartsMenuMoveToTop: getLocalizedString("Move to top"),
300
+ chartsMenuMoveToBottom: getLocalizedString("Move to bottom"),
301
+ chartsMenuOptions: getLocalizedString("Field options"),
302
+ chartsMenuRemove: getLocalizedString("Remove"),
303
+ chartsDragToDimensions: (dimensionLabel: string) => localize`Drag here to use column as ${dimensionLabel}`,
304
+ chartsDragToValues: (valuesLabel: string) => localize`Drag here to use column as ${valuesLabel}`,
305
+
306
+ // AI Assistant panel
307
+ aiAssistantPanelTitle: getLocalizedString("AI Assistant"),
308
+ aiAssistantPanelClose: getLocalizedString("Close AI Assistant"),
309
+ aiAssistantPanelNewConversation: getLocalizedString("New conversation"),
310
+ aiAssistantPanelConversationHistory: getLocalizedString("Conversation history"),
311
+ aiAssistantPanelEmptyConversation: getLocalizedString("No prompt history"),
312
+ aiAssistantSuggestions: getLocalizedString("Suggestions"),
313
+
314
+ // Prompt field
315
+ promptFieldLabel: getLocalizedString("Prompt"),
316
+ promptFieldPlaceholder: getLocalizedString("Type a prompt…"),
317
+ promptFieldPlaceholderWithRecording: getLocalizedString("Type or record a prompt…"),
318
+ promptFieldPlaceholderListening: getLocalizedString("Listening for prompt…"),
319
+ promptFieldSpeechRecognitionNotSupported: getLocalizedString("Speech recognition is not supported in this browser"),
320
+ promptFieldSend: getLocalizedString("Send"),
321
+ promptFieldRecord: getLocalizedString("Record"),
322
+ promptFieldStopRecording: getLocalizedString("Stop recording"),
323
+
324
+ // Prompt
325
+ promptRerun: getLocalizedString("Run again"),
326
+ promptProcessing: getLocalizedString("Processing…"),
327
+ promptAppliedChanges: getLocalizedString("Applied changes"),
328
+
329
+ // Prompt changes
330
+ promptChangeGroupDescription: (column: string) => localize`Group by ${column}`,
331
+ promptChangeAggregationLabel: (column: string, aggregation: string) => localize`${column} (${aggregation})`,
332
+ promptChangeAggregationDescription: (column: string, aggregation: string) =>
333
+ localize`Aggregate ${column} (${aggregation})`,
334
+ promptChangeFilterLabel: (column: string, operator: string, value: string) => {
335
+ if (operator === "is any of") {
336
+ return localize`${column} is any of: ${value}`;
337
+ }
338
+ return localize`${column} ${operator} ${value}`;
339
+ },
340
+ promptChangeFilterDescription: (column: string, operator: string, value: string) => {
341
+ if (operator === "is any of") {
342
+ return localize`Filter where ${column} is any of: ${value}`;
343
+ }
344
+ return localize`Filter where ${column} ${operator} ${value}`;
345
+ },
346
+ promptChangeSortDescription: (column: string, direction: string) => localize`Sort by ${column} (${direction})`,
347
+ promptChangePivotEnableLabel: getLocalizedString("Pivot"),
348
+ promptChangePivotEnableDescription: getLocalizedString("Enable pivot"),
349
+ promptChangePivotColumnsLabel: (count: number) => localize`Columns (${count})`,
350
+ promptChangePivotColumnsDescription: (column: string, direction: string) =>
351
+ localize`${column}${direction ? ` (${direction})` : ""}`,
352
+ promptChangePivotRowsLabel: (count: number) => localize`Rows (${count})`,
353
+ promptChangePivotValuesLabel: (count: number) => localize`Values (${count})`,
354
+ promptChangePivotValuesDescription: (column: string, aggregation: string) => localize`${column} (${aggregation})`,
355
+ promptChangeChartsLabel: (dimensionsCount: number, valuesCount: number) =>
356
+ localize`Dimensions (${dimensionsCount}), Values (${valuesCount})`,
357
+
358
+ // Custom AF filters
359
+ filterOperatorNotContains: getLocalizedString("not contains"),
360
+ filterOperatorNotEquals: getLocalizedString("not equals"),
361
+ filterOperatorLike: getLocalizedString("like"),
362
+ filterOperatorNotLike: getLocalizedString("not like"),
363
+ filterOperatorIsNoneOf: getLocalizedString("is none of"),
364
+ headerFilterOperatorNotContains: getLocalizedString("Not contains??"),
365
+ headerFilterOperatorNotEquals: getLocalizedString("Not equals"),
366
+ headerFilterOperatorLike: getLocalizedString("Like"),
367
+ headerFilterOperatorNotLike: getLocalizedString("Not like"),
368
+ headerFilterOperatorIsNoneOf: getLocalizedString("Is none of"),
369
+ };
@@ -0,0 +1,275 @@
1
+ import "./slots.css";
2
+
3
+ import {
4
+ Badge,
5
+ Button,
6
+ Checkbox,
7
+ Divider,
8
+ Select,
9
+ Skeleton,
10
+ Spinner,
11
+ Switch,
12
+ Tag,
13
+ Textfield,
14
+ Tooltip,
15
+ } from "@digdir/designsystemet-react";
16
+ import type { GridSlotProps, GridSlotsComponent } from "@mui/x-data-grid-pro";
17
+
18
+ import clsx from "clsx";
19
+ import { type Ref, useCallback } from "react";
20
+
21
+ type DsSize = "2xs" | "xs" | "sm" | "md" | "lg";
22
+
23
+ /**
24
+ * Maps the MUI slot sizes onto the Designsystemet size tokens. The grid only
25
+ * ever asks for `small` or `medium`, but the toolbar buttons also allow `large`.
26
+ */
27
+ function dsSize(size?: "small" | "medium" | "large"): DsSize {
28
+ switch (size) {
29
+ case "small":
30
+ return "sm";
31
+ case "large":
32
+ return "lg";
33
+ default:
34
+ return "sm";
35
+ }
36
+ }
37
+
38
+ /**
39
+ * The grid passes `title` as a `ReactNode`, but Designsystemet's tooltip only
40
+ * accepts a string. Anything else is rendered without a tooltip.
41
+ */
42
+ function tooltipContent(title: React.ReactNode) {
43
+ return typeof title === "string" ? title : typeof title === "number" ? String(title) : undefined;
44
+ }
45
+
46
+ function BaseCheckbox(props: GridSlotProps["baseCheckbox"]) {
47
+ let {
48
+ indeterminate,
49
+ inputRef,
50
+ label,
51
+ size,
52
+ density: _density,
53
+ fullWidth: _fullWidth,
54
+ touchRippleRef: _touchRippleRef,
55
+ slotProps,
56
+ className,
57
+ ...other
58
+ } = props;
59
+
60
+ // `indeterminate` is not a DOM attribute, so it has to be set on the input
61
+ // element itself.
62
+ let ref = useCallback(
63
+ (element: HTMLInputElement | null) => {
64
+ if (element) {
65
+ element.indeterminate = !!indeterminate;
66
+ }
67
+
68
+ if (typeof inputRef === "function") {
69
+ inputRef(element);
70
+ } else if (inputRef) {
71
+ (inputRef as React.RefObject<HTMLInputElement | null>).current = element;
72
+ }
73
+ },
74
+ [indeterminate, inputRef],
75
+ );
76
+
77
+ return (
78
+ <Checkbox
79
+ {...(other as any)}
80
+ {...slotProps?.htmlInput}
81
+ ref={ref}
82
+ className={clsx("AfGrid-checkbox", className)}
83
+ data-size={dsSize(size)}
84
+ label={label}
85
+ aria-label={label ? undefined : ((other as any)["aria-label"] ?? "")}
86
+ />
87
+ );
88
+ }
89
+
90
+ function BaseButton(props: GridSlotProps["baseButton"]) {
91
+ let { children, size, startIcon, touchRippleRef: _touchRippleRef, ...other } = props;
92
+
93
+ return (
94
+ <Button {...(other as any)} variant="tertiary" data-size={dsSize(size)}>
95
+ {startIcon}
96
+ {children}
97
+ </Button>
98
+ );
99
+ }
100
+
101
+ function BaseIconButton(props: GridSlotProps["baseIconButton"]) {
102
+ let { children, size, label, color: _color, edge: _edge, touchRippleRef: _touchRippleRef, ...other } = props;
103
+
104
+ return (
105
+ <Button {...(other as any)} icon variant="tertiary" data-size={dsSize(size)} aria-label={label}>
106
+ {children}
107
+ </Button>
108
+ );
109
+ }
110
+
111
+ function BaseTooltip(props: GridSlotProps["baseTooltip"]) {
112
+ let { children, title, enterDelay: _enterDelay, disableInteractive: _disableInteractive, ...other } = props;
113
+ let content = tooltipContent(title);
114
+
115
+ if (!content) {
116
+ return children;
117
+ }
118
+
119
+ return (
120
+ <Tooltip {...(other as any)} content={content}>
121
+ {children}
122
+ </Tooltip>
123
+ );
124
+ }
125
+
126
+ function BaseTextField(props: GridSlotProps["baseTextField"]) {
127
+ let {
128
+ inputRef,
129
+ size,
130
+ error,
131
+ helperText,
132
+ slotProps,
133
+ fullWidth: _fullWidth,
134
+ color: _color,
135
+ className,
136
+ ...other
137
+ } = props;
138
+
139
+ return (
140
+ <Textfield
141
+ {...(other as any)}
142
+ {...slotProps?.htmlInput}
143
+ ref={inputRef as Ref<HTMLInputElement>}
144
+ className={clsx("AfGrid-textField", className)}
145
+ data-size={dsSize(size)}
146
+ error={error ? helperText : undefined}
147
+ aria-label={other.label ? undefined : ((other as any)["aria-label"] ?? "")}
148
+ />
149
+ );
150
+ }
151
+
152
+ function BaseSelect(props: GridSlotProps["baseSelect"]) {
153
+ let {
154
+ children,
155
+ size,
156
+ label,
157
+ labelId: _labelId,
158
+ native: _native,
159
+ fullWidth: _fullWidth,
160
+ open: _open,
161
+ onOpen: _onOpen,
162
+ onClose: _onClose,
163
+ slotProps,
164
+ className,
165
+ ...other
166
+ } = props;
167
+
168
+ return (
169
+ <Select
170
+ {...(other as any)}
171
+ {...slotProps?.htmlInput}
172
+ className={clsx("AfGrid-select", className)}
173
+ data-size={dsSize(size)}
174
+ aria-label={typeof label === "string" ? label : ((other as any)["aria-label"] ?? "")}
175
+ >
176
+ {children}
177
+ </Select>
178
+ );
179
+ }
180
+
181
+ function BaseSelectOption(props: GridSlotProps["baseSelectOption"]) {
182
+ let { native: _native, children, ...other } = props;
183
+
184
+ return <Select.Option {...(other as any)}>{children}</Select.Option>;
185
+ }
186
+
187
+ function BaseSwitch(props: GridSlotProps["baseSwitch"]) {
188
+ let { size, label, ...other } = props;
189
+
190
+ return (
191
+ <Switch
192
+ {...(other as any)}
193
+ data-size={dsSize(size)}
194
+ label={label}
195
+ aria-label={label ? undefined : ((other as any)["aria-label"] ?? "")}
196
+ />
197
+ );
198
+ }
199
+
200
+ function BaseChip(props: GridSlotProps["baseChip"]) {
201
+ let { label, size, icon, variant, children: _children, ...other } = props;
202
+
203
+ return (
204
+ <Tag {...(other as any)} data-size={dsSize(size)} variant={variant === "outlined" ? "outline" : "default"}>
205
+ {icon}
206
+ {label}
207
+ </Tag>
208
+ );
209
+ }
210
+
211
+ function BaseBadge(props: GridSlotProps["baseBadge"]) {
212
+ let { badgeContent, children, invisible, color, variant, overlap, className, ...other } = props;
213
+ let count = typeof badgeContent === "number" ? badgeContent : Number(badgeContent);
214
+
215
+ return (
216
+ <span {...(other as any)} className={clsx("AfGrid-badgeWrapper", className)}>
217
+ {children}
218
+ {!invisible && (
219
+ <Badge.Position placement="top-right" overlap={overlap === "circular" ? "circle" : "rectangle"}>
220
+ <Badge
221
+ count={variant === "dot" || Number.isNaN(count) ? undefined : count}
222
+ data-color={color === "error" ? "danger" : color === "primary" ? "accent" : "neutral"}
223
+ />
224
+ </Badge.Position>
225
+ )}
226
+ </span>
227
+ );
228
+ }
229
+
230
+ function BaseCircularProgress(props: GridSlotProps["baseCircularProgress"]) {
231
+ let { size, color: _color, ...other } = props;
232
+ let numericSize = typeof size === "number" ? size : 24;
233
+
234
+ return <Spinner {...(other as any)} aria-hidden data-size={numericSize <= 20 ? "xs" : "sm"} />;
235
+ }
236
+
237
+ function BaseLinearProgress(props: GridSlotProps["baseLinearProgress"]) {
238
+ let { className, ...other } = props;
239
+
240
+ return <div {...(other as any)} role="progressbar" className={clsx("AfGrid-linearProgress", className)} />;
241
+ }
242
+
243
+ function BaseSkeleton(props: GridSlotProps["baseSkeleton"]) {
244
+ let { variant, width, height, ...other } = props;
245
+
246
+ return (
247
+ <Skeleton {...(other as any)} variant={variant === "circular" ? "circle" : variant} width={width} height={height} />
248
+ );
249
+ }
250
+
251
+ function BaseDivider(props: GridSlotProps["baseDivider"]) {
252
+ return <Divider {...(props as any)} />;
253
+ }
254
+
255
+ /**
256
+ * Designsystemet replacements for the grid's base components. Passed to the
257
+ * `slots` prop of the data grid, so the grid renders Designsystemet components
258
+ * instead of the MUI ones wherever it can.
259
+ */
260
+ export const dsBaseSlots: Partial<GridSlotsComponent> = {
261
+ baseBadge: BaseBadge,
262
+ baseButton: BaseButton,
263
+ baseCheckbox: BaseCheckbox,
264
+ baseChip: BaseChip,
265
+ baseCircularProgress: BaseCircularProgress,
266
+ baseDivider: BaseDivider,
267
+ baseIconButton: BaseIconButton,
268
+ baseLinearProgress: BaseLinearProgress,
269
+ baseSelect: BaseSelect,
270
+ baseSelectOption: BaseSelectOption,
271
+ baseSkeleton: BaseSkeleton,
272
+ baseSwitch: BaseSwitch,
273
+ baseTextField: BaseTextField,
274
+ baseTooltip: BaseTooltip,
275
+ };