@olenbetong/appframe-ds 1.8.0 → 1.9.1

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 +16 -1
  4. package/src/AfLookup/Lookup.tsx +37 -12
  5. package/src/AfLookup/LookupCombobox.tsx +1 -9
  6. package/src/AfLookup/LookupEdit.tsx +5 -4
  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 +3 -4
  66. package/src/grid/Toolbar.tsx +9 -38
  67. package/src/grid/index.css +44 -0
  68. package/src/grid/index.tsx +12 -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,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,
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
+ }