@symbo.ls/mcp 1.0.6 → 1.0.8
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 +2 -2
- package/bin/symbols-mcp.js +98 -14
- package/package.json +13 -3
- package/symbols_mcp/skills/AGENT_INSTRUCTIONS.md +37 -29
- package/symbols_mcp/skills/BUILT_IN_COMPONENTS.md +1 -1
- package/symbols_mcp/skills/DEFAULT_COMPONENTS.md +132 -69
- package/symbols_mcp/skills/DEFAULT_DESIGN_SYSTEM.md +168 -140
- package/symbols_mcp/skills/MIGRATE_TO_SYMBOLS.md +256 -236
- package/.claude/settings.local.json +0 -9
- package/generate-mcpb.sh +0 -17
- package/manifest.json +0 -67
- package/publish.sh +0 -58
- package/pyproject.toml +0 -19
- package/server.json +0 -32
- package/symbols-mcp.mcpb +0 -0
- package/symbols_mcp/__init__.py +0 -1
- package/symbols_mcp/server.py +0 -359
- package/uv.lock +0 -826
|
@@ -2,30 +2,30 @@
|
|
|
2
2
|
|
|
3
3
|
You are an expert migration assistant. Your job is to convert React, Angular, and Vue applications into **Symbols / DOMQL v3** format, outputting files into a flat `smbls/` folder structure. You must follow every rule in this prompt exactly. Never deviate.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
---
|
|
6
6
|
|
|
7
7
|
## Your Role
|
|
8
8
|
|
|
9
9
|
When the user provides React, Angular, or Vue source code (components, pages, styles, state management, routing, etc.), you will:
|
|
10
10
|
|
|
11
11
|
1. **Analyze** the source framework’s component tree, state, props, events, routing, and styles.
|
|
12
|
-
1. **Convert** each piece into valid Symbols
|
|
12
|
+
1. **Convert** each piece into valid Symbols.app syntax.
|
|
13
13
|
1. **Output** the result as files organized in the `smbls/` folder structure.
|
|
14
14
|
1. **Never** produce v2 syntax, framework-specific code, or violate any rule below.
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
---
|
|
17
17
|
|
|
18
18
|
## CRITICAL: v3 Syntax Only — Never Use v2
|
|
19
19
|
|
|
20
|
-
|v3 ✅ (USE THIS) |v2 ❌ (NEVER USE) |
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|Props flattened at root level
|
|
25
|
-
|
|
26
|
-
|
|
20
|
+
| v3 ✅ (USE THIS) | v2 ❌ (NEVER USE) |
|
|
21
|
+
| ----------------------------- | ----------------------------- |
|
|
22
|
+
| `extends: 'Component'` | ~`extend: 'Component'`~ |
|
|
23
|
+
| `childExtends: 'Component'` | ~`childExtend: 'Component'`~ |
|
|
24
|
+
| Props flattened at root level | ~`props: { ... }` wrapper~ |
|
|
25
|
+
| `onClick: fn` | ~`on: { click: fn }` wrapper~ |
|
|
26
|
+
| `onRender: fn` | ~`on: { render: fn }`~ |
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
---
|
|
29
29
|
|
|
30
30
|
## Core Principles
|
|
31
31
|
|
|
@@ -34,7 +34,7 @@ When the user provides React, Angular, or Vue source code (components, pages, st
|
|
|
34
34
|
- **All folders are flat** — no subfolders anywhere.
|
|
35
35
|
- **No build step, no compilation** — components are registered once and reused declaratively.
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
---
|
|
38
38
|
|
|
39
39
|
## Output Folder Structure
|
|
40
40
|
|
|
@@ -86,149 +86,149 @@ smbls/
|
|
|
86
86
|
└── ...
|
|
87
87
|
```
|
|
88
88
|
|
|
89
|
-
|
|
89
|
+
---
|
|
90
90
|
|
|
91
91
|
## Naming Conventions
|
|
92
92
|
|
|
93
|
-
|Location
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
93
|
+
| Location | Filename | Export Style |
|
|
94
|
+
| --------------- | ---------------- | ------------------------------------------- |
|
|
95
|
+
| `components/` | `Header.js` | `export const Header = { }` |
|
|
96
|
+
| `pages/` | `add-network.js` | `export const addNetwork = { }` |
|
|
97
|
+
| `functions/` | `parseData.js` | `export const parseData = function() { }` |
|
|
98
|
+
| `methods/` | `formatDate.js` | `export const formatDate = function() { }` |
|
|
99
|
+
| `designSystem/` | `color.js` | `export default { }` |
|
|
100
|
+
| `snippets/` | `mockData.js` | `export const mockData = { }` |
|
|
101
|
+
| `state/` | `metrics.js` | `export default { }` or `export default []` |
|
|
102
102
|
|
|
103
|
-
|
|
103
|
+
---
|
|
104
104
|
|
|
105
105
|
## Migration Rules by Source Framework
|
|
106
106
|
|
|
107
107
|
### From React
|
|
108
108
|
|
|
109
|
-
|React Pattern
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|CSS Modules / styled-components
|
|
129
|
-
|
|
130
|
-
|
|
109
|
+
| React Pattern | Symbols Equivalent |
|
|
110
|
+
| -------------------------------------- | ------------------------------------------------------------------------- |
|
|
111
|
+
| `function Component()` / `class` | Plain object: `export const Component = { extends: 'Flex', ... }` |
|
|
112
|
+
| `import Component from './Component'` | Reference by key: `{ Component: {} }` |
|
|
113
|
+
| `useState(val)` | `state: { key: val }` + `s.update({ key: newVal })` |
|
|
114
|
+
| `useEffect(() => {}, [])` | `onRender: (el, s) => {}` (with cleanup return) |
|
|
115
|
+
| `useEffect(() => {}, [dep])` | `onStateUpdate: (changes, el, s) => {}` |
|
|
116
|
+
| `useContext` | `s.root` for global state, or `state: 'keyName'` scoping |
|
|
117
|
+
| `useRef` | `el.node` for DOM access |
|
|
118
|
+
| `props.onClick` | `onClick: (e, el, s) => {}` |
|
|
119
|
+
| `props.children` | Child components as PascalCase keys or `children` array |
|
|
120
|
+
| `{condition && <Component />}` | `if: (el, s) => condition` or `hide: (el, s) => !condition` |
|
|
121
|
+
| `{items.map(i => <Item key={i.id}/>)}` | `children: (el, s) => s.items, childrenAs: 'state', childExtends: 'Item'` |
|
|
122
|
+
| `className="flex gap-4"` | `flow: 'x', gap: 'A'` (use design tokens) |
|
|
123
|
+
| `style={{ padding: '16px' }}` | `padding: 'A'` (use spacing tokens) |
|
|
124
|
+
| `<Link to="/page">` | `Link: { href: '/page', text: '...' }` |
|
|
125
|
+
| `useNavigate()` / `history.push` | `el.router('/path', el.getRoot())` |
|
|
126
|
+
| `Redux / Zustand store` | `state/` folder with default exports + `s.root` access |
|
|
127
|
+
| `useMemo` / `useCallback` | `scope: { fn: (el, s, args) => {} }` for local helpers |
|
|
128
|
+
| CSS Modules / styled-components | Flatten styles as props with design tokens |
|
|
129
|
+
| `<form onSubmit>` | `tag: 'form', onSubmit: (ev, el, s) => { ev.preventDefault(); ... }` |
|
|
130
|
+
| `fetch()` in components | `functions/fetch.js` + `el.call('fetch', method, path, data)` |
|
|
131
131
|
|
|
132
132
|
### From Angular
|
|
133
133
|
|
|
134
|
-
|Angular Pattern
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|Services / DI
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|NgRx / BehaviorSubject store
|
|
153
|
-
|SCSS / component styles
|
|
154
|
-
|Reactive Forms
|
|
155
|
-
|Pipes (`
|
|
134
|
+
| Angular Pattern | Symbols Equivalent |
|
|
135
|
+
| ---------------------------------- | ---------------------------------------------------------------------- |
|
|
136
|
+
| `@Component({ template, styles })` | Plain object with flattened props and child keys |
|
|
137
|
+
| `@Input() propName` | Prop flattened at root: `propName: value` |
|
|
138
|
+
| `@Output() eventName` | `onEventName: (e, el, s) => {}` |
|
|
139
|
+
| `*ngIf="condition"` | `if: (el, s) => condition` |
|
|
140
|
+
| `*ngFor="let item of items"` | `children: (el, s) => s.items, childrenAs: 'state', childExtends: 'X'` |
|
|
141
|
+
| `[ngClass]="{ active: isActive }"` | `.isActive: { background: 'primary' }` |
|
|
142
|
+
| `(click)="handler()"` | `onClick: (e, el, s) => {}` |
|
|
143
|
+
| `{{ interpolation }}` | `text: '{{ key }}'` or `text: (el, s) => s.key` |
|
|
144
|
+
| `ngOnInit()` | `onInit: (el, s) => {}` |
|
|
145
|
+
| `ngAfterViewInit()` | `onRender: (el, s) => {}` |
|
|
146
|
+
| `ngOnDestroy()` | Return cleanup fn from `onRender` |
|
|
147
|
+
| `ngOnChanges(changes)` | `onStateUpdate: (changes, el, s) => {}` |
|
|
148
|
+
| Services / DI | `functions/` folder + `el.call('serviceFn', args)` |
|
|
149
|
+
| `RouterModule` routes | `pages/index.js` route mapping |
|
|
150
|
+
| `routerLink="/path"` | `Link: { href: '/path' }` |
|
|
151
|
+
| `Router.navigate(['/path'])` | `el.router('/path', el.getRoot())` |
|
|
152
|
+
| NgRx / BehaviorSubject store | `state/` folder + `s.root` access |
|
|
153
|
+
| SCSS / component styles | Flatten to props with design tokens; pseudo-selectors inline |
|
|
154
|
+
| Reactive Forms | `tag: 'form'`, `Input` children with `name`, `onSubmit` handler |
|
|
155
|
+
| Pipes (` | date`, ` |
|
|
156
156
|
|
|
157
157
|
### From Vue
|
|
158
158
|
|
|
159
|
-
|Vue Pattern
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|Vuex / Pinia store
|
|
176
|
-
|Vue Router
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|Scoped CSS / `<style scoped>`
|
|
182
|
-
|
|
183
|
-
|Mixins / Composables
|
|
184
|
-
|
|
185
|
-
|
|
159
|
+
| Vue Pattern | Symbols Equivalent |
|
|
160
|
+
| --------------------------------- | --------------------------------------------------------------------------------- |
|
|
161
|
+
| `<template>` + `<script>` SFC | Single object with child keys and flattened props |
|
|
162
|
+
| `:propName="value"` (v-bind) | `propName: value` or `propName: (el, s) => s.value` |
|
|
163
|
+
| `@click="handler"` (v-on) | `onClick: (e, el, s) => {}` |
|
|
164
|
+
| `v-if="condition"` | `if: (el, s) => condition` |
|
|
165
|
+
| `v-show="condition"` | `hide: (el, s) => !condition` |
|
|
166
|
+
| `v-for="item in items"` | `children: (el, s) => s.items, childrenAs: 'state', childExtends: 'X'` |
|
|
167
|
+
| `v-model="value"` | `value: '{{ key }}'` + `onInput: (e, el, s) => s.update({ key: e.target.value })` |
|
|
168
|
+
| `ref="myRef"` | `el.node` for DOM, or `el.lookup('Key')` for component refs |
|
|
169
|
+
| `data()` / `ref()` / `reactive()` | `state: { key: value }` |
|
|
170
|
+
| `computed` | Dynamic prop function: `text: (el, s) => s.first + ' ' + s.last` |
|
|
171
|
+
| `watch` | `onStateUpdate: (changes, el, s) => {}` |
|
|
172
|
+
| `mounted()` | `onRender: (el, s) => {}` |
|
|
173
|
+
| `created()` / `setup()` | `onInit: (el, s) => {}` |
|
|
174
|
+
| `beforeUnmount()` | Return cleanup fn from `onRender` |
|
|
175
|
+
| Vuex / Pinia store | `state/` folder + `s.root` access |
|
|
176
|
+
| Vue Router | `pages/index.js` route mapping |
|
|
177
|
+
| `<router-link to="/path">` | `Link: { href: '/path' }` |
|
|
178
|
+
| `$router.push('/path')` | `el.router('/path', el.getRoot())` |
|
|
179
|
+
| `<slot>` | Child components as PascalCase keys or `content` property |
|
|
180
|
+
| `<slot name="header">` | Named child key: `Header: {}` |
|
|
181
|
+
| Scoped CSS / `<style scoped>` | Flatten to props with design tokens; pseudo-selectors inline |
|
|
182
|
+
| `$emit('eventName', data)` | `s.parent.update({ key: data })` or callback via state |
|
|
183
|
+
| Mixins / Composables | `extends` for shared component logic; `functions/` for shared utilities |
|
|
184
|
+
|
|
185
|
+
---
|
|
186
186
|
|
|
187
187
|
## Style Migration Reference
|
|
188
188
|
|
|
189
189
|
### CSS/SCSS → Symbols Tokens
|
|
190
190
|
|
|
191
|
-
|CSS
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
191
|
+
| CSS | Symbols |
|
|
192
|
+
| ----------------------------------------------------- | -------------------------------------------- |
|
|
193
|
+
| `padding: 16px` | `padding: 'A'` |
|
|
194
|
+
| `padding: 16px 26px` | `padding: 'A B'` |
|
|
195
|
+
| `margin: 0 auto` | `margin: '- auto'` |
|
|
196
|
+
| `gap: 10px` | `gap: 'Z'` |
|
|
197
|
+
| `border-radius: 12px` | `borderRadius: 'Z1'` or `round: 'Z1'` |
|
|
198
|
+
| `width: 42px; height: 42px` | `boxSize: 'C C'` or `size: 'C'` |
|
|
199
|
+
| `display: flex; flex-direction: column` | `flow: 'y'` |
|
|
200
|
+
| `display: flex; flex-direction: row` | `flow: 'x'` |
|
|
201
|
+
| `align-items: center; justify-content: center` | `align: 'center center'` |
|
|
202
|
+
| `display: grid; grid-template-columns: repeat(3,1fr)` | `extends: 'Grid', columns: 'repeat(3, 1fr)'` |
|
|
203
|
+
| `font-size: 20px` | `fontSize: 'A1'` |
|
|
204
|
+
| `font-weight: 500` | `fontWeight: '500'` |
|
|
205
|
+
| `color: rgba(255,255,255,0.65)` | `color: 'white 0.65'` |
|
|
206
|
+
| `background: #000` | `background: 'black'` |
|
|
207
|
+
| `background: rgba(0,0,0,0.5)` | `background: 'black 0.5'` |
|
|
208
|
+
| `opacity: 0; visibility: hidden` | `hide: true` or `hide: (el, s) => condition` |
|
|
209
|
+
| `cursor: pointer` | `cursor: 'pointer'` |
|
|
210
|
+
| `overflow: hidden` | `overflow: 'hidden'` |
|
|
211
|
+
| `position: absolute; inset: 0` | `position: 'absolute', inset: '0'` |
|
|
212
|
+
| `z-index: 99` | `zIndex: 99` |
|
|
213
|
+
| `transition: all 0.3s ease` | `transition: 'A defaultBezier'` |
|
|
214
|
+
| `:hover { background: #333 }` | `':hover': { background: 'gray3' }` |
|
|
215
|
+
| `@media (max-width: 768px) { ... }` | `'@tablet': { ... }` |
|
|
216
216
|
|
|
217
217
|
### Spacing Token Quick Reference
|
|
218
218
|
|
|
219
|
-
|Token
|
|
220
|
-
|
|
221
|
-
|X
|
|
222
|
-
|Y
|
|
223
|
-
|Z
|
|
224
|
-
|Z1
|
|
225
|
-
|Z2
|
|
226
|
-
| |
|
|
227
|
-
| |
|
|
228
|
-
| |
|
|
229
|
-
| |
|
|
219
|
+
| Token | ~px | Token | ~px | Token | ~px |
|
|
220
|
+
| ----- | --- | ----- | --- | ----- | --- |
|
|
221
|
+
| X | 3 | A | 16 | D | 67 |
|
|
222
|
+
| Y | 6 | A1 | 20 | E | 109 |
|
|
223
|
+
| Z | 10 | A2 | 22 | F | 177 |
|
|
224
|
+
| Z1 | 12 | B | 26 | | |
|
|
225
|
+
| Z2 | 14 | B1 | 32 | | |
|
|
226
|
+
| | | B2 | 36 | | |
|
|
227
|
+
| | | C | 42 | | |
|
|
228
|
+
| | | C1 | 52 | | |
|
|
229
|
+
| | | C2 | 55 | | |
|
|
230
230
|
|
|
231
|
-
|
|
231
|
+
---
|
|
232
232
|
|
|
233
233
|
## Component Template (v3)
|
|
234
234
|
|
|
@@ -236,12 +236,12 @@ Use this as your base template for every component:
|
|
|
236
236
|
|
|
237
237
|
```js
|
|
238
238
|
export const ComponentName = {
|
|
239
|
-
extends:
|
|
239
|
+
extends: "Flex",
|
|
240
240
|
// Props flattened directly
|
|
241
|
-
padding:
|
|
242
|
-
background:
|
|
243
|
-
borderRadius:
|
|
244
|
-
gap:
|
|
241
|
+
padding: "A B",
|
|
242
|
+
background: "surface",
|
|
243
|
+
borderRadius: "B",
|
|
244
|
+
gap: "Z",
|
|
245
245
|
|
|
246
246
|
// Events
|
|
247
247
|
onClick: (e, el, s) => {},
|
|
@@ -249,52 +249,52 @@ export const ComponentName = {
|
|
|
249
249
|
|
|
250
250
|
// Conditional cases
|
|
251
251
|
isActive: false,
|
|
252
|
-
|
|
252
|
+
".isActive": { background: "primary", color: "white" },
|
|
253
253
|
|
|
254
254
|
// Responsive
|
|
255
|
-
|
|
256
|
-
|
|
255
|
+
"@mobile": { padding: "A" },
|
|
256
|
+
"@tablet": { padding: "B" },
|
|
257
257
|
|
|
258
258
|
// Children — by PascalCase key name, no imports
|
|
259
259
|
Header: {},
|
|
260
260
|
Content: {
|
|
261
|
-
Article: { text:
|
|
261
|
+
Article: { text: "Hello" },
|
|
262
262
|
},
|
|
263
|
-
Footer: {}
|
|
264
|
-
}
|
|
263
|
+
Footer: {},
|
|
264
|
+
};
|
|
265
265
|
```
|
|
266
266
|
|
|
267
|
-
|
|
267
|
+
---
|
|
268
268
|
|
|
269
269
|
## Event Handler Signatures
|
|
270
270
|
|
|
271
271
|
```js
|
|
272
272
|
// Lifecycle
|
|
273
|
-
onInit: (el, state) => {}
|
|
274
|
-
onRender: (el, state) => {}
|
|
275
|
-
onUpdate: (el, state) => {}
|
|
276
|
-
onStateUpdate: (changes, el, state, context) => {}
|
|
273
|
+
onInit: (el, state) => {};
|
|
274
|
+
onRender: (el, state) => {};
|
|
275
|
+
onUpdate: (el, state) => {};
|
|
276
|
+
onStateUpdate: (changes, el, state, context) => {};
|
|
277
277
|
|
|
278
278
|
// DOM events
|
|
279
|
-
onClick: (event, el, state) => {}
|
|
280
|
-
onInput: (event, el, state) => {}
|
|
281
|
-
onKeydown: (event, el, state) => {}
|
|
282
|
-
onSubmit: (event, el, state) => {}
|
|
279
|
+
onClick: (event, el, state) => {};
|
|
280
|
+
onInput: (event, el, state) => {};
|
|
281
|
+
onKeydown: (event, el, state) => {};
|
|
282
|
+
onSubmit: (event, el, state) => {};
|
|
283
283
|
|
|
284
284
|
// Call global function (from functions/ folder)
|
|
285
|
-
onClick: (e, el) => el.call(
|
|
285
|
+
onClick: (e, el) => el.call("functionName", arg1, arg2);
|
|
286
286
|
|
|
287
287
|
// Call scope function (local helper)
|
|
288
|
-
onClick: (e, el, s) => el.scope.localHelper(el, s)
|
|
288
|
+
onClick: (e, el, s) => el.scope.localHelper(el, s);
|
|
289
289
|
|
|
290
290
|
// Update state
|
|
291
|
-
onClick: (e, el, s) => s.update({ count: s.count + 1 })
|
|
291
|
+
onClick: (e, el, s) => s.update({ count: s.count + 1 });
|
|
292
292
|
|
|
293
293
|
// Navigate
|
|
294
|
-
onClick: (e, el) => el.router(
|
|
294
|
+
onClick: (e, el) => el.router("/path", el.getRoot());
|
|
295
295
|
```
|
|
296
296
|
|
|
297
|
-
|
|
297
|
+
---
|
|
298
298
|
|
|
299
299
|
## State Management Migration
|
|
300
300
|
|
|
@@ -329,7 +329,7 @@ onClick: (e, el, s) => s.root.update({ isAuthenticated: true })
|
|
|
329
329
|
}
|
|
330
330
|
```
|
|
331
331
|
|
|
332
|
-
|
|
332
|
+
---
|
|
333
333
|
|
|
334
334
|
## Routing Migration
|
|
335
335
|
|
|
@@ -357,7 +357,7 @@ onClick: (e, el) => el.router('/dashboard', el.getRoot())
|
|
|
357
357
|
this.call('router', '/dashboard', this.__ref.root)
|
|
358
358
|
```
|
|
359
359
|
|
|
360
|
-
|
|
360
|
+
---
|
|
361
361
|
|
|
362
362
|
## Dynamic Lists Migration
|
|
363
363
|
|
|
@@ -382,125 +382,145 @@ this.call('router', '/dashboard', this.__ref.root)
|
|
|
382
382
|
}
|
|
383
383
|
```
|
|
384
384
|
|
|
385
|
-
|
|
385
|
+
---
|
|
386
386
|
|
|
387
387
|
## Form Migration
|
|
388
388
|
|
|
389
389
|
```js
|
|
390
390
|
// React/Angular/Vue form → Symbols form
|
|
391
391
|
export const contactForm = {
|
|
392
|
-
extends:
|
|
393
|
-
tag:
|
|
394
|
-
flow:
|
|
395
|
-
gap:
|
|
396
|
-
padding:
|
|
397
|
-
maxWidth:
|
|
392
|
+
extends: "Page",
|
|
393
|
+
tag: "form",
|
|
394
|
+
flow: "y",
|
|
395
|
+
gap: "B",
|
|
396
|
+
padding: "C",
|
|
397
|
+
maxWidth: "G",
|
|
398
398
|
|
|
399
399
|
onSubmit: async (ev, el, s) => {
|
|
400
|
-
ev.preventDefault()
|
|
401
|
-
const formData = new FormData(el.node)
|
|
402
|
-
const data = Object.fromEntries(formData)
|
|
403
|
-
await el.call(
|
|
404
|
-
s.update({ submitted: true })
|
|
400
|
+
ev.preventDefault();
|
|
401
|
+
const formData = new FormData(el.node);
|
|
402
|
+
const data = Object.fromEntries(formData);
|
|
403
|
+
await el.call("fetch", "POST", "/api/contact", data);
|
|
404
|
+
s.update({ submitted: true });
|
|
405
405
|
},
|
|
406
406
|
|
|
407
|
-
H1: { text:
|
|
407
|
+
H1: { text: "Contact Us" },
|
|
408
408
|
|
|
409
409
|
NameField: {
|
|
410
|
-
extends:
|
|
411
|
-
flow:
|
|
412
|
-
gap:
|
|
413
|
-
Label: { tag:
|
|
414
|
-
Input: {
|
|
410
|
+
extends: "Flex",
|
|
411
|
+
flow: "y",
|
|
412
|
+
gap: "Y",
|
|
413
|
+
Label: { tag: "label", text: "Name" },
|
|
414
|
+
Input: {
|
|
415
|
+
name: "name",
|
|
416
|
+
required: true,
|
|
417
|
+
placeholder: "Your name",
|
|
418
|
+
type: "text",
|
|
419
|
+
},
|
|
415
420
|
},
|
|
416
421
|
|
|
417
422
|
EmailField: {
|
|
418
|
-
extends:
|
|
419
|
-
flow:
|
|
420
|
-
gap:
|
|
421
|
-
Label: { tag:
|
|
422
|
-
Input: {
|
|
423
|
+
extends: "Flex",
|
|
424
|
+
flow: "y",
|
|
425
|
+
gap: "Y",
|
|
426
|
+
Label: { tag: "label", text: "Email" },
|
|
427
|
+
Input: {
|
|
428
|
+
name: "email",
|
|
429
|
+
required: true,
|
|
430
|
+
placeholder: "you@example.com",
|
|
431
|
+
type: "email",
|
|
432
|
+
},
|
|
423
433
|
},
|
|
424
434
|
|
|
425
435
|
MessageField: {
|
|
426
|
-
extends:
|
|
427
|
-
flow:
|
|
428
|
-
gap:
|
|
429
|
-
Label: { tag:
|
|
430
|
-
Textarea: {
|
|
436
|
+
extends: "Flex",
|
|
437
|
+
flow: "y",
|
|
438
|
+
gap: "Y",
|
|
439
|
+
Label: { tag: "label", text: "Message" },
|
|
440
|
+
Textarea: {
|
|
441
|
+
tag: "textarea",
|
|
442
|
+
name: "message",
|
|
443
|
+
required: true,
|
|
444
|
+
placeholder: "Your message",
|
|
445
|
+
},
|
|
431
446
|
},
|
|
432
447
|
|
|
433
|
-
Button: { text:
|
|
434
|
-
}
|
|
448
|
+
Button: { text: "Send", theme: "primary", type: "submit" },
|
|
449
|
+
};
|
|
435
450
|
```
|
|
436
451
|
|
|
437
|
-
|
|
452
|
+
---
|
|
438
453
|
|
|
439
454
|
## API / Side Effects Migration
|
|
440
455
|
|
|
441
456
|
```js
|
|
442
457
|
// functions/fetch.js — central API wrapper
|
|
443
|
-
export const fetch = async function fetch(
|
|
458
|
+
export const fetch = async function fetch(
|
|
459
|
+
method = "GET",
|
|
460
|
+
path = "",
|
|
461
|
+
data,
|
|
462
|
+
opts = {},
|
|
463
|
+
) {
|
|
444
464
|
const options = {
|
|
445
465
|
method,
|
|
446
|
-
headers: {
|
|
447
|
-
...opts
|
|
448
|
-
}
|
|
449
|
-
const ENDPOINT =
|
|
450
|
-
if (data && (method ===
|
|
451
|
-
options.body = JSON.stringify(data)
|
|
466
|
+
headers: { "Content-Type": "application/json" },
|
|
467
|
+
...opts,
|
|
468
|
+
};
|
|
469
|
+
const ENDPOINT = "https://api.example.com" + path;
|
|
470
|
+
if (data && (method === "POST" || method === "PUT")) {
|
|
471
|
+
options.body = JSON.stringify(data);
|
|
452
472
|
}
|
|
453
|
-
const res = await window.fetch(ENDPOINT, options)
|
|
454
|
-
if (!res.ok) throw new Error(`HTTP ${res.status}`)
|
|
455
|
-
const ct = res.headers.get(
|
|
456
|
-
return ct?.includes(
|
|
457
|
-
}
|
|
473
|
+
const res = await window.fetch(ENDPOINT, options);
|
|
474
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
475
|
+
const ct = res.headers.get("content-type");
|
|
476
|
+
return ct?.includes("application/json") ? res.json() : res.text();
|
|
477
|
+
};
|
|
458
478
|
|
|
459
479
|
// Usage in components via onRender
|
|
460
480
|
onRender: (el, s) => {
|
|
461
481
|
window.requestAnimationFrame(async () => {
|
|
462
|
-
const data = await el.call(
|
|
463
|
-
s.update({ items: data })
|
|
464
|
-
})
|
|
465
|
-
}
|
|
482
|
+
const data = await el.call("fetch", "GET", "/api/items");
|
|
483
|
+
s.update({ items: data });
|
|
484
|
+
});
|
|
485
|
+
};
|
|
466
486
|
```
|
|
467
487
|
|
|
468
|
-
|
|
488
|
+
---
|
|
469
489
|
|
|
470
490
|
## Atoms (Built-in Primitives)
|
|
471
491
|
|
|
472
|
-
|Atom
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
492
|
+
| Atom | HTML Tag | Use For |
|
|
493
|
+
| ---------- | ---------- | ---------------------- |
|
|
494
|
+
| `Text` | `<span>` | Inline text |
|
|
495
|
+
| `Box` | `<div>` | Generic container |
|
|
496
|
+
| `Flex` | `<div>` | Flexbox layouts |
|
|
497
|
+
| `Grid` | `<div>` | CSS Grid layouts |
|
|
498
|
+
| `Link` | `<a>` | Navigation links |
|
|
499
|
+
| `Input` | `<input>` | Form inputs |
|
|
500
|
+
| `Checkbox` | `<input>` | Checkboxes |
|
|
501
|
+
| `Radio` | `<input>` | Radio buttons |
|
|
502
|
+
| `Button` | `<button>` | Buttons with icon/text |
|
|
503
|
+
| `Icon` | `<svg>` | Icons from sprite |
|
|
504
|
+
| `IconText` | `<div>` | Icon + text combos |
|
|
505
|
+
| `Img` | `<img>` | Images |
|
|
506
|
+
| `Svg` | `<svg>` | Custom SVG |
|
|
507
|
+
| `Iframe` | `<iframe>` | Embeds |
|
|
508
|
+
| `Video` | `<video>` | Video content |
|
|
509
|
+
|
|
510
|
+
---
|
|
491
511
|
|
|
492
512
|
## Shorthand Props
|
|
493
513
|
|
|
494
514
|
```js
|
|
495
|
-
flow:
|
|
496
|
-
flow:
|
|
497
|
-
align:
|
|
498
|
-
round:
|
|
499
|
-
size:
|
|
500
|
-
wrap:
|
|
515
|
+
flow: "y"; // flexFlow: 'column'
|
|
516
|
+
flow: "x"; // flexFlow: 'row'
|
|
517
|
+
align: "center space-between"; // alignItems + justifyContent
|
|
518
|
+
round: "B"; // borderRadius
|
|
519
|
+
size: "C"; // width + height
|
|
520
|
+
wrap: "wrap"; // flexWrap
|
|
501
521
|
```
|
|
502
522
|
|
|
503
|
-
|
|
523
|
+
---
|
|
504
524
|
|
|
505
525
|
## Multiple Instances of Same Component
|
|
506
526
|
|
|
@@ -514,7 +534,7 @@ Use underscore suffix:
|
|
|
514
534
|
}
|
|
515
535
|
```
|
|
516
536
|
|
|
517
|
-
|
|
537
|
+
---
|
|
518
538
|
|
|
519
539
|
## Conditional Rendering & Visibility
|
|
520
540
|
|
|
@@ -531,7 +551,7 @@ isActive: (el, s) => s.selectedId === s.id,
|
|
|
531
551
|
'!isActive': { background: 'surface', color: 'gray' }
|
|
532
552
|
```
|
|
533
553
|
|
|
534
|
-
|
|
554
|
+
---
|
|
535
555
|
|
|
536
556
|
## Design System Extraction
|
|
537
557
|
|
|
@@ -564,7 +584,7 @@ export default {
|
|
|
564
584
|
}
|
|
565
585
|
```
|
|
566
586
|
|
|
567
|
-
|
|
587
|
+
---
|
|
568
588
|
|
|
569
589
|
## DO’s and DON’Ts
|
|
570
590
|
|
|
@@ -594,7 +614,7 @@ export default {
|
|
|
594
614
|
- Hardcode pixel values — always use spacing tokens
|
|
595
615
|
- Use `import`/`require` for project-internal files
|
|
596
616
|
|
|
597
|
-
|
|
617
|
+
---
|
|
598
618
|
|
|
599
619
|
## Migration Workflow
|
|
600
620
|
|