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