@symbo.ls/mcp 1.0.10 → 1.0.11
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.
- package/package.json +5 -2
- package/symbols_mcp/skills/COMPONENTS.md +421 -0
- package/symbols_mcp/skills/DESIGN_SYSTEM.md +430 -0
- package/symbols_mcp/skills/MIGRATION.md +520 -0
- package/symbols_mcp/skills/PATTERNS.md +509 -0
- package/symbols_mcp/skills/PROJECT_STRUCTURE.md +400 -0
- package/symbols_mcp/skills/RULES.md +488 -0
- package/symbols_mcp/skills/SEO-METADATA.md +41 -1
- package/symbols_mcp/skills/SYNTAX.md +777 -0
- package/symbols_mcp/skills/ACCESSIBILITY.md +0 -471
- package/symbols_mcp/skills/ACCESSIBILITY_AUDITORY.md +0 -70
- package/symbols_mcp/skills/AGENT_INSTRUCTIONS.md +0 -265
- package/symbols_mcp/skills/BUILT_IN_COMPONENTS.md +0 -304
- package/symbols_mcp/skills/CLAUDE.md +0 -2158
- package/symbols_mcp/skills/CLI_QUICK_START.md +0 -205
- package/symbols_mcp/skills/DEFAULT_COMPONENTS.md +0 -2002
- package/symbols_mcp/skills/DEFAULT_DESIGN_SYSTEM.md +0 -496
- package/symbols_mcp/skills/DESIGN_SYSTEM_CONFIG.md +0 -487
- package/symbols_mcp/skills/DESIGN_SYSTEM_IN_PROPS.md +0 -136
- package/symbols_mcp/skills/DOMQL_v2-v3_MIGRATION.md +0 -236
- package/symbols_mcp/skills/MIGRATE_TO_SYMBOLS.md +0 -634
- package/symbols_mcp/skills/OPTIMIZATIONS_FOR_AGENT.md +0 -253
- package/symbols_mcp/skills/PROJECT_SETUP.md +0 -217
- package/symbols_mcp/skills/QUICKSTART.md +0 -79
- package/symbols_mcp/skills/REMOTE_PREVIEW.md +0 -144
- package/symbols_mcp/skills/SYMBOLS_LOCAL_INSTRUCTIONS.md +0 -1405
- package/symbols_mcp/skills/UI_UX_PATTERNS.md +0 -68
|
@@ -0,0 +1,520 @@
|
|
|
1
|
+
# Migration Guide — DOMQL v2→v3 & React/Angular/Vue→Symbols
|
|
2
|
+
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
## Part 1: DOMQL v2 → v3 Migration
|
|
6
|
+
|
|
7
|
+
### Summary of changes
|
|
8
|
+
|
|
9
|
+
| Change | v2 ❌ | v3 ✅ |
|
|
10
|
+
| ------------------------- | ----------------------------- | ----------------------------- |
|
|
11
|
+
| CSS props wrapper | `props: { padding: 'A' }` | `padding: 'A'` (at root) |
|
|
12
|
+
| Events wrapper | `on: { click: fn }` | `onClick: fn` (at root) |
|
|
13
|
+
| Inheritance | `extend: 'Component'` | `extends: 'Component'` |
|
|
14
|
+
| Child extend | `childExtend: 'Item'` | `childExtends: 'Item'` |
|
|
15
|
+
| Child element detection | any key including lowercase | PascalCase keys ONLY |
|
|
16
|
+
|
|
17
|
+
### Flatten `props` and `on`
|
|
18
|
+
|
|
19
|
+
```js
|
|
20
|
+
// v2
|
|
21
|
+
{
|
|
22
|
+
props: { position: 'absolute', gap: 'A' },
|
|
23
|
+
on: {
|
|
24
|
+
render: e => {},
|
|
25
|
+
click: (e, t) => {},
|
|
26
|
+
wheel: (e, t) => {},
|
|
27
|
+
},
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// v3
|
|
31
|
+
{
|
|
32
|
+
position: 'absolute',
|
|
33
|
+
gap: 'A',
|
|
34
|
+
onRender: e => {},
|
|
35
|
+
onClick: (e, t) => {},
|
|
36
|
+
onWheel: (e, t) => {},
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Rules:
|
|
41
|
+
- Move all `props` entries directly to the component object root
|
|
42
|
+
- Remove the `on` wrapper; prefix each event with `on` + `CapitalizedEventName`
|
|
43
|
+
- Applies to root and all nested elements
|
|
44
|
+
|
|
45
|
+
### Rename properties
|
|
46
|
+
|
|
47
|
+
```js
|
|
48
|
+
// v2
|
|
49
|
+
{ extend: SomeComponent, childExtend: AnotherComponent }
|
|
50
|
+
|
|
51
|
+
// v3
|
|
52
|
+
{ extends: SomeComponent, childExtends: AnotherComponent }
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Replace `$stateCollection` with `children` + `childrenAs: 'state'`
|
|
56
|
+
|
|
57
|
+
```js
|
|
58
|
+
// v2
|
|
59
|
+
{
|
|
60
|
+
childExtend: 'TeamItem',
|
|
61
|
+
$stateCollection: (el, s) => s.data
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// v3 — Option A: childrenAs: 'state' (explicit)
|
|
65
|
+
{
|
|
66
|
+
childExtends: 'TeamItem',
|
|
67
|
+
childrenAs: 'state',
|
|
68
|
+
children: (el, s) => s.data
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// v3 — Option B: state: 'dataKey' shorthand (preferred for nested state)
|
|
72
|
+
{
|
|
73
|
+
state: 'data',
|
|
74
|
+
childExtends: 'TeamItem',
|
|
75
|
+
children: ({ state }) => state
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
#### `state: true` on child components
|
|
80
|
+
|
|
81
|
+
Components used with `childExtends` that read individual item state (e.g. `({ state }) => state.title`) need `state: true` at their root so each child receives its own state from the parent's children array:
|
|
82
|
+
|
|
83
|
+
```js
|
|
84
|
+
// Parent container
|
|
85
|
+
export const TeamList = {
|
|
86
|
+
state: 'members',
|
|
87
|
+
childExtends: 'TeamItem',
|
|
88
|
+
children: ({ state }) => state
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Child component — state: true is REQUIRED
|
|
92
|
+
export const TeamItem = {
|
|
93
|
+
state: true,
|
|
94
|
+
Title: { text: ({ state }) => state.name },
|
|
95
|
+
Role: { text: ({ state }) => state.role }
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
#### Common pitfall: `children: ({ state }) => state.data`
|
|
100
|
+
|
|
101
|
+
**WRONG**: `children: ({ state }) => state.data` does NOT properly pass individual state to child components — all children receive the parent's full state instead of their own item.
|
|
102
|
+
|
|
103
|
+
**CORRECT**: Use `state: 'data'` on the container first, then `children: ({ state }) => state`:
|
|
104
|
+
|
|
105
|
+
```js
|
|
106
|
+
// ❌ WRONG — children don't get individual state
|
|
107
|
+
{ children: ({ state }) => state.data, childExtends: 'Item' }
|
|
108
|
+
|
|
109
|
+
// ✅ CORRECT — state: 'data' narrows scope, children get individual items
|
|
110
|
+
{ state: 'data', children: ({ state }) => state, childExtends: 'Item' }
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Replace `$propsCollection` with `children`
|
|
114
|
+
|
|
115
|
+
```js
|
|
116
|
+
// v2
|
|
117
|
+
{
|
|
118
|
+
childExtend: 'Paragraph',
|
|
119
|
+
$propsCollection: ({ state }) => state.parse()
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// v3
|
|
123
|
+
{
|
|
124
|
+
childExtends: 'Paragraph',
|
|
125
|
+
children: ({ state }) => state.parse()
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
`$propsCollection` is fully removed in v3. Replace 1:1 with `children`. The same pattern applies to array data:
|
|
130
|
+
|
|
131
|
+
```js
|
|
132
|
+
// v2
|
|
133
|
+
$propsCollection: ({ state }) => state
|
|
134
|
+
|
|
135
|
+
// v3
|
|
136
|
+
children: ({ state }) => state
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Replace `props: (fn)` function with individual prop functions
|
|
140
|
+
|
|
141
|
+
```js
|
|
142
|
+
// v2 — dynamic props function (REMOVED in v3)
|
|
143
|
+
{
|
|
144
|
+
props: ({ state }) => ({
|
|
145
|
+
color: state.active ? 'red' : 'blue',
|
|
146
|
+
opacity: state.loading ? 0.5 : 1
|
|
147
|
+
})
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// v3 — each property is its own function
|
|
151
|
+
{
|
|
152
|
+
color: ({ state }) => state.active ? 'red' : 'blue',
|
|
153
|
+
opacity: ({ state }) => state.loading ? 0.5 : 1
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### Child element detection
|
|
158
|
+
|
|
159
|
+
```js
|
|
160
|
+
// v2 — lowercase keys could create elements
|
|
161
|
+
{ div: {} } // creates <div>
|
|
162
|
+
|
|
163
|
+
// v3 — only PascalCase keys create elements
|
|
164
|
+
{ div: {} } // treated as a plain prop, NOT rendered
|
|
165
|
+
{ Div: {} } // creates a child element (equivalent to { extends: 'Div' })
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Replace array spread `...[items]` with `children: [items]`
|
|
169
|
+
|
|
170
|
+
```js
|
|
171
|
+
// v2 — numeric keys from spread are lowercase, treated as CSS in v3
|
|
172
|
+
{
|
|
173
|
+
childExtend: 'NavLink',
|
|
174
|
+
...[{ text: 'About us' }, { text: 'Hiring' }]
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// v3 — use children array
|
|
178
|
+
{
|
|
179
|
+
childExtends: 'NavLink',
|
|
180
|
+
children: [{ text: 'About us' }, { text: 'Hiring' }]
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Array spread creates numeric keys (`0`, `1`, `2`…) which v3 treats as CSS properties (lowercase). Always use `children` array instead.
|
|
185
|
+
|
|
186
|
+
### Picture `src` must go on Img child
|
|
187
|
+
|
|
188
|
+
The HTML `<picture>` tag does NOT support `src` as an attribute. In v3, lowercase props move to `element.props`, so `element.parent.src` returns `undefined`.
|
|
189
|
+
|
|
190
|
+
```js
|
|
191
|
+
// ❌ WRONG — src on Picture is silently ignored
|
|
192
|
+
Picture: { src: '/files/photo.jpg', width: '100%' }
|
|
193
|
+
|
|
194
|
+
// ✅ CORRECT — src goes on the Img child inside Picture
|
|
195
|
+
Picture: {
|
|
196
|
+
Img: { src: '/files/photo.jpg' },
|
|
197
|
+
width: '100%',
|
|
198
|
+
aspectRatio: '16/9'
|
|
199
|
+
}
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
For theme-aware sources, use `@dark`/`@light` with `srcset` on Picture, but always put the default `src` on the Img child.
|
|
203
|
+
|
|
204
|
+
### `<map>` tag auto-detection
|
|
205
|
+
|
|
206
|
+
Component keys named `Map` auto-detect as the HTML `<map>` tag (for image maps), which defaults to `display: inline` and has height 0. If using `Map` as a component name for geographic maps or similar, add `tag: 'div'`:
|
|
207
|
+
|
|
208
|
+
```js
|
|
209
|
+
export const Map = {
|
|
210
|
+
extends: 'Flex',
|
|
211
|
+
tag: 'div', // prevents <map> auto-detection
|
|
212
|
+
// ...
|
|
213
|
+
}
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### Non-existent base components
|
|
217
|
+
|
|
218
|
+
These v2 component names don't exist in v3 uikit:
|
|
219
|
+
- `extends: 'Page'` → use `extends: 'Flex', flexFlow: 'column'`
|
|
220
|
+
- `extends: 'Overflow'` → use `extends: 'Flex'`
|
|
221
|
+
|
|
222
|
+
### `state: true` vs `state: 'fieldName'` for children
|
|
223
|
+
|
|
224
|
+
- `state: true` — for `childExtends`-created children; maps individual array items as each child's state
|
|
225
|
+
- `state: 'fieldName'` — for direct PascalCase children that need a specific field from parent state
|
|
226
|
+
|
|
227
|
+
```js
|
|
228
|
+
// childExtends children — use state: true on the child component
|
|
229
|
+
export const ListItem = {
|
|
230
|
+
state: true,
|
|
231
|
+
Title: { text: ({ state }) => state.name }
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Direct PascalCase child — use state: 'fieldName'
|
|
235
|
+
Description: {
|
|
236
|
+
state: 'description',
|
|
237
|
+
childExtends: 'Paragraph',
|
|
238
|
+
children: ({ state }) => state.parse()
|
|
239
|
+
}
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
### Full example
|
|
243
|
+
|
|
244
|
+
```js
|
|
245
|
+
// v2
|
|
246
|
+
{
|
|
247
|
+
extend: 'Flex',
|
|
248
|
+
childExtend: 'ListItem',
|
|
249
|
+
props: { position: 'absolute' },
|
|
250
|
+
attr: { 'gs-w': 1, 'gs-h': 1 },
|
|
251
|
+
SectionTitle: { text: 'Notes' },
|
|
252
|
+
Box: {
|
|
253
|
+
props: {
|
|
254
|
+
'--section-background': '#7a5e0e55',
|
|
255
|
+
id: 'editorjs',
|
|
256
|
+
},
|
|
257
|
+
on: {
|
|
258
|
+
frame: (e, t) => {},
|
|
259
|
+
render: e => {},
|
|
260
|
+
wheel: (e, t) => {},
|
|
261
|
+
dblclick: (e, t) => { e.stopPropagation() },
|
|
262
|
+
},
|
|
263
|
+
},
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// v3
|
|
267
|
+
{
|
|
268
|
+
extends: 'Flex',
|
|
269
|
+
childExtends: 'ListItem',
|
|
270
|
+
position: 'absolute',
|
|
271
|
+
attr: { 'gs-w': 1, 'gs-h': 1 },
|
|
272
|
+
SectionTitle: { text: 'Notes' },
|
|
273
|
+
Box: {
|
|
274
|
+
'--section-background': '#7a5e0e55',
|
|
275
|
+
id: 'editorjs',
|
|
276
|
+
onFrame: (e, t) => {},
|
|
277
|
+
onRender: e => {},
|
|
278
|
+
onWheel: (e, t) => {},
|
|
279
|
+
onDblclick: (e, t) => { e.stopPropagation() },
|
|
280
|
+
},
|
|
281
|
+
}
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
---
|
|
285
|
+
|
|
286
|
+
## Part 2: React / Angular / Vue → Symbols
|
|
287
|
+
|
|
288
|
+
### From React
|
|
289
|
+
|
|
290
|
+
| React Pattern | Symbols Equivalent |
|
|
291
|
+
| -------------------------------------- | ------------------------------------------------------------------------- |
|
|
292
|
+
| `function Component()` / `class` | `export const Component = { extends: 'Flex', ... }` |
|
|
293
|
+
| `import Component from './Component'` | Reference by key: `{ Component: {} }` |
|
|
294
|
+
| `useState(val)` | `state: { key: val }` + `s.update({ key: newVal })` |
|
|
295
|
+
| `useEffect(() => {}, [])` | `onRender: (el, s) => {}` |
|
|
296
|
+
| `useEffect(() => {}, [dep])` | `onStateUpdate: (changes, el, s) => {}` |
|
|
297
|
+
| `useContext` | `s.root` for global state |
|
|
298
|
+
| `useRef` | `el.node` for DOM access |
|
|
299
|
+
| `props.onClick` | `onClick: (e, el, s) => {}` |
|
|
300
|
+
| `props.children` | PascalCase child keys or `children` array |
|
|
301
|
+
| `{condition && <Component />}` | `if: (el, s) => condition` |
|
|
302
|
+
| `{items.map(i => <Item key={i.id}/>)}` | `children: (el, s) => s.items, childExtends: 'Item'` |
|
|
303
|
+
| `className="flex gap-4"` | `flow: 'x', gap: 'A'` (design tokens) |
|
|
304
|
+
| `style={{ padding: '16px' }}` | `padding: 'A'` |
|
|
305
|
+
| `<Link to="/page">` | `Link: { href: '/page', text: '...' }` |
|
|
306
|
+
| `useNavigate()` / `history.push` | `el.call('router', '/path', el.getRoot())` |
|
|
307
|
+
| Redux / Zustand store | `state/` folder + `s.root` access |
|
|
308
|
+
| `useMemo` / `useCallback` | Dynamic prop function: `text: (el, s) => s.a + s.b` |
|
|
309
|
+
| CSS Modules / styled-components | Flatten styles as props with design tokens |
|
|
310
|
+
| `<form onSubmit>` | `tag: 'form', onSubmit: (ev, el, s) => { ev.preventDefault(); ... }` |
|
|
311
|
+
| `fetch()` in components | `functions/fetch.js` + `el.call('fetch', method, path, data)` |
|
|
312
|
+
|
|
313
|
+
### From Angular
|
|
314
|
+
|
|
315
|
+
| Angular Pattern | Symbols Equivalent |
|
|
316
|
+
| ---------------------------------- | ---------------------------------------------------------------------- |
|
|
317
|
+
| `@Component({ template, styles })` | Plain object with flattened props and child keys |
|
|
318
|
+
| `@Input() propName` | `propName: value` at root |
|
|
319
|
+
| `@Output() eventName` | `onEventName: (e, el, s) => {}` |
|
|
320
|
+
| `*ngIf="condition"` | `if: (el, s) => condition` |
|
|
321
|
+
| `*ngFor="let item of items"` | `children: (el, s) => s.items, childExtends: 'X'` |
|
|
322
|
+
| `[ngClass]="{ active: isActive }"` | `.isActive: { background: 'primary' }` |
|
|
323
|
+
| `(click)="handler()"` | `onClick: (e, el, s) => {}` |
|
|
324
|
+
| `{{ interpolation }}` | `text: '{{ key }}'` or `text: (el, s) => s.key` |
|
|
325
|
+
| `ngOnInit()` | `onInit: (el, s) => {}` |
|
|
326
|
+
| `ngAfterViewInit()` | `onRender: (el, s) => {}` |
|
|
327
|
+
| `ngOnDestroy()` | Return cleanup fn from `onRender` |
|
|
328
|
+
| `ngOnChanges(changes)` | `onStateUpdate: (changes, el, s) => {}` |
|
|
329
|
+
| Services / DI | `functions/` folder + `el.call('serviceFn', args)` |
|
|
330
|
+
| `RouterModule` routes | `pages/index.js` route mapping |
|
|
331
|
+
| `routerLink="/path"` | `Link: { href: '/path' }` |
|
|
332
|
+
| `Router.navigate(['/path'])` | `el.call('router', '/path', el.getRoot())` |
|
|
333
|
+
| NgRx / BehaviorSubject store | `state/` folder + `s.root` access |
|
|
334
|
+
| SCSS / component styles | Flatten to props with design tokens |
|
|
335
|
+
| Reactive Forms | `tag: 'form'`, `Input` children with `name`, `onSubmit` handler |
|
|
336
|
+
|
|
337
|
+
### From Vue
|
|
338
|
+
|
|
339
|
+
| Vue Pattern | Symbols Equivalent |
|
|
340
|
+
| --------------------------------- | --------------------------------------------------------------------------------- |
|
|
341
|
+
| `<template>` + `<script>` SFC | Single object with child keys and flattened props |
|
|
342
|
+
| `:propName="value"` (v-bind) | `propName: value` or `propName: (el, s) => s.value` |
|
|
343
|
+
| `@click="handler"` (v-on) | `onClick: (e, el, s) => {}` |
|
|
344
|
+
| `v-if="condition"` | `if: (el, s) => condition` |
|
|
345
|
+
| `v-show="condition"` | `hide: (el, s) => !condition` |
|
|
346
|
+
| `v-for="item in items"` | `children: (el, s) => s.items, childExtends: 'X'` |
|
|
347
|
+
| `v-model="value"` | `value: '{{ key }}'` + `onInput: (e, el, s) => s.update({ key: e.target.value })` |
|
|
348
|
+
| `ref="myRef"` | `el.node` or `el.lookup('Key')` |
|
|
349
|
+
| `data()` / `ref()` / `reactive()` | `state: { key: value }` |
|
|
350
|
+
| `computed` | `text: (el, s) => s.first + ' ' + s.last` |
|
|
351
|
+
| `watch` | `onStateUpdate: (changes, el, s) => {}` |
|
|
352
|
+
| `mounted()` | `onRender: (el, s) => {}` |
|
|
353
|
+
| `created()` / `setup()` | `onInit: (el, s) => {}` |
|
|
354
|
+
| Vuex / Pinia store | `state/` folder + `s.root` access |
|
|
355
|
+
| `<router-link to="/path">` | `Link: { href: '/path' }` |
|
|
356
|
+
| `$router.push('/path')` | `el.call('router', '/path', el.getRoot())` |
|
|
357
|
+
| `<slot>` | PascalCase child keys or `content` property |
|
|
358
|
+
| `<slot name="header">` | Named child key: `Header: {}` |
|
|
359
|
+
| Mixins / Composables | `extends` for shared logic; `functions/` for shared utilities |
|
|
360
|
+
| `$emit('eventName', data)` | `s.parent.update({ key: data })` or callback via state |
|
|
361
|
+
|
|
362
|
+
---
|
|
363
|
+
|
|
364
|
+
## Part 3: CSS → Design Tokens
|
|
365
|
+
|
|
366
|
+
| CSS | Symbols |
|
|
367
|
+
| ----------------------------------------------------- | -------------------------------------------- |
|
|
368
|
+
| `padding: 16px` | `padding: 'A'` |
|
|
369
|
+
| `padding: 16px 26px` | `padding: 'A B'` |
|
|
370
|
+
| `margin: 0 auto` | `margin: '- auto'` |
|
|
371
|
+
| `gap: 10px` | `gap: 'Z'` |
|
|
372
|
+
| `border-radius: 12px` | `borderRadius: 'Z1'` or `round: 'Z1'` |
|
|
373
|
+
| `width: 42px; height: 42px` | `boxSize: 'C'` |
|
|
374
|
+
| `display: flex; flex-direction: column` | `flow: 'y'` |
|
|
375
|
+
| `display: flex; flex-direction: row` | `flow: 'x'` |
|
|
376
|
+
| `align-items: center; justify-content: center` | `flexAlign: 'center center'` |
|
|
377
|
+
| `display: grid; grid-template-columns: repeat(3,1fr)` | `extends: 'Grid', columns: 'repeat(3, 1fr)'` |
|
|
378
|
+
| `font-size: 20px` | `fontSize: 'A1'` |
|
|
379
|
+
| `font-weight: 500` | `fontWeight: '500'` |
|
|
380
|
+
| `color: rgba(255,255,255,0.65)` | `color: 'white.65'` |
|
|
381
|
+
| `background: #000` | `background: 'black'` |
|
|
382
|
+
| `background: rgba(0,0,0,0.5)` | `background: 'black.5'` |
|
|
383
|
+
| `cursor: pointer` | `cursor: 'pointer'` |
|
|
384
|
+
| `overflow: hidden` | `overflow: 'hidden'` |
|
|
385
|
+
| `position: absolute; inset: 0` | `position: 'absolute', inset: '0'` |
|
|
386
|
+
| `z-index: 99` | `zIndex: 99` |
|
|
387
|
+
| `transition: all 0.3s ease` | `transition: 'A defaultBezier'` |
|
|
388
|
+
| `:hover { background: #333 }` | `':hover': { background: 'gray3' }` |
|
|
389
|
+
| `@media (max-width: 768px) { ... }` | `'@mobileL': { ... }` |
|
|
390
|
+
|
|
391
|
+
### Spacing token quick reference
|
|
392
|
+
|
|
393
|
+
| Token | ~px | Token | ~px | Token | ~px |
|
|
394
|
+
| ----- | --- | ----- | --- | ----- | --- |
|
|
395
|
+
| X | 3 | A | 16 | D | 67 |
|
|
396
|
+
| Y | 6 | A1 | 20 | E | 109 |
|
|
397
|
+
| Z | 10 | A2 | 22 | F | 177 |
|
|
398
|
+
| Z1 | 12 | B | 26 | | |
|
|
399
|
+
| Z2 | 14 | B1 | 32 | | |
|
|
400
|
+
| | | B2 | 36 | | |
|
|
401
|
+
| | | C | 42 | | |
|
|
402
|
+
| | | C1 | 52 | | |
|
|
403
|
+
| | | C2 | 55 | | |
|
|
404
|
+
|
|
405
|
+
---
|
|
406
|
+
|
|
407
|
+
## Part 4: v3 Component Template
|
|
408
|
+
|
|
409
|
+
Use this as the base template for every new component:
|
|
410
|
+
|
|
411
|
+
```js
|
|
412
|
+
// components/ComponentName.js
|
|
413
|
+
export const ComponentName = {
|
|
414
|
+
extends: 'Flex',
|
|
415
|
+
|
|
416
|
+
// Props flattened at root (design tokens)
|
|
417
|
+
padding: 'A B',
|
|
418
|
+
background: 'surface',
|
|
419
|
+
borderRadius: 'B',
|
|
420
|
+
gap: 'Z',
|
|
421
|
+
|
|
422
|
+
// Lifecycle events
|
|
423
|
+
onRender: (el, s) => {},
|
|
424
|
+
onInit: (el, s) => {},
|
|
425
|
+
|
|
426
|
+
// DOM events
|
|
427
|
+
onClick: (e, el, s) => {},
|
|
428
|
+
|
|
429
|
+
// Conditional cases (v3 pattern)
|
|
430
|
+
isActive: false,
|
|
431
|
+
'.isActive': { background: 'primary', color: 'white' },
|
|
432
|
+
|
|
433
|
+
// Responsive
|
|
434
|
+
'@mobileL': { padding: 'A' },
|
|
435
|
+
'@tabletS': { padding: 'B' },
|
|
436
|
+
|
|
437
|
+
// Children — PascalCase keys, no imports
|
|
438
|
+
Header: {},
|
|
439
|
+
Content: {
|
|
440
|
+
Article: { text: 'Hello' },
|
|
441
|
+
},
|
|
442
|
+
Footer: {},
|
|
443
|
+
}
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
---
|
|
447
|
+
|
|
448
|
+
## Part 5: State Management Migration
|
|
449
|
+
|
|
450
|
+
| Framework pattern | Symbols equivalent |
|
|
451
|
+
| ------------------------- | ---------------------------------------------------- |
|
|
452
|
+
| Global store (Redux, Pinia, NgRx) | `state/index.js` with initial flat state. Access via `s.root` in any component |
|
|
453
|
+
| Local component state | `state: { key: val }` on the component |
|
|
454
|
+
| Derived/computed state | Dynamic prop function: `text: (el, s) => derived(s)` |
|
|
455
|
+
| Async data fetch | `onRender: async (el, s) => { ... s.update({data}) }` |
|
|
456
|
+
| State persistence | Functions that read/write to localStorage/cookie |
|
|
457
|
+
|
|
458
|
+
Example async state pattern:
|
|
459
|
+
```js
|
|
460
|
+
export const DataView = {
|
|
461
|
+
state: { data: null, loading: true, error: null },
|
|
462
|
+
|
|
463
|
+
onRender: async (el, state) => {
|
|
464
|
+
try {
|
|
465
|
+
const data = await el.call('fetchData', el.props.id)
|
|
466
|
+
state.update({ data, loading: false })
|
|
467
|
+
} catch (e) {
|
|
468
|
+
state.update({ error: e.message, loading: false })
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
---
|
|
475
|
+
|
|
476
|
+
## Part 6: Color/Border/Shadow Syntax Migration (v3.1)
|
|
477
|
+
|
|
478
|
+
### Color tokens: space-separated to dot-notation
|
|
479
|
+
|
|
480
|
+
| Old | New | Notes |
|
|
481
|
+
|---|---|---|
|
|
482
|
+
| `'white .1'` | `'white.1'` | Opacity 0.1 |
|
|
483
|
+
| `'gray 0.85'` | `'gray.85'` | Opacity 0.85 |
|
|
484
|
+
| `'gray .92 +8'` | `'gray.92+8'` | Opacity + relative tone |
|
|
485
|
+
| `'gray 1 +16'` | `'gray+16'` | Alpha 1 = default, omit |
|
|
486
|
+
| `'gray 1 -68'` | `'gray-68'` | Relative tone |
|
|
487
|
+
| `'gray 1 90'` | `'gray=90'` | Absolute lightness (=prefix) |
|
|
488
|
+
| `'white 1 -78'` | `'white-78'` | Tone only |
|
|
489
|
+
|
|
490
|
+
### border: comma-separated to space-separated (CSS order)
|
|
491
|
+
|
|
492
|
+
| Old | New |
|
|
493
|
+
|---|---|
|
|
494
|
+
| `'solid, gray, 1px'` | `'1px solid gray'` |
|
|
495
|
+
| `'gray6 .1, solid, 1px'` | `'1px solid gray6.1'` |
|
|
496
|
+
| `'solid, mediumGrey'` | `'solid mediumGrey'` |
|
|
497
|
+
| `'1px, solid'` | `'1px solid'` |
|
|
498
|
+
|
|
499
|
+
### boxShadow: commas to spaces within shadow, pipe to comma between shadows
|
|
500
|
+
|
|
501
|
+
| Old | New |
|
|
502
|
+
|---|---|
|
|
503
|
+
| `'white .1, 0, A, C, C'` | `'white.1 0 A C C'` |
|
|
504
|
+
| `'black .10, 0px, 2px, 8px, 0px'` | `'black.1 0px 2px 8px 0px'` |
|
|
505
|
+
| `'a, b \| c, d'` | `'a b, c d'` |
|
|
506
|
+
|
|
507
|
+
### textStroke/textShadow: comma-separated to space-separated
|
|
508
|
+
|
|
509
|
+
| Old | New |
|
|
510
|
+
|---|---|
|
|
511
|
+
| `'1px, gray6'` | `'1px gray6'` |
|
|
512
|
+
| `'gray1, 6px, 6px'` | `'gray1 6px 6px'` |
|
|
513
|
+
|
|
514
|
+
### CSS fallback
|
|
515
|
+
|
|
516
|
+
Raw CSS values now pass through unchanged:
|
|
517
|
+
```js
|
|
518
|
+
boxShadow: '0 2px 8px rgba(0,0,0,0.1)' // passes through as-is
|
|
519
|
+
border: '1px solid #333' // passes through as-is
|
|
520
|
+
```
|