@drivenets/design-system 0.0.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 (242) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/LICENSE +201 -0
  3. package/dist/index.cjs +5394 -0
  4. package/dist/index.css +1 -0
  5. package/dist/index.d.cts +9157 -0
  6. package/dist/index.d.cts.map +1 -0
  7. package/dist/index.d.ts +9157 -0
  8. package/dist/index.d.ts.map +1 -0
  9. package/dist/index.js +5335 -0
  10. package/dist/index.js.map +1 -0
  11. package/package.json +104 -0
  12. package/src/components/ds-alert-banner/ds-alert-banner.module.scss +116 -0
  13. package/src/components/ds-alert-banner/ds-alert-banner.tsx +115 -0
  14. package/src/components/ds-alert-banner/ds-alert-banner.types.tsx +47 -0
  15. package/src/components/ds-alert-banner/index.tsx +2 -0
  16. package/src/components/ds-breadcrumb/ds-breadcrumb.module.scss +99 -0
  17. package/src/components/ds-breadcrumb/ds-breadcrumb.tsx +94 -0
  18. package/src/components/ds-breadcrumb/ds-breadcrumb.types.tsx +32 -0
  19. package/src/components/ds-breadcrumb/index.tsx +2 -0
  20. package/src/components/ds-button/ds-button.tsx +12 -0
  21. package/src/components/ds-button/ds-button.types.ts +6 -0
  22. package/src/components/ds-button/index.ts +2 -0
  23. package/src/components/ds-button/versions/ds-button-legacy/ds-button-legacy.module.scss +257 -0
  24. package/src/components/ds-button/versions/ds-button-legacy/ds-button-legacy.tsx +34 -0
  25. package/src/components/ds-button/versions/ds-button-legacy/ds-button-legacy.types.ts +41 -0
  26. package/src/components/ds-button/versions/ds-button-legacy/index.ts +2 -0
  27. package/src/components/ds-button/versions/ds-button-new/ds-button-new.module.scss +580 -0
  28. package/src/components/ds-button/versions/ds-button-new/ds-button-new.tsx +49 -0
  29. package/src/components/ds-button/versions/ds-button-new/ds-button-new.types.ts +41 -0
  30. package/src/components/ds-button/versions/ds-button-new/index.ts +2 -0
  31. package/src/components/ds-checkbox/ds-checkbox.module.scss +130 -0
  32. package/src/components/ds-checkbox/ds-checkbox.tsx +55 -0
  33. package/src/components/ds-checkbox/ds-checkbox.types.tsx +27 -0
  34. package/src/components/ds-checkbox/index.tsx +2 -0
  35. package/src/components/ds-chip/ds-chip.module.scss +71 -0
  36. package/src/components/ds-chip/ds-chip.tsx +73 -0
  37. package/src/components/ds-chip/ds-chip.types.tsx +46 -0
  38. package/src/components/ds-chip/index.tsx +2 -0
  39. package/src/components/ds-chip-group/ds-chip-group.module.scss +59 -0
  40. package/src/components/ds-chip-group/ds-chip-group.tsx +122 -0
  41. package/src/components/ds-chip-group/ds-chip-group.types.ts +52 -0
  42. package/src/components/ds-chip-group/hooks/use-chip-row-calculation.ts +76 -0
  43. package/src/components/ds-chip-group/index.ts +2 -0
  44. package/src/components/ds-confirmation/ds-confirmation.module.scss +58 -0
  45. package/src/components/ds-confirmation/ds-confirmation.tsx +121 -0
  46. package/src/components/ds-confirmation/ds-confirmation.types.tsx +28 -0
  47. package/src/components/ds-confirmation/index.ts +2 -0
  48. package/src/components/ds-dialog/ds-dialog.module.scss +50 -0
  49. package/src/components/ds-dialog/ds-dialog.tsx +70 -0
  50. package/src/components/ds-dialog/ds-dialog.types.tsx +48 -0
  51. package/src/components/ds-dialog/index.tsx +2 -0
  52. package/src/components/ds-drawer/ds-drawer.module.scss +212 -0
  53. package/src/components/ds-drawer/ds-drawer.tsx +160 -0
  54. package/src/components/ds-drawer/ds-drawer.types.tsx +54 -0
  55. package/src/components/ds-drawer/index.ts +2 -0
  56. package/src/components/ds-dropdown-menu/ds-dropdown-menu.module.scss +123 -0
  57. package/src/components/ds-dropdown-menu/ds-dropdown-menu.tsx +396 -0
  58. package/src/components/ds-dropdown-menu/ds-dropdown-menu.types.tsx +262 -0
  59. package/src/components/ds-dropdown-menu/index.tsx +2 -0
  60. package/src/components/ds-expandable-text-input/ds-expandable-text-input.module.scss +70 -0
  61. package/src/components/ds-expandable-text-input/ds-expandable-text-input.tsx +106 -0
  62. package/src/components/ds-expandable-text-input/ds-expandable-text-input.types.tsx +19 -0
  63. package/src/components/ds-expandable-text-input/index.ts +2 -0
  64. package/src/components/ds-file-upload/components/file-upload/file-upload.module.scss +90 -0
  65. package/src/components/ds-file-upload/components/file-upload/file-upload.tsx +106 -0
  66. package/src/components/ds-file-upload/components/file-upload/file-upload.types.tsx +94 -0
  67. package/src/components/ds-file-upload/components/file-upload/index.ts +2 -0
  68. package/src/components/ds-file-upload/components/file-upload-item/file-upload-item.module.scss +125 -0
  69. package/src/components/ds-file-upload/components/file-upload-item/file-upload-item.tsx +143 -0
  70. package/src/components/ds-file-upload/components/file-upload-item/file-upload-item.types.tsx +26 -0
  71. package/src/components/ds-file-upload/components/file-upload-item/index.ts +2 -0
  72. package/src/components/ds-file-upload/ds-file-upload-api.types.ts +38 -0
  73. package/src/components/ds-file-upload/ds-file-upload.tsx +46 -0
  74. package/src/components/ds-file-upload/ds-file-upload.types.tsx +54 -0
  75. package/src/components/ds-file-upload/errors/file-upload-errors.ts +27 -0
  76. package/src/components/ds-file-upload/hooks/index.ts +2 -0
  77. package/src/components/ds-file-upload/hooks/use-file-upload.ts +350 -0
  78. package/src/components/ds-file-upload/index.ts +7 -0
  79. package/src/components/ds-file-upload/types/accept-types.ts +106 -0
  80. package/src/components/ds-file-upload/utils/accept-utils.ts +25 -0
  81. package/src/components/ds-file-upload/utils/constants.ts +21 -0
  82. package/src/components/ds-file-upload/utils/file-factory.ts +18 -0
  83. package/src/components/ds-file-upload/utils/format-file-size.ts +15 -0
  84. package/src/components/ds-file-upload/utils/generate-helper-text.ts +39 -0
  85. package/src/components/ds-file-upload/utils/get-error-message.ts +17 -0
  86. package/src/components/ds-file-upload/utils/is-file-equal.ts +8 -0
  87. package/src/components/ds-form-control/ds-form-control.module.scss +135 -0
  88. package/src/components/ds-form-control/ds-form-control.tsx +94 -0
  89. package/src/components/ds-form-control/ds-form-control.types.tsx +58 -0
  90. package/src/components/ds-form-control/index.tsx +2 -0
  91. package/src/components/ds-grid/ds-grid.module.scss +0 -0
  92. package/src/components/ds-grid/ds-grid.tsx +43 -0
  93. package/src/components/ds-grid/ds-grid.types.ts +55 -0
  94. package/src/components/ds-grid/index.ts +2 -0
  95. package/src/components/ds-icon/ds-icon.module.scss +25 -0
  96. package/src/components/ds-icon/ds-icon.tsx +33 -0
  97. package/src/components/ds-icon/ds-icon.types.ts +53 -0
  98. package/src/components/ds-icon/index.ts +2 -0
  99. package/src/components/ds-icon/material-icons.ts +6203 -0
  100. package/src/components/ds-modal/ds-modal.module.scss +100 -0
  101. package/src/components/ds-modal/ds-modal.tsx +136 -0
  102. package/src/components/ds-modal/ds-modal.types.tsx +44 -0
  103. package/src/components/ds-modal/index.tsx +2 -0
  104. package/src/components/ds-number-input/ds-number-input.module.scss +15 -0
  105. package/src/components/ds-number-input/ds-number-input.tsx +74 -0
  106. package/src/components/ds-number-input/ds-number-input.types.tsx +76 -0
  107. package/src/components/ds-number-input/index.tsx +2 -0
  108. package/src/components/ds-panel/ds-panel.module.scss +95 -0
  109. package/src/components/ds-panel/ds-panel.tsx +46 -0
  110. package/src/components/ds-panel/ds-panel.types.ts +17 -0
  111. package/src/components/ds-panel/index.tsx +2 -0
  112. package/src/components/ds-password-input/ds-password-input.module.scss +10 -0
  113. package/src/components/ds-password-input/ds-password-input.tsx +67 -0
  114. package/src/components/ds-password-input/ds-password-input.types.tsx +62 -0
  115. package/src/components/ds-password-input/index.tsx +2 -0
  116. package/src/components/ds-popover/ds-popover.module.scss +23 -0
  117. package/src/components/ds-popover/ds-popover.tsx +33 -0
  118. package/src/components/ds-popover/ds-popover.types.tsx +24 -0
  119. package/src/components/ds-popover/index.tsx +2 -0
  120. package/src/components/ds-radio-group/ds-radio-group.module.scss +138 -0
  121. package/src/components/ds-radio-group/ds-radio-group.tsx +115 -0
  122. package/src/components/ds-radio-group/ds-radio-group.types.tsx +93 -0
  123. package/src/components/ds-radio-group/index.tsx +2 -0
  124. package/src/components/ds-select/ds-select.module.scss +66 -0
  125. package/src/components/ds-select/ds-select.tsx +239 -0
  126. package/src/components/ds-select/ds-select.types.tsx +80 -0
  127. package/src/components/ds-select/index.tsx +2 -0
  128. package/src/components/ds-select/select-items-chips.tsx +56 -0
  129. package/src/components/ds-smart-tabs/ds-smart-tabs.module.scss +69 -0
  130. package/src/components/ds-smart-tabs/ds-smart-tabs.tsx +113 -0
  131. package/src/components/ds-smart-tabs/ds-smart-tabs.types.tsx +74 -0
  132. package/src/components/ds-smart-tabs/index.tsx +2 -0
  133. package/src/components/ds-spinner/ds-spinner.module.scss +39 -0
  134. package/src/components/ds-spinner/ds-spinner.tsx +51 -0
  135. package/src/components/ds-spinner/ds-spinner.types.tsx +20 -0
  136. package/src/components/ds-spinner/index.tsx +2 -0
  137. package/src/components/ds-status-badge/ds-status-badge.module.scss +79 -0
  138. package/src/components/ds-status-badge/ds-status-badge.tsx +42 -0
  139. package/src/components/ds-status-badge/ds-status-badge.types.tsx +46 -0
  140. package/src/components/ds-status-badge/index.tsx +2 -0
  141. package/src/components/ds-stepper/components/ds-step-content.tsx +32 -0
  142. package/src/components/ds-stepper/components/ds-step-description.tsx +6 -0
  143. package/src/components/ds-stepper/components/ds-step-label.tsx +6 -0
  144. package/src/components/ds-stepper/components/ds-step-next-button.tsx +13 -0
  145. package/src/components/ds-stepper/components/ds-step.tsx +59 -0
  146. package/src/components/ds-stepper/ds-stepper.module.scss +134 -0
  147. package/src/components/ds-stepper/ds-stepper.tsx +34 -0
  148. package/src/components/ds-stepper/ds-stepper.types.ts +12 -0
  149. package/src/components/ds-stepper/hooks/use-stepper.ts +20 -0
  150. package/src/components/ds-stepper/hooks/use-steps.ts +18 -0
  151. package/src/components/ds-stepper/index.tsx +6 -0
  152. package/src/components/ds-system-status/ds-system-status.module.scss +87 -0
  153. package/src/components/ds-system-status/ds-system-status.tsx +33 -0
  154. package/src/components/ds-system-status/ds-system-status.types.tsx +37 -0
  155. package/src/components/ds-system-status/index.tsx +10 -0
  156. package/src/components/ds-table/components/core-table/core-table.module.scss +32 -0
  157. package/src/components/ds-table/components/core-table/core-table.tsx +83 -0
  158. package/src/components/ds-table/components/core-table/index.ts +1 -0
  159. package/src/components/ds-table/components/ds-table-bulk-actions/ds-table-bulk-actions.module.scss +115 -0
  160. package/src/components/ds-table/components/ds-table-bulk-actions/ds-table-bulk-actions.tsx +70 -0
  161. package/src/components/ds-table/components/ds-table-bulk-actions/ds-table-bulk-actions.types.ts +41 -0
  162. package/src/components/ds-table/components/ds-table-bulk-actions/index.ts +2 -0
  163. package/src/components/ds-table/components/ds-table-cell/ds-table-cell.module.scss +53 -0
  164. package/src/components/ds-table/components/ds-table-cell/ds-table-cell.tsx +95 -0
  165. package/src/components/ds-table/components/ds-table-cell/ds-table-cell.types.ts +67 -0
  166. package/src/components/ds-table/components/ds-table-cell/index.ts +2 -0
  167. package/src/components/ds-table/components/ds-table-header/ds-table-header.module.scss +81 -0
  168. package/src/components/ds-table/components/ds-table-header/ds-table-header.tsx +99 -0
  169. package/src/components/ds-table/components/ds-table-header/ds-table-header.types.ts +11 -0
  170. package/src/components/ds-table/components/ds-table-header/index.ts +2 -0
  171. package/src/components/ds-table/components/ds-table-row/ds-table-row.module.scss +137 -0
  172. package/src/components/ds-table/components/ds-table-row/ds-table-row.tsx +205 -0
  173. package/src/components/ds-table/components/ds-table-row/ds-table-row.types.ts +22 -0
  174. package/src/components/ds-table/components/ds-table-row/index.ts +2 -0
  175. package/src/components/ds-table/context/ds-table-context.tsx +39 -0
  176. package/src/components/ds-table/ds-table.module.scss +60 -0
  177. package/src/components/ds-table/ds-table.tsx +312 -0
  178. package/src/components/ds-table/ds-table.types.ts +377 -0
  179. package/src/components/ds-table/filters/adapters/create-checkbox-filter-adapter.tsx +116 -0
  180. package/src/components/ds-table/filters/adapters/create-dual-range-filter-adapter.tsx +171 -0
  181. package/src/components/ds-table/filters/adapters/create-filter-adapter.ts +131 -0
  182. package/src/components/ds-table/filters/adapters/index.ts +12 -0
  183. package/src/components/ds-table/filters/components/checkbox-filter/checkbox-filter.tsx +43 -0
  184. package/src/components/ds-table/filters/components/checkbox-filter/index.ts +2 -0
  185. package/src/components/ds-table/filters/components/range-filter/index.ts +2 -0
  186. package/src/components/ds-table/filters/components/range-filter/range-filter.module.scss +25 -0
  187. package/src/components/ds-table/filters/components/range-filter/range-filter.tsx +79 -0
  188. package/src/components/ds-table/filters/hooks/index.ts +1 -0
  189. package/src/components/ds-table/filters/hooks/use-table-filters.ts +234 -0
  190. package/src/components/ds-table/filters/index.ts +50 -0
  191. package/src/components/ds-table/filters/types/filter-adapter.types.ts +111 -0
  192. package/src/components/ds-table/hooks/use-drag-and-drop.tsx +107 -0
  193. package/src/components/ds-table/index.ts +4 -0
  194. package/src/components/ds-table/styles/_variables.scss +17 -0
  195. package/src/components/ds-table/styles/shared/ds-table-shared.module.scss +16 -0
  196. package/src/components/ds-table/utils/column-styling.ts +30 -0
  197. package/src/components/ds-table/utils/create-columns-grid-template.ts +23 -0
  198. package/src/components/ds-table/utils/merge-refs.ts +15 -0
  199. package/src/components/ds-table/utils/story-data-generator.ts +91 -0
  200. package/src/components/ds-text-input/ds-text-input.module.scss +27 -0
  201. package/src/components/ds-text-input/ds-text-input.tsx +75 -0
  202. package/src/components/ds-text-input/ds-text-input.types.tsx +110 -0
  203. package/src/components/ds-text-input/index.ts +2 -0
  204. package/src/components/ds-textarea/ds-textarea.module.scss +5 -0
  205. package/src/components/ds-textarea/ds-textarea.tsx +49 -0
  206. package/src/components/ds-textarea/ds-textarea.types.tsx +67 -0
  207. package/src/components/ds-textarea/index.tsx +2 -0
  208. package/src/components/ds-toast/ds-toast-context.tsx +79 -0
  209. package/src/components/ds-toast/ds-toast.module.scss +134 -0
  210. package/src/components/ds-toast/ds-toast.tsx +54 -0
  211. package/src/components/ds-toast/ds-toast.types.tsx +53 -0
  212. package/src/components/ds-toast/index.ts +3 -0
  213. package/src/components/ds-tooltip/ds-tooltip.module.scss +19 -0
  214. package/src/components/ds-tooltip/ds-tooltip.tsx +33 -0
  215. package/src/components/ds-tooltip/ds-tooltip.types.tsx +12 -0
  216. package/src/components/ds-tooltip/index.tsx +2 -0
  217. package/src/components/ds-typography/ds-typography.config.ts +32 -0
  218. package/src/components/ds-typography/ds-typography.module.scss +93 -0
  219. package/src/components/ds-typography/ds-typography.tsx +27 -0
  220. package/src/components/ds-typography/ds-typography.types.tsx +27 -0
  221. package/src/components/ds-typography/index.tsx +2 -0
  222. package/src/components/ds-vertical-tabs/ds-vertical-tabs.module.scss +60 -0
  223. package/src/components/ds-vertical-tabs/ds-vertical-tabs.tsx +80 -0
  224. package/src/components/ds-vertical-tabs/ds-vertical-tabs.types.ts +69 -0
  225. package/src/components/ds-vertical-tabs/index.ts +7 -0
  226. package/src/index.ts +34 -0
  227. package/src/styles/_colors.scss +65 -0
  228. package/src/styles/_grid.scss +111 -0
  229. package/src/styles/_mixin.scss +1 -0
  230. package/src/styles/_reset.scss +38 -0
  231. package/src/styles/_root.scss +390 -0
  232. package/src/styles/_scrollbars.scss +41 -0
  233. package/src/styles/_spacing.scss +47 -0
  234. package/src/styles/_typography.scss +301 -0
  235. package/src/styles/_utilities.scss +33 -0
  236. package/src/styles/_variables.scss +44 -0
  237. package/src/styles/mixins/_bounding-box.scss +53 -0
  238. package/src/styles/mixins/_list.scss +74 -0
  239. package/src/styles/shared/_dropdown.scss +89 -0
  240. package/src/styles/shared/_input.scss +365 -0
  241. package/src/styles/shared/_modal.scss +123 -0
  242. package/src/styles/styles.scss +12 -0
@@ -0,0 +1,239 @@
1
+ import { useState } from 'react';
2
+ import { type UseSelectReturn, Select, createListCollection, useSelect } from '@ark-ui/react/select';
3
+ import { Portal } from '@ark-ui/react/portal';
4
+ import classNames from 'classnames';
5
+ import styles from './ds-select.module.scss';
6
+ import type { DsSelectOption, DsSelectProps } from './ds-select.types';
7
+ import { DsIcon } from '../ds-icon';
8
+ import { type DsCheckboxProps, DsCheckbox } from '../ds-checkbox';
9
+ import { SelectItemsChips } from './select-items-chips';
10
+ import { DsTypography } from '../ds-typography';
11
+ import { DsTextInput } from '../ds-text-input';
12
+
13
+ const SEARCH_THRESHOLD = 13;
14
+ const SELECT_ALL_VALUE = '__INTERNAL_SELECT_ALL_VALUE__';
15
+
16
+ const SELECT_ALL: DsSelectOption = {
17
+ label: 'All',
18
+ value: SELECT_ALL_VALUE,
19
+ };
20
+
21
+ const DsSelect = ({
22
+ id,
23
+ options: userOptions,
24
+ value,
25
+ style,
26
+ size,
27
+ clearable,
28
+ onClear,
29
+ onBlur,
30
+ className,
31
+ placeholder = 'Click to select a value',
32
+ disabled,
33
+ ...multiselectProps
34
+ }: DsSelectProps) => {
35
+ const [searchTerm, setSearchTerm] = useState('');
36
+ const [showAllItems, setShowAllItems] = useState(false);
37
+
38
+ const internalOptions = multiselectProps.multiple ? [SELECT_ALL, ...userOptions] : userOptions;
39
+
40
+ const collection = createListCollection({
41
+ items: internalOptions,
42
+ itemToValue: (item) => item.value,
43
+ });
44
+
45
+ const filteredOptions = internalOptions.filter((option) =>
46
+ option.label.toLowerCase().includes(searchTerm.toLowerCase()),
47
+ );
48
+
49
+ const normalizedValue = Array.isArray(value) ? value : [value].filter((value) => !!value);
50
+
51
+ const select = useSelect({
52
+ collection,
53
+ disabled,
54
+ multiple: multiselectProps.multiple,
55
+ value: normalizedValue,
56
+
57
+ // Override Ark's auto-generated trigger id if an id provided by the user.
58
+ // See: https://github.com/chakra-ui/zag/blob/f6e079095/packages/machines/select/src/select.dom.ts#L5
59
+ ids: { trigger: id },
60
+
61
+ onOpenChange: () => {
62
+ setSearchTerm('');
63
+ setShowAllItems(false);
64
+ },
65
+
66
+ onValueChange: (details) => {
67
+ // Single select mode.
68
+ if (!multiselectProps.multiple) {
69
+ multiselectProps.onValueChange?.(details.value[0]);
70
+ return;
71
+ }
72
+
73
+ // "Select All" clicked in multi select mode.
74
+ const isSelectAllClicked = details.value.includes(SELECT_ALL_VALUE);
75
+
76
+ if (isSelectAllClicked) {
77
+ const areAllOptionsSelected = select.selectedItems.length === userOptions.length;
78
+
79
+ const newValues = areAllOptionsSelected ? [] : userOptions.map((opt) => opt.value);
80
+
81
+ multiselectProps.onValueChange?.(newValues);
82
+
83
+ return;
84
+ }
85
+
86
+ // "Regular" multi select mode.
87
+ const newValueWithoutSelectAll = details.value.filter((val) => val !== SELECT_ALL_VALUE);
88
+
89
+ multiselectProps.onValueChange?.(newValueWithoutSelectAll);
90
+ },
91
+ });
92
+
93
+ return (
94
+ <Select.RootProvider
95
+ value={select}
96
+ // When rendering the Select inside a Modal, it has an `aria-hidden="true"` attribute
97
+ // which causes screen readers to ignore it.
98
+ //
99
+ // See: https://github.com/chakra-ui/ark/issues/3728
100
+ lazyMount
101
+ >
102
+ <Select.Control
103
+ className={classNames(styles.control, size === 'small' && styles.small, className)}
104
+ style={style}
105
+ onBlur={onBlur}
106
+ onKeyDown={(e) => {
107
+ if (!clearable) {
108
+ return;
109
+ }
110
+
111
+ if (select.open) {
112
+ return;
113
+ }
114
+
115
+ if (e.key === 'Backspace' || e.key === 'Delete') {
116
+ if (multiselectProps.multiple) {
117
+ multiselectProps.onValueChange?.([]);
118
+ } else {
119
+ multiselectProps.onValueChange?.('');
120
+ }
121
+
122
+ onClear?.();
123
+ }
124
+ }}
125
+ >
126
+ <Select.Trigger
127
+ id={id}
128
+ className={styles.trigger}
129
+ // Ark expects a `Select.Label` component to exist and automatically label the trigger
130
+ // with it. We're using the `DsFormControl` label handling instead, so we disable the
131
+ // automatic labelling here.
132
+ aria-labelledby={null as never}
133
+ >
134
+ <DsTypography className={styles.valueText} variant="body-sm-reg" asChild>
135
+ <Select.ValueText placeholder={placeholder} />
136
+ </DsTypography>
137
+
138
+ <Select.Indicator className={styles.triggerIcon}>
139
+ <DsIcon icon="keyboard_arrow_down" size={size === 'small' ? 'small' : 'medium'} />
140
+ </Select.Indicator>
141
+ </Select.Trigger>
142
+
143
+ {clearable && (
144
+ <Select.ClearTrigger className={styles.clearIcon} onClick={onClear}>
145
+ <DsIcon icon="close" size={size === 'small' ? 'small' : 'medium'} />
146
+ </Select.ClearTrigger>
147
+ )}
148
+ </Select.Control>
149
+ <Portal>
150
+ <Select.Positioner className={styles.viewport}>
151
+ <Select.Content className={styles.content}>
152
+ {userOptions.length > SEARCH_THRESHOLD && (
153
+ <div className={styles.searchInput}>
154
+ <DsTextInput
155
+ placeholder="Search"
156
+ value={searchTerm}
157
+ onChange={(e) => setSearchTerm(e.target.value)}
158
+ onKeyDown={(e) => {
159
+ // Prevent the select from opening/closing when pressing space in the search input.
160
+ if (e.key === ' ') {
161
+ e.stopPropagation();
162
+ }
163
+ }}
164
+ slots={{
165
+ startAdornment: <DsIcon icon="search" size="tiny" />,
166
+ }}
167
+ />
168
+ </div>
169
+ )}
170
+
171
+ {multiselectProps.multiple && (
172
+ <SelectItemsChips
173
+ onValueChange={multiselectProps.onValueChange}
174
+ showAll={showAllItems}
175
+ onShowAll={() => setShowAllItems(true)}
176
+ // TODO: Find a way to calculate this based on the size of the select.
177
+ count={6}
178
+ />
179
+ )}
180
+
181
+ {filteredOptions.map((item) => {
182
+ const checked = getItemCheckedState({ item, select, options: internalOptions });
183
+
184
+ return (
185
+ <DsTypography
186
+ variant="body-sm-reg"
187
+ asChild
188
+ key={collection.getItemValue(item)}
189
+ className={styles.item}
190
+ >
191
+ <Select.Item item={item}>
192
+ {multiselectProps.multiple && <DsCheckbox checked={checked} />}
193
+ {item.icon && <DsIcon className={styles.itemIcon} icon={item.icon} aria-hidden="true" />}
194
+ <Select.ItemText>{item.label}</Select.ItemText>
195
+ </Select.Item>
196
+ </DsTypography>
197
+ );
198
+ })}
199
+ </Select.Content>
200
+ </Select.Positioner>
201
+ </Portal>
202
+
203
+ {/*
204
+ This element isn't properly hidden by Zag, so we hide it manually.
205
+ For more information, see: https://github.com/chakra-ui/zag/issues/2865
206
+ */}
207
+ <Select.HiddenSelect style={{ display: 'none' }} />
208
+ </Select.RootProvider>
209
+ );
210
+ };
211
+
212
+ function getItemCheckedState({
213
+ item,
214
+ select,
215
+ options,
216
+ }: {
217
+ item: DsSelectOption;
218
+ select: UseSelectReturn<DsSelectOption>;
219
+ options: DsSelectOption[];
220
+ }): DsCheckboxProps['checked'] {
221
+ const isRegularItem = item.value !== SELECT_ALL_VALUE;
222
+
223
+ if (isRegularItem) {
224
+ return select.getItemState({ item }).selected;
225
+ }
226
+
227
+ // Doing -1 since it contains the "Select All" option itself.
228
+ const allSelected = select.selectedItems.length === options.length - 1;
229
+
230
+ if (allSelected) {
231
+ return true;
232
+ }
233
+
234
+ const someSelected = select.hasSelectedItems;
235
+
236
+ return someSelected ? 'indeterminate' : false;
237
+ }
238
+
239
+ export default DsSelect;
@@ -0,0 +1,80 @@
1
+ import type React from 'react';
2
+ import type { IconName } from '../ds-icon';
3
+
4
+ export type SelectSize = 'default' | 'small';
5
+
6
+ export interface DsSelectOption {
7
+ /**
8
+ * Label to display in the select
9
+ */
10
+ label: string;
11
+ /**
12
+ * Value to return when the option is selected
13
+ */
14
+ value: SelectOptionValue;
15
+ /**
16
+ * Optional icon to display next to the label
17
+ */
18
+ icon?: IconName;
19
+ }
20
+
21
+ export type SelectOptionValue = string;
22
+
23
+ export type DsSelectProps = {
24
+ /**
25
+ * Unique identifier for the select component
26
+ */
27
+ id?: string;
28
+ /**
29
+ * Options to display in the select dropdown
30
+ */
31
+ options: DsSelectOption[];
32
+ /**
33
+ * Additional styles to apply to the icon
34
+ */
35
+ style?: React.CSSProperties;
36
+ /**
37
+ * Additional CSS class names
38
+ */
39
+ className?: string;
40
+ /**
41
+ * Event handler called when the select loses focus
42
+ *
43
+ * @param event
44
+ */
45
+ onBlur?: (event: React.FocusEvent) => void;
46
+ /**
47
+ * Placeholder text to display when no option is selected
48
+ */
49
+ placeholder?: string;
50
+ /**
51
+ * Whether the select is disabled
52
+ */
53
+ disabled?: boolean;
54
+ /**
55
+ * The size of the select component
56
+ * @default 'default'
57
+ */
58
+ size?: SelectSize;
59
+ } & (
60
+ | {
61
+ clearable?: undefined | false;
62
+ onClear?: never;
63
+ }
64
+ | {
65
+ clearable: true;
66
+ onClear?: () => void;
67
+ }
68
+ ) &
69
+ (
70
+ | {
71
+ multiple?: undefined | false;
72
+ value: SelectOptionValue;
73
+ onValueChange?: (value: SelectOptionValue) => void;
74
+ }
75
+ | {
76
+ multiple: true;
77
+ value: SelectOptionValue[];
78
+ onValueChange?: (value: SelectOptionValue[]) => void;
79
+ }
80
+ );
@@ -0,0 +1,2 @@
1
+ export { default as DsSelect } from './ds-select';
2
+ export type { DsSelectProps, SelectSize, DsSelectOption } from './ds-select.types';
@@ -0,0 +1,56 @@
1
+ import { useSelectContext, type UseSelectContext } from '@ark-ui/react/select';
2
+ import styles from './ds-select.module.scss';
3
+ import DsButton from '../ds-button/ds-button';
4
+ import DsChip from '../ds-chip/ds-chip';
5
+ import type { DsSelectOption, SelectOptionValue } from './ds-select.types';
6
+
7
+ type SelectItemsChipsProps = {
8
+ showAll: boolean;
9
+ onValueChange?: (value: SelectOptionValue[]) => void;
10
+ onShowAll: () => void;
11
+ count: number;
12
+ };
13
+
14
+ export function SelectItemsChips({ showAll, onShowAll, onValueChange, count }: SelectItemsChipsProps) {
15
+ const { collection, value: selectedItems } = useSelectContext() as UseSelectContext<DsSelectOption>;
16
+
17
+ if (!selectedItems.length) {
18
+ return null;
19
+ }
20
+
21
+ const itemsToShow = showAll ? selectedItems : selectedItems.slice(0, count);
22
+
23
+ return (
24
+ <div className={styles.multiSelectItemsContainer}>
25
+ {itemsToShow.map((itemValue) => {
26
+ const item = collection.find(itemValue);
27
+
28
+ if (!item) {
29
+ return null;
30
+ }
31
+
32
+ const onDelete = () => {
33
+ const filteredValue = selectedItems.filter((v) => v !== item.value);
34
+
35
+ onValueChange?.(filteredValue);
36
+ };
37
+
38
+ return <DsChip key={itemValue} label={item.label} onDelete={onDelete} />;
39
+ })}
40
+
41
+ {!showAll && selectedItems.length > count && (
42
+ <DsChip label={`+${String(selectedItems.length - count)}`} onClick={onShowAll} size="small" />
43
+ )}
44
+
45
+ <DsButton
46
+ design="v1.2"
47
+ buttonType="tertiary"
48
+ variant="ghost"
49
+ size="small"
50
+ onClick={() => onValueChange?.([])}
51
+ >
52
+ Clear All
53
+ </DsButton>
54
+ </div>
55
+ );
56
+ }
@@ -0,0 +1,69 @@
1
+ @use '../../styles/typography';
2
+
3
+ .container {
4
+ display: flex;
5
+ gap: 8px;
6
+
7
+ .tab {
8
+ @include typography.body-sm-reg;
9
+ @include typography.ellipsis;
10
+ display: flex;
11
+ height: 48px;
12
+ padding: var(--spacing-xs) var(--spacing-sm);
13
+ align-items: center;
14
+ gap: 8px;
15
+ border-radius: 4px;
16
+ border: 1px solid var(--color-border);
17
+ background: var(--color-background);
18
+ min-width: 250px;
19
+
20
+ &.active {
21
+ @include typography.body-sm-md;
22
+ border-color: var(--color-border-action-secondary);
23
+
24
+ .icon {
25
+ color: var(--color-background);
26
+ background: var(--tab-color, var(--color-background-action));
27
+ }
28
+
29
+ .value {
30
+ color: var(--color-font-main);
31
+ }
32
+ }
33
+
34
+ &:hover:not(.active):not(:disabled) {
35
+ border-color: var(--color-border-action-primary-hover);
36
+ }
37
+
38
+ &:focus {
39
+ outline: auto var(--color-border-action-primary-hover);
40
+ }
41
+
42
+ &.disabled {
43
+ opacity: 0.5;
44
+ cursor: not-allowed;
45
+ }
46
+
47
+ .icon {
48
+ display: flex;
49
+ justify-content: center;
50
+ align-items: center;
51
+ color: var(--color-background-action);
52
+ background: var(--color-background-tertiary);
53
+ border-radius: 50%;
54
+ width: var(--icon-width-extra-large);
55
+ height: var(--icon-width-extra-large);
56
+ }
57
+
58
+ .label {
59
+ flex: 1;
60
+ white-space: nowrap;
61
+ text-align: start;
62
+ }
63
+
64
+ .content {
65
+ padding-right: 4px;
66
+ color: var(--color-font-secondary);
67
+ }
68
+ }
69
+ }
@@ -0,0 +1,113 @@
1
+ import { type CSSProperties, createContext, useContext } from 'react';
2
+ import type React from 'react';
3
+ import classNames from 'classnames';
4
+ import { DsIcon } from '../ds-icon';
5
+ import styles from './ds-smart-tabs.module.scss';
6
+ import type { Color, DsSmartTabProps, DsSmartTabsProps } from './ds-smart-tabs.types';
7
+
8
+ const colorMap: Record<Color, string> = {
9
+ 'dark-blue': 'var(--color-background-info-strong)',
10
+ 'light-blue': 'var(--color-background-active-moderate)',
11
+ 'dark-gray': 'var(--color-background-brand)',
12
+ green: 'var(--color-background-success-strong)',
13
+ fuchsia: 'var(--color-data-fuchsia)',
14
+ blue: 'var(--color-icon-information-secondary)',
15
+ gray: 'var(--color-icon-information-main)',
16
+ red: 'var(--color-icon-warning)',
17
+ amber: 'var(--color-data-amber)',
18
+ };
19
+
20
+ interface SmartTabsContextType {
21
+ activeTab: string;
22
+ onTabClick: (value: string) => void;
23
+ }
24
+
25
+ const SmartTabsContext = createContext<SmartTabsContextType | null>(null);
26
+
27
+ const useSmartTabsContext = () => {
28
+ const context = useContext(SmartTabsContext);
29
+ if (!context) {
30
+ throw new Error('DsTab must be used within DsSmartTabs');
31
+ }
32
+ return context;
33
+ };
34
+
35
+ /**
36
+ * Individual tab component
37
+ */
38
+ const DsSmartTab: React.FC<DsSmartTabProps> = ({
39
+ label,
40
+ value,
41
+ icon,
42
+ content,
43
+ color,
44
+ disabled,
45
+ className,
46
+ style = {},
47
+ ...props
48
+ }) => {
49
+ const { activeTab, onTabClick } = useSmartTabsContext();
50
+
51
+ const tabClass = classNames(
52
+ styles.tab,
53
+ {
54
+ [styles.active]: activeTab === value,
55
+ [styles.disabled]: disabled,
56
+ },
57
+ className,
58
+ );
59
+ const tabStyle = {
60
+ ...style,
61
+ ...(color && ({ '--tab-color': colorMap[color] } as CSSProperties)),
62
+ };
63
+
64
+ const handleClick = () => {
65
+ if (!disabled) {
66
+ onTabClick(value);
67
+ }
68
+ };
69
+
70
+ return (
71
+ <button
72
+ type="button"
73
+ className={tabClass}
74
+ style={tabStyle}
75
+ onClick={handleClick}
76
+ disabled={disabled}
77
+ {...props}
78
+ >
79
+ <DsIcon className={styles.icon} icon={icon} size="small" />
80
+ <span className={styles.label}>{label}</span>
81
+ <span className={styles.content}>{content}</span>
82
+ </button>
83
+ );
84
+ };
85
+
86
+ /**
87
+ * Design system SmartTabs component
88
+ */
89
+ const DsSmartTabs = ({
90
+ activeTab,
91
+ onTabClick,
92
+ className,
93
+ style = {},
94
+ children,
95
+ ...props
96
+ }: DsSmartTabsProps) => {
97
+ const contextValue: SmartTabsContextType = {
98
+ activeTab,
99
+ onTabClick,
100
+ };
101
+
102
+ return (
103
+ <SmartTabsContext.Provider value={contextValue}>
104
+ <div className={classNames(styles.container, className)} style={style} {...props}>
105
+ {children}
106
+ </div>
107
+ </SmartTabsContext.Provider>
108
+ );
109
+ };
110
+
111
+ DsSmartTabs.Tab = DsSmartTab;
112
+
113
+ export default DsSmartTabs;
@@ -0,0 +1,74 @@
1
+ import type { ReactNode } from 'react';
2
+ import type React from 'react';
3
+ import type { IconType } from '../ds-icon/ds-icon.types';
4
+
5
+ export const colors = [
6
+ 'dark-blue',
7
+ 'light-blue',
8
+ 'dark-gray',
9
+ 'green',
10
+ 'fuchsia',
11
+ 'blue',
12
+ 'gray',
13
+ 'red',
14
+ 'amber',
15
+ ] as const;
16
+ export type Color = (typeof colors)[number];
17
+
18
+ export interface DsSmartTabProps {
19
+ /**
20
+ * The label of the tab
21
+ */
22
+ label: string;
23
+ /**
24
+ * The value of the tab (used for filtering/identification)
25
+ */
26
+ value: string;
27
+ /**
28
+ * The icon to display in the tab
29
+ */
30
+ icon: IconType;
31
+ /**
32
+ * The content to display in the tab (typically a count or text)
33
+ */
34
+ content: number | string;
35
+ /**
36
+ * Color for the tab (limited to predefined color values)
37
+ */
38
+ color?: Color;
39
+ /**
40
+ * Whether the tab is disabled
41
+ */
42
+ disabled?: boolean;
43
+ /**
44
+ * Additional CSS class names
45
+ */
46
+ className?: string;
47
+ /**
48
+ * Additional styles to apply to the tab
49
+ */
50
+ style?: React.CSSProperties;
51
+ }
52
+
53
+ export interface DsSmartTabsProps {
54
+ /**
55
+ * Currently active tab value
56
+ */
57
+ activeTab: string;
58
+ /**
59
+ * Callback function when a tab is clicked
60
+ */
61
+ onTabClick: (value: string) => void;
62
+ /**
63
+ * Additional CSS class names
64
+ */
65
+ className?: string;
66
+ /**
67
+ * Additional styles to apply to the component
68
+ */
69
+ style?: React.CSSProperties;
70
+ /**
71
+ * The tab components as children
72
+ */
73
+ children: ReactNode;
74
+ }
@@ -0,0 +1,2 @@
1
+ export { default as DsSmartTabs } from './ds-smart-tabs';
2
+ export * from './ds-smart-tabs.types';
@@ -0,0 +1,39 @@
1
+ @use '../../styles/typography';
2
+
3
+ .spinnerContainer {
4
+ display: flex;
5
+ align-items: center;
6
+ justify-content: center;
7
+ }
8
+
9
+ .progressCircle {
10
+ button &,
11
+ [role='button'] & {
12
+ width: 1em;
13
+ height: 1em;
14
+ }
15
+ }
16
+
17
+ .spin {
18
+ animation: spin 2s linear infinite;
19
+ }
20
+
21
+ .progressArc {
22
+ transition: stroke-dasharray 0.3s ease-in-out;
23
+ stroke: var(--color-background-selected);
24
+
25
+ button &,
26
+ [role='button'] & {
27
+ stroke: currentColor;
28
+ }
29
+ }
30
+
31
+ @keyframes spin {
32
+ 0% {
33
+ transform: rotate(0deg);
34
+ }
35
+
36
+ 100% {
37
+ transform: rotate(360deg);
38
+ }
39
+ }