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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (199) hide show
  1. package/.claude/skills/create-mxds-component/SKILL.md +77 -0
  2. package/DESIGN.md +595 -0
  3. package/README.md +1 -2
  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/button.css +1 -1
  9. package/dist/build/card.css +1 -1
  10. package/dist/build/chunks/Accordion-D2wp8hBG.js +44 -0
  11. package/dist/build/chunks/AccordionGroup-BYtoAFxg.js +47 -0
  12. package/dist/build/chunks/AccordionMobile-D3sTeiM5.js +26 -0
  13. package/dist/build/chunks/Autocomplete-kRo8RNbC.js +135 -0
  14. package/dist/build/chunks/BackToTop-4M-Wl7z3.js +64 -0
  15. package/dist/build/chunks/ClosableAlert-yy84gae6.js +55 -0
  16. package/dist/build/chunks/CookieConsent-wd8gQ2Oe.js +18 -0
  17. package/dist/build/chunks/Dialog-DWoXW-7Z.js +48 -0
  18. package/dist/build/chunks/DropMenu-CSTZq0Zt.js +68 -0
  19. package/dist/build/chunks/FeedbackForm-BochXEDx.js +64 -0
  20. package/dist/build/chunks/Gallery-Rkfv7Mbb.js +165 -0
  21. package/dist/build/chunks/GlobalToggle-CzlkpLpc.js +82 -0
  22. package/dist/build/chunks/InPageNavigation-D1Kk-rCm.js +86 -0
  23. package/dist/build/chunks/Navigation-CDQnN61h.js +147 -0
  24. package/dist/build/chunks/Popover-CiTAftNU.js +30 -0
  25. package/dist/build/chunks/Sticky-DoVndENm.js +51 -0
  26. package/dist/build/chunks/Tabs-BDlxCDNh.js +160 -0
  27. package/dist/build/chunks/Translate-bmM38j6z.js +46 -0
  28. package/dist/build/chunks/cookie-compliance-CG6x-TcV.js +119 -0
  29. package/dist/build/chunks/enums-BrHxPZZ_.js +36 -0
  30. package/dist/build/chunks/polyfills-D-qh1a_2.js +33 -0
  31. package/dist/build/chunks/utilities-Dd-08SW6.js +255 -0
  32. package/dist/build/content-block.css +1 -1
  33. package/dist/build/cookie-consent.entry.js +1 -1
  34. package/dist/build/dialog.entry.js +1 -1
  35. package/dist/build/drop-menu.entry.js +1 -1
  36. package/dist/build/elements.js +147 -1
  37. package/dist/build/feedback-form.css +1 -0
  38. package/dist/build/feedback-form.entry.js +1 -0
  39. package/dist/build/filters.entry.js +145 -1
  40. package/dist/build/gallery.entry.js +1 -1
  41. package/dist/build/global-alert.entry.js +1 -1
  42. package/dist/build/header.css +1 -1
  43. package/dist/build/header.entry.js +1 -1
  44. package/dist/build/icon.css +1 -1
  45. package/dist/build/in-page-navigation.entry.js +1 -1
  46. package/dist/build/navigation.entry.js +1 -1
  47. package/dist/build/pagination.css +1 -1
  48. package/dist/build/popover.entry.js +1 -1
  49. package/dist/build/react.js +2270 -1
  50. package/dist/build/sticky.entry.js +1 -1
  51. package/dist/build/tabs.entry.js +1 -1
  52. package/dist/build/translate.css +1 -0
  53. package/dist/build/translate.entry.js +1 -0
  54. package/dist/build/utility-list.entry.js +53 -1
  55. package/dist/types/Component/BackToTop/BackToTop.d.ts +20 -0
  56. package/dist/types/Component/BackToTop/BackToTop.d.ts.map +1 -0
  57. package/dist/types/Component/BackToTop/Elements/BackToTop.d.ts +27 -0
  58. package/dist/types/Component/BackToTop/Elements/BackToTop.d.ts.map +1 -0
  59. package/dist/types/Component/BackToTop/back-to-top.entry.d.ts +2 -0
  60. package/dist/types/Component/BackToTop/back-to-top.entry.d.ts.map +1 -0
  61. package/dist/types/Component/DropMenu/DropMenu.d.ts +2 -1
  62. package/dist/types/Component/DropMenu/DropMenu.d.ts.map +1 -1
  63. package/dist/types/Component/Tabs/Components/TabDropMenu.d.ts.map +1 -1
  64. package/dist/types/Component/Tabs/Elements/Tabs.d.ts.map +1 -1
  65. package/dist/types/Component/Translate/Elements/Translate.d.ts +23 -0
  66. package/dist/types/Component/Translate/Elements/Translate.d.ts.map +1 -0
  67. package/dist/types/Component/Translate/Translate.d.ts +37 -0
  68. package/dist/types/Component/Translate/Translate.d.ts.map +1 -0
  69. package/dist/types/Component/Translate/languages.d.ts +11 -0
  70. package/dist/types/Component/Translate/languages.d.ts.map +1 -0
  71. package/dist/types/Component/Translate/translate.entry.d.ts +2 -0
  72. package/dist/types/Component/Translate/translate.entry.d.ts.map +1 -0
  73. package/dist/types/Form/FeedbackForm/Elements/FeedbackForm.d.ts +37 -0
  74. package/dist/types/Form/FeedbackForm/Elements/FeedbackForm.d.ts.map +1 -0
  75. package/dist/types/Form/FeedbackForm/FeedbackForm.d.ts +36 -0
  76. package/dist/types/Form/FeedbackForm/FeedbackForm.d.ts.map +1 -0
  77. package/dist/types/Form/FeedbackForm/feedback-form.entry.d.ts +2 -0
  78. package/dist/types/Form/FeedbackForm/feedback-form.entry.d.ts.map +1 -0
  79. package/dist/types/Utility/Elements/google-translate-widget.d.ts +91 -0
  80. package/dist/types/Utility/Elements/google-translate-widget.d.ts.map +1 -0
  81. package/dist/types/Utility/utilities.d.ts +2 -0
  82. package/dist/types/Utility/utilities.d.ts.map +1 -1
  83. package/dist/types/elements.d.ts +3 -0
  84. package/dist/types/elements.d.ts.map +1 -1
  85. package/dist/types/enums.d.ts +3 -1
  86. package/dist/types/enums.d.ts.map +1 -1
  87. package/dist/types/react.d.ts +3 -0
  88. package/dist/types/react.d.ts.map +1 -1
  89. package/docs/API.md +1215 -0
  90. package/docs/CSSGuidelines.md +891 -0
  91. package/docs/CodeQuality.md +940 -0
  92. package/docs/ComponentArchitecture.md +714 -0
  93. package/docs/Contributing.md +626 -0
  94. package/docs/MemoryManagement.md +598 -0
  95. package/docs/Migrations.md +6 -0
  96. package/docs/README.md +100 -0
  97. package/docs/Security.md +444 -0
  98. package/docs/Testing.md +739 -0
  99. package/docs/Troubleshooting.md +674 -0
  100. package/package.json +6 -3
  101. package/src/Atom/Button/Button.tsx +1 -1
  102. package/src/Atom/Button/__snapshots__/Button.stories.tsx.snap +2 -2
  103. package/src/Atom/Button/_buttons.css +1 -0
  104. package/src/Atom/Icon/README.md +1 -1
  105. package/src/Atom/Icon/_icon.css +1 -0
  106. package/src/Atom/Icon/images/thumb.svg +1 -0
  107. package/src/Atom/Media/__snapshots__/Media.stories.ts.snap +4 -9
  108. package/src/Atom/Media/media.twig +4 -5
  109. package/src/Component/BackToTop/BackToTop.stories.ts +127 -0
  110. package/src/Component/BackToTop/BackToTop.stories.tsx +53 -0
  111. package/src/Component/BackToTop/BackToTop.tsx +120 -0
  112. package/src/Component/BackToTop/Elements/BackToTop.ts +119 -0
  113. package/src/Component/BackToTop/README.md +164 -0
  114. package/src/Component/BackToTop/__snapshots__/BackToTop.stories.ts.snap +24 -0
  115. package/src/Component/BackToTop/__snapshots__/BackToTop.stories.tsx.snap +3 -0
  116. package/src/Component/BackToTop/back-to-top.css +70 -0
  117. package/src/Component/BackToTop/back-to-top.entry.js +1 -0
  118. package/src/Component/BackToTop/back-to-top.twig +26 -0
  119. package/src/Component/Card/Components/CardContent.tsx +1 -1
  120. package/src/Component/Card/Components/CardMedia.tsx +1 -1
  121. package/src/Component/Card/__snapshots__/Card.stories.tsx.snap +3 -3
  122. package/src/Component/Card/card.css +1 -1
  123. package/src/Component/Carousel/Carousel.stories.ts +66 -36
  124. package/src/Component/Carousel/__snapshots__/Carousel.stories.tsx.snap +5 -5
  125. package/src/Component/ContentBlock/Components/ContentBlockMedia.tsx +1 -1
  126. package/src/Component/ContentBlock/content-block.css +1 -1
  127. package/src/Component/DropMenu/DropMenu.stories.ts +21 -2
  128. package/src/Component/DropMenu/DropMenu.tsx +14 -4
  129. package/src/Component/DropMenu/__snapshots__/DropMenu.stories.ts.snap +3 -3
  130. package/src/Component/DropMenu/__snapshots__/DropMenu.stories.tsx.snap +2 -2
  131. package/src/Component/DropMenu/drop-menu.twig +7 -1
  132. package/src/Component/HeroBanner/__snapshots__/HeroBanner.stories.tsx.snap +1 -1
  133. package/src/Component/InPageNavigation/__snapshots__/InPageNavigation.stories.ts.snap +48 -13
  134. package/src/Component/InPageNavigation/in-page-navigation.twig +4 -6
  135. package/src/Component/ListItem/Components/ListItemMedia.tsx +1 -1
  136. package/src/Component/Pagination/Pagination.stories.ts +57 -27
  137. package/src/Component/Pagination/pagination.css +16 -7
  138. package/src/Component/Popover/__snapshots__/Popover.stories.tsx.snap +3 -3
  139. package/src/Component/Tabs/Components/TabDropMenu.tsx +7 -1
  140. package/src/Component/Tabs/Elements/Tabs.ts +13 -3
  141. package/src/Component/Tabs/Tabs.stories.ts +33 -12
  142. package/src/Component/Tabs/Tabs.stories.tsx +28 -14
  143. package/src/Component/Tabs/__snapshots__/Tabs.stories.ts.snap +3 -3
  144. package/src/Component/Tabs/__snapshots__/Tabs.stories.tsx.snap +2 -2
  145. package/src/Component/Translate/Elements/Translate.ts +76 -0
  146. package/src/Component/Translate/README.md +283 -0
  147. package/src/Component/Translate/Translate.stories.ts +87 -0
  148. package/src/Component/Translate/Translate.stories.tsx +77 -0
  149. package/src/Component/Translate/Translate.tsx +213 -0
  150. package/src/Component/Translate/__snapshots__/Translate.stories.ts.snap +18 -0
  151. package/src/Component/Translate/__snapshots__/Translate.stories.tsx.snap +3 -0
  152. package/src/Component/Translate/languages.ts +39 -0
  153. package/src/Component/Translate/translate.css +62 -0
  154. package/src/Component/Translate/translate.entry.js +1 -0
  155. package/src/Component/Translate/translate.twig +44 -0
  156. package/src/Form/Autocomplete/Autocomplete.stories.tsx +2 -1
  157. package/src/Form/FeedbackForm/Elements/FeedbackForm.ts +122 -0
  158. package/src/Form/FeedbackForm/FeedbackForm.stories.ts +99 -0
  159. package/src/Form/FeedbackForm/FeedbackForm.stories.tsx +73 -0
  160. package/src/Form/FeedbackForm/FeedbackForm.tsx +196 -0
  161. package/src/Form/FeedbackForm/README.md +233 -0
  162. package/src/Form/FeedbackForm/__snapshots__/FeedbackForm.stories.ts.snap +125 -0
  163. package/src/Form/FeedbackForm/__snapshots__/FeedbackForm.stories.tsx.snap +5 -0
  164. package/src/Form/FeedbackForm/feedback-form.css +74 -0
  165. package/src/Form/FeedbackForm/feedback-form.entry.js +1 -0
  166. package/src/Form/FeedbackForm/feedback-form.twig +80 -0
  167. package/src/Layout/Header/Components/GlobalToggle.tsx +1 -1
  168. package/src/Layout/Header/Header.stories.ts +53 -48
  169. package/src/Layout/Header/Header.stories.tsx +35 -22
  170. package/src/Layout/Header/Header.tsx +2 -2
  171. package/src/Layout/Header/__snapshots__/Header.stories.ts.snap +14 -10
  172. package/src/Layout/Header/__snapshots__/Header.stories.tsx.snap +2 -2
  173. package/src/Layout/Header/_toggles.css +7 -7
  174. package/src/Layout/Page/__snapshots__/Page.stories.tsx.snap +1 -1
  175. package/src/Utility/Elements/google-translate-widget.ts +293 -0
  176. package/src/Utility/global.d.ts +4 -0
  177. package/src/Utility/utilities.ts +2 -0
  178. package/src/elements.ts +3 -0
  179. package/src/enums.ts +2 -0
  180. package/src/react.ts +3 -0
  181. package/dist/build/chunks/Accordion-GN0sME4W.js +0 -1
  182. package/dist/build/chunks/AccordionGroup-Bzqp5__q.js +0 -1
  183. package/dist/build/chunks/AccordionMobile-DG5jSe8Q.js +0 -1
  184. package/dist/build/chunks/Autocomplete-B4RtkAyh.js +0 -1
  185. package/dist/build/chunks/ClosableAlert-DEhj8Nxm.js +0 -1
  186. package/dist/build/chunks/CookieConsent-BkCMRVST.js +0 -1
  187. package/dist/build/chunks/Dialog-Haahn-Yn.js +0 -1
  188. package/dist/build/chunks/DropMenu-1baQc8Yc.js +0 -1
  189. package/dist/build/chunks/Gallery-D8NGXy2Q.js +0 -1
  190. package/dist/build/chunks/GlobalToggle-B52fdBQu.js +0 -1
  191. package/dist/build/chunks/InPageNavigation-Dyn2TR-N.js +0 -1
  192. package/dist/build/chunks/Navigation-hOrfLq7h.js +0 -1
  193. package/dist/build/chunks/Popover-Cxg-s5Jn.js +0 -1
  194. package/dist/build/chunks/Sticky-BGCuuEJK.js +0 -1
  195. package/dist/build/chunks/Tabs-DYy1YTdU.js +0 -15
  196. package/dist/build/chunks/cookie-compliance-Bbm-0vpj.js +0 -1
  197. package/dist/build/chunks/enums-DLV_1NAf.js +0 -1
  198. package/dist/build/chunks/polyfills-C7pRt54M.js +0 -1
  199. package/dist/build/chunks/utilities-_Bq3TxAQ.js +0 -1
@@ -0,0 +1,940 @@
1
+ # Code Quality Guidelines
2
+
3
+ This document provides a quick reference for code quality standards. For detailed guides, see the focused documentation below.
4
+
5
+ ## 📚 Focused Documentation
6
+
7
+ - **[Component Architecture](./ComponentArchitecture.md)** - How to build Web Components and React components
8
+ - **[Memory Management](./MemoryManagement.md)** - Prevent memory leaks (essential reading!)
9
+ - **[Security Guidelines](./Security.md)** - XSS prevention, input validation, secure practices
10
+ - **[CSS Guidelines](./CSSGuidelines.md)** - BEM, tokens, logical properties, custom media
11
+ - **[Testing Guide](./Testing.md)** - Storybook, snapshots, accessibility testing
12
+ - **[Contributing Guide](./Contributing.md)** - Git workflow, commit conventions
13
+ - **[API Documentation](./API.md)** - Validators utility and helper functions
14
+ - **[Troubleshooting](./Troubleshooting.md)** - Common issues and solutions
15
+
16
+ ## Table of Contents
17
+
18
+ - [Quick Reference](#quick-reference)
19
+ - [Memory Management](#memory-management)
20
+ - [Security](#security)
21
+ - [TypeScript](#typescript)
22
+ - [Component Architecture](#component-architecture)
23
+ - [CSS & Styling](#css--styling)
24
+ - [Testing](#testing)
25
+ - [Code Formatting](#code-formatting)
26
+ - [Git & Commits](#git--commits)
27
+ - [Validation](#validation)
28
+
29
+ ---
30
+
31
+ ## Quick Reference
32
+
33
+ ### Essential Rules
34
+
35
+ 1. ✅ **Always use AbortController** for event listeners → [Details](./MemoryManagement.md#event-listeners)
36
+ 2. ✅ **Always implement disconnectedCallback()** → [Details](./MemoryManagement.md#the-solution-store-as-properties)
37
+ 3. ✅ **Never use dangerouslySetInnerHTML or innerHTML** → [Details](./Security.md#xss-prevention)
38
+ 4. ✅ **Always validate input** with validators utility → [Details](./Security.md#input-validation)
39
+ 5. ✅ **Use BEM naming** for all CSS → [Details](./CSSGuidelines.md#bem-naming)
40
+ 6. ✅ **Use logical properties** not physical → [Details](./CSSGuidelines.md#logical-properties)
41
+ 7. ✅ **Import custom media** in every CSS file → [Details](./CSSGuidelines.md#custom-media)
42
+ 8. ✅ **Follow TypeScript types** strictly → [Details](#typescript)
43
+ 9. ✅ **Write Storybook stories** for all components → [Details](./Testing.md)
44
+ 10. ✅ **Use conventional commits** → [Details](#git--commits)
45
+
46
+ ---
47
+
48
+ ## Memory Management
49
+
50
+ > 📖 **[Full Memory Management Guide](./MemoryManagement.md)**
51
+
52
+ ### Critical Rules
53
+
54
+ 1. ✅ **Use AbortController** for event listeners
55
+ 2. ✅ **Store observers** as properties (iO, rO, mO)
56
+ 3. ✅ **Store timer IDs** and clear them
57
+ 4. ✅ **Implement disconnectedCallback()** always
58
+ 5. ✅ **Return cleanup** from React useEffect
59
+
60
+ ### Quick Pattern
61
+
62
+ ```typescript
63
+ class Component extends HTMLElement {
64
+ controller: AbortController
65
+ iO: IntersectionObserver
66
+ timeout: ReturnType<typeof setTimeout>
67
+
68
+ constructor() {
69
+ super()
70
+ this.controller = new AbortController()
71
+ }
72
+
73
+ connectedCallback() {
74
+ // Setup with signal
75
+ const { signal } = this.controller
76
+ this.addEventListener('click', this.handleClick, { signal })
77
+
78
+ this.iO = new IntersectionObserver(...)
79
+ this.timeout = setTimeout(...)
80
+ }
81
+
82
+ disconnectedCallback() {
83
+ // Cleanup everything
84
+ this.controller.abort()
85
+ this.iO?.disconnect()
86
+ clearTimeout(this.timeout)
87
+ }
88
+ }
89
+ ```
90
+
91
+ **→ See [Memory Management Guide](./MemoryManagement.md) for complete details**
92
+
93
+ ---
94
+
95
+ ## Security
96
+
97
+ > 📖 **[Full Security Guidelines](./Security.md)**
98
+
99
+ ### Critical Rules
100
+
101
+ 1. ❌ **Never** use `dangerouslySetInnerHTML`
102
+ 2. ❌ **Never** use `innerHTML`
103
+ 3. ✅ **Always** validate URLs with `validators.url()`
104
+ 4. ✅ **Always** validate numbers with `validators.integer()`
105
+ 5. ✅ **Always** wrap JSON.parse with `validators.json()`
106
+ 6. ✅ Use environment-specific logging
107
+ 7. ✅ Add `rel="noopener noreferrer"` to external `target="_blank"` links
108
+
109
+ ### Quick Patterns
110
+
111
+ ```typescript
112
+ // XSS Prevention
113
+ import { createElement } from "../Utility/utilities"
114
+ const el = createElement(htmlString) // Safe, not innerHTML
115
+
116
+ // URL Validation
117
+ import { validators } from "../Utility/utilities"
118
+ if (!validators.url(url)) {
119
+ url = window.location.href // Safe fallback
120
+ }
121
+
122
+ // Number Validation
123
+ if (!validators.integer(page, 1, lastPage)) {
124
+ return // Reject invalid input
125
+ }
126
+
127
+ // JSON Validation
128
+ if (!validators.json(stored)) {
129
+ return defaultValue
130
+ }
131
+ ```
132
+
133
+ **→ See [Security Guidelines](./Security.md) for OWASP Top 10 coverage and complete details**
134
+
135
+ ---
136
+
137
+ ## TypeScript
138
+
139
+ ### Type Safety Best Practices
140
+
141
+ 1. **Use strict TypeScript settings** (already configured in tsconfig.json)
142
+ - `isolatedModules: true`
143
+ - `skipLibCheck: true`
144
+ - `esModuleInterop: true`
145
+
146
+ 2. **Use explicit types for component props**
147
+
148
+ ```typescript
149
+ // ✅ Good
150
+ type CardProps = PropsWithChildren & {
151
+ reversed?: boolean
152
+ block?: boolean
153
+ as?: AsTypes
154
+ background?: BackgroundStyles
155
+ className?: string
156
+ }
157
+
158
+ const Card = ({ children, reversed = null, ... }: CardProps): JSX.Element => {
159
+ // ...
160
+ }
161
+ ```
162
+
163
+ 3. **Use proper return types for functions**
164
+
165
+ ```typescript
166
+ // ✅ Good
167
+ export const makeAnchor = (string: string, length: number = 20): string => {
168
+ // ...
169
+ }
170
+ ```
171
+
172
+ 4. **Use `@ts-expect-error` sparingly and document why**
173
+
174
+ ```typescript
175
+ // ✅ Good - Explains why
176
+ // @ts-expect-error inert is allowed but not in TS yet
177
+ element.inert = true
178
+ ```
179
+
180
+ 5. **Use oxlint disable comments when necessary** and document why
181
+
182
+ ```typescript
183
+ // For user-facing errors where logging is intentional
184
+ console.error("Could not copy URL") // eslint-disable-line no-console
185
+
186
+ // For React patterns that need exceptions
187
+ /* eslint-disable consistent-return, @eslint-react/hooks-extra/no-direct-set-state-in-use-effect */
188
+
189
+ // At file level for specific cases
190
+ /* eslint-disable no-console */
191
+ ```
192
+
193
+ 6. **Avoid `any` - use `unknown` or proper types**
194
+ ```typescript
195
+ // ⚠️ Acceptable for utility functions with generic types
196
+ validators: {
197
+ oneOf: <T>(value: T, allowedValues: T[]): boolean => {
198
+ return allowedValues.includes(value)
199
+ }
200
+ }
201
+ ```
202
+
203
+ ---
204
+
205
+ ## Component Architecture
206
+
207
+ > 📖 **[Full Component Architecture Guide](./ComponentArchitecture.md)**
208
+
209
+ ### Web Components (Custom Elements)
210
+
211
+ 1. **Component structure**
212
+
213
+ ```typescript
214
+ export default class MyComponent extends HTMLElement {
215
+ // 1. Properties
216
+ internals_: ElementInternals
217
+ controller: AbortController
218
+
219
+ // 2. Constructor
220
+ constructor() {
221
+ super()
222
+ this.internals_ = this.attachInternals()
223
+ this.controller = new AbortController()
224
+ }
225
+
226
+ // 3. Lifecycle
227
+ connectedCallback(): void {
228
+ // Setup
229
+ }
230
+
231
+ disconnectedCallback(): void {
232
+ // Cleanup
233
+ this.controller.abort()
234
+ }
235
+
236
+ // 4. Handlers
237
+ handleClick = (): void => {
238
+ // ...
239
+ }
240
+
241
+ // 5. Getters
242
+ get trigger(): HTMLButtonElement {
243
+ const trigger = this.querySelector("button")
244
+ if (!trigger) {
245
+ throw new Error(`${this.localName} must contain a <button>`)
246
+ }
247
+ return trigger
248
+ }
249
+ }
250
+
251
+ // 6. Registration
252
+ if (!customElements.get("mx-component")) {
253
+ customElements.define("mx-component", MyComponent)
254
+ }
255
+
256
+ // 7. TypeScript declaration
257
+ declare global {
258
+ interface HTMLElementTagNameMap {
259
+ "mx-component": MyComponent
260
+ }
261
+ }
262
+ ```
263
+
264
+ 2. **Use getters for DOM queries** with validation
265
+
266
+ ```typescript
267
+ get details(): HTMLDetailsElement | null {
268
+ const details = this.querySelector("details")
269
+ if (!details) {
270
+ throw new Error(`${this.localName} must contain a <details> element.`)
271
+ }
272
+ return details
273
+ }
274
+ ```
275
+
276
+ 3. **Generate IDs when needed**
277
+ ```typescript
278
+ details.id = details.id || this.generatedId()
279
+ ```
280
+ > `generatedId()` is a private method on Web Component classes. See [API Documentation → generatedId()](./API.md#generatedid) for details.
281
+
282
+ ### React Components
283
+
284
+ 1. **Component structure**
285
+
286
+ ```typescript
287
+ import { JSX, PropsWithChildren } from "react"
288
+ import classNames from "classnames"
289
+
290
+ type ComponentProps = PropsWithChildren & {
291
+ // Props with defaults
292
+ reversed?: boolean
293
+ // Required props
294
+ className?: string
295
+ }
296
+
297
+ const Component = ({
298
+ children,
299
+ reversed = false,
300
+ className = null,
301
+ }: ComponentProps): JSX.Element => {
302
+ return (
303
+ <div className={classNames("mx-component", className, {
304
+ "mx-component--reversed": reversed,
305
+ })}>
306
+ {children}
307
+ </div>
308
+ )
309
+ }
310
+
311
+ export default Component
312
+ ```
313
+
314
+ 2. **Use proper React hooks**
315
+
316
+ ```typescript
317
+ // State
318
+ const [activePage, setActivePage] = useState(0)
319
+
320
+ // Refs
321
+ const polyfillsInstance = useRef<any>(null)
322
+
323
+ // Effects with cleanup
324
+ useEffect(() => {
325
+ const controller = new AbortController()
326
+ // Setup
327
+
328
+ return () => {
329
+ controller.abort() // Cleanup
330
+ }
331
+ }, [dependencies])
332
+
333
+ // Memoization
334
+ const value = useMemo(() => {
335
+ return expensiveCalculation(dep)
336
+ }, [dep])
337
+ ```
338
+
339
+ 3. **Use classNames for conditional classes**
340
+
341
+ ```typescript
342
+ import classNames from "classnames"
343
+
344
+ <div className={classNames("mx-card", className, {
345
+ "mx-card--reversed": reversed,
346
+ "mx-card--block": block,
347
+ [`mx-background--${background}`]: background,
348
+ })} />
349
+ ```
350
+
351
+ ---
352
+
353
+ ## CSS & Styling
354
+
355
+ > 📖 **[Full CSS Guidelines](./CSSGuidelines.md)**
356
+
357
+ > ⚙️ **[PostCSS Pipeline](./CSSGuidelines.md#postcss-pipeline)** — explains non-standard syntax: `token()`, `@media (--medium-up)`, and px→rem conversion
358
+
359
+ ### BEM Naming Convention
360
+
361
+ Follow BEM (Block Element Modifier) strictly:
362
+
363
+ ```css
364
+ /* ✅ Good - BEM structure */
365
+ .mx-card {
366
+ }
367
+ .mx-card__header {
368
+ }
369
+ .mx-card__body {
370
+ }
371
+ .mx-card--reversed {
372
+ }
373
+ .mx-card--block {
374
+ }
375
+
376
+ /* ❌ Bad - Not BEM */
377
+ .mx-card-header {
378
+ }
379
+ .mx-card.reversed {
380
+ }
381
+ ```
382
+
383
+ ### CSS Organization
384
+
385
+ 1. **File structure**
386
+
387
+ ```
388
+ Component/
389
+ _component-partial.css (internal styles)
390
+ component.css (public API)
391
+ Component.tsx
392
+ component.twig
393
+ ```
394
+
395
+ 2. **Import custom media in every CSS file**
396
+
397
+ This is only required if you're not using something like [@csstools/postcss-global-data](https://www.npmjs.com/package/@csstools/postcss-global-data).
398
+
399
+ ```css
400
+ @import "../../_custom-media.css";
401
+
402
+ .mx-component {
403
+ /* Mobile first */
404
+ display: block;
405
+
406
+ /* Responsive */
407
+ @media (--medium-up) {
408
+ display: flex;
409
+ }
410
+ }
411
+ ```
412
+
413
+ 3. **Use CSS custom properties from constants.css**
414
+
415
+ Design tokens (in `src/tokens.js`) are used with PostCSS to generate `src/constants.css` which defines all CSS custom properties. Load `constants.css` once at the page level, then all components can use the custom properties:
416
+
417
+ ```css
418
+ /* Component CSS - NO import needed */
419
+ .mx-component {
420
+ color: var(--colour-foreground);
421
+ padding: var(--spacing-m);
422
+ font-size: var(--font-size-m);
423
+ }
424
+ ```
425
+
426
+ The custom properties are available because `constants.css` is loaded at the page level, preventing duplication.
427
+
428
+ 4. **Use logical properties**
429
+
430
+ ```css
431
+ /* ✅ Good - Logical properties */
432
+ padding-inline: 1rem;
433
+ margin-block-start: 1rem;
434
+ inset-inline-start: 0;
435
+
436
+ /* ❌ Avoid - Physical properties */
437
+ padding-left: 1rem;
438
+ margin-top: 1rem;
439
+ left: 0;
440
+ ```
441
+
442
+ 5. **Nest selectors properly**
443
+
444
+ ```css
445
+ /* ✅ Good - Use & for nesting */
446
+ .mx-component {
447
+ & button {
448
+ }
449
+ }
450
+
451
+ .mx-component--modifier {
452
+ & button {
453
+ }
454
+ }
455
+ .mx-component__element {
456
+ & button {
457
+ }
458
+ }
459
+
460
+ /* Stylelint enforces this pattern */
461
+ ```
462
+
463
+ 6. **Use cascade layers**
464
+ ```css
465
+ /* All component styles are wrapped in a layer */
466
+ @layer design-system.components {
467
+ .mx-component {
468
+ }
469
+ }
470
+ ```
471
+
472
+ ---
473
+
474
+ ## Validation
475
+
476
+ > 📖 **[Full API Documentation](./API.md)**
477
+
478
+ ### Use the validators utility
479
+
480
+ Always use the centralized `validators` utility for input validation:
481
+
482
+ ```typescript
483
+ import { validators } from "../Utility/utilities"
484
+
485
+ // URL validation
486
+ if (!validators.url(url)) {
487
+ // Handle invalid URL
488
+ }
489
+
490
+ // Integer with bounds
491
+ if (!validators.integer(page, 1, lastPage)) {
492
+ // Handle invalid page
493
+ }
494
+
495
+ // JSON validation
496
+ if (!validators.json(jsonString)) {
497
+ return fallbackValue
498
+ }
499
+
500
+ // Email validation
501
+ if (!validators.email(email)) {
502
+ // Handle invalid email
503
+ }
504
+
505
+ // Non-empty string
506
+ if (!validators.notEmpty(input)) {
507
+ return ""
508
+ }
509
+ ```
510
+
511
+ ### Available validators:
512
+
513
+ - `url(value, allowedProtocols)` - URL with protocol checking
514
+ - `email(value)` - Email format
515
+ - `number(value, min, max)` - Number with bounds
516
+ - `integer(value, min, max)` - Integer with bounds
517
+ - `string(value, minLength, maxLength)` - String with length
518
+ - `notEmpty(value)` - Non-empty after trim
519
+ - `pattern(value, regex)` - Regex matching
520
+ - `oneOf(value, allowedValues)` - Enum validation
521
+ - `array(value, minLength, maxLength)` - Array validation
522
+ - `json(value)` - JSON string validation
523
+ - `date(value, minDate, maxDate)` - Date validation
524
+ - `hexColor(value)` - Hex color validation
525
+
526
+ ---
527
+
528
+ ## Testing
529
+
530
+ > 📖 **[Full Testing Guide](./Testing.md)**
531
+
532
+ ### Storybook Stories
533
+
534
+ 1. **Create stories for all components**
535
+
536
+ ```typescript
537
+ import type { Meta, StoryObj } from "@storybook/html"
538
+
539
+ const meta: Meta = {
540
+ title: "Component/MyComponent",
541
+ tags: ["autodocs"],
542
+ render: args => {
543
+ return `<mx-component>${args.content}</mx-component>`
544
+ },
545
+ }
546
+
547
+ export default meta
548
+ type Story = StoryObj
549
+
550
+ export const Default: Story = {
551
+ args: {
552
+ content: "Default content",
553
+ },
554
+ }
555
+ ```
556
+
557
+ 2. **Use snapshots for visual regression**
558
+ - Snapshots are automatically generated from stories
559
+ - Located in `__snapshots__/` directories
560
+ - Run `pnpm test` to verify
561
+
562
+ 3. **Tag stories appropriately**
563
+ ```typescript
564
+ export const NoSnapshotExample: Story = {
565
+ tags: ["no-snapshot"], // Skip snapshot testing
566
+ args: { ... }
567
+ }
568
+ ```
569
+
570
+ ### Testing Commands
571
+
572
+ ```bash
573
+ # Run all tests
574
+ pnpm test
575
+
576
+ # Run HTML component tests
577
+ pnpm test-html
578
+
579
+ # Run React component tests
580
+ pnpm test-react
581
+
582
+ # Lint everything
583
+ pnpm lint
584
+
585
+ # Fix linting issues
586
+ pnpm format
587
+ ```
588
+
589
+ ---
590
+
591
+ ## Code Formatting
592
+
593
+ ### oxfmt Configuration
594
+
595
+ oxfmt is configured via `.oxfmtrc.json`:
596
+
597
+ - **Print width**: 100 characters
598
+ - **Arrow parens**: avoid
599
+ - **Semi**: false (no semicolons)
600
+ - **Sort package.json**: false
601
+ - **Ignore patterns**: node_modules, package files, storybook output, markdown docs
602
+
603
+ **Commands:**
604
+
605
+ ```bash
606
+ # Format all files
607
+ pnpm format
608
+
609
+ # Check formatting without making changes
610
+ pnpm lint-format
611
+ ```
612
+
613
+ > **Why oxfmt?** oxfmt is a Rust-based formatter that's significantly faster than Prettier while maintaining compatibility with Prettier configurations.
614
+
615
+ ### Oxlint
616
+
617
+ Using `oxlint` for fast, performant linting (configured in `.oxlintrc.json`):
618
+
619
+ **Configuration highlights:**
620
+
621
+ - **Plugins**: unicorn, typescript, oxc, import, react, react-perf, jsx-a11y, jest, jsdoc
622
+ - **Categories**: correctness (error), suspicious (warn), perf (warn)
623
+ - **Globals defined**: Drupal, once, Mixtape
624
+ - **Key rules**:
625
+ - `no-console: error` (use `// eslint-disable-line no-console` for exceptions)
626
+ - `import/no-unused-imports: error`
627
+ - `consistent-return: warn`
628
+ - `no-unused-vars: warn`
629
+
630
+ **Globals:**
631
+
632
+ These are injected by the host environment and declared in `.oxlintrc.json`. See [API Documentation → Global Variables](./API.md#global-variables) for full details.
633
+
634
+ | Global | Source | Purpose |
635
+ |---|---|---|
636
+ | `Drupal` | Drupal CMS | CMS integration — use `loadOnReady()` rather than accessing directly |
637
+ | `once` / `window.once` | [`@drupal/once`](https://www.npmjs.com/package/@drupal/once) (standalone or Drupal core) | DOM initialisation guard — assign to `window.once` in non-Drupal projects |
638
+ | `Mixtape` | Internal CMS layer | App-level config and feature flags |
639
+
640
+ **Commands:**
641
+
642
+ ```bash
643
+ # Lint TypeScript files
644
+ pnpm lint-js
645
+
646
+ # Fix auto-fixable issues
647
+ pnpm fix-js
648
+ ```
649
+
650
+ **React/JSX overrides:**
651
+
652
+ - `consistent-return: off` (React components don't always need explicit returns)
653
+ - `react/react-in-jsx-scope: off` (React 17+ JSX transform)
654
+ - `jsx-a11y/anchor-is-valid: off` (design system uses custom Link components)
655
+
656
+ ### Stylelint
657
+
658
+ Enforces:
659
+
660
+ - BEM naming convention
661
+ - Logical properties preferred
662
+ - Standard CSS rules
663
+ - No descending specificity issues (disabled for flexibility)
664
+
665
+ ```bash
666
+ # Lint CSS
667
+ pnpm lint-css
668
+
669
+ # Fix CSS issues
670
+ pnpm fix-css
671
+ ```
672
+
673
+ ### EditorConfig
674
+
675
+ Use consistent settings:
676
+
677
+ - UTF-8 encoding
678
+ - LF line endings
679
+ - 2 spaces for indentation
680
+ - Trim trailing whitespace
681
+
682
+ ---
683
+
684
+ ## Git & Commits
685
+
686
+ > 📖 **[Full Contributing Guide](./Contributing.md)**
687
+
688
+ ### Conventional Commits
689
+
690
+ Use Commitizen for standardized commit messages:
691
+
692
+ ```bash
693
+ pnpm commit
694
+ ```
695
+
696
+ Commit types:
697
+
698
+ - `feat`: New feature
699
+ - `fix`: Bug fix
700
+ - `docs`: Documentation only
701
+ - `style`: Code style (formatting, missing semicolons, etc)
702
+ - `refactor`: Code change that neither fixes a bug nor adds a feature
703
+ - `perf`: Performance improvement
704
+ - `test`: Adding or updating tests
705
+ - `chore`: Maintenance tasks, dependencies, etc
706
+
707
+ ### Example Commits
708
+
709
+ ```
710
+ feat: add validation utility for form inputs
711
+ fix: prevent memory leak in Carousel component
712
+ docs: update CodeQuality.md with validation patterns
713
+ refactor: use validators utility in Pagination
714
+ perf: optimize image loading with intersection observer
715
+ test: add snapshot tests for Button variants
716
+ chore: update dependencies to latest versions
717
+ ```
718
+
719
+ ---
720
+
721
+ ## File Organization
722
+
723
+ ### Component Structure
724
+
725
+ ```
726
+ Component/
727
+ README.md # Component documentation (optional)
728
+ Component.tsx # React component
729
+ Component.stories.tsx # React stories
730
+ component.twig # Twig template
731
+ Component.stories.ts # Twig stories
732
+ component.css # Public styles
733
+ _component-partial.css # Internal styles
734
+ Elements/ # Web Components
735
+ Component.ts
736
+ Components/ # React Sub-components
737
+ ComponentPart.tsx
738
+ __snapshots__/ # Snapshot regression tests
739
+ Component.stories.ts.snap
740
+ ```
741
+
742
+ ### Import Order
743
+
744
+ 1. External dependencies
745
+ 2. Internal utilities
746
+ 3. Types/enums
747
+ 4. Components
748
+ 5. Styles (in CSS/Twig files)
749
+
750
+ ```typescript
751
+ // 1. External
752
+ import { JSX, PropsWithChildren } from "react"
753
+ import classNames from "classnames"
754
+
755
+ // 2. Internal utilities
756
+ import { validators } from "../../Utility/utilities"
757
+
758
+ // 3. Types/enums
759
+ import { AsTypes } from "@pnx-mixtape/ids-shape"
760
+ import { BackgroundStyles } from "../../enums"
761
+
762
+ // 4. Components
763
+ import Button from "../Button/Button"
764
+
765
+ // Component code
766
+ ```
767
+
768
+ ---
769
+
770
+ ## Performance
771
+
772
+ > 📖 **[Full Component Architecture Guide - Performance](./ComponentArchitecture.md)**
773
+
774
+ 1. **Lazy load components when appropriate**
775
+ - Use `IntersectionLoader` for below-fold content
776
+ - Use `BreakpointLoader` for responsive loading
777
+
778
+ 2. **Use memoization for expensive calculations**
779
+
780
+ ```typescript
781
+ const value = useMemo(() => expensiveCalculation(dep), [dep])
782
+ ```
783
+
784
+ 3. **Debounce/throttle frequent events**
785
+
786
+ ```typescript
787
+ clearTimeout(this.debounced)
788
+ this.debounced = setTimeout(this.handler, 200)
789
+ ```
790
+
791
+ 4. **Use CSS containment where appropriate**
792
+ ```css
793
+ .mx-component {
794
+ contain: layout style paint;
795
+ }
796
+ ```
797
+
798
+ ---
799
+
800
+ ## Accessibility
801
+
802
+ > 📖 **[Full Testing Guide - Accessibility](./Testing.md#accessibility-testing)**
803
+
804
+ 1. **Always include ARIA labels**
805
+
806
+ ```html
807
+ <nav aria-label="Primary navigation">
808
+ <button aria-expanded="false" aria-controls="menu">
809
+ ```
810
+
811
+ 2. **Use semantic HTML**
812
+
813
+ ```html
814
+ <!-- ✅ Good -->
815
+ <nav>, <main>, <article>, <section>, <aside>
816
+
817
+ <!-- ❌ Avoid -->
818
+ <div class="nav">, <div class="main">
819
+ ```
820
+
821
+ 3. **Support keyboard navigation**
822
+ - Tab order must be logical
823
+ - Escape key closes modals/menus
824
+ - Arrow keys navigate menus
825
+
826
+ 4. **Include skip links**
827
+
828
+ ```html
829
+ <a href="#main-content" class="sr-only">Skip to main content</a>
830
+ ```
831
+
832
+ 5. **Test with Storybook a11y addon**
833
+ - Addon runs automatically
834
+ - Check for WCAG violations
835
+
836
+ ---
837
+
838
+ ## Documentation
839
+
840
+ 1. **Add JSDoc to public APIs**
841
+
842
+ ```typescript
843
+ /**
844
+ * Validates a URL string and optionally checks protocol.
845
+ * @param value - URL string to validate
846
+ * @param allowedProtocols - Array of allowed protocols
847
+ * @returns true if valid URL with allowed protocol
848
+ * @example
849
+ * validators.url('https://example.com') // true
850
+ */
851
+ url: (value: string, allowedProtocols: string[] = ["http:", "https:"]): boolean => {
852
+ ```
853
+
854
+ 2. **Create README files for complex components**
855
+ - Usage examples
856
+ - Props documentation
857
+ - Common patterns
858
+
859
+ 3. **Use MDX for Storybook documentation**
860
+
861
+ ```mdx
862
+ import { Meta } from "@storybook/blocks"
863
+
864
+ <Meta title="Component/MyComponent/Docs" />
865
+
866
+ # MyComponent
867
+
868
+ Description and usage guide...
869
+ ```
870
+
871
+ ---
872
+
873
+ ## Common Patterns
874
+
875
+ ### Singleton Pattern (CookieCompliance)
876
+
877
+ ```typescript
878
+ static create({ context = null } = {}): void {
879
+ const [el] = window.once("cookie-compliance", "#cookie-compliance", context)
880
+ if (el) {
881
+ const instance = new CookieCompliance(el)
882
+ instance.init()
883
+ window.CookieCompliance = instance // Global reference
884
+ }
885
+ }
886
+ ```
887
+
888
+ > `window.once` is a DOM initialisation guard from [`@drupal/once`](https://www.npmjs.com/package/@drupal/once) — works standalone (assign to `window.once` at your entry point) or automatically inside Drupal. See [API Documentation → Global Variables](./API.md#global-variables) for full details.
889
+
890
+ ### Disclosure Pattern (Accordion, Tabs)
891
+
892
+ ```typescript
893
+ import { DisclosureWidget } from "../../../Utility/utilities"
894
+
895
+ this.widget = new DisclosureWidget(this.trigger, this.target)
896
+ this.widget.init()
897
+ ```
898
+
899
+ ### Keyboard Navigation Pattern
900
+
901
+ ```typescript
902
+ import { Keyboard } from "../../../Utility/utilities"
903
+
904
+ const keyboard = new Keyboard()
905
+ keyboard.attachMenu(container, items)
906
+ keyboard.attachEventListeners(signal)
907
+ ```
908
+
909
+ ---
910
+
911
+ ## Checklist for New Components
912
+
913
+ - [ ] TypeScript types defined
914
+ - [ ] Props have defaults where appropriate
915
+ - [ ] Memory cleanup implemented (disconnectedCallback/useEffect cleanup)
916
+ - [ ] Input validation using `validators` utility
917
+ - [ ] Error handling with try-catch
918
+ - [ ] Environment-specific logging
919
+ - [ ] BEM class names
920
+ - [ ] Responsive styles with custom media
921
+ - [ ] Logical CSS properties
922
+ - [ ] ARIA labels and roles
923
+ - [ ] Keyboard navigation support
924
+ - [ ] Storybook stories created
925
+ - [ ] README documentation
926
+ - [ ] Snapshot tests passing
927
+ - [ ] No TypeScript errors
928
+ - [ ] Linting passes (`pnpm lint`)
929
+ - [ ] Manual testing in Storybook
930
+
931
+ ---
932
+
933
+ ## Resources
934
+
935
+ - [Project README](../README.md)
936
+ - [Storybook](https://mxds.pnx.io)
937
+ - [Conventional Commits](https://www.conventionalcommits.org/)
938
+ - [BEM Methodology](https://getbem.com/)
939
+ - [MDN Web Components](https://developer.mozilla.org/en-US/docs/Web/Web_Components)
940
+ - [React Documentation](https://react.dev/)