@arbor-education/design-system.components 0.0.4 → 0.0.5

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 (245) hide show
  1. package/README.md +1 -1
  2. package/dist/components/button/Button.d.ts +5 -2
  3. package/dist/components/button/Button.d.ts.map +1 -1
  4. package/dist/components/button/Button.js +3 -1
  5. package/dist/components/button/Button.js.map +1 -1
  6. package/dist/components/card/Card.d.ts +1 -2
  7. package/dist/components/card/Card.d.ts.map +1 -1
  8. package/dist/components/card/Card.js +3 -3
  9. package/dist/components/card/Card.js.map +1 -1
  10. package/dist/components/card/Card.test.js +0 -5
  11. package/dist/components/card/Card.test.js.map +1 -1
  12. package/dist/components/formField/FormField.d.ts +4 -0
  13. package/dist/components/formField/FormField.d.ts.map +1 -1
  14. package/dist/components/formField/FormField.js +2 -1
  15. package/dist/components/formField/FormField.js.map +1 -1
  16. package/dist/components/formField/FormField.stories.d.ts.map +1 -1
  17. package/dist/components/formField/FormField.stories.js +3 -1
  18. package/dist/components/formField/FormField.stories.js.map +1 -1
  19. package/dist/components/formField/FormField.test.js +5 -0
  20. package/dist/components/formField/FormField.test.js.map +1 -1
  21. package/dist/components/formField/inputs/checkbox/CheckboxInput.d.ts +7 -0
  22. package/dist/components/formField/inputs/checkbox/CheckboxInput.d.ts.map +1 -0
  23. package/dist/components/formField/inputs/checkbox/CheckboxInput.js +31 -0
  24. package/dist/components/formField/inputs/checkbox/CheckboxInput.js.map +1 -0
  25. package/dist/components/formField/inputs/checkbox/CheckboxInput.stories.d.ts +17 -0
  26. package/dist/components/formField/inputs/checkbox/CheckboxInput.stories.d.ts.map +1 -0
  27. package/dist/components/formField/inputs/checkbox/CheckboxInput.stories.js +19 -0
  28. package/dist/components/formField/inputs/checkbox/CheckboxInput.stories.js.map +1 -0
  29. package/dist/components/formField/inputs/checkbox/CheckboxInput.test.d.ts +2 -0
  30. package/dist/components/formField/inputs/checkbox/CheckboxInput.test.d.ts.map +1 -0
  31. package/dist/components/formField/inputs/checkbox/CheckboxInput.test.js +30 -0
  32. package/dist/components/formField/inputs/checkbox/CheckboxInput.test.js.map +1 -0
  33. package/dist/components/formField/inputs/dropdown/Dropdown.d.ts +11 -0
  34. package/dist/components/formField/inputs/dropdown/Dropdown.d.ts.map +1 -0
  35. package/dist/components/formField/inputs/dropdown/Dropdown.js +43 -0
  36. package/dist/components/formField/inputs/dropdown/Dropdown.js.map +1 -0
  37. package/dist/components/formField/inputs/dropdown/Dropdown.stories.d.ts +161 -0
  38. package/dist/components/formField/inputs/dropdown/Dropdown.stories.d.ts.map +1 -0
  39. package/dist/components/formField/inputs/dropdown/Dropdown.stories.js +172 -0
  40. package/dist/components/formField/inputs/dropdown/Dropdown.stories.js.map +1 -0
  41. package/dist/components/formField/inputs/dropdown/Dropdown.test.d.ts +2 -0
  42. package/dist/components/formField/inputs/dropdown/Dropdown.test.d.ts.map +1 -0
  43. package/dist/components/formField/inputs/dropdown/Dropdown.test.js +93 -0
  44. package/dist/components/formField/inputs/dropdown/Dropdown.test.js.map +1 -0
  45. package/dist/components/formField/inputs/dropdown/buttons/dropdownButton/DropdownButton.d.ts +11 -0
  46. package/dist/components/formField/inputs/dropdown/buttons/dropdownButton/DropdownButton.d.ts.map +1 -0
  47. package/dist/components/formField/inputs/dropdown/buttons/dropdownButton/DropdownButton.js +15 -0
  48. package/dist/components/formField/inputs/dropdown/buttons/dropdownButton/DropdownButton.js.map +1 -0
  49. package/dist/components/formField/inputs/dropdown/items/DropdownItemRenderer.d.ts +10 -0
  50. package/dist/components/formField/inputs/dropdown/items/DropdownItemRenderer.d.ts.map +1 -0
  51. package/dist/components/formField/inputs/dropdown/items/DropdownItemRenderer.js +12 -0
  52. package/dist/components/formField/inputs/dropdown/items/DropdownItemRenderer.js.map +1 -0
  53. package/dist/components/formField/inputs/dropdown/items/dropdownItem/DropdownItem.d.ts +9 -0
  54. package/dist/components/formField/inputs/dropdown/items/dropdownItem/DropdownItem.d.ts.map +1 -0
  55. package/dist/components/formField/inputs/dropdown/items/dropdownItem/DropdownItem.js +17 -0
  56. package/dist/components/formField/inputs/dropdown/items/dropdownItem/DropdownItem.js.map +1 -0
  57. package/dist/components/formField/inputs/dropdown/items/dropdownMultiLineItem/DropdownMultiLineItem.d.ts +7 -0
  58. package/dist/components/formField/inputs/dropdown/items/dropdownMultiLineItem/DropdownMultiLineItem.d.ts.map +1 -0
  59. package/dist/components/formField/inputs/dropdown/items/dropdownMultiLineItem/DropdownMultiLineItem.js +16 -0
  60. package/dist/components/formField/inputs/dropdown/items/dropdownMultiLineItem/DropdownMultiLineItem.js.map +1 -0
  61. package/dist/components/formField/inputs/dropdown/wrapper/DropdownWrapper.d.ts +16 -0
  62. package/dist/components/formField/inputs/dropdown/wrapper/DropdownWrapper.d.ts.map +1 -0
  63. package/dist/components/formField/inputs/dropdown/wrapper/DropdownWrapper.js +73 -0
  64. package/dist/components/formField/inputs/dropdown/wrapper/DropdownWrapper.js.map +1 -0
  65. package/dist/components/formField/inputs/number/NumberInput.d.ts +6 -0
  66. package/dist/components/formField/inputs/number/NumberInput.d.ts.map +1 -0
  67. package/dist/components/formField/inputs/number/NumberInput.js +39 -0
  68. package/dist/components/formField/inputs/number/NumberInput.js.map +1 -0
  69. package/dist/components/formField/inputs/number/NumberInput.stories.d.ts +20 -0
  70. package/dist/components/formField/inputs/number/NumberInput.stories.d.ts.map +1 -0
  71. package/dist/components/formField/inputs/number/NumberInput.stories.js +22 -0
  72. package/dist/components/formField/inputs/number/NumberInput.stories.js.map +1 -0
  73. package/dist/components/formField/inputs/number/NumberInput.test.d.ts +2 -0
  74. package/dist/components/formField/inputs/number/NumberInput.test.d.ts.map +1 -0
  75. package/dist/components/formField/inputs/number/NumberInput.test.js +30 -0
  76. package/dist/components/formField/inputs/number/NumberInput.test.js.map +1 -0
  77. package/dist/components/formField/inputs/radio/RadioButtonInput.d.ts +7 -0
  78. package/dist/components/formField/inputs/radio/RadioButtonInput.d.ts.map +1 -0
  79. package/dist/components/formField/inputs/radio/RadioButtonInput.js +9 -0
  80. package/dist/components/formField/inputs/radio/RadioButtonInput.js.map +1 -0
  81. package/dist/components/formField/inputs/radio/RadioButtonInput.stories.d.ts +46 -0
  82. package/dist/components/formField/inputs/radio/RadioButtonInput.stories.d.ts.map +1 -0
  83. package/dist/components/formField/inputs/radio/RadioButtonInput.stories.js +83 -0
  84. package/dist/components/formField/inputs/radio/RadioButtonInput.stories.js.map +1 -0
  85. package/dist/components/formField/inputs/radio/RadioButtonInput.test.d.ts +2 -0
  86. package/dist/components/formField/inputs/radio/RadioButtonInput.test.d.ts.map +1 -0
  87. package/dist/components/formField/inputs/radio/RadioButtonInput.test.js +34 -0
  88. package/dist/components/formField/inputs/radio/RadioButtonInput.test.js.map +1 -0
  89. package/dist/components/heading/Heading.d.ts +392 -388
  90. package/dist/components/heading/Heading.d.ts.map +1 -1
  91. package/dist/components/heading/Heading.js +8 -1
  92. package/dist/components/heading/Heading.js.map +1 -1
  93. package/dist/components/heading/Heading.stories.d.ts.map +1 -1
  94. package/dist/components/heading/Heading.stories.js +7 -8
  95. package/dist/components/heading/Heading.stories.js.map +1 -1
  96. package/dist/components/heading/HeadingInnerContainer.d.ts +2 -2
  97. package/dist/components/heading/HeadingInnerContainer.js +4 -4
  98. package/dist/components/icon/Icon.d.ts +2 -2
  99. package/dist/components/icon/Icon.d.ts.map +1 -1
  100. package/dist/components/icon/Icon.js.map +1 -1
  101. package/dist/components/icon/allowedIcons.d.ts +1 -0
  102. package/dist/components/icon/allowedIcons.d.ts.map +1 -1
  103. package/dist/components/section/Section.d.ts +18 -0
  104. package/dist/components/section/Section.d.ts.map +1 -0
  105. package/dist/components/section/Section.js +36 -0
  106. package/dist/components/section/Section.js.map +1 -0
  107. package/dist/components/section/Section.stories.d.ts +18 -0
  108. package/dist/components/section/Section.stories.d.ts.map +1 -0
  109. package/dist/components/section/Section.stories.js +27 -0
  110. package/dist/components/section/Section.stories.js.map +1 -0
  111. package/dist/components/section/Section.test.d.ts +2 -0
  112. package/dist/components/section/Section.test.d.ts.map +1 -0
  113. package/dist/components/section/Section.test.js +157 -0
  114. package/dist/components/section/Section.test.js.map +1 -0
  115. package/dist/components/slideover/Slideover.d.ts +11 -0
  116. package/dist/components/slideover/Slideover.d.ts.map +1 -0
  117. package/dist/components/slideover/Slideover.js +11 -0
  118. package/dist/components/slideover/Slideover.js.map +1 -0
  119. package/dist/components/slideover/Slideover.test.d.ts +2 -0
  120. package/dist/components/slideover/Slideover.test.d.ts.map +1 -0
  121. package/dist/components/slideover/Slideover.test.js +33 -0
  122. package/dist/components/slideover/Slideover.test.js.map +1 -0
  123. package/dist/components/slideoverManager/SlideoverManager.d.ts +7 -0
  124. package/dist/components/slideoverManager/SlideoverManager.d.ts.map +1 -0
  125. package/dist/components/slideoverManager/SlideoverManager.js +29 -0
  126. package/dist/components/slideoverManager/SlideoverManager.js.map +1 -0
  127. package/dist/components/slideoverManager/SlideoverManager.stories.d.ts +15 -0
  128. package/dist/components/slideoverManager/SlideoverManager.stories.d.ts.map +1 -0
  129. package/dist/components/slideoverManager/SlideoverManager.stories.js +102 -0
  130. package/dist/components/slideoverManager/SlideoverManager.stories.js.map +1 -0
  131. package/dist/components/slideoverManager/SlideoverManager.test.d.ts +2 -0
  132. package/dist/components/slideoverManager/SlideoverManager.test.d.ts.map +1 -0
  133. package/dist/components/slideoverManager/SlideoverManager.test.js +53 -0
  134. package/dist/components/slideoverManager/SlideoverManager.test.js.map +1 -0
  135. package/dist/index.css +1948 -1334
  136. package/dist/index.css.map +1 -1
  137. package/dist/index.d.ts +7 -1
  138. package/dist/index.d.ts.map +1 -1
  139. package/dist/index.js +7 -1
  140. package/dist/index.js.map +1 -1
  141. package/dist/utils/Constants.d.ts +6 -0
  142. package/dist/utils/Constants.d.ts.map +1 -0
  143. package/dist/utils/Constants.js +6 -0
  144. package/dist/utils/Constants.js.map +1 -0
  145. package/dist/utils/PopupParentContext.d.ts +3 -0
  146. package/dist/utils/PopupParentContext.d.ts.map +1 -0
  147. package/dist/utils/PopupParentContext.js +6 -0
  148. package/dist/utils/PopupParentContext.js.map +1 -0
  149. package/dist/utils/PubSub.d.ts +11 -0
  150. package/dist/utils/PubSub.d.ts.map +1 -0
  151. package/dist/utils/PubSub.js +27 -0
  152. package/dist/utils/PubSub.js.map +1 -0
  153. package/dist/utils/PubSub.test.d.ts +2 -0
  154. package/dist/utils/PubSub.test.d.ts.map +1 -0
  155. package/dist/utils/PubSub.test.js +229 -0
  156. package/dist/utils/PubSub.test.js.map +1 -0
  157. package/dist/utils/SlideoverUtils.d.ts +7 -0
  158. package/dist/utils/SlideoverUtils.d.ts.map +1 -0
  159. package/dist/utils/SlideoverUtils.js +8 -0
  160. package/dist/utils/SlideoverUtils.js.map +1 -0
  161. package/dist/utils/getDefaultPopupParent.d.ts +2 -0
  162. package/dist/utils/getDefaultPopupParent.d.ts.map +1 -0
  163. package/dist/utils/getDefaultPopupParent.js +13 -0
  164. package/dist/utils/getDefaultPopupParent.js.map +1 -0
  165. package/dist/utils/hooks/useComponentDidMount.d.ts +3 -0
  166. package/dist/utils/hooks/useComponentDidMount.d.ts.map +1 -0
  167. package/dist/utils/hooks/useComponentDidMount.js +5 -0
  168. package/dist/utils/hooks/useComponentDidMount.js.map +1 -0
  169. package/dist/utils/hooks/usePubSub.d.ts +2 -0
  170. package/dist/utils/hooks/usePubSub.d.ts.map +1 -0
  171. package/dist/utils/hooks/usePubSub.js +12 -0
  172. package/dist/utils/hooks/usePubSub.js.map +1 -0
  173. package/package.json +3 -3
  174. package/src/components/button/Button.story.tsx +9 -0
  175. package/src/components/button/Button.tsx +10 -2
  176. package/src/components/button/button.scss +75 -33
  177. package/src/components/card/Card.test.tsx +0 -6
  178. package/src/components/card/Card.tsx +12 -7
  179. package/src/components/card/card.scss +32 -18
  180. package/src/components/formField/FormField.stories.tsx +9 -1
  181. package/src/components/formField/FormField.test.tsx +6 -0
  182. package/src/components/formField/FormField.tsx +5 -0
  183. package/src/components/formField/formField.scss +20 -8
  184. package/src/components/formField/inputs/checkbox/CheckboxInput.stories.tsx +22 -0
  185. package/src/components/formField/inputs/checkbox/CheckboxInput.test.tsx +35 -0
  186. package/src/components/formField/inputs/checkbox/CheckboxInput.tsx +79 -0
  187. package/src/components/formField/inputs/checkbox/checkboxInput.scss +96 -0
  188. package/src/components/formField/inputs/dropdown/Dropdown.stories.tsx +185 -0
  189. package/src/components/formField/inputs/dropdown/Dropdown.test.tsx +185 -0
  190. package/src/components/formField/inputs/dropdown/Dropdown.tsx +82 -0
  191. package/src/components/formField/inputs/dropdown/buttons/dropdownButton/DropdownButton.tsx +41 -0
  192. package/src/components/formField/inputs/dropdown/buttons/dropdownButton/dropdownButton.scss +12 -0
  193. package/src/components/formField/inputs/dropdown/dropdown.scss +24 -0
  194. package/src/components/formField/inputs/dropdown/items/DropdownItemRenderer.tsx +38 -0
  195. package/src/components/formField/inputs/dropdown/items/dropdownItem/DropdownItem.tsx +49 -0
  196. package/src/components/formField/inputs/dropdown/items/dropdownItem/dropdownItem.scss +62 -0
  197. package/src/components/formField/inputs/dropdown/items/dropdownMultiLineItem/DropdownMultiLineItem.tsx +48 -0
  198. package/src/components/formField/inputs/dropdown/items/dropdownMultiLineItem/dropdownMultiLineItem.scss +52 -0
  199. package/src/components/formField/inputs/dropdown/wrapper/DropdownWrapper.tsx +138 -0
  200. package/src/components/formField/inputs/dropdown/wrapper/dropdownWrapper.scss +32 -0
  201. package/src/components/formField/inputs/input.scss +25 -26
  202. package/src/components/formField/inputs/number/NumberInput.stories.tsx +25 -0
  203. package/src/components/formField/inputs/number/NumberInput.test.tsx +33 -0
  204. package/src/components/formField/inputs/number/NumberInput.tsx +107 -0
  205. package/src/components/formField/inputs/number/numberInput.scss +68 -0
  206. package/src/components/formField/inputs/radio/RadioButtonInput.stories.tsx +97 -0
  207. package/src/components/formField/inputs/radio/RadioButtonInput.test.tsx +37 -0
  208. package/src/components/formField/inputs/radio/RadioButtonInput.tsx +46 -0
  209. package/src/components/formField/inputs/radio/radioButtonInput.scss +100 -0
  210. package/src/components/formField/label/label.scss +5 -1
  211. package/src/components/heading/Heading.stories.tsx +11 -12
  212. package/src/components/heading/Heading.tsx +21 -2
  213. package/src/components/heading/heading.scss +4 -0
  214. package/src/components/icon/Icon.tsx +2 -2
  215. package/src/components/icon/allowedIcons.tsx +2 -0
  216. package/src/components/pill/pill.scss +7 -7
  217. package/src/components/section/Section.stories.tsx +34 -0
  218. package/src/components/section/Section.test.tsx +308 -0
  219. package/src/components/section/Section.tsx +131 -0
  220. package/src/components/section/section.scss +42 -0
  221. package/src/components/slideover/Slideover.test.tsx +36 -0
  222. package/src/components/slideover/Slideover.tsx +38 -0
  223. package/src/components/slideover/slideover.scss +50 -0
  224. package/src/components/slideoverManager/SlideoverManager.stories.tsx +374 -0
  225. package/src/components/slideoverManager/SlideoverManager.test.tsx +64 -0
  226. package/src/components/slideoverManager/SlideoverManager.tsx +51 -0
  227. package/src/components/slideoverManager/slideoverManager.scss +13 -0
  228. package/src/components/tabs/tabs.scss +8 -7
  229. package/src/global.scss +10 -1
  230. package/src/index.scss +14 -3
  231. package/src/index.ts +9 -3
  232. package/src/tokens.scss +1321 -1239
  233. package/src/utils/Constants.ts +5 -0
  234. package/src/utils/PopupParentContext.ts +6 -0
  235. package/src/utils/PubSub.test.ts +303 -0
  236. package/src/utils/PubSub.ts +34 -0
  237. package/src/utils/SlideoverUtils.ts +9 -0
  238. package/src/utils/getDefaultPopupParent.ts +14 -0
  239. package/src/utils/hooks/useComponentDidMount.ts +5 -0
  240. package/src/utils/hooks/usePubSub.ts +12 -0
  241. package/tokens/export-config.json +32 -0
  242. package/tokens/json/$metadata.json +5 -0
  243. package/tokens/json/$themes.json +1333 -0
  244. package/tokens/json/Arbor.json +6329 -0
  245. package/src/components/heading/HeadingInnerContainer.tsx +0 -18
@@ -0,0 +1,96 @@
1
+ .ds-checkbox-input__container {
2
+ display: inline-flex;
3
+ align-items: center;
4
+ justify-content: center;
5
+ gap: var(--checkbox-spacing-gap-horizontal);
6
+
7
+ &:focus-within {
8
+ outline: var(--focus-border) solid var(--color-brand-500);
9
+ border-radius: var(--checkbox-radius);
10
+ }
11
+
12
+ .ds-checkbox-input {
13
+ width: var(--icon-size-small);
14
+ height: var(--icon-size-small);
15
+ padding: 0;
16
+ box-sizing: border-box;
17
+ border-radius: var(--checkbox-input-control-radius);
18
+ border: none;
19
+ box-shadow: inset 0 0 0 var(--checkbox-input-control-unchecked-weight-border) var(--checkbox-input-control-unchecked-default-color-border);
20
+ background-color: var(--checkbox-input-control-unchecked-default-color-background);
21
+ align-items: center;
22
+ justify-content: center;
23
+ align-self: center;
24
+ display: flex;
25
+ position: relative;
26
+
27
+ .ds-checkbox-input__icon {
28
+ position: absolute;
29
+ top: 50%;
30
+ left: 50%;
31
+ transform: translate(-50%, -50%);
32
+ }
33
+
34
+ &:hover {
35
+ box-shadow: inset 0 0 0 var(--checkbox-input-control-unchecked-weight-border) var(--checkbox-input-control-unchecked-hover-color-border);
36
+ background-color: var(--checkbox-input-control-unchecked-hover-color-background);
37
+ }
38
+ }
39
+
40
+ .ds-checkbox-label__text {
41
+ flex: 1 0 0;
42
+ font-size: var(--type-body-p-size);
43
+ font-weight: var(--type-body-p-weight);
44
+ color: var(--checkbox-default-color-text);
45
+ line-height: var(--line-height-default);
46
+ }
47
+
48
+ input:checked + .ds-checkbox-input {
49
+ background-color: var(--checkbox-input-control-checked-default-color-background);
50
+ box-shadow: none;
51
+ }
52
+
53
+ input:indeterminate + .ds-checkbox-input {
54
+ background-color: var(--checkbox-input-control-indeterminate-default-color-background);
55
+ box-shadow: none;
56
+ }
57
+
58
+ input:disabled + .ds-checkbox-input {
59
+ background-color: var(--checkbox-input-control-unchecked-disabled-color-background);
60
+ cursor: not-allowed;
61
+ pointer-events: none;
62
+ box-shadow: inset 0 0 0 var(--checkbox-input-control-unchecked-weight-border) var(--checkbox-input-control-unchecked-disabled-color-border);
63
+ }
64
+
65
+ input:checked:disabled + .ds-checkbox-input {
66
+ background-color: var(--checkbox-input-control-checked-disabled-color-background);
67
+ }
68
+
69
+ input:indeterminate:disabled + .ds-checkbox-input {
70
+ background-color: var(--checkbox-input-control-indeterminate-disabled-color-background);
71
+ }
72
+
73
+ input:checked:not(:disabled) + .ds-checkbox-input:hover {
74
+ background-color: var(--checkbox-input-control-checked-hover-color-background);
75
+ }
76
+
77
+ input:indeterminate:not(:disabled) + .ds-checkbox-input:hover {
78
+ background-color: var(--checkbox-input-control-indeterminate-hover-color-background);
79
+ }
80
+
81
+ &:hover {
82
+ .ds-checkbox-label__text {
83
+ color: var(--checkbox-hover-color-text);
84
+ }
85
+ }
86
+
87
+ /* Hide the native checkbox input */
88
+ input[type='checkbox'] {
89
+ clip-path: inset(50%);
90
+ height: 0;
91
+ width: 0;
92
+ overflow: hidden;
93
+ position: absolute;
94
+ white-space: nowrap;
95
+ }
96
+ }
@@ -0,0 +1,185 @@
1
+ import type { Meta } from '@storybook/react-vite';
2
+ import { Dropdown } from './Dropdown';
3
+
4
+ const meta: Meta<typeof Dropdown> = {
5
+ title: 'Components/Dropdown',
6
+ component: Dropdown,
7
+ };
8
+
9
+ export const Default = {
10
+ args: {
11
+ title: 'titleValue',
12
+ options: [
13
+ { label: 'Option 1', value: 'option1' },
14
+ { label: 'Option 2', value: 'option2' },
15
+ { label: 'Option 3', value: 'option3' },
16
+ ],
17
+ multiple: false,
18
+ onSelectionChange: (value: string[]) => { console.log(value); },
19
+ },
20
+ };
21
+
22
+ export const WithIcon = {
23
+ args: {
24
+ title: 'titleValue',
25
+ options: [
26
+ { label: 'Option 1', value: 'option1', icon: '3-dot' },
27
+ { label: 'Option 2', value: 'option2', icon: 'user' },
28
+ { label: 'Option 3', value: 'option3', icon: 'chart-spline' },
29
+ ],
30
+ multiple: true,
31
+ onSelectionChange: (value: string[]) => { console.log(value); },
32
+ },
33
+ };
34
+
35
+ export const DefaultWithGroups = {
36
+ args: {
37
+ title: 'titleValue',
38
+ multiple: true,
39
+ options: [
40
+ { label: 'Option 5', value: 'option5', group: 'Group 1' },
41
+ { label: 'Option 6', value: 'option6', group: 'Group 1' },
42
+ { label: 'Option 7', value: 'option7', group: 'Group 2' },
43
+ { label: 'Option 8', value: 'option8', group: 'Group 2' },
44
+ { label: 'Option 1', value: 'option1' },
45
+ { label: 'Option 2', value: 'option2' },
46
+ { label: 'Option 3', value: 'option3' },
47
+ { label: 'Option 4', value: 'option4' },
48
+ { label: 'Option 9', value: 'option9' },
49
+ ],
50
+ onSelectionChange: (value: string[]) => { console.log(value); },
51
+ },
52
+ };
53
+
54
+ export const DefaultMultiSelect = {
55
+ args: {
56
+ title: 'titleValue',
57
+ options: [
58
+ { label: 'Option 1', value: 'option1' },
59
+ { label: 'Option 2', value: 'option2' },
60
+ { label: 'Option 3', value: 'option3' },
61
+ ],
62
+ multiple: true,
63
+ onSelectionChange: (value: string[]) => { console.log(value); },
64
+ },
65
+ };
66
+
67
+ export const Placeholder = {
68
+ args: {
69
+ placeholder: 'placeholder',
70
+ title: 'titleValue',
71
+ options: [
72
+ { label: 'Option 1', value: 'option1' },
73
+ { label: 'Option 2', value: 'option2' },
74
+ { label: 'Option 3', value: 'option3' },
75
+ ],
76
+ onSelectionChange: (value: string[]) => { console.log(value); },
77
+ },
78
+ };
79
+
80
+ export const EmptyPlaceHolder = {
81
+ args: {
82
+ placeholder: '',
83
+ options: [
84
+ { label: 'Option 1', value: 'option1' },
85
+ { label: 'Option 2', value: 'option2' },
86
+ { label: 'Option 3', value: 'option3' },
87
+ ],
88
+ disabled: false,
89
+ onSelectionChange: (value: string[]) => { console.log(value); },
90
+ },
91
+ };
92
+
93
+ export const WithError = {
94
+ args: {
95
+ title: 'titleValue',
96
+ options: [
97
+ { label: 'Option 1', value: 'option1' },
98
+ { label: 'Option 2', value: 'option2' },
99
+ { label: 'Option 3', value: 'option3' },
100
+ ],
101
+ disabled: false,
102
+ errorText: 'This field is required',
103
+ onSelectionChange: (value: string[]) => { console.log(value); },
104
+ },
105
+ };
106
+
107
+ export const Disabled = {
108
+ args: {
109
+ title: 'titleValue',
110
+ options: [
111
+ { label: 'Option 1', value: 'option1' },
112
+ { label: 'Option 2', value: 'option2' },
113
+ { label: 'Option 3', value: 'option3' },
114
+ ],
115
+ disabled: true,
116
+ onSelectionChange: (value: string[]) => { console.log(value); },
117
+ },
118
+ };
119
+
120
+ export const WithGroups = {
121
+ args: {
122
+ title: 'titleValue',
123
+ options: [
124
+ { label: 'Option 1', value: 'option1', group: 'Group 1' },
125
+ { label: 'Option 2', value: 'option2', group: 'Group 1' },
126
+ { label: 'Option 4', value: 'option4', group: 'Group 2' },
127
+ { label: 'Option 5', value: 'option5', group: 'Group 2' },
128
+ { label: 'Option 3', value: 'option3' },
129
+ { label: 'Option 6', value: 'option6' },
130
+ { label: 'Option 7', value: 'option7' },
131
+ ],
132
+ onSelectionChange: (value: string[]) => { console.log(value); },
133
+ },
134
+ };
135
+
136
+ export const MultilineItems = {
137
+ args: {
138
+ title: 'titleValue',
139
+ options: [
140
+ { label: 'Option 1', value: 'option1', header: 'header1' },
141
+ { label: 'Option 2', value: 'option2', header: 'header2' },
142
+ { label: 'Option 3', value: 'option3', header: 'header3' },
143
+ { label: 'Option 4', value: 'option4', header: 'header4' },
144
+ ],
145
+ onSelectionChange: (value: string[]) => { console.log(value); },
146
+ },
147
+ };
148
+
149
+ export const MultilineItemsGrouped = {
150
+ args: {
151
+ title: 'titleValue',
152
+ options: [
153
+ { label: 'Option 1', value: 'option1', header: 'header1' },
154
+ { label: 'Option 2', value: 'option2', header: 'header2' },
155
+ { label: 'Option 3', value: 'option3', header: 'header3' },
156
+ { label: 'Option 4', value: 'option4', header: 'header4' },
157
+ { label: 'Option 5', value: 'option5', header: 'header5', group: 'Group 1' },
158
+ { label: 'Option 6', value: 'option6', header: 'header6', group: 'Group 1' },
159
+ { label: 'Option 7', value: 'option7', header: 'header7', group: 'Group 2' },
160
+ { label: 'Option 8', value: 'option8', header: 'header8', group: 'Group 2' },
161
+ { label: 'Option 9', value: 'option9', header: 'header9' },
162
+ ],
163
+ onSelectionChange: (value: string[]) => { console.log(value); },
164
+ },
165
+ };
166
+
167
+ export const MultilineItemsGroupedMultiSelect = {
168
+ args: {
169
+ title: 'titleValue',
170
+ multiple: true,
171
+ options: [
172
+ { label: 'Option 5', value: 'option5', header: 'header5', group: 'Group 1' },
173
+ { label: 'Option 6', value: 'option6', header: 'header6', group: 'Group 1' },
174
+ { label: 'Option 7', value: 'option7', header: 'header7', group: 'Group 2' },
175
+ { label: 'Option 8', value: 'option8', header: 'header8', group: 'Group 2' },
176
+ { label: 'Option 1', value: 'option1', header: 'header1' },
177
+ { label: 'Option 2', value: 'option2', header: 'header2' },
178
+ { label: 'Option 3', value: 'option3', header: 'header3' },
179
+ { label: 'Option 4', value: 'option4', header: 'header4' },
180
+ { label: 'Option 9', value: 'option9', header: 'header9' },
181
+ ],
182
+ onSelectionChange: (value: string[]) => { console.log(value); },
183
+ },
184
+ };
185
+ export default meta;
@@ -0,0 +1,185 @@
1
+ import { describe, expect, test, vi } from 'vitest';
2
+ import { render, screen } from '@testing-library/react';
3
+ import userEvent from '@testing-library/user-event';
4
+ import '@testing-library/jest-dom/vitest';
5
+ import { Dropdown } from './Dropdown';
6
+
7
+ describe('Dropdown component', () => {
8
+ test('renders with default placeholder', () => {
9
+ render(
10
+ <Dropdown
11
+ onSelectionChange={vi.fn()}
12
+ disabled={false}
13
+ options={[{ label: 'Label 1', value: 'label1' }]}
14
+ />,
15
+ );
16
+ expect(screen.getByText('Select')).toBeInTheDocument();
17
+ });
18
+
19
+ test('renders custom placeholder', () => {
20
+ render(
21
+ <Dropdown
22
+ onSelectionChange={vi.fn()}
23
+ placeholder="Hello I'm a Dropdown!"
24
+ disabled={false}
25
+ options={[{ label: 'Label 1', value: 'label1' }]}
26
+ />,
27
+ );
28
+ expect(screen.getByText("Hello I'm a Dropdown!")).toBeInTheDocument();
29
+ });
30
+
31
+ test('click opens options', async () => {
32
+ render(
33
+ <Dropdown
34
+ onSelectionChange={vi.fn()}
35
+ placeholder="Hello I'm a Dropdown!"
36
+ disabled={false}
37
+ options={[{ label: 'Label 1', value: 'label1' }]}
38
+ />,
39
+ );
40
+
41
+ await userEvent.click(screen.getByText("Hello I'm a Dropdown!"));
42
+ expect(await screen.findByText('Label 1')).toBeInTheDocument();
43
+ });
44
+
45
+ test('does not open options when disabled', async () => {
46
+ render(
47
+ <Dropdown
48
+ onSelectionChange={vi.fn()}
49
+ placeholder="Disabled Dropdown"
50
+ disabled={true}
51
+ options={[{ label: 'Label 1', value: 'label1' }]}
52
+ />,
53
+ );
54
+
55
+ await userEvent.click(screen.getByText('Disabled Dropdown'));
56
+ expect(screen.queryByText('Label 1')).toBeNull();
57
+ });
58
+
59
+ test('clicking option triggers onSelectionChange', async () => {
60
+ const onSelectionChange = vi.fn();
61
+ render(
62
+ <Dropdown
63
+ onSelectionChange={onSelectionChange}
64
+ placeholder="Hello I'm a Dropdown!"
65
+ disabled={false}
66
+ options={[{ label: 'Label 1', value: 'label1' }]}
67
+ />,
68
+ );
69
+
70
+ await userEvent.click(screen.getByText("Hello I'm a Dropdown!"));
71
+ const option = await screen.findByText('Label 1');
72
+ await userEvent.click(option);
73
+
74
+ expect(onSelectionChange).toHaveBeenCalledWith(['label1']);
75
+ });
76
+
77
+ test('renders multiple options (Default story)', async () => {
78
+ render(
79
+ <Dropdown
80
+ options={[
81
+ { label: 'Option 1', value: 'option1' },
82
+ { label: 'Option 2', value: 'option2' },
83
+ { label: 'Option 3', value: 'option3' },
84
+ ]}
85
+ onSelectionChange={vi.fn()}
86
+ />,
87
+ );
88
+
89
+ await userEvent.click(screen.getByText('Select'));
90
+ expect(await screen.findByText('Option 1')).toBeInTheDocument();
91
+ expect(screen.getByText('Option 2')).toBeInTheDocument();
92
+ expect(screen.getByText('Option 3')).toBeInTheDocument();
93
+ });
94
+
95
+ test('renders icons (WithIcon story)', async () => {
96
+ render(
97
+ <Dropdown
98
+ options={[
99
+ { label: 'Option 1', value: 'option1', icon: '3-dot' },
100
+ { label: 'Option 2', value: 'option2', icon: 'user' },
101
+ ]}
102
+ multiple
103
+ onSelectionChange={vi.fn()}
104
+ />,
105
+ );
106
+
107
+ await userEvent.click(screen.getByText('Select'));
108
+ expect(await screen.findByText('Option 1')).toBeInTheDocument();
109
+ expect(screen.getByText('Option 2')).toBeInTheDocument();
110
+ });
111
+
112
+ test('supports multi-select (DefaultMultiSelect story)', async () => {
113
+ const onSelectionChange = vi.fn();
114
+ render(
115
+ <Dropdown
116
+ multiple
117
+ options={[
118
+ { label: 'Option 1', value: 'option1' },
119
+ { label: 'Option 2', value: 'option2' },
120
+ ]}
121
+ onSelectionChange={onSelectionChange}
122
+ />,
123
+ );
124
+
125
+ await userEvent.click(screen.getByText('Select'));
126
+ const option1 = await screen.findByText('Option 1');
127
+ const option2 = screen.getByText('Option 2');
128
+
129
+ await userEvent.click(option1);
130
+ await userEvent.click(option2);
131
+
132
+ expect(onSelectionChange).toHaveBeenCalledWith(['option1', 'option2']);
133
+ });
134
+
135
+ test('applies error class when errorText is provided', () => {
136
+ render(
137
+ <Dropdown
138
+ onSelectionChange={vi.fn()}
139
+ placeholder="Select"
140
+ disabled={false}
141
+ errorText="Something went wrong"
142
+ options={[{ label: 'Label 1', value: 'label1' }]}
143
+ />,
144
+ );
145
+
146
+ const selectElement = screen.getByRole('button');
147
+ expect(selectElement).toHaveClass('ds-button--error');
148
+ });
149
+
150
+ test('renders grouped options (WithGroups story)', async () => {
151
+ render(
152
+ <Dropdown
153
+ options={[
154
+ { label: 'Option 1', value: 'option1', group: 'Group 1' },
155
+ { label: 'Option 2', value: 'option2', group: 'Group 1' },
156
+ { label: 'Option 3', value: 'option3' },
157
+ ]}
158
+ onSelectionChange={vi.fn()}
159
+ />,
160
+ );
161
+
162
+ await userEvent.click(screen.getByText('Select'));
163
+
164
+ expect(await screen.findByText('Group 1')).toBeInTheDocument();
165
+ expect(screen.getByText('Option 1')).toBeInTheDocument();
166
+ expect(screen.getByText('Option 3')).toBeInTheDocument();
167
+ });
168
+
169
+ test('renders multiline items (MultilineItems story)', async () => {
170
+ render(
171
+ <Dropdown
172
+ options={[
173
+ { label: 'Option 1', value: 'option1', header: 'header1' },
174
+ { label: 'Option 2', value: 'option2', header: 'header2' },
175
+ ]}
176
+ onSelectionChange={vi.fn()}
177
+ />,
178
+ );
179
+
180
+ await userEvent.click(screen.getByText('Select'));
181
+
182
+ expect(await screen.findByText('Option 1')).toBeInTheDocument();
183
+ expect(screen.getByText('header1')).toBeInTheDocument();
184
+ });
185
+ });
@@ -0,0 +1,82 @@
1
+ import { useEffect, useState } from 'react';
2
+ import { DropdownWrapper } from 'Components/formField/inputs/dropdown/wrapper/DropdownWrapper';
3
+ import {
4
+ DropdownItemRenderer,
5
+ type DropdownOptionsProps,
6
+ } from 'Components/formField/inputs/dropdown/items/DropdownItemRenderer';
7
+
8
+ export type DropdownInputProps = {
9
+ placeholder?: string;
10
+ options: DropdownOptionsProps[];
11
+ multiple?: boolean;
12
+ disabled?: boolean;
13
+ errorText?: string;
14
+ onSelectionChange: (value: string[]) => void;
15
+ };
16
+
17
+ export const Dropdown = (props: DropdownInputProps) => {
18
+ const { options, disabled, multiple, placeholder, errorText, onSelectionChange } = props;
19
+
20
+ const [selectedValues, setSelectedValues] = useState<string[]>([]);
21
+ const [renderedSelectContent, setRenderedSelectContent] = useState('');
22
+
23
+ useEffect(() => {
24
+ onSelectionChange(selectedValues);
25
+
26
+ if (selectedValues.length === 0) {
27
+ setRenderedSelectContent(placeholder ?? 'Select');
28
+ }
29
+ else if (selectedValues.length === 1) {
30
+ const selectedLabel = options.find(option => option.value === selectedValues[0])?.label;
31
+ if (selectedLabel) setRenderedSelectContent(selectedLabel);
32
+ }
33
+ else {
34
+ setRenderedSelectContent(placeholder ?? `Select (${selectedValues.length})`);
35
+ }
36
+ }, [selectedValues, options, placeholder, onSelectionChange]);
37
+
38
+ // Flatten grouped options into a single array with headers
39
+ const UNGROUPED_KEY = 'Ungrouped';
40
+ const groupedOptions = options.reduce((acc, option) => {
41
+ const key = option.group ?? UNGROUPED_KEY;
42
+ if (!acc[key]) acc[key] = [];
43
+ acc[key].push(option);
44
+ return acc;
45
+ }, {} as Record<string, DropdownOptionsProps[]>);
46
+
47
+ const flatOptions: (DropdownOptionsProps | { headerLabel: string })[] = [];
48
+ Object.entries(groupedOptions).forEach(([groupName, groupItems]) => {
49
+ if (Object.keys(groupedOptions).length > 1) {
50
+ flatOptions.push({ headerLabel: groupName });
51
+ }
52
+ flatOptions.push(...groupItems);
53
+ });
54
+
55
+ return (
56
+ <DropdownWrapper
57
+ placeholder={renderedSelectContent}
58
+ multiple={multiple}
59
+ errorText={errorText}
60
+ onSelectionChange={(value: string[]) => setSelectedValues(value)}
61
+ disabled={disabled}
62
+ >
63
+ {flatOptions.map(item =>
64
+ 'headerLabel' in item
65
+ ? (
66
+ <h3 key={`${item.headerLabel}-header`} className="ds-dropdown-wrapper--items--header">
67
+ {item.headerLabel}
68
+ </h3>
69
+ )
70
+ : (
71
+ <DropdownItemRenderer
72
+ value={item.value}
73
+ header={item.header}
74
+ label={item.label}
75
+ icon={item.icon}
76
+ key={item.value}
77
+ />
78
+ ),
79
+ )}
80
+ </DropdownWrapper>
81
+ );
82
+ };
@@ -0,0 +1,41 @@
1
+ import React from 'react';
2
+ import { Icon } from 'Components/icon/Icon';
3
+ import { Button } from 'Components/button/Button';
4
+
5
+ type DropdownButtonProps = {
6
+ label: string;
7
+ disabled?: boolean;
8
+ error?: boolean;
9
+ pressed?: boolean;
10
+ onClick?: (e: React.MouseEvent<HTMLButtonElement>) => void;
11
+ };
12
+
13
+ export const DropdownButton: React.FC<DropdownButtonProps> = (props: DropdownButtonProps) => {
14
+ const {
15
+ label,
16
+ disabled,
17
+ error,
18
+ pressed,
19
+ onClick,
20
+ } = props;
21
+
22
+ const classNames = [
23
+ 'ds-dropdown-select',
24
+ pressed ? 'ds-dropdown-select--pressed' : '',
25
+ ]
26
+ .filter(Boolean)
27
+ .join(' ');
28
+
29
+ return (
30
+ <Button
31
+ error={error}
32
+ type="dropdown"
33
+ className={classNames}
34
+ disabled={disabled}
35
+ onClick={onClick}
36
+ >
37
+ <span>{label}</span>
38
+ <span className="ds-dropdown-select--icon"><Icon name="chevron-down" /></span>
39
+ </Button>
40
+ );
41
+ };
@@ -0,0 +1,12 @@
1
+ .ds-dropdown-select {
2
+ &--pressed {
3
+ border: 1px solid var(--button-medium-secondary-default-color-border);
4
+ background: var(--button-medium-secondary-active-color-background);
5
+ }
6
+
7
+ &--icon {
8
+ margin-left: var(--spacing-small);
9
+ display: inline-flex;
10
+ align-items: center;
11
+ }
12
+ }
@@ -0,0 +1,24 @@
1
+
2
+ .ds-dropdown {
3
+ select {
4
+ display: flex;
5
+ padding: var(--spacing-small) var(--spacing-small) var(--spacing-small) var(--spacing-medium);
6
+ justify-content: space-between;
7
+ align-items: center;
8
+ flex-shrink: 0;
9
+ border-radius: var(--border-radius-small);
10
+ background: var(--button-medium-secondary-default-color-background);
11
+
12
+ &:focus-visible {
13
+ outline: none;
14
+ box-shadow: 0 0 0 3px var(--color-brand-500);
15
+ background: var(--button-medium-secondary-default-color-background);
16
+ }
17
+
18
+ &:hover {
19
+ border: 1px solid var(--color-grey-500);
20
+ background: var(--color-mono-white);
21
+ cursor: pointer;
22
+ }
23
+ }
24
+ }
@@ -0,0 +1,38 @@
1
+ // DropdownItemRenderer.tsx
2
+ import { DropdownItem } from './dropdownItem/DropdownItem';
3
+ import { DropdownMultiLineItem } from './dropdownMultiLineItem/DropdownMultiLineItem';
4
+ import { type DropdownItemBaseProps } from '../wrapper/DropdownWrapper';
5
+ import type { allowedIcons } from 'Components/icon/allowedIcons';
6
+
7
+ export type DropdownOptionsProps = {
8
+ label: string;
9
+ header?: string;
10
+ group?: string;
11
+ icon?: keyof typeof allowedIcons;
12
+ } & DropdownItemBaseProps;
13
+
14
+ export const DropdownItemRenderer = (props: DropdownOptionsProps) => {
15
+ const { value, label, header, icon, selected, onSelection } = props;
16
+
17
+ return (
18
+ header
19
+ ? (
20
+ <DropdownMultiLineItem
21
+ value={value}
22
+ label={label}
23
+ header={header}
24
+ selected={selected}
25
+ onSelection={onSelection}
26
+ />
27
+ )
28
+ : (
29
+ <DropdownItem
30
+ value={value}
31
+ label={label}
32
+ icon={icon}
33
+ selected={selected}
34
+ onSelection={onSelection}
35
+ />
36
+ )
37
+ );
38
+ };