@drivenets/design-system 0.18.1 → 0.20.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 (216) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/dist/index.cjs +17038 -10114
  3. package/dist/index.d.cts +1355 -444
  4. package/dist/index.d.cts.map +1 -1
  5. package/dist/index.d.ts +1354 -443
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +17073 -10172
  8. package/dist/index.js.map +1 -1
  9. package/dist/index.min.css +1 -1
  10. package/package.json +41 -41
  11. package/src/components/ds-bot-button/ds-bot-button.figma.ts +38 -0
  12. package/src/components/ds-bot-button/ds-bot-button.module.scss +41 -0
  13. package/src/components/ds-bot-button/ds-bot-button.tsx +48 -0
  14. package/src/components/ds-bot-button/ds-bot-button.types.ts +33 -0
  15. package/src/components/ds-bot-button/index.ts +2 -0
  16. package/src/components/ds-bot-button/net-gen-logo.tsx +46 -0
  17. package/src/components/ds-bulk-actions/components/action-button.tsx +42 -0
  18. package/src/components/ds-bulk-actions/components/menu-entries.tsx +76 -0
  19. package/src/components/ds-bulk-actions/components/overflow.tsx +75 -0
  20. package/src/components/ds-bulk-actions/ds-bulk-actions.figma.ts +28 -0
  21. package/src/components/ds-bulk-actions/ds-bulk-actions.module.scss +199 -0
  22. package/src/components/ds-bulk-actions/ds-bulk-actions.tsx +220 -0
  23. package/src/components/ds-bulk-actions/ds-bulk-actions.types.ts +170 -0
  24. package/src/components/ds-bulk-actions/ds-bulk-actions.unit.test.ts +195 -0
  25. package/src/components/ds-bulk-actions/ds-bulk-actions.utils.ts +91 -0
  26. package/src/components/ds-bulk-actions/ds-bulk-actions.utils.unit.test.ts +134 -0
  27. package/src/components/ds-bulk-actions/hooks/compute-bulk-actions-overflow.ts +85 -0
  28. package/src/components/ds-bulk-actions/hooks/use-bulk-actions-overflow.ts +153 -0
  29. package/src/components/ds-bulk-actions/index.ts +18 -0
  30. package/src/components/ds-button-v3/ds-button-v3.module.scss +2 -2
  31. package/src/components/ds-catalog-layout/ds-catalog-layout.figma.ts +1 -1
  32. package/src/components/ds-checkbox/ds-checkbox.figma.ts +3 -0
  33. package/src/components/ds-checkbox/ds-checkbox.module.scss +102 -20
  34. package/src/components/ds-checkbox/ds-checkbox.tsx +30 -2
  35. package/src/components/ds-checkbox/ds-checkbox.types.ts +10 -0
  36. package/src/components/ds-code-input/collapsed-code-field/collapsed-code-field.module.scss +169 -0
  37. package/src/components/ds-code-input/collapsed-code-field/collapsed-code-field.tsx +92 -0
  38. package/src/components/ds-code-input/collapsed-code-field/index.ts +1 -0
  39. package/src/components/ds-code-input/ds-code-input-panel/ds-code-input-panel.module.scss +110 -0
  40. package/src/components/ds-code-input/ds-code-input-panel/ds-code-input-panel.tsx +61 -0
  41. package/src/components/ds-code-input/ds-code-input-panel/index.ts +1 -0
  42. package/src/components/ds-code-input/ds-code-input.module.scss +25 -0
  43. package/src/components/ds-code-input/ds-code-input.tsx +401 -0
  44. package/src/components/ds-code-input/ds-code-input.types.ts +166 -0
  45. package/src/components/ds-code-input/ds-code-input.utils.ts +94 -0
  46. package/src/components/ds-code-input/ds-code-input.utils.unit.test.ts +166 -0
  47. package/src/components/ds-code-input/index.ts +2 -0
  48. package/src/components/ds-code-input/use-collapsed-viewport.ts +289 -0
  49. package/src/components/ds-code-input/use-editor-host.ts +87 -0
  50. package/src/components/ds-dropdown-menu/ds-dropdown-menu.tsx +2 -0
  51. package/src/components/ds-empty-state/ds-empty-state.module.scss +16 -0
  52. package/src/components/ds-empty-state/ds-empty-state.tsx +45 -0
  53. package/src/components/ds-empty-state/ds-empty-state.types.ts +38 -0
  54. package/src/components/ds-empty-state/index.ts +3 -0
  55. package/src/components/ds-file-upload/ds-file-upload.tsx +4 -1
  56. package/src/components/ds-filters-bar/components/ds-filters-bar-builder/ds-filters-bar-builder.ts +11 -0
  57. package/src/components/ds-filters-bar/components/ds-filters-bar-builder/ds-filters-bar-builder.types.ts +41 -0
  58. package/src/components/ds-filters-bar/components/ds-filters-bar-builder/index.ts +1 -0
  59. package/src/components/ds-filters-bar/components/ds-filters-bar-clear-all.ts +11 -0
  60. package/src/components/ds-filters-bar/components/ds-filters-bar-conditions/ds-filters-bar-conditions.ts +11 -0
  61. package/src/components/ds-filters-bar/components/ds-filters-bar-conditions/ds-filters-bar-conditions.types.ts +30 -0
  62. package/src/components/ds-filters-bar/components/ds-filters-bar-conditions/index.ts +1 -0
  63. package/src/components/ds-filters-bar/components/ds-filters-bar-pinned.ts +31 -0
  64. package/src/components/ds-filters-bar/components/ds-filters-bar-query/ds-filters-bar-query.ts +11 -0
  65. package/src/components/ds-filters-bar/components/ds-filters-bar-query/ds-filters-bar-query.types.ts +22 -0
  66. package/src/components/ds-filters-bar/components/ds-filters-bar-query/index.ts +1 -0
  67. package/src/components/ds-filters-bar/components/ds-filters-bar-saved-filters.ts +19 -0
  68. package/src/components/ds-filters-bar/components/ds-filters-bar-search.ts +11 -0
  69. package/src/components/ds-filters-bar/components/ds-filters-bar-summary.ts +11 -0
  70. package/src/components/ds-filters-bar/components/ds-filters-bar-toolbar.ts +11 -0
  71. package/src/components/ds-filters-bar/components/ds-filters-bar-view-switch.ts +11 -0
  72. package/src/components/ds-filters-bar/components/ds-filters-bar-view.ts +11 -0
  73. package/src/components/ds-filters-bar/components/index.ts +11 -0
  74. package/src/components/ds-filters-bar/ds-filters-bar.context.ts +69 -0
  75. package/src/components/ds-filters-bar/ds-filters-bar.tsx +120 -0
  76. package/src/components/ds-filters-bar/ds-filters-bar.types.ts +440 -0
  77. package/src/components/ds-filters-bar/ds-filters-bar.unit.test.ts +216 -0
  78. package/src/components/ds-filters-bar/ds-filters-bar.utils.ts +141 -0
  79. package/src/components/ds-filters-bar/index.ts +1 -0
  80. package/src/components/ds-form-control/ds-form-control.module.scss +18 -0
  81. package/src/components/ds-form-control/ds-form-control.tsx +61 -19
  82. package/src/components/ds-form-control/ds-form-control.types.ts +2 -1
  83. package/src/components/ds-icon/custom-icons/index.ts +2 -0
  84. package/src/components/ds-icon/custom-icons/special-adv-filters.tsx +28 -0
  85. package/src/components/ds-illustration/ds-illustration.figma.ts +34 -0
  86. package/src/components/ds-illustration/ds-illustration.module.scss +6 -0
  87. package/src/components/ds-illustration/ds-illustration.tsx +29 -0
  88. package/src/components/ds-illustration/ds-illustration.types.ts +45 -0
  89. package/src/components/ds-illustration/illustrations/configuration.tsx +51 -0
  90. package/src/components/ds-illustration/illustrations/device.tsx +133 -0
  91. package/src/components/ds-illustration/illustrations/done.tsx +107 -0
  92. package/src/components/{ds-catalog-layout/catalog-layout-empty-illustration.tsx → ds-illustration/illustrations/empty-inbox.tsx} +10 -14
  93. package/src/components/ds-illustration/illustrations/error.tsx +86 -0
  94. package/src/components/ds-illustration/illustrations/go-filter.tsx +51 -0
  95. package/src/components/ds-illustration/illustrations/illustration-clouds.tsx +10 -0
  96. package/src/components/ds-illustration/illustrations/index.ts +39 -0
  97. package/src/components/ds-illustration/illustrations/no-connection.tsx +54 -0
  98. package/src/components/ds-illustration/illustrations/no-credit-card.tsx +48 -0
  99. package/src/components/ds-illustration/illustrations/no-device.tsx +61 -0
  100. package/src/components/ds-illustration/illustrations/no-documents.tsx +52 -0
  101. package/src/components/ds-illustration/illustrations/no-gps.tsx +57 -0
  102. package/src/components/ds-illustration/illustrations/no-images.tsx +82 -0
  103. package/src/components/ds-illustration/illustrations/no-items-cart.tsx +86 -0
  104. package/src/components/ds-illustration/illustrations/no-messages.tsx +51 -0
  105. package/src/components/ds-illustration/illustrations/no-search-result.tsx +76 -0
  106. package/src/components/ds-illustration/illustrations/no-tasks.tsx +61 -0
  107. package/src/components/ds-illustration/illustrations/site.tsx +80 -0
  108. package/src/components/ds-illustration/index.ts +3 -0
  109. package/src/components/ds-main-menu/components/expanded-item/expanded-item.tsx +108 -0
  110. package/src/components/ds-main-menu/components/expanded-item/expanded-item.types.ts +8 -0
  111. package/src/components/ds-main-menu/components/expanded-item/index.ts +1 -0
  112. package/src/components/ds-main-menu/components/menu-graphic/index.ts +1 -0
  113. package/src/components/ds-main-menu/components/menu-graphic/menu-graphic.tsx +16 -0
  114. package/src/components/ds-main-menu/components/menu-graphic/menu-graphic.types.ts +7 -0
  115. package/src/components/ds-main-menu/components/tile-link/index.ts +1 -0
  116. package/src/components/ds-main-menu/components/tile-link/tile-link.tsx +85 -0
  117. package/src/components/ds-main-menu/components/tile-link/tile-link.types.ts +8 -0
  118. package/src/components/ds-main-menu/components/utility-link/index.ts +1 -0
  119. package/src/components/ds-main-menu/components/utility-link/utility-link.tsx +37 -0
  120. package/src/components/ds-main-menu/components/utility-link/utility-link.types.ts +7 -0
  121. package/src/components/ds-main-menu/ds-main-menu.figma.ts +17 -0
  122. package/src/components/ds-main-menu/ds-main-menu.module.scss +349 -0
  123. package/src/components/ds-main-menu/ds-main-menu.tsx +102 -0
  124. package/src/components/ds-main-menu/ds-main-menu.types.ts +86 -0
  125. package/src/components/ds-main-menu/ds-main-menu.utils.ts +40 -0
  126. package/src/components/ds-main-menu/index.ts +9 -0
  127. package/src/components/ds-modal/figma/ds-modal-footer.figma.ts +1 -1
  128. package/src/components/ds-pin-toggle/ds-pin-toggle.figma.ts +27 -0
  129. package/src/components/ds-pin-toggle/ds-pin-toggle.module.scss +41 -0
  130. package/src/components/ds-pin-toggle/ds-pin-toggle.tsx +43 -0
  131. package/src/components/ds-pin-toggle/ds-pin-toggle.types.ts +29 -0
  132. package/src/components/ds-pin-toggle/index.ts +2 -0
  133. package/src/components/ds-popover/components/popover-trigger/index.ts +1 -0
  134. package/src/components/ds-popover/components/popover-trigger/popover-trigger.tsx +15 -0
  135. package/src/components/ds-popover/ds-popover.context.ts +18 -0
  136. package/src/components/ds-popover/ds-popover.hover-intent.ts +65 -0
  137. package/src/components/ds-popover/ds-popover.tsx +164 -41
  138. package/src/components/ds-popover/ds-popover.types.ts +65 -0
  139. package/src/components/ds-popover/ds-popover.utils.ts +60 -0
  140. package/src/components/ds-popover/ds-popover.utils.unit.test.ts +25 -0
  141. package/src/components/ds-saved-filters/components/ds-saved-filters-delete-confirm.tsx +53 -0
  142. package/src/components/ds-saved-filters/components/ds-saved-filters-name-entry.tsx +64 -0
  143. package/src/components/ds-saved-filters/components/ds-saved-filters-name-modal.tsx +50 -0
  144. package/src/components/ds-saved-filters/components/ds-saved-filters-row/ds-saved-filters-row.module.scss +68 -0
  145. package/src/components/ds-saved-filters/components/ds-saved-filters-row/ds-saved-filters-row.tsx +83 -0
  146. package/src/components/ds-saved-filters/components/ds-saved-filters-row/index.ts +1 -0
  147. package/src/components/ds-saved-filters/components/ds-saved-filters-save.tsx +164 -0
  148. package/src/components/ds-saved-filters/components/ds-saved-filters-trigger/ds-saved-filters-trigger.module.scss +21 -0
  149. package/src/components/ds-saved-filters/components/ds-saved-filters-trigger/ds-saved-filters-trigger.tsx +237 -0
  150. package/src/components/ds-saved-filters/components/ds-saved-filters-trigger/index.ts +1 -0
  151. package/src/components/ds-saved-filters/components/index.ts +2 -0
  152. package/src/components/ds-saved-filters/ds-saved-filters.ts +6 -0
  153. package/src/components/ds-saved-filters/ds-saved-filters.types.ts +106 -0
  154. package/src/components/ds-saved-filters/ds-saved-filters.utils.ts +35 -0
  155. package/src/components/ds-saved-filters/hooks/index.ts +1 -0
  156. package/src/components/ds-saved-filters/hooks/use-picker-outside-dismiss.ts +42 -0
  157. package/src/components/ds-saved-filters/index.ts +2 -0
  158. package/src/components/ds-segment-group/ds-segment-group.module.scss +65 -37
  159. package/src/components/ds-segment-group/ds-segment-group.tsx +17 -5
  160. package/src/components/ds-segment-group/ds-segment-group.types.ts +20 -5
  161. package/src/components/ds-segment-group/figma/ds-segment-group-v2-item.figma.ts +109 -0
  162. package/src/components/ds-segment-group/figma/ds-segment-group-v2.figma.ts +31 -0
  163. package/src/components/ds-site-node/ds-site-node.tsx +2 -2
  164. package/src/components/ds-table/components/ds-table-body-virtualized/ds-table-body-virtualized.module.scss +12 -2
  165. package/src/components/ds-table/components/ds-table-body-virtualized/ds-table-body-virtualized.tsx +2 -3
  166. package/src/components/ds-table/components/ds-table-group-header-cell/ds-table-group-header-cell.module.scss +1 -6
  167. package/src/components/ds-table/components/ds-table-group-header-cell/ds-table-group-header-cell.tsx +6 -3
  168. package/src/components/ds-table/components/ds-table-header/ds-table-header.module.scss +18 -7
  169. package/src/components/ds-table/components/ds-table-header/ds-table-header.tsx +27 -21
  170. package/src/components/ds-table/components/ds-table-header-label.module.scss +12 -0
  171. package/src/components/ds-table/components/ds-table-header-label.tsx +28 -0
  172. package/src/components/ds-table/components/ds-table-row/ds-table-row.module.scss +0 -1
  173. package/src/components/ds-table/context/ds-table-context.tsx +8 -0
  174. package/src/components/ds-table/ds-table.module.scss +26 -16
  175. package/src/components/ds-table/ds-table.tsx +46 -14
  176. package/src/components/ds-table/ds-table.types.ts +33 -3
  177. package/src/components/ds-table/hooks/use-column-resize.ts +114 -11
  178. package/src/components/ds-table/hooks/use-scrollbar-spacer.ts +108 -0
  179. package/src/components/ds-table/hooks/use-scrollbar-spacer.unit.test.ts +26 -0
  180. package/src/components/ds-table/styles/_variables.scss +0 -2
  181. package/src/components/ds-table/utils/column-size.ts +274 -1
  182. package/src/components/ds-table/utils/column-size.unit.test.ts +157 -0
  183. package/src/components/ds-table/utils/constants.ts +13 -5
  184. package/src/components/ds-table/utils/table-columns.tsx +27 -13
  185. package/src/components/ds-table/utils/table-columns.unit.test.ts +44 -0
  186. package/src/components/ds-tag/ds-tag.figma.ts +23 -3
  187. package/src/components/ds-tag/ds-tag.module.scss +71 -7
  188. package/src/components/ds-tag/ds-tag.tsx +115 -14
  189. package/src/components/ds-tag/ds-tag.types.ts +70 -3
  190. package/src/components/ds-tag/ds-tag.unit.test.ts +23 -4
  191. package/src/components/ds-tag/index.ts +2 -2
  192. package/src/components/ds-text-input/ds-input-field-v02.figma.ts +2 -0
  193. package/src/components/ds-toggle/ds-toggle.module.scss +18 -0
  194. package/src/components/ds-toggle/ds-toggle.tsx +15 -9
  195. package/src/components/ds-toggle/ds-toggle.types.ts +6 -0
  196. package/src/components/ds-toggle-filter-data/ds-toggle-filter-data.module.scss +69 -0
  197. package/src/components/ds-toggle-filter-data/ds-toggle-filter-data.tsx +55 -0
  198. package/src/components/ds-toggle-filter-data/ds-toggle-filter-data.types.ts +43 -0
  199. package/src/components/ds-toggle-filter-data/index.ts +1 -0
  200. package/src/components/ds-tooltip/ds-tooltip.module.scss +8 -0
  201. package/src/components/ds-tooltip/ds-tooltip.tsx +7 -3
  202. package/src/components/ds-tooltip/ds-tooltip.types.ts +17 -0
  203. package/src/components/ds-top-bar-navigation/ds-top-bar-navigation.figma.ts +35 -0
  204. package/src/components/ds-top-bar-navigation/ds-top-bar-navigation.module.scss +150 -0
  205. package/src/components/ds-top-bar-navigation/ds-top-bar-navigation.tsx +161 -0
  206. package/src/components/ds-top-bar-navigation/ds-top-bar-navigation.types.ts +117 -0
  207. package/src/components/ds-top-bar-navigation/index.ts +2 -0
  208. package/src/components/ds-tree/ds-tree.tsx +10 -10
  209. package/src/components/ds-tree/ds-tree.types.ts +40 -7
  210. package/src/index.ts +8 -0
  211. package/src/utils/use-is-horizontally-overflowing.ts +35 -0
  212. package/src/components/ds-site-node/hooks/use-is-overflowing.ts +0 -31
  213. package/src/components/ds-table/components/ds-table-bulk-actions/ds-table-bulk-actions.module.scss +0 -112
  214. package/src/components/ds-table/components/ds-table-bulk-actions/ds-table-bulk-actions.tsx +0 -67
  215. package/src/components/ds-table/components/ds-table-bulk-actions/ds-table-bulk-actions.types.ts +0 -41
  216. package/src/components/ds-table/components/ds-table-bulk-actions/index.ts +0 -2
@@ -0,0 +1,237 @@
1
+ import { useRef, useState } from 'react';
2
+ import { DsButtonV3 } from '../../../ds-button-v3';
3
+ import { DsEmptyState } from '../../../ds-empty-state';
4
+ import { DsIcon } from '../../../ds-icon';
5
+ import { DsPopover } from '../../../ds-popover';
6
+ import { DsSpinner } from '../../../ds-spinner';
7
+ import { DsTag } from '../../../ds-tag';
8
+ import styles from './ds-saved-filters-trigger.module.scss';
9
+ import type { DsSavedFiltersTriggerProps } from '../../ds-saved-filters.types';
10
+ import {
11
+ isThenable,
12
+ PICKER_GUTTER_PX,
13
+ resolveLocale,
14
+ runMaybeAsync,
15
+ type NamedTarget,
16
+ } from '../../ds-saved-filters.utils';
17
+ import { usePickerOutsideDismiss } from '../../hooks';
18
+ import { DeleteConfirm } from '../ds-saved-filters-delete-confirm';
19
+ import { NameModal } from '../ds-saved-filters-name-modal';
20
+ import { SavedFilterRow } from '../ds-saved-filters-row';
21
+
22
+ const PICKER_WIDTH_PX = 320;
23
+
24
+ /**
25
+ * @summary Tag and picker for named filter snapshots, including rename and delete.
26
+ *
27
+ * Internal to the filters component and not exported from the package.
28
+ */
29
+ export const DsSavedFiltersTrigger = ({
30
+ items,
31
+ value,
32
+ dirty,
33
+ locale: localeProp,
34
+ ref,
35
+ className,
36
+ style,
37
+ onValueChange,
38
+ onClear,
39
+ onRename,
40
+ onDelete,
41
+ }: DsSavedFiltersTriggerProps) => {
42
+ const locale = resolveLocale(localeProp);
43
+ const [open, setOpen] = useState(false);
44
+ const [renameTarget, setRenameTarget] = useState<NamedTarget | null>(null);
45
+ const [deleteTarget, setDeleteTarget] = useState<NamedTarget | null>(null);
46
+ const [clearing, setClearing] = useState(false);
47
+ const [renaming, setRenaming] = useState(false);
48
+ const [deleting, setDeleting] = useState(false);
49
+ const triggerWrapRef = useRef<HTMLDivElement>(null);
50
+ const activeItem = items.find((item) => item.id === value);
51
+ const label = activeItem?.name ?? locale.savedFilters;
52
+ const hasItems = items.length > 0;
53
+ const triggerBusy = clearing || renaming || deleting;
54
+
55
+ usePickerOutsideDismiss(open, triggerWrapRef, () => setOpen(false));
56
+
57
+ const togglePicker = () => {
58
+ if (triggerBusy) {
59
+ return;
60
+ }
61
+
62
+ setOpen((current) => !current);
63
+ };
64
+
65
+ const handleApply = (id: string) => {
66
+ if (triggerBusy) {
67
+ return;
68
+ }
69
+
70
+ onValueChange(id);
71
+ setOpen(false);
72
+ };
73
+
74
+ const handleClear = async () => {
75
+ if (triggerBusy) {
76
+ return;
77
+ }
78
+
79
+ await runMaybeAsync(onClear(), setClearing);
80
+ };
81
+
82
+ const handleRename = async (name: string) => {
83
+ if (!renameTarget || triggerBusy) {
84
+ return;
85
+ }
86
+
87
+ const target = renameTarget;
88
+ const result = onRename(target.id, name);
89
+
90
+ if (!isThenable(result)) {
91
+ setRenameTarget(null);
92
+
93
+ return;
94
+ }
95
+
96
+ setRenaming(true);
97
+
98
+ try {
99
+ await result;
100
+ setRenameTarget(null);
101
+ } catch {
102
+ // Overlay stays open; the consumer handles the error.
103
+ } finally {
104
+ setRenaming(false);
105
+ }
106
+ };
107
+
108
+ const handleDelete = async () => {
109
+ if (!deleteTarget || triggerBusy) {
110
+ return;
111
+ }
112
+
113
+ const target = deleteTarget;
114
+ const result = onDelete(target.id);
115
+
116
+ if (!isThenable(result)) {
117
+ setDeleteTarget(null);
118
+
119
+ return;
120
+ }
121
+
122
+ setDeleting(true);
123
+
124
+ try {
125
+ await result;
126
+ setDeleteTarget(null);
127
+ } catch {
128
+ // Overlay stays open; the consumer handles the error.
129
+ } finally {
130
+ setDeleting(false);
131
+ }
132
+ };
133
+
134
+ const tag = (
135
+ <DsTag
136
+ ref={ref}
137
+ className={className}
138
+ style={style}
139
+ label={label}
140
+ selected={Boolean(activeItem)}
141
+ warning={dirty}
142
+ slots={{
143
+ icon: clearing ? (
144
+ <DsSpinner size="small" />
145
+ ) : (
146
+ <DsIcon icon="bookmark" size="small" filled={Boolean(activeItem)} />
147
+ ),
148
+ }}
149
+ locale={{
150
+ deleteAriaLabel: locale.clearAriaLabel,
151
+ expandAriaLabel: locale.expandAriaLabel,
152
+ warningAriaLabel: locale.unsavedChanges,
153
+ }}
154
+ onClick={togglePicker}
155
+ onDelete={activeItem ? handleClear : undefined}
156
+ onExpandClick={hasItems ? togglePicker : undefined}
157
+ />
158
+ );
159
+
160
+ return (
161
+ <>
162
+ <DsPopover.Root
163
+ open={open}
164
+ side="bottom"
165
+ align="start"
166
+ gutter={PICKER_GUTTER_PX}
167
+ onInteractOutside={(event) => event.preventDefault()}
168
+ onOpenChange={setOpen}
169
+ >
170
+ <DsPopover.Anchor>
171
+ <div ref={triggerWrapRef}>{tag}</div>
172
+ </DsPopover.Anchor>
173
+ <DsPopover.Panel width={PICKER_WIDTH_PX}>
174
+ <div className={styles.pickerHeader}>
175
+ <DsPopover.Header className={styles.pickerTitle}>{locale.savedFilters}</DsPopover.Header>
176
+ <DsButtonV3
177
+ className={styles.pickerClose}
178
+ variant="tertiary"
179
+ size="small"
180
+ icon="close"
181
+ aria-label={locale.close}
182
+ onClick={() => setOpen(false)}
183
+ />
184
+ </div>
185
+ <DsPopover.Content className={styles.pickerContent}>
186
+ {hasItems ? (
187
+ items.map((item) => (
188
+ <SavedFilterRow
189
+ key={item.id}
190
+ item={item}
191
+ selected={item.id === value}
192
+ blocked={triggerBusy}
193
+ locale={locale}
194
+ onRename={setRenameTarget}
195
+ onDelete={setDeleteTarget}
196
+ onApply={handleApply}
197
+ />
198
+ ))
199
+ ) : (
200
+ <DsEmptyState variant="noData" message={locale.noSavedFilters} />
201
+ )}
202
+ </DsPopover.Content>
203
+ </DsPopover.Panel>
204
+ </DsPopover.Root>
205
+ {renameTarget ? (
206
+ <NameModal
207
+ title={locale.renameTitle}
208
+ initialName={renameTarget.name}
209
+ entryKey={`rename-${renameTarget.id}`}
210
+ locale={locale}
211
+ loading={renaming}
212
+ onSubmit={handleRename}
213
+ onClose={() => {
214
+ if (!renaming) {
215
+ setRenameTarget(null);
216
+ }
217
+ }}
218
+ />
219
+ ) : null}
220
+ {deleteTarget ? (
221
+ <DeleteConfirm
222
+ target={deleteTarget}
223
+ locale={locale}
224
+ loading={deleting}
225
+ onConfirm={handleDelete}
226
+ onClose={() => {
227
+ if (!deleting) {
228
+ setDeleteTarget(null);
229
+ }
230
+ }}
231
+ />
232
+ ) : null}
233
+ </>
234
+ );
235
+ };
236
+
237
+ DsSavedFiltersTrigger.displayName = 'DsSavedFilters.Trigger';
@@ -0,0 +1 @@
1
+ export { DsSavedFiltersTrigger } from './ds-saved-filters-trigger';
@@ -0,0 +1,2 @@
1
+ export { DsSavedFiltersSave } from './ds-saved-filters-save';
2
+ export { DsSavedFiltersTrigger } from './ds-saved-filters-trigger';
@@ -0,0 +1,6 @@
1
+ import { DsSavedFiltersSave, DsSavedFiltersTrigger } from './components';
2
+
3
+ export const DsSavedFilters = {
4
+ Trigger: DsSavedFiltersTrigger,
5
+ Save: DsSavedFiltersSave,
6
+ };
@@ -0,0 +1,106 @@
1
+ import type { CSSProperties, Ref } from 'react';
2
+
3
+ export interface DsSavedFilterItem {
4
+ id: string;
5
+ name: string;
6
+ /**
7
+ * Number of **Filter conditions** in this snapshot. Omit to hide the trailing count.
8
+ */
9
+ count?: number;
10
+ }
11
+
12
+ export interface DsSavedFiltersLocale {
13
+ savedFilters?: string;
14
+ noSavedFilters?: string;
15
+ saveFilter?: string;
16
+ /**
17
+ * Menu item for overwriting the active snapshot. `{name}` is replaced with the active name.
18
+ */
19
+ updateFilter?: string;
20
+ saveAsNew?: string;
21
+ renameFilter?: string;
22
+ deleteFilter?: string;
23
+ nameLabel?: string;
24
+ confirm?: string;
25
+ cancel?: string;
26
+ close?: string;
27
+ unsavedChanges?: string;
28
+ rowActions?: string;
29
+ expandAriaLabel?: string;
30
+ clearAriaLabel?: string;
31
+ saveAsTitle?: string;
32
+ renameTitle?: string;
33
+ deleteTitle?: string;
34
+ /**
35
+ * Confirmation body. `{name}` is replaced with the snapshot name.
36
+ */
37
+ deleteMessage?: string;
38
+ deleteConfirm?: string;
39
+ }
40
+
41
+ export const defaultDsSavedFiltersLocale: Required<DsSavedFiltersLocale> = Object.freeze({
42
+ savedFilters: 'Saved filters',
43
+ noSavedFilters: 'No saved filters yet. Build a filter, then choose “Save filter”.',
44
+ saveFilter: 'Save filter',
45
+ updateFilter: 'Update “{name}”',
46
+ saveAsNew: 'Save as a new filter',
47
+ renameFilter: 'Rename filter',
48
+ deleteFilter: 'Delete filter',
49
+ nameLabel: 'Filter name',
50
+ confirm: 'Save',
51
+ cancel: 'Cancel',
52
+ close: 'Close',
53
+ unsavedChanges: 'Unsaved changes',
54
+ rowActions: 'Filter actions',
55
+ expandAriaLabel: 'Open saved filters',
56
+ clearAriaLabel: 'Clear filters',
57
+ saveAsTitle: 'Save as a new filter',
58
+ renameTitle: 'Rename filter',
59
+ deleteTitle: 'Delete filter',
60
+ deleteMessage: 'Delete “{name}”? This cannot be undone.',
61
+ deleteConfirm: 'Delete',
62
+ });
63
+
64
+ /**
65
+ * Return a Promise to keep the launching control in a loading state until it settles.
66
+ */
67
+ export type DsSavedFiltersAsyncHandler<Args extends unknown[] = []> = (...args: Args) => void | Promise<void>;
68
+
69
+ export interface DsSavedFiltersTriggerProps {
70
+ items: ReadonlyArray<DsSavedFilterItem>;
71
+ /**
72
+ * Id of the **Active saved filter**, or `null` when none is applied.
73
+ */
74
+ value: string | null;
75
+ /**
76
+ * Whether the working document diverged from the **Active saved filter**.
77
+ */
78
+ dirty?: boolean;
79
+ locale?: DsSavedFiltersLocale;
80
+ ref?: Ref<HTMLDivElement>;
81
+ className?: string;
82
+ style?: CSSProperties;
83
+ onValueChange: (id: string | null) => void;
84
+ onClear: DsSavedFiltersAsyncHandler;
85
+ onRename: DsSavedFiltersAsyncHandler<[id: string, name: string]>;
86
+ onDelete: DsSavedFiltersAsyncHandler<[id: string]>;
87
+ }
88
+
89
+ export interface DsSavedFiltersSaveProps {
90
+ items: ReadonlyArray<DsSavedFilterItem>;
91
+ /**
92
+ * Id of the **Active saved filter**, or `null` when none is applied.
93
+ */
94
+ value: string | null;
95
+ /**
96
+ * Blocks saving, for example while there is nothing to save
97
+ * @default false
98
+ */
99
+ disabled?: boolean;
100
+ locale?: DsSavedFiltersLocale;
101
+ ref?: Ref<HTMLButtonElement>;
102
+ className?: string;
103
+ style?: CSSProperties;
104
+ onUpdate: DsSavedFiltersAsyncHandler;
105
+ onSaveAs: DsSavedFiltersAsyncHandler<[name: string]>;
106
+ }
@@ -0,0 +1,35 @@
1
+ import { defaultDsSavedFiltersLocale, type DsSavedFiltersLocale } from './ds-saved-filters.types';
2
+
3
+ export const PICKER_GUTTER_PX = 4;
4
+ export const NAME_MODAL_COLUMNS = 4;
5
+
6
+ export type Locale = Required<DsSavedFiltersLocale>;
7
+ export type NamedTarget = { id: string; name: string };
8
+
9
+ export const resolveLocale = (locale?: DsSavedFiltersLocale): Locale => ({
10
+ ...defaultDsSavedFiltersLocale,
11
+ ...locale,
12
+ });
13
+
14
+ export const interpolateName = (template: string, name: string) => template.replace('{name}', name);
15
+
16
+ export const isThenable = (value: unknown): value is Promise<unknown> =>
17
+ typeof value === 'object' && value !== null && typeof (value as Promise<unknown>).then === 'function';
18
+
19
+ export const runMaybeAsync = async (result: void | Promise<void>, setBusy: (busy: boolean) => void) => {
20
+ if (!isThenable(result)) {
21
+ return true;
22
+ }
23
+
24
+ setBusy(true);
25
+
26
+ try {
27
+ await result;
28
+
29
+ return true;
30
+ } catch {
31
+ return false;
32
+ } finally {
33
+ setBusy(false);
34
+ }
35
+ };
@@ -0,0 +1 @@
1
+ export { usePickerOutsideDismiss } from './use-picker-outside-dismiss';
@@ -0,0 +1,42 @@
1
+ import { useEffect, type RefObject } from 'react';
2
+
3
+ const OVERLAY_SCOPES = '[data-scope="popover"], [data-scope="menu"], [data-scope="dialog"]';
4
+
5
+ const isPickerDismissTarget = (event: Event, triggerEl: HTMLElement | null) => {
6
+ const path = event.composedPath();
7
+
8
+ if (triggerEl && path.includes(triggerEl)) {
9
+ return true;
10
+ }
11
+
12
+ return path.some((node) => node instanceof Element && Boolean(node.closest(OVERLAY_SCOPES)));
13
+ };
14
+
15
+ /**
16
+ * Closes the picker on a real outside pointer.
17
+ * Ark interact-outside is suppressed so portaled menus/dialogs stay usable;
18
+ * this restores dismiss without treating those layers as outside.
19
+ */
20
+ export const usePickerOutsideDismiss = (
21
+ enabled: boolean,
22
+ triggerEl: RefObject<HTMLElement | null>,
23
+ onDismiss: () => void,
24
+ ) => {
25
+ useEffect(() => {
26
+ if (!enabled) {
27
+ return;
28
+ }
29
+
30
+ const onPointerDown = (event: PointerEvent) => {
31
+ if (isPickerDismissTarget(event, triggerEl.current)) {
32
+ return;
33
+ }
34
+
35
+ onDismiss();
36
+ };
37
+
38
+ document.addEventListener('pointerdown', onPointerDown);
39
+
40
+ return () => document.removeEventListener('pointerdown', onPointerDown);
41
+ }, [enabled, triggerEl, onDismiss]);
42
+ };
@@ -0,0 +1,2 @@
1
+ export { DsSavedFilters } from './ds-saved-filters';
2
+ export type { DsSavedFiltersSaveProps, DsSavedFiltersTriggerProps } from './ds-saved-filters.types';
@@ -1,20 +1,30 @@
1
+ $transition-duration: 0.2s;
2
+ $focus-border-width: 2px;
3
+
4
+ @mixin item-size($height, $inline-padding) {
5
+ height: $height;
6
+ padding: 0 $inline-padding;
7
+
8
+ &:not(:has(.segmentItemText)) {
9
+ width: $height;
10
+ padding: 0;
11
+ }
12
+ }
13
+
1
14
  .segmentGroupRoot {
2
15
  display: inline-flex;
3
16
  align-items: center;
4
17
  position: relative;
18
+ box-sizing: border-box;
19
+ padding: var(--3xs);
20
+ gap: var(--2xs);
5
21
  background: var(--background-secondary);
6
22
  border: 1px solid var(--border-contrast);
7
- border-radius: 4px;
8
- gap: var(--2xs);
9
- height: 36px;
10
- padding: var(--3xs);
23
+ border-radius: var(--3xs);
11
24
 
12
25
  &.small {
13
- height: 28px;
14
- padding: var(--4xs);
15
-
16
26
  .segmentItem {
17
- padding: var(--4xs) var(--xs);
27
+ @include item-size(var(--lg), var(--xs));
18
28
  }
19
29
  }
20
30
  }
@@ -24,49 +34,68 @@
24
34
  display: flex;
25
35
  align-items: center;
26
36
  justify-content: center;
27
- padding: var(--3xs) var(--sm);
28
- gap: var(--2xs);
29
- flex: 1;
37
+ box-sizing: border-box;
30
38
  z-index: 1;
39
+ gap: var(--3xs);
40
+ @include item-size(var(--xl), var(--sm));
41
+ border: 1px solid transparent;
42
+ border-radius: var(--3xs);
43
+ color: var(--font-main);
31
44
  cursor: pointer;
32
- border-radius: 4px;
33
-
34
- &[data-state='checked'] {
35
- .segmentItemText {
36
- color: var(--font-main);
37
- font-weight: var(--font-weight-medium);
38
- }
39
- }
45
+ transition:
46
+ background-color $transition-duration,
47
+ border-color $transition-duration,
48
+ color $transition-duration,
49
+ border-width $transition-duration;
40
50
 
41
51
  &[data-state='unchecked'] {
42
52
  background: transparent;
43
53
 
44
- .segmentItemText {
45
- color: var(--background-action);
54
+ &:hover:not([data-disabled]) {
55
+ background: var(--background);
46
56
  }
47
57
 
58
+ &:active:not([data-disabled]) {
59
+ background: var(--background-tertiary);
60
+ }
61
+
62
+ &[data-focus-visible]:not([data-disabled]) {
63
+ background: var(--background-action-hover-weak);
64
+ border-width: $focus-border-width;
65
+ border-color: var(--border-action-secondary);
66
+ }
67
+ }
68
+
69
+ &[data-state='checked'] {
48
70
  &:hover:not([data-disabled]) {
49
- background: var(--background-action-secondary);
71
+ background: var(--background-action-hover-weak);
72
+ border-color: var(--border);
50
73
  }
51
74
 
52
75
  &:active:not([data-disabled]) {
53
- background: var(--background);
54
- border-color: var(--border-contrast);
76
+ background: var(--background-secondary-selected-weak);
77
+ border-color: var(--border-action-secondary);
78
+ }
79
+
80
+ &[data-focus-visible]:not([data-disabled]) {
81
+ background: var(--background-secondary-selected-weak);
82
+ border-width: $focus-border-width;
83
+ border-color: var(--border-action-secondary);
55
84
  }
56
85
  }
57
86
 
58
87
  &[data-disabled] {
59
88
  cursor: not-allowed;
60
- background: var(--background-secondary);
89
+ color: var(--font-disabled);
61
90
 
62
- .segmentItemText {
63
- color: var(--font-disabled);
91
+ &[data-state='unchecked'] {
92
+ background: transparent;
64
93
  }
65
- }
66
94
 
67
- &:focus-visible {
68
- outline: 2px solid var(--border-action-secondary);
69
- outline-offset: 2px;
95
+ &[data-state='checked'] {
96
+ background: var(--background);
97
+ border-color: var(--border-disabled);
98
+ }
70
99
  }
71
100
  }
72
101
 
@@ -81,19 +110,18 @@
81
110
  align-items: center;
82
111
  justify-content: center;
83
112
  white-space: nowrap;
84
- transition: color 150ms ease-in-out;
113
+ transition: color $transition-duration;
85
114
  }
86
115
 
87
116
  .segmentIndicator {
88
117
  position: absolute;
89
- background: var(--background);
90
- border: 1px solid var(--border-contrast);
91
- border-radius: 4px;
92
- box-shadow: 0 1.25px 4px 0 rgba(0, 0, 0, 0.25);
93
118
  z-index: 0;
94
- pointer-events: none;
95
119
  width: var(--width);
96
120
  height: var(--height);
97
121
  left: var(--left);
98
122
  top: var(--top);
123
+ background: var(--background);
124
+ border: 1px solid var(--border);
125
+ border-radius: var(--3xs);
126
+ pointer-events: none;
99
127
  }
@@ -32,7 +32,15 @@ const Root: React.FC<DsSegmentGroupRootProps> = ({
32
32
  /**
33
33
  * Item component - renders a single segment button
34
34
  */
35
- const Item: React.FC<DsSegmentGroupItemProps> = ({ value, label, className, style, children, ...props }) => {
35
+ const Item: React.FC<DsSegmentGroupItemProps> = ({
36
+ value,
37
+ label,
38
+ className,
39
+ style,
40
+ children,
41
+ size = 'default',
42
+ ...props
43
+ }) => {
36
44
  return (
37
45
  <SegmentGroup.Item
38
46
  value={value}
@@ -40,7 +48,7 @@ const Item: React.FC<DsSegmentGroupItemProps> = ({ value, label, className, styl
40
48
  style={style}
41
49
  {...props}
42
50
  >
43
- {label ? <ItemText>{label}</ItemText> : children}
51
+ {label ? <ItemText size={size}>{label}</ItemText> : children}
44
52
  <SegmentGroup.ItemControl />
45
53
  <SegmentGroup.ItemHiddenInput />
46
54
  </SegmentGroup.Item>
@@ -50,9 +58,14 @@ const Item: React.FC<DsSegmentGroupItemProps> = ({ value, label, className, styl
50
58
  /**
51
59
  * ItemText component - renders text content within an item
52
60
  */
53
- const ItemText: React.FC<DsSegmentGroupItemTextProps> = ({ className, children, ...props }) => (
61
+ const ItemText: React.FC<DsSegmentGroupItemTextProps> = ({
62
+ className,
63
+ children,
64
+ size = 'default',
65
+ ...props
66
+ }) => (
54
67
  <SegmentGroup.ItemText className={classNames(styles.segmentItemText, className)} {...props} asChild>
55
- <DsTypography variant="body-sm-reg">{children}</DsTypography>
68
+ <DsTypography variant={size === 'small' ? 'body-xs-md' : 'body-sm-md'}>{children}</DsTypography>
56
69
  </SegmentGroup.ItemText>
57
70
  );
58
71
 
@@ -61,7 +74,6 @@ const ItemText: React.FC<DsSegmentGroupItemTextProps> = ({ className, children,
61
74
  *
62
75
  * @example
63
76
  * <DsSegmentGroup.Root value={value} onValueChange={setValue} size="default">
64
- * <DsSegmentGroup.Indicator />
65
77
  * <DsSegmentGroup.Item value="option1" label="Option 1" />
66
78
  * <DsSegmentGroup.Item value="option2">
67
79
  * <DsIcon icon="settings" />
@@ -8,7 +8,8 @@ import {
8
8
  /**
9
9
  * Size variants for the segment group
10
10
  */
11
- export type DsSegmentGroupSize = 'small' | 'default';
11
+ export const segmentGroupSizes = ['small', 'default'] as const;
12
+ export type DsSegmentGroupSize = (typeof segmentGroupSizes)[number];
12
13
 
13
14
  /**
14
15
  * Props for the DsSegmentGroup Root component
@@ -50,9 +51,14 @@ export interface DsSegmentGroupRootProps extends Pick<
50
51
  */
51
52
  export type DsSegmentGroupItemProps = Pick<
52
53
  SegmentGroupItemProps,
53
- 'id' | 'value' | 'disabled' | 'className' | 'style'
54
- > &
55
- (
54
+ 'id' | 'value' | 'disabled' | 'className' | 'style' | 'aria-label'
55
+ > & {
56
+ /**
57
+ * Size variant of the segment item. Pass through to `ItemText` when using `label`.
58
+ * @default 'default'
59
+ */
60
+ size?: DsSegmentGroupSize;
61
+ } & (
56
62
  | {
57
63
  /**
58
64
  * The segment group item content
@@ -78,4 +84,13 @@ export type DsSegmentGroupItemProps = Pick<
78
84
  /**
79
85
  * Props for the DsSegmentGroup ItemText component
80
86
  */
81
- export type DsSegmentGroupItemTextProps = Pick<SegmentGroupItemTextProps, 'className' | 'style' | 'children'>;
87
+ export type DsSegmentGroupItemTextProps = Pick<
88
+ SegmentGroupItemTextProps,
89
+ 'className' | 'style' | 'children'
90
+ > & {
91
+ /**
92
+ * Size variant of the item text
93
+ * @default 'default'
94
+ */
95
+ size?: DsSegmentGroupSize;
96
+ };