@symbo.ls/mcp 1.0.11 → 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.
@@ -0,0 +1,598 @@
1
+ # Symbols Snippets — Production Component Patterns
2
+
3
+ 11 production-tested component patterns. Use as starting points for common UI needs.
4
+
5
+ ---
6
+
7
+ ## 1. Navigation Header
8
+
9
+ Sticky responsive header with logo, nav links, and mobile menu button.
10
+
11
+ ```js
12
+ export const Header = {
13
+ tag: 'header',
14
+ position: 'sticky',
15
+ top: '0',
16
+ width: '100%',
17
+ zIndex: '100',
18
+ padding: 'Z2 B',
19
+ align: 'center space-between',
20
+ theme: 'header',
21
+
22
+ Logo: {
23
+ extends: 'Link',
24
+ href: '/',
25
+ text: 'Brand',
26
+ fontWeight: '700',
27
+ fontSize: 'B',
28
+ textDecoration: 'none',
29
+ onClick: (e, el) => {
30
+ e.preventDefault()
31
+ el.router('/', el.getRoot())
32
+ },
33
+ },
34
+
35
+ Nav: {
36
+ gap: 'B',
37
+ align: 'center',
38
+
39
+ children: ['Home', 'About', 'Contact'],
40
+ childrenAs: 'state',
41
+ childExtends: 'Link',
42
+ childProps: {
43
+ text: '{{ value }}',
44
+ href: (el, s) => '/' + s.value.toLowerCase(),
45
+ textDecoration: 'none',
46
+ ':hover': { opacity: '.7' },
47
+ onClick: (e, el, s) => {
48
+ e.preventDefault()
49
+ el.router('/' + s.value.toLowerCase(), el.getRoot())
50
+ },
51
+ },
52
+
53
+ '@tabletS': { display: 'none' },
54
+ },
55
+
56
+ MenuButton: {
57
+ extends: 'IconButton',
58
+ icon: 'menu',
59
+ display: 'none',
60
+ '@tabletS': { display: 'flex' },
61
+ },
62
+ }
63
+ ```
64
+
65
+ ---
66
+
67
+ ## 2. Hero Section
68
+
69
+ Full-width hero with heading, description, and CTA buttons.
70
+
71
+ ```js
72
+ export const Hero = {
73
+ flow: 'y',
74
+ align: 'center center',
75
+ padding: 'F A',
76
+ gap: 'B',
77
+ textAlign: 'center',
78
+
79
+ Tag: {
80
+ tag: 'span',
81
+ text: 'NEW',
82
+ padding: 'X A',
83
+ round: 'Z',
84
+ theme: 'primary',
85
+ fontSize: 'Y',
86
+ fontWeight: '600',
87
+ letterSpacing: '1px',
88
+ },
89
+
90
+ H: {
91
+ tag: 'h1',
92
+ text: 'Build faster with Symbols',
93
+ maxWidth: 'H',
94
+ fontSize: 'E',
95
+ fontWeight: '800',
96
+ lineHeight: '1.1',
97
+ '@tabletS': { fontSize: 'D' },
98
+ '@mobileL': { fontSize: 'C2' },
99
+ },
100
+
101
+ P: {
102
+ text: 'Design system framework for building modern interfaces',
103
+ maxWidth: 'G',
104
+ fontSize: 'A2',
105
+ opacity: '.7',
106
+ lineHeight: '1.6',
107
+ },
108
+
109
+ Actions: {
110
+ gap: 'A',
111
+ align: 'center',
112
+ Button_Primary: {
113
+ text: 'Get Started',
114
+ theme: 'primary',
115
+ padding: 'Z2 B',
116
+ },
117
+ Button_Secondary: {
118
+ text: 'Learn More',
119
+ theme: 'secondary',
120
+ padding: 'Z2 B',
121
+ },
122
+ },
123
+ }
124
+ ```
125
+
126
+ ---
127
+
128
+ ## 3. Feature Card
129
+
130
+ Card with icon, title, and description.
131
+
132
+ ```js
133
+ export const FeatureCard = {
134
+ flow: 'y',
135
+ gap: 'A',
136
+ padding: 'B',
137
+ round: 'A',
138
+ theme: 'card',
139
+
140
+ Icon: {
141
+ name: 'star',
142
+ boxSize: 'B',
143
+ color: 'primary',
144
+ },
145
+
146
+ H: {
147
+ tag: 'h3',
148
+ text: 'Feature Title',
149
+ fontSize: 'A2',
150
+ fontWeight: '600',
151
+ },
152
+
153
+ P: {
154
+ text: 'Description of the feature goes here.',
155
+ opacity: '.7',
156
+ lineHeight: '1.5',
157
+ },
158
+ }
159
+ ```
160
+
161
+ ---
162
+
163
+ ## 4. Feature Grid
164
+
165
+ Responsive grid of feature cards using `children` + `childExtends`.
166
+
167
+ ```js
168
+ export const FeatureGrid = {
169
+ extends: 'Grid',
170
+ columns: 'repeat(3, 1fr)',
171
+ gap: 'B',
172
+ padding: 'D A',
173
+
174
+ '@tabletS': { columns: 'repeat(2, 1fr)' },
175
+ '@mobileL': { columns: '1fr' },
176
+
177
+ children: [
178
+ { icon: 'zap', title: 'Fast', description: 'Lightning fast rendering' },
179
+ { icon: 'shield', title: 'Secure', description: 'Built-in security features' },
180
+ { icon: 'code', title: 'Clean', description: 'No-import architecture' },
181
+ ],
182
+ childrenAs: 'state',
183
+ childExtends: 'FeatureCard',
184
+ childProps: {
185
+ Icon: { name: '{{ icon }}' },
186
+ H: { text: '{{ title }}' },
187
+ P: { text: '{{ description }}' },
188
+ },
189
+ }
190
+ ```
191
+
192
+ ---
193
+
194
+ ## 5. Pricing Card
195
+
196
+ Pricing option with features list and CTA.
197
+
198
+ ```js
199
+ export const PriceCard = {
200
+ flow: 'y',
201
+ padding: 'C',
202
+ round: 'A',
203
+ theme: 'card',
204
+ gap: 'B',
205
+ minWidth: 'F',
206
+
207
+ Header: {
208
+ flow: 'y',
209
+ gap: 'X2',
210
+ H: {
211
+ tag: 'h3',
212
+ text: 'Pro Plan',
213
+ fontSize: 'A2',
214
+ },
215
+ Price: {
216
+ align: 'end',
217
+ gap: 'X',
218
+ H2: {
219
+ tag: 'span',
220
+ text: '$29',
221
+ fontSize: 'D',
222
+ fontWeight: '800',
223
+ },
224
+ P: {
225
+ text: '/month',
226
+ opacity: '.5',
227
+ marginBottom: 'X2',
228
+ },
229
+ },
230
+ },
231
+
232
+ Features: {
233
+ flow: 'y',
234
+ gap: 'Z2',
235
+ children: [
236
+ 'Unlimited projects',
237
+ 'Priority support',
238
+ 'Custom themes',
239
+ 'Team collaboration',
240
+ ],
241
+ childrenAs: 'state',
242
+ childExtends: 'Flex',
243
+ childProps: {
244
+ align: 'center',
245
+ gap: 'Z',
246
+ Icon: { name: 'check', color: 'green', boxSize: 'Z2' },
247
+ Text: { text: '{{ value }}' },
248
+ },
249
+ },
250
+
251
+ Button: {
252
+ text: 'Get Started',
253
+ theme: 'primary',
254
+ width: '100%',
255
+ padding: 'Z2',
256
+ },
257
+ }
258
+ ```
259
+
260
+ ---
261
+
262
+ ## 6. Testimonial Card
263
+
264
+ User testimonial with avatar and quote.
265
+
266
+ ```js
267
+ export const TestimonialCard = {
268
+ flow: 'y',
269
+ gap: 'A2',
270
+ padding: 'B',
271
+ round: 'A',
272
+ theme: 'card',
273
+
274
+ Quote: {
275
+ tag: 'blockquote',
276
+ text: '"This framework changed how we build interfaces."',
277
+ fontSize: 'A1',
278
+ lineHeight: '1.6',
279
+ fontStyle: 'italic',
280
+ opacity: '.85',
281
+ },
282
+
283
+ Author: {
284
+ align: 'center',
285
+ gap: 'A',
286
+ Avatar: {
287
+ extends: 'Img',
288
+ round: '100%',
289
+ boxSize: 'B2 B2',
290
+ },
291
+ Info: {
292
+ flow: 'y',
293
+ gap: '0',
294
+ Name: { text: 'Jane Doe', fontWeight: '600', fontSize: 'Z2' },
295
+ Role: { text: 'CTO at Acme', opacity: '.6', fontSize: 'Y' },
296
+ },
297
+ },
298
+ }
299
+ ```
300
+
301
+ ---
302
+
303
+ ## 7. Search with Dropdown
304
+
305
+ Search input with filtered results dropdown.
306
+
307
+ ```js
308
+ export const SearchDropdown = {
309
+ state: {
310
+ query: '',
311
+ items: ['Dashboard', 'Settings', 'Profile', 'Analytics', 'Reports'],
312
+ },
313
+ position: 'relative',
314
+
315
+ Input: {
316
+ placeholder: 'Search...',
317
+ padding: 'Z2 A',
318
+ width: '100%',
319
+ onInput: (e, el, s) => s.update({ query: el.node.value }),
320
+ },
321
+
322
+ Results: {
323
+ if: (el, s) => s.query.length > 0,
324
+ position: 'absolute',
325
+ top: '100%',
326
+ left: '0',
327
+ width: '100%',
328
+ theme: 'dropdown',
329
+ round: '0 0 Z Z',
330
+ maxHeight: 'E',
331
+ overflow: 'auto',
332
+ zIndex: '10',
333
+
334
+ children: (el, s) =>
335
+ s.items.filter(i => i.toLowerCase().includes(s.query.toLowerCase())),
336
+ childrenAs: 'state',
337
+ childExtends: 'Box',
338
+ childProps: {
339
+ text: '{{ value }}',
340
+ padding: 'Z A',
341
+ cursor: 'pointer',
342
+ ':hover': { background: 'hover' },
343
+ onClick: (e, el, s) => {
344
+ s.root.update({ query: s.value })
345
+ },
346
+ },
347
+ },
348
+ }
349
+ ```
350
+
351
+ ---
352
+
353
+ ## 8. Footer
354
+
355
+ Responsive footer with column layout.
356
+
357
+ ```js
358
+ export const Footer = {
359
+ tag: 'footer',
360
+ flow: 'y',
361
+ gap: 'C',
362
+ padding: 'D B',
363
+ theme: 'footer',
364
+
365
+ Columns: {
366
+ extends: 'Grid',
367
+ columns: 'repeat(4, 1fr)',
368
+ gap: 'C',
369
+ '@tabletS': { columns: 'repeat(2, 1fr)' },
370
+ '@mobileL': { columns: '1fr' },
371
+
372
+ Column_Product: {
373
+ flow: 'y',
374
+ gap: 'Z2',
375
+ H: { tag: 'h4', text: 'Product', fontSize: 'Z2', fontWeight: '600' },
376
+ Link_1: { text: 'Features', href: '/features' },
377
+ Link_2: { text: 'Pricing', href: '/pricing' },
378
+ Link_3: { text: 'Docs', href: '/docs' },
379
+ },
380
+
381
+ Column_Company: {
382
+ flow: 'y',
383
+ gap: 'Z2',
384
+ H: { tag: 'h4', text: 'Company', fontSize: 'Z2', fontWeight: '600' },
385
+ Link_1: { text: 'About', href: '/about' },
386
+ Link_2: { text: 'Blog', href: '/blog' },
387
+ Link_3: { text: 'Careers', href: '/careers' },
388
+ },
389
+ },
390
+
391
+ Bottom: {
392
+ align: 'center space-between',
393
+ borderTop: '1px solid',
394
+ borderTopColor: 'gray3',
395
+ paddingTop: 'A',
396
+ P: { text: '© 2025 Brand. All rights reserved.', opacity: '.5', fontSize: 'Y' },
397
+ },
398
+ }
399
+ ```
400
+
401
+ ---
402
+
403
+ ## 9. Data Table
404
+
405
+ Structured data table from state array.
406
+
407
+ ```js
408
+ export const DataTable = {
409
+ state: {
410
+ rows: [
411
+ { name: 'Alice', role: 'Engineer', status: 'Active' },
412
+ { name: 'Bob', role: 'Designer', status: 'Away' },
413
+ { name: 'Carol', role: 'Manager', status: 'Active' },
414
+ ],
415
+ },
416
+
417
+ tag: 'table',
418
+ width: '100%',
419
+ borderCollapse: 'collapse',
420
+
421
+ Thead: {
422
+ tag: 'thead',
423
+ Tr: {
424
+ tag: 'tr',
425
+ Th_Name: { tag: 'th', text: 'Name', padding: 'Z A', textAlign: 'left' },
426
+ Th_Role: { tag: 'th', text: 'Role', padding: 'Z A', textAlign: 'left' },
427
+ Th_Status: { tag: 'th', text: 'Status', padding: 'Z A', textAlign: 'left' },
428
+ },
429
+ },
430
+
431
+ Tbody: {
432
+ tag: 'tbody',
433
+ children: (el, s) => s.rows,
434
+ childrenAs: 'state',
435
+ childExtends: {
436
+ tag: 'tr',
437
+ borderBottom: '1px solid',
438
+ borderBottomColor: 'gray2',
439
+ },
440
+ childProps: {
441
+ Td_Name: { tag: 'td', text: '{{ name }}', padding: 'Z A' },
442
+ Td_Role: { tag: 'td', text: '{{ role }}', padding: 'Z A' },
443
+ Td_Status: { tag: 'td', text: '{{ status }}', padding: 'Z A' },
444
+ },
445
+ },
446
+ }
447
+ ```
448
+
449
+ ---
450
+
451
+ ## 10. Layout with Sidebar
452
+
453
+ App layout with sidebar navigation and main content area.
454
+
455
+ ```js
456
+ export const AppLayout = {
457
+ flow: 'x',
458
+ width: '100%',
459
+ minHeight: '100vh',
460
+
461
+ Sidebar: {
462
+ flow: 'y',
463
+ width: 'F',
464
+ padding: 'A',
465
+ gap: 'X2',
466
+ theme: 'sidebar',
467
+ '@tabletS': { display: 'none' },
468
+
469
+ children: [
470
+ { label: 'Dashboard', icon: 'home', path: '/' },
471
+ { label: 'Projects', icon: 'folder', path: '/projects' },
472
+ { label: 'Settings', icon: 'settings', path: '/settings' },
473
+ ],
474
+ childrenAs: 'state',
475
+ childExtends: 'Flex',
476
+ childProps: {
477
+ align: 'center',
478
+ gap: 'Z',
479
+ padding: 'Z A',
480
+ round: 'Z',
481
+ cursor: 'pointer',
482
+ ':hover': { background: 'hover' },
483
+ Icon: { name: '{{ icon }}', boxSize: 'Z2' },
484
+ Text: { text: '{{ label }}' },
485
+ onClick: (e, el, s) => el.router(s.path, el.getRoot()),
486
+ },
487
+ },
488
+
489
+ Main: {
490
+ flow: 'y',
491
+ flex: '1',
492
+ padding: 'B',
493
+ overflow: 'auto',
494
+ },
495
+ }
496
+ ```
497
+
498
+ ---
499
+
500
+ ## 11. Notification Toast
501
+
502
+ Auto-dismissing fixed-position notification.
503
+
504
+ ```js
505
+ export const Toast = {
506
+ align: 'center',
507
+ gap: 'A',
508
+ padding: 'A B',
509
+ round: 'Z',
510
+ position: 'fixed',
511
+ bottom: 'B',
512
+ right: 'B',
513
+ zIndex: '1000',
514
+ theme: 'dialog',
515
+ transition: 'opacity 0.3s, transform 0.3s',
516
+
517
+ Icon: {
518
+ name: 'check',
519
+ boxSize: 'A',
520
+ },
521
+
522
+ Text: {
523
+ text: 'Operation successful',
524
+ fontSize: 'Z2',
525
+ },
526
+
527
+ CloseButton: {
528
+ extends: 'IconButton',
529
+ icon: 'x',
530
+ boxSize: 'A',
531
+ onClick: (e, el) => {
532
+ el.parent.node.style.opacity = '0'
533
+ setTimeout(() => el.parent.setProps({ if: false }), 300)
534
+ },
535
+ },
536
+ }
537
+ ```
538
+
539
+ ---
540
+
541
+ ## 12. Contact Form
542
+
543
+ Multi-field form with submit handling.
544
+
545
+ ```js
546
+ export const ContactForm = {
547
+ tag: 'form',
548
+ flow: 'y',
549
+ gap: 'A',
550
+ maxWidth: 'G',
551
+
552
+ state: {
553
+ name: '',
554
+ email: '',
555
+ message: '',
556
+ submitted: false,
557
+ },
558
+
559
+ Field_Name: {
560
+ extends: 'Field',
561
+ label: 'Name',
562
+ Input: {
563
+ placeholder: 'Your name',
564
+ onInput: (e, el, s) => s.update({ name: el.node.value }),
565
+ },
566
+ },
567
+
568
+ Field_Email: {
569
+ extends: 'Field',
570
+ label: 'Email',
571
+ Input: {
572
+ type: 'email',
573
+ placeholder: 'you@example.com',
574
+ onInput: (e, el, s) => s.update({ email: el.node.value }),
575
+ },
576
+ },
577
+
578
+ Field_Message: {
579
+ extends: 'Field',
580
+ label: 'Message',
581
+ Textarea: {
582
+ placeholder: 'Your message...',
583
+ rows: 4,
584
+ onInput: (e, el, s) => s.update({ message: el.node.value }),
585
+ },
586
+ },
587
+
588
+ Button: {
589
+ text: (el, s) => s.submitted ? 'Sent!' : 'Send Message',
590
+ theme: 'primary',
591
+ width: '100%',
592
+ onClick: async (e, el, s) => {
593
+ e.preventDefault()
594
+ s.update({ submitted: true })
595
+ },
596
+ },
597
+ }
598
+ ```
@@ -0,0 +1,99 @@
1
+ # Server-Side Rendering with Brender
2
+
3
+ Pre-render Symbols apps to static HTML using `@symbo.ls/brender`. Uses linkedom (virtual DOM) to run the same DOMQL component tree server-side, producing HTML with `data-br` hydration keys for client-side reconnection without re-rendering.
4
+
5
+ ---
6
+
7
+ ## Quick Start — CLI
8
+
9
+ ```bash
10
+ # Render all static routes
11
+ smbls brender
12
+
13
+ # Custom output directory
14
+ smbls brender --out-dir build
15
+
16
+ # Without prefetch or ISR client bundle
17
+ smbls brender --no-prefetch --no-isr
18
+
19
+ # Watch mode
20
+ smbls brender --watch
21
+ ```
22
+
23
+ Output goes to `dist-brender/` by default (configurable via `brenderDistDir` in `symbols.json`), separate from the SPA's `dist/` folder.
24
+
25
+ ---
26
+
27
+ ## Quick Start — Programmatic
28
+
29
+ ```js
30
+ import { renderPage, loadProject } from '@symbo.ls/brender'
31
+
32
+ const data = await loadProject('/path/to/project')
33
+ const result = await renderPage(data, '/about', { prefetch: true })
34
+
35
+ // result.html -> complete <!DOCTYPE html> page
36
+ // result.route -> '/about'
37
+ // result.brKeyCount -> number of hydration keys
38
+ ```
39
+
40
+ ---
41
+
42
+ ## How It Works
43
+
44
+ ### Render Phase (Server)
45
+
46
+ 1. Create virtual DOM with linkedom
47
+ 2. Run DOMQL `create()` against it — full component tree resolves
48
+ 3. Stamp `data-br="br-N"` on every element node (sequential, deterministic)
49
+ 4. Return HTML string, registry, and element tree
50
+
51
+ ### Hydrate Phase (Browser)
52
+
53
+ 1. Pre-rendered HTML already in DOM — instant page display
54
+ 2. DOMQL re-creates element tree from source definitions
55
+ 3. `hydrate()` matches `data-br` keys between DOMQL tree and real DOM
56
+ 4. Bidirectional links: `element.node = domNode` and `domNode.ref = element`
57
+ 5. Reactive updates, event handlers, and state changes work as if client-rendered
58
+
59
+ ---
60
+
61
+ ## Key APIs
62
+
63
+ | Function | Purpose |
64
+ |---|---|
65
+ | `renderElement(def, opts?)` | Render a single component to HTML |
66
+ | `render(data, opts?)` | Render a full project (routing, state, designSystem) |
67
+ | `renderPage(data, route, opts?)` | Complete HTML page with metadata, CSS, fonts |
68
+ | `prefetchPageData(data, route)` | SSR data prefetching via DB adapter |
69
+ | `hydrate(element, opts?)` | Client-side: reconnect DOMQL tree to DOM |
70
+ | `loadProject(path)` | Import a `symbols/` directory structure |
71
+ | `generateSitemap(data)` | Generate sitemap.xml from routes |
72
+
73
+ ---
74
+
75
+ ## Features
76
+
77
+ | Feature | Details |
78
+ |---|---|
79
+ | Metadata | Title, description, Open Graph, Twitter cards from declarative `metadata` objects |
80
+ | Emotion CSS | Full CSS extraction including emotion-generated rules, CSS variables, reset, font imports |
81
+ | Theme support | Generates `prefers-color-scheme` media queries and `[data-theme]` selectors (no JS needed) |
82
+ | Data prefetching | Executes declarative `fetch` definitions during SSR via DB adapter (Supabase) |
83
+ | ISR | Optional client bundle for hydration + SPA navigation after initial static load |
84
+ | Sitemap | Auto-generated `sitemap.xml` from route definitions |
85
+
86
+ ---
87
+
88
+ ## Configuration
89
+
90
+ In `symbols.json`:
91
+
92
+ ```json
93
+ {
94
+ "brender": true,
95
+ "brenderDistDir": "dist-brender"
96
+ }
97
+ ```
98
+
99
+ Param routes (e.g. `/blog/:id`) are automatically skipped during static generation — they require runtime data.