@times-design-system/components-wordpress 1.2.2-alpha.0 → 1.2.2-alpha.4

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 (72) hide show
  1. package/BLOCK_CREATION_CHECKLIST.md +131 -19
  2. package/README.md +10 -4
  3. package/SCSS_VARIABLES_REFERENCE.md +314 -0
  4. package/STYLING_SYNC_GUIDE.md +306 -0
  5. package/TRANSFORMATION_GUIDE.md +280 -13
  6. package/dist/blocks/ad-container/block.json +1 -0
  7. package/dist/blocks/ad-container/render.php +27 -0
  8. package/dist/blocks/ad-container/style.css +23 -20
  9. package/dist/blocks/button/block.json +1 -0
  10. package/dist/blocks/button/render.php +71 -0
  11. package/dist/blocks/button/style-resolved.css +153 -0
  12. package/dist/blocks/button/style.css +138 -18
  13. package/dist/blocks/chip/block.json +1 -0
  14. package/dist/blocks/chip/render.php +40 -0
  15. package/dist/blocks/chip/style.css +68 -29
  16. package/dist/blocks/divider/block.json +1 -0
  17. package/dist/blocks/divider/render.php +31 -0
  18. package/dist/blocks/divider/style.css +25 -13
  19. package/dist/blocks/flag/block.json +1 -0
  20. package/dist/blocks/flag/render.php +34 -0
  21. package/dist/blocks/flag/style.css +39 -30
  22. package/dist/blocks/icon-button/block.json +1 -0
  23. package/dist/blocks/icon-button/render.php +46 -0
  24. package/dist/blocks/icon-button/style.css +21 -22
  25. package/dist/blocks/input/block.json +1 -0
  26. package/dist/blocks/input/render.php +39 -0
  27. package/dist/blocks/input/style.css +40 -17
  28. package/dist/blocks/link/block.json +1 -0
  29. package/dist/blocks/link/render.php +44 -0
  30. package/dist/blocks/link/style.css +87 -34
  31. package/dist/blocks/text/block.json +1 -0
  32. package/dist/blocks/text/render.php +26 -0
  33. package/dist/blocks/text/style.css +5 -18
  34. package/dist/blocks/toast/block.json +1 -0
  35. package/dist/blocks/toast/render.php +37 -0
  36. package/dist/blocks/toast/style.css +50 -34
  37. package/dist/index.cjs +20 -0
  38. package/dist/index.cjs.map +1 -1
  39. package/dist/index.js +20 -0
  40. package/dist/index.js.map +1 -1
  41. package/package.json +3 -3
  42. package/src/blocks/ad-container/block.json +1 -0
  43. package/src/blocks/ad-container/render.php +27 -0
  44. package/src/blocks/ad-container/style.css +23 -20
  45. package/src/blocks/button/block.json +1 -0
  46. package/src/blocks/button/render.php +71 -0
  47. package/src/blocks/button/style-resolved.css +153 -0
  48. package/src/blocks/button/style.css +138 -18
  49. package/src/blocks/chip/block.json +1 -0
  50. package/src/blocks/chip/render.php +40 -0
  51. package/src/blocks/chip/style.css +68 -29
  52. package/src/blocks/divider/block.json +1 -0
  53. package/src/blocks/divider/render.php +31 -0
  54. package/src/blocks/divider/style.css +25 -13
  55. package/src/blocks/flag/block.json +1 -0
  56. package/src/blocks/flag/render.php +34 -0
  57. package/src/blocks/flag/style.css +39 -30
  58. package/src/blocks/icon-button/block.json +1 -0
  59. package/src/blocks/icon-button/render.php +46 -0
  60. package/src/blocks/icon-button/style.css +21 -22
  61. package/src/blocks/input/block.json +1 -0
  62. package/src/blocks/input/render.php +39 -0
  63. package/src/blocks/input/style.css +40 -17
  64. package/src/blocks/link/block.json +1 -0
  65. package/src/blocks/link/render.php +44 -0
  66. package/src/blocks/link/style.css +87 -34
  67. package/src/blocks/text/block.json +1 -0
  68. package/src/blocks/text/render.php +26 -0
  69. package/src/blocks/text/style.css +5 -18
  70. package/src/blocks/toast/block.json +1 -0
  71. package/src/blocks/toast/render.php +37 -0
  72. package/src/blocks/toast/style.css +50 -34
@@ -0,0 +1,306 @@
1
+ # WordPress Block Styling Synchronization Guide
2
+
3
+ This guide ensures that WordPress block styles remain synchronized with React component styles, maintaining feature parity across implementations.
4
+
5
+ ## Overview
6
+
7
+ All WordPress blocks must have equivalent CSS to their React counterparts. This includes:
8
+
9
+ - All variant combinations (intent, size, behaviour)
10
+ - All state handlers (hover, focus, active, disabled)
11
+ - All design token references
12
+ - Identical visual appearance in patterns and editor
13
+
14
+ ## Process: React to WordPress Style Synchronization
15
+
16
+ ### Step 1: Identify React Component Styles
17
+
18
+ ```bash
19
+ # Locate React component styles
20
+ ls packages/components-react/src/<ComponentName>/
21
+ # Look for: <ComponentName>.scss or <ComponentName>.css
22
+
23
+ # Count lines to understand complexity
24
+ wc -l packages/components-react/src/<ComponentName>/<ComponentName>.scss
25
+ ```
26
+
27
+ ### Step 2: Extract Style Segments
28
+
29
+ Read the React component stylesheet and identify:
30
+
31
+ 1. **Base styles** (`.tds-<component>` class)
32
+ - Display, flex properties, gaps
33
+ - Basic colors, borders, padding
34
+ - Font properties (family, weight, size, line-height)
35
+ - Transitions and animations
36
+
37
+ 2. **Variant modifiers** (`.tds-<component>--<variant>`)
38
+ - Intent: primary, secondary, negative
39
+ - Size: small, medium, large
40
+ - Behaviour: full, hug
41
+ - Channel: on, off
42
+ - Toggle: on, off
43
+
44
+ 3. **State handlers** (pseudo-classes)
45
+ - `:hover` - hover states
46
+ - `:focus-visible` - focus states with focus ring
47
+ - `:active` - pressed states
48
+ - `:disabled` - disabled states
49
+ - `:visited` - visited states (for links)
50
+
51
+ 4. **Nested selectors** (children and pseudo-elements)
52
+ - `.tds-<component>__<child>` - nested elements
53
+ - `::after` - focus rings, decorative elements
54
+ - `::before` - pseudo-elements
55
+
56
+ ### Step 3: Copy and Adapt to WordPress
57
+
58
+ Create `style.css` in the block directory:
59
+
60
+ ```bash
61
+ cp packages/components-react/src/<ComponentName>/<ComponentName>.scss \
62
+ packages/components-wordpress/src/blocks/<component>/style-temp.css
63
+ ```
64
+
65
+ Then:
66
+
67
+ 1. Convert SCSS to CSS (remove variables, nesting, mixins)
68
+ 2. Replace any SCSS variables with CSS custom properties
69
+ 3. Ensure all color/spacing/typography use design tokens
70
+ 4. Verify BEM naming convention (hyphens, no underscores)
71
+ 5. Save as `style.css`
72
+
73
+ ### Step 4: Design Token Mapping
74
+
75
+ All style values must use CSS custom properties from `@times-design-system/theme-scss`.
76
+
77
+ **Common Design Token Categories**:
78
+
79
+ | Category | Tokens | Example |
80
+ | -------------- | -------------------------------------------------------------------- | -------------------------------------------- |
81
+ | **Colors** | `--interactive-*`, `--text-*`, `--border-*`, `--flag-*`, `--toast-*` | `var(--interactive-primary-fill-default)` |
82
+ | **Spacing** | `--spacing-01` through `--spacing-20` | `var(--spacing-03)` |
83
+ | **Typography** | `--fontFamily*`, `--fontWeight*`, `--fontSize*`, `--fontLineHeight*` | `var(--fontFamily040)`, `var(--fontSize030)` |
84
+ | **Borders** | `--border-radius-*` | `var(--border-radius-full)` |
85
+ | **Effects** | `--focus-border`, `--shadow-*` | `var(--focus-border)` |
86
+
87
+ **Validation**:
88
+
89
+ ```bash
90
+ # Check for hardcoded hex colors (should be 0 results)
91
+ grep -E '#[0-9a-fA-F]{6}' packages/components-wordpress/src/blocks/<component>/style.css
92
+
93
+ # Check for hardcoded values (should use variables)
94
+ grep -E '[0-9]+px|[0-9]+rem' packages/components-wordpress/src/blocks/<component>/style.css | grep -v 'var('
95
+ ```
96
+
97
+ ### Step 5: Variant Coverage
98
+
99
+ Ensure **all** variant combinations are styled:
100
+
101
+ **Button Example**:
102
+
103
+ ```
104
+ Intents: --intent-primary ✓, --intent-secondary ✓, --intent-negative ✓
105
+ Sizes: --size-small ✓, --size-medium ✓, --size-large ✓
106
+ Behaviour: --behaviour-full ✓, --behaviour-hug ✓
107
+ States: :hover ✓, :focus-visible ✓, :active ✓, :disabled ✓
108
+ Focus rings: ::after with --focus-border ✓
109
+ ```
110
+
111
+ **Link Example**:
112
+
113
+ ```
114
+ Intents: --intent-primary ✓, --intent-secondary ✓
115
+ Sentiments: --sentiment-brand ✓, --sentiment-utility ✓
116
+ Sizes: --size-xsmall ✓, --size-small ✓, --size-medium ✓, --size-large ✓
117
+ States: :hover ✓, :focus-visible ✓, :active ✓, :visited ✓, :disabled ✓
118
+ ```
119
+
120
+ **Chip Example**:
121
+
122
+ ```
123
+ Intents: --intent-primary ✓ (on/off variants)
124
+ Channels: --channel-on ✓, --channel-off ✓
125
+ Sizes: --size-small ✓, --size-large ✓
126
+ States: :hover ✓, :focus ✓, :active ✓, :disabled ✓
127
+ Toggle: on state (bg-dark) ✓, off state (bg-light) ✓
128
+ ```
129
+
130
+ ### Step 6: Test for Feature Parity
131
+
132
+ ```bash
133
+ # 1. Line count comparison
134
+ echo "React component:"
135
+ wc -l packages/components-react/src/<ComponentName>/<ComponentName>.scss
136
+ echo "WordPress block:"
137
+ wc -l packages/components-wordpress/src/blocks/<component>/style.css
138
+
139
+ # Acceptable difference: ±20% (accounting for SCSS nesting/mixins vs flat CSS)
140
+ # If WordPress is <50% of React, styles are likely incomplete
141
+
142
+ # 2. Variant count comparison
143
+ echo "React variants:"
144
+ grep -o '\.[a-z-]*--[a-z-]*' packages/components-react/src/<ComponentName>/<ComponentName>.scss | sort -u | wc -l
145
+ echo "WordPress variants:"
146
+ grep -o '\.[a-z-]*--[a-z-]*' packages/components-wordpress/src/blocks/<component>/style.css | sort -u | wc -l
147
+
148
+ # 3. Visual comparison
149
+ # - Open React Storybook: packages/components-react (npm run storybook)
150
+ # - Open WordPress block in editor: packages/components-wordpress
151
+ # - Compare side-by-side for each variant
152
+ # - Verify colors match exactly (CSS custom properties should be identical)
153
+ ```
154
+
155
+ ## Style Structure Template
156
+
157
+ Use this template as a reference for comprehensive block styling:
158
+
159
+ ```css
160
+ /* Base component styles */
161
+ .tds-<component > {
162
+ --tds-<component>-bg: var(--interactive-primary-fill-default);
163
+ --tds-<component>-fg: var(--interactive-primary-text-default);
164
+ --tds-<component>-border: var(--interactive-primary-border-default);
165
+
166
+ display: flex; /* or block, grid, etc. */
167
+ align-items: center;
168
+ justify-content: center;
169
+ gap: var(--spacing-03);
170
+
171
+ border: 1px solid var(--tds-<component>-border);
172
+ background-color: var(--tds-<component>-bg);
173
+ color: var(--tds-<component>-fg);
174
+
175
+ font-family: var(--fontFamily040);
176
+ font-weight: var(--fontWeight050);
177
+ font-size: var(--fontSize030);
178
+ line-height: var(--fontLineHeight040);
179
+
180
+ border-radius: 0;
181
+ transition:
182
+ background-color 0.15s ease,
183
+ border-color 0.15s ease,
184
+ color 0.15s ease;
185
+ cursor: pointer;
186
+ }
187
+
188
+ /* Intent variants */
189
+ .tds-<component > --intent-primary {
190
+ --tds-<component>-bg: var(--interactive-primary-fill-default);
191
+ --tds-<component>-fg: var(--interactive-primary-text-default);
192
+ --tds-<component>-border: var(--interactive-primary-border-default);
193
+ }
194
+
195
+ .tds-<component > --intent-secondary {
196
+ --tds-<component>-bg: var(--interactive-secondary-fill-default);
197
+ --tds-<component>-fg: var(--interactive-secondary-text-default);
198
+ --tds-<component>-border: var(--interactive-secondary-border-default);
199
+ }
200
+
201
+ /* Size variants */
202
+ .tds-<component > --size-small {
203
+ min-height: var(--spacing-15);
204
+ padding: var(--spacing-03) var(--spacing-05);
205
+ font-size: var(--fontSize020);
206
+ }
207
+
208
+ .tds-<component > --size-large {
209
+ min-height: var(--spacing-20);
210
+ padding: var(--spacing-07) var(--spacing-11);
211
+ font-size: var(--fontSize040);
212
+ }
213
+
214
+ /* Behaviour variants */
215
+ .tds-<component > --behaviour-full {
216
+ width: 100%;
217
+ }
218
+
219
+ /* State handlers */
220
+ .tds-<component > :hover:not(:disabled) {
221
+ background-color: var(--interactive-primary-fill-hover);
222
+ border-color: var(--interactive-primary-border-hover);
223
+ color: var(--interactive-primary-text-hover);
224
+ }
225
+
226
+ .tds-<component > :active:not(:disabled) {
227
+ background-color: var(--interactive-primary-fill-pressed);
228
+ border-color: var(--interactive-primary-border-pressed);
229
+ color: var(--interactive-primary-text-pressed);
230
+ }
231
+
232
+ .tds-<component > :focus-visible {
233
+ outline: none;
234
+ }
235
+
236
+ .tds-<component > :focus-visible::after {
237
+ content: '';
238
+ position: absolute;
239
+ inset: calc(var(--spacing-03) * -1);
240
+ border: var(--spacing-01) solid var(--focus-border);
241
+ border-radius: inherit;
242
+ pointer-events: none;
243
+ }
244
+
245
+ .tds-<component > :disabled {
246
+ background-color: var(--interactive-disabled-b);
247
+ border-color: transparent;
248
+ color: var(--text-disabled);
249
+ cursor: not-allowed;
250
+ opacity: 0.5;
251
+ }
252
+
253
+ /* Nested elements */
254
+ .tds-<component > __label {
255
+ display: inline-flex;
256
+ align-items: center;
257
+ gap: var(--spacing-03);
258
+ }
259
+ ```
260
+
261
+ ## Build and Verification
262
+
263
+ ```bash
264
+ cd packages/components-wordpress
265
+
266
+ # Build the package
267
+ npm run build
268
+
269
+ # Verify styles compiled correctly
270
+ wc -l dist/blocks/<component>/style.css
271
+
272
+ # Test in WordPress
273
+ # 1. Copy dist/ to WordPress installation
274
+ # 2. Activate plugin or install NPM package
275
+ # 3. Add block to page
276
+ # 4. Test each variant combination
277
+ # 5. Test in patterns (server-side rendering)
278
+ ```
279
+
280
+ ## Maintenance
281
+
282
+ When React component styles change:
283
+
284
+ 1. **Identify the change**: What CSS properties, variants, or states changed?
285
+ 2. **Update React component**: Make the change in `packages/components-react/src/<ComponentName>/<ComponentName>.scss`
286
+ 3. **Sync to WordPress**: Update `packages/components-wordpress/src/blocks/<component>/style.css` with the same change
287
+ 4. **Build and test**: `npm run build` in both packages and test in WordPress
288
+ 5. **Document the change**: Update CHANGELOG.md in both packages
289
+
290
+ ## Common Styling Issues
291
+
292
+ | Issue | Cause | Solution |
293
+ | -------------------------------- | -------------------------------------- | --------------------------------------------------------------- |
294
+ | Block renders but styles missing | CSS not linked in `block.json` | Verify `"style": "file:./style.css"` in block.json |
295
+ | Colors don't match React | Hardcoded hex colors or wrong token | Replace with `var(--design-token)` from design system |
296
+ | Variants not styling | Modifier CSS not written | Compare to React, copy missing `.tds-<component>--*` selectors |
297
+ | Focus ring not visible | Missing `::after` pseudo-element | Add focus-visible handler with border and `var(--focus-border)` |
298
+ | Styles different in patterns | render.php classes don't match save.js | Verify class names exactly match between save.js and render.php |
299
+ | Line count too low | Styles incomplete or too minimal | Compare to React line count, should be similar (±20%) |
300
+
301
+ ## Related Documentation
302
+
303
+ - [BLOCK_CREATION_CHECKLIST.md](./BLOCK_CREATION_CHECKLIST.md) - Step-by-step checklist for creating new blocks
304
+ - [TRANSFORMATION_GUIDE.md](./TRANSFORMATION_GUIDE.md) - Complete transformation process documentation
305
+ - Design tokens: `packages/tokens/README.md`
306
+ - Component React source: `packages/components-react/src/`
@@ -263,36 +263,174 @@ export default function Save({ attributes }) {
263
263
  - No event handlers or dynamic behavior (it's static HTML)
264
264
  - Can be PHP or JavaScript-rendered depending on config
265
265
 
266
- ### 7. **Create CSS Files**
266
+ ### 7. **Create CSS Files — Full Parity with React**
267
267
 
268
- #### `style.css` (Frontend Styles)
268
+ #### `style.css` (Frontend Styles) — Critical for Feature Parity
269
269
 
270
- - Imported on both editor and frontend
271
- - Contains production CSS for the component
272
- - References SCSS classes from `theme-scss` package
273
- - Use CSS variables for tokens
270
+ The WordPress block's `style.css` **must mirror the React component's styles exactly**. This ensures visual and behavioral consistency across all implementations.
274
271
 
275
- **Template**:
272
+ **Process**:
273
+
274
+ 1. **Copy React component styles** from `packages/components-react/src/<ComponentName>/<ComponentName>.scss`
275
+ 2. **Map all className modifiers** to CSS selectors (e.g., React `className="tds-button--size-small"` → CSS `.tds-button--size-small { ... }`)
276
+ 3. **Preserve all CSS custom properties** for design tokens
277
+ 4. **Include all state handlers**: hover, focus-visible, active, disabled
278
+ 5. **Include all variants**: intent (primary, secondary, negative), size (small, medium, large), behaviour (full, hug)
279
+
280
+ **Key Principles**:
281
+
282
+ - Use **resolved design token values** for ALL properties: actual hex colors (e.g., `#005c8a`), pixel sizes (e.g., `8px`), and font names (e.g., `Roboto`)
283
+ - Follow BEM naming: `.tds-<component>`, `.tds-<component>--<modifier>`
284
+ - Include transitions for smooth state changes (usually `0.15s ease`)
285
+ - Focus rings should use concrete colors (e.g., `#737373`) with box-shadow pseudo-element
286
+ - Disabled states need opacity reduction and cursor change
287
+ - All pseudo-classes (`:hover`, `:focus-visible`, `:active`, `:disabled`) must match React behavior
288
+
289
+ **Example Structure** (from Button component):
276
290
 
277
291
  ```css
278
- .tds-<component > -wrapper {
279
- /* Component wrapper styles */
292
+ .tds-button {
293
+ --tds-button-bg: var(--interactive-primary-fill-default);
294
+ --tds-button-fg: var(--interactive-primary-text-default);
295
+ --tds-button-border: var(--interactive-primary-border-default);
296
+
297
+ display: flex;
298
+ align-items: center;
299
+ gap: var(--spacing-03);
300
+ border: 1px solid var(--tds-button-border);
301
+ background-color: var(--tds-button-bg);
302
+ color: var(--tds-button-fg);
303
+ font-family: Roboto;
304
+ font-weight: 500;
305
+ transition: 0.15s ease;
306
+ cursor: pointer;
307
+ }
308
+
309
+ /* Intent variants */
310
+ .tds-button--intent-primary {
311
+ --tds-button-bg: var(--interactive-primary-fill-default);
312
+ --tds-button-fg: var(--interactive-primary-text-default);
313
+ }
314
+
315
+ .tds-button--intent-secondary {
316
+ --tds-button-bg: var(--interactive-secondary-fill-default);
317
+ --tds-button-fg: var(--interactive-secondary-text-default);
318
+ }
319
+
320
+ /* Size variants */
321
+ .tds-button--size-small {
322
+ min-height: var(--spacing-15);
323
+ padding: var(--spacing-03) var(--spacing-05);
324
+ font-size: var(--fontSize020);
325
+ }
326
+
327
+ .tds-button--size-large {
328
+ min-height: var(--spacing-20);
329
+ padding: var(--spacing-07) var(--spacing-11);
330
+ font-size: var(--fontSize040);
331
+ }
332
+
333
+ /* State handlers */
334
+ .tds-button:hover:not(:disabled) {
335
+ background-color: var(--interactive-primary-fill-hover);
336
+ border-color: var(--interactive-primary-border-hover);
337
+ }
338
+
339
+ .tds-button:focus-visible {
340
+ outline: none;
341
+ }
342
+
343
+ .tds-button:focus-visible::after {
344
+ content: '';
345
+ position: absolute;
346
+ inset: calc(var(--spacing-03) * -1);
347
+ border: var(--spacing-01) solid var(--focus-border);
348
+ pointer-events: none;
280
349
  }
281
350
 
282
- .tds-<component > {
283
- /* Component styles - mostly inherited from theme-scss */
351
+ .tds-button:disabled {
352
+ background-color: var(--interactive-disabled-b);
353
+ color: var(--text-disabled);
354
+ cursor: not-allowed;
355
+ opacity: 0.5;
284
356
  }
285
357
 
286
- .tds-<component > --full {
358
+ /* Behaviour variants */
359
+ .tds-button--behaviour-full {
287
360
  width: 100%;
288
361
  }
289
362
  ```
290
363
 
291
- #### `style-editor.css` (Editor Styles)
364
+ **Validation Checklist**:
365
+
366
+ ✓ All CSS custom properties match design tokens
367
+ ✓ BEM naming is consistent (no underscores, only hyphens)
368
+ ✓ All variant combinations present (intent × size × behaviour)
369
+ ✓ State handlers for hover, focus, active, disabled
370
+ ✓ Line count similar to React component (indicates parity)
371
+ ✓ Transitions preserved
372
+ ✓ No hardcoded hex colors (all use CSS variables)
373
+
374
+ #### `style-editor.css` (Editor Styles) — Optional
292
375
 
293
376
  - Only loaded in WordPress editor
294
377
  - Can override styles for better editing experience
295
378
  - Optional — only add if editor preview differs significantly from frontend
379
+ - Example: Adjust padding/margins for better visual feedback in editor
380
+ - Example: Add borders to clarify block boundaries during editing
381
+
382
+ #### CSS Variables and theme-scss Integration
383
+
384
+ **IMPORTANT**: WordPress blocks use **resolved design token values** — actual hex colors, pixel measurements, and font definitions are compiled directly into the CSS. This means blocks are completely self-contained and do not depend on CSS custom properties from theme-scss.
385
+
386
+ **Key Approach**:
387
+
388
+ ```
389
+ Design Tokens (theme-scss)
390
+
391
+ Resolved to concrete values
392
+
393
+ Hardcoded into WordPress block CSS
394
+
395
+ No runtime CSS variable dependencies needed
396
+ ```
397
+
398
+ **Why Resolved Values**:
399
+
400
+ - Blocks work reliably in any WordPress theme context
401
+ - No need for theme-scss CSS variables to be loaded
402
+ - Consistent appearance across installations
403
+ - Simpler debugging (actual colors/sizes visible in DevTools)
404
+
405
+ **Value Reference**:
406
+
407
+ | Value Type | Examples | Notes |
408
+ | ----------------- | ----------------------------------------------- | ------------------------------------- |
409
+ | **Colors (hex)** | `#005c8a`, `#ffffff`, `#737373` | All interactive colors use hex values |
410
+ | **Spacing (px)** | `4px`, `8px`, `12px`, `16px`, `24px` | All spacing uses pixel units |
411
+ | **Typography** | `-apple-system, BlinkMacSystemFont, "Segoe UI"` | System fonts specified directly |
412
+ | **Font weight** | `400`, `500`, `700` | Numeric weight values |
413
+ | **Font size** | `0.75rem`, `1rem`, `1.125rem` | REMs for scalability |
414
+ | **Border radius** | `0`, `2px`, `4px`, `9999px` | Explicit pixel or full values |
415
+
416
+ **Example Block CSS** (resolved values):
417
+
418
+ ```css
419
+ .tds-button {
420
+ /* NO CSS variables — all values are concrete */
421
+ --tds-button-bg: #005c8a; /* Direct hex, not var(--color-*) */
422
+ --tds-button-fg: #ffffff; /* No fallbacks needed */
423
+
424
+ gap: 4px; /* Direct pixel, not var(--spacing-03) */
425
+ padding: 8px 12px; /* Resolved from design tokens */
426
+
427
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
428
+ font-weight: 500; /* Direct numeric value */
429
+ font-size: 1rem; /* Direct size, not var(--fontSize030) */
430
+ }
431
+ ```
432
+
433
+ **See Also**: [SCSS_VARIABLES_REFERENCE.md](./SCSS_VARIABLES_REFERENCE.md) for complete mapping of all resolved values used across all blocks.
296
434
 
297
435
  ### 8. **Create/Update Utility Functions**
298
436
 
@@ -343,6 +481,32 @@ import './blocks/button/index.js';
343
481
  import './blocks/<new-component>/index.js';
344
482
  ```
345
483
 
484
+ ### 10. **Build and Verify**
485
+
486
+ **After completing all files**:
487
+
488
+ ```bash
489
+ # Build the package
490
+ cd packages/components-wordpress
491
+ npm run build
492
+
493
+ # Verify block appears in dist/ with all styles
494
+ ls dist/blocks/<new-component>/
495
+ # Expected: block.json, index.js, edit.js, save.js, render.php, style.css, style-editor.css
496
+
497
+ # Check line count of compiled style.css (should be substantial)
498
+ wc -l dist/blocks/<new-component>/style.css
499
+ ```
500
+
501
+ **Build verification checklist**:
502
+
503
+ ✓ `dist/blocks/<new-component>/render.php` exists (server-side rendering)
504
+ ✓ `dist/blocks/<new-component>/style.css` contains all styles (50+ lines for complex components)
505
+ ✓ `block.json` has `"render": "file:./render.php"` reference
506
+ ✓ No TypeScript/build errors
507
+ ✓ Block registrations all successful
508
+ ✓ CSS custom properties properly compiled
509
+
346
510
  ---
347
511
 
348
512
  ## Prop-to-Attribute Conversion Reference
@@ -593,6 +757,97 @@ Suggested order for consistent, dependency-aware transformation:
593
757
 
594
758
  ---
595
759
 
760
+ ## Testing & Validation Checklist
761
+
762
+ ### Pre-Build Testing
763
+
764
+ - [ ] **Styling Parity**: Run visual comparison of React component vs WordPress block
765
+ - Open React component in Storybook: `packages/components-react/src/<ComponentName>`
766
+ - Compare all variants side-by-side (intent, size, behaviour)
767
+ - Verify colors match exactly (should be identical CSS custom properties)
768
+ - Check spacing and padding are correct
769
+ - Test state transitions (hover, focus, active, disabled)
770
+
771
+ - [ ] **CSS Validation**:
772
+ - Line count check: `wc -l src/blocks/<component>/style.css` — should be substantial (50+ for complex components like Button/Link, 20+ for simple components)
773
+ - Compare to React component: `wc -l packages/components-react/src/<ComponentName>/<ComponentName>.scss`
774
+ - Should be similar complexity (±20% acceptable difference)
775
+ - No hardcoded hex colors: `grep -E '#[0-9a-fA-F]{6}' src/blocks/<component>/style.css` — should be zero results (all colors use CSS variables)
776
+ - Validate JSON: `npm run test`
777
+
778
+ - [ ] **Render Callback Testing**:
779
+ - Verify `render.php` exists for server-side pattern rendering
780
+ - Test in WordPress patterns: create pattern with block variants
781
+ - Confirm HTML output matches `save.js` structure exactly
782
+ - Test with different attribute combinations in patterns
783
+
784
+ ### Post-Build Testing
785
+
786
+ ```bash
787
+ # Navigate to packages/components-wordpress
788
+ cd packages/components-wordpress
789
+
790
+ # Run build
791
+ npm run build
792
+
793
+ # Verify compiled styles
794
+ wc -l dist/blocks/<component>/style.css # Check line count (should match src/)
795
+ grep -c 'tds-<component>--' dist/blocks/<component>/style.css # Count modifiers
796
+
797
+ # Verify render.php exists
798
+ ls dist/blocks/<component>/render.php
799
+ ```
800
+
801
+ - [ ] **Compiled Output**:
802
+ - All variant CSS is present in `dist/blocks/<component>/style.css`
803
+ - `render.php` file exists and is copied to dist
804
+ - `block.json` contains `"render": "file:./render.php"`
805
+
806
+ - [ ] **WordPress Installation Testing**:
807
+ 1. Build package: `npm run build`
808
+ 2. Copy `dist/` to WordPress installation
809
+ 3. Activate plugin or install NPM package
810
+ 4. Add block to page in editor
811
+ 5. Test each variant combination:
812
+ - Different intents (primary, secondary, negative)
813
+ - Different sizes (small, medium, large)
814
+ - Different states (hover by mouse, focus by keyboard, active by click, disabled)
815
+ 6. Test in patterns (server-side rendering):
816
+ - Create pattern with block variants
817
+ - Confirm HTML renders correctly before editor JS loads
818
+ - Verify CSS loads properly
819
+ 7. Test responsive breakpoints (if applicable):
820
+ - Resize browser window
821
+ - Verify layout adapts correctly
822
+ 8. Test accessibility:
823
+ - Keyboard navigation through interactive elements
824
+ - Screen reader announcements
825
+ - Focus ring visibility
826
+
827
+ ### Styling Feature Parity Validation
828
+
829
+ For components with variants (Button, Link, Chip, etc.), verify complete coverage:
830
+
831
+ ```
832
+ Component: Button
833
+ Intents: primary ✓, secondary ✓, negative ✓
834
+ Sizes: small ✓, medium ✓, large ✓
835
+ Behaviour: hug ✓, full ✓
836
+ States: base ✓, hover ✓, focus ✓, active ✓, disabled ✓
837
+ CSS Classes: .tds-button--intent-primary ✓, .tds-button--size-small ✓, etc.
838
+ ```
839
+
840
+ - [ ] All intent variants styled (must match React exactly)
841
+ - [ ] All size variants styled (must match React exactly)
842
+ - [ ] All behaviour variants styled (must match React exactly)
843
+ - [ ] All state handlers present (hover, focus-visible, active, disabled)
844
+ - [ ] CSS custom properties for all tokens (colors, spacing, typography)
845
+ - [ ] Focus rings use consistent `var(--focus-border)`
846
+ - [ ] Transitions preserved from React component
847
+ - [ ] No visual regressions compared to React
848
+
849
+ ---
850
+
596
851
  ## Debugging & Troubleshooting
597
852
 
598
853
  ### Block not appearing in Gutenberg?
@@ -607,6 +862,10 @@ Suggested order for consistent, dependency-aware transformation:
607
862
  1. Verify CSS files in `block.json` point to correct paths
608
863
  2. Check class names match SCSS in `theme-scss`
609
864
  3. Ensure theme-scss is installed: `npm install @times-design-system/theme-scss`
865
+ 4. **Styling gap**: Compare `style.css` line count to React component — if significantly lower, styles may be incomplete
866
+ - Check for all variant modifiers: grep `.tds-<component>--` src/blocks/<component>/style.css
867
+ - Verify state handlers (hover, focus, active, disabled) are present
868
+ - Copy missing styles from React component
610
869
 
611
870
  ### Attributes not saving?
612
871
 
@@ -614,6 +873,14 @@ Suggested order for consistent, dependency-aware transformation:
614
873
  2. Ensure `setAttributes({ key: value })` is called correctly
615
874
  3. Check attribute types match intended use
616
875
 
876
+ ### Block doesn't render in patterns?
877
+
878
+ 1. Verify `render.php` exists in block directory
879
+ 2. Check `block.json` has `"render": "file:./render.php"` reference
880
+ 3. Rebuild with `npm run build` to copy render.php to dist/
881
+ 4. Verify render.php HTML structure matches save.js exactly
882
+ 5. Test in WordPress patterns (not just editor)
883
+
617
884
  ---
618
885
 
619
886
  ## References
@@ -13,6 +13,7 @@
13
13
  "padding": true
14
14
  }
15
15
  },
16
+ "render": "file:./render.php",
16
17
  "attributes": {
17
18
  "type": {
18
19
  "type": "string",
@@ -0,0 +1,27 @@
1
+ <?php
2
+ /**
3
+ * Ad Container Block Render Callback
4
+ *
5
+ * Renders the Ad Container block on the frontend.
6
+ *
7
+ * @package TimesDesignSystem\ComponentsWordPress
8
+ * @var array $attributes Block attributes
9
+ * @var string $content Block content
10
+ * @var WP_Block $block Block instance
11
+ */
12
+
13
+ $type = isset($attributes['type']) ? sanitize_text_field($attributes['type']) : '';
14
+ $slot_id = isset($attributes['slotID']) ? sanitize_text_field($attributes['slotID']) : '';
15
+
16
+ $class_name = 'tds-ad-container';
17
+ if ($type) {
18
+ $class_name .= ' tds-ad-container--' . sanitize_html_class($type);
19
+ }
20
+
21
+ $wrapper_attrs = get_block_wrapper_attributes(['class' => $class_name]);
22
+ ?>
23
+
24
+ <div
25
+ <?php echo wp_kses_post($wrapper_attrs); ?>
26
+ <?php echo $slot_id ? 'data-slot-id="' . esc_attr($slot_id) . '"' : ''; ?>
27
+ ></div>