@pnx-mixtape/mxds 0.1.0-rc.4 → 0.1.0-rc.6

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 (236) hide show
  1. package/.claude/skills/create-mxds-component/SKILL.md +77 -0
  2. package/DESIGN.md +595 -0
  3. package/README.md +2 -3
  4. package/dist/build/accordion.entry.js +2 -1
  5. package/dist/build/autocomplete.entry.js +1 -1
  6. package/dist/build/back-to-top.css +1 -0
  7. package/dist/build/back-to-top.entry.js +1 -0
  8. package/dist/build/base.css +1 -1
  9. package/dist/build/button.css +1 -1
  10. package/dist/build/card.css +1 -1
  11. package/dist/build/chunks/Accordion-Bqe8vTNR.js +44 -0
  12. package/dist/build/chunks/AccordionGroup-BYtoAFxg.js +47 -0
  13. package/dist/build/chunks/AccordionMobile-D517oXYZ.js +26 -0
  14. package/dist/build/chunks/Autocomplete-kRo8RNbC.js +135 -0
  15. package/dist/build/chunks/BackToTop-4M-Wl7z3.js +64 -0
  16. package/dist/build/chunks/ClosableAlert-CZ114JsL.js +55 -0
  17. package/dist/build/chunks/CookieConsent-wd8gQ2Oe.js +18 -0
  18. package/dist/build/chunks/Dialog-DoYcGqhW.js +48 -0
  19. package/dist/build/chunks/DropMenu-CSTZq0Zt.js +68 -0
  20. package/dist/build/chunks/FeedbackForm-BsL7fv8s.js +64 -0
  21. package/dist/build/chunks/Gallery-DqJPujX5.js +165 -0
  22. package/dist/build/chunks/GlobalToggle-7H3F1Wvc.js +82 -0
  23. package/dist/build/chunks/InPageNavigation-D7m3IN89.js +86 -0
  24. package/dist/build/chunks/Navigation-CRModT40.js +142 -0
  25. package/dist/build/chunks/Popover-CiTAftNU.js +30 -0
  26. package/dist/build/chunks/Sticky-C1H8MNqK.js +51 -0
  27. package/dist/build/chunks/Tabs-BJD3kABL.js +160 -0
  28. package/dist/build/chunks/Translate-yAgySwet.js +46 -0
  29. package/dist/build/chunks/cookie-compliance-CG6x-TcV.js +119 -0
  30. package/dist/build/chunks/enums-BrHxPZZ_.js +36 -0
  31. package/dist/build/chunks/polyfills-D-qh1a_2.js +33 -0
  32. package/dist/build/chunks/utilities-B9mNRs7V.js +255 -0
  33. package/dist/build/content-block.css +1 -1
  34. package/dist/build/cookie-consent.entry.js +1 -1
  35. package/dist/build/dialog.entry.js +1 -1
  36. package/dist/build/drop-menu.entry.js +1 -1
  37. package/dist/build/elements.js +147 -1
  38. package/dist/build/feedback-form.css +1 -0
  39. package/dist/build/feedback-form.entry.js +1 -0
  40. package/dist/build/filters.entry.js +145 -1
  41. package/dist/build/form.css +1 -1
  42. package/dist/build/gallery.entry.js +1 -1
  43. package/dist/build/global-alert.entry.js +1 -1
  44. package/dist/build/header.css +1 -1
  45. package/dist/build/header.entry.js +1 -1
  46. package/dist/build/icon.css +1 -1
  47. package/dist/build/in-page-navigation.entry.js +1 -1
  48. package/dist/build/navigation.css +1 -1
  49. package/dist/build/navigation.entry.js +1 -1
  50. package/dist/build/pagination.css +1 -1
  51. package/dist/build/popover.entry.js +1 -1
  52. package/dist/build/react.js +2291 -1
  53. package/dist/build/side-navigation.css +1 -1
  54. package/dist/build/sidebar.css +1 -1
  55. package/dist/build/sticky.entry.js +1 -1
  56. package/dist/build/tabs.entry.js +1 -1
  57. package/dist/build/translate.css +1 -0
  58. package/dist/build/translate.entry.js +1 -0
  59. package/dist/build/utility-list.entry.js +53 -1
  60. package/dist/types/Component/BackToTop/BackToTop.d.ts +20 -0
  61. package/dist/types/Component/BackToTop/BackToTop.d.ts.map +1 -0
  62. package/dist/types/Component/BackToTop/Elements/BackToTop.d.ts +27 -0
  63. package/dist/types/Component/BackToTop/Elements/BackToTop.d.ts.map +1 -0
  64. package/dist/types/Component/BackToTop/back-to-top.entry.d.ts +2 -0
  65. package/dist/types/Component/BackToTop/back-to-top.entry.d.ts.map +1 -0
  66. package/dist/types/Component/DropMenu/DropMenu.d.ts +2 -1
  67. package/dist/types/Component/DropMenu/DropMenu.d.ts.map +1 -1
  68. package/dist/types/Component/Navigation/Elements/Navigation.d.ts +2 -3
  69. package/dist/types/Component/Navigation/Elements/Navigation.d.ts.map +1 -1
  70. package/dist/types/Component/SideNavigation/Components/SideNavigationTree.d.ts +3 -3
  71. package/dist/types/Component/SideNavigation/Components/SideNavigationTree.d.ts.map +1 -1
  72. package/dist/types/Component/SideNavigation/SideNavigation.d.ts +3 -1
  73. package/dist/types/Component/SideNavigation/SideNavigation.d.ts.map +1 -1
  74. package/dist/types/Component/Tabs/Components/TabDropMenu.d.ts.map +1 -1
  75. package/dist/types/Component/Tabs/Elements/Tabs.d.ts.map +1 -1
  76. package/dist/types/Component/Translate/Elements/Translate.d.ts +23 -0
  77. package/dist/types/Component/Translate/Elements/Translate.d.ts.map +1 -0
  78. package/dist/types/Component/Translate/Translate.d.ts +37 -0
  79. package/dist/types/Component/Translate/Translate.d.ts.map +1 -0
  80. package/dist/types/Component/Translate/languages.d.ts +11 -0
  81. package/dist/types/Component/Translate/languages.d.ts.map +1 -0
  82. package/dist/types/Component/Translate/translate.entry.d.ts +2 -0
  83. package/dist/types/Component/Translate/translate.entry.d.ts.map +1 -0
  84. package/dist/types/Form/FeedbackForm/Elements/FeedbackForm.d.ts +37 -0
  85. package/dist/types/Form/FeedbackForm/Elements/FeedbackForm.d.ts.map +1 -0
  86. package/dist/types/Form/FeedbackForm/FeedbackForm.d.ts +36 -0
  87. package/dist/types/Form/FeedbackForm/FeedbackForm.d.ts.map +1 -0
  88. package/dist/types/Form/FeedbackForm/feedback-form.entry.d.ts +2 -0
  89. package/dist/types/Form/FeedbackForm/feedback-form.entry.d.ts.map +1 -0
  90. package/dist/types/Utility/Elements/google-translate-widget.d.ts +91 -0
  91. package/dist/types/Utility/Elements/google-translate-widget.d.ts.map +1 -0
  92. package/dist/types/Utility/utilities.d.ts +2 -0
  93. package/dist/types/Utility/utilities.d.ts.map +1 -1
  94. package/dist/types/elements.d.ts +3 -0
  95. package/dist/types/elements.d.ts.map +1 -1
  96. package/dist/types/enums.d.ts +3 -1
  97. package/dist/types/enums.d.ts.map +1 -1
  98. package/dist/types/react.d.ts +3 -0
  99. package/dist/types/react.d.ts.map +1 -1
  100. package/dist/types/tokens.d.ts +1 -0
  101. package/dist/types/tokens.d.ts.map +1 -1
  102. package/docs/API.md +1215 -0
  103. package/docs/CSSGuidelines.md +893 -0
  104. package/docs/CodeQuality.md +940 -0
  105. package/docs/ComponentArchitecture.md +714 -0
  106. package/docs/Contributing.md +626 -0
  107. package/docs/MemoryManagement.md +598 -0
  108. package/docs/Migrations.md +6 -0
  109. package/docs/README.md +100 -0
  110. package/docs/Security.md +444 -0
  111. package/docs/Testing.md +739 -0
  112. package/docs/Troubleshooting.md +674 -0
  113. package/package.json +9 -6
  114. package/src/Atom/Button/Button.tsx +1 -1
  115. package/src/Atom/Button/__snapshots__/Button.stories.tsx.snap +2 -2
  116. package/src/Atom/Button/_buttons.css +1 -0
  117. package/src/Atom/Icon/README.md +1 -1
  118. package/src/Atom/Icon/_icon.css +1 -0
  119. package/src/Atom/Icon/images/thumb.svg +1 -0
  120. package/src/Atom/Link/_links.css +1 -0
  121. package/src/Atom/Media/__snapshots__/Media.stories.ts.snap +4 -9
  122. package/src/Atom/Media/media.twig +4 -5
  123. package/src/Component/BackToTop/BackToTop.stories.ts +127 -0
  124. package/src/Component/BackToTop/BackToTop.stories.tsx +53 -0
  125. package/src/Component/BackToTop/BackToTop.tsx +120 -0
  126. package/src/Component/BackToTop/Elements/BackToTop.ts +119 -0
  127. package/src/Component/BackToTop/README.md +164 -0
  128. package/src/Component/BackToTop/__snapshots__/BackToTop.stories.ts.snap +24 -0
  129. package/src/Component/BackToTop/__snapshots__/BackToTop.stories.tsx.snap +3 -0
  130. package/src/Component/BackToTop/back-to-top.css +70 -0
  131. package/src/Component/BackToTop/back-to-top.entry.js +1 -0
  132. package/src/Component/BackToTop/back-to-top.twig +26 -0
  133. package/src/Component/Card/Components/CardContent.tsx +1 -1
  134. package/src/Component/Card/Components/CardMedia.tsx +1 -1
  135. package/src/Component/Card/__snapshots__/Card.stories.tsx.snap +3 -3
  136. package/src/Component/Card/card.css +1 -1
  137. package/src/Component/Carousel/Carousel.stories.ts +66 -36
  138. package/src/Component/Carousel/__snapshots__/Carousel.stories.tsx.snap +5 -5
  139. package/src/Component/ContentBlock/Components/ContentBlockMedia.tsx +1 -1
  140. package/src/Component/ContentBlock/content-block.css +1 -1
  141. package/src/Component/DropMenu/DropMenu.stories.ts +21 -2
  142. package/src/Component/DropMenu/DropMenu.tsx +14 -4
  143. package/src/Component/DropMenu/__snapshots__/DropMenu.stories.ts.snap +3 -3
  144. package/src/Component/DropMenu/__snapshots__/DropMenu.stories.tsx.snap +2 -2
  145. package/src/Component/DropMenu/drop-menu.twig +7 -1
  146. package/src/Component/HeroBanner/__snapshots__/HeroBanner.stories.tsx.snap +1 -1
  147. package/src/Component/InPageNavigation/__snapshots__/InPageNavigation.stories.ts.snap +48 -13
  148. package/src/Component/InPageNavigation/in-page-navigation.twig +4 -6
  149. package/src/Component/ListItem/Components/ListItemMedia.tsx +1 -1
  150. package/src/Component/Navigation/Elements/Navigation.ts +25 -29
  151. package/src/Component/Navigation/README.md +11 -10
  152. package/src/Component/Navigation/__snapshots__/Navigation.stories.ts.snap +8 -8
  153. package/src/Component/Navigation/_navigation-dropdown.css +9 -11
  154. package/src/Component/Navigation/_navigation-mega.css +5 -11
  155. package/src/Component/Navigation/_navigation.css +5 -3
  156. package/src/Component/Navigation/menu.twig +1 -1
  157. package/src/Component/Navigation/navigation.twig +0 -1
  158. package/src/Component/Pagination/Pagination.stories.ts +57 -27
  159. package/src/Component/Pagination/pagination.css +16 -7
  160. package/src/Component/Popover/__snapshots__/Popover.stories.tsx.snap +3 -3
  161. package/src/Component/SideNavigation/Components/SideNavigationTree.tsx +68 -24
  162. package/src/Component/SideNavigation/README.md +78 -32
  163. package/src/Component/SideNavigation/SideNavigation.stories.ts +41 -2
  164. package/src/Component/SideNavigation/SideNavigation.stories.tsx +31 -0
  165. package/src/Component/SideNavigation/SideNavigation.tsx +13 -3
  166. package/src/Component/SideNavigation/__snapshots__/SideNavigation.stories.ts.snap +87 -3
  167. package/src/Component/SideNavigation/__snapshots__/SideNavigation.stories.tsx.snap +2 -0
  168. package/src/Component/SideNavigation/side-navigation.css +3 -3
  169. package/src/Component/SideNavigation/side-navigation.twig +10 -3
  170. package/src/Component/Tabs/Components/TabDropMenu.tsx +7 -1
  171. package/src/Component/Tabs/Elements/Tabs.ts +13 -3
  172. package/src/Component/Tabs/Tabs.stories.ts +33 -12
  173. package/src/Component/Tabs/Tabs.stories.tsx +28 -14
  174. package/src/Component/Tabs/__snapshots__/Tabs.stories.ts.snap +3 -3
  175. package/src/Component/Tabs/__snapshots__/Tabs.stories.tsx.snap +2 -2
  176. package/src/Component/Translate/Elements/Translate.ts +76 -0
  177. package/src/Component/Translate/README.md +285 -0
  178. package/src/Component/Translate/Translate.stories.ts +87 -0
  179. package/src/Component/Translate/Translate.stories.tsx +77 -0
  180. package/src/Component/Translate/Translate.tsx +213 -0
  181. package/src/Component/Translate/__snapshots__/Translate.stories.ts.snap +18 -0
  182. package/src/Component/Translate/__snapshots__/Translate.stories.tsx.snap +3 -0
  183. package/src/Component/Translate/languages.ts +39 -0
  184. package/src/Component/Translate/translate.css +62 -0
  185. package/src/Component/Translate/translate.entry.js +1 -0
  186. package/src/Component/Translate/translate.twig +44 -0
  187. package/src/Form/Autocomplete/Autocomplete.stories.tsx +2 -1
  188. package/src/Form/FeedbackForm/Elements/FeedbackForm.ts +122 -0
  189. package/src/Form/FeedbackForm/FeedbackForm.stories.ts +99 -0
  190. package/src/Form/FeedbackForm/FeedbackForm.stories.tsx +73 -0
  191. package/src/Form/FeedbackForm/FeedbackForm.tsx +196 -0
  192. package/src/Form/FeedbackForm/README.md +233 -0
  193. package/src/Form/FeedbackForm/__snapshots__/FeedbackForm.stories.ts.snap +125 -0
  194. package/src/Form/FeedbackForm/__snapshots__/FeedbackForm.stories.tsx.snap +5 -0
  195. package/src/Form/FeedbackForm/feedback-form.css +74 -0
  196. package/src/Form/FeedbackForm/feedback-form.entry.js +1 -0
  197. package/src/Form/FeedbackForm/feedback-form.twig +80 -0
  198. package/src/Form/form.css +17 -19
  199. package/src/Introduction.mdx +1 -1
  200. package/src/Layout/Footer/__snapshots__/Footer.stories.ts.snap +3 -3
  201. package/src/Layout/Header/Components/GlobalToggle.tsx +1 -1
  202. package/src/Layout/Header/Header.stories.ts +53 -48
  203. package/src/Layout/Header/Header.stories.tsx +35 -22
  204. package/src/Layout/Header/Header.tsx +2 -2
  205. package/src/Layout/Header/__snapshots__/Header.stories.ts.snap +18 -14
  206. package/src/Layout/Header/__snapshots__/Header.stories.tsx.snap +2 -2
  207. package/src/Layout/Header/_toggles.css +7 -7
  208. package/src/Layout/Page/__snapshots__/Page.stories.tsx.snap +1 -1
  209. package/src/Layout/Sidebar/sidebar.css +10 -3
  210. package/src/Utility/Elements/google-translate-widget.ts +293 -0
  211. package/src/Utility/global.d.ts +4 -0
  212. package/src/Utility/utilities.ts +2 -0
  213. package/src/_custom-media.css +2 -0
  214. package/src/elements.ts +3 -0
  215. package/src/enums.ts +2 -0
  216. package/src/react.ts +3 -0
  217. package/src/tokens.js +1 -0
  218. package/dist/build/chunks/Accordion-GN0sME4W.js +0 -1
  219. package/dist/build/chunks/AccordionGroup-Bzqp5__q.js +0 -1
  220. package/dist/build/chunks/AccordionMobile-DG5jSe8Q.js +0 -1
  221. package/dist/build/chunks/Autocomplete-B4RtkAyh.js +0 -1
  222. package/dist/build/chunks/ClosableAlert-DEhj8Nxm.js +0 -1
  223. package/dist/build/chunks/CookieConsent-BkCMRVST.js +0 -1
  224. package/dist/build/chunks/Dialog-Haahn-Yn.js +0 -1
  225. package/dist/build/chunks/DropMenu-1baQc8Yc.js +0 -1
  226. package/dist/build/chunks/Gallery-D8NGXy2Q.js +0 -1
  227. package/dist/build/chunks/GlobalToggle-B52fdBQu.js +0 -1
  228. package/dist/build/chunks/InPageNavigation-Dyn2TR-N.js +0 -1
  229. package/dist/build/chunks/Navigation-hOrfLq7h.js +0 -1
  230. package/dist/build/chunks/Popover-Cxg-s5Jn.js +0 -1
  231. package/dist/build/chunks/Sticky-BGCuuEJK.js +0 -1
  232. package/dist/build/chunks/Tabs-DYy1YTdU.js +0 -15
  233. package/dist/build/chunks/cookie-compliance-Bbm-0vpj.js +0 -1
  234. package/dist/build/chunks/enums-DLV_1NAf.js +0 -1
  235. package/dist/build/chunks/polyfills-C7pRt54M.js +0 -1
  236. package/dist/build/chunks/utilities-_Bq3TxAQ.js +0 -1
package/docs/API.md ADDED
@@ -0,0 +1,1215 @@
1
+ # API Documentation
2
+
3
+ Reference documentation for utilities and helper functions in the Mixtape Design System.
4
+
5
+ ## Table of Contents
6
+
7
+ - [Validators](#validators)
8
+ - [Utilities](#utilities)
9
+ - [Helper Classes](#helper-classes)
10
+ - [Hooks](#hooks)
11
+ - [Elements](#elements)
12
+ - [React Components](#react-components)
13
+ - [Design Tokens](#design-tokens)
14
+ - [Enums](#enums)
15
+ - [Global Variables](#global-variables)
16
+
17
+ ---
18
+
19
+ ## Validators
20
+
21
+ > Located in: `src/Utility/utilities.ts`
22
+
23
+ The `validators` object provides type-safe input validation without external dependencies.
24
+
25
+ ### Import
26
+
27
+ ```typescript
28
+ import { validators } from "@pnx-mixtape/mxds/src/Utility/utilities"
29
+ ```
30
+
31
+ ### validators.url()
32
+
33
+ Validates a URL string and checks protocol.
34
+
35
+ ```typescript
36
+ validators.url(value: string, allowedProtocols?: string[]): boolean
37
+ ```
38
+
39
+ **Parameters:**
40
+
41
+ - `value` - URL string to validate
42
+ - `allowedProtocols` - Array of allowed protocols (default: `['http:', 'https:']`)
43
+
44
+ **Returns:** `true` if valid URL with allowed protocol
45
+
46
+ **Examples:**
47
+
48
+ ```typescript
49
+ validators.url("https://example.com") // true
50
+ validators.url("http://example.com") // true
51
+ validators.url("javascript:alert(1)") // false
52
+ validators.url("ftp://example.com") // false
53
+ validators.url("ftp://example.com", ["ftp:", "ftps:"]) // true
54
+ ```
55
+
56
+ ---
57
+
58
+ ### validators.email()
59
+
60
+ Validates email address format.
61
+
62
+ ```typescript
63
+ validators.email(value: string): boolean
64
+ ```
65
+
66
+ **Parameters:**
67
+
68
+ - `value` - Email string to validate
69
+
70
+ **Returns:** `true` if valid email format
71
+
72
+ **Examples:**
73
+
74
+ ```typescript
75
+ validators.email("user@example.com") // true
76
+ validators.email("invalid-email") // false
77
+ validators.email("user@domain") // false
78
+ ```
79
+
80
+ ---
81
+
82
+ ### validators.number()
83
+
84
+ Validates a number with optional min/max bounds.
85
+
86
+ ```typescript
87
+ validators.number(value: any, min?: number, max?: number): boolean
88
+ ```
89
+
90
+ **Parameters:**
91
+
92
+ - `value` - Value to validate as number
93
+ - `min` - Optional minimum value (inclusive)
94
+ - `max` - Optional maximum value (inclusive)
95
+
96
+ **Returns:** `true` if valid number within bounds
97
+
98
+ **Examples:**
99
+
100
+ ```typescript
101
+ validators.number(5) // true
102
+ validators.number("5") // true (converts)
103
+ validators.number(5, 1, 10) // true
104
+ validators.number(15, 1, 10) // false
105
+ validators.number("abc") // false
106
+ ```
107
+
108
+ ---
109
+
110
+ ### validators.integer()
111
+
112
+ Validates an integer with optional min/max bounds.
113
+
114
+ ```typescript
115
+ validators.integer(value: any, min?: number, max?: number): boolean
116
+ ```
117
+
118
+ **Parameters:**
119
+
120
+ - `value` - Value to validate as integer
121
+ - `min` - Optional minimum value (inclusive)
122
+ - `max` - Optional maximum value (inclusive)
123
+
124
+ **Returns:** `true` if valid integer within bounds
125
+
126
+ **Examples:**
127
+
128
+ ```typescript
129
+ validators.integer(5, 1, 10) // true
130
+ validators.integer(5.5, 1, 10) // false (not integer)
131
+ validators.integer(-1, 1, 10) // false (below min)
132
+ validators.integer("5", 1, 10) // true (converts)
133
+ ```
134
+
135
+ ---
136
+
137
+ ### validators.string()
138
+
139
+ Validates a string with optional length constraints.
140
+
141
+ ```typescript
142
+ validators.string(value: any, minLength?: number, maxLength?: number): boolean
143
+ ```
144
+
145
+ **Parameters:**
146
+
147
+ - `value` - Value to validate as string
148
+ - `minLength` - Optional minimum length
149
+ - `maxLength` - Optional maximum length
150
+
151
+ **Returns:** `true` if valid string within length bounds
152
+
153
+ **Examples:**
154
+
155
+ ```typescript
156
+ validators.string("hello", 1, 10) // true
157
+ validators.string("", 1, 10) // false
158
+ validators.string("toolongstring", 1, 5) // false
159
+ ```
160
+
161
+ ---
162
+
163
+ ### validators.notEmpty()
164
+
165
+ Validates a string is not empty (after trimming).
166
+
167
+ ```typescript
168
+ validators.notEmpty(value: string): boolean
169
+ ```
170
+
171
+ **Parameters:**
172
+
173
+ - `value` - String to validate
174
+
175
+ **Returns:** `true` if string has content after trimming
176
+
177
+ **Examples:**
178
+
179
+ ```typescript
180
+ validators.notEmpty("hello") // true
181
+ validators.notEmpty(" ") // false
182
+ validators.notEmpty("") // false
183
+ ```
184
+
185
+ ---
186
+
187
+ ### validators.pattern()
188
+
189
+ Validates a value matches a regex pattern.
190
+
191
+ ```typescript
192
+ validators.pattern(value: string, pattern: RegExp): boolean
193
+ ```
194
+
195
+ **Parameters:**
196
+
197
+ - `value` - String to validate
198
+ - `pattern` - Regex pattern to match
199
+
200
+ **Returns:** `true` if value matches pattern
201
+
202
+ **Examples:**
203
+
204
+ ```typescript
205
+ validators.pattern("abc123", /^[a-z0-9]+$/) // true
206
+ validators.pattern("ABC", /^[a-z]+$/) // false
207
+ validators.pattern("test@example.com", /^[\w.]+@[\w.]+$/) // true
208
+ ```
209
+
210
+ ---
211
+
212
+ ### validators.oneOf()
213
+
214
+ Validates a value is one of the allowed values.
215
+
216
+ ```typescript
217
+ validators.oneOf<T>(value: T, allowedValues: T[]): boolean
218
+ ```
219
+
220
+ **Parameters:**
221
+
222
+ - `value` - Value to check
223
+ - `allowedValues` - Array of allowed values
224
+
225
+ **Returns:** `true` if value is in allowed list
226
+
227
+ **Examples:**
228
+
229
+ ```typescript
230
+ validators.oneOf("red", ["red", "green", "blue"]) // true
231
+ validators.oneOf("yellow", ["red", "green", "blue"]) // false
232
+ validators.oneOf(5, [1, 5, 10]) // true
233
+ ```
234
+
235
+ ---
236
+
237
+ ### validators.array()
238
+
239
+ Validates an array with optional length constraints.
240
+
241
+ ```typescript
242
+ validators.array(value: any, minLength?: number, maxLength?: number): boolean
243
+ ```
244
+
245
+ **Parameters:**
246
+
247
+ - `value` - Value to validate as array
248
+ - `minLength` - Optional minimum array length
249
+ - `maxLength` - Optional maximum array length
250
+
251
+ **Returns:** `true` if valid array within length bounds
252
+
253
+ **Examples:**
254
+
255
+ ```typescript
256
+ validators.array([1, 2, 3], 1, 5) // true
257
+ validators.array([], 1, 5) // false
258
+ validators.array([1, 2, 3], 5, 10) // false
259
+ ```
260
+
261
+ ---
262
+
263
+ ### validators.json()
264
+
265
+ Validates a value is a valid JSON string.
266
+
267
+ ```typescript
268
+ validators.json(value: string): boolean
269
+ ```
270
+
271
+ **Parameters:**
272
+
273
+ - `value` - String to validate as JSON
274
+
275
+ **Returns:** `true` if valid JSON string
276
+
277
+ **Examples:**
278
+
279
+ ```typescript
280
+ validators.json('{"key": "value"}') // true
281
+ validators.json("invalid{json") // false
282
+ validators.json("[1, 2, 3]") // true
283
+ ```
284
+
285
+ ---
286
+
287
+ ### validators.date()
288
+
289
+ Validates a date string or Date object.
290
+
291
+ ```typescript
292
+ validators.date(value: string | Date, minDate?: Date, maxDate?: Date): boolean
293
+ ```
294
+
295
+ **Parameters:**
296
+
297
+ - `value` - Date string or Date object to validate
298
+ - `minDate` - Optional minimum date
299
+ - `maxDate` - Optional maximum date
300
+
301
+ **Returns:** `true` if valid date within bounds
302
+
303
+ **Examples:**
304
+
305
+ ```typescript
306
+ validators.date("2024-01-01") // true
307
+ validators.date("invalid-date") // false
308
+ validators.date(new Date()) // true
309
+ validators.date("2024-06-15", new Date("2024-01-01"), new Date("2024-12-31")) // true
310
+ ```
311
+
312
+ ---
313
+
314
+ ### validators.hexColor()
315
+
316
+ Validates a hex color code.
317
+
318
+ ```typescript
319
+ validators.hexColor(value: string): boolean
320
+ ```
321
+
322
+ **Parameters:**
323
+
324
+ - `value` - Color string to validate
325
+
326
+ **Returns:** `true` if valid hex color (3 or 6 digits)
327
+
328
+ **Examples:**
329
+
330
+ ```typescript
331
+ validators.hexColor("#fff") // true
332
+ validators.hexColor("#ffffff") // true
333
+ validators.hexColor("#FF0000") // true
334
+ validators.hexColor("fff") // false (missing #)
335
+ validators.hexColor("#gggggg") // false (invalid hex)
336
+ ```
337
+
338
+ ---
339
+
340
+ ## Utilities
341
+
342
+ ### makeAnchor()
343
+
344
+ Converts a string to a valid HTML ID.
345
+
346
+ ```typescript
347
+ makeAnchor(string: string, length?: number): string
348
+ ```
349
+
350
+ **Parameters:**
351
+
352
+ - `string` - String to convert
353
+ - `length` - Maximum length (default: 20)
354
+
355
+ **Returns:** Valid HTML ID string
356
+
357
+ **Examples:**
358
+
359
+ ```typescript
360
+ makeAnchor("Hello World") // 'hello-world'
361
+ makeAnchor("Hello World", 5) // 'hello'
362
+ makeAnchor("Test 123!") // 'test-123'
363
+ makeAnchor(" spaces ") // 'spaces'
364
+ ```
365
+
366
+ ---
367
+
368
+ ### makeCamelCase()
369
+
370
+ Converts a string to camelCase.
371
+
372
+ ```typescript
373
+ makeCamelCase(string: string): string
374
+ ```
375
+
376
+ **Parameters:**
377
+
378
+ - `string` - String to convert
379
+
380
+ **Returns:** camelCase string
381
+
382
+ **Examples:**
383
+
384
+ ```typescript
385
+ makeCamelCase("hello world") // 'helloWorld'
386
+ makeCamelCase("Hello World") // 'helloWorld'
387
+ makeCamelCase("hello-world") // 'helloWorld'
388
+ ```
389
+
390
+ ---
391
+
392
+ ### isInternalLink()
393
+
394
+ Checks if a URL is internal to the current site.
395
+
396
+ ```typescript
397
+ isInternalLink(href: string, internalHostnames: string[]): boolean
398
+ ```
399
+
400
+ **Parameters:**
401
+
402
+ - `href` - URL to check
403
+ - `internalHostnames` - Array of internal hostnames
404
+
405
+ **Returns:** `true` if URL is internal
406
+
407
+ **Examples:**
408
+
409
+ ```typescript
410
+ isInternalLink("https://example.com/page", ["example.com"]) // true
411
+ isInternalLink("https://other.com/page", ["example.com"]) // false
412
+ ```
413
+
414
+ ---
415
+
416
+ ### createElement()
417
+
418
+ Safely creates an HTML element from a string.
419
+
420
+ ```typescript
421
+ createElement(string: string): Element | null
422
+ ```
423
+
424
+ **Parameters:**
425
+
426
+ - `string` - HTML string to parse
427
+
428
+ **Returns:** Parsed Element or null
429
+
430
+ **Examples:**
431
+
432
+ ```typescript
433
+ const el = createElement('<div class="test">Content</div>')
434
+ // Returns: <div class="test">Content</div>
435
+
436
+ const unsafe = createElement("<script>alert(1)</script>")
437
+ // Returns: <script> element (but script doesn't execute)
438
+ ```
439
+
440
+ ---
441
+
442
+ ### handleOutsideClick()
443
+
444
+ Handles clicks outside a container.
445
+
446
+ ```typescript
447
+ handleOutsideClick(
448
+ event: MouseEvent,
449
+ container: HTMLElement,
450
+ callback: () => void,
451
+ ignored?: HTMLElement[]
452
+ ): void
453
+ ```
454
+
455
+ **Parameters:**
456
+
457
+ - `event` - Mouse event
458
+ - `container` - Container element
459
+ - `callback` - Function to call if outside
460
+ - `ignored` - Elements to ignore (optional)
461
+
462
+ **Examples:**
463
+
464
+ ```typescript
465
+ document.addEventListener('click', (e) => {
466
+ handleOutsideClick(e, dialog, () => {
467
+ dialog.close()
468
+ }, [triggerButton])
469
+ })
470
+ ```
471
+
472
+ ---
473
+
474
+ ### handleEscape()
475
+
476
+ Handles Escape key press.
477
+
478
+ ```typescript
479
+ handleEscape(event: KeyboardEvent, callback: () => void): void
480
+ ```
481
+
482
+ **Parameters:**
483
+
484
+ - `event` - Keyboard event
485
+ - `callback` - Function to call on Escape
486
+
487
+ **Examples:**
488
+
489
+ ```typescript
490
+ document.addEventListener("keydown", e => {
491
+ handleEscape(e, () => {
492
+ modal.close()
493
+ })
494
+ })
495
+ ```
496
+
497
+ ---
498
+
499
+ ### getElementBox()
500
+
501
+ Observes an element's dimensions via `ResizeObserver` and writes them as CSS custom properties on the element itself. Useful for container-query-style layouts without native container queries.
502
+
503
+ ```typescript
504
+ getElementBox(element: HTMLElement, once?: boolean, content?: boolean): void
505
+ ```
506
+
507
+ **Parameters:**
508
+
509
+ - `element` - Element to observe
510
+ - `once` - If `true`, stops observing after the first measurement (default: `false`)
511
+ - `content` - If `true`, measures `contentBoxSize` instead of `borderBoxSize` (default: `false`)
512
+
513
+ **Sets on the element:**
514
+
515
+ - `--inline-size` — current inline (horizontal) size in `px`
516
+ - `--block-size` — current block (vertical) size in `px`
517
+
518
+ **Examples:**
519
+
520
+ ```typescript
521
+ // Continuously track size
522
+ getElementBox(containerEl)
523
+
524
+ // Measure once (e.g. for initial layout)
525
+ getElementBox(containerEl, true)
526
+
527
+ // Use in CSS
528
+ // .mx-component { width: calc(var(--inline-size) / 2); }
529
+ ```
530
+
531
+ ---
532
+
533
+ ### generatedId()
534
+
535
+ Generates a stable HTML-safe ID from a Web Component's text content. Used internally by Web Components when an element has no existing `id`.
536
+
537
+ ```typescript
538
+ private generatedId(): string
539
+ ```
540
+
541
+ **Returns:** A kebab-case HTML ID string derived from the component's text content, via `makeAnchor()`.
542
+
543
+ **Examples:**
544
+
545
+ ```typescript
546
+ // Inside a Web Component:
547
+ details.id = details.id || this.generatedId()
548
+ // If textContent is "My Accordion Section", produces: "my-accordion-section"
549
+ ```
550
+
551
+ **Note:** This is a private method on Web Component classes. It is not exported from the utilities module. Use `makeAnchor()` directly if you need the same behaviour outside a component.
552
+
553
+ ---
554
+
555
+ ## Helper Classes
556
+
557
+ ### DisclosureWidget
558
+
559
+ Manages disclosure patterns (show/hide content) using an `inert` attribute toggle and `aria-expanded` state.
560
+
561
+ ```typescript
562
+ import { DisclosureWidget } from "@pnx-mixtape/mxds/src/Utility/utilities"
563
+
564
+ const widget = new DisclosureWidget(trigger, target, options?, context?)
565
+ widget.init() // Attach and set initial state (idempotent — safe to call multiple times)
566
+ widget.attach() // Add event listeners
567
+ widget.detach() // Remove event listeners and close if open
568
+ ```
569
+
570
+ **Constructor:**
571
+
572
+ ```typescript
573
+ new DisclosureWidget(
574
+ trigger: Element, // The button/trigger element
575
+ element: Element, // The content element to show/hide
576
+ options?: DisclosureWidgetOptions, // Optional overrides (see below)
577
+ context?: Element | Document, // Default: document
578
+ )
579
+ ```
580
+
581
+ **Options (`DisclosureWidgetOptions`):**
582
+
583
+ | Option | Type | Default | Description |
584
+ |---|---|---|---|
585
+ | `attribute` | `string` | `"inert"` | HTML attribute toggled on the target element |
586
+ | `shouldPreventDefault` | `boolean` | `true` | Call `preventDefault()` on trigger events |
587
+ | `setInitialAttribute` | `boolean` | `true` | Set the attribute on init (starts closed) |
588
+ | `clickEvent` | `"click" \| "mouseover"` | `"click"` | Event type to trigger toggle |
589
+
590
+ **Custom Event — `disclosure-toggle`:**
591
+
592
+ Dispatched on the trigger element after each toggle:
593
+
594
+ ```typescript
595
+ trigger.addEventListener("disclosure-toggle", (e: CustomEvent<DisclosureWidgetEvent>) => {
596
+ const { isOpen, id, options, target } = e.detail
597
+ })
598
+ ```
599
+
600
+ ```typescript
601
+ type DisclosureWidgetEvent = {
602
+ isOpen: boolean // Whether the widget is now open
603
+ id: string // ID of the target element
604
+ options: DisclosureWidgetOptions
605
+ target: Element // The content element
606
+ }
607
+ ```
608
+
609
+ **Used in:** Accordion, Tabs, Dialog
610
+
611
+ ---
612
+
613
+ ### Keyboard
614
+
615
+ Manages keyboard navigation (arrow keys, Escape) for menus and popup overlays. Uses `window.Mixtape` to coordinate state across components — see [Global Variables → Mixtape](#global-variables).
616
+
617
+ ```typescript
618
+ import { Keyboard } from "@pnx-mixtape/mxds/src/Utility/utilities"
619
+
620
+ const keyboard = new Keyboard()
621
+
622
+ // Register a popup so Escape closes it
623
+ keyboard.attachPopup(id, closefn)
624
+ keyboard.detachPopup(id)
625
+
626
+ // Register a menu for arrow-key navigation
627
+ keyboard.attachMenu(container, items)
628
+ keyboard.detachMenu()
629
+
630
+ // Attach global click + keydown listeners (pass AbortSignal for cleanup)
631
+ keyboard.attachEventListeners(signal)
632
+ ```
633
+
634
+ **Key bindings handled by `attachEventListeners`:**
635
+
636
+ | Key | Behaviour |
637
+ |---|---|
638
+ | `Escape` | Calls `closefn` on the most recently attached popup |
639
+ | `ArrowDown` / `ArrowRight` | Moves focus forward in attached menu (wraps) |
640
+ | `ArrowUp` / `ArrowLeft` | Moves focus backward in attached menu (wraps) |
641
+
642
+ **Note:** `attachEventListeners(signal)` must be called after `attachMenu`/`attachPopup`. Pass an `AbortSignal` so listeners are cleaned up automatically.
643
+
644
+ **Used in:** Navigation, DropMenu, Tabs
645
+
646
+ ---
647
+
648
+ ### CookieCompliance
649
+
650
+ Manages cookie consent banner. Initialised via the static `create()` factory which uses `window.once` to prevent double-initialisation.
651
+
652
+ ```typescript
653
+ import { CookieCompliance } from "@pnx-mixtape/mxds/src/Utility/utilities"
654
+
655
+ CookieCompliance.create({ context: document })
656
+ ```
657
+
658
+ After creation, the instance is available at `window.CookieCompliance`. Other scripts can check consent before initialising tracking:
659
+
660
+ ```typescript
661
+ if (window.CookieCompliance?.hasConsent()) {
662
+ // safe to init analytics
663
+ }
664
+ ```
665
+
666
+ **Required HTML attributes on child elements:**
667
+
668
+ | Attribute | Element | Purpose |
669
+ |---|---|---|
670
+ | `data-cookie-accept` | `<button>` | Accept button |
671
+ | `data-cookie-reject` | `<button>` | Reject button |
672
+ | `data-cookie-more` | `<button>` | "More info" button |
673
+ | `data-wait-remove` | Banner root | ms delay before banner is removed from DOM |
674
+
675
+ ---
676
+
677
+ ### IntersectionLoader
678
+
679
+ Lazy-loads a component when its placeholder element scrolls into the viewport.
680
+
681
+ ```typescript
682
+ import { IntersectionLoader } from "@pnx-mixtape/mxds/src/Utility/utilities"
683
+
684
+ IntersectionLoader.create("[data-lazy]", async () => {
685
+ const { default: Component } = await import("./Component")
686
+ // initialise Component
687
+ })
688
+ ```
689
+
690
+ **Required HTML attributes on target elements:**
691
+
692
+ | Attribute | Purpose |
693
+ |---|---|
694
+ | `data-load-fallback` | Optional `<button>` shown if JS lazy load fails |
695
+ | `data-load-error` | Optional element revealed on load error |
696
+
697
+ **Note:** `create()` requires `window.once` to be present. In non-Drupal projects, assign [`@drupal/once`](https://www.npmjs.com/package/@drupal/once) to `window.once` at your entry point. Elements are guarded with the `"intersection-loader"` once key.
698
+
699
+ ---
700
+
701
+ ### BreakpointLoader
702
+
703
+ Lazy-loads a component when a media query matches.
704
+
705
+ ```typescript
706
+ import { BreakpointLoader } from "@pnx-mixtape/mxds/src/Utility/utilities"
707
+
708
+ BreakpointLoader.create(
709
+ "[data-responsive]",
710
+ async () => {
711
+ const { default: Component } = await import("./Component")
712
+ // initialise Component
713
+ },
714
+ { mediaQuery: "(max-width: 720px)" }, // default if omitted
715
+ )
716
+ ```
717
+
718
+ **Options (`BreakpointLoaderOptions`):**
719
+
720
+ | Option | Type | Default | Description |
721
+ |---|---|---|---|
722
+ | `mediaQuery` | `string` | `"(max-width: 720px)"` | Standard media query string passed to `window.matchMedia` |
723
+
724
+ **Required HTML attributes** are the same as `IntersectionLoader` (`data-load-fallback`, `data-load-error`).
725
+
726
+ **Note:** `create()` requires `window.once` to be present. Elements are guarded with the `"breakpoint-loader"` once key.
727
+
728
+ ---
729
+
730
+ ## Hooks
731
+
732
+ ### useLocalStorage
733
+
734
+ React hook for localStorage with sync across tabs.
735
+
736
+ ```typescript
737
+ import { useLocalStorage } from "@pnx-mixtape/mxds/src/Utility/Hooks/useLocalStorage"
738
+
739
+ const [value, setValue] = useLocalStorage({
740
+ key: "theme",
741
+ initialValue: "light",
742
+ })
743
+ ```
744
+
745
+ **Features:**
746
+
747
+ - Syncs across tabs
748
+ - JSON validation
749
+ - Error handling
750
+ - SSR-safe
751
+
752
+ ---
753
+
754
+ ### useUrlParams
755
+
756
+ React hook for URL parameters management.
757
+
758
+ ```typescript
759
+ import { useUrlParams } from "@pnx-mixtape/mxds/src/Utility/Hooks/useUrlParams"
760
+
761
+ const { searchParams, pathname, history } = useUrlParams()
762
+
763
+ // Read param
764
+ const page = searchParams?.get("page")
765
+
766
+ // Update URL
767
+ history?.pushState(null, "", `${pathname}?page=2`)
768
+ ```
769
+
770
+ ---
771
+
772
+ ## Elements
773
+
774
+ ### Web Component Exports
775
+
776
+ Web Components are exported from `src/elements.ts`:
777
+
778
+ ```typescript
779
+ // Import all Web Components
780
+ import {
781
+ Accordion,
782
+ AccordionGroup,
783
+ AccordionMobile,
784
+ AccordionDiv,
785
+ Dialog,
786
+ DropMenu,
787
+ ClosableAlert,
788
+ Sticky,
789
+ Tabs,
790
+ GlobalToggle,
791
+ InPageNavigation,
792
+ Navigation,
793
+ } from "@pnx-mixtape/mxds"
794
+ ```
795
+
796
+ **Or import individually:**
797
+
798
+ ```typescript
799
+ import Accordion from "@pnx-mixtape/mxds/src/Component/Accordion/Elements/Accordion"
800
+ import Dialog from "@pnx-mixtape/mxds/src/Component/Dialog/Elements/Dialog"
801
+ // ... and so on for each component
802
+ ```
803
+
804
+ **Available Web Components:**
805
+
806
+ - `Accordion` - Accordion component
807
+ - `AccordionGroup` - Grouped accordions
808
+ - `AccordionMobile` - Mobile accordion variant
809
+ - `AccordionDiv` - Accordion with div wrapper
810
+ - `Dialog` - Modal dialog
811
+ - `DropMenu` - Dropdown menu
812
+ - `ClosableAlert` - Dismissible alert
813
+ - `Sticky` - Sticky positioning
814
+ - `Tabs` - Tab component
815
+ - `GlobalToggle` - Header toggle
816
+ - `InPageNavigation` - In-page navigation
817
+ - `Navigation` - Main navigation
818
+
819
+ **Utilities also exported:**
820
+
821
+ The `src/elements.ts` file also re-exports all utilities from `src/Utility/utilities.ts`:
822
+
823
+ ```typescript
824
+ import {
825
+ // Helper classes
826
+ DisclosureWidget,
827
+ Keyboard,
828
+ CookieCompliance,
829
+ IntersectionLoader,
830
+ BreakpointLoader,
831
+ // Utility functions
832
+ validators,
833
+ makeAnchor,
834
+ makeCamelCase,
835
+ isInternalLink,
836
+ createElement,
837
+ handleOutsideClick,
838
+ handleEscape,
839
+ } from "@pnx-mixtape/mxds"
840
+ ```
841
+
842
+ ---
843
+
844
+ ## React Components
845
+
846
+ ### React Component Exports
847
+
848
+ React components are exported from `src/react.ts`:
849
+
850
+ ```typescript
851
+ // Import React components
852
+ import {
853
+ // Atoms
854
+ Heading,
855
+ Media,
856
+ Link,
857
+ Text,
858
+ Video,
859
+ Button,
860
+ Icon,
861
+
862
+ // Components
863
+ Accordion,
864
+ AccordionTitle,
865
+ AccordionTitleIcon,
866
+ AccordionContent,
867
+ Breadcrumb,
868
+ Card,
869
+ CardContent,
870
+ CardMedia,
871
+ ContentBlock,
872
+ ContentBlockContent,
873
+ ContentBlockMedia,
874
+ Dialog,
875
+ DialogContent,
876
+ DialogCloseButton,
877
+ InPageNavigation,
878
+ InPageAlert,
879
+ GlobalAlert,
880
+ HeroBanner,
881
+ LinkList,
882
+ LinkListItem,
883
+ Pagination,
884
+ PaginationItem,
885
+ Sticky,
886
+ Tabs,
887
+ Tab,
888
+ TabPanel,
889
+ TabList,
890
+ TabDropMenu,
891
+ Tag,
892
+ ListItem,
893
+ ListItemContent,
894
+ ListItemMedia,
895
+ Navigation,
896
+ NavigationDropdown,
897
+ NavigationDropdownLevel,
898
+ DropMenu,
899
+ DropMenuItem,
900
+ ResultsBar,
901
+ ResultsBarInfo,
902
+ ResultsBarSort,
903
+
904
+ // Forms
905
+ FormCheckbox,
906
+ Checkbox,
907
+ SingleCheckbox,
908
+ FormDescription,
909
+ FormLabel,
910
+ FormRadio,
911
+ Radio,
912
+ FormSelect,
913
+ Option,
914
+ FormText,
915
+ FormTitle,
916
+ Form,
917
+
918
+ // Layout
919
+ Grid,
920
+ Page,
921
+ Section,
922
+ SectionGrid,
923
+
924
+ // Hooks
925
+ useAccordion,
926
+ useDialog,
927
+ useInPageNavigation,
928
+ usePagination,
929
+ useTabContext,
930
+ useDropMenuContext,
931
+ useLocalStorage,
932
+ useMediaQuery,
933
+ useUrlParams,
934
+
935
+ // Context Providers
936
+ ImageComponentProvider,
937
+ useImageComponent,
938
+ LinkComponentProvider,
939
+ useLinkComponent,
940
+ PaginationProvider,
941
+ DropMenuProvider,
942
+ } from "@pnx-mixtape/mxds/react"
943
+ ```
944
+
945
+ **Or import individually:**
946
+
947
+ ```typescript
948
+ import Button from "@pnx-mixtape/mxds/src/Atom/Button/Button"
949
+ import Card from "@pnx-mixtape/mxds/src/Component/Card/Card"
950
+ import { useLocalStorage } from "@pnx-mixtape/mxds/react"
951
+ ```
952
+
953
+ ---
954
+
955
+ ## Design Tokens
956
+
957
+ ### Accessing Tokens
958
+
959
+ ```javascript
960
+ // In PostCSS config
961
+ import tokens from "@pnx-mixtape/mxds/tokens"
962
+
963
+ console.log(tokens.colour.brand.primary)
964
+ console.log(tokens.spacing.m)
965
+ console.log(tokens.breakpoint.m)
966
+ ```
967
+
968
+ ### Token Structure
969
+
970
+ ```javascript
971
+ tokens = {
972
+ colour: {
973
+ text: { primary, secondary, ... },
974
+ background: { primary, secondary, ... },
975
+ brand: { primary, secondary, ... },
976
+ },
977
+ spacing: { xxs, xs, s, m, l, xl, xxl },
978
+ font: {
979
+ size: { ... },
980
+ family: { ... },
981
+ weight: { ... },
982
+ },
983
+ breakpoint: { xxs, xs, s, m, l, xl },
984
+ // ... more categories
985
+ }
986
+ ```
987
+
988
+ ---
989
+
990
+ ## Related Documentation
991
+
992
+ - [Code Quality Guidelines](./CodeQuality.md)
993
+ - [Component Architecture](./ComponentArchitecture.md)
994
+ - [Security Guidelines](./Security.md)
995
+ - [Validation Examples](./Security.md#input-validation)
996
+
997
+ ---
998
+
999
+ ## Enums
1000
+
1001
+ Enums come from two sources:
1002
+
1003
+ - **`src/enums.ts`** — local enums, some of which are migration targets for `@pnx-mixtape/ids-shape`
1004
+ - **`@pnx-mixtape/ids-shape`** — the canonical enum package; prefer these going forward
1005
+
1006
+ ### Migration status
1007
+
1008
+ | Local enum | ids-shape equivalent | Status |
1009
+ |---|---|---|
1010
+ | `AsTags` | `AsTypes` | ⏳ Migrate when reworking JSX components |
1011
+ | `FormStatus` | `FormStatusTypes` | ⏳ Migrate when reworking form components |
1012
+ | `Alignment` | `MediaAlignmentTypes` | ⏳ Migrate — ids-shape adds `center` value |
1013
+ | `TextTags` | — | 🏠 Local only |
1014
+ | `HeadingStyles` | — | 🏠 Local only |
1015
+ | `TextStyles` | — | 🏠 Local only |
1016
+ | `Icons` | — | 🏠 Local only |
1017
+ | `PageWidths` | — | 🏠 Local only |
1018
+ | `SectionFlow` | — | 🏠 Local only |
1019
+ | `SidebarOrder` | — | 🏠 Local only |
1020
+ | `BackgroundStyles` | — | 🏠 Local only |
1021
+ | `TableStyles` | — | 🏠 Local only |
1022
+ | `TabStyles` | — | 🏠 Local only |
1023
+ | `SocialShareTypes` | — | 🏠 Local only |
1024
+
1025
+ ### Also available in ids-shape (not yet used)
1026
+
1027
+ These ids-shape enums exist and can replace current string literals in components during the rework:
1028
+
1029
+ | ids-shape enum | Purpose |
1030
+ |---|---|
1031
+ | `ButtonTypes`, `ButtonModifiers` | Button variants and types |
1032
+ | `IconColourModifier`, `IconRotateModifier`, `IconSizeModifier` | Icon modifiers |
1033
+ | `LoadingTypes` | `lazy` / `eager` image loading |
1034
+ | `SectionTypes` | HTML tag selection for sections |
1035
+ | `GridModifiers` | Grid column layouts |
1036
+ | `TagTypes` | Tag component variants |
1037
+ | `InPageAlertTypes`, `GlobalAlertTypes` | Alert type variants |
1038
+ | `CardModifier`, `HeroBannerModifier`, `StepsModifier` | Component modifiers |
1039
+ | `ListItemModifiers`, `MastheadModifier`, `FooterModifier` | Layout modifiers |
1040
+ | `PopoverPlacementModifier` | Popover positioning |
1041
+ | `TextModifiers`, `InputTypeTypes` | Text and input variants |
1042
+
1043
+ ### Importing local enums
1044
+
1045
+ ```typescript
1046
+ import { BackgroundStyles, Icons, TabStyles } from "@pnx-mixtape/mxds/src/enums"
1047
+ ```
1048
+
1049
+ ### Importing ids-shape enums
1050
+
1051
+ ```typescript
1052
+ import { AsTypes, FormStatusTypes, ButtonModifiers } from "@pnx-mixtape/ids-shape"
1053
+ ```
1054
+
1055
+ ### Local enum values
1056
+
1057
+ #### Layout & Structure
1058
+
1059
+ | Enum | Values |
1060
+ |---|---|
1061
+ | `AsTags` | `div`, `li`, `article`, `aside`, `section` |
1062
+ | `PageWidths` | `narrow`, `feature`, `full` |
1063
+ | `SectionFlow` | `flow`, `group` |
1064
+ | `SidebarOrder` | `""` (default), `sidebar-rev`, `sidebar-rev-lg` |
1065
+
1066
+ #### Typography
1067
+
1068
+ | Enum | Values |
1069
+ |---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
1070
+ | `TextTags` | `p`, `div`, `strong`, `em` |
1071
+ | `HeadingStyles` | `mx-headline`, `mx-text--eyebrow`, `mx-text--embellished`, `mx-text--l`, `mx-heading--xxl`, `mx-heading--xl`, `mx-heading--l`, `mx-heading--m`, `mx-heading--s`, `mx-heading--xs`, `mx-spacing--reset`, `mx-spacing--top-reset`, `mx-spacing--bottom-reset` |
1072
+ | `TextStyles` | `mx-headline`, `mx-text--eyebrow`, `mx-text--embellished`, `mx-text--xxl`, `mx-text--xl`, `mx-text--l`, `mx-text--m`, `mx-text--s`, `mx-text--xs`, `mx-text--primary`, `mx-text--accent` |
1073
+ | `Alignment` | `left`, `right` |
1074
+
1075
+ #### Components
1076
+
1077
+ | Enum | Values |
1078
+ |---|---|
1079
+ | `BackgroundStyles` | `primary`, `accent`, `alt`, `reverse`, `reset`, `box` |
1080
+ | `TabStyles` | `default`, `centered`, `lined`, `same-height` |
1081
+ | `TableStyles` | `flush`, `striped`, `small`, `bordered` |
1082
+
1083
+ #### Forms
1084
+
1085
+ | Enum | Values |
1086
+ |---|---|
1087
+ | `FormStatus` | `default`, `valid`, `invalid` |
1088
+
1089
+ #### Social & Sharing
1090
+
1091
+ | Enum | Values |
1092
+ |---|---|
1093
+ | `SocialShareTypes` | `twitter`, `facebook`, `linkedin` |
1094
+
1095
+ #### Icons
1096
+
1097
+ The `Icons` enum maps semantic names to SVG file slugs:
1098
+
1099
+ ```typescript
1100
+ import { Icons } from "@pnx-mixtape/mxds/src/enums"
1101
+ // Icons.ARROW_DOWN = "arrow-down"
1102
+ // Icons.CLOSE = "close"
1103
+ // Icons.SEARCH = "search"
1104
+ ```
1105
+
1106
+ | Key | Value | Key | Value |
1107
+ |---|---|---|---|
1108
+ | `ARROW_DOWN` | `arrow-down` | `ARROW_LEFT` | `arrow-left` |
1109
+ | `ARROW_UP` | `arrow-up` | `ARROW_RIGHT` | `arrow-right` |
1110
+ | `CHEVRON_DOWN` | `chevron-down` | `CHEVRON_LEFT` | `chevron-left` |
1111
+ | `CHEVRON_RIGHT` | `chevron-right` | `CHEVRON_UP` | `chevron-up` |
1112
+ | `CLOSE` | `close` | `COLLAPSE` | `collapse` |
1113
+ | `DOWNLOAD` | `download` | `DRUPAL` | `drupal` |
1114
+ | `ERROR` | `error` | `EXPAND` | `expand` |
1115
+ | `EXTERNAL` | `external-link` | `FACEBOOK` | `facebook` |
1116
+ | `GOOGLE` | `google` | `INFO` | `info` |
1117
+ | `FILE` | `file` | `INSTAGRAM` | `instagram` |
1118
+ | `LINKEDIN` | `linkedin` | `MASTODON` | `mastodon` |
1119
+ | `MENU` | `menu` | `SEARCH` | `search` |
1120
+ | `SUCCESS` | `success` | `TWITTER` | `twitter` |
1121
+ | `WARNING` | `warning` | `YOUTUBE` | `youtube` |
1122
+ | `EMAIL` | `email` | `HEART` | `heart-selected` |
1123
+ | `EMPTY_HEART` | `heart-unselected` | `LINK` | `link` |
1124
+ | `LOCK` | `lock` | `LOGIN` | `login` |
1125
+ | `MORE` | `more` | `EDIT` | `pencil` |
1126
+ | `PHONE` | `phone` | `PIN` | `pin` |
1127
+ | `PRINT` | `print` | `RESTART` | `restart` |
1128
+ | `STAR` | `star-selected` | `EMPTY_STAR` | `star-unselected` |
1129
+ | `TICK` | `tick` | `TRASH` | `trash` |
1130
+ | `USER` | `user` | `SHARE` | `share` |
1131
+ | `QUESTION` | `question` | | |
1132
+
1133
+ ---
1134
+
1135
+ ## Global Variables
1136
+
1137
+ These globals are injected by the host environment (Drupal CMS, etc.) and are declared in `.oxlintrc.json` so the linter does not flag them as undefined. They are **not** provided by this design system.
1138
+
1139
+ | Global | Type | Source | Purpose |
1140
+ |---|---|---|---|
1141
+ | `Drupal` | `object` | Drupal CMS | CMS integration — use `loadOnReady()` rather than accessing directly |
1142
+ | `once` / `window.once` | `function` | [`@drupal/once`](https://www.npmjs.com/package/@drupal/once) (standalone or Drupal core) | DOM initialisation guard — assign to `window.once` in non-Drupal projects |
1143
+ | `Mixtape` | `object` | Internal layer | App-level configuration and feature flags |
1144
+
1145
+ ### window.once / once
1146
+
1147
+ `once()` is a DOM initialisation guard that ensures a selector is only processed once per key, even if the initialisation function is called multiple times. The design system accesses it exclusively via `window.once` and guards every call with `Object.hasOwn(window, "once")`, so **it works with or without Drupal**.
1148
+
1149
+ **Standalone usage (non-Drupal projects):**
1150
+
1151
+ Install the standalone package:
1152
+
1153
+ ```bash
1154
+ npm install @drupal/once
1155
+ ```
1156
+
1157
+ Then assign it to `window` once at your entry point:
1158
+
1159
+ ```typescript
1160
+ import once from "@drupal/once"
1161
+ window.once = once
1162
+ ```
1163
+
1164
+ That's all — `IntersectionLoader`, `BreakpointLoader`, and `CookieCompliance` will all pick it up automatically.
1165
+
1166
+ **Inside Drupal:** `once` is already on `window` as part of Drupal core — no setup needed.
1167
+
1168
+ **Signature (from `src/Utility/global.d.ts`):**
1169
+
1170
+ ```typescript
1171
+ window.once(name: string, selector: string, context?: any): NodeListOf<HTMLElement>
1172
+ ```
1173
+
1174
+ - Returns a `NodeList` of elements that have **not** already been processed for this `name`.
1175
+ - If already processed, returns an empty `NodeList` — preventing double-initialisation.
1176
+ - `@see` [npm: @drupal/once](https://www.npmjs.com/package/@drupal/once)
1177
+
1178
+ **Example:**
1179
+
1180
+ ```typescript
1181
+ const [el] = window.once("cookie-compliance", "#cookie-compliance", context)
1182
+ if (el) {
1183
+ const instance = new CookieCompliance(el)
1184
+ instance.init()
1185
+ }
1186
+ ```
1187
+
1188
+
1189
+ ### Mixtape
1190
+
1191
+ `window.Mixtape` is the internal app-level namespace used by the `Keyboard` class to coordinate menu focus and popup state across components. Its shape is typed in `src/Utility/global.d.ts`:
1192
+
1193
+ ```typescript
1194
+ window.Mixtape = {
1195
+ // Stack of open popups. Keyboard Escape closes the most recent one.
1196
+ Popups: Array<{
1197
+ id: string // ID of the popup element
1198
+ closefn: () => void // Function to close it
1199
+ }>,
1200
+
1201
+ // Currently active keyboard-navigable menu.
1202
+ Menu: {
1203
+ context?: HTMLElement // The menu container
1204
+ items?: NodeListOf<HTMLAnchorElement | HTMLButtonElement> // Focusable items
1205
+ active?: number // Index of currently focused item
1206
+ }
1207
+ }
1208
+ ```
1209
+
1210
+ Direct use of `window.Mixtape` in component code is rare — use the `Keyboard` class methods (`attachMenu`, `attachPopup`, etc.) which manage this state for you.
1211
+
1212
+ ### Drupal
1213
+
1214
+ The `Drupal` global is provided by Drupal CMS. Direct use of `Drupal.behaviors` in components is rare — use the `loadOnReady()` utility instead, which handles the Drupal/non-Drupal fallback automatically. See the JSDoc on `loadOnReady` in `src/Utility/utilities.ts` for details.
1215
+