@invisibleloop/pulse 0.20.11 → 0.20.20
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 +6 -5
- package/public/.pulse-ui-version +1 -1
- package/scripts/build.js +26 -0
- package/scripts/release-version.js +17 -8
- package/scripts/strip-server.test.js +60 -0
- package/src/agent/checklist.md +8 -12
- package/src/agent/commands/pulse-start.md +2 -0
- package/src/agent/commands/verify.md +30 -8
- package/src/agent/guide-components.md +7 -25
- package/src/agent/guide-routing.md +0 -12
- package/src/agent/guide-server.md +102 -1
- package/src/agent/guide-spec.md +8 -1
- package/src/agent/guide-styles.md +23 -0
- package/src/agent/identity.md +9 -0
- package/src/agent/workflow.md +4 -23
- package/src/cli/check-bundles.js +113 -0
- package/src/cli/check-bundles.test.js +137 -0
- package/src/cli/cli.test.js +105 -0
- package/src/cli/dev.js +48 -3
- package/src/cli/diagnose.js +105 -0
- package/src/cli/diagnose.test.js +124 -0
- package/src/cli/discover.js +9 -2
- package/src/cli/index.js +171 -9
- package/src/cli/process-utils.js +57 -0
- package/src/cli/process-utils.test.js +114 -0
- package/src/cli/review.js +391 -0
- package/src/cli/review.test.js +143 -0
- package/src/cli/scaffold.js +9 -4
- package/src/cli/stamp.js +102 -0
- package/src/cli/stamp.test.js +149 -0
- package/src/cli/start.js +15 -2
- package/src/cli/validate.js +184 -0
- package/src/cli/validate.test.js +153 -0
- package/src/mcp/mcp.test.js +261 -30
- package/src/mcp/server.js +207 -515
- package/src/mcp/validate-worker.js +18 -1
- package/src/runtime/index.js +144 -11
- package/src/runtime/runtime.test.js +199 -0
- package/src/runtime/ssr.js +13 -6
- package/src/runtime/ssr.test.js +31 -0
- package/src/server/index.js +162 -9
- package/src/server/server.test.js +225 -2
- package/src/spec/schema.js +1 -1
- package/src/ui/index.js +0 -5
- package/src/ui/ui.test.js +0 -75
- package/types/ui.d.ts +0 -21
- package/.claude/commands/build-page.md +0 -63
- package/.claude/commands/new-doc-page.md +0 -45
- package/.claude/commands/pulse-start.md +0 -11
- package/.claude/commands/pulse-stop.md +0 -5
- package/.claude/commands/verify.md +0 -118
- package/.claude/pulse-checklist.md +0 -189
- package/.github/workflows/ci.yml +0 -25
- package/.github/workflows/publish.yml +0 -45
- package/.pulse/load-reports/home/1773432711417.json +0 -22
- package/.pulse-verified +0 -1
- package/AGENTS.md +0 -697
- package/CLAUDE.md +0 -697
- package/README.md +0 -95
- package/benchmark/public/dist/counter.boot-PO6SNSJZ.js +0 -8
- package/benchmark/public/dist/home.boot-YK6VAEQI.js +0 -6
- package/benchmark/public/dist/manifest.json +0 -5
- package/benchmark/public/dist/runtime-5Y7ESAIB.js +0 -2
- package/benchmark/public/dist/runtime-KO4BHUQ3.js +0 -49
- package/benchmark/results/2026-03-27-09-20.json +0 -188
- package/benchmark/results/2026-03-27-09-21.json +0 -188
- package/benchmark/results/2026-03-27-09-29.json +0 -188
- package/benchmark/results/2026-03-27-10-14.json +0 -188
- package/benchmark/results/2026-03-27-10-18.json +0 -232
- package/benchmark/src/pages/counter.js +0 -37
- package/benchmark/src/pages/home.js +0 -21
- package/docs/.claude/commands/pulse-build.md +0 -7
- package/docs/.claude/commands/pulse-dev.md +0 -7
- package/docs/.claude/commands/pulse-start.md +0 -9
- package/docs/.claude/commands/pulse-stop.md +0 -5
- package/docs/.claude/commands/verify.md +0 -84
- package/docs/.claude/coverage-check.js +0 -196
- package/docs/.claude/pulse-checklist.md +0 -189
- package/docs/public/.pulse-ui-version +0 -1
- package/docs/public/andypixel.css +0 -237
- package/docs/public/bookworms.css +0 -422
- package/docs/public/coutumes.css +0 -561
- package/docs/public/docs.css +0 -2388
- package/docs/public/favicon.svg +0 -4
- package/docs/public/home-brut.css +0 -982
- package/docs/public/images/bookworms/hero-books.jpg +0 -0
- package/docs/public/images/bookworms/how-we-read-now.jpg +0 -0
- package/docs/public/images/bookworms/lantern-keepers.jpg +0 -0
- package/docs/public/images/bookworms/provincial-bookseller.jpg +0 -0
- package/docs/public/images/journey/178.jpg +0 -0
- package/docs/public/images/journey/29.jpg +0 -0
- package/docs/public/images/retrowheels/hero.jpg +0 -0
- package/docs/public/images/shop/devbook-pro.jpg +0 -0
- package/docs/public/images/suzukibento/1015.jpg +0 -0
- package/docs/public/images/suzukibento/1025.jpg +0 -0
- package/docs/public/images/suzukibento/1043.jpg +0 -0
- package/docs/public/images/suzukibento/1059.jpg +0 -0
- package/docs/public/images/suzukibento/1062.jpg +0 -0
- package/docs/public/images/suzukibento/1084.jpg +0 -0
- package/docs/public/images/swish/1469334031218-e382a71b716b.jpg +0 -0
- package/docs/public/images/swish/1483985988355-763728e1935b.jpg +0 -0
- package/docs/public/images/swish/1485518882345-15568b007407.jpg +0 -0
- package/docs/public/images/swish/1490481651871-ab68de25d43d.jpg +0 -0
- package/docs/public/images/swish/1496747611176-843222e1e57c.jpg +0 -0
- package/docs/public/images/swish/1502716119720-b23a93e5fe1b.jpg +0 -0
- package/docs/public/images/swish/1539109136881-3be0616acf4b.jpg +0 -0
- package/docs/public/images/swish/1551803091-e20673f15770.jpg +0 -0
- package/docs/public/images/swish/1558769132-cb1aea458c5e.jpg +0 -0
- package/docs/public/images/swish/1572804013309-59a88b7e92f1.jpg +0 -0
- package/docs/public/img/heic1501a.jpg +0 -0
- package/docs/public/img/placeholder-landscape.svg +0 -18
- package/docs/public/img/placeholder-square.svg +0 -14
- package/docs/public/img/placeholder-tall.svg +0 -22
- package/docs/public/journey.css +0 -528
- package/docs/public/menu.js +0 -327
- package/docs/public/pulse-ui.css +0 -3028
- package/docs/public/pulse-ui.js +0 -257
- package/docs/public/retrowheels.css +0 -425
- package/docs/public/shop.css +0 -269
- package/docs/public/sparkys.css +0 -382
- package/docs/public/suzukibento.css +0 -517
- package/docs/public/swish.css +0 -394
- package/docs/public/theme.css +0 -52
- package/docs/public/themes/lumio.css +0 -50
- package/docs/public/themes/observatory.css +0 -104
- package/docs/server.js +0 -161
- package/docs/src/lib/component-page.js +0 -49
- package/docs/src/lib/highlight.js +0 -2
- package/docs/src/lib/layout.js +0 -190
- package/docs/src/lib/metrics-store.js +0 -6
- package/docs/src/lib/nav.js +0 -171
- package/docs/src/lib/stats.js +0 -81
- package/docs/src/pages/accessibility.js +0 -157
- package/docs/src/pages/actions.js +0 -194
- package/docs/src/pages/auth.js +0 -176
- package/docs/src/pages/caching.js +0 -95
- package/docs/src/pages/components/accordion.js +0 -48
- package/docs/src/pages/components/alert.js +0 -35
- package/docs/src/pages/components/app-badge.js +0 -41
- package/docs/src/pages/components/avatar.js +0 -35
- package/docs/src/pages/components/badge.js +0 -36
- package/docs/src/pages/components/breadcrumbs.js +0 -94
- package/docs/src/pages/components/button.js +0 -84
- package/docs/src/pages/components/card.js +0 -225
- package/docs/src/pages/components/charts.js +0 -283
- package/docs/src/pages/components/checkbox.js +0 -129
- package/docs/src/pages/components/cluster.js +0 -47
- package/docs/src/pages/components/container.js +0 -40
- package/docs/src/pages/components/cta.js +0 -54
- package/docs/src/pages/components/divider.js +0 -37
- package/docs/src/pages/components/empty.js +0 -36
- package/docs/src/pages/components/feature.js +0 -60
- package/docs/src/pages/components/fieldset.js +0 -65
- package/docs/src/pages/components/fileupload.js +0 -128
- package/docs/src/pages/components/footer.js +0 -58
- package/docs/src/pages/components/grid.js +0 -169
- package/docs/src/pages/components/heading.js +0 -107
- package/docs/src/pages/components/hero.js +0 -123
- package/docs/src/pages/components/icons.js +0 -410
- package/docs/src/pages/components/image.js +0 -71
- package/docs/src/pages/components/input.js +0 -51
- package/docs/src/pages/components/media.js +0 -51
- package/docs/src/pages/components/modal.js +0 -111
- package/docs/src/pages/components/nav.js +0 -136
- package/docs/src/pages/components/phone-frame.js +0 -74
- package/docs/src/pages/components/progress.js +0 -102
- package/docs/src/pages/components/prose.js +0 -111
- package/docs/src/pages/components/pullquote.js +0 -71
- package/docs/src/pages/components/radio.js +0 -194
- package/docs/src/pages/components/rating.js +0 -106
- package/docs/src/pages/components/search.js +0 -61
- package/docs/src/pages/components/section.js +0 -98
- package/docs/src/pages/components/segmented.js +0 -121
- package/docs/src/pages/components/select.js +0 -45
- package/docs/src/pages/components/slider.js +0 -115
- package/docs/src/pages/components/spinner.js +0 -73
- package/docs/src/pages/components/stack.js +0 -48
- package/docs/src/pages/components/stat.js +0 -55
- package/docs/src/pages/components/stepper.js +0 -66
- package/docs/src/pages/components/table.js +0 -45
- package/docs/src/pages/components/textarea.js +0 -31
- package/docs/src/pages/components/toggle.js +0 -84
- package/docs/src/pages/components/tooltip.js +0 -48
- package/docs/src/pages/components.js +0 -200
- package/docs/src/pages/config.js +0 -193
- package/docs/src/pages/constraints.js +0 -100
- package/docs/src/pages/deploy.js +0 -233
- package/docs/src/pages/error-pages.js +0 -99
- package/docs/src/pages/extending.js +0 -198
- package/docs/src/pages/faq.js +0 -99
- package/docs/src/pages/forms.js +0 -129
- package/docs/src/pages/getting-started.js +0 -147
- package/docs/src/pages/guard.js +0 -120
- package/docs/src/pages/home.js +0 -350
- package/docs/src/pages/how-it-works.js +0 -244
- package/docs/src/pages/hydration.js +0 -108
- package/docs/src/pages/images.js +0 -120
- package/docs/src/pages/markdown.js +0 -156
- package/docs/src/pages/meta.js +0 -193
- package/docs/src/pages/mutations.js +0 -107
- package/docs/src/pages/navigation.js +0 -85
- package/docs/src/pages/performance.js +0 -157
- package/docs/src/pages/persist.js +0 -89
- package/docs/src/pages/project-structure.js +0 -91
- package/docs/src/pages/prompt-examples.js +0 -186
- package/docs/src/pages/raw-responses.js +0 -122
- package/docs/src/pages/routing.js +0 -120
- package/docs/src/pages/server-api.js +0 -291
- package/docs/src/pages/server-data.js +0 -203
- package/docs/src/pages/slash-commands.js +0 -55
- package/docs/src/pages/spec.js +0 -207
- package/docs/src/pages/state.js +0 -102
- package/docs/src/pages/store.js +0 -203
- package/docs/src/pages/streaming.js +0 -107
- package/docs/src/pages/stripe.js +0 -194
- package/docs/src/pages/supabase.js +0 -324
- package/docs/src/pages/templates/andypixel.js +0 -260
- package/docs/src/pages/templates/blog-post.js +0 -226
- package/docs/src/pages/templates/bookworms.js +0 -227
- package/docs/src/pages/templates/coutumes.js +0 -237
- package/docs/src/pages/templates/journey.js +0 -186
- package/docs/src/pages/templates/mobile-app.js +0 -371
- package/docs/src/pages/templates/retrowheels.js +0 -355
- package/docs/src/pages/templates/shop.js +0 -268
- package/docs/src/pages/templates/sparkys.js +0 -264
- package/docs/src/pages/templates/suzukibento.js +0 -200
- package/docs/src/pages/templates/swish.js +0 -313
- package/docs/src/pages/templates.js +0 -534
- package/docs/src/pages/testing.js +0 -199
- package/docs/src/pages/validation.js +0 -138
- package/examples/.claude/commands/pulse-build.md +0 -7
- package/examples/.claude/commands/pulse-dev.md +0 -7
- package/examples/.claude/commands/pulse-start.md +0 -9
- package/examples/.claude/commands/pulse-stop.md +0 -5
- package/examples/.claude/commands/verify.md +0 -84
- package/examples/.claude/coverage-check.js +0 -196
- package/examples/.claude/pulse-checklist.md +0 -189
- package/examples/andypixel.css +0 -237
- package/examples/andypixel.js +0 -258
- package/examples/bookworms.css +0 -422
- package/examples/bookworms.js +0 -225
- package/examples/contact.js +0 -166
- package/examples/counter.js +0 -94
- package/examples/dev.server.js +0 -110
- package/examples/examples.test.js +0 -394
- package/examples/journey.css +0 -528
- package/examples/journey.js +0 -186
- package/examples/pricing.js +0 -262
- package/examples/products.js +0 -190
- package/examples/public/.pulse-ui-version +0 -1
- package/examples/public/pulse-ui.css +0 -3028
- package/examples/public/pulse-ui.js +0 -257
- package/examples/quiz.js +0 -208
- package/examples/retrowheels.css +0 -425
- package/examples/retrowheels.js +0 -353
- package/examples/shared.js +0 -84
- package/examples/suzukibento.css +0 -513
- package/examples/suzukibento.js +0 -200
- package/examples/swish.css +0 -394
- package/examples/swish.js +0 -311
- package/examples/todos.js +0 -162
- package/src/ui/fieldset.js +0 -37
- package/src/ui/phone-frame.js +0 -40
- package/src/ui/search.js +0 -78
- package/src/ui/segmented.js +0 -50
- package/src/ui/tooltip.js +0 -28
|
@@ -1,225 +0,0 @@
|
|
|
1
|
-
import { renderComponentPage, demo } from '../../lib/component-page.js'
|
|
2
|
-
import { prevNext } from '../../lib/nav.js'
|
|
3
|
-
import { table } from '../../lib/layout.js'
|
|
4
|
-
import { button, card, badge, grid } from '../../../../src/ui/index.js'
|
|
5
|
-
|
|
6
|
-
const { prev, next } = prevNext('/components/card')
|
|
7
|
-
|
|
8
|
-
export default {
|
|
9
|
-
route: '/components/card',
|
|
10
|
-
meta: {
|
|
11
|
-
theme: 'light',
|
|
12
|
-
title: 'Card — Pulse Docs',
|
|
13
|
-
description: 'Card component for Pulse UI.',
|
|
14
|
-
styles: ['/pulse-ui.css', '/theme.css', '/docs.css'],
|
|
15
|
-
},
|
|
16
|
-
view: () => renderComponentPage({
|
|
17
|
-
currentHref: '/components/card',
|
|
18
|
-
prev,
|
|
19
|
-
next,
|
|
20
|
-
name: 'card',
|
|
21
|
-
description: '<code>content</code> and <code>footer</code> are raw HTML slots — they pass through as-is. Wrap user-supplied values in <code>escHtml()</code> before composing them into the string.',
|
|
22
|
-
content: `
|
|
23
|
-
${demo(
|
|
24
|
-
card({
|
|
25
|
-
title: 'Recent activity',
|
|
26
|
-
content: '<p class="u-text-muted u-text-sm" style="margin:0">No activity in the last 7 days.</p>',
|
|
27
|
-
footer: button({ label: 'View all', href: '#', variant: 'ghost', size: 'sm' }),
|
|
28
|
-
}),
|
|
29
|
-
`card({
|
|
30
|
-
title: 'Recent activity',
|
|
31
|
-
content: \`<p class="u-text-muted u-text-sm">No activity in the last 7 days.</p>\`,
|
|
32
|
-
footer: button({ label: 'View all', href: '/activity', variant: 'ghost', size: 'sm' }),
|
|
33
|
-
})`
|
|
34
|
-
)}
|
|
35
|
-
|
|
36
|
-
<h2 class="doc-h2" id="image">With image</h2>
|
|
37
|
-
<p>Use <code>flush</code> to remove body padding, then add an image at the top and restore padding on the text content below it.</p>
|
|
38
|
-
|
|
39
|
-
${demo(
|
|
40
|
-
card({
|
|
41
|
-
flush: true,
|
|
42
|
-
content: `
|
|
43
|
-
<div class="u-overflow-hidden" style="height:180px;border-radius:var(--ui-radius) var(--ui-radius) 0 0">
|
|
44
|
-
<img src="/img/heic1501a.jpg" alt="The Pillars of Creation in the Eagle Nebula, captured by the Hubble Space Telescope" style="width:100%;height:100%;object-fit:cover;display:block">
|
|
45
|
-
</div>
|
|
46
|
-
<div class="u-p-5">
|
|
47
|
-
<p class="u-font-semibold u-mb-2" style="margin-top:0">Deep field</p>
|
|
48
|
-
<p class="u-text-muted u-text-sm" style="margin:0">The Pillars of Creation, imaged by the Hubble Space Telescope.</p>
|
|
49
|
-
</div>
|
|
50
|
-
`,
|
|
51
|
-
footer: button({ label: 'View gallery', href: '#', variant: 'primary', size: 'sm' }),
|
|
52
|
-
}),
|
|
53
|
-
`card({
|
|
54
|
-
flush: true,
|
|
55
|
-
content: \`
|
|
56
|
-
<div class="u-overflow-hidden" style="height:180px;border-radius:var(--ui-radius) var(--ui-radius) 0 0">
|
|
57
|
-
<img src="/img/nebula.jpg" alt="The Pillars of Creation in the Eagle Nebula" style="width:100%;height:100%;object-fit:cover">
|
|
58
|
-
</div>
|
|
59
|
-
<div class="u-p-5">
|
|
60
|
-
<p class="u-font-semibold u-mb-2">Deep field</p>
|
|
61
|
-
<p class="u-text-muted u-text-sm">The Pillars of Creation, imaged by the Hubble Space Telescope.</p>
|
|
62
|
-
</div>
|
|
63
|
-
\`,
|
|
64
|
-
footer: button({ label: 'View gallery', href: '/gallery', variant: 'primary', size: 'sm' }),
|
|
65
|
-
})`
|
|
66
|
-
)}
|
|
67
|
-
|
|
68
|
-
<h2 class="doc-h2" id="badges">With badges</h2>
|
|
69
|
-
<p>Compose <code>badge()</code> inside <code>content</code> to show status labels or category tags.</p>
|
|
70
|
-
|
|
71
|
-
${demo(
|
|
72
|
-
card({
|
|
73
|
-
title: 'API rate limits',
|
|
74
|
-
content: `
|
|
75
|
-
<div class="u-flex u-gap-2 u-mb-4">
|
|
76
|
-
${badge({ label: 'Production', variant: 'success' })}
|
|
77
|
-
${badge({ label: 'v2.1', variant: 'info' })}
|
|
78
|
-
</div>
|
|
79
|
-
<p class="u-text-muted u-text-sm" style="margin:0">Requests are capped at 1,000/min per API key. Contact support to increase your limit.</p>
|
|
80
|
-
`,
|
|
81
|
-
footer: `
|
|
82
|
-
<div class="u-flex u-gap-2">
|
|
83
|
-
${button({ label: 'View docs', href: '#', variant: 'ghost', size: 'sm' })}
|
|
84
|
-
${button({ label: 'Request increase', href: '#', variant: 'secondary', size: 'sm' })}
|
|
85
|
-
</div>
|
|
86
|
-
`,
|
|
87
|
-
}),
|
|
88
|
-
`card({
|
|
89
|
-
title: 'API rate limits',
|
|
90
|
-
content: \`
|
|
91
|
-
<div class="u-flex u-gap-2 u-mb-4">
|
|
92
|
-
\${badge({ label: 'Production', variant: 'success' })}
|
|
93
|
-
\${badge({ label: 'v2.1', variant: 'info' })}
|
|
94
|
-
</div>
|
|
95
|
-
<p class="u-text-muted u-text-sm">Requests are capped at 1,000/min per API key.</p>
|
|
96
|
-
\`,
|
|
97
|
-
footer: \`
|
|
98
|
-
\${button({ label: 'View docs', href: '/docs', variant: 'ghost', size: 'sm' })}
|
|
99
|
-
\${button({ label: 'Request increase', href: '/contact', variant: 'secondary', size: 'sm' })}
|
|
100
|
-
\`,
|
|
101
|
-
})`
|
|
102
|
-
)}
|
|
103
|
-
|
|
104
|
-
<h2 class="doc-h2" id="metadata">With metadata row</h2>
|
|
105
|
-
<p>A flex row inside <code>content</code> works well for author, date, read-time and similar metadata.</p>
|
|
106
|
-
|
|
107
|
-
${demo(
|
|
108
|
-
card({
|
|
109
|
-
title: 'Building with Pulse',
|
|
110
|
-
content: `
|
|
111
|
-
<p class="u-text-muted u-text-sm u-mb-4">Learn how to scaffold a new project and ship your first spec-driven page in under ten minutes.</p>
|
|
112
|
-
<div class="u-flex u-gap-3" style="align-items:center;flex-wrap:wrap">
|
|
113
|
-
<div style="width:28px;height:28px;border-radius:50%;background:linear-gradient(135deg,#667eea,#764ba2);flex-shrink:0"></div>
|
|
114
|
-
<span class="u-text-sm u-text-muted">Jane Smith</span>
|
|
115
|
-
<span class="u-text-sm u-text-muted">·</span>
|
|
116
|
-
<span class="u-text-sm u-text-muted">Mar 15, 2024</span>
|
|
117
|
-
<span class="u-text-sm u-text-muted u-ml-auto">5 min read</span>
|
|
118
|
-
</div>
|
|
119
|
-
`,
|
|
120
|
-
footer: button({ label: 'Read article →', href: '#', variant: 'ghost', size: 'sm' }),
|
|
121
|
-
}),
|
|
122
|
-
`card({
|
|
123
|
-
title: 'Building with Pulse',
|
|
124
|
-
content: \`
|
|
125
|
-
<p class="u-text-muted u-text-sm u-mb-4">Learn how to scaffold a new project...</p>
|
|
126
|
-
<div class="u-flex u-gap-3" style="align-items:center;flex-wrap:wrap">
|
|
127
|
-
<img src="/img/avatar.jpg" class="u-rounded-full" style="width:28px;height:28px" alt="">
|
|
128
|
-
<span class="u-text-sm u-text-muted">Jane Smith</span>
|
|
129
|
-
<span class="u-text-sm u-text-muted">·</span>
|
|
130
|
-
<span class="u-text-sm u-text-muted">Mar 15, 2024</span>
|
|
131
|
-
<span class="u-text-sm u-text-muted u-ml-auto">5 min read</span>
|
|
132
|
-
</div>
|
|
133
|
-
\`,
|
|
134
|
-
footer: button({ label: 'Read article →', href: '/blog/pulse', variant: 'ghost', size: 'sm' }),
|
|
135
|
-
})`
|
|
136
|
-
)}
|
|
137
|
-
|
|
138
|
-
<h2 class="doc-h2" id="stat">Stat / metric card</h2>
|
|
139
|
-
<p>Cards without a title work well as simple metric tiles. Use <code>grid()</code> to lay multiple cards out side by side.</p>
|
|
140
|
-
|
|
141
|
-
${demo(
|
|
142
|
-
grid({
|
|
143
|
-
cols: 3,
|
|
144
|
-
gap: 'sm',
|
|
145
|
-
content: [
|
|
146
|
-
card({ content: `<p class="u-text-muted u-text-sm u-mb-1" style="margin-top:0">Total revenue</p><p class="u-text-3xl u-font-bold" style="margin:0">£48,295</p><p class="u-text-sm u-text-green u-mb-0" style="margin-top:.25rem">↑ 12% this month</p>` }),
|
|
147
|
-
card({ content: `<p class="u-text-muted u-text-sm u-mb-1" style="margin-top:0">Active users</p><p class="u-text-3xl u-font-bold" style="margin:0">3,842</p><p class="u-text-sm u-text-muted u-mb-0" style="margin-top:.25rem">Across all plans</p>` }),
|
|
148
|
-
card({ content: `<p class="u-text-muted u-text-sm u-mb-1" style="margin-top:0">Churn rate</p><p class="u-text-3xl u-font-bold" style="margin:0">1.4%</p><p class="u-text-sm u-text-red u-mb-0" style="margin-top:.25rem">↑ 0.2% vs last month</p>` }),
|
|
149
|
-
].join(''),
|
|
150
|
-
}),
|
|
151
|
-
`grid({
|
|
152
|
-
cols: 3,
|
|
153
|
-
gap: 'sm',
|
|
154
|
-
content: [
|
|
155
|
-
card({
|
|
156
|
-
content: \`
|
|
157
|
-
<p class="u-text-muted u-text-sm u-mb-1">Total revenue</p>
|
|
158
|
-
<p class="u-text-3xl u-font-bold">£48,295</p>
|
|
159
|
-
<p class="u-text-sm u-text-green">↑ 12% this month</p>
|
|
160
|
-
\`,
|
|
161
|
-
}),
|
|
162
|
-
card({ ... }),
|
|
163
|
-
card({ ... }),
|
|
164
|
-
].join(''),
|
|
165
|
-
})`
|
|
166
|
-
)}
|
|
167
|
-
|
|
168
|
-
<h2 class="doc-h2" id="grid-inside">Grid inside a card</h2>
|
|
169
|
-
<p>Components compose freely — pass a <code>grid()</code> into a card's <code>content</code> slot for structured layouts inside a surface.</p>
|
|
170
|
-
|
|
171
|
-
${demo(
|
|
172
|
-
card({
|
|
173
|
-
title: 'Plan comparison',
|
|
174
|
-
content: grid({
|
|
175
|
-
cols: 3,
|
|
176
|
-
gap: 'sm',
|
|
177
|
-
content: [
|
|
178
|
-
`<div class="u-text-center u-p-2">
|
|
179
|
-
<p class="u-font-semibold u-mb-1" style="margin-top:0">Starter</p>
|
|
180
|
-
<p class="u-text-2xl u-font-bold u-mb-1" style="margin:0">£0</p>
|
|
181
|
-
<p class="u-text-muted u-text-sm" style="margin:0">Up to 3 projects</p>
|
|
182
|
-
</div>`,
|
|
183
|
-
`<div class="u-text-center u-p-2" style="border-left:1px solid var(--ui-border);border-right:1px solid var(--ui-border)">
|
|
184
|
-
<p class="u-font-semibold u-mb-1" style="margin-top:0;color:var(--ui-accent)">Pro</p>
|
|
185
|
-
<p class="u-text-2xl u-font-bold u-mb-1" style="margin:0">£12</p>
|
|
186
|
-
<p class="u-text-muted u-text-sm" style="margin:0">Unlimited projects</p>
|
|
187
|
-
</div>`,
|
|
188
|
-
`<div class="u-text-center u-p-2">
|
|
189
|
-
<p class="u-font-semibold u-mb-1" style="margin-top:0">Enterprise</p>
|
|
190
|
-
<p class="u-text-2xl u-font-bold u-mb-1" style="margin:0">Custom</p>
|
|
191
|
-
<p class="u-text-muted u-text-sm" style="margin:0">SLA + support</p>
|
|
192
|
-
</div>`,
|
|
193
|
-
].join(''),
|
|
194
|
-
}),
|
|
195
|
-
footer: button({ label: 'View full pricing', href: '#', variant: 'ghost', size: 'sm' }),
|
|
196
|
-
}),
|
|
197
|
-
`card({
|
|
198
|
-
title: 'Plan comparison',
|
|
199
|
-
content: grid({
|
|
200
|
-
cols: 3,
|
|
201
|
-
gap: 'sm',
|
|
202
|
-
content: [
|
|
203
|
-
\`<div class="u-text-center u-p-2">...</div>\`,
|
|
204
|
-
\`<div class="u-text-center u-p-2">...</div>\`,
|
|
205
|
-
\`<div class="u-text-center u-p-2">...</div>\`,
|
|
206
|
-
].join(''),
|
|
207
|
-
}),
|
|
208
|
-
footer: button({ label: 'View full pricing', href: '/pricing', variant: 'ghost', size: 'sm' }),
|
|
209
|
-
})`
|
|
210
|
-
)}
|
|
211
|
-
|
|
212
|
-
${table(
|
|
213
|
-
['Prop', 'Type', 'Default', 'Description'],
|
|
214
|
-
[
|
|
215
|
-
['<code>title</code>', 'string', '—', 'Escaped automatically'],
|
|
216
|
-
['<code>level</code>', 'number', '3', 'Heading tag for the title (1–6). Visual style is unchanged — use this to keep the document outline correct when the surrounding context already has an h2 or h3.'],
|
|
217
|
-
['<code>content</code>', 'string', '—', 'HTML string — not escaped'],
|
|
218
|
-
['<code>footer</code>', 'string', '—', 'HTML string — not escaped'],
|
|
219
|
-
['<code>flush</code>', 'boolean', 'false', 'Removes body padding — useful for full-bleed images or tables'],
|
|
220
|
-
['<code>class</code>', 'string', '—', ''],
|
|
221
|
-
]
|
|
222
|
-
)}
|
|
223
|
-
`,
|
|
224
|
-
}),
|
|
225
|
-
}
|
|
@@ -1,283 +0,0 @@
|
|
|
1
|
-
import { renderComponentPage, demo } from '../../lib/component-page.js'
|
|
2
|
-
import { prevNext } from '../../lib/nav.js'
|
|
3
|
-
import { table } from '../../lib/layout.js'
|
|
4
|
-
import { stat, card, grid } from '../../../../src/ui/index.js'
|
|
5
|
-
import { barChart, lineChart, donutChart, sparkline } from '../../../../src/ui/charts.js'
|
|
6
|
-
|
|
7
|
-
const { prev, next } = prevNext('/components/charts')
|
|
8
|
-
|
|
9
|
-
// Shared sample data
|
|
10
|
-
const monthly = [
|
|
11
|
-
{ label: 'Jan', value: 42 },
|
|
12
|
-
{ label: 'Feb', value: 78 },
|
|
13
|
-
{ label: 'Mar', value: 55 },
|
|
14
|
-
{ label: 'Apr', value: 91 },
|
|
15
|
-
{ label: 'May', value: 63 },
|
|
16
|
-
{ label: 'Jun', value: 84 },
|
|
17
|
-
]
|
|
18
|
-
|
|
19
|
-
const traffic = [
|
|
20
|
-
{ label: 'Mon', value: 1200 },
|
|
21
|
-
{ label: 'Tue', value: 1850 },
|
|
22
|
-
{ label: 'Wed', value: 1540 },
|
|
23
|
-
{ label: 'Thu', value: 2100 },
|
|
24
|
-
{ label: 'Fri', value: 1760 },
|
|
25
|
-
{ label: 'Sat', value: 890 },
|
|
26
|
-
{ label: 'Sun', value: 720 },
|
|
27
|
-
]
|
|
28
|
-
|
|
29
|
-
export default {
|
|
30
|
-
route: '/components/charts',
|
|
31
|
-
meta: {
|
|
32
|
-
theme: 'light',
|
|
33
|
-
title: 'Charts — Pulse Docs',
|
|
34
|
-
description: 'Server-rendered SVG charts for Pulse UI.',
|
|
35
|
-
styles: ['/pulse-ui.css', '/theme.css', '/docs.css'],
|
|
36
|
-
},
|
|
37
|
-
view: () => renderComponentPage({
|
|
38
|
-
currentHref: '/components/charts',
|
|
39
|
-
prev,
|
|
40
|
-
next,
|
|
41
|
-
name: 'charts',
|
|
42
|
-
description: 'Server-rendered SVG charts — no JavaScript, no external library. Pure functions that return SVG strings, composable with any layout component. All colours use design tokens and respond to light/dark theme.',
|
|
43
|
-
content: `
|
|
44
|
-
|
|
45
|
-
<h2 class="doc-h2" id="bar">Bar chart</h2>
|
|
46
|
-
<p>Vertical bars with optional grid, value labels, and a zero baseline. All colour variants available.</p>
|
|
47
|
-
${demo(
|
|
48
|
-
barChart({ data: monthly, color: 'accent' }),
|
|
49
|
-
`barChart({
|
|
50
|
-
data: [
|
|
51
|
-
{ label: 'Jan', value: 42 },
|
|
52
|
-
{ label: 'Feb', value: 78 },
|
|
53
|
-
{ label: 'Mar', value: 55 },
|
|
54
|
-
{ label: 'Apr', value: 91 },
|
|
55
|
-
{ label: 'May', value: 63 },
|
|
56
|
-
{ label: 'Jun', value: 84 },
|
|
57
|
-
],
|
|
58
|
-
color: 'accent',
|
|
59
|
-
})`
|
|
60
|
-
)}
|
|
61
|
-
|
|
62
|
-
<h3 class="doc-h3">With value labels</h3>
|
|
63
|
-
${demo(
|
|
64
|
-
barChart({ data: monthly, color: 'success', showValues: true }),
|
|
65
|
-
`barChart({ data, color: 'success', showValues: true })`
|
|
66
|
-
)}
|
|
67
|
-
|
|
68
|
-
<h3 class="doc-h3">Large dataset with tight gap</h3>
|
|
69
|
-
${demo(
|
|
70
|
-
barChart({ data: traffic, color: 'blue', gap: 0.15 }),
|
|
71
|
-
`barChart({ data: traffic, color: 'blue', gap: 0.15 })`
|
|
72
|
-
)}
|
|
73
|
-
|
|
74
|
-
<h3 class="doc-h3">Negative values</h3>
|
|
75
|
-
${demo(
|
|
76
|
-
barChart({
|
|
77
|
-
data: [
|
|
78
|
-
{ label: 'Q1', value: 24 },
|
|
79
|
-
{ label: 'Q2', value: -8 },
|
|
80
|
-
{ label: 'Q3', value: 41 },
|
|
81
|
-
{ label: 'Q4', value: -15 },
|
|
82
|
-
],
|
|
83
|
-
color: 'warning',
|
|
84
|
-
showValues: true,
|
|
85
|
-
}),
|
|
86
|
-
`barChart({
|
|
87
|
-
data: [
|
|
88
|
-
{ label: 'Q1', value: 24 },
|
|
89
|
-
{ label: 'Q2', value: -8 },
|
|
90
|
-
{ label: 'Q3', value: 41 },
|
|
91
|
-
{ label: 'Q4', value: -15 },
|
|
92
|
-
],
|
|
93
|
-
color: 'warning',
|
|
94
|
-
showValues: true,
|
|
95
|
-
})`
|
|
96
|
-
)}
|
|
97
|
-
|
|
98
|
-
<h2 class="doc-h2" id="line">Line chart</h2>
|
|
99
|
-
<p>Connected data points with optional dots, area fill, and grid lines.</p>
|
|
100
|
-
${demo(
|
|
101
|
-
lineChart({ data: monthly, color: 'accent' }),
|
|
102
|
-
`lineChart({
|
|
103
|
-
data: [
|
|
104
|
-
{ label: 'Jan', value: 42 },
|
|
105
|
-
...
|
|
106
|
-
],
|
|
107
|
-
color: 'accent',
|
|
108
|
-
})`
|
|
109
|
-
)}
|
|
110
|
-
|
|
111
|
-
<h3 class="doc-h3">With area fill</h3>
|
|
112
|
-
${demo(
|
|
113
|
-
lineChart({ data: monthly, color: 'accent', area: true }),
|
|
114
|
-
`lineChart({ data, color: 'accent', area: true })`
|
|
115
|
-
)}
|
|
116
|
-
|
|
117
|
-
<h3 class="doc-h3">No dots, area fill, success colour</h3>
|
|
118
|
-
${demo(
|
|
119
|
-
lineChart({ data: traffic, color: 'success', area: true, showDots: false }),
|
|
120
|
-
`lineChart({ data, color: 'success', area: true, showDots: false })`
|
|
121
|
-
)}
|
|
122
|
-
|
|
123
|
-
<h2 class="doc-h2" id="donut">Donut chart</h2>
|
|
124
|
-
<p>Ring chart with multiple segments. Each item can override its colour. Pass <code>label</code> and <code>sublabel</code> for a centred annotation. Add <code>fluid: true</code> to expand to the parent container width.</p>
|
|
125
|
-
${demo(
|
|
126
|
-
`<div style="display:flex;justify-content:center">` +
|
|
127
|
-
donutChart({
|
|
128
|
-
label: '73%',
|
|
129
|
-
sublabel: 'satisfied',
|
|
130
|
-
data: [
|
|
131
|
-
{ label: 'Satisfied', value: 73, color: 'success' },
|
|
132
|
-
{ label: 'Neutral', value: 18, color: 'muted' },
|
|
133
|
-
{ label: 'Unsatisfied', value: 9, color: 'error' },
|
|
134
|
-
],
|
|
135
|
-
}) + `</div>`,
|
|
136
|
-
`donutChart({
|
|
137
|
-
label: '73%',
|
|
138
|
-
sublabel: 'satisfied',
|
|
139
|
-
data: [
|
|
140
|
-
{ label: 'Satisfied', value: 73, color: 'success' },
|
|
141
|
-
{ label: 'Neutral', value: 18, color: 'muted' },
|
|
142
|
-
{ label: 'Unsatisfied', value: 9, color: 'error' },
|
|
143
|
-
],
|
|
144
|
-
})`
|
|
145
|
-
)}
|
|
146
|
-
|
|
147
|
-
<h3 class="doc-h3">Thinner ring</h3>
|
|
148
|
-
${demo(
|
|
149
|
-
`<div style="display:flex;justify-content:center">` +
|
|
150
|
-
donutChart({
|
|
151
|
-
size: 180,
|
|
152
|
-
thickness: 22,
|
|
153
|
-
label: '4',
|
|
154
|
-
sublabel: 'segments',
|
|
155
|
-
data: [
|
|
156
|
-
{ label: 'A', value: 40, color: 'accent' },
|
|
157
|
-
{ label: 'B', value: 30, color: 'blue' },
|
|
158
|
-
{ label: 'C', value: 20, color: 'success' },
|
|
159
|
-
{ label: 'D', value: 10, color: 'warning' },
|
|
160
|
-
],
|
|
161
|
-
}) + `</div>`,
|
|
162
|
-
`donutChart({
|
|
163
|
-
size: 180, thickness: 22,
|
|
164
|
-
label: '4', sublabel: 'segments',
|
|
165
|
-
data: [
|
|
166
|
-
{ label: 'A', value: 40, color: 'accent' },
|
|
167
|
-
{ label: 'B', value: 30, color: 'blue' },
|
|
168
|
-
{ label: 'C', value: 20, color: 'success' },
|
|
169
|
-
{ label: 'D', value: 10, color: 'warning' },
|
|
170
|
-
],
|
|
171
|
-
})`
|
|
172
|
-
)}
|
|
173
|
-
|
|
174
|
-
<h2 class="doc-h2" id="sparkline">Sparkline</h2>
|
|
175
|
-
<p>Minimal inline trend line — pass a plain array of numbers. Designed to sit alongside <code>stat()</code> tiles or inside table cells. Add <code>fluid: true</code> to expand to the parent container width.</p>
|
|
176
|
-
${demo(
|
|
177
|
-
`<div style="display:flex;gap:.75rem;align-items:center;flex-wrap:wrap">` +
|
|
178
|
-
sparkline({ data: [12,18,14,22,19,28,24,31], color: 'accent', area: true }) +
|
|
179
|
-
sparkline({ data: [31,24,28,19,22,14,18,12], color: 'error', area: true }) +
|
|
180
|
-
sparkline({ data: [12,18,14,22,19,28,24,31], color: 'success', area: false }) +
|
|
181
|
-
`</div>`,
|
|
182
|
-
`sparkline({ data: [12,18,14,22,19,28,24,31], color: 'accent', area: true })
|
|
183
|
-
sparkline({ data: [31,24,28,19,22,14,18,12], color: 'error', area: true })`
|
|
184
|
-
)}
|
|
185
|
-
|
|
186
|
-
<h2 class="doc-h2" id="composition">Composition</h2>
|
|
187
|
-
<p>Charts compose with <code>card()</code>, <code>stat()</code>, <code>grid()</code> — drop any chart into any content slot.</p>
|
|
188
|
-
${demo(
|
|
189
|
-
grid({
|
|
190
|
-
cols: 2,
|
|
191
|
-
gap: 'md',
|
|
192
|
-
content:
|
|
193
|
-
card({
|
|
194
|
-
title: 'Monthly signups',
|
|
195
|
-
content: barChart({ data: monthly, color: 'accent', height: 180 }),
|
|
196
|
-
}) +
|
|
197
|
-
card({
|
|
198
|
-
title: 'Daily traffic',
|
|
199
|
-
content: lineChart({ data: traffic, color: 'blue', area: true, height: 180 }),
|
|
200
|
-
}),
|
|
201
|
-
}),
|
|
202
|
-
`grid({
|
|
203
|
-
cols: 2,
|
|
204
|
-
content:
|
|
205
|
-
card({ title: 'Monthly signups', content: barChart({ data, height: 180 }) }) +
|
|
206
|
-
card({ title: 'Daily traffic', content: lineChart({ data, color: 'blue', area: true, height: 180 }) }),
|
|
207
|
-
})`
|
|
208
|
-
)}
|
|
209
|
-
|
|
210
|
-
<h3 class="doc-h3">Sparkline in stat tiles</h3>
|
|
211
|
-
${demo(
|
|
212
|
-
grid({
|
|
213
|
-
cols: 3,
|
|
214
|
-
gap: 'md',
|
|
215
|
-
content:
|
|
216
|
-
card({ content: stat({ label: 'Revenue', value: '$18.2k', change: '+12%', trend: 'up' }) + `<div style="margin-top:.75rem">${sparkline({ data: [8,11,9,14,12,16,15,18], color: 'success', area: true })}</div>` }) +
|
|
217
|
-
card({ content: stat({ label: 'Users', value: '4,821', change: '+8.4%', trend: 'up' }) + `<div style="margin-top:.75rem">${sparkline({ data: [22,28,24,31,27,34,30,38], color: 'accent', area: true })}</div>` }) +
|
|
218
|
-
card({ content: stat({ label: 'Churn', value: '2.1%', change: '−0.3%', trend: 'down' }) + `<div style="margin-top:.75rem">${sparkline({ data: [8,6,7,5,6,4,5,3], color: 'error', area: true })}</div>` }),
|
|
219
|
-
}),
|
|
220
|
-
`card({
|
|
221
|
-
content: stat({ label: 'Revenue', value: '$18.2k', change: '+12%', trend: 'up' }) +
|
|
222
|
-
\`<div style="margin-top:.75rem">\${sparkline({ data, color: 'success', area: true })}</div>\`,
|
|
223
|
-
})`
|
|
224
|
-
)}
|
|
225
|
-
|
|
226
|
-
<h2 class="doc-h2" id="props">Props</h2>
|
|
227
|
-
|
|
228
|
-
<h3 class="doc-h3">barChart()</h3>
|
|
229
|
-
${table(
|
|
230
|
-
['Prop', 'Type', 'Default', 'Description'],
|
|
231
|
-
[
|
|
232
|
-
['<code>data</code>', 'array', '—', '<code>{ label, value }[]</code>'],
|
|
233
|
-
['<code>height</code>', 'number', '220', 'SVG height in px'],
|
|
234
|
-
['<code>color</code>', 'string', "'accent'", 'accent · success · warning · error · blue · muted'],
|
|
235
|
-
['<code>showValues</code>', 'boolean', 'false', 'Value labels above each bar'],
|
|
236
|
-
['<code>showGrid</code>', 'boolean', 'true', 'Horizontal grid lines'],
|
|
237
|
-
['<code>gap</code>', 'number', '0.25', 'Gap between bars as fraction 0–0.9'],
|
|
238
|
-
]
|
|
239
|
-
)}
|
|
240
|
-
<p class="doc-note u-mt-2">Always renders at full parent width (<code>width="100%"</code>).</p>
|
|
241
|
-
|
|
242
|
-
<h3 class="doc-h3" style="margin-top:2rem">lineChart()</h3>
|
|
243
|
-
${table(
|
|
244
|
-
['Prop', 'Type', 'Default', 'Description'],
|
|
245
|
-
[
|
|
246
|
-
['<code>data</code>', 'array', '—', '<code>{ label, value }[]</code>'],
|
|
247
|
-
['<code>height</code>', 'number', '220', 'SVG height in px'],
|
|
248
|
-
['<code>color</code>', 'string', "'accent'", 'accent · success · warning · error · blue · muted'],
|
|
249
|
-
['<code>area</code>', 'boolean', 'false', 'Fill area under the line'],
|
|
250
|
-
['<code>showDots</code>', 'boolean', 'true', 'Dots at each data point'],
|
|
251
|
-
['<code>showGrid</code>', 'boolean', 'true', 'Horizontal grid lines'],
|
|
252
|
-
]
|
|
253
|
-
)}
|
|
254
|
-
<p class="doc-note u-mt-2">Always renders at full parent width (<code>width="100%"</code>).</p>
|
|
255
|
-
|
|
256
|
-
<h3 class="doc-h3" style="margin-top:2rem">donutChart()</h3>
|
|
257
|
-
${table(
|
|
258
|
-
['Prop', 'Type', 'Default', 'Description'],
|
|
259
|
-
[
|
|
260
|
-
['<code>data</code>', 'array', '—', '<code>{ label, value, color? }[]</code> — color per segment'],
|
|
261
|
-
['<code>size</code>', 'number', '200', 'Diameter in px'],
|
|
262
|
-
['<code>thickness</code>', 'number', '40', 'Ring thickness in px'],
|
|
263
|
-
['<code>label</code>', 'string', '—', 'Large centre text'],
|
|
264
|
-
['<code>sublabel</code>', 'string', '—', 'Smaller text below centre label'],
|
|
265
|
-
['<code>fluid</code>', 'boolean', 'false', 'Expand to parent width, preserving aspect ratio'],
|
|
266
|
-
]
|
|
267
|
-
)}
|
|
268
|
-
|
|
269
|
-
<h3 class="doc-h3" style="margin-top:2rem">sparkline()</h3>
|
|
270
|
-
${table(
|
|
271
|
-
['Prop', 'Type', 'Default', 'Description'],
|
|
272
|
-
[
|
|
273
|
-
['<code>data</code>', 'number[]', '—', 'Plain array of numbers'],
|
|
274
|
-
['<code>width</code>', 'number', '80', 'SVG width in px (ignored when fluid: true)'],
|
|
275
|
-
['<code>height</code>', 'number', '32', 'SVG height in px'],
|
|
276
|
-
['<code>color</code>', 'string', "'accent'", 'accent · success · warning · error · blue · muted'],
|
|
277
|
-
['<code>area</code>', 'boolean', 'false', 'Fill area under the line'],
|
|
278
|
-
['<code>fluid</code>', 'boolean', 'false', 'Expand to parent width, preserving aspect ratio'],
|
|
279
|
-
]
|
|
280
|
-
)}
|
|
281
|
-
`,
|
|
282
|
-
}),
|
|
283
|
-
}
|
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
import { renderComponentPage, demo } from '../../lib/component-page.js'
|
|
2
|
-
import { prevNext } from '../../lib/nav.js'
|
|
3
|
-
import { table, callout } from '../../lib/layout.js'
|
|
4
|
-
import { checkbox, fieldset } from '../../../../src/ui/index.js'
|
|
5
|
-
|
|
6
|
-
const { prev, next } = prevNext('/components/checkbox')
|
|
7
|
-
|
|
8
|
-
export default {
|
|
9
|
-
route: '/components/checkbox',
|
|
10
|
-
meta: {
|
|
11
|
-
theme: 'light',
|
|
12
|
-
title: 'Checkbox — Pulse Docs',
|
|
13
|
-
description: 'Custom-styled checkbox component for Pulse UI.',
|
|
14
|
-
styles: ['/pulse-ui.css', '/theme.css', '/docs.css'],
|
|
15
|
-
},
|
|
16
|
-
view: () => renderComponentPage({
|
|
17
|
-
currentHref: '/components/checkbox',
|
|
18
|
-
prev,
|
|
19
|
-
next,
|
|
20
|
-
name: 'checkbox',
|
|
21
|
-
description: 'Custom-styled checkbox with animated check mark, full keyboard and screen-reader support. Pairs with <a href="/components/fieldset">fieldset</a> for labelled groups.',
|
|
22
|
-
content: `
|
|
23
|
-
|
|
24
|
-
<h2 class="doc-h2" id="basic">Basic</h2>
|
|
25
|
-
<p>Pass <code>label</code> for the visible text. The <code>id</code> is auto-generated from <code>name</code> and <code>value</code>.</p>
|
|
26
|
-
${demo(
|
|
27
|
-
checkbox({ name: 'agree', label: 'I agree to the terms' }),
|
|
28
|
-
`checkbox({ name: 'agree', label: 'I agree to the terms' })`,
|
|
29
|
-
{ col: true }
|
|
30
|
-
)}
|
|
31
|
-
|
|
32
|
-
<h2 class="doc-h2" id="checked">Checked</h2>
|
|
33
|
-
${demo(
|
|
34
|
-
checkbox({ name: 'newsletter', label: 'Send me updates', checked: true }),
|
|
35
|
-
`checkbox({ name: 'newsletter', label: 'Send me updates', checked: state.newsletter })`,
|
|
36
|
-
{ col: true }
|
|
37
|
-
)}
|
|
38
|
-
|
|
39
|
-
<h2 class="doc-h2" id="hint">Hint</h2>
|
|
40
|
-
<p>A <code>hint</code> string renders below the label as supporting copy.</p>
|
|
41
|
-
${demo(
|
|
42
|
-
checkbox({ name: 'marketing', label: 'Marketing emails', hint: 'Product news and tips. Unsubscribe any time.', checked: true }),
|
|
43
|
-
`checkbox({
|
|
44
|
-
name: 'marketing',
|
|
45
|
-
label: 'Marketing emails',
|
|
46
|
-
hint: 'Product news and tips. Unsubscribe any time.',
|
|
47
|
-
checked: state.marketing,
|
|
48
|
-
})`,
|
|
49
|
-
{ col: true }
|
|
50
|
-
)}
|
|
51
|
-
|
|
52
|
-
<h2 class="doc-h2" id="error">Error state</h2>
|
|
53
|
-
<p>Pass <code>error</code> to show a validation message. The error is announced via <code>role="alert"</code>.</p>
|
|
54
|
-
${demo(
|
|
55
|
-
checkbox({ name: 'terms', label: 'I accept the terms and conditions', error: 'You must accept the terms to continue.' }),
|
|
56
|
-
`checkbox({
|
|
57
|
-
name: 'terms',
|
|
58
|
-
label: 'I accept the terms and conditions',
|
|
59
|
-
error: server.errors.terms,
|
|
60
|
-
})`,
|
|
61
|
-
{ col: true }
|
|
62
|
-
)}
|
|
63
|
-
|
|
64
|
-
<h2 class="doc-h2" id="disabled">Disabled</h2>
|
|
65
|
-
${demo(
|
|
66
|
-
checkbox({ name: 'feature', label: 'Enable beta features', disabled: true }) +
|
|
67
|
-
' ' +
|
|
68
|
-
checkbox({ name: 'feature2', label: 'Beta feature (on)', disabled: true, checked: true }),
|
|
69
|
-
`checkbox({ name: 'feature', label: 'Enable beta features', disabled: true })`,
|
|
70
|
-
{ col: true }
|
|
71
|
-
)}
|
|
72
|
-
|
|
73
|
-
<h2 class="doc-h2" id="group">Group in a fieldset</h2>
|
|
74
|
-
<p>Compose multiple checkboxes inside a <a href="/components/fieldset">fieldset</a> for a semantic group.</p>
|
|
75
|
-
${demo(
|
|
76
|
-
fieldset({
|
|
77
|
-
legend: 'Notifications',
|
|
78
|
-
content:
|
|
79
|
-
checkbox({ name: 'notif', value: 'email', label: 'Email', checked: true }) +
|
|
80
|
-
checkbox({ name: 'notif', value: 'sms', label: 'SMS' }) +
|
|
81
|
-
checkbox({ name: 'notif', value: 'browser', label: 'Browser push', checked: true }) +
|
|
82
|
-
checkbox({ name: 'notif', value: 'weekly', label: 'Weekly digest', disabled: true }),
|
|
83
|
-
}),
|
|
84
|
-
`fieldset({
|
|
85
|
-
legend: 'Notifications',
|
|
86
|
-
content:
|
|
87
|
-
checkbox({ name: 'notif', value: 'email', label: 'Email', checked: true }) +
|
|
88
|
-
checkbox({ name: 'notif', value: 'sms', label: 'SMS' }) +
|
|
89
|
-
checkbox({ name: 'notif', value: 'browser', label: 'Browser push', checked: true }) +
|
|
90
|
-
checkbox({ name: 'notif', value: 'weekly', label: 'Weekly digest', disabled: true }),
|
|
91
|
-
})`,
|
|
92
|
-
{ col: true }
|
|
93
|
-
)}
|
|
94
|
-
|
|
95
|
-
<h2 class="doc-h2" id="in-forms">In forms</h2>
|
|
96
|
-
${callout('note', 'Checkboxes submit their <code>value</code> string (defaulting to <code>"on"</code>) under <code>name</code> in FormData only when checked. Unchecked checkboxes are absent from FormData. Read them in <code>action.onStart</code> via <code>formData.get(\'agree\')</code> — a <code>null</code> result means unchecked.')}
|
|
97
|
-
|
|
98
|
-
<h2 class="doc-h2" id="labelhtml">Custom label HTML</h2>
|
|
99
|
-
<p>When the label needs inline styling — for example a strikethrough on a completed todo — use <code>labelHtml</code> instead of <code>label</code>. The value is inserted as raw HTML so you are responsible for escaping any user content.</p>
|
|
100
|
-
${demo(
|
|
101
|
-
checkbox({ name: 'task', labelHtml: '<span style="text-decoration:line-through;opacity:.5">Buy milk</span>', checked: true }),
|
|
102
|
-
`checkbox({
|
|
103
|
-
name: 'task',
|
|
104
|
-
checked: todo.done,
|
|
105
|
-
labelHtml: \`<span class="\${todo.done ? 'u-text-muted' : ''}">\${esc(todo.text)}</span>\`,
|
|
106
|
-
})`,
|
|
107
|
-
{ col: true }
|
|
108
|
-
)}
|
|
109
|
-
|
|
110
|
-
<h2 class="doc-h2" id="props">Props</h2>
|
|
111
|
-
${table(
|
|
112
|
-
['Prop', 'Type', 'Default', 'Description'],
|
|
113
|
-
[
|
|
114
|
-
['<code>name</code>', 'string', '—', 'Field name'],
|
|
115
|
-
['<code>value</code>', 'string', '—', 'Submitted value when checked (defaults to browser default <code>"on"</code>)'],
|
|
116
|
-
['<code>label</code>', 'string', '—', 'Visible label text — escaped'],
|
|
117
|
-
['<code>labelHtml</code>', 'string', '—', 'Raw HTML label slot — not escaped, use for styled spans'],
|
|
118
|
-
['<code>checked</code>', 'boolean', 'false', ''],
|
|
119
|
-
['<code>disabled</code>', 'boolean', 'false', ''],
|
|
120
|
-
['<code>id</code>', 'string', '—', 'Override the auto-generated <code>id</code>'],
|
|
121
|
-
['<code>event</code>', 'string', '—', '<code>data-event</code> binding, e.g. <code>"change:toggle"</code>'],
|
|
122
|
-
['<code>hint</code>', 'string', '—', 'Helper text below the label'],
|
|
123
|
-
['<code>error</code>', 'string', '—', 'Validation error — announced via <code>role="alert"</code>'],
|
|
124
|
-
['<code>class</code>', 'string', '—', ''],
|
|
125
|
-
]
|
|
126
|
-
)}
|
|
127
|
-
`,
|
|
128
|
-
}),
|
|
129
|
-
}
|