@pnx-mixtape/mxds 0.1.0-rc.0 → 0.1.0-rc.2

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 (204) hide show
  1. package/.storybook/decorators.ts +15 -0
  2. package/.storybook/preview.ts +4 -1
  3. package/.storybook/themes/campus-vibrant.css +181 -0
  4. package/.storybook/themes/civic-formal.css +246 -0
  5. package/.storybook/{theme-demo.css → themes/demo.css} +14 -7
  6. package/.storybook/themes/health-services.css +255 -0
  7. package/.storybook/themes/research-faculty.css +169 -0
  8. package/dist/build/accordion.css +1 -1
  9. package/dist/build/base.css +1 -1
  10. package/dist/build/button.css +1 -1
  11. package/dist/build/chunks/DropMenu-1baQc8Yc.js +1 -0
  12. package/dist/build/chunks/InPageNavigation-CVieEl9u.js +1 -0
  13. package/dist/build/chunks/Popover-Cxg-s5Jn.js +1 -0
  14. package/dist/build/chunks/Sticky-cWUVSOXN.js +1 -0
  15. package/dist/build/chunks/{Tabs-Dm8g301e.js → Tabs-DA_Ez8pv.js} +2 -2
  16. package/dist/build/chunks/polyfills-C7pRt54M.js +1 -0
  17. package/dist/build/constants.css +1 -1
  18. package/dist/build/drop-menu.css +1 -1
  19. package/dist/build/drop-menu.entry.js +1 -1
  20. package/dist/build/elements.js +1 -1
  21. package/dist/build/form.css +1 -1
  22. package/dist/build/grid.css +1 -1
  23. package/dist/build/header.css +1 -1
  24. package/dist/build/icon.css +1 -1
  25. package/dist/build/in-page-alert.css +1 -1
  26. package/dist/build/in-page-navigation.entry.js +1 -1
  27. package/dist/build/navigation.css +1 -1
  28. package/dist/build/pagination.css +1 -1
  29. package/dist/build/popover.entry.js +1 -1
  30. package/dist/build/print.css +1 -1
  31. package/dist/build/progress-indicator.css +1 -0
  32. package/dist/build/react.js +1 -1
  33. package/dist/build/side-navigation.css +1 -1
  34. package/dist/build/sidebar.css +1 -1
  35. package/dist/build/steps.css +1 -1
  36. package/dist/build/sticky.entry.js +1 -1
  37. package/dist/build/tabs.entry.js +1 -1
  38. package/dist/build/tag.css +1 -1
  39. package/dist/build/utilities.css +1 -1
  40. package/dist/types/Atom/DefinitionList/DefinitionList.d.ts +3 -2
  41. package/dist/types/Atom/DefinitionList/DefinitionList.d.ts.map +1 -1
  42. package/dist/types/Atom/Heading/Heading.d.ts.map +1 -1
  43. package/dist/types/Atom/List/List.d.ts +10 -0
  44. package/dist/types/Atom/List/List.d.ts.map +1 -0
  45. package/dist/types/Component/DropMenu/Elements/DropMenu.d.ts.map +1 -1
  46. package/dist/types/Component/InPageNavigation/Elements/InPageNavigation.d.ts +2 -2
  47. package/dist/types/Component/InPageNavigation/Elements/InPageNavigation.d.ts.map +1 -1
  48. package/dist/types/Component/InPageNavigation/Hooks/useInPageNavigation.d.ts.map +1 -1
  49. package/dist/types/Component/ProgressIndicator/ProgressIndicator.d.ts +9 -0
  50. package/dist/types/Component/ProgressIndicator/ProgressIndicator.d.ts.map +1 -0
  51. package/dist/types/Component/Tabs/Elements/Tabs.d.ts.map +1 -1
  52. package/dist/types/Component/Tag/Tag.d.ts +4 -2
  53. package/dist/types/Component/Tag/Tag.d.ts.map +1 -1
  54. package/dist/types/Form/Autocomplete/Autocomplete.d.ts.map +1 -1
  55. package/dist/types/Form/Checkbox/FormCheckbox.d.ts.map +1 -1
  56. package/dist/types/Form/CheckboxCard/CheckboxCard.d.ts +23 -0
  57. package/dist/types/Form/CheckboxCard/CheckboxCard.d.ts.map +1 -0
  58. package/dist/types/Form/Radio/FormRadio.d.ts.map +1 -1
  59. package/dist/types/Form/RadioCard/RadioCard.d.ts +16 -0
  60. package/dist/types/Form/RadioCard/RadioCard.d.ts.map +1 -0
  61. package/dist/types/Form/Select/FormSelect.d.ts.map +1 -1
  62. package/dist/types/Form/TextInput/FormText.d.ts.map +1 -1
  63. package/dist/types/Form/Textarea/FormTextarea.d.ts.map +1 -1
  64. package/dist/types/enums.d.ts +15 -0
  65. package/dist/types/enums.d.ts.map +1 -1
  66. package/dist/types/react.d.ts +4 -0
  67. package/dist/types/react.d.ts.map +1 -1
  68. package/package.json +7 -8
  69. package/src/Atom/Background/_background.css +2 -2
  70. package/src/Atom/Blockquote/_blockquote.css +9 -2
  71. package/src/Atom/Button/README.md +1 -0
  72. package/src/Atom/Button/_buttons-styles.css +6 -6
  73. package/src/Atom/Button/_buttons.css +6 -3
  74. package/src/Atom/DefinitionList/DefinitionList.stories.ts +39 -2
  75. package/src/Atom/DefinitionList/DefinitionList.stories.tsx +36 -0
  76. package/src/Atom/DefinitionList/DefinitionList.tsx +4 -1
  77. package/src/Atom/DefinitionList/DefinitionListItem.stories.ts +1 -1
  78. package/src/Atom/DefinitionList/README.md +43 -0
  79. package/src/Atom/DefinitionList/__snapshots__/DefinitionList.stories.ts.snap +37 -3
  80. package/src/Atom/DefinitionList/__snapshots__/DefinitionList.stories.tsx.snap +2 -0
  81. package/src/Atom/DefinitionList/__snapshots__/DefinitionListItem.stories.ts.snap +3 -3
  82. package/src/Atom/DefinitionList/definition-list-item.twig +1 -0
  83. package/src/Atom/DefinitionList/definition-list.twig +6 -1
  84. package/src/Atom/Heading/Heading.tsx +6 -0
  85. package/src/Atom/Heading/_headings.css +2 -1
  86. package/src/Atom/Icon/_extended-set.css +30 -30
  87. package/src/Atom/Icon/_icon.css +37 -36
  88. package/src/Atom/Image/README.md +2 -2
  89. package/src/Atom/Image/_image.css +12 -6
  90. package/src/Atom/List/List.stories.ts +102 -0
  91. package/src/Atom/List/List.stories.tsx +103 -0
  92. package/src/Atom/List/List.tsx +22 -0
  93. package/src/Atom/List/README.md +150 -0
  94. package/src/Atom/List/__snapshots__/List.stories.ts.snap +43 -0
  95. package/src/Atom/List/__snapshots__/List.stories.tsx.snap +13 -0
  96. package/src/Atom/List/_list.css +123 -0
  97. package/src/Atom/List/list.twig +5 -0
  98. package/src/Atom/Media/Media.stories.ts +1 -1
  99. package/src/Atom/Media/__snapshots__/Media.stories.ts.snap +2 -2
  100. package/src/Atom/Spacing/_spacing.css +24 -24
  101. package/src/Atom/Table/__snapshots__/Table.stories.ts.snap +24 -4
  102. package/src/Atom/Table/_table.css +15 -3
  103. package/src/Atom/Table/table.twig +6 -1
  104. package/src/Atom/Text/__snapshots__/Text.stories.ts.snap +3 -2
  105. package/src/Atom/Text/_text-styles.css +21 -1
  106. package/src/Atom/Text/twig/text-styles-example.twig +3 -2
  107. package/src/Atom/_base.css +6 -0
  108. package/src/Atom/_flow.css +1 -1
  109. package/src/Atom/_generic.css +18 -11
  110. package/src/Component/Accordion/accordion.css +2 -0
  111. package/src/Component/Card/__snapshots__/Card.stories.ts.snap +2 -2
  112. package/src/Component/Card/card.twig +1 -1
  113. package/src/Component/Carousel/Carousel.stories.ts +46 -23
  114. package/src/Component/DropMenu/Elements/DropMenu.ts +10 -1
  115. package/src/Component/InPageAlert/in-page-alert.css +2 -1
  116. package/src/Component/InPageNavigation/Elements/InPageNavigation.ts +10 -5
  117. package/src/Component/InPageNavigation/Hooks/useInPageNavigation.ts +10 -5
  118. package/src/Component/InPageNavigation/InPageNavigation.stories.ts +33 -0
  119. package/src/Component/InPageNavigation/InPageNavigation.stories.tsx +52 -0
  120. package/src/Component/InPageNavigation/__snapshots__/InPageNavigation.stories.ts.snap +26 -0
  121. package/src/Component/InPageNavigation/__snapshots__/InPageNavigation.stories.tsx.snap +2 -0
  122. package/src/Component/InPageNavigation/twig/content-example-duplicate-headings.twig +20 -0
  123. package/src/Component/InPageNavigation/twig/in-page-navigation-story-duplicate-headings.twig +2 -0
  124. package/src/Component/ListItem/__snapshots__/ListItem.stories.ts.snap +4 -4
  125. package/src/Component/ListItem/list-item.twig +2 -2
  126. package/src/Component/Navigation/_navigation.css +1 -1
  127. package/src/Component/Pagination/Pagination.stories.ts +8 -4
  128. package/src/Component/Pagination/pagination.css +3 -3
  129. package/src/Component/ProgressIndicator/ProgressIndicator.stories.ts +36 -0
  130. package/src/Component/ProgressIndicator/ProgressIndicator.stories.tsx +30 -0
  131. package/src/Component/ProgressIndicator/ProgressIndicator.tsx +60 -0
  132. package/src/Component/ProgressIndicator/README.md +111 -0
  133. package/src/Component/ProgressIndicator/__snapshots__/ProgressIndicator.stories.ts.snap +67 -0
  134. package/src/Component/ProgressIndicator/__snapshots__/ProgressIndicator.stories.tsx.snap +7 -0
  135. package/src/Component/ProgressIndicator/progress-indicator.css +59 -0
  136. package/src/Component/ProgressIndicator/progress-indicator.twig +22 -0
  137. package/src/Component/Sticky/Elements/Sticky.ts +1 -1
  138. package/src/Component/Sticky/sticky.css +3 -3
  139. package/src/Component/Tabs/Elements/Tabs.ts +3 -1
  140. package/src/Component/Tabs/Tabs.stories.ts +55 -21
  141. package/src/Component/Tabs/__snapshots__/Tabs.stories.ts.snap +2 -2
  142. package/src/Component/Tag/README.md +52 -26
  143. package/src/Component/Tag/Tag.stories.tsx +24 -0
  144. package/src/Component/Tag/Tag.tsx +12 -2
  145. package/src/Component/Tag/TagItem.stories.ts +25 -0
  146. package/src/Component/Tag/__snapshots__/Tag.stories.ts.snap +2 -2
  147. package/src/Component/Tag/__snapshots__/Tag.stories.tsx.snap +2 -0
  148. package/src/Component/Tag/tag-item.twig +2 -2
  149. package/src/Component/Tag/tag.css +76 -2
  150. package/src/Form/Autocomplete/Autocomplete.stories.ts +8 -3
  151. package/src/Form/Autocomplete/Autocomplete.tsx +1 -4
  152. package/src/Form/Autocomplete/README.md +26 -26
  153. package/src/Form/Autocomplete/__snapshots__/Autocomplete.stories.ts.snap +1 -1
  154. package/src/Form/Autocomplete/__snapshots__/Autocomplete.stories.tsx.snap +1 -1
  155. package/src/Form/Autocomplete/autocomplete.twig +0 -2
  156. package/src/Form/Checkbox/FormCheckbox.tsx +2 -8
  157. package/src/Form/Checkbox/README.md +23 -18
  158. package/src/Form/Checkbox/input-checkbox.twig +0 -2
  159. package/src/Form/CheckboxCard/CheckboxCard.stories.ts +90 -0
  160. package/src/Form/CheckboxCard/CheckboxCard.stories.tsx +43 -0
  161. package/src/Form/CheckboxCard/CheckboxCard.tsx +102 -0
  162. package/src/Form/CheckboxCard/README.md +163 -0
  163. package/src/Form/CheckboxCard/__snapshots__/CheckboxCard.stories.ts.snap +63 -0
  164. package/src/Form/CheckboxCard/__snapshots__/CheckboxCard.stories.tsx.snap +7 -0
  165. package/src/Form/CheckboxCard/checkbox-card.twig +29 -0
  166. package/src/Form/FormItem/__snapshots__/FormItem.stories.ts.snap +2 -2
  167. package/src/Form/README.md +65 -13
  168. package/src/Form/Radio/FormRadio.tsx +1 -4
  169. package/src/Form/Radio/input-radio.twig +0 -2
  170. package/src/Form/RadioCard/README.md +146 -0
  171. package/src/Form/RadioCard/RadioCard.stories.ts +95 -0
  172. package/src/Form/RadioCard/RadioCard.stories.tsx +26 -0
  173. package/src/Form/RadioCard/RadioCard.tsx +59 -0
  174. package/src/Form/RadioCard/__snapshots__/RadioCard.stories.ts.snap +69 -0
  175. package/src/Form/RadioCard/__snapshots__/RadioCard.stories.tsx.snap +3 -0
  176. package/src/Form/RadioCard/radio-card.twig +20 -0
  177. package/src/Form/Select/FormSelect.tsx +1 -4
  178. package/src/Form/Select/input-select.twig +0 -2
  179. package/src/Form/TextInput/FormText.stories.tsx +14 -0
  180. package/src/Form/TextInput/FormText.tsx +1 -4
  181. package/src/Form/TextInput/__snapshots__/FormText.stories.tsx.snap +2 -0
  182. package/src/Form/TextInput/input-text.twig +0 -2
  183. package/src/Form/Textarea/FormTextarea.tsx +1 -4
  184. package/src/Form/Textarea/input-textarea.twig +0 -2
  185. package/src/Form/Validation.stories.ts +153 -0
  186. package/src/Form/__snapshots__/Validation.stories.ts.snap +130 -0
  187. package/src/Form/form.css +155 -28
  188. package/src/Layout/Grid/grid.css +4 -4
  189. package/src/Layout/Sidebar/__snapshots__/Sidebar.stories.ts.snap +5 -5
  190. package/src/Layout/Sidebar/sidebar.css +1 -0
  191. package/src/Layout/Sidebar/sidebar.twig +1 -1
  192. package/src/Utility/_layout-utils.css +8 -0
  193. package/src/Utility/print.css +50 -28
  194. package/src/Utility/utilities.css +2 -2
  195. package/src/constants.css +12 -2
  196. package/src/enums.ts +17 -0
  197. package/src/react.ts +4 -0
  198. package/tools/lib/extract-classnames.mjs +0 -0
  199. package/tools/lint-classnames.mjs +1 -1
  200. package/dist/build/chunks/DropMenu-Co9jZZ4S.js +0 -1
  201. package/dist/build/chunks/InPageNavigation-CzR_KZaz.js +0 -1
  202. package/dist/build/chunks/Popover-De_CQqcd.js +0 -1
  203. package/dist/build/chunks/Sticky-Kyjykb36.js +0 -1
  204. package/dist/build/chunks/polyfills-Dokbkxxk.js +0 -1
@@ -0,0 +1,67 @@
1
+ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
+
3
+ exports[`Last Step 1`] = `
4
+ "
5
+ <div class="mx-progress-indicator">
6
+ <p class="mx-progress-indicator__count">Step 5 of 5 — Review and submit</p>
7
+ <div class="mx-progress-indicator__track" aria-hidden="true">
8
+ <i class="mx-progress-indicator__marker mx-progress-indicator__marker--done"></i>
9
+ <i class="mx-progress-indicator__marker mx-progress-indicator__marker--done"></i>
10
+ <i class="mx-progress-indicator__marker mx-progress-indicator__marker--done"></i>
11
+ <i class="mx-progress-indicator__marker mx-progress-indicator__marker--done"></i>
12
+ <i class="mx-progress-indicator__marker mx-progress-indicator__marker--now"></i>
13
+ </div>
14
+ <ol class="mx-progress-indicator__steps">
15
+ <li class="mx-progress-indicator__step mx-progress-indicator__step--done">Your card</li>
16
+ <li class="mx-progress-indicator__step mx-progress-indicator__step--done">Your details</li>
17
+ <li class="mx-progress-indicator__step mx-progress-indicator__step--done">Your income</li>
18
+ <li class="mx-progress-indicator__step mx-progress-indicator__step--done">Evidence</li>
19
+ <li class="mx-progress-indicator__step" aria-current="step">Review and submit</li>
20
+ </ol>
21
+ </div>
22
+ "
23
+ `;
24
+
25
+ exports[`Middle Step 1`] = `
26
+ "
27
+ <div class="mx-progress-indicator">
28
+ <p class="mx-progress-indicator__count">Step 3 of 5 — Your income</p>
29
+ <div class="mx-progress-indicator__track" aria-hidden="true">
30
+ <i class="mx-progress-indicator__marker mx-progress-indicator__marker--done"></i>
31
+ <i class="mx-progress-indicator__marker mx-progress-indicator__marker--done"></i>
32
+ <i class="mx-progress-indicator__marker mx-progress-indicator__marker--now"></i>
33
+ <i class="mx-progress-indicator__marker"></i>
34
+ <i class="mx-progress-indicator__marker"></i>
35
+ </div>
36
+ <ol class="mx-progress-indicator__steps">
37
+ <li class="mx-progress-indicator__step mx-progress-indicator__step--done">Your card</li>
38
+ <li class="mx-progress-indicator__step mx-progress-indicator__step--done">Your details</li>
39
+ <li class="mx-progress-indicator__step" aria-current="step">Your income</li>
40
+ <li class="mx-progress-indicator__step">Evidence</li>
41
+ <li class="mx-progress-indicator__step">Review and submit</li>
42
+ </ol>
43
+ </div>
44
+ "
45
+ `;
46
+
47
+ exports[`Progress Indicator 1`] = `
48
+ "
49
+ <div class="mx-progress-indicator">
50
+ <p class="mx-progress-indicator__count">Step 1 of 5 — Your card</p>
51
+ <div class="mx-progress-indicator__track" aria-hidden="true">
52
+ <i class="mx-progress-indicator__marker mx-progress-indicator__marker--now"></i>
53
+ <i class="mx-progress-indicator__marker"></i>
54
+ <i class="mx-progress-indicator__marker"></i>
55
+ <i class="mx-progress-indicator__marker"></i>
56
+ <i class="mx-progress-indicator__marker"></i>
57
+ </div>
58
+ <ol class="mx-progress-indicator__steps">
59
+ <li class="mx-progress-indicator__step" aria-current="step">Your card</li>
60
+ <li class="mx-progress-indicator__step">Your details</li>
61
+ <li class="mx-progress-indicator__step">Your income</li>
62
+ <li class="mx-progress-indicator__step">Evidence</li>
63
+ <li class="mx-progress-indicator__step">Review and submit</li>
64
+ </ol>
65
+ </div>
66
+ "
67
+ `;
@@ -0,0 +1,7 @@
1
+ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
+
3
+ exports[`Last Step 1`] = `"<div class="mx-progress-indicator"><p class="mx-progress-indicator__count">Step 5 of 5 — Review and submit</p><div class="mx-progress-indicator__track" aria-hidden="true"><i class="mx-progress-indicator__marker mx-progress-indicator__marker--done"></i><i class="mx-progress-indicator__marker mx-progress-indicator__marker--done"></i><i class="mx-progress-indicator__marker mx-progress-indicator__marker--done"></i><i class="mx-progress-indicator__marker mx-progress-indicator__marker--done"></i><i class="mx-progress-indicator__marker mx-progress-indicator__marker--now"></i></div><ol class="mx-progress-indicator__steps"><li class="mx-progress-indicator__step mx-progress-indicator__step--done">Your card</li><li class="mx-progress-indicator__step mx-progress-indicator__step--done">Your details</li><li class="mx-progress-indicator__step mx-progress-indicator__step--done">Your income</li><li class="mx-progress-indicator__step mx-progress-indicator__step--done">Evidence</li><li class="mx-progress-indicator__step" aria-current="step">Review and submit</li></ol></div>"`;
4
+
5
+ exports[`Middle Step 1`] = `"<div class="mx-progress-indicator"><p class="mx-progress-indicator__count">Step 3 of 5 — Your income</p><div class="mx-progress-indicator__track" aria-hidden="true"><i class="mx-progress-indicator__marker mx-progress-indicator__marker--done"></i><i class="mx-progress-indicator__marker mx-progress-indicator__marker--done"></i><i class="mx-progress-indicator__marker mx-progress-indicator__marker--now"></i><i class="mx-progress-indicator__marker"></i><i class="mx-progress-indicator__marker"></i></div><ol class="mx-progress-indicator__steps"><li class="mx-progress-indicator__step mx-progress-indicator__step--done">Your card</li><li class="mx-progress-indicator__step mx-progress-indicator__step--done">Your details</li><li class="mx-progress-indicator__step" aria-current="step">Your income</li><li class="mx-progress-indicator__step">Evidence</li><li class="mx-progress-indicator__step">Review and submit</li></ol></div>"`;
6
+
7
+ exports[`Progress Indicator 1`] = `"<div class="mx-progress-indicator"><p class="mx-progress-indicator__count">Step 1 of 5 — Your card</p><div class="mx-progress-indicator__track" aria-hidden="true"><i class="mx-progress-indicator__marker mx-progress-indicator__marker--now"></i><i class="mx-progress-indicator__marker"></i><i class="mx-progress-indicator__marker"></i><i class="mx-progress-indicator__marker"></i><i class="mx-progress-indicator__marker"></i></div><ol class="mx-progress-indicator__steps"><li class="mx-progress-indicator__step" aria-current="step">Your card</li><li class="mx-progress-indicator__step">Your details</li><li class="mx-progress-indicator__step">Your income</li><li class="mx-progress-indicator__step">Evidence</li><li class="mx-progress-indicator__step">Review and submit</li></ol></div>"`;
@@ -0,0 +1,59 @@
1
+ /**
2
+ * ProgressIndicator
3
+ */
4
+
5
+ @layer design-system.components {
6
+ .mx-progress-indicator {
7
+ /* @public --gap */
8
+ display: grid;
9
+ gap: var(--gap, var(--spacing-xs));
10
+ }
11
+
12
+ .mx-progress-indicator__count {
13
+ margin: 0;
14
+ font-size: var(--font-size-s);
15
+ font-weight: var(--font-weight-bold);
16
+ color: var(--colour-foreground-alt);
17
+ }
18
+
19
+ .mx-progress-indicator__track {
20
+ /* @public --track-gap --marker-size --marker-colour --marker-colour-complete --marker-colour-active */
21
+ display: flex;
22
+ gap: var(--track-gap, var(--spacing-xxxs));
23
+ }
24
+
25
+ .mx-progress-indicator__marker {
26
+ flex: 1;
27
+ block-size: var(--marker-size, var(--spacing-xxxs));
28
+ border-radius: var(--border-radius);
29
+ background-color: var(--marker-colour, var(--colour-border));
30
+ }
31
+
32
+ .mx-progress-indicator__marker--done {
33
+ background-color: var(--marker-colour-complete, var(--colour-primary));
34
+ }
35
+
36
+ .mx-progress-indicator__marker--now {
37
+ background-color: var(--marker-colour-active, var(--colour-primary));
38
+ }
39
+
40
+ .mx-progress-indicator__steps {
41
+ display: flex;
42
+ flex-wrap: wrap;
43
+ gap: var(--spacing-xxxs) var(--spacing-m);
44
+ margin: 0;
45
+ padding-inline-start: 0;
46
+ list-style: none;
47
+ font-size: var(--font-size-xs);
48
+ color: var(--colour-foreground-alt);
49
+ }
50
+
51
+ .mx-progress-indicator__step[aria-current="step"] {
52
+ color: var(--marker-colour-active, var(--colour-primary));
53
+ font-weight: var(--font-weight-bold);
54
+ }
55
+
56
+ .mx-progress-indicator__step--done::before {
57
+ content: "✓ ";
58
+ }
59
+ }
@@ -0,0 +1,22 @@
1
+ {% set attributes = (attributes ?? create_attribute()).addClass('mx-progress-indicator') %}
2
+ {% set total = steps|length %}
3
+
4
+ <div{{ attributes }}>
5
+ <p class="mx-progress-indicator__count">Step {{ current }} of {{ total }} — {{ steps[current - 1] }}</p>
6
+ <div class="mx-progress-indicator__track" aria-hidden="true">
7
+ {% for step in steps %}
8
+ {% set markerClass = 'mx-progress-indicator__marker' %}
9
+ {% if loop.index < current %}
10
+ {% set markerClass = markerClass ~ ' mx-progress-indicator__marker--done' %}
11
+ {% elseif loop.index == current %}
12
+ {% set markerClass = markerClass ~ ' mx-progress-indicator__marker--now' %}
13
+ {% endif %}
14
+ <i class="{{ markerClass }}"></i>
15
+ {% endfor %}
16
+ </div>
17
+ <ol class="mx-progress-indicator__steps">
18
+ {% for step in steps %}
19
+ <li class="mx-progress-indicator__step{% if loop.index < current %} mx-progress-indicator__step--done{% endif %}"{% if loop.index == current %} aria-current="step"{% endif %}>{{ step }}</li>
20
+ {% endfor %}
21
+ </ol>
22
+ </div>
@@ -25,7 +25,7 @@ export default class Sticky extends HTMLElement {
25
25
  }
26
26
 
27
27
  connectedCallback(): void {
28
- this.offsetPropName = this.getAttribute("offsetpropname")
28
+ this.offsetPropName = this.getAttribute("offsetpropname") || "--sticky-offset"
29
29
  const options: IOOptions = {
30
30
  root: this.root,
31
31
  rootMargin: this.rootMargin,
@@ -16,18 +16,18 @@
16
16
 
17
17
  @layer design-system.components {
18
18
  .mx-sticky {
19
- @mixin sticky;
19
+ @add-mixin sticky;
20
20
  }
21
21
 
22
22
  .mx-sticky--m {
23
23
  @media (--medium-up) {
24
- @mixin sticky;
24
+ @add-mixin sticky;
25
25
  }
26
26
  }
27
27
 
28
28
  .mx-sticky--l {
29
29
  @media (--large-up) {
30
- @mixin sticky;
30
+ @add-mixin sticky;
31
31
  }
32
32
  }
33
33
 
@@ -227,14 +227,16 @@ export default class Tabs extends HTMLElement {
227
227
  }
228
228
 
229
229
  set active(id: string) {
230
- this.tabs.forEach(({ tab, panel }: TabsType, key: string): void => {
230
+ this.tabs.forEach(({ tab, button, panel }: TabsType, key: string): void => {
231
231
  if (key === id) {
232
232
  tab.setAttribute("aria-selected", "true")
233
+ button.setAttribute("aria-selected", "true")
233
234
  tab.setAttribute("tabindex", "0")
234
235
  panel.removeAttribute("inert")
235
236
  this.dropMenuTrigger.textContent = tab.textContent
236
237
  } else {
237
238
  tab.setAttribute("aria-selected", "false")
239
+ button.setAttribute("aria-selected", "false")
238
240
  tab.setAttribute("tabindex", "-1")
239
241
  panel.setAttribute("inert", "")
240
242
  }
@@ -1,5 +1,5 @@
1
1
  import { Meta, StoryObj } from "@storybook/html-vite"
2
- import { expect } from "storybook/test"
2
+ import { expect, waitFor } from "storybook/test"
3
3
  import Component from "./tabs.twig"
4
4
  import TabItem from "./tab-item.twig"
5
5
  import { HeadingTypes, Tabs as TabsType } from "@pnx-mixtape/ids-shape"
@@ -98,47 +98,81 @@ type Story = StoryObj<TabsType>
98
98
 
99
99
  export const Tabs: Story = {
100
100
  play: async ({ canvas, canvasElement, userEvent, step }) => {
101
- const tab1 = canvas.getByRole("tab", { name: "Funk" })
102
- const tab2 = canvas.getByRole("tab", { name: "Soul" })
103
- const tab3 = canvas.getByRole("tab", { name: "Motown" })
101
+ const isMobile = matchMedia("(max-width: 767px)").matches
102
+ const [button1, tab1] = canvas.getAllByRole("tab", { name: "Funk", hidden: true })
103
+ const [button2, tab2] = canvas.getAllByRole("tab", { name: "Soul", hidden: true })
104
+ const [button3, tab3] = canvas.getAllByRole("tab", { name: "Motown", hidden: true })
104
105
  const panel = (id: string): HTMLElement => canvasElement.querySelector(`#${id}`) as HTMLElement
105
106
 
106
107
  await step("Funk starts selected", async () => {
107
- await expect(tab1).toHaveAttribute("aria-selected", "true")
108
+ if (isMobile) {
109
+ await expect(button1).toHaveAttribute("aria-selected", "true")
110
+ } else {
111
+ await expect(tab1).toHaveAttribute("aria-selected", "true")
112
+ }
108
113
  await expect(panel("tab-1")).not.toHaveAttribute("inert")
109
114
  await expect(panel("tab-2")).toHaveAttribute("inert")
110
115
  })
111
116
 
112
117
  await step("Click selects a tab and moves focus to its panel", async () => {
113
- await userEvent.click(tab2)
114
- await expect(tab2).toHaveAttribute("aria-selected", "true")
115
- await expect(tab1).toHaveAttribute("aria-selected", "false")
118
+ if (isMobile) {
119
+ await userEvent.click(button2)
120
+ await waitFor(() => expect(button2).toHaveAttribute("aria-selected", "true"))
121
+ await expect(button1).toHaveAttribute("aria-selected", "false")
122
+ } else {
123
+ await userEvent.click(tab2)
124
+ await waitFor(() => expect(tab2).toHaveAttribute("aria-selected", "true"))
125
+ await expect(tab1).toHaveAttribute("aria-selected", "false")
126
+ }
116
127
  await expect(panel("tab-2")).not.toHaveAttribute("inert")
117
128
  await expect(panel("tab-1")).toHaveAttribute("inert")
118
129
  await expect(panel("tab-2")).toHaveFocus()
119
130
  })
120
131
 
121
132
  await step("ArrowRight/ArrowLeft move focus, with wraparound", async () => {
122
- tab2.focus()
123
- await userEvent.keyboard("{ArrowRight}")
124
- await expect(tab3).toHaveFocus()
125
- await userEvent.keyboard("{ArrowRight}")
126
- await expect(tab1).toHaveFocus()
127
- await userEvent.keyboard("{ArrowLeft}")
128
- await expect(tab3).toHaveFocus()
133
+ if (isMobile) {
134
+ button2.focus()
135
+ await userEvent.keyboard("{ArrowRight}")
136
+ await expect(button3).toHaveFocus()
137
+ await userEvent.keyboard("{ArrowRight}")
138
+ await expect(button1).toHaveFocus()
139
+ await userEvent.keyboard("{ArrowLeft}")
140
+ await expect(button3).toHaveFocus()
141
+ } else {
142
+ tab2.focus()
143
+ await userEvent.keyboard("{ArrowRight}")
144
+ await expect(tab3).toHaveFocus()
145
+ await userEvent.keyboard("{ArrowRight}")
146
+ await expect(tab1).toHaveFocus()
147
+ await userEvent.keyboard("{ArrowLeft}")
148
+ await expect(tab3).toHaveFocus()
149
+ }
129
150
  })
130
151
 
131
152
  await step("Home/End jump to first/last tab", async () => {
132
- await userEvent.keyboard("{Home}")
133
- await expect(tab1).toHaveFocus()
134
- await userEvent.keyboard("{End}")
135
- await expect(tab3).toHaveFocus()
153
+ if (isMobile) {
154
+ await userEvent.keyboard("{Home}")
155
+ await expect(button1).toHaveFocus()
156
+ await userEvent.keyboard("{End}")
157
+ await expect(button3).toHaveFocus()
158
+ } else {
159
+ await userEvent.keyboard("{Home}")
160
+ await expect(tab1).toHaveFocus()
161
+ await userEvent.keyboard("{End}")
162
+ await expect(tab3).toHaveFocus()
163
+ }
136
164
  })
137
165
 
138
166
  await step("Reset: reselect Funk and clear the ?tab= param", async () => {
139
- await userEvent.click(tab1)
140
- await expect(tab1).toHaveAttribute("aria-selected", "true")
167
+ if (isMobile) {
168
+ await userEvent.click(button1)
169
+ await expect(button1).toHaveAttribute("aria-selected", "true")
170
+ } else {
171
+ await userEvent.click(tab1)
172
+ await expect(tab1).toHaveAttribute("aria-selected", "true")
173
+ }
141
174
  window.history.replaceState({}, "", window.location.pathname)
175
+ await userEvent.click(canvasElement)
142
176
  })
143
177
  },
144
178
  }
@@ -3,7 +3,7 @@
3
3
  exports[`Tabs 1`] = `
4
4
  "
5
5
 
6
- <mx-tabs class="mx-tabs mx-tabs--lined" id="tab-example"><mx-dropmenu closeonclick="true"><button id="unique-3" class="mx-popover__trigger mx-button mx-icon mx-icon--chevron-down mx-icon--end" popovertarget="tab-example" style="anchor-name: --tab-example" hidden="">Funk</button><div class="mx-popover mx-drop-menu" id="tab-example" aria-describedby="unique-0" style="position-anchor: --tab-example" data-placement="bottom-start" popover="" role="tablist"><button type="button" role="tab" aria-controls="unique-1" aria-selected="false">Funk</button><button type="button" role="tab" aria-controls="unique-2" aria-selected="false">Soul</button><button type="button" role="tab" aria-controls="unique-3" aria-selected="false">Motown</button></div></mx-dropmenu><div class="mx-tabs__list" role="tablist"><button type="button" role="tab" aria-controls="unique-4" aria-selected="true" tabindex="0">Funk</button><button type="button" role="tab" aria-controls="unique-5" aria-selected="false" tabindex="-1">Soul</button><button type="button" role="tab" aria-controls="unique-6" aria-selected="false" tabindex="-1">Motown</button></div>
6
+ <mx-tabs class="mx-tabs mx-tabs--lined" id="tab-example"><mx-dropmenu closeonclick="true"><button id="unique-3" class="mx-popover__trigger mx-button mx-icon mx-icon--chevron-down mx-icon--end" popovertarget="tab-example" style="anchor-name: --tab-example" hidden="">Funk</button><div class="mx-popover mx-drop-menu" id="tab-example" aria-describedby="unique-0" style="position-anchor: --tab-example" data-placement="bottom-start" popover="" role="tablist"><button type="button" role="tab" aria-controls="unique-1" aria-selected="true">Funk</button><button type="button" role="tab" aria-controls="unique-2" aria-selected="false">Soul</button><button type="button" role="tab" aria-controls="unique-3" aria-selected="false">Motown</button></div></mx-dropmenu><div class="mx-tabs__list" role="tablist"><button type="button" role="tab" aria-controls="unique-4" aria-selected="true" tabindex="0">Funk</button><button type="button" role="tab" aria-controls="unique-5" aria-selected="false" tabindex="-1">Soul</button><button type="button" role="tab" aria-controls="unique-6" aria-selected="false" tabindex="-1">Motown</button></div>
7
7
  <div class="mx-tabs__panel mx-vertical-flow mx-container" id="unique-0" data-tab="Funk" role="tabpanel" tabindex="-1" aria-label="Funk">
8
8
  Funk is the ipsum dolor sit amet, consectetur adipiscing elit. Proin at finibus velit. Curabitur consequat, dolor venenatis ullamcorper commodo, quam mi iaculis sem, non varius massa risus et enim. Interdum et malesuada fames ac ante ipsum primis in faucibus. Phasellus non mi lorem. Morbi semper sagittis euismod. Vestibulum non ornare orci.
9
9
  </div>
@@ -23,7 +23,7 @@ exports[`With Heading 1`] = `
23
23
 
24
24
  <h2>Tabs heading</h2>
25
25
 
26
- <mx-tabs class="mx-tabs mx-tabs--lined" id="tab-example"><mx-dropmenu closeonclick="true"><button id="unique-3" class="mx-popover__trigger mx-button mx-icon mx-icon--chevron-down mx-icon--end" popovertarget="tab-example" style="anchor-name: --tab-example" hidden="">Funk</button><div class="mx-popover mx-drop-menu" id="tab-example" aria-describedby="unique-0" style="position-anchor: --tab-example" data-placement="bottom-start" popover="" role="tablist"><button type="button" role="tab" aria-controls="unique-1" aria-selected="false">Funk</button><button type="button" role="tab" aria-controls="unique-2" aria-selected="false">Soul</button><button type="button" role="tab" aria-controls="unique-3" aria-selected="false">Motown</button></div></mx-dropmenu><div class="mx-tabs__list" role="tablist"><button type="button" role="tab" aria-controls="unique-4" aria-selected="true" tabindex="0">Funk</button><button type="button" role="tab" aria-controls="unique-5" aria-selected="false" tabindex="-1">Soul</button><button type="button" role="tab" aria-controls="unique-6" aria-selected="false" tabindex="-1">Motown</button></div>
26
+ <mx-tabs class="mx-tabs mx-tabs--lined" id="tab-example"><mx-dropmenu closeonclick="true"><button id="unique-3" class="mx-popover__trigger mx-button mx-icon mx-icon--chevron-down mx-icon--end" popovertarget="tab-example" style="anchor-name: --tab-example" hidden="">Funk</button><div class="mx-popover mx-drop-menu" id="tab-example" aria-describedby="unique-0" style="position-anchor: --tab-example" data-placement="bottom-start" popover="" role="tablist"><button type="button" role="tab" aria-controls="unique-1" aria-selected="true">Funk</button><button type="button" role="tab" aria-controls="unique-2" aria-selected="false">Soul</button><button type="button" role="tab" aria-controls="unique-3" aria-selected="false">Motown</button></div></mx-dropmenu><div class="mx-tabs__list" role="tablist"><button type="button" role="tab" aria-controls="unique-4" aria-selected="true" tabindex="0">Funk</button><button type="button" role="tab" aria-controls="unique-5" aria-selected="false" tabindex="-1">Soul</button><button type="button" role="tab" aria-controls="unique-6" aria-selected="false" tabindex="-1">Motown</button></div>
27
27
  <div class="mx-tabs__panel mx-vertical-flow mx-container" id="unique-0" data-tab="Funk" role="tabpanel" tabindex="-1" aria-label="Funk">
28
28
  Funk is the ipsum dolor sit amet, consectetur adipiscing elit. Proin at finibus velit. Curabitur consequat, dolor venenatis ullamcorper commodo, quam mi iaculis sem, non varius massa risus et enim. Interdum et malesuada fames ac ante ipsum primis in faucibus. Phasellus non mi lorem. Morbi semper sagittis euismod. Vestibulum non ornare orci.
29
29
  </div>
@@ -39,14 +39,15 @@ A small inline label used to categorise content. Renders as plain text metadata,
39
39
 
40
40
  **tags.twig props** (`Tags` type from `ids-shape`):
41
41
 
42
- | Prop | Type | Description |
43
- | --------------- | ------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
44
- | `type` | `TagTypes` | Controls which markup is rendered per item. One of `text`, `link`, `checkbox`. Changing the type also changes the required shape of `items`. |
45
- | `items` | `string[] \| Link[] \| FormControl[]` | List of tag items. Shape depends on `type` — see below. |
46
- | `attributes` | `Attribute` | Extra attributes on the outer `<ul class="mx-tags">` |
47
- | `tagAttributes` | `Attribute` | Extra attributes on each `.mx-tag` element |
48
- | `dismissible` | `boolean` | When true, plain (`text`) tags render a remove button (`mx-tag--dismissible`). Wire up removal behaviour in the consuming app. |
49
- | `removeLabel` | `string` | Accessible label for the remove button. Defaults to `"Remove"`. |
42
+ | Prop | Type | Description |
43
+ | --------------- | ------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
44
+ | `type` | `TagTypes` | Controls which markup is rendered per item. One of `text`, `link`, `checkbox`. Changing the type also changes the required shape of `items`. |
45
+ | `items` | `string[] \| Link[] \| FormControl[]` | List of tag items. Shape depends on `type` — see below. |
46
+ | `attributes` | `Attribute` | Extra attributes on the outer `<ul class="mx-tags">` |
47
+ | `tagAttributes` | `Attribute` | Extra attributes on each `.mx-tag` element |
48
+ | `dismissible` | `boolean` | When true, plain (`text`) tags render a remove button (`mx-tag--dismissible`). Wire up removal behaviour in the consuming app. |
49
+ | `removeLabel` | `string` | Accessible label for the remove button. Defaults to `"Remove"`. |
50
+ | `variant` | `TagVariants` | Colour variant: `info`, `success`, `warning`, `error`, `featured`. Applied per-item via `tag-item.twig`. Defaults to the neutral, unstyled tag. |
50
51
 
51
52
  **Item shapes per `type`:**
52
53
 
@@ -78,20 +79,20 @@ Link tags:
78
79
  </ul>
79
80
  ```
80
81
 
81
- Checkbox tags (the `<input>` is visually hidden off-screen; the `<label>` is the click target):
82
+ Checkbox tags (the `<input>` is visually hidden off-screen; the `<label>` is the click target). The `mx-icon mx-icon--tick` classes on the label render the shared checkmark icon (from `@pnx-mixtape/mxds/src/Atom/Icon/icon.css`) — its space is always reserved, but it's only visible once the input is checked, so checking a tag doesn't change its size:
82
83
 
83
84
  ```html
84
85
  <ul class="mx-tags">
85
86
  <li>
86
87
  <div class="mx-tag">
87
88
  <input type="checkbox" id="music" name="tag" value="music" />
88
- <label for="music">Music</label>
89
+ <label class="mx-icon mx-icon--tick" for="music">Music</label>
89
90
  </div>
90
91
  </li>
91
92
  <li>
92
93
  <div class="mx-tag">
93
94
  <input type="checkbox" id="news" name="tag" value="news" checked />
94
- <label for="news">News</label>
95
+ <label class="mx-icon mx-icon--tick" for="news">News</label>
95
96
  </div>
96
97
  </li>
97
98
  </ul>
@@ -131,7 +132,7 @@ import { AsTags } from "@pnx-mixtape/mxds/src/enums"
131
132
  <li>
132
133
  <div className="mx-tag">
133
134
  <input type="checkbox" id="music" name="tag" value="music" />
134
- <label htmlFor="music">Music</label>
135
+ <label className="mx-icon mx-icon--tick" htmlFor="music">Music</label>
135
136
  </div>
136
137
  </li>
137
138
  </ul>
@@ -139,13 +140,14 @@ import { AsTags } from "@pnx-mixtape/mxds/src/enums"
139
140
 
140
141
  **`Tag` props:**
141
142
 
142
- | Prop | Type | Default | Description |
143
- | ------------- | ------------ | ---------- | ----------------------------------------------------------------------------------------------- |
144
- | `as` | `AsTags` | `div` | Semantic element for the tag: `div`, `li`, `article`, `aside`, `section` |
145
- | `className` | `string` | — | Extra class on the tag element |
146
- | `children` | `ReactNode` | — | Tag label content |
147
- | `onRemove` | `() => void` | — | When set, renders a remove button (`mx-tag--dismissible`). Consumer removes the tag from state. |
148
- | `removeLabel` | `string` | `"Remove"` | Accessible label for the remove button |
143
+ | Prop | Type | Default | Description |
144
+ | ------------- | ------------- | ---------- | --------------------------------------------------------------------------------------------------------- |
145
+ | `as` | `AsTags` | `div` | Semantic element for the tag: `div`, `li`, `article`, `aside`, `section` |
146
+ | `className` | `string` | — | Extra class on the tag element |
147
+ | `children` | `ReactNode` | — | Tag label content |
148
+ | `onRemove` | `() => void` | — | When set, renders a remove button (`mx-tag--dismissible`). Consumer removes the tag from state. |
149
+ | `removeLabel` | `string` | `"Remove"` | Accessible label for the remove button |
150
+ | `variant` | `TagVariants` | — | Colour variant: `info`, `success`, `warning`, `error`, `featured`. Defaults to the neutral, unstyled tag. |
149
151
 
150
152
  > For filter UIs, tag removal can also be handled upstream (e.g. via the checkbox type toggling its checked state, or via the Filters component). Use `onRemove` when you need a self-contained dismissible chip.
151
153
 
@@ -155,11 +157,11 @@ import { AsTags } from "@pnx-mixtape/mxds/src/enums"
155
157
 
156
158
  The `type` prop on the Twig template maps to `TagTypes` values from `@pnx-mixtape/ids-shape`. The same base `mx-tag` class is used for all variants — the variant changes the inner element, not the class.
157
159
 
158
- | `TagTypes` value | Element rendered | Visual intent |
159
- | ---------------- | --------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
160
- | `text` | `<span class="mx-tag">` | Read-only metadata label (e.g. category shown on a Card) |
161
- | `link` | `<a class="mx-tag" href>` | Navigational tag — clicking takes the user to a filtered / category view |
162
- | `checkbox` | `<div class="mx-tag">` wrapping `<input type="checkbox">` + `<label>` | Toggleable filter tag inside a form; checked state is visually indicated via the primary link colour |
160
+ | `TagTypes` value | Element rendered | Visual intent |
161
+ | ---------------- | --------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
162
+ | `text` | `<span class="mx-tag">` | Read-only metadata label (e.g. category shown on a Card) |
163
+ | `link` | `<a class="mx-tag" href>` | Navigational tag — clicking takes the user to a filtered / category view |
164
+ | `checkbox` | `<div class="mx-tag">` wrapping `<input type="checkbox">` + `<label>` | Toggleable filter tag inside a form; checked state fills the tag with the primary link colour (or the variant's own colour) and prefixes the label with a checkmark icon |
163
165
 
164
166
  ---
165
167
 
@@ -168,7 +170,7 @@ The `type` prop on the Twig template maps to `TagTypes` values from `@pnx-mixtap
168
170
  - **Text tags** are plain `<span>` elements — they are metadata, not interactive. Do not add click handlers or `role` attributes; if a tag needs to be clickable, use the `link` or `checkbox` variant.
169
171
  - **Link tags** use a real `<a href>`. Ensure the destination makes sense out of context — the visible label is the accessible name, so labels like "Music" should lead to an unambiguous page (e.g. `/category/music`).
170
172
  - **Checkbox tags** rely on a native `<input type="checkbox">` paired with a `<label for="…">`. The `<input>` is positioned off-screen (not `display: none`) so it remains focusable and announceable. The whole `.mx-tag` gets a focus outline via `:has(input:focus)` — do not remove this selector.
171
- - The checked state is communicated by colour alone in the default styling. When rendering checkbox tags in a form, ensure surrounding context (a fieldset legend, heading, or `aria-label`) describes what group of filters the tags belong to.
173
+ - The checked state is communicated by colour (a solid fill) **and** a checkmark icon prefixed to the label — not colour alone. When rendering checkbox tags in a form, ensure surrounding context (a fieldset legend, heading, or `aria-label`) describes what group of filters the tags belong to.
172
174
  - If you need to render a list of tags for assistive-tech as a single labelled group, add `aria-label` (or `aria-labelledby`) to the `<ul class="mx-tags">`:
173
175
 
174
176
  ```html
@@ -201,7 +203,31 @@ The `type` prop on the Twig template maps to `TagTypes` values from `@pnx-mixtap
201
203
  }
202
204
  ```
203
205
 
204
- Hover (on `a.mx-tag`) and checked (`:has(input:checked)`) states both set `--tag-foreground` to `--link-colour` (falls back to `--colour-primary`).
206
+ Hover (on `a.mx-tag`) sets `--tag-foreground` to `--link-colour` (falls back to `--colour-primary`). Checked (`:has(input:checked)`) fills `--tag-background`/`--line-colour` with that same colour and switches `--tag-foreground` to `--colour-foreground-reverse`, plus prefixes the label with a checkmark icon — except on a colour variant (see below), where checked keeps the variant's own colours and hover darkens them instead.
207
+
208
+ ---
209
+
210
+ ## Colour variants — `TagVariants` enum
211
+
212
+ Apply `mx-tag--{variant}` for `info`, `success`, `warning`, `error`, or `featured`. Each variant fills `--tag-background` and sets the border/outline to match; foreground uses `--colour-foreground-reverse` (`featured` excepted, which stays dark-on-light).
213
+
214
+ ```tsx
215
+ <Tag variant={TagVariants.INFO}>New</Tag>
216
+ ```
217
+
218
+ `tags.twig` itself only prints pre-rendered `items` markup — `variant`, like `dismissible`, is applied per item when building that markup with `tag-item.twig`:
219
+
220
+ ```twig
221
+ {{ include('@mixtape/Component/Tag/tag-item.twig', {
222
+ type: 'text',
223
+ item: 'New',
224
+ variant: 'info',
225
+ }) }}
226
+ ```
227
+
228
+ - A checked variant tag keeps its own colours (the solid fill and "checked" link colour swap only apply to non-variant tags) — it's already a solid fill. The checkmark icon on checked still applies.
229
+ - The focus outline (`:has(input:focus)`) on a variant tag uses the variant's own colour instead of the standard blue.
230
+ - Hovering a variant **link** tag darkens its background/border by 20% (via `color-mix`) rather than switching to the link hover colour; the text colour is unchanged. `featured` only darkens by 10% — it keeps dark text on its light background, so a smaller step protects contrast.
205
231
 
206
232
  ---
207
233
 
@@ -2,6 +2,7 @@ import { Meta, StoryObj } from "@storybook/react-vite"
2
2
  import Component from "./Tag"
3
3
  import "./tag.css"
4
4
  import { Page } from "../../../.storybook-react/decorators"
5
+ import { TagVariants } from "../../enums"
5
6
 
6
7
  const meta: Meta<typeof Component> = {
7
8
  component: Component,
@@ -9,7 +10,30 @@ const meta: Meta<typeof Component> = {
9
10
  args: {
10
11
  children: "Tag name",
11
12
  },
13
+ argTypes: {
14
+ variant: {
15
+ control: {
16
+ type: "select",
17
+ labels: { undefined: "default" },
18
+ },
19
+ options: [undefined, ...Object.values(TagVariants)],
20
+ },
21
+ },
12
22
  }
13
23
 
14
24
  export default meta
15
25
  export const Tag: StoryObj<typeof meta> = {}
26
+
27
+ const variantsListStyle = { display: "flex", gap: "1rem" }
28
+
29
+ export const Variants: StoryObj<typeof meta> = {
30
+ render: args => (
31
+ <div style={variantsListStyle}>
32
+ {Object.values(TagVariants).map(variant => (
33
+ <Component key={variant} {...args} variant={variant}>
34
+ {variant}
35
+ </Component>
36
+ ))}
37
+ </div>
38
+ ),
39
+ }
@@ -1,10 +1,12 @@
1
1
  import { JSX, PropsWithChildren } from "react"
2
2
  import classNames from "classnames"
3
- import { AsTags, Icons } from "../../enums"
3
+ import { AsTags, Icons, TagVariants } from "../../enums"
4
4
 
5
5
  type TagProps = PropsWithChildren & {
6
6
  className?: string
7
7
  as?: AsTags
8
+ /** Colour variant. Defaults to the neutral, unstyled tag. */
9
+ variant?: TagVariants
8
10
  /**
9
11
  * When provided, renders a remove button. Called when the button is
10
12
  * activated — the consumer is responsible for removing the tag from state.
@@ -18,12 +20,20 @@ const Tag = ({
18
20
  children,
19
21
  className,
20
22
  as = AsTags.DIV,
23
+ variant,
21
24
  onRemove,
22
25
  removeLabel = "Remove",
23
26
  }: TagProps): JSX.Element => {
24
27
  const TagEl: JSX.ElementType = `${as}` as unknown as JSX.ElementType
25
28
  return (
26
- <TagEl className={classNames("mx-tag", { "mx-tag--dismissible": onRemove }, className)}>
29
+ <TagEl
30
+ className={classNames(
31
+ "mx-tag",
32
+ { "mx-tag--dismissible": onRemove },
33
+ variant && `mx-tag--${variant}`,
34
+ className,
35
+ )}
36
+ >
27
37
  {children}
28
38
  {onRemove && (
29
39
  <button
@@ -2,6 +2,7 @@ import { Meta, StoryObj } from "@storybook/html-vite"
2
2
  import Component from "./tag-item.twig"
3
3
  import "./tag.css"
4
4
  import { TagTypes } from "@pnx-mixtape/ids-shape"
5
+ import { TagVariants } from "../../enums"
5
6
 
6
7
  type TagItemType = {
7
8
  type: TagTypes
@@ -11,6 +12,7 @@ type TagItemType = {
11
12
  | { label: string; id: string; name: string; value: string; checked?: boolean }
12
13
  dismissible?: boolean
13
14
  removeLabel?: string
15
+ variant?: TagVariants
14
16
  }
15
17
 
16
18
  /**
@@ -49,6 +51,17 @@ const meta: Meta<TagItemType> = {
49
51
  "When true, plain (text) tags render a remove button. Wire up removal behaviour in the consuming app.",
50
52
  control: "boolean",
51
53
  },
54
+ variant: {
55
+ description: "Colour variant.",
56
+ control: {
57
+ type: "select",
58
+ labels: { undefined: "default" },
59
+ },
60
+ options: [undefined, ...Object.values(TagVariants)],
61
+ table: {
62
+ type: { summary: "enum" },
63
+ },
64
+ },
52
65
  },
53
66
  render: args => {
54
67
  return `<ul class="mx-tags">${Component({ ...args })}</ul>`
@@ -99,3 +112,15 @@ export const Checkbox: Story = {
99
112
  },
100
113
  },
101
114
  }
115
+
116
+ /**
117
+ * Colour variants for conveying status or emphasis.
118
+ */
119
+ export const Variants: Story = {
120
+ render: () => {
121
+ const items = Object.values(TagVariants)
122
+ .map(variant => Component({ type: TagTypes.TEXT, item: variant, variant }))
123
+ .join("")
124
+ return `<ul class="mx-tags">${items}</ul>`
125
+ },
126
+ }