@arbor-education/design-system.components 0.6.0 → 0.7.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 (285) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/dist/components/avatar/Avatar.d.ts +1 -1
  3. package/dist/components/avatar/Avatar.d.ts.map +1 -1
  4. package/dist/components/avatar/Avatar.js +1 -1
  5. package/dist/components/avatar/Avatar.js.map +1 -1
  6. package/dist/components/avatar/Avatar.stories.d.ts.map +1 -1
  7. package/dist/components/avatar/Avatar.stories.js +7 -0
  8. package/dist/components/avatar/Avatar.stories.js.map +1 -1
  9. package/dist/components/badge/Badge.d.ts +12 -0
  10. package/dist/components/badge/Badge.d.ts.map +1 -0
  11. package/dist/components/badge/Badge.js +6 -0
  12. package/dist/components/badge/Badge.js.map +1 -0
  13. package/dist/components/badge/Badge.stories.d.ts +10 -0
  14. package/dist/components/badge/Badge.stories.d.ts.map +1 -0
  15. package/dist/components/badge/Badge.stories.js +51 -0
  16. package/dist/components/badge/Badge.stories.js.map +1 -0
  17. package/dist/components/badge/Badge.test.d.ts +2 -0
  18. package/dist/components/badge/Badge.test.d.ts.map +1 -0
  19. package/dist/components/badge/Badge.test.js +23 -0
  20. package/dist/components/badge/Badge.test.js.map +1 -0
  21. package/dist/components/card/Card.js +1 -1
  22. package/dist/components/card/Card.js.map +1 -1
  23. package/dist/components/combobox/Combobox.d.ts +16 -0
  24. package/dist/components/combobox/Combobox.d.ts.map +1 -0
  25. package/dist/components/combobox/Combobox.js +195 -0
  26. package/dist/components/combobox/Combobox.js.map +1 -0
  27. package/dist/components/combobox/Combobox.stories.d.ts +24 -0
  28. package/dist/components/combobox/Combobox.stories.d.ts.map +1 -0
  29. package/dist/components/combobox/Combobox.stories.js +246 -0
  30. package/dist/components/combobox/Combobox.stories.js.map +1 -0
  31. package/dist/components/combobox/Combobox.test.d.ts +2 -0
  32. package/dist/components/combobox/Combobox.test.d.ts.map +1 -0
  33. package/dist/components/combobox/Combobox.test.js +798 -0
  34. package/dist/components/combobox/Combobox.test.js.map +1 -0
  35. package/dist/components/combobox/ComboboxButtonTrigger.d.ts +28 -0
  36. package/dist/components/combobox/ComboboxButtonTrigger.d.ts.map +1 -0
  37. package/dist/components/combobox/ComboboxButtonTrigger.js +64 -0
  38. package/dist/components/combobox/ComboboxButtonTrigger.js.map +1 -0
  39. package/dist/components/combobox/ComboboxListbox.d.ts +44 -0
  40. package/dist/components/combobox/ComboboxListbox.d.ts.map +1 -0
  41. package/dist/components/combobox/ComboboxListbox.js +37 -0
  42. package/dist/components/combobox/ComboboxListbox.js.map +1 -0
  43. package/dist/components/combobox/ComboboxOptionRow.d.ts +23 -0
  44. package/dist/components/combobox/ComboboxOptionRow.d.ts.map +1 -0
  45. package/dist/components/combobox/ComboboxOptionRow.js +27 -0
  46. package/dist/components/combobox/ComboboxOptionRow.js.map +1 -0
  47. package/dist/components/combobox/ComboboxTrigger.d.ts +35 -0
  48. package/dist/components/combobox/ComboboxTrigger.d.ts.map +1 -0
  49. package/dist/components/combobox/ComboboxTrigger.js +15 -0
  50. package/dist/components/combobox/ComboboxTrigger.js.map +1 -0
  51. package/dist/components/combobox/buildListboxDisplayOptions.d.ts +3 -0
  52. package/dist/components/combobox/buildListboxDisplayOptions.d.ts.map +1 -0
  53. package/dist/components/combobox/buildListboxDisplayOptions.js +13 -0
  54. package/dist/components/combobox/buildListboxDisplayOptions.js.map +1 -0
  55. package/dist/components/combobox/buildListboxDisplayOptions.test.d.ts +2 -0
  56. package/dist/components/combobox/buildListboxDisplayOptions.test.d.ts.map +1 -0
  57. package/dist/components/combobox/buildListboxDisplayOptions.test.js +22 -0
  58. package/dist/components/combobox/buildListboxDisplayOptions.test.js.map +1 -0
  59. package/dist/components/combobox/comboboxKeyboardTypes.d.ts +41 -0
  60. package/dist/components/combobox/comboboxKeyboardTypes.d.ts.map +1 -0
  61. package/dist/components/combobox/comboboxKeyboardTypes.js +2 -0
  62. package/dist/components/combobox/comboboxKeyboardTypes.js.map +1 -0
  63. package/dist/components/combobox/highlightLabel.d.ts +10 -0
  64. package/dist/components/combobox/highlightLabel.d.ts.map +1 -0
  65. package/dist/components/combobox/highlightLabel.js +18 -0
  66. package/dist/components/combobox/highlightLabel.js.map +1 -0
  67. package/dist/components/combobox/normaliseComboboxQuery.d.ts +2 -0
  68. package/dist/components/combobox/normaliseComboboxQuery.d.ts.map +1 -0
  69. package/dist/components/combobox/normaliseComboboxQuery.js +2 -0
  70. package/dist/components/combobox/normaliseComboboxQuery.js.map +1 -0
  71. package/dist/components/combobox/types.d.ts +46 -0
  72. package/dist/components/combobox/types.d.ts.map +1 -0
  73. package/dist/components/combobox/types.js +2 -0
  74. package/dist/components/combobox/types.js.map +1 -0
  75. package/dist/components/combobox/useChipSelection.d.ts +11 -0
  76. package/dist/components/combobox/useChipSelection.d.ts.map +1 -0
  77. package/dist/components/combobox/useChipSelection.js +35 -0
  78. package/dist/components/combobox/useChipSelection.js.map +1 -0
  79. package/dist/components/combobox/useComboboxChipKeyboard.d.ts +3 -0
  80. package/dist/components/combobox/useComboboxChipKeyboard.d.ts.map +1 -0
  81. package/dist/components/combobox/useComboboxChipKeyboard.js +103 -0
  82. package/dist/components/combobox/useComboboxChipKeyboard.js.map +1 -0
  83. package/dist/components/combobox/useComboboxChipKeyboard.test.d.ts +2 -0
  84. package/dist/components/combobox/useComboboxChipKeyboard.test.d.ts.map +1 -0
  85. package/dist/components/combobox/useComboboxChipKeyboard.test.js +116 -0
  86. package/dist/components/combobox/useComboboxChipKeyboard.test.js.map +1 -0
  87. package/dist/components/combobox/useComboboxKeyboard.d.ts +4 -0
  88. package/dist/components/combobox/useComboboxKeyboard.d.ts.map +1 -0
  89. package/dist/components/combobox/useComboboxKeyboard.js +68 -0
  90. package/dist/components/combobox/useComboboxKeyboard.js.map +1 -0
  91. package/dist/components/combobox/useComboboxListboxDom.d.ts +11 -0
  92. package/dist/components/combobox/useComboboxListboxDom.d.ts.map +1 -0
  93. package/dist/components/combobox/useComboboxListboxDom.js +15 -0
  94. package/dist/components/combobox/useComboboxListboxDom.js.map +1 -0
  95. package/dist/components/combobox/useComboboxListboxKeyboard.d.ts +3 -0
  96. package/dist/components/combobox/useComboboxListboxKeyboard.d.ts.map +1 -0
  97. package/dist/components/combobox/useComboboxListboxKeyboard.js +143 -0
  98. package/dist/components/combobox/useComboboxListboxKeyboard.js.map +1 -0
  99. package/dist/components/combobox/useComboboxListboxKeyboard.test.d.ts +2 -0
  100. package/dist/components/combobox/useComboboxListboxKeyboard.test.d.ts.map +1 -0
  101. package/dist/components/combobox/useComboboxListboxKeyboard.test.js +152 -0
  102. package/dist/components/combobox/useComboboxListboxKeyboard.test.js.map +1 -0
  103. package/dist/components/combobox/useComboboxPopoverBehavior.d.ts +38 -0
  104. package/dist/components/combobox/useComboboxPopoverBehavior.d.ts.map +1 -0
  105. package/dist/components/combobox/useComboboxPopoverBehavior.js +104 -0
  106. package/dist/components/combobox/useComboboxPopoverBehavior.js.map +1 -0
  107. package/dist/components/combobox/useComboboxState.d.ts +27 -0
  108. package/dist/components/combobox/useComboboxState.d.ts.map +1 -0
  109. package/dist/components/combobox/useComboboxState.js +122 -0
  110. package/dist/components/combobox/useComboboxState.js.map +1 -0
  111. package/dist/components/combobox/useElementWidth.d.ts +2 -0
  112. package/dist/components/combobox/useElementWidth.d.ts.map +1 -0
  113. package/dist/components/combobox/useElementWidth.js +31 -0
  114. package/dist/components/combobox/useElementWidth.js.map +1 -0
  115. package/dist/components/combobox/useVisibleChips.d.ts +21 -0
  116. package/dist/components/combobox/useVisibleChips.d.ts.map +1 -0
  117. package/dist/components/combobox/useVisibleChips.js +59 -0
  118. package/dist/components/combobox/useVisibleChips.js.map +1 -0
  119. package/dist/components/combobox/useVisibleChips.test.d.ts +2 -0
  120. package/dist/components/combobox/useVisibleChips.test.d.ts.map +1 -0
  121. package/dist/components/combobox/useVisibleChips.test.js +81 -0
  122. package/dist/components/combobox/useVisibleChips.test.js.map +1 -0
  123. package/dist/components/dot/Dot.d.ts +8 -0
  124. package/dist/components/dot/Dot.d.ts.map +1 -0
  125. package/dist/components/dot/Dot.js +6 -0
  126. package/dist/components/dot/Dot.js.map +1 -0
  127. package/dist/components/dot/Dot.stories.d.ts +15 -0
  128. package/dist/components/dot/Dot.stories.d.ts.map +1 -0
  129. package/dist/components/dot/Dot.stories.js +25 -0
  130. package/dist/components/dot/Dot.stories.js.map +1 -0
  131. package/dist/components/dot/Dot.test.d.ts +2 -0
  132. package/dist/components/dot/Dot.test.d.ts.map +1 -0
  133. package/dist/components/dot/Dot.test.js +19 -0
  134. package/dist/components/dot/Dot.test.js.map +1 -0
  135. package/dist/components/formField/FormField.d.ts +8 -4
  136. package/dist/components/formField/FormField.d.ts.map +1 -1
  137. package/dist/components/formField/FormField.js +7 -6
  138. package/dist/components/formField/FormField.js.map +1 -1
  139. package/dist/components/formField/FormField.stories.d.ts +1 -0
  140. package/dist/components/formField/FormField.stories.d.ts.map +1 -1
  141. package/dist/components/formField/FormField.stories.js +13 -1
  142. package/dist/components/formField/FormField.stories.js.map +1 -1
  143. package/dist/components/formField/FormField.test.js +10 -0
  144. package/dist/components/formField/FormField.test.js.map +1 -1
  145. package/dist/components/icon/allowedIcons.d.ts +1 -0
  146. package/dist/components/icon/allowedIcons.d.ts.map +1 -1
  147. package/dist/components/icon/allowedIcons.js +2 -1
  148. package/dist/components/icon/allowedIcons.js.map +1 -1
  149. package/dist/components/progress/Progress.stories.d.ts +49 -49
  150. package/dist/components/singleUser/SingleUser.d.ts +15 -0
  151. package/dist/components/singleUser/SingleUser.d.ts.map +1 -0
  152. package/dist/components/singleUser/SingleUser.js +9 -0
  153. package/dist/components/singleUser/SingleUser.js.map +1 -0
  154. package/dist/components/singleUser/SingleUser.stories.d.ts +11 -0
  155. package/dist/components/singleUser/SingleUser.stories.d.ts.map +1 -0
  156. package/dist/components/singleUser/SingleUser.stories.js +52 -0
  157. package/dist/components/singleUser/SingleUser.stories.js.map +1 -0
  158. package/dist/components/singleUser/SingleUser.test.d.ts +2 -0
  159. package/dist/components/singleUser/SingleUser.test.d.ts.map +1 -0
  160. package/dist/components/singleUser/SingleUser.test.js +30 -0
  161. package/dist/components/singleUser/SingleUser.test.js.map +1 -0
  162. package/dist/components/tabs/TabsItem.stories.d.ts +2 -2
  163. package/dist/components/tag/Tag.d.ts +9 -6
  164. package/dist/components/tag/Tag.d.ts.map +1 -1
  165. package/dist/components/tag/Tag.js +8 -2
  166. package/dist/components/tag/Tag.js.map +1 -1
  167. package/dist/components/tag/Tag.stories.d.ts +11 -6
  168. package/dist/components/tag/Tag.stories.d.ts.map +1 -1
  169. package/dist/components/tag/Tag.stories.js +68 -4
  170. package/dist/components/tag/Tag.stories.js.map +1 -1
  171. package/dist/components/tag/Tag.test.js +86 -50
  172. package/dist/components/tag/Tag.test.js.map +1 -1
  173. package/dist/components/toggle/Toggle.d.ts +3 -0
  174. package/dist/components/toggle/Toggle.d.ts.map +1 -0
  175. package/dist/components/toggle/Toggle.js +8 -0
  176. package/dist/components/toggle/Toggle.js.map +1 -0
  177. package/dist/components/toggle/Toggle.stories.d.ts +97 -0
  178. package/dist/components/toggle/Toggle.stories.d.ts.map +1 -0
  179. package/dist/components/toggle/Toggle.stories.js +186 -0
  180. package/dist/components/toggle/Toggle.stories.js.map +1 -0
  181. package/dist/components/toggle/Toggle.test.d.ts +2 -0
  182. package/dist/components/toggle/Toggle.test.d.ts.map +1 -0
  183. package/dist/components/toggle/Toggle.test.js +58 -0
  184. package/dist/components/toggle/Toggle.test.js.map +1 -0
  185. package/dist/index.css +656 -25
  186. package/dist/index.css.map +1 -1
  187. package/dist/index.d.ts +34 -25
  188. package/dist/index.d.ts.map +1 -1
  189. package/dist/index.js +30 -25
  190. package/dist/index.js.map +1 -1
  191. package/dist/mocks/comboboxStoryOptions.d.ts +5 -0
  192. package/dist/mocks/comboboxStoryOptions.d.ts.map +1 -0
  193. package/dist/mocks/comboboxStoryOptions.js +22 -0
  194. package/dist/mocks/comboboxStoryOptions.js.map +1 -0
  195. package/dist/utils/isSelectAllChord.d.ts +5 -0
  196. package/dist/utils/isSelectAllChord.d.ts.map +1 -0
  197. package/dist/utils/isSelectAllChord.js +7 -0
  198. package/dist/utils/isSelectAllChord.js.map +1 -0
  199. package/dist/utils/isSelectAllChord.test.d.ts +2 -0
  200. package/dist/utils/isSelectAllChord.test.d.ts.map +1 -0
  201. package/dist/utils/isSelectAllChord.test.js +19 -0
  202. package/dist/utils/isSelectAllChord.test.js.map +1 -0
  203. package/dist/utils/nextCircularIndex.d.ts +3 -0
  204. package/dist/utils/nextCircularIndex.d.ts.map +1 -0
  205. package/dist/utils/nextCircularIndex.js +10 -0
  206. package/dist/utils/nextCircularIndex.js.map +1 -0
  207. package/dist/utils/nextCircularIndex.test.d.ts +2 -0
  208. package/dist/utils/nextCircularIndex.test.d.ts.map +1 -0
  209. package/dist/utils/nextCircularIndex.test.js +23 -0
  210. package/dist/utils/nextCircularIndex.test.js.map +1 -0
  211. package/dist/utils/scrollElementIntoViewById.d.ts +2 -0
  212. package/dist/utils/scrollElementIntoViewById.d.ts.map +1 -0
  213. package/dist/utils/scrollElementIntoViewById.js +16 -0
  214. package/dist/utils/scrollElementIntoViewById.js.map +1 -0
  215. package/dist/utils/scrollElementIntoViewById.test.d.ts +2 -0
  216. package/dist/utils/scrollElementIntoViewById.test.d.ts.map +1 -0
  217. package/dist/utils/scrollElementIntoViewById.test.js +31 -0
  218. package/dist/utils/scrollElementIntoViewById.test.js.map +1 -0
  219. package/package.json +1 -1
  220. package/src/components/avatar/Avatar.stories.tsx +8 -0
  221. package/src/components/avatar/Avatar.tsx +3 -3
  222. package/src/components/badge/Badge.stories.tsx +74 -0
  223. package/src/components/badge/Badge.test.tsx +28 -0
  224. package/src/components/badge/Badge.tsx +35 -0
  225. package/src/components/badge/badge.scss +86 -0
  226. package/src/components/card/Card.tsx +1 -1
  227. package/src/components/combobox/Combobox.stories.tsx +340 -0
  228. package/src/components/combobox/Combobox.test.tsx +1160 -0
  229. package/src/components/combobox/Combobox.tsx +434 -0
  230. package/src/components/combobox/ComboboxButtonTrigger.tsx +195 -0
  231. package/src/components/combobox/ComboboxListbox.tsx +224 -0
  232. package/src/components/combobox/ComboboxOptionRow.tsx +128 -0
  233. package/src/components/combobox/ComboboxTrigger.tsx +134 -0
  234. package/src/components/combobox/buildListboxDisplayOptions.test.ts +24 -0
  235. package/src/components/combobox/buildListboxDisplayOptions.ts +12 -0
  236. package/src/components/combobox/combobox.scss +390 -0
  237. package/src/components/combobox/comboboxKeyboardTypes.ts +45 -0
  238. package/src/components/combobox/highlightLabel.tsx +42 -0
  239. package/src/components/combobox/normaliseComboboxQuery.ts +1 -0
  240. package/src/components/combobox/types.ts +53 -0
  241. package/src/components/combobox/useChipSelection.ts +53 -0
  242. package/src/components/combobox/useComboboxChipKeyboard.test.tsx +141 -0
  243. package/src/components/combobox/useComboboxChipKeyboard.ts +121 -0
  244. package/src/components/combobox/useComboboxKeyboard.ts +108 -0
  245. package/src/components/combobox/useComboboxListboxDom.ts +36 -0
  246. package/src/components/combobox/useComboboxListboxKeyboard.test.tsx +186 -0
  247. package/src/components/combobox/useComboboxListboxKeyboard.ts +172 -0
  248. package/src/components/combobox/useComboboxPopoverBehavior.ts +179 -0
  249. package/src/components/combobox/useComboboxState.ts +232 -0
  250. package/src/components/combobox/useElementWidth.ts +40 -0
  251. package/src/components/combobox/useVisibleChips.test.tsx +91 -0
  252. package/src/components/combobox/useVisibleChips.ts +100 -0
  253. package/src/components/dot/Dot.stories.tsx +41 -0
  254. package/src/components/dot/Dot.test.tsx +21 -0
  255. package/src/components/dot/Dot.tsx +18 -0
  256. package/src/components/dot/dot.scss +35 -0
  257. package/src/components/formField/FormField.stories.tsx +30 -1
  258. package/src/components/formField/FormField.test.tsx +20 -0
  259. package/src/components/formField/FormField.tsx +11 -5
  260. package/src/components/formField/inputs/number/numberInput.scss +12 -4
  261. package/src/components/icon/allowedIcons.tsx +2 -0
  262. package/src/components/pill/pill.scss +4 -6
  263. package/src/components/singleUser/SingleUser.stories.tsx +63 -0
  264. package/src/components/singleUser/SingleUser.test.tsx +61 -0
  265. package/src/components/singleUser/SingleUser.tsx +45 -0
  266. package/src/components/singleUser/singleUser.scss +14 -0
  267. package/src/components/tag/Tag.stories.tsx +88 -6
  268. package/src/components/tag/Tag.test.tsx +110 -44
  269. package/src/components/tag/Tag.tsx +38 -14
  270. package/src/components/tag/tag.scss +45 -30
  271. package/src/components/toggle/Toggle.stories.tsx +239 -0
  272. package/src/components/toggle/Toggle.test.tsx +66 -0
  273. package/src/components/toggle/Toggle.tsx +12 -0
  274. package/src/components/toggle/toggle.scss +126 -0
  275. package/src/index.scss +5 -0
  276. package/src/index.ts +47 -31
  277. package/src/mocks/comboboxStoryOptions.ts +25 -0
  278. package/src/tokens.scss +33 -4
  279. package/src/utils/isSelectAllChord.test.ts +24 -0
  280. package/src/utils/isSelectAllChord.ts +8 -0
  281. package/src/utils/nextCircularIndex.test.ts +26 -0
  282. package/src/utils/nextCircularIndex.ts +15 -0
  283. package/src/utils/scrollElementIntoViewById.test.ts +38 -0
  284. package/src/utils/scrollElementIntoViewById.ts +20 -0
  285. package/tokens/json/Arbor.json +3828 -3704
@@ -1,16 +1,98 @@
1
- import type { Meta } from '@storybook/react-vite';
2
- import { Tag } from './Tag';
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
+ import { fn } from '@storybook/test';
3
+ import { Dot } from 'Components/dot/Dot';
4
+ import { Icon } from 'Components/icon/Icon';
5
+ import { Tag, type TagColor } from './Tag';
3
6
 
4
7
  const meta: Meta<typeof Tag> = {
5
8
  tags: ['autodocs'],
6
9
  title: 'Components/Tag',
7
10
  component: Tag,
11
+ parameters: {
12
+ docs: {
13
+ description: {
14
+ component:
15
+ 'Displays compact tag content with optional start/end slots and removable actions. Removable tags keep their remove button in the tab order by default; composite parents such as Combobox can pass `removeButtonTabIndex={-1}` for roving tab focus.',
16
+ },
17
+ },
18
+ },
8
19
  };
9
20
 
10
- export const Default = {
21
+ export default meta;
22
+
23
+ type Story = StoryObj<typeof Tag>;
24
+
25
+ const withDescription = (story: Story, description: string): Story => ({
26
+ ...story,
27
+ parameters: {
28
+ ...story.parameters,
29
+ docs: {
30
+ ...story.parameters?.docs,
31
+ description: {
32
+ story: description,
33
+ },
34
+ },
35
+ },
36
+ });
37
+
38
+ export const Default: Story = withDescription({
39
+ args: { children: 'Tag content' },
40
+ }, 'Shows the default neutral Tag with plain text content.');
41
+
42
+ export const WithColour: Story = withDescription({
43
+ args: { children: 'Blue tag', color: 'blue' },
44
+ }, 'Applies one of the semantic colour variants to the Tag.');
45
+
46
+ export const Selected: Story = withDescription({
47
+ args: { children: 'Selected tag', color: 'blue', selected: true },
48
+ }, 'Shows the selected treatment, which intentionally prioritises selection styling over the base colour variant.');
49
+
50
+ export const WithDot: Story = withDescription({
11
51
  args: {
12
- text: 'Tag content',
52
+ children: 'With dot',
53
+ slotStart: <Dot colour="purple" />,
13
54
  },
14
- };
55
+ }, 'Uses `slotStart` to render a decorative dot before the label.');
15
56
 
16
- export default meta;
57
+ export const WithIcon: Story = withDescription({
58
+ args: {
59
+ children: 'Alice Johnson',
60
+ slotStart: <Icon name="user" size={12} />,
61
+ },
62
+ }, 'Uses `slotStart` to show an icon before the Tag label.');
63
+
64
+ export const Removable: Story = withDescription({
65
+ args: {
66
+ children: 'Removable tag',
67
+ onRemove: fn(),
68
+ removeLabel: 'Remove removable tag',
69
+ },
70
+ }, 'Demonstrates a removable Tag with the default close affordance and a custom accessible remove label. The remove button is tabbable by default.');
71
+
72
+ export const RemovableWithIcon: Story = withDescription({
73
+ args: {
74
+ children: 'Alice Johnson',
75
+ slotStart: <Icon name="user" size={12} />,
76
+ onRemove: fn(),
77
+ removeLabel: 'Remove Alice Johnson',
78
+ },
79
+ }, 'Combines a leading icon with the removable Tag interaction.');
80
+
81
+ export const WithSlotEnd: Story = withDescription({
82
+ args: {
83
+ children: 'Custom end',
84
+ slotEnd: <Icon name="chevron-down" size={12} />,
85
+ },
86
+ }, 'Uses `slotEnd` to render trailing custom content.');
87
+
88
+ const allColours: TagColor[] = ['neutral', 'orange', 'blue', 'green', 'purple', 'teal', 'salmon', 'yellow'];
89
+
90
+ export const AllColours: Story = withDescription({
91
+ render: () => (
92
+ <div style={{ display: 'flex', flexWrap: 'wrap', gap: 8 }}>
93
+ {allColours.map(color => (
94
+ <Tag key={color} color={color}>{color}</Tag>
95
+ ))}
96
+ </div>
97
+ ),
98
+ }, 'Displays every available Tag colour variant side by side.');
@@ -1,65 +1,131 @@
1
- import { describe, expect, test } from 'vitest';
1
+ import { describe, expect, test, vi } from 'vitest';
2
2
  import { render, screen } from '@testing-library/react';
3
- import { Tag } from './Tag';
3
+ import userEvent from '@testing-library/user-event';
4
4
  import '@testing-library/jest-dom/vitest';
5
+ import { Tag } from './Tag';
6
+ import { Dot } from 'Components/dot/Dot';
5
7
 
6
8
  describe('Tag', () => {
7
- test('Tag says hello', () => {
8
- render(<Tag text="Hello I'm a Pill!" />);
9
- expect(screen.getByText("Hello I'm a Pill!")).toBeInTheDocument();
9
+ test('renders children as text content', () => {
10
+ render(<Tag>Hello</Tag>);
11
+ expect(screen.getByText('Hello')).toBeInTheDocument();
12
+ });
13
+
14
+ test('has default color neutral', () => {
15
+ const { container } = render(<Tag>Neutral</Tag>);
16
+ expect(container.querySelector('.ds-tag')).toHaveClass('ds-tag--neutral');
17
+ });
18
+
19
+ test('applies custom color class', () => {
20
+ const { container } = render(<Tag color="blue">Blue</Tag>);
21
+ expect(container.querySelector('.ds-tag')).toHaveClass('ds-tag--blue');
10
22
  });
11
23
 
12
- test('Tag has default color neutral', () => {
13
- render(<Tag text="Hello I'm a Pill!" />);
14
- expect(screen.getByText("Hello I'm a Pill!")).toHaveClass('ds-tag--neutral');
24
+ test('applies selected class when selected is true', () => {
25
+ const { container } = render(<Tag selected>Selected</Tag>);
26
+ expect(container.querySelector('.ds-tag')).toHaveClass('ds-tag--selected');
15
27
  });
16
28
 
17
- test('Tag has custom color', () => {
18
- render(<Tag text="Hello I'm a Pill!" color="blue" />);
19
- expect(screen.getByText("Hello I'm a Pill!")).toHaveClass('ds-tag--blue');
29
+ describe('slotStart', () => {
30
+ test('renders slotStart content', () => {
31
+ const { container } = render(<Tag slotStart={<Dot colour="purple" />}>With dot</Tag>);
32
+ expect(container.querySelector('.ds-tag__slot-start .ds-dot--purple')).toBeInTheDocument();
33
+ expect(container.querySelector('.ds-tag__slot-start .ds-dot')).toHaveAttribute('aria-hidden', 'true');
34
+ });
35
+
36
+ test('does not render slot-start wrapper when slotStart is not provided', () => {
37
+ const { container } = render(<Tag>No slot</Tag>);
38
+ expect(container.querySelector('.ds-tag__slot-start')).not.toBeInTheDocument();
39
+ });
40
+ });
41
+
42
+ describe('slotEnd', () => {
43
+ test('renders slotEnd content', () => {
44
+ const { container } = render(<Tag slotEnd={<span>End</span>}>With end</Tag>);
45
+ expect(container.querySelector('.ds-tag__slot-end')).toHaveTextContent('End');
46
+ });
47
+
48
+ test('does not render slot-end wrapper when slotEnd is not provided', () => {
49
+ const { container } = render(<Tag>No slot</Tag>);
50
+ expect(container.querySelector('.ds-tag__slot-end')).not.toBeInTheDocument();
51
+ });
20
52
  });
21
53
 
22
- describe('dot functionality', () => {
23
- test('Tag does not render dot when dotColour is not provided', () => {
24
- render(<Tag text="No dot here" />);
25
- expect(screen.queryByLabelText(/dot/i)).not.toBeInTheDocument();
54
+ describe('onRemove', () => {
55
+ test('renders remove button when onRemove is provided', () => {
56
+ render(<Tag onRemove={() => {}}>Removable</Tag>);
57
+ expect(screen.getByRole('button', { name: 'Remove' })).toBeInTheDocument();
58
+ });
59
+
60
+ test('uses a custom remove label when provided', () => {
61
+ render(
62
+ <Tag onRemove={() => {}} removeLabel="Remove Alice Johnson">
63
+ Removable
64
+ </Tag>,
65
+ );
66
+ expect(screen.getByRole('button', { name: 'Remove Alice Johnson' })).toBeInTheDocument();
67
+ });
68
+
69
+ test('keeps the remove button in the tab order by default', () => {
70
+ render(<Tag onRemove={() => {}}>Removable</Tag>);
71
+ expect(screen.getByRole('button', { name: 'Remove' })).toHaveAttribute('tabindex', '0');
26
72
  });
27
73
 
28
- test('Tag renders dot when dotColour is provided', () => {
29
- render(<Tag text="Has dot" dotColour="purple" />);
30
- expect(screen.getByLabelText('purple dot')).toBeInTheDocument();
74
+ test('allows composite parents to remove the button from the tab order', () => {
75
+ render(
76
+ <Tag onRemove={() => {}} removeButtonTabIndex={-1}>
77
+ Removable
78
+ </Tag>,
79
+ );
80
+ expect(screen.getByRole('button', { name: 'Remove' })).toHaveAttribute('tabindex', '-1');
31
81
  });
32
82
 
33
- test('Tag dot has correct color class', () => {
34
- render(<Tag text="Purple dot" dotColour="purple" />);
35
- const dot = screen.getByLabelText('purple dot');
36
- expect(dot).toHaveClass('ds-tag__dot');
37
- expect(dot).toHaveClass('ds-tag__dot--purple');
83
+ test('does not render remove button when onRemove is not provided', () => {
84
+ render(<Tag>Static</Tag>);
85
+ expect(screen.queryByRole('button', { name: 'Remove' })).not.toBeInTheDocument();
38
86
  });
39
87
 
40
- test('Tag dot renders with each available colour', () => {
41
- const colours: Array<'purple' | 'salmon' | 'teal' | 'yellow' | 'green' | 'orange' | 'blue'> = [
42
- 'purple',
43
- 'salmon',
44
- 'teal',
45
- 'yellow',
46
- 'green',
47
- 'orange',
48
- 'blue',
49
- ];
50
-
51
- colours.forEach((colour) => {
52
- const { unmount } = render(<Tag text={`${colour} dot`} dotColour={colour} />);
53
- const dot = screen.getByLabelText(`${colour} dot`);
54
- expect(dot).toHaveClass(`ds-tag__dot--${colour}`);
55
- unmount();
56
- });
88
+ test('fires onRemove callback when close button is clicked', async () => {
89
+ const onRemove = vi.fn();
90
+ render(<Tag onRemove={onRemove}>Removable</Tag>);
91
+
92
+ await userEvent.click(screen.getByRole('button', { name: 'Remove' }));
93
+ expect(onRemove).toHaveBeenCalledOnce();
94
+ });
95
+
96
+ test('stops event propagation on remove click', async () => {
97
+ const onRemove = vi.fn();
98
+ const outerClick = vi.fn();
99
+ render(
100
+ <div onClick={outerClick}>
101
+ <Tag onRemove={onRemove}>Removable</Tag>
102
+ </div>,
103
+ );
104
+
105
+ await userEvent.click(screen.getByRole('button', { name: 'Remove' }));
106
+ expect(onRemove).toHaveBeenCalledOnce();
107
+ expect(outerClick).not.toHaveBeenCalled();
57
108
  });
109
+ });
110
+
111
+ describe('combined slots', () => {
112
+ test('renders slotStart, children, slotEnd, and remove button in correct order', () => {
113
+ const { container } = render(
114
+ <Tag
115
+ slotStart={<span>S</span>}
116
+ slotEnd={<span>E</span>}
117
+ onRemove={() => {}}
118
+ >
119
+ Label
120
+ </Tag>,
121
+ );
58
122
 
59
- test('Tag renders both dot and text correctly', () => {
60
- render(<Tag text="Text with dot" dotColour="teal" />);
61
- expect(screen.getByText('Text with dot')).toBeInTheDocument();
62
- expect(screen.getByLabelText('teal dot')).toBeInTheDocument();
123
+ const tag = container.querySelector('.ds-tag')!;
124
+ const children = Array.from(tag.children);
125
+ expect(children[0]).toHaveClass('ds-tag__slot-start');
126
+ expect(children[1]).toHaveClass('ds-tag__label');
127
+ expect(children[2]).toHaveClass('ds-tag__slot-end');
128
+ expect(children[3]).toHaveClass('ds-tag__remove');
63
129
  });
64
130
  });
65
131
  });
@@ -1,4 +1,5 @@
1
1
  import classNames from 'classnames';
2
+ import { Icon } from 'Components/icon/Icon';
2
3
 
3
4
  export type TagColor = 'neutral'
4
5
  | 'orange'
@@ -9,26 +10,49 @@ export type TagColor = 'neutral'
9
10
  | 'salmon'
10
11
  | 'yellow';
11
12
 
12
- export type DotColour = 'purple' | 'salmon' | 'teal' | 'yellow' | 'green' | 'orange' | 'blue';
13
-
14
- type TagProps = {
15
- text: string;
13
+ export type TagProps = {
14
+ children: React.ReactNode;
16
15
  color?: TagColor;
17
- dotColour?: DotColour;
16
+ selected?: boolean;
17
+ slotStart?: React.ReactNode;
18
+ slotEnd?: React.ReactNode;
19
+ onRemove?: () => void;
20
+ removeLabel?: string;
21
+ removeButtonTabIndex?: 0 | -1;
18
22
  };
19
23
 
20
- export const Tag = ({ text, color = 'neutral', dotColour }: TagProps) => {
24
+ export const Tag = ({
25
+ children,
26
+ color = 'neutral',
27
+ selected = false,
28
+ slotStart,
29
+ slotEnd,
30
+ onRemove,
31
+ removeLabel = 'Remove',
32
+ removeButtonTabIndex = 0,
33
+ }: TagProps): React.JSX.Element => {
21
34
  return (
22
- <span
23
- className={classNames('ds-tag', `ds-tag--${color}`)}
35
+ <span className={classNames('ds-tag', `ds-tag--${color}`, {
36
+ 'ds-tag--selected': selected,
37
+ })}
24
38
  >
25
- {dotColour && (
26
- <span
27
- aria-label={`${dotColour} dot`}
28
- className={classNames('ds-tag__dot', `ds-tag__dot--${dotColour}`)}
29
- />
39
+ {slotStart && <span className="ds-tag__slot-start">{slotStart}</span>}
40
+ <span className="ds-tag__label">{children}</span>
41
+ {slotEnd && <span className="ds-tag__slot-end">{slotEnd}</span>}
42
+ {onRemove && (
43
+ <button
44
+ type="button"
45
+ className="ds-tag__remove"
46
+ aria-label={removeLabel}
47
+ onClick={(e) => {
48
+ e.stopPropagation();
49
+ onRemove();
50
+ }}
51
+ tabIndex={removeButtonTabIndex}
52
+ >
53
+ <Icon name="x" size={12} />
54
+ </button>
30
55
  )}
31
- {text}
32
56
  </span>
33
57
  );
34
58
  };
@@ -1,21 +1,24 @@
1
1
  .ds-tag {
2
- display: flex;
3
- padding: var(--tag-spacing-vertical) var(--tag-spacing-horizontal);
2
+ display: inline-flex;
3
+ padding: 0 var(--tag-spacing-horizontal);
4
4
  align-items: center;
5
5
  gap: var(--tag-spacing-gap-horizontal);
6
6
  border-radius: var(--tag-radius);
7
+ border: var(--border-weight) solid transparent;
8
+ box-sizing: border-box;
7
9
  flex-grow: 0;
8
10
  width: fit-content;
9
-
10
- /* typography/body/p1-reg */
11
+ white-space: nowrap;
12
+ max-width: 100%;
13
+ height: var(--tag-height);
11
14
  font-style: normal;
12
- line-height: 150%; /* 19.5px */
15
+ line-height: 150%;
13
16
 
14
17
  &--neutral {
15
18
  color: var(--tag-neutral-color-text);
16
19
  background: var(--tag-neutral-color-background);
17
20
  }
18
-
21
+
19
22
  &--orange {
20
23
  color: var(--tag-category-orange-color-text);
21
24
  background: var(--tag-category-orange-color-background);
@@ -50,38 +53,50 @@
50
53
  color: var(--tag-category-yellow-color-text);
51
54
  background: var(--tag-category-yellow-color-background);
52
55
  }
53
- }
54
56
 
55
- .ds-tag__dot {
56
- width: 10px;
57
- height: 10px;
58
- border-radius: 50%;
59
-
60
- &--purple {
61
- background-color: var(--color-extended-colours-purple-500);
57
+ &--selected {
58
+ background: var(--tag-selected-color-background);
59
+ color: var(--tag-neutral-color-text);
60
+ border-color: var(--tag-selected-color-border);
62
61
  }
63
62
 
64
- &--salmon {
65
- background-color: var(--color-extended-colours-salmon-500);
66
- }
63
+ }
67
64
 
68
- &--teal {
69
- background-color: var(--color-extended-colours-teal-500);
70
- }
65
+ .ds-tag__slot-start {
66
+ display: flex;
67
+ align-items: center;
68
+ flex-shrink: 0;
69
+ }
71
70
 
72
- &--yellow {
73
- background-color: var(--color-extended-colours-yellow-500);
74
- }
71
+ .ds-tag__label {
72
+ overflow: hidden;
73
+ text-overflow: ellipsis;
74
+ }
75
75
 
76
- &--green {
77
- background-color: var(--color-extended-colours-green-500);
78
- }
76
+ .ds-tag__slot-end {
77
+ display: flex;
78
+ align-items: center;
79
+ flex-shrink: 0;
80
+ }
79
81
 
80
- &--orange {
81
- background-color: var(--color-extended-colours-orange-500);
82
+ .ds-tag__remove {
83
+ display: flex;
84
+ align-items: center;
85
+ justify-content: center;
86
+ border: none;
87
+ background: transparent;
88
+ padding: 0;
89
+ cursor: pointer;
90
+ color: inherit;
91
+ border-radius: 50%;
92
+ flex-shrink: 0;
93
+
94
+ &:hover {
95
+ background-color: var(--color-grey-200);
82
96
  }
83
97
 
84
- &--blue {
85
- background-color: var(--color-extended-colours-blue-500);
98
+ &:disabled {
99
+ cursor: not-allowed;
100
+ opacity: 0.5;
86
101
  }
87
102
  }