@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,440 @@
1
+ import type { CSSProperties, ReactNode, Ref } from 'react';
2
+ import type { DsSavedFiltersSaveProps, DsSavedFiltersTriggerProps } from '../ds-saved-filters';
3
+
4
+ export interface DsFilterOperator {
5
+ /**
6
+ * Stable id stored on the condition, for example `!=`
7
+ */
8
+ value: string;
9
+ /**
10
+ * Words for the summary line and menus, for example `not equals`
11
+ */
12
+ label: string;
13
+ /**
14
+ * Compact form for chips, for example `≠`. Falls back to `label`.
15
+ */
16
+ symbol?: string;
17
+ }
18
+
19
+ export interface DsFilterOption {
20
+ value: string;
21
+ label: string;
22
+ }
23
+
24
+ interface DsFilterFieldBase {
25
+ id: string;
26
+ label: string;
27
+ }
28
+
29
+ /**
30
+ * Picked from a fixed list of options, possibly several at once
31
+ */
32
+ export interface DsFilterEnumField extends DsFilterFieldBase {
33
+ type: 'enum';
34
+ operators: ReadonlyArray<DsFilterOperator>;
35
+ options: ReadonlyArray<DsFilterOption>;
36
+ }
37
+
38
+ export interface DsFilterTextField extends DsFilterFieldBase {
39
+ type: 'text';
40
+ operators: ReadonlyArray<DsFilterOperator>;
41
+ }
42
+
43
+ export interface DsFilterNumberField extends DsFilterFieldBase {
44
+ type: 'number';
45
+ operators: ReadonlyArray<DsFilterOperator>;
46
+ }
47
+
48
+ /**
49
+ * Filtered by a named preset such as "Today", or by a range of ISO 8601 dates
50
+ */
51
+ export interface DsFilterDateField extends DsFilterFieldBase {
52
+ type: 'date';
53
+ operators: ReadonlyArray<DsFilterOperator>;
54
+ /**
55
+ * @default []
56
+ */
57
+ presets?: ReadonlyArray<DsFilterOption>;
58
+ }
59
+
60
+ /**
61
+ * An object with named parts, for example Input › Name / Vendor / Version. A condition on it names
62
+ * one subfield.
63
+ */
64
+ export interface DsFilterCompoundField extends DsFilterFieldBase {
65
+ type: 'compound';
66
+ subfields: ReadonlyArray<DsFilterScalarField>;
67
+ }
68
+
69
+ export type DsFilterScalarField =
70
+ | DsFilterEnumField
71
+ | DsFilterTextField
72
+ | DsFilterNumberField
73
+ | DsFilterDateField;
74
+ export type DsFilterField = DsFilterScalarField | DsFilterCompoundField;
75
+
76
+ /**
77
+ * Inclusive range. `null` leaves that side open.
78
+ */
79
+ export interface DsFilterRange<T> {
80
+ from: T | null;
81
+ to: T | null;
82
+ }
83
+
84
+ /**
85
+ * The value shape depends on the field type:
86
+ * - enum: option values, always an array, even for one
87
+ * - text: a string
88
+ * - number: a number, or a range
89
+ * - date: a preset value, or a range of ISO 8601 dates
90
+ */
91
+ export type DsFilterValue =
92
+ | ReadonlyArray<string>
93
+ | string
94
+ | number
95
+ | DsFilterRange<number>
96
+ | DsFilterRange<string>;
97
+
98
+ export interface DsFilterSearchCondition {
99
+ kind: 'search';
100
+ id: string;
101
+ text: string;
102
+ }
103
+
104
+ export interface DsFilterFieldCondition {
105
+ kind: 'field';
106
+ id: string;
107
+ /**
108
+ * `DsFilterField.id`
109
+ */
110
+ field: string;
111
+ /**
112
+ * Subfield id. Required when `field` is a compound field, absent otherwise.
113
+ */
114
+ subfield?: string;
115
+ /**
116
+ * `DsFilterOperator.value`
117
+ */
118
+ operator: string;
119
+ value: DsFilterValue;
120
+ }
121
+
122
+ /**
123
+ * One **Filter condition**: produced by any view, rendered by any view
124
+ */
125
+ export type DsFilterCondition = DsFilterSearchCondition | DsFilterFieldCondition;
126
+
127
+ /**
128
+ * A field option the user pinned for quick access in the pinned row. A user preference, not part
129
+ * of the filter document: loading a saved filter or clearing leaves pins alone.
130
+ */
131
+ export interface DsFilterPin {
132
+ field: string;
133
+ value: string;
134
+ }
135
+
136
+ export const filtersBarViews = ['filters', 'builder', 'advanced'] as const;
137
+ export type DsFiltersBarView = (typeof filtersBarViews)[number];
138
+
139
+ export interface DsFiltersBarLocale {
140
+ /**
141
+ * Accessible name of the region that holds the bar
142
+ */
143
+ label?: string;
144
+ /**
145
+ * Accessible name of the disclosure button while collapsed
146
+ */
147
+ expand?: string;
148
+ /**
149
+ * Accessible name of the disclosure button while expanded
150
+ */
151
+ collapse?: string;
152
+ }
153
+
154
+ export const defaultDsFiltersBarLocale: Required<DsFiltersBarLocale> = Object.freeze({
155
+ label: 'Filters',
156
+ expand: 'Show filters',
157
+ collapse: 'Hide filters',
158
+ });
159
+
160
+ export interface DsFiltersBarRootProps {
161
+ /**
162
+ * What can be filtered. Omit for a search-only bar.
163
+ * @default []
164
+ */
165
+ fields?: ReadonlyArray<DsFilterField>;
166
+ /**
167
+ * Current conditions. Pair with `onConditionsChange`.
168
+ */
169
+ conditions?: ReadonlyArray<DsFilterCondition>;
170
+ /**
171
+ * @default []
172
+ */
173
+ defaultConditions?: ReadonlyArray<DsFilterCondition>;
174
+ /**
175
+ * Edited advanced query, or `null` while the conditions are the source. While set, it alone
176
+ * filters the data, the conditions are ignored, and the filters and builder views lock. The bar
177
+ * never parses it. Pair with `onQueryChange`.
178
+ */
179
+ query?: string | null;
180
+ /**
181
+ * @default null
182
+ */
183
+ defaultQuery?: string | null;
184
+ /**
185
+ * Pinned field options. Pair with `onPinsChange`.
186
+ */
187
+ pins?: ReadonlyArray<DsFilterPin>;
188
+ /**
189
+ * @default []
190
+ */
191
+ defaultPins?: ReadonlyArray<DsFilterPin>;
192
+ /**
193
+ * Whether the full toolbar is shown. Collapsed shows the one-line summary. Pair with
194
+ * `onExpandedChange`.
195
+ */
196
+ expanded?: boolean;
197
+ /**
198
+ * @default false
199
+ */
200
+ defaultExpanded?: boolean;
201
+ /**
202
+ * Active view. Pair with `onViewChange`.
203
+ */
204
+ view?: DsFiltersBarView;
205
+ /**
206
+ * @default 'filters'
207
+ */
208
+ defaultView?: DsFiltersBarView;
209
+ /**
210
+ * Renders the conditions as query text, shown by the advanced view while `query` is `null`. The
211
+ * query language belongs to the consumer.
212
+ * @default () => ''
213
+ */
214
+ formatQuery?: (
215
+ conditions: ReadonlyArray<DsFilterCondition>,
216
+ fields: ReadonlyArray<DsFilterField>,
217
+ ) => string;
218
+ locale?: DsFiltersBarLocale;
219
+ ref?: Ref<HTMLDivElement>;
220
+ className?: string;
221
+ style?: CSSProperties;
222
+ children: ReactNode;
223
+ onConditionsChange?: (conditions: ReadonlyArray<DsFilterCondition>) => void;
224
+ onQueryChange?: (query: string | null) => void;
225
+ onPinsChange?: (pins: ReadonlyArray<DsFilterPin>) => void;
226
+ onExpandedChange?: (expanded: boolean) => void;
227
+ onViewChange?: (view: DsFiltersBarView) => void;
228
+ }
229
+
230
+ export interface DsFiltersBarSummaryLocale {
231
+ /**
232
+ * Announced result count. Receives the count.
233
+ */
234
+ resultCount?: (count: number) => string;
235
+ /**
236
+ * Label shown before `activeSavedFilterName`
237
+ */
238
+ activeSavedFilter?: string;
239
+ /**
240
+ * Label and value shown while there are no conditions, as in `View: All`
241
+ */
242
+ emptyLabel?: string;
243
+ emptyValue?: string;
244
+ }
245
+
246
+ export const defaultDsFiltersBarSummaryLocale: Required<DsFiltersBarSummaryLocale> = Object.freeze({
247
+ resultCount: (count: number) => `${String(count)} results`,
248
+ activeSavedFilter: 'Filter',
249
+ emptyLabel: 'View',
250
+ emptyValue: 'All',
251
+ });
252
+
253
+ /**
254
+ * Collapsed row. Lists the conditions from the filter document; renders nothing while expanded.
255
+ */
256
+ export interface DsFiltersBarSummaryProps {
257
+ /**
258
+ * Number of results the current filters produce. Omit to hide the trailing count.
259
+ */
260
+ count?: number;
261
+ /**
262
+ * Name of the **Active saved filter**, shown before the conditions
263
+ */
264
+ activeSavedFilterName?: string;
265
+ locale?: DsFiltersBarSummaryLocale;
266
+ className?: string;
267
+ style?: CSSProperties;
268
+ }
269
+
270
+ /**
271
+ * Expanded row. Renders nothing while collapsed.
272
+ */
273
+ export interface DsFiltersBarToolbarProps {
274
+ className?: string;
275
+ style?: CSSProperties;
276
+ children: ReactNode;
277
+ }
278
+
279
+ /**
280
+ * Clearing the **Active saved filter** also clears the filter document before `onClear` runs.
281
+ */
282
+ export type DsFiltersBarSavedFiltersProps = DsSavedFiltersTriggerProps;
283
+
284
+ export interface DsFiltersBarSaveFilterProps extends Omit<DsSavedFiltersSaveProps, 'disabled'> {
285
+ /**
286
+ * Defaults to disabled while the filter document is empty
287
+ */
288
+ disabled?: boolean;
289
+ }
290
+
291
+ export interface DsFiltersBarSearchLocale {
292
+ label?: string;
293
+ placeholder?: string;
294
+ }
295
+
296
+ export const defaultDsFiltersBarSearchLocale: Required<DsFiltersBarSearchLocale> = Object.freeze({
297
+ label: 'Search',
298
+ placeholder: 'Type ‘/’ to search',
299
+ });
300
+
301
+ /**
302
+ * Free-text input. Enter adds the trimmed text as a search condition and clears the input; `/`
303
+ * focuses it from anywhere outside an editable element.
304
+ */
305
+ export interface DsFiltersBarSearchProps {
306
+ /**
307
+ * Pending text, before it becomes a condition. Pair with `onValueChange`.
308
+ */
309
+ value?: string;
310
+ /**
311
+ * @default ''
312
+ */
313
+ defaultValue?: string;
314
+ /**
315
+ * @default false
316
+ */
317
+ disabled?: boolean;
318
+ locale?: DsFiltersBarSearchLocale;
319
+ ref?: Ref<HTMLInputElement>;
320
+ className?: string;
321
+ style?: CSSProperties;
322
+ onValueChange?: (value: string) => void;
323
+ }
324
+
325
+ export interface DsFiltersBarViewSwitchLocale {
326
+ label?: string;
327
+ /**
328
+ * Accessible names of the icon-only items
329
+ */
330
+ views?: Partial<Record<DsFiltersBarView, string>>;
331
+ /**
332
+ * Tooltip on a view locked by an edited advanced query
333
+ */
334
+ lockedView?: string;
335
+ }
336
+
337
+ export const defaultDsFiltersBarViewSwitchLocale = Object.freeze({
338
+ label: 'Filter view',
339
+ views: Object.freeze({
340
+ filters: 'Filters',
341
+ builder: 'Query builder',
342
+ advanced: 'Advanced query',
343
+ }),
344
+ lockedView: 'Clear the advanced query to switch views',
345
+ }) satisfies Required<DsFiltersBarViewSwitchLocale>;
346
+
347
+ /**
348
+ * While an edited advanced query is the source, the filters and builder views are locked.
349
+ */
350
+ export interface DsFiltersBarViewSwitchProps {
351
+ locale?: DsFiltersBarViewSwitchLocale;
352
+ className?: string;
353
+ style?: CSSProperties;
354
+ }
355
+
356
+ export interface DsFiltersBarViewProps {
357
+ /**
358
+ * Children render only while this view is active
359
+ */
360
+ value: DsFiltersBarView;
361
+ children: ReactNode;
362
+ }
363
+
364
+ export interface DsFiltersBarClearAllLocale {
365
+ label?: string;
366
+ }
367
+
368
+ export const defaultDsFiltersBarClearAllLocale: Required<DsFiltersBarClearAllLocale> = Object.freeze({
369
+ label: 'Clear all',
370
+ });
371
+
372
+ /**
373
+ * Empties the conditions and drops the advanced query. Renders nothing while the filter document
374
+ * is empty.
375
+ */
376
+ export interface DsFiltersBarClearAllProps {
377
+ locale?: DsFiltersBarClearAllLocale;
378
+ ref?: Ref<HTMLButtonElement>;
379
+ className?: string;
380
+ style?: CSSProperties;
381
+ /**
382
+ * Called after the document is cleared, for example to drop the **Active saved filter**
383
+ */
384
+ onClick?: () => void;
385
+ }
386
+
387
+ export interface DsFiltersBarPinnedLocale {
388
+ label?: string;
389
+ }
390
+
391
+ export const defaultDsFiltersBarPinnedLocale: Required<DsFiltersBarPinnedLocale> = Object.freeze({
392
+ label: 'Pinned',
393
+ });
394
+
395
+ /**
396
+ * Quick-toggle row, shown in both collapsed and expanded states. Toggles narrow the results the
397
+ * conditions already produced; they never widen them.
398
+ */
399
+ export interface DsFiltersBarPinnedProps {
400
+ locale?: DsFiltersBarPinnedLocale;
401
+ className?: string;
402
+ style?: CSSProperties;
403
+ /**
404
+ * `DsFiltersBar.PinnedGroup` elements
405
+ */
406
+ children: ReactNode;
407
+ }
408
+
409
+ export interface DsFiltersBarPinnedGroupProps {
410
+ /**
411
+ * Category name, also the accessible name of the group
412
+ */
413
+ label: string;
414
+ className?: string;
415
+ style?: CSSProperties;
416
+ /**
417
+ * `DsFiltersBar.PinnedToggle` elements
418
+ */
419
+ children: ReactNode;
420
+ }
421
+
422
+ export interface DsFiltersBarPinnedToggleProps {
423
+ label: string;
424
+ /**
425
+ * Matches within the results the conditions already produced
426
+ */
427
+ count: number;
428
+ /**
429
+ * Controlled. Surfaced as `aria-pressed`.
430
+ */
431
+ active: boolean;
432
+ /**
433
+ * Defaults to disabled when `count` is 0
434
+ */
435
+ disabled?: boolean;
436
+ ref?: Ref<HTMLButtonElement>;
437
+ className?: string;
438
+ style?: CSSProperties;
439
+ onActiveChange?: (active: boolean) => void;
440
+ }
@@ -0,0 +1,216 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import type { DsFilterCondition, DsFilterField } from './ds-filters-bar.types';
3
+ import {
4
+ appendCondition,
5
+ createConditionId,
6
+ createSearchCondition,
7
+ describeCondition,
8
+ isFiltersBarView,
9
+ lockedViewsFor,
10
+ normalizeQuery,
11
+ removeConditionById,
12
+ replaceCondition,
13
+ } from './ds-filters-bar.utils';
14
+
15
+ const EQUALS = { value: '=', label: 'equals', symbol: '=' };
16
+ const NOT_EQUALS = { value: '!=', label: 'not equals', symbol: '≠' };
17
+
18
+ const FIELDS: DsFilterField[] = [
19
+ {
20
+ type: 'enum',
21
+ id: 'status',
22
+ label: 'Status',
23
+ operators: [EQUALS, NOT_EQUALS],
24
+ options: [
25
+ { value: 'active', label: 'Active' },
26
+ { value: 'deprecated', label: 'Deprecated' },
27
+ ],
28
+ },
29
+ { type: 'number', id: 'parents', label: 'Parents', operators: [{ value: '>', label: 'greater than' }] },
30
+ {
31
+ type: 'date',
32
+ id: 'lastRun',
33
+ label: 'Last run',
34
+ operators: [EQUALS],
35
+ presets: [{ value: 'today', label: 'Today' }],
36
+ },
37
+ {
38
+ type: 'compound',
39
+ id: 'input',
40
+ label: 'Input',
41
+ subfields: [{ type: 'text', id: 'name', label: 'Name', operators: [{ value: '~', label: 'contains' }] }],
42
+ },
43
+ ];
44
+
45
+ describe('isFiltersBarView', () => {
46
+ it.each(['filters', 'builder', 'advanced'])('accepts %s', (value) => {
47
+ expect(isFiltersBarView(value)).toBe(true);
48
+ });
49
+
50
+ it.each(['code', '', null])('rejects %s', (value) => {
51
+ expect(isFiltersBarView(value)).toBe(false);
52
+ });
53
+ });
54
+
55
+ describe('normalizeQuery', () => {
56
+ it('keeps edited text as is', () => {
57
+ expect(normalizeQuery(' status = "Active" ')).toBe(' status = "Active" ');
58
+ });
59
+
60
+ it.each(['', ' ', null])('turns %j into null', (query) => {
61
+ expect(normalizeQuery(query)).toBeNull();
62
+ });
63
+ });
64
+
65
+ describe('lockedViewsFor', () => {
66
+ it('locks nothing while the conditions are the source', () => {
67
+ expect(lockedViewsFor(null)).toEqual([]);
68
+ });
69
+
70
+ it('locks the filters and builder views while an edited query is the source', () => {
71
+ expect(lockedViewsFor('status = "Active"')).toEqual(['filters', 'builder']);
72
+ });
73
+ });
74
+
75
+ describe('createConditionId', () => {
76
+ it('returns a different id every call', () => {
77
+ expect(createConditionId()).not.toBe(createConditionId());
78
+ });
79
+ });
80
+
81
+ describe('createSearchCondition', () => {
82
+ it('creates a search condition with trimmed text', () => {
83
+ const condition = createSearchCondition(' AAA ');
84
+
85
+ expect(condition).toMatchObject({ kind: 'search', text: 'AAA' });
86
+ expect(condition?.id).toMatch(/^condition-/);
87
+ });
88
+
89
+ it('returns null for blank text', () => {
90
+ expect(createSearchCondition(' ')).toBeNull();
91
+ });
92
+ });
93
+
94
+ describe('condition list helpers', () => {
95
+ const first: DsFilterCondition = { kind: 'search', id: 'a', text: 'AAA' };
96
+ const second: DsFilterCondition = {
97
+ kind: 'field',
98
+ id: 'b',
99
+ field: 'status',
100
+ operator: '=',
101
+ value: 'active',
102
+ };
103
+
104
+ it('appends a condition', () => {
105
+ expect(appendCondition([first], second)).toEqual([first, second]);
106
+ });
107
+
108
+ it('replaces the condition with the same id', () => {
109
+ const edited: DsFilterCondition = { ...second, operator: '!=' };
110
+
111
+ expect(replaceCondition([first, second], edited)).toEqual([first, edited]);
112
+ });
113
+
114
+ it('removes a condition by id', () => {
115
+ expect(removeConditionById([first, second], 'a')).toEqual([second]);
116
+ });
117
+ });
118
+
119
+ describe('describeCondition', () => {
120
+ it('describes a search condition by its text only', () => {
121
+ expect(describeCondition({ kind: 'search', id: '1', text: 'WF456' }, FIELDS)).toEqual({
122
+ fieldPath: [],
123
+ value: 'WF456',
124
+ });
125
+ });
126
+
127
+ it('uses option labels for enum values', () => {
128
+ const condition: DsFilterCondition = {
129
+ kind: 'field',
130
+ id: '1',
131
+ field: 'status',
132
+ operator: '!=',
133
+ value: ['active', 'deprecated'],
134
+ };
135
+
136
+ expect(describeCondition(condition, FIELDS)).toEqual({
137
+ fieldPath: ['Status'],
138
+ operator: 'not equals',
139
+ operatorSymbol: '≠',
140
+ value: 'Active, Deprecated',
141
+ });
142
+ });
143
+
144
+ it('falls back to the operator label when there is no symbol', () => {
145
+ const condition: DsFilterCondition = {
146
+ kind: 'field',
147
+ id: '1',
148
+ field: 'parents',
149
+ operator: '>',
150
+ value: 2,
151
+ };
152
+
153
+ expect(describeCondition(condition, FIELDS)).toMatchObject({
154
+ operatorSymbol: 'greater than',
155
+ value: '2',
156
+ });
157
+ });
158
+
159
+ it('uses the preset label for a date preset', () => {
160
+ const condition: DsFilterCondition = {
161
+ kind: 'field',
162
+ id: '1',
163
+ field: 'lastRun',
164
+ operator: '=',
165
+ value: 'today',
166
+ };
167
+
168
+ expect(describeCondition(condition, FIELDS)).toMatchObject({ value: 'Today' });
169
+ });
170
+
171
+ it('joins both ends of a range', () => {
172
+ const condition: DsFilterCondition = {
173
+ kind: 'field',
174
+ id: '1',
175
+ field: 'parents',
176
+ operator: '>',
177
+ value: { from: 2, to: 5 },
178
+ };
179
+
180
+ expect(describeCondition(condition, FIELDS)).toMatchObject({ value: '2 – 5' });
181
+ });
182
+
183
+ it('includes the subfield of a compound field in the path', () => {
184
+ const condition: DsFilterCondition = {
185
+ kind: 'field',
186
+ id: '1',
187
+ field: 'input',
188
+ subfield: 'name',
189
+ operator: '~',
190
+ value: 'WF456',
191
+ };
192
+
193
+ expect(describeCondition(condition, FIELDS)).toMatchObject({
194
+ fieldPath: ['Input', 'Name'],
195
+ operator: 'contains',
196
+ value: 'WF456',
197
+ });
198
+ });
199
+
200
+ it('falls back to raw ids for a field missing from the schema', () => {
201
+ const condition: DsFilterCondition = {
202
+ kind: 'field',
203
+ id: '1',
204
+ field: 'removed',
205
+ operator: '=',
206
+ value: 'x',
207
+ };
208
+
209
+ expect(describeCondition(condition, FIELDS)).toEqual({
210
+ fieldPath: ['removed'],
211
+ operator: '=',
212
+ operatorSymbol: '=',
213
+ value: 'x',
214
+ });
215
+ });
216
+ });