@olenbetong/appframe-ds 1.6.0 → 1.7.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 (154) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/package.json +3 -3
  3. package/scripts/copyAssets.mjs +27 -27
  4. package/src/autocomplete/Autocomplete.css +253 -253
  5. package/src/autocomplete/Autocomplete.tsx +320 -320
  6. package/src/autocomplete/AutocompleteFrame.tsx +168 -168
  7. package/src/autocomplete/Combobox.tsx +286 -286
  8. package/src/autocomplete/index.ts +4 -4
  9. package/src/autocomplete/types.ts +204 -204
  10. package/src/autocomplete/utils.ts +183 -183
  11. package/src/binding/BoundNumberInput.tsx +42 -42
  12. package/src/binding/BoundNumericTextField.tsx +102 -102
  13. package/src/binding/BoundTextField.tsx +49 -49
  14. package/src/binding/CancelButton.tsx +30 -30
  15. package/src/binding/CancelIconButton.tsx +30 -30
  16. package/src/binding/DataEditToolbar.css +23 -23
  17. package/src/binding/DataEditToolbar.tsx +84 -84
  18. package/src/binding/DeleteButton.tsx +35 -35
  19. package/src/binding/DeleteIconButton.tsx +35 -35
  20. package/src/binding/RefreshButton.tsx +31 -31
  21. package/src/binding/RefreshIconButton.tsx +33 -33
  22. package/src/binding/RefreshRowIconButton.tsx +33 -33
  23. package/src/binding/SaveButton.tsx +37 -37
  24. package/src/binding/SaveIconButton.tsx +38 -38
  25. package/src/binding/index.ts +20 -20
  26. package/src/container/Container.tsx +50 -50
  27. package/src/container/index.ts +1 -1
  28. package/src/cutoff/CutOffPicker.css +12 -12
  29. package/src/cutoff/CutOffPicker.tsx +165 -165
  30. package/src/cutoff/context.tsx +194 -194
  31. package/src/cutoff/dsCutOffDates.ts +21 -21
  32. package/src/cutoff/index.ts +4 -4
  33. package/src/cutoff/utils.test.ts +59 -59
  34. package/src/cutoff/utils.ts +276 -276
  35. package/src/filter/ChipListInput.tsx +79 -79
  36. package/src/filter/FieldFilterPanel.css +150 -150
  37. package/src/filter/FieldFilterPanel.tsx +309 -309
  38. package/src/filter/FilterBuilder.css +152 -152
  39. package/src/filter/FilterBuilder.test.tsx +153 -153
  40. package/src/filter/FilterBuilder.tsx +435 -435
  41. package/src/filter/FilterEditor.css +231 -231
  42. package/src/filter/FilterEditor.test.tsx +285 -285
  43. package/src/filter/FilterEditor.tsx +75 -75
  44. package/src/filter/FilterGroupEditor.tsx +160 -160
  45. package/src/filter/FilterNameDialog.css +22 -22
  46. package/src/filter/FilterNameDialog.tsx +81 -81
  47. package/src/filter/FilterRow.tsx +117 -117
  48. package/src/filter/FilterShareDialog.css +28 -28
  49. package/src/filter/FilterShareDialog.tsx +201 -201
  50. package/src/filter/FilterStringField.tsx +78 -78
  51. package/src/filter/FilterValueEditor.tsx +239 -239
  52. package/src/filter/SavedFilterTree.css +107 -107
  53. package/src/filter/SavedFilterTree.test.tsx +94 -94
  54. package/src/filter/SavedFilterTree.tsx +218 -218
  55. package/src/filter/fieldFilter.test.ts +158 -158
  56. package/src/filter/fieldFilter.ts +139 -139
  57. package/src/filter/index.ts +27 -27
  58. package/src/filter/types.ts +38 -38
  59. package/src/filter/useDistinctValues.test.ts +102 -102
  60. package/src/filter/useDistinctValues.ts +230 -230
  61. package/src/global.d.ts +11 -11
  62. package/src/grid/formatters.ts +23 -0
  63. package/src/grid/index.css +8 -2
  64. package/src/grid/index.tsx +73 -3
  65. package/src/grid/useAfColumns.tsx +28 -4
  66. package/src/grid/useAfGridApi.ts +3 -0
  67. package/src/grid/utcDate.test.ts +25 -0
  68. package/src/grid/utcDate.ts +28 -0
  69. package/src/grid/utcDateEditing.tsx +11 -0
  70. package/src/i18n.ts +13 -13
  71. package/src/index.ts +14 -14
  72. package/src/input/InputAdornments.css +80 -80
  73. package/src/input/InputAdornments.tsx +102 -102
  74. package/src/input/NumberInput.css +9 -9
  75. package/src/input/NumberInput.test.tsx +212 -212
  76. package/src/input/NumberInput.tsx +268 -268
  77. package/src/input/index.ts +3 -3
  78. package/src/input/numberFormat.test.ts +79 -79
  79. package/src/input/numberFormat.ts +94 -94
  80. package/src/layout/AppMenu.tsx +74 -74
  81. package/src/layout/AppMenuDrawer.css +71 -71
  82. package/src/layout/AppMenuDrawer.tsx +122 -122
  83. package/src/layout/AppMenuItem.css +6 -2
  84. package/src/layout/DialogMotion.css +79 -79
  85. package/src/layout/ErrorBoundary.tsx +162 -162
  86. package/src/layout/PageContainer.css +48 -48
  87. package/src/layout/PageContainer.tsx +41 -41
  88. package/src/layout/ResponsiveDialog.css +86 -86
  89. package/src/layout/ResponsiveDialog.tsx +55 -55
  90. package/src/layout/index.tsx +70 -70
  91. package/src/link/Link.tsx +23 -23
  92. package/src/link/index.ts +1 -1
  93. package/src/mdi/Mdi.css +8 -8
  94. package/src/mdi/Mdi.test.tsx +91 -91
  95. package/src/mdi/Mdi.tsx +141 -141
  96. package/src/mdi/MdiConfirmCloseDialog.tsx +47 -47
  97. package/src/mdi/MdiContext.tsx +59 -59
  98. package/src/mdi/MdiDocumentContext.tsx +60 -60
  99. package/src/mdi/MdiPanel.tsx +61 -61
  100. package/src/mdi/MdiRestore.test.tsx +86 -86
  101. package/src/mdi/MdiTab.tsx +30 -30
  102. package/src/mdi/controller.test.ts +382 -382
  103. package/src/mdi/controller.ts +656 -656
  104. package/src/mdi/dockviewHost.ts +132 -132
  105. package/src/mdi/fakeHost.ts +200 -200
  106. package/src/mdi/host.ts +44 -44
  107. package/src/mdi/ids.test.ts +20 -20
  108. package/src/mdi/ids.ts +18 -18
  109. package/src/mdi/index.ts +31 -31
  110. package/src/mdi/layoutRestore.test.ts +156 -156
  111. package/src/mdi/layoutRestore.ts +311 -311
  112. package/src/mdi/layoutSpec.test.ts +143 -143
  113. package/src/mdi/layoutSpec.ts +214 -214
  114. package/src/mdi/layoutStore.test.ts +93 -93
  115. package/src/mdi/layoutStore.ts +99 -99
  116. package/src/mdi/memorySettingsStore.ts +21 -21
  117. package/src/mdi/registry.ts +34 -34
  118. package/src/mdi/testTypes.ts +37 -37
  119. package/src/mdi/theme.ts +22 -22
  120. package/src/mdi/types.ts +214 -214
  121. package/src/page/Page.css +57 -57
  122. package/src/page/PageBlock.css +64 -64
  123. package/src/page/PageBlock.tsx +93 -93
  124. package/src/page/PageHeader.css +88 -88
  125. package/src/page/PageHeader.tsx +115 -115
  126. package/src/page/PageTitle.css +5 -5
  127. package/src/page/PageTitle.tsx +21 -21
  128. package/src/page/index.ts +4 -4
  129. package/src/paper/Paper.tsx +52 -52
  130. package/src/progress/LinearProgress.css +121 -121
  131. package/src/progress/LinearProgress.tsx +106 -106
  132. package/src/progress/index.ts +1 -1
  133. package/src/report-downloader/ReportDownloader.css +67 -67
  134. package/src/report-downloader/components/StatusItem.tsx +87 -87
  135. package/src/report-downloader/components/StatusList.tsx +28 -28
  136. package/src/report-downloader/index.ts +41 -41
  137. package/src/report-downloader/status.tsx +92 -92
  138. package/src/sortable/DropIndicator.css +51 -51
  139. package/src/sortable/DropIndicator.tsx +27 -27
  140. package/src/sortable/SortableItemList.css +21 -21
  141. package/src/sortable/SortableItemList.tsx +264 -264
  142. package/src/sortable/index.ts +8 -8
  143. package/src/sortable/reorderSortOrder.ts +126 -126
  144. package/src/sortable/sortOrder.ts +101 -101
  145. package/src/sortable/types.ts +9 -9
  146. package/src/sortable/useSortableContainer.ts +53 -53
  147. package/src/sortable/useSortableItem.ts +95 -95
  148. package/src/sortable/useSortableList.ts +117 -117
  149. package/src/test/happy-dom-document-class.ts +24 -24
  150. package/src/test/setup.ts +27 -27
  151. package/src/theme/useDsColorScheme.ts +53 -53
  152. package/tsconfig.build.json +9 -9
  153. package/tsconfig.json +8 -8
  154. package/vitest.config.ts +10 -10
@@ -1,218 +1,218 @@
1
- import { Dropdown } from "@digdir/designsystemet-react";
2
- import { canDeleteSavedFilter, canRenameSavedFilter, type SavedFilter } from "@olenbetong/appframe-core";
3
- import {
4
- ChevronDownIcon,
5
- ChevronRightIcon,
6
- FilesIcon,
7
- FileTextIcon,
8
- MenuElipsisVerticalIcon,
9
- PadlockLockedIcon,
10
- PencilIcon,
11
- StarIcon,
12
- TrashIcon,
13
- } from "@navikt/aksel-icons";
14
- import clsx from "clsx";
15
- import { useState } from "react";
16
-
17
- import "./SavedFilterTree.css";
18
- import { getLocalizedString } from "../i18n.js";
19
-
20
- /** `null` is the "new filter" node, i.e. no saved filter selected. */
21
- export type SavedFilterSelection = number | null;
22
-
23
- export type SavedFilterTreeProps = {
24
- mine: SavedFilter[];
25
- shared: SavedFilter[];
26
- /** `FilterID` of the selected filter, or `null` for "new filter". */
27
- value: SavedFilterSelection;
28
- onChange: (filterId: SavedFilterSelection) => void;
29
- /**
30
- * Marks the selected filter as having unsaved changes; it is then rendered
31
- * in bold, like the Windows client does.
32
- */
33
- dirty?: boolean;
34
- onRename?: (filter: SavedFilter) => void;
35
- onDelete?: (filter: SavedFilter) => void;
36
- /** Shows an "info" item in the context menu when supplied. */
37
- onShowInfo?: (filter: SavedFilter) => void;
38
- loading?: boolean;
39
- className?: string;
40
- "data-size"?: "sm" | "md" | "lg";
41
- };
42
-
43
- /**
44
- * The saved-filter list of the filter builder: a "new filter" entry followed by
45
- * the user's own filters and the filters shared with them.
46
- *
47
- * Which actions a filter offers depends on the access the database resolved for
48
- * it — renaming and deleting are restricted to filters the user created, even
49
- * when they manage a filter someone else made.
50
- *
51
- * @example
52
- * ```jsx
53
- * let { mine, shared, rename, remove } = useSavedFilters({ dbObjectId: "aviw_Personnel_Persons" });
54
- *
55
- * <SavedFilterTree
56
- * mine={mine}
57
- * shared={shared}
58
- * value={selected}
59
- * onChange={setSelected}
60
- * onRename={(filter) => rename(filter.filterId, prompt("Name", filter.name))}
61
- * />
62
- * ```
63
- */
64
- export function SavedFilterTree({
65
- mine,
66
- shared,
67
- value,
68
- onChange,
69
- dirty,
70
- onRename,
71
- onDelete,
72
- onShowInfo,
73
- loading,
74
- className,
75
- "data-size": size = "sm",
76
- }: SavedFilterTreeProps) {
77
- let [expanded, setExpanded] = useState({ mine: true, shared: true });
78
-
79
- function renderBranch(key: "mine" | "shared", label: string, icon: React.ReactNode, filters: SavedFilter[]) {
80
- let isExpanded = expanded[key];
81
-
82
- return (
83
- <li className="ObSavedFilterTree-branch">
84
- <button
85
- type="button"
86
- className="ObSavedFilterTree-node"
87
- aria-expanded={isExpanded}
88
- onClick={() => setExpanded((state) => ({ ...state, [key]: !state[key] }))}
89
- >
90
- <span className="ObSavedFilterTree-chevron" aria-hidden>
91
- {isExpanded ? <ChevronDownIcon /> : <ChevronRightIcon />}
92
- </span>
93
- <span className="ObSavedFilterTree-icon" aria-hidden>
94
- {icon}
95
- </span>
96
- <span className="ObSavedFilterTree-label">{label}</span>
97
- </button>
98
- {isExpanded && (
99
- <ul className="ObSavedFilterTree-children">
100
- {filters.length === 0 && !loading && (
101
- <li className="ObSavedFilterTree-empty">{getLocalizedString("No filters")}</li>
102
- )}
103
- {filters.map((filter) => (
104
- <SavedFilterNode
105
- key={filter.filterId}
106
- filter={filter}
107
- selected={filter.filterId === value}
108
- dirty={dirty && filter.filterId === value}
109
- onSelect={() => onChange(filter.filterId)}
110
- onRename={onRename}
111
- onDelete={onDelete}
112
- onShowInfo={onShowInfo}
113
- />
114
- ))}
115
- </ul>
116
- )}
117
- </li>
118
- );
119
- }
120
-
121
- return (
122
- <ul className={clsx("ObSavedFilterTree-root", className)} data-size={size}>
123
- <li>
124
- <button
125
- type="button"
126
- className={clsx("ObSavedFilterTree-node", value === null && "ObSavedFilterTree-selected")}
127
- aria-current={value === null}
128
- onClick={() => onChange(null)}
129
- >
130
- <span className="ObSavedFilterTree-chevron" aria-hidden />
131
- <span className="ObSavedFilterTree-icon" aria-hidden>
132
- <FileTextIcon />
133
- </span>
134
- <span className="ObSavedFilterTree-label">{getLocalizedString("New Filter")}</span>
135
- </button>
136
- </li>
137
- {renderBranch("mine", getLocalizedString("My Filters"), <StarIcon />, mine)}
138
- {renderBranch("shared", getLocalizedString("Shared With Me"), <FilesIcon />, shared)}
139
- </ul>
140
- );
141
- }
142
-
143
- type SavedFilterNodeProps = {
144
- filter: SavedFilter;
145
- selected: boolean;
146
- dirty?: boolean;
147
- onSelect: () => void;
148
- onRename?: (filter: SavedFilter) => void;
149
- onDelete?: (filter: SavedFilter) => void;
150
- onShowInfo?: (filter: SavedFilter) => void;
151
- };
152
-
153
- function SavedFilterNode({ filter, selected, dirty, onSelect, onRename, onDelete, onShowInfo }: SavedFilterNodeProps) {
154
- let canRename = Boolean(onRename) && canRenameSavedFilter(filter);
155
- let canDelete = Boolean(onDelete) && canDeleteSavedFilter(filter);
156
- let hasMenu = canRename || canDelete || Boolean(onShowInfo);
157
- // A shared filter the user may not change; the Windows client shows a lock.
158
- let isReadOnly = !filter.createdByUser && filter.accessLevel === "Reader";
159
-
160
- return (
161
- <li className="ObSavedFilterTree-item">
162
- <button
163
- type="button"
164
- className={clsx(
165
- "ObSavedFilterTree-node",
166
- selected && "ObSavedFilterTree-selected",
167
- dirty && "ObSavedFilterTree-dirty",
168
- )}
169
- aria-current={selected}
170
- onClick={onSelect}
171
- >
172
- <span className="ObSavedFilterTree-icon" aria-hidden>
173
- {isReadOnly ? <PadlockLockedIcon /> : <FileTextIcon />}
174
- </span>
175
- <span className="ObSavedFilterTree-label">{filter.name}</span>
176
- </button>
177
- {hasMenu && (
178
- <Dropdown.TriggerContext>
179
- <Dropdown.Trigger
180
- variant="tertiary"
181
- icon
182
- className="ObSavedFilterTree-menuButton"
183
- aria-label={getLocalizedString("Options for {0}").replace("{0}", filter.name)}
184
- >
185
- <MenuElipsisVerticalIcon aria-hidden />
186
- </Dropdown.Trigger>
187
- <Dropdown>
188
- <Dropdown.List>
189
- {canRename && (
190
- <Dropdown.Item>
191
- <Dropdown.Button onClick={() => onRename?.(filter)}>
192
- <PencilIcon aria-hidden />
193
- {getLocalizedString("Rename")}
194
- </Dropdown.Button>
195
- </Dropdown.Item>
196
- )}
197
- {onShowInfo && (
198
- <Dropdown.Item>
199
- <Dropdown.Button onClick={() => onShowInfo(filter)}>
200
- {getLocalizedString("Information")}
201
- </Dropdown.Button>
202
- </Dropdown.Item>
203
- )}
204
- {canDelete && (
205
- <Dropdown.Item>
206
- <Dropdown.Button data-color="danger" onClick={() => onDelete?.(filter)}>
207
- <TrashIcon aria-hidden />
208
- {getLocalizedString("Delete")}
209
- </Dropdown.Button>
210
- </Dropdown.Item>
211
- )}
212
- </Dropdown.List>
213
- </Dropdown>
214
- </Dropdown.TriggerContext>
215
- )}
216
- </li>
217
- );
218
- }
1
+ import { Dropdown } from "@digdir/designsystemet-react";
2
+ import { canDeleteSavedFilter, canRenameSavedFilter, type SavedFilter } from "@olenbetong/appframe-core";
3
+ import {
4
+ ChevronDownIcon,
5
+ ChevronRightIcon,
6
+ FilesIcon,
7
+ FileTextIcon,
8
+ MenuElipsisVerticalIcon,
9
+ PadlockLockedIcon,
10
+ PencilIcon,
11
+ StarIcon,
12
+ TrashIcon,
13
+ } from "@navikt/aksel-icons";
14
+ import clsx from "clsx";
15
+ import { useState } from "react";
16
+
17
+ import "./SavedFilterTree.css";
18
+ import { getLocalizedString } from "../i18n.js";
19
+
20
+ /** `null` is the "new filter" node, i.e. no saved filter selected. */
21
+ export type SavedFilterSelection = number | null;
22
+
23
+ export type SavedFilterTreeProps = {
24
+ mine: SavedFilter[];
25
+ shared: SavedFilter[];
26
+ /** `FilterID` of the selected filter, or `null` for "new filter". */
27
+ value: SavedFilterSelection;
28
+ onChange: (filterId: SavedFilterSelection) => void;
29
+ /**
30
+ * Marks the selected filter as having unsaved changes; it is then rendered
31
+ * in bold, like the Windows client does.
32
+ */
33
+ dirty?: boolean;
34
+ onRename?: (filter: SavedFilter) => void;
35
+ onDelete?: (filter: SavedFilter) => void;
36
+ /** Shows an "info" item in the context menu when supplied. */
37
+ onShowInfo?: (filter: SavedFilter) => void;
38
+ loading?: boolean;
39
+ className?: string;
40
+ "data-size"?: "sm" | "md" | "lg";
41
+ };
42
+
43
+ /**
44
+ * The saved-filter list of the filter builder: a "new filter" entry followed by
45
+ * the user's own filters and the filters shared with them.
46
+ *
47
+ * Which actions a filter offers depends on the access the database resolved for
48
+ * it — renaming and deleting are restricted to filters the user created, even
49
+ * when they manage a filter someone else made.
50
+ *
51
+ * @example
52
+ * ```jsx
53
+ * let { mine, shared, rename, remove } = useSavedFilters({ dbObjectId: "aviw_Personnel_Persons" });
54
+ *
55
+ * <SavedFilterTree
56
+ * mine={mine}
57
+ * shared={shared}
58
+ * value={selected}
59
+ * onChange={setSelected}
60
+ * onRename={(filter) => rename(filter.filterId, prompt("Name", filter.name))}
61
+ * />
62
+ * ```
63
+ */
64
+ export function SavedFilterTree({
65
+ mine,
66
+ shared,
67
+ value,
68
+ onChange,
69
+ dirty,
70
+ onRename,
71
+ onDelete,
72
+ onShowInfo,
73
+ loading,
74
+ className,
75
+ "data-size": size = "sm",
76
+ }: SavedFilterTreeProps) {
77
+ let [expanded, setExpanded] = useState({ mine: true, shared: true });
78
+
79
+ function renderBranch(key: "mine" | "shared", label: string, icon: React.ReactNode, filters: SavedFilter[]) {
80
+ let isExpanded = expanded[key];
81
+
82
+ return (
83
+ <li className="ObSavedFilterTree-branch">
84
+ <button
85
+ type="button"
86
+ className="ObSavedFilterTree-node"
87
+ aria-expanded={isExpanded}
88
+ onClick={() => setExpanded((state) => ({ ...state, [key]: !state[key] }))}
89
+ >
90
+ <span className="ObSavedFilterTree-chevron" aria-hidden>
91
+ {isExpanded ? <ChevronDownIcon /> : <ChevronRightIcon />}
92
+ </span>
93
+ <span className="ObSavedFilterTree-icon" aria-hidden>
94
+ {icon}
95
+ </span>
96
+ <span className="ObSavedFilterTree-label">{label}</span>
97
+ </button>
98
+ {isExpanded && (
99
+ <ul className="ObSavedFilterTree-children">
100
+ {filters.length === 0 && !loading && (
101
+ <li className="ObSavedFilterTree-empty">{getLocalizedString("No filters")}</li>
102
+ )}
103
+ {filters.map((filter) => (
104
+ <SavedFilterNode
105
+ key={filter.filterId}
106
+ filter={filter}
107
+ selected={filter.filterId === value}
108
+ dirty={dirty && filter.filterId === value}
109
+ onSelect={() => onChange(filter.filterId)}
110
+ onRename={onRename}
111
+ onDelete={onDelete}
112
+ onShowInfo={onShowInfo}
113
+ />
114
+ ))}
115
+ </ul>
116
+ )}
117
+ </li>
118
+ );
119
+ }
120
+
121
+ return (
122
+ <ul className={clsx("ObSavedFilterTree-root", className)} data-size={size}>
123
+ <li>
124
+ <button
125
+ type="button"
126
+ className={clsx("ObSavedFilterTree-node", value === null && "ObSavedFilterTree-selected")}
127
+ aria-current={value === null}
128
+ onClick={() => onChange(null)}
129
+ >
130
+ <span className="ObSavedFilterTree-chevron" aria-hidden />
131
+ <span className="ObSavedFilterTree-icon" aria-hidden>
132
+ <FileTextIcon />
133
+ </span>
134
+ <span className="ObSavedFilterTree-label">{getLocalizedString("New Filter")}</span>
135
+ </button>
136
+ </li>
137
+ {renderBranch("mine", getLocalizedString("My Filters"), <StarIcon />, mine)}
138
+ {renderBranch("shared", getLocalizedString("Shared With Me"), <FilesIcon />, shared)}
139
+ </ul>
140
+ );
141
+ }
142
+
143
+ type SavedFilterNodeProps = {
144
+ filter: SavedFilter;
145
+ selected: boolean;
146
+ dirty?: boolean;
147
+ onSelect: () => void;
148
+ onRename?: (filter: SavedFilter) => void;
149
+ onDelete?: (filter: SavedFilter) => void;
150
+ onShowInfo?: (filter: SavedFilter) => void;
151
+ };
152
+
153
+ function SavedFilterNode({ filter, selected, dirty, onSelect, onRename, onDelete, onShowInfo }: SavedFilterNodeProps) {
154
+ let canRename = Boolean(onRename) && canRenameSavedFilter(filter);
155
+ let canDelete = Boolean(onDelete) && canDeleteSavedFilter(filter);
156
+ let hasMenu = canRename || canDelete || Boolean(onShowInfo);
157
+ // A shared filter the user may not change; the Windows client shows a lock.
158
+ let isReadOnly = !filter.createdByUser && filter.accessLevel === "Reader";
159
+
160
+ return (
161
+ <li className="ObSavedFilterTree-item">
162
+ <button
163
+ type="button"
164
+ className={clsx(
165
+ "ObSavedFilterTree-node",
166
+ selected && "ObSavedFilterTree-selected",
167
+ dirty && "ObSavedFilterTree-dirty",
168
+ )}
169
+ aria-current={selected}
170
+ onClick={onSelect}
171
+ >
172
+ <span className="ObSavedFilterTree-icon" aria-hidden>
173
+ {isReadOnly ? <PadlockLockedIcon /> : <FileTextIcon />}
174
+ </span>
175
+ <span className="ObSavedFilterTree-label">{filter.name}</span>
176
+ </button>
177
+ {hasMenu && (
178
+ <Dropdown.TriggerContext>
179
+ <Dropdown.Trigger
180
+ variant="tertiary"
181
+ icon
182
+ className="ObSavedFilterTree-menuButton"
183
+ aria-label={getLocalizedString("Options for {0}").replace("{0}", filter.name)}
184
+ >
185
+ <MenuElipsisVerticalIcon aria-hidden />
186
+ </Dropdown.Trigger>
187
+ <Dropdown>
188
+ <Dropdown.List>
189
+ {canRename && (
190
+ <Dropdown.Item>
191
+ <Dropdown.Button onClick={() => onRename?.(filter)}>
192
+ <PencilIcon aria-hidden />
193
+ {getLocalizedString("Rename")}
194
+ </Dropdown.Button>
195
+ </Dropdown.Item>
196
+ )}
197
+ {onShowInfo && (
198
+ <Dropdown.Item>
199
+ <Dropdown.Button onClick={() => onShowInfo(filter)}>
200
+ {getLocalizedString("Information")}
201
+ </Dropdown.Button>
202
+ </Dropdown.Item>
203
+ )}
204
+ {canDelete && (
205
+ <Dropdown.Item>
206
+ <Dropdown.Button data-color="danger" onClick={() => onDelete?.(filter)}>
207
+ <TrashIcon aria-hidden />
208
+ {getLocalizedString("Delete")}
209
+ </Dropdown.Button>
210
+ </Dropdown.Item>
211
+ )}
212
+ </Dropdown.List>
213
+ </Dropdown>
214
+ </Dropdown.TriggerContext>
215
+ )}
216
+ </li>
217
+ );
218
+ }