@invisibleloop/pulse 0.1.21
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/.claude/commands/build-page.md +59 -0
- package/.claude/commands/new-doc-page.md +45 -0
- package/.claude/commands/verify.md +52 -0
- package/.claude/pulse-checklist.md +111 -0
- package/.claude/settings.local.json +102 -0
- package/.github/workflows/ci.yml +22 -0
- package/.github/workflows/publish.yml +41 -0
- package/.pulse/load-reports/home/1773432711417.json +22 -0
- package/CLAUDE.md +383 -0
- package/README.md +95 -0
- package/docs/.claude/pulse-checklist.md +111 -0
- package/docs/public/.pulse-ui-version +1 -0
- package/docs/public/dist/accessibility.boot-5DVTARJU.js +115 -0
- package/docs/public/dist/actions.boot-P66HKQEM.js +164 -0
- package/docs/public/dist/auth.boot-IMAJAUPH.js +140 -0
- package/docs/public/dist/caching.boot-DVR6KDE7.js +53 -0
- package/docs/public/dist/components--accordion.boot-3HVKMNWC.js +11 -0
- package/docs/public/dist/components--alert.boot-GCEXOZAC.js +6 -0
- package/docs/public/dist/components--app-badge.boot-DVT3GCHJ.js +6 -0
- package/docs/public/dist/components--avatar.boot-PSW24EVA.js +5 -0
- package/docs/public/dist/components--badge.boot-TYDY2RMK.js +7 -0
- package/docs/public/dist/components--banner.boot-EI5PZSZK.js +7 -0
- package/docs/public/dist/components--breadcrumbs.boot-SMA2E2GO.js +34 -0
- package/docs/public/dist/components--button.boot-J54BQM2E.js +23 -0
- package/docs/public/dist/components--card.boot-PZGNDIB6.js +138 -0
- package/docs/public/dist/components--carousel.boot-TP6LPFZZ.js +12 -0
- package/docs/public/dist/components--charts.boot-2EOYQWKL.js +108 -0
- package/docs/public/dist/components--checkbox.boot-DS5BSL6T.js +54 -0
- package/docs/public/dist/components--cluster.boot-HHVIBBJG.js +9 -0
- package/docs/public/dist/components--code-window.boot-2GR2DV33.js +20 -0
- package/docs/public/dist/components--container.boot-7LOOGK2K.js +5 -0
- package/docs/public/dist/components--cta.boot-FSNZ5YRT.js +11 -0
- package/docs/public/dist/components--divider.boot-3NI2C3QG.js +6 -0
- package/docs/public/dist/components--empty.boot-YX2UR3PV.js +7 -0
- package/docs/public/dist/components--feature.boot-MUD7NSUO.js +13 -0
- package/docs/public/dist/components--fieldset.boot-J7BYHMKF.js +19 -0
- package/docs/public/dist/components--fileupload.boot-NIKVTTPD.js +52 -0
- package/docs/public/dist/components--footer.boot-EYUK5FRG.js +14 -0
- package/docs/public/dist/components--grid.boot-URDQVDDR.js +59 -0
- package/docs/public/dist/components--heading.boot-BPQKU43E.js +44 -0
- package/docs/public/dist/components--hero.boot-4RAPRGAB.js +17 -0
- package/docs/public/dist/components--icons.boot-ZITNU5JP.js +68 -0
- package/docs/public/dist/components--image.boot-XEEGHQZF.js +19 -0
- package/docs/public/dist/components--input.boot-SGASZG5K.js +7 -0
- package/docs/public/dist/components--list.boot-W3XC5MHD.js +55 -0
- package/docs/public/dist/components--media.boot-5VFIETZO.js +13 -0
- package/docs/public/dist/components--modal.boot-RZUYXBN2.js +47 -0
- package/docs/public/dist/components--nav.boot-ODBOHU7O.js +33 -0
- package/docs/public/dist/components--pricing.boot-4AQ4ZVBY.js +21 -0
- package/docs/public/dist/components--progress.boot-GHAGYZOK.js +30 -0
- package/docs/public/dist/components--prose.boot-QANJL6JI.js +67 -0
- package/docs/public/dist/components--pullquote.boot-Q2WMNAZU.js +22 -0
- package/docs/public/dist/components--radio.boot-TJRDQ2OL.js +75 -0
- package/docs/public/dist/components--rating.boot-QBAN6DEL.js +38 -0
- package/docs/public/dist/components--search.boot-PXH5O5AG.js +17 -0
- package/docs/public/dist/components--section.boot-AQGIYHWW.js +12 -0
- package/docs/public/dist/components--segmented.boot-BEVTKEJO.js +33 -0
- package/docs/public/dist/components--select.boot-47X5RHOC.js +10 -0
- package/docs/public/dist/components--slider.boot-PSRRX7XL.js +47 -0
- package/docs/public/dist/components--spinner.boot-MZ5MO2OH.js +22 -0
- package/docs/public/dist/components--stack.boot-DI4NJXBF.js +9 -0
- package/docs/public/dist/components--stat.boot-QMFUWBQT.js +9 -0
- package/docs/public/dist/components--stepper.boot-34PP2NEV.js +22 -0
- package/docs/public/dist/components--table.boot-FCQGSFIQ.js +11 -0
- package/docs/public/dist/components--testimonial.boot-DWQPDKYG.js +11 -0
- package/docs/public/dist/components--textarea.boot-QVXLBOJ5.js +4 -0
- package/docs/public/dist/components--timeline.boot-26LN52P2.js +95 -0
- package/docs/public/dist/components--toggle.boot-IQQEI76S.js +29 -0
- package/docs/public/dist/components--tooltip.boot-LGHCO6NN.js +9 -0
- package/docs/public/dist/components.boot-SE6PQ4P7.js +103 -0
- package/docs/public/dist/config.boot-DTRRWUE6.js +126 -0
- package/docs/public/dist/constraints.boot-DUHDZBMC.js +71 -0
- package/docs/public/dist/deploy.boot-SLAD3NI2.js +163 -0
- package/docs/public/dist/docs-8e3d4b5c.css +1 -0
- package/docs/public/dist/extending.boot-UA3CN243.js +159 -0
- package/docs/public/dist/faq.boot-6EQAWLQR.js +43 -0
- package/docs/public/dist/getting-started.boot-TDKIFL5U.js +86 -0
- package/docs/public/dist/guard.boot-AUHAWTG4.js +80 -0
- package/docs/public/dist/home.boot-BVQXRH32.js +383 -0
- package/docs/public/dist/how-it-works.boot-LTWAKWKW.js +104 -0
- package/docs/public/dist/hydration.boot-JRM6IPJL.js +78 -0
- package/docs/public/dist/images.boot-M6ZVKTZS.js +80 -0
- package/docs/public/dist/manifest.json +94 -0
- package/docs/public/dist/meta.boot-7NXGPHR4.js +79 -0
- package/docs/public/dist/mutations.boot-F6F43UDX.js +79 -0
- package/docs/public/dist/navigation.boot-AOXWS3ZF.js +57 -0
- package/docs/public/dist/performance.boot-C3UPCOBK.js +98 -0
- package/docs/public/dist/persist.boot-WT32PQOQ.js +61 -0
- package/docs/public/dist/project-structure.boot-FB3LRVJ4.js +63 -0
- package/docs/public/dist/prompt-examples.boot-YKR4VDK4.js +31 -0
- package/docs/public/dist/pulse-ui-81a85c03.css +1 -0
- package/docs/public/dist/raw-responses.boot-M4KA5YXL.js +104 -0
- package/docs/public/dist/routing.boot-FNX5FDGH.js +70 -0
- package/docs/public/dist/runtime-B73WLANC.js +1 -0
- package/docs/public/dist/runtime-KO4BHUQ3.js +49 -0
- package/docs/public/dist/runtime-L2HNXIHW.js +59 -0
- package/docs/public/dist/runtime-QFURDKA2.js +5 -0
- package/docs/public/dist/runtime-UVPXO4IR.js +375 -0
- package/docs/public/dist/runtime-VMJA3Z4N.js +10 -0
- package/docs/public/dist/runtime-ZJ4FXT5O.js +11 -0
- package/docs/public/dist/server-api.boot-K7X3LCFB.js +219 -0
- package/docs/public/dist/server-data.boot-Y7HQYC4R.js +157 -0
- package/docs/public/dist/slash-commands.boot-V2UV7OW2.js +26 -0
- package/docs/public/dist/spec.boot-2WU7ZHCV.js +159 -0
- package/docs/public/dist/state.boot-B24GUE3R.js +73 -0
- package/docs/public/dist/store.boot-TLIB4XHH.js +150 -0
- package/docs/public/dist/streaming.boot-W2DZSMW4.js +80 -0
- package/docs/public/dist/stripe.boot-QN3C2GEL.js +164 -0
- package/docs/public/dist/supabase.boot-BG4XXLZE.js +303 -0
- package/docs/public/dist/testing.boot-6U4WKMTE.js +130 -0
- package/docs/public/dist/validation.boot-PQHYGW5B.js +100 -0
- package/docs/public/docs.css +2020 -0
- package/docs/public/menu.js +83 -0
- package/docs/public/pulse-ui.css +2739 -0
- package/docs/public/pulse-ui.js +236 -0
- package/docs/server.js +192 -0
- package/docs/src/lib/component-page.js +47 -0
- package/docs/src/lib/highlight.js +255 -0
- package/docs/src/lib/layout.js +131 -0
- package/docs/src/lib/metrics-store.js +6 -0
- package/docs/src/lib/nav.js +159 -0
- package/docs/src/lib/stats.js +81 -0
- package/docs/src/pages/accessibility.js +157 -0
- package/docs/src/pages/actions.js +191 -0
- package/docs/src/pages/auth.js +177 -0
- package/docs/src/pages/caching.js +95 -0
- package/docs/src/pages/components/accordion.js +48 -0
- package/docs/src/pages/components/alert.js +35 -0
- package/docs/src/pages/components/app-badge.js +41 -0
- package/docs/src/pages/components/avatar.js +35 -0
- package/docs/src/pages/components/badge.js +36 -0
- package/docs/src/pages/components/banner.js +45 -0
- package/docs/src/pages/components/breadcrumbs.js +94 -0
- package/docs/src/pages/components/button.js +84 -0
- package/docs/src/pages/components/card.js +225 -0
- package/docs/src/pages/components/carousel.js +72 -0
- package/docs/src/pages/components/charts.js +278 -0
- package/docs/src/pages/components/checkbox.js +129 -0
- package/docs/src/pages/components/cluster.js +47 -0
- package/docs/src/pages/components/code-window.js +57 -0
- package/docs/src/pages/components/container.js +40 -0
- package/docs/src/pages/components/cta.js +53 -0
- package/docs/src/pages/components/divider.js +37 -0
- package/docs/src/pages/components/empty.js +36 -0
- package/docs/src/pages/components/feature.js +60 -0
- package/docs/src/pages/components/fieldset.js +65 -0
- package/docs/src/pages/components/fileupload.js +127 -0
- package/docs/src/pages/components/footer.js +58 -0
- package/docs/src/pages/components/grid.js +165 -0
- package/docs/src/pages/components/heading.js +107 -0
- package/docs/src/pages/components/hero.js +65 -0
- package/docs/src/pages/components/icons.js +285 -0
- package/docs/src/pages/components/image.js +71 -0
- package/docs/src/pages/components/input.js +51 -0
- package/docs/src/pages/components/list.js +112 -0
- package/docs/src/pages/components/media.js +51 -0
- package/docs/src/pages/components/modal.js +111 -0
- package/docs/src/pages/components/nav.js +86 -0
- package/docs/src/pages/components/pricing.js +68 -0
- package/docs/src/pages/components/progress.js +102 -0
- package/docs/src/pages/components/prose.js +111 -0
- package/docs/src/pages/components/pullquote.js +71 -0
- package/docs/src/pages/components/radio.js +194 -0
- package/docs/src/pages/components/rating.js +106 -0
- package/docs/src/pages/components/search.js +61 -0
- package/docs/src/pages/components/section.js +59 -0
- package/docs/src/pages/components/segmented.js +121 -0
- package/docs/src/pages/components/select.js +45 -0
- package/docs/src/pages/components/slider.js +114 -0
- package/docs/src/pages/components/spinner.js +73 -0
- package/docs/src/pages/components/stack.js +48 -0
- package/docs/src/pages/components/stat.js +55 -0
- package/docs/src/pages/components/stepper.js +66 -0
- package/docs/src/pages/components/table.js +45 -0
- package/docs/src/pages/components/testimonial.js +49 -0
- package/docs/src/pages/components/textarea.js +31 -0
- package/docs/src/pages/components/timeline.js +227 -0
- package/docs/src/pages/components/toggle.js +84 -0
- package/docs/src/pages/components/tooltip.js +48 -0
- package/docs/src/pages/components.js +204 -0
- package/docs/src/pages/config.js +193 -0
- package/docs/src/pages/constraints.js +99 -0
- package/docs/src/pages/deploy.js +233 -0
- package/docs/src/pages/extending.js +198 -0
- package/docs/src/pages/faq.js +96 -0
- package/docs/src/pages/getting-started.js +106 -0
- package/docs/src/pages/guard.js +121 -0
- package/docs/src/pages/home.js +401 -0
- package/docs/src/pages/how-it-works.js +183 -0
- package/docs/src/pages/hydration.js +98 -0
- package/docs/src/pages/images.js +121 -0
- package/docs/src/pages/meta.js +120 -0
- package/docs/src/pages/mutations.js +106 -0
- package/docs/src/pages/navigation.js +85 -0
- package/docs/src/pages/performance.js +157 -0
- package/docs/src/pages/persist.js +88 -0
- package/docs/src/pages/project-structure.js +90 -0
- package/docs/src/pages/prompt-examples.js +186 -0
- package/docs/src/pages/raw-responses.js +124 -0
- package/docs/src/pages/routing.js +99 -0
- package/docs/src/pages/server-api.js +281 -0
- package/docs/src/pages/server-data.js +185 -0
- package/docs/src/pages/slash-commands.js +55 -0
- package/docs/src/pages/spec.js +207 -0
- package/docs/src/pages/state.js +101 -0
- package/docs/src/pages/store.js +181 -0
- package/docs/src/pages/streaming.js +108 -0
- package/docs/src/pages/stripe.js +193 -0
- package/docs/src/pages/supabase.js +323 -0
- package/docs/src/pages/testing.js +198 -0
- package/docs/src/pages/validation.js +138 -0
- package/examples/contact.js +166 -0
- package/examples/counter.js +94 -0
- package/examples/dev.server.js +91 -0
- package/examples/examples.test.js +394 -0
- package/examples/pricing.js +244 -0
- package/examples/products.js +191 -0
- package/examples/quiz.js +208 -0
- package/examples/shared.js +78 -0
- package/examples/todos.js +162 -0
- package/package.json +75 -0
- package/public/.pulse-ui-version +1 -0
- package/public/chippy-bird.css +246 -0
- package/public/examples/contact.css +119 -0
- package/public/examples/counter.css +79 -0
- package/public/examples/pricing.css +132 -0
- package/public/examples/products.css +100 -0
- package/public/examples/quiz.css +200 -0
- package/public/examples/todos.css +137 -0
- package/public/favicon.ico +0 -0
- package/public/log-dashboard.css +383 -0
- package/public/pulse-ui.css +2740 -0
- package/public/pulse-ui.js +236 -0
- package/public/pulse.css +149 -0
- package/scripts/build.js +411 -0
- package/src/agent/checklist.md +111 -0
- package/src/agent/coverage-check.js +66 -0
- package/src/agent/guide-components.md +274 -0
- package/src/agent/guide-examples.md +54 -0
- package/src/agent/guide-routing.md +36 -0
- package/src/agent/guide-server.md +258 -0
- package/src/agent/guide-spec.md +103 -0
- package/src/agent/guide-styles.md +191 -0
- package/src/agent/guide.md +979 -0
- package/src/agent/identity.md +106 -0
- package/src/agent/workflow.md +108 -0
- package/src/cli/cli.test.js +82 -0
- package/src/cli/dev.js +195 -0
- package/src/cli/discover.js +113 -0
- package/src/cli/index.js +361 -0
- package/src/cli/load-report.js +91 -0
- package/src/cli/load-runner.js +121 -0
- package/src/cli/report-server.js +723 -0
- package/src/cli/report.js +116 -0
- package/src/cli/scaffold.archive.js +1371 -0
- package/src/cli/scaffold.js +349 -0
- package/src/cli/start.js +74 -0
- package/src/html.js +19 -0
- package/src/mcp/server.js +884 -0
- package/src/mcp/validate-worker.js +110 -0
- package/src/runtime/image.js +74 -0
- package/src/runtime/image.test.js +111 -0
- package/src/runtime/index.js +621 -0
- package/src/runtime/navigate.js +146 -0
- package/src/runtime/runtime.test.js +773 -0
- package/src/runtime/ssr.js +464 -0
- package/src/runtime/ssr.test.js +421 -0
- package/src/runtime/store.js +92 -0
- package/src/runtime/toast.js +163 -0
- package/src/server/index.js +1386 -0
- package/src/server/server.test.js +1248 -0
- package/src/spec/schema.js +428 -0
- package/src/spec/schema.test.js +291 -0
- package/src/store/index.js +102 -0
- package/src/store/store.test.js +210 -0
- package/src/testing/html.js +283 -0
- package/src/testing/index.js +249 -0
- package/src/testing/testing.test.js +450 -0
- package/src/ui/accordion.js +28 -0
- package/src/ui/alert.js +43 -0
- package/src/ui/app-badge.js +48 -0
- package/src/ui/avatar.js +47 -0
- package/src/ui/badge.js +24 -0
- package/src/ui/banner.js +26 -0
- package/src/ui/breadcrumbs.js +38 -0
- package/src/ui/button.js +66 -0
- package/src/ui/card.js +34 -0
- package/src/ui/carousel.js +59 -0
- package/src/ui/charts.js +321 -0
- package/src/ui/checkbox.js +65 -0
- package/src/ui/cluster.js +44 -0
- package/src/ui/code-window.js +39 -0
- package/src/ui/container.js +24 -0
- package/src/ui/cta.js +37 -0
- package/src/ui/divider.js +29 -0
- package/src/ui/empty.js +33 -0
- package/src/ui/feature.js +33 -0
- package/src/ui/fieldset.js +37 -0
- package/src/ui/fileupload.js +89 -0
- package/src/ui/footer.js +38 -0
- package/src/ui/grid.js +36 -0
- package/src/ui/heading.js +45 -0
- package/src/ui/hero.js +37 -0
- package/src/ui/icons.js +161 -0
- package/src/ui/index.js +89 -0
- package/src/ui/input.js +74 -0
- package/src/ui/list.js +36 -0
- package/src/ui/media.js +44 -0
- package/src/ui/modal.js +80 -0
- package/src/ui/nav.js +61 -0
- package/src/ui/pricing.js +56 -0
- package/src/ui/progress.js +62 -0
- package/src/ui/prose.js +29 -0
- package/src/ui/pullquote.js +34 -0
- package/src/ui/radio.js +102 -0
- package/src/ui/rating.js +93 -0
- package/src/ui/search.js +77 -0
- package/src/ui/section.js +69 -0
- package/src/ui/segmented.js +50 -0
- package/src/ui/select.js +77 -0
- package/src/ui/slider.js +84 -0
- package/src/ui/spinner.js +34 -0
- package/src/ui/stack.js +36 -0
- package/src/ui/stat.js +52 -0
- package/src/ui/stepper.js +46 -0
- package/src/ui/switch.js +57 -0
- package/src/ui/table.js +45 -0
- package/src/ui/testimonial.js +48 -0
- package/src/ui/textarea.js +72 -0
- package/src/ui/timeline.js +72 -0
- package/src/ui/tooltip.js +28 -0
- package/src/ui/ui.test.js +1241 -0
- package/src/ui/uiimage.js +65 -0
- package/tsconfig.json +13 -0
- package/types/html.d.ts +17 -0
- package/types/image.d.ts +70 -0
- package/types/index.d.ts +7 -0
- package/types/navigate.d.ts +38 -0
- package/types/runtime.d.ts +63 -0
- package/types/schema.d.ts +243 -0
- package/types/server.d.ts +145 -0
- package/types/ssr.d.ts +110 -0
- package/types/testing.d.ts +154 -0
- package/types/ui.d.ts +704 -0
|
@@ -0,0 +1,53 @@
|
|
|
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 { cta, button } from '../../../../src/ui/index.js'
|
|
5
|
+
|
|
6
|
+
const { prev, next } = prevNext('/components/cta')
|
|
7
|
+
|
|
8
|
+
export default {
|
|
9
|
+
route: '/components/cta',
|
|
10
|
+
meta: {
|
|
11
|
+
title: 'CTA — Pulse Docs',
|
|
12
|
+
description: 'Call-to-action block with eyebrow, heading, body text, and an actions slot.',
|
|
13
|
+
styles: ['/pulse-ui.css', '/docs.css'],
|
|
14
|
+
},
|
|
15
|
+
state: {},
|
|
16
|
+
view: () => renderComponentPage({
|
|
17
|
+
currentHref: '/components/cta',
|
|
18
|
+
prev,
|
|
19
|
+
next,
|
|
20
|
+
name: 'cta',
|
|
21
|
+
description: 'Call-to-action block with eyebrow, heading, body text, and an actions slot. Sits inside <code>section()</code> + <code>container()</code> — adds no padding of its own.',
|
|
22
|
+
content: `
|
|
23
|
+
${demo(
|
|
24
|
+
cta({
|
|
25
|
+
eyebrow: 'Get started today',
|
|
26
|
+
title: 'Ready to build?',
|
|
27
|
+
subtitle: 'One spec file per page. SSR always on. Lighthouse 100 out of the box.',
|
|
28
|
+
actions: button({ label: 'Start building →', href: '#', variant: 'primary', size: 'lg' }) +
|
|
29
|
+
button({ label: 'View on GitHub', href: '#', variant: 'ghost', size: 'lg' }),
|
|
30
|
+
}),
|
|
31
|
+
`cta({
|
|
32
|
+
eyebrow: 'Get started today',
|
|
33
|
+
title: 'Ready to build?',
|
|
34
|
+
subtitle: 'One spec file per page. SSR always on. Lighthouse 100 out of the box.',
|
|
35
|
+
actions: button({ label: 'Start building →', href: '/docs', variant: 'primary', size: 'lg' }) +
|
|
36
|
+
button({ label: 'View on GitHub', href: 'https://github.com/...', variant: 'ghost', size: 'lg' }),
|
|
37
|
+
})`
|
|
38
|
+
)}
|
|
39
|
+
|
|
40
|
+
${table(
|
|
41
|
+
['Prop', 'Type', 'Default', ''],
|
|
42
|
+
[
|
|
43
|
+
['<code>eyebrow</code>', 'string', '—', 'Small label above the heading'],
|
|
44
|
+
['<code>title</code>', 'string', '—', 'Main heading'],
|
|
45
|
+
['<code>level</code>', 'number', '2', 'Heading tag for the title (1–6). Visual style is unchanged.'],
|
|
46
|
+
['<code>subtitle</code>', 'string', '—', 'Supporting paragraph'],
|
|
47
|
+
['<code>actions</code>', 'string (HTML)', '—', 'Raw HTML slot — typically button() calls'],
|
|
48
|
+
['<code>align</code>', 'string', "'center'", "'center' · 'left'"],
|
|
49
|
+
]
|
|
50
|
+
)}
|
|
51
|
+
`,
|
|
52
|
+
}),
|
|
53
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
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 { divider, stack } from '../../../../src/ui/index.js'
|
|
5
|
+
|
|
6
|
+
const { prev, next } = prevNext('/components/divider')
|
|
7
|
+
|
|
8
|
+
export default {
|
|
9
|
+
route: '/components/divider',
|
|
10
|
+
meta: {
|
|
11
|
+
title: 'Divider — Pulse Docs',
|
|
12
|
+
description: 'Divider component for Pulse UI.',
|
|
13
|
+
styles: ['/pulse-ui.css', '/docs.css'],
|
|
14
|
+
},
|
|
15
|
+
state: {},
|
|
16
|
+
view: () => renderComponentPage({
|
|
17
|
+
currentHref: '/components/divider',
|
|
18
|
+
prev,
|
|
19
|
+
next,
|
|
20
|
+
name: 'divider',
|
|
21
|
+
description: 'Horizontal rule for visual separation. With a <code>label</code>, the line splits either side of the text — the classic "or" pattern between login options.',
|
|
22
|
+
content: `
|
|
23
|
+
${demo(
|
|
24
|
+
stack({ gap: 'lg', content: divider() + divider({ label: 'or continue with' }) }),
|
|
25
|
+
`divider()
|
|
26
|
+
divider({ label: 'or continue with' })`
|
|
27
|
+
)}
|
|
28
|
+
|
|
29
|
+
${table(
|
|
30
|
+
['Prop', 'Type', 'Default', ''],
|
|
31
|
+
[
|
|
32
|
+
['<code>label</code>', 'string', '—', 'Optional centred text — renders as <code><div></code> with role="separator" when provided, <code><hr></code> otherwise'],
|
|
33
|
+
]
|
|
34
|
+
)}
|
|
35
|
+
`,
|
|
36
|
+
}),
|
|
37
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { renderComponentPage, demo } from '../../lib/component-page.js'
|
|
2
|
+
import { prevNext } from '../../lib/nav.js'
|
|
3
|
+
import { empty } from '../../../../src/ui/index.js'
|
|
4
|
+
|
|
5
|
+
const { prev, next } = prevNext('/components/empty')
|
|
6
|
+
|
|
7
|
+
export default {
|
|
8
|
+
route: '/components/empty',
|
|
9
|
+
meta: {
|
|
10
|
+
title: 'Empty — Pulse Docs',
|
|
11
|
+
description: 'Empty state component for Pulse UI.',
|
|
12
|
+
styles: ['/pulse-ui.css', '/docs.css'],
|
|
13
|
+
},
|
|
14
|
+
state: {},
|
|
15
|
+
view: () => renderComponentPage({
|
|
16
|
+
currentHref: '/components/empty',
|
|
17
|
+
prev,
|
|
18
|
+
next,
|
|
19
|
+
name: 'empty',
|
|
20
|
+
description: 'Empty state placeholder. Shows a centred title, optional description, and an optional call-to-action when there is nothing to display.',
|
|
21
|
+
content: `
|
|
22
|
+
${demo(
|
|
23
|
+
empty({
|
|
24
|
+
title: 'No results found',
|
|
25
|
+
description: 'Try adjusting your search or clearing the filters.',
|
|
26
|
+
action: { label: 'Clear filters', href: '#', variant: 'secondary' },
|
|
27
|
+
}),
|
|
28
|
+
`empty({
|
|
29
|
+
title: 'No results found',
|
|
30
|
+
description: 'Try adjusting your search or clearing the filters.',
|
|
31
|
+
action: { label: 'Clear filters', href: '/products', variant: 'secondary' },
|
|
32
|
+
})`
|
|
33
|
+
)}
|
|
34
|
+
`,
|
|
35
|
+
}),
|
|
36
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
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 { feature } from '../../../../src/ui/index.js'
|
|
5
|
+
import { iconZap, iconLock, iconPhone } from '../../../../src/ui/icons.js'
|
|
6
|
+
|
|
7
|
+
const { prev, next } = prevNext('/components/feature')
|
|
8
|
+
|
|
9
|
+
export default {
|
|
10
|
+
route: '/components/feature',
|
|
11
|
+
meta: {
|
|
12
|
+
title: 'Feature — Pulse Docs',
|
|
13
|
+
description: 'Feature component for Pulse UI.',
|
|
14
|
+
styles: ['/pulse-ui.css', '/docs.css'],
|
|
15
|
+
},
|
|
16
|
+
state: {},
|
|
17
|
+
view: () => renderComponentPage({
|
|
18
|
+
currentHref: '/components/feature',
|
|
19
|
+
prev,
|
|
20
|
+
next,
|
|
21
|
+
name: 'feature',
|
|
22
|
+
description: 'Icon + title + description block. Compose three or four in a CSS grid for the standard "why us" section.',
|
|
23
|
+
content: `
|
|
24
|
+
${demo(
|
|
25
|
+
`<div style="display:grid;grid-template-columns:repeat(3,1fr);gap:2rem">` +
|
|
26
|
+
feature({ icon: iconZap({ size: 20 }), title: 'Instant load', description: 'Streaming SSR ships HTML before data resolves. Fast by default.' }) +
|
|
27
|
+
feature({ icon: iconLock({ size: 20 }), title: 'Private by default', description: 'No trackers. No third-party scripts. Ever.' }) +
|
|
28
|
+
feature({ icon: iconPhone({ size: 20 }), title: 'Works offline', description: 'Full functionality without a connection.' }) +
|
|
29
|
+
`</div>`,
|
|
30
|
+
`import { iconZap, iconLock, iconPhone } from '@invisibleloop/pulse/ui'
|
|
31
|
+
|
|
32
|
+
feature({ icon: iconZap({ size: 20 }), title: 'Instant load', description: 'Streaming SSR ships HTML before data resolves.' })
|
|
33
|
+
feature({ icon: iconLock({ size: 20 }), title: 'Private by default', description: 'No trackers, ever.' })
|
|
34
|
+
feature({ icon: iconPhone({ size: 20 }), title: 'Works offline', description: 'Full functionality without a connection.' })`
|
|
35
|
+
)}
|
|
36
|
+
|
|
37
|
+
${demo(
|
|
38
|
+
`<div style="display:grid;grid-template-columns:repeat(3,1fr);gap:2rem">` +
|
|
39
|
+
feature({ icon: iconZap({ size: 20 }), title: 'Instant load', description: 'Streaming SSR ships HTML before data resolves. Fast by default.', center: true }) +
|
|
40
|
+
feature({ icon: iconLock({ size: 20 }), title: 'Private by default', description: 'No trackers. No third-party scripts. Ever.', center: true }) +
|
|
41
|
+
feature({ icon: iconPhone({ size: 20 }), title: 'Works offline', description: 'Full functionality without a connection.', center: true }) +
|
|
42
|
+
`</div>`,
|
|
43
|
+
`feature({ icon: iconZap({ size: 20 }), title: 'Instant load', description: '...', center: true })
|
|
44
|
+
feature({ icon: iconLock({ size: 20 }), title: 'Private by default', description: '...', center: true })
|
|
45
|
+
feature({ icon: iconPhone({ size: 20 }), title: 'Works offline', description: '...', center: true })`
|
|
46
|
+
)}
|
|
47
|
+
|
|
48
|
+
${table(
|
|
49
|
+
['Prop', 'Type', 'Default', ''],
|
|
50
|
+
[
|
|
51
|
+
['<code>icon</code>', 'string (HTML)', '—', 'Raw HTML slot — SVG or emoji; displayed in an accent-tinted box'],
|
|
52
|
+
['<code>title</code>', 'string', '—', ''],
|
|
53
|
+
['<code>level</code>', 'number', '3', 'Heading tag for the title (1–6). Visual style is unchanged.'],
|
|
54
|
+
['<code>description</code>', 'string', '—', ''],
|
|
55
|
+
['<code>center</code>', 'boolean', 'false', 'Centre-align the icon, title, and description'],
|
|
56
|
+
]
|
|
57
|
+
)}
|
|
58
|
+
`,
|
|
59
|
+
}),
|
|
60
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
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 { fieldset, input, textarea, grid, button } from '../../../../src/ui/index.js'
|
|
5
|
+
|
|
6
|
+
const { prev, next } = prevNext('/components/fieldset')
|
|
7
|
+
|
|
8
|
+
export default {
|
|
9
|
+
route: '/components/fieldset',
|
|
10
|
+
meta: {
|
|
11
|
+
title: 'Fieldset — Pulse Docs',
|
|
12
|
+
description: 'Semantic grouping of related form fields with an accessible legend.',
|
|
13
|
+
styles: ['/pulse-ui.css', '/docs.css'],
|
|
14
|
+
},
|
|
15
|
+
state: {},
|
|
16
|
+
view: () => renderComponentPage({
|
|
17
|
+
currentHref: '/components/fieldset',
|
|
18
|
+
prev,
|
|
19
|
+
next,
|
|
20
|
+
name: 'fieldset',
|
|
21
|
+
description: 'Wraps related fields in a semantic <code><fieldset></code> with a styled <code><legend></code>. Screen readers announce the legend when focus enters the group — use it whenever fields belong together (address, card details, contact info). Works naturally inside a <code><form data-action="..."></code>.',
|
|
22
|
+
content: `
|
|
23
|
+
${demo(
|
|
24
|
+
`<form class="u-flex u-flex-col u-gap-4">` +
|
|
25
|
+
fieldset({ legend: 'Your details', content:
|
|
26
|
+
grid({ cols: 2, gap: 'md', content:
|
|
27
|
+
input({ name: 'first', label: 'First name', required: true }) +
|
|
28
|
+
input({ name: 'last', label: 'Last name', required: true })
|
|
29
|
+
}) +
|
|
30
|
+
input({ name: 'email', label: 'Email address', type: 'email', required: true })
|
|
31
|
+
}) +
|
|
32
|
+
fieldset({ legend: 'Message', content:
|
|
33
|
+
textarea({ name: 'message', label: 'Tell us about your project', rows: 4, required: true })
|
|
34
|
+
}) +
|
|
35
|
+
button({ label: 'Send message', type: 'submit', variant: 'primary', fullWidth: true }) +
|
|
36
|
+
`</form>`,
|
|
37
|
+
`import { fieldset, grid, input, textarea, button } from '@invisibleloop/pulse/ui'
|
|
38
|
+
|
|
39
|
+
\`<form data-action="submit" class="u-flex u-flex-col u-gap-4">
|
|
40
|
+
\${fieldset({ legend: 'Your details', content: \`
|
|
41
|
+
\${grid({ cols: 2, gap: 'md', content: \`
|
|
42
|
+
\${input({ name: 'first', label: 'First name', required: true })}
|
|
43
|
+
\${input({ name: 'last', label: 'Last name', required: true })}
|
|
44
|
+
\` })}
|
|
45
|
+
\${input({ name: 'email', label: 'Email address', type: 'email', required: true })}
|
|
46
|
+
\` })}
|
|
47
|
+
\${fieldset({ legend: 'Message', content: \`
|
|
48
|
+
\${textarea({ name: 'message', label: 'Tell us about your project', rows: 4, required: true })}
|
|
49
|
+
\` })}
|
|
50
|
+
\${button({ label: 'Send message', type: 'submit', variant: 'primary', fullWidth: true })}
|
|
51
|
+
</form>\``
|
|
52
|
+
)}
|
|
53
|
+
|
|
54
|
+
${table(
|
|
55
|
+
['Prop', 'Type', 'Default', ''],
|
|
56
|
+
[
|
|
57
|
+
['<code>legend</code>', 'string', '—', 'Group label — rendered as <code><legend></code>, announced by screen readers on focus'],
|
|
58
|
+
['<code>content</code>', 'string', '—', 'Raw HTML slot — input(), select(), textarea(), grid(), etc.'],
|
|
59
|
+
['<code>gap</code>', 'string', 'md', 'Vertical gap between fields: xs / sm / md / lg'],
|
|
60
|
+
['<code>class</code>', 'string', '—', 'Extra classes on the <code><fieldset></code> element'],
|
|
61
|
+
]
|
|
62
|
+
)}
|
|
63
|
+
`,
|
|
64
|
+
}),
|
|
65
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
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 { fileUpload } from '../../../../src/ui/index.js'
|
|
5
|
+
|
|
6
|
+
const { prev, next } = prevNext('/components/file-upload')
|
|
7
|
+
|
|
8
|
+
export default {
|
|
9
|
+
route: '/components/file-upload',
|
|
10
|
+
meta: {
|
|
11
|
+
title: 'File Upload — Pulse Docs',
|
|
12
|
+
description: 'Drag-and-drop file upload zone component for Pulse UI.',
|
|
13
|
+
styles: ['/pulse-ui.css', '/docs.css'],
|
|
14
|
+
},
|
|
15
|
+
state: { fileName: '', fileSize: '' },
|
|
16
|
+
mutations: {
|
|
17
|
+
setFile: (state, e) => {
|
|
18
|
+
const file = e.target.files[0]
|
|
19
|
+
return file
|
|
20
|
+
? { fileName: file.name, fileSize: (file.size / 1024).toFixed(1) + ' KB' }
|
|
21
|
+
: { fileName: '', fileSize: '' }
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
view: (state) => renderComponentPage({
|
|
25
|
+
currentHref: '/components/file-upload',
|
|
26
|
+
prev,
|
|
27
|
+
next,
|
|
28
|
+
name: 'fileUpload',
|
|
29
|
+
description: 'Drag-and-drop upload zone with a hidden <code><input type="file"></code>. Clicking or dropping files opens the picker. Requires <code>pulse-ui.js</code> for drag highlighting and click-to-open behaviour.',
|
|
30
|
+
content: `
|
|
31
|
+
|
|
32
|
+
<h2 class="doc-h2" id="basic">Basic</h2>
|
|
33
|
+
${demo(
|
|
34
|
+
fileUpload({ name: 'file', label: 'Upload file', hint: 'PNG, JPG, PDF up to 10 MB', event: 'change:setFile' }) +
|
|
35
|
+
(state.fileName ? `<p class="u-mt-3 u-text-sm">Selected: <strong>${state.fileName}</strong> (${state.fileSize})</p>` : ''),
|
|
36
|
+
`fileUpload({ name: 'file', label: 'Upload file', hint: 'PNG, JPG, PDF up to 10 MB', event: 'change:setFile' })`,
|
|
37
|
+
{ col: true }
|
|
38
|
+
)}
|
|
39
|
+
|
|
40
|
+
<h2 class="doc-h2" id="accept">Accepted file types</h2>
|
|
41
|
+
${demo(
|
|
42
|
+
fileUpload({ name: 'avatar', label: 'Profile photo', accept: 'image/*', hint: 'PNG or JPG' }),
|
|
43
|
+
`fileUpload({ name: 'avatar', label: 'Profile photo', accept: 'image/*', hint: 'PNG or JPG' })`,
|
|
44
|
+
{ col: true }
|
|
45
|
+
)}
|
|
46
|
+
|
|
47
|
+
<h2 class="doc-h2" id="multiple">Multiple files</h2>
|
|
48
|
+
${demo(
|
|
49
|
+
fileUpload({ name: 'docs', label: 'Attachments', multiple: true, hint: 'Select one or more files' }),
|
|
50
|
+
`fileUpload({ name: 'docs', label: 'Attachments', multiple: true, hint: 'Select one or more files' })`,
|
|
51
|
+
{ col: true }
|
|
52
|
+
)}
|
|
53
|
+
|
|
54
|
+
<h2 class="doc-h2" id="error">Error state</h2>
|
|
55
|
+
${demo(
|
|
56
|
+
fileUpload({ name: 'file', label: 'Upload file', error: 'File must be under 10 MB' }),
|
|
57
|
+
`fileUpload({ name: 'file', label: 'Upload file', error: 'File must be under 10 MB' })`,
|
|
58
|
+
{ col: true }
|
|
59
|
+
)}
|
|
60
|
+
|
|
61
|
+
<h2 class="doc-h2" id="disabled">Disabled</h2>
|
|
62
|
+
${demo(
|
|
63
|
+
fileUpload({ name: 'file', label: 'Upload file', disabled: true, hint: 'Uploads are currently unavailable' }),
|
|
64
|
+
`fileUpload({ name: 'file', label: 'Upload file', disabled: true, hint: 'Uploads are currently unavailable' })`,
|
|
65
|
+
{ col: true }
|
|
66
|
+
)}
|
|
67
|
+
|
|
68
|
+
<h2 class="doc-h2" id="with-action">Reading the file in an action</h2>
|
|
69
|
+
<p class="u-mb-4 u-text-sm u-color-muted">Use the component inside a <code><form data-action="..."></code>. The file is available in FormData under <code>name</code>.</p>
|
|
70
|
+
${demo(
|
|
71
|
+
fileUpload({ name: 'attachment', label: 'Attachment', accept: '.pdf,.doc,.docx', required: true }),
|
|
72
|
+
`// view
|
|
73
|
+
\`<form data-action="upload" class="u-flex u-flex-col u-gap-4">
|
|
74
|
+
\${fileUpload({ name: 'attachment', label: 'Attachment', accept: '.pdf,.doc,.docx', required: true })}
|
|
75
|
+
\${button({ label: 'Upload', type: 'submit', variant: 'primary' })}
|
|
76
|
+
</form>\`
|
|
77
|
+
|
|
78
|
+
// action
|
|
79
|
+
upload: {
|
|
80
|
+
onStart: (state) => ({ status: 'loading' }),
|
|
81
|
+
run: async (state, serverState, formData) => {
|
|
82
|
+
const file = formData.get('attachment') // File object
|
|
83
|
+
// process file...
|
|
84
|
+
},
|
|
85
|
+
onSuccess: (state) => ({ status: 'success' }),
|
|
86
|
+
onError: (state, err) => ({ status: 'error', message: err.message }),
|
|
87
|
+
}`,
|
|
88
|
+
{ col: true }
|
|
89
|
+
)}
|
|
90
|
+
|
|
91
|
+
<h2 class="doc-h2" id="with-mutation">Live file name via mutation</h2>
|
|
92
|
+
<p class="u-mb-4 u-text-sm u-color-muted">Use <code>event: 'change:mutationName'</code> to capture the selected filename in state immediately — without a form submission.</p>
|
|
93
|
+
${demo(
|
|
94
|
+
fileUpload({ name: 'photo', label: 'Photo', event: 'change:setFile', accept: 'image/*' }),
|
|
95
|
+
`// state
|
|
96
|
+
{ fileName: '' }
|
|
97
|
+
|
|
98
|
+
// mutation — e.target.files[0] gives the File object
|
|
99
|
+
setFile: (state, e) => ({ fileName: e.target.files[0]?.name ?? '' })
|
|
100
|
+
|
|
101
|
+
// view
|
|
102
|
+
fileUpload({ name: 'photo', label: 'Photo', accept: 'image/*', event: 'change:setFile' })
|
|
103
|
+
\${state.fileName ? \`<p class="u-mt-2 u-text-sm">\${state.fileName}</p>\` : ''}`,
|
|
104
|
+
{ col: true }
|
|
105
|
+
)}
|
|
106
|
+
|
|
107
|
+
${callout('note', 'The file object itself cannot be stored in Pulse state — state must be serialisable. Store the filename or a preview URL (via <code>URL.createObjectURL</code>) instead, and upload the file in an action via FormData.')}
|
|
108
|
+
|
|
109
|
+
${table(
|
|
110
|
+
['Prop', 'Type', 'Default', ''],
|
|
111
|
+
[
|
|
112
|
+
['<code>name</code>', 'string', '—', 'Field name — file available in FormData under this key'],
|
|
113
|
+
['<code>label</code>', 'string', '—', 'Visible label text'],
|
|
114
|
+
['<code>hint</code>', 'string', '—', 'Helper text, e.g. accepted types and max size'],
|
|
115
|
+
['<code>error</code>', 'string', '—', 'Validation error message'],
|
|
116
|
+
['<code>accept</code>', 'string', '—', 'Accepted MIME types or extensions, e.g. <code>image/*</code> or <code>.pdf,.docx</code>'],
|
|
117
|
+
['<code>multiple</code>', 'boolean', 'false', 'Allow selecting multiple files'],
|
|
118
|
+
['<code>required</code>', 'boolean', 'false', ''],
|
|
119
|
+
['<code>disabled</code>', 'boolean', 'false', ''],
|
|
120
|
+
['<code>event</code>', 'string', '—', 'data-event on the input — use <code>change:mutationName</code> to capture file selection in state'],
|
|
121
|
+
['<code>id</code>', 'string', '—', 'Override the generated <code>id</code>'],
|
|
122
|
+
['<code>class</code>', 'string', '—', ''],
|
|
123
|
+
]
|
|
124
|
+
)}
|
|
125
|
+
`,
|
|
126
|
+
}),
|
|
127
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
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 { footer } from '../../../../src/ui/index.js'
|
|
5
|
+
|
|
6
|
+
const { prev, next } = prevNext('/components/footer')
|
|
7
|
+
|
|
8
|
+
export default {
|
|
9
|
+
route: '/components/footer',
|
|
10
|
+
meta: {
|
|
11
|
+
title: 'Footer — Pulse Docs',
|
|
12
|
+
description: 'Accessible site footer with logo slot, navigation links, and legal text.',
|
|
13
|
+
styles: ['/pulse-ui.css', '/docs.css'],
|
|
14
|
+
},
|
|
15
|
+
state: {},
|
|
16
|
+
view: () => renderComponentPage({
|
|
17
|
+
currentHref: '/components/footer',
|
|
18
|
+
prev,
|
|
19
|
+
next,
|
|
20
|
+
name: 'footer',
|
|
21
|
+
description: 'Accessible site footer with logo slot, navigation links, and legal text. Handles responsive stacking, hover states, and focus styles automatically.',
|
|
22
|
+
content: `
|
|
23
|
+
${demo(
|
|
24
|
+
footer({
|
|
25
|
+
logo: 'MyApp',
|
|
26
|
+
logoHref: '/',
|
|
27
|
+
links: [
|
|
28
|
+
{ label: 'Docs', href: '/docs' },
|
|
29
|
+
{ label: 'Pricing', href: '/pricing' },
|
|
30
|
+
{ label: 'Blog', href: '/blog' },
|
|
31
|
+
{ label: 'GitHub', href: 'https://github.com' },
|
|
32
|
+
],
|
|
33
|
+
legal: '© 2026 MyApp Ltd.',
|
|
34
|
+
}),
|
|
35
|
+
`footer({
|
|
36
|
+
logo: 'MyApp',
|
|
37
|
+
logoHref: '/',
|
|
38
|
+
links: [
|
|
39
|
+
{ label: 'Docs', href: '/docs' },
|
|
40
|
+
{ label: 'Pricing', href: '/pricing' },
|
|
41
|
+
{ label: 'GitHub', href: 'https://github.com' },
|
|
42
|
+
],
|
|
43
|
+
legal: '© 2026 MyApp Ltd.',
|
|
44
|
+
})`
|
|
45
|
+
)}
|
|
46
|
+
|
|
47
|
+
${table(
|
|
48
|
+
['Prop', 'Type', 'Default', ''],
|
|
49
|
+
[
|
|
50
|
+
['<code>logo</code>', 'string (HTML)', '—', 'Raw HTML slot — SVG, img, or text'],
|
|
51
|
+
['<code>logoHref</code>', 'string', "'/'", 'Logo link destination'],
|
|
52
|
+
['<code>links</code>', 'array', '[]', '[{label, href}] — footer navigation links'],
|
|
53
|
+
['<code>legal</code>', 'string', '—', 'Copyright / legal text'],
|
|
54
|
+
]
|
|
55
|
+
)}
|
|
56
|
+
`,
|
|
57
|
+
}),
|
|
58
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
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 { grid, card, stat, feature, testimonial } from '../../../../src/ui/index.js'
|
|
5
|
+
|
|
6
|
+
const { prev, next } = prevNext('/components/grid')
|
|
7
|
+
|
|
8
|
+
// Reusable placeholder item
|
|
9
|
+
const item = (label) =>
|
|
10
|
+
`<div style="background:var(--surface-2);border:1px solid var(--border);border-radius:6px;padding:1.25rem;text-align:center;color:var(--muted)">${label}</div>`
|
|
11
|
+
|
|
12
|
+
export default {
|
|
13
|
+
route: '/components/grid',
|
|
14
|
+
meta: {
|
|
15
|
+
title: 'Grid — Pulse Docs',
|
|
16
|
+
description: 'Grid component for Pulse UI.',
|
|
17
|
+
styles: ['/pulse-ui.css', '/docs.css'],
|
|
18
|
+
},
|
|
19
|
+
state: {},
|
|
20
|
+
view: () => renderComponentPage({
|
|
21
|
+
currentHref: '/components/grid',
|
|
22
|
+
prev,
|
|
23
|
+
next,
|
|
24
|
+
name: 'grid',
|
|
25
|
+
description: 'Responsive CSS grid. Collapses to a single column on mobile. Direct children of the content slot become grid items — no wrapper needed.',
|
|
26
|
+
content: `
|
|
27
|
+
<h2 class="doc-h2" id="cols">Column counts</h2>
|
|
28
|
+
<p>Use <code>cols</code> to set the number of columns. All layouts collapse to one column on mobile.</p>
|
|
29
|
+
|
|
30
|
+
<h3 class="doc-h3">2 columns</h3>
|
|
31
|
+
${demo(
|
|
32
|
+
grid({ cols: 2, content: item('Left') + item('Right') }),
|
|
33
|
+
`grid({ cols: 2, content: left + right })`
|
|
34
|
+
)}
|
|
35
|
+
|
|
36
|
+
<h3 class="doc-h3">3 columns (default)</h3>
|
|
37
|
+
${demo(
|
|
38
|
+
grid({ cols: 3, content: item('One') + item('Two') + item('Three') }),
|
|
39
|
+
`grid({ cols: 3, content: items.join('') })`
|
|
40
|
+
)}
|
|
41
|
+
|
|
42
|
+
<h3 class="doc-h3">4 columns</h3>
|
|
43
|
+
${demo(
|
|
44
|
+
grid({ cols: 4, content: item('A') + item('B') + item('C') + item('D') }),
|
|
45
|
+
`grid({ cols: 4, content: items.join('') })`
|
|
46
|
+
)}
|
|
47
|
+
|
|
48
|
+
<h2 class="doc-h2" id="gap">Gap sizes</h2>
|
|
49
|
+
<p>Control spacing between items with <code>gap: 'sm' | 'md' | 'lg'</code>. Default is <code>'md'</code>.</p>
|
|
50
|
+
|
|
51
|
+
<h3 class="doc-h3">Small gap</h3>
|
|
52
|
+
${demo(
|
|
53
|
+
grid({ cols: 3, gap: 'sm', content: item('One') + item('Two') + item('Three') }),
|
|
54
|
+
`grid({ cols: 3, gap: 'sm', content: items.join('') })`
|
|
55
|
+
)}
|
|
56
|
+
|
|
57
|
+
<h3 class="doc-h3">Large gap</h3>
|
|
58
|
+
${demo(
|
|
59
|
+
grid({ cols: 3, gap: 'lg', content: item('One') + item('Two') + item('Three') }),
|
|
60
|
+
`grid({ cols: 3, gap: 'lg', content: items.join('') })`
|
|
61
|
+
)}
|
|
62
|
+
|
|
63
|
+
<h2 class="doc-h2" id="with-components">With components</h2>
|
|
64
|
+
<p>Grid accepts any HTML string as content — pass other component outputs directly.</p>
|
|
65
|
+
|
|
66
|
+
<h3 class="doc-h3">Feature grid</h3>
|
|
67
|
+
${demo(
|
|
68
|
+
grid({
|
|
69
|
+
cols: 3,
|
|
70
|
+
content: [
|
|
71
|
+
feature({ icon: '⚡', title: 'Fast', description: 'Sub-100ms server responses with streaming SSR.' }),
|
|
72
|
+
feature({ icon: '🔒', title: 'Secure', description: 'Security headers on every response, including errors.' }),
|
|
73
|
+
feature({ icon: '🎯', title: 'Simple', description: 'No build step, no virtual DOM, no dependencies.' }),
|
|
74
|
+
].join(''),
|
|
75
|
+
}),
|
|
76
|
+
`grid({
|
|
77
|
+
cols: 3,
|
|
78
|
+
content: features.map(f => feature(f)).join(''),
|
|
79
|
+
})`
|
|
80
|
+
)}
|
|
81
|
+
|
|
82
|
+
<h3 class="doc-h3">Stat grid</h3>
|
|
83
|
+
${demo(
|
|
84
|
+
grid({
|
|
85
|
+
cols: 4,
|
|
86
|
+
content: [
|
|
87
|
+
stat({ label: 'Monthly users', value: '24.8k', change: '+12%', trend: 'up' }),
|
|
88
|
+
stat({ label: 'Revenue', value: '$18.2k', change: '+8.4%', trend: 'up' }),
|
|
89
|
+
stat({ label: 'Churn rate', value: '2.1%', change: '−0.3%', trend: 'down' }),
|
|
90
|
+
stat({ label: 'Uptime', value: '99.98%', change: '0.0%', trend: 'neutral' }),
|
|
91
|
+
].join(''),
|
|
92
|
+
}),
|
|
93
|
+
`grid({
|
|
94
|
+
cols: 4,
|
|
95
|
+
content: stats.map(s => stat(s)).join(''),
|
|
96
|
+
})`
|
|
97
|
+
)}
|
|
98
|
+
|
|
99
|
+
<h3 class="doc-h3">Testimonial grid</h3>
|
|
100
|
+
${demo(
|
|
101
|
+
grid({
|
|
102
|
+
cols: 3,
|
|
103
|
+
content: [
|
|
104
|
+
testimonial({ quote: 'Shipped our redesign in a weekend. No boilerplate, no config hell.', name: 'Alex Morgan', role: 'CTO at Launchpad', rating: 5 }),
|
|
105
|
+
testimonial({ quote: 'The streaming SSR makes our pages feel instant. Lighthouse is happy.', name: 'Sara Kim', role: 'Lead Engineer, Orbit', rating: 5 }),
|
|
106
|
+
testimonial({ quote: 'Finally a UI kit that doesn\'t fight the platform. Just HTML.', name: 'Dan Okafor', role: 'Founder, Stackly', rating: 5 }),
|
|
107
|
+
].join(''),
|
|
108
|
+
}),
|
|
109
|
+
`grid({
|
|
110
|
+
cols: 3,
|
|
111
|
+
content: testimonials.map(t => testimonial(t)).join(''),
|
|
112
|
+
})`
|
|
113
|
+
)}
|
|
114
|
+
|
|
115
|
+
<h3 class="doc-h3">Card grid</h3>
|
|
116
|
+
${demo(
|
|
117
|
+
grid({
|
|
118
|
+
cols: 3,
|
|
119
|
+
gap: 'md',
|
|
120
|
+
content: [
|
|
121
|
+
card({
|
|
122
|
+
title: 'Getting started',
|
|
123
|
+
content: `<p style="color:var(--muted);margin:0">Install and run your first Pulse app in under five minutes.</p>`,
|
|
124
|
+
footer: `<span class="ui-badge">Guide</span>`,
|
|
125
|
+
}),
|
|
126
|
+
card({
|
|
127
|
+
title: 'Components',
|
|
128
|
+
content: `<p style="color:var(--muted);margin:0">30+ accessible, composable UI primitives ready to drop in.</p>`,
|
|
129
|
+
footer: `<span class="ui-badge">Reference</span>`,
|
|
130
|
+
}),
|
|
131
|
+
card({
|
|
132
|
+
title: 'Deployment',
|
|
133
|
+
content: `<p style="color:var(--muted);margin:0">Static hosting, Node servers, or edge runtimes — one build.</p>`,
|
|
134
|
+
footer: `<span class="ui-badge">Deploy</span>`,
|
|
135
|
+
}),
|
|
136
|
+
].join(''),
|
|
137
|
+
}),
|
|
138
|
+
`grid({
|
|
139
|
+
cols: 3,
|
|
140
|
+
content: docs.map(d => card({
|
|
141
|
+
title: d.title,
|
|
142
|
+
content: \`<p>\${d.summary}</p>\`,
|
|
143
|
+
footer: badge({ label: d.tag }),
|
|
144
|
+
})).join(''),
|
|
145
|
+
})`
|
|
146
|
+
)}
|
|
147
|
+
|
|
148
|
+
<h2 class="doc-h2" id="1col">Single column</h2>
|
|
149
|
+
<p>Use <code>cols: 1</code> for a stacked list with consistent spacing — useful for form sections or timelines.</p>
|
|
150
|
+
${demo(
|
|
151
|
+
grid({ cols: 1, gap: 'sm', content: item('Step one') + item('Step two') + item('Step three') }),
|
|
152
|
+
`grid({ cols: 1, gap: 'sm', content: steps.join('') })`
|
|
153
|
+
)}
|
|
154
|
+
|
|
155
|
+
${table(
|
|
156
|
+
['Prop', 'Type', 'Default', ''],
|
|
157
|
+
[
|
|
158
|
+
['<code>content</code>', 'string (HTML)', '—', 'Raw HTML slot — direct children are grid items'],
|
|
159
|
+
['<code>cols</code>', 'number', '3', '1 · 2 · 3 · 4'],
|
|
160
|
+
['<code>gap</code>', 'string', "'md'", "'sm' · 'md' · 'lg'"],
|
|
161
|
+
]
|
|
162
|
+
)}
|
|
163
|
+
`,
|
|
164
|
+
}),
|
|
165
|
+
}
|