@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,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pulse UI — Container
|
|
3
|
+
*
|
|
4
|
+
* Max-width wrapper with horizontal padding. Every page section needs one.
|
|
5
|
+
*
|
|
6
|
+
* @param {object} opts
|
|
7
|
+
* @param {string} opts.content - Raw HTML slot
|
|
8
|
+
* @param {'sm'|'md'|'lg'|'xl'} opts.size - Max-width preset (default: 'lg')
|
|
9
|
+
* @param {string} opts.class
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { escHtml as e } from '../html.js'
|
|
13
|
+
|
|
14
|
+
const SIZES = new Set(['sm', 'md', 'lg', 'xl'])
|
|
15
|
+
|
|
16
|
+
export function container({
|
|
17
|
+
content = '',
|
|
18
|
+
size = 'lg',
|
|
19
|
+
class: cls = '',
|
|
20
|
+
} = {}) {
|
|
21
|
+
if (!SIZES.has(size)) size = 'lg'
|
|
22
|
+
const classes = ['ui-container', `ui-container--${size}`, cls].filter(Boolean).join(' ')
|
|
23
|
+
return `<div class="${e(classes)}">${content}</div>`
|
|
24
|
+
}
|
package/src/ui/cta.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pulse UI — CTA (Call to Action)
|
|
3
|
+
*
|
|
4
|
+
* Centred block with eyebrow, large heading, body text, and an actions slot.
|
|
5
|
+
* Sits inside a section() + container() — does not add its own padding.
|
|
6
|
+
*
|
|
7
|
+
* @param {object} opts
|
|
8
|
+
* @param {string} opts.eyebrow - Small label above the heading
|
|
9
|
+
* @param {string} opts.title - Main heading
|
|
10
|
+
* @param {number} opts.level - Heading level 1–6 (default 2). Visual style is always ui-cta-title.
|
|
11
|
+
* @param {string} opts.subtitle - Supporting paragraph
|
|
12
|
+
* @param {string} opts.actions - Raw HTML slot — typically button() calls
|
|
13
|
+
* @param {'center'|'left'} opts.align - Text alignment (default: 'center')
|
|
14
|
+
* @param {string} opts.class
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { escHtml as e } from '../html.js'
|
|
18
|
+
|
|
19
|
+
export function cta({
|
|
20
|
+
eyebrow = '',
|
|
21
|
+
title = '',
|
|
22
|
+
level = 2,
|
|
23
|
+
subtitle = '',
|
|
24
|
+
actions = '',
|
|
25
|
+
align = 'center',
|
|
26
|
+
class: cls = '',
|
|
27
|
+
} = {}) {
|
|
28
|
+
const classes = ['ui-cta', align === 'left' && 'ui-cta--left', cls].filter(Boolean).join(' ')
|
|
29
|
+
const tag = `h${Math.min(Math.max(Math.floor(level), 1), 6)}`
|
|
30
|
+
|
|
31
|
+
return `<div class="${e(classes)}">
|
|
32
|
+
${eyebrow ? `<p class="ui-cta-eyebrow">${e(eyebrow)}</p>` : ''}
|
|
33
|
+
${title ? `<${tag} class="ui-cta-title">${e(title)}</${tag}>` : ''}
|
|
34
|
+
${subtitle ? `<p class="ui-cta-subtitle">${e(subtitle)}</p>` : ''}
|
|
35
|
+
${actions ? `<div class="ui-cta-actions">${actions}</div>` : ''}
|
|
36
|
+
</div>`
|
|
37
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pulse UI — Divider
|
|
3
|
+
*
|
|
4
|
+
* Horizontal rule with an optional centred label.
|
|
5
|
+
* With a label, the line splits either side of the text.
|
|
6
|
+
*
|
|
7
|
+
* @param {object} opts
|
|
8
|
+
* @param {string} opts.label - Optional centred text (e.g. "or", "continue with")
|
|
9
|
+
* @param {string} opts.class
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { escHtml as e } from '../html.js'
|
|
13
|
+
|
|
14
|
+
export function divider({
|
|
15
|
+
label = '',
|
|
16
|
+
class: cls = '',
|
|
17
|
+
} = {}) {
|
|
18
|
+
const classes = ['ui-divider', label && 'ui-divider--label', cls].filter(Boolean).join(' ')
|
|
19
|
+
|
|
20
|
+
if (label) {
|
|
21
|
+
return `<div class="${e(classes)}" role="separator" aria-label="${e(label)}">
|
|
22
|
+
<span class="ui-divider-line" aria-hidden="true"></span>
|
|
23
|
+
<span class="ui-divider-text">${e(label)}</span>
|
|
24
|
+
<span class="ui-divider-line" aria-hidden="true"></span>
|
|
25
|
+
</div>`
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return `<hr class="${e(classes)}">`
|
|
29
|
+
}
|
package/src/ui/empty.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pulse UI — Empty state
|
|
3
|
+
*
|
|
4
|
+
* Shown when a list or section has no content yet.
|
|
5
|
+
*
|
|
6
|
+
* @param {object} opts
|
|
7
|
+
* @param {string} opts.title - Primary message
|
|
8
|
+
* @param {string} opts.description - Supporting text (optional)
|
|
9
|
+
* @param {object} opts.action - { label, href, variant } — renders a button (optional)
|
|
10
|
+
* @param {string} opts.class
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { escHtml as e } from '../html.js'
|
|
14
|
+
import { button } from './button.js'
|
|
15
|
+
|
|
16
|
+
export function empty({
|
|
17
|
+
title = 'Nothing here yet',
|
|
18
|
+
description = '',
|
|
19
|
+
action = null,
|
|
20
|
+
class: cls = '',
|
|
21
|
+
} = {}) {
|
|
22
|
+
const classes = ['ui-empty', cls].filter(Boolean).join(' ')
|
|
23
|
+
|
|
24
|
+
const actionHtml = action
|
|
25
|
+
? button({ label: action.label, href: action.href, variant: action.variant || 'secondary' })
|
|
26
|
+
: ''
|
|
27
|
+
|
|
28
|
+
return `<div class="${e(classes)}">
|
|
29
|
+
<p class="ui-empty-title">${e(title)}</p>
|
|
30
|
+
${description ? `<p class="ui-empty-desc">${e(description)}</p>` : ''}
|
|
31
|
+
${actionHtml}
|
|
32
|
+
</div>`
|
|
33
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pulse UI — Feature
|
|
3
|
+
*
|
|
4
|
+
* Icon + title + description block. The standard "why us" grid card.
|
|
5
|
+
*
|
|
6
|
+
* @param {object} opts
|
|
7
|
+
* @param {string} opts.icon - Raw HTML slot — SVG or emoji; displayed in an accent-tinted box
|
|
8
|
+
* @param {string} opts.title
|
|
9
|
+
* @param {number} opts.level - Heading level 1–6 (default 3). Visual style is always ui-feature-title.
|
|
10
|
+
* @param {string} opts.description
|
|
11
|
+
* @param {boolean} opts.center - Centre-align icon, title, and description
|
|
12
|
+
* @param {string} opts.class
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { escHtml as e } from '../html.js'
|
|
16
|
+
|
|
17
|
+
export function feature({
|
|
18
|
+
icon = '',
|
|
19
|
+
title = '',
|
|
20
|
+
level = 3,
|
|
21
|
+
description = '',
|
|
22
|
+
center = false,
|
|
23
|
+
class: cls = '',
|
|
24
|
+
} = {}) {
|
|
25
|
+
const classes = ['ui-feature', center && 'ui-feature--center', cls].filter(Boolean).join(' ')
|
|
26
|
+
const tag = `h${Math.min(Math.max(Math.floor(level), 1), 6)}`
|
|
27
|
+
|
|
28
|
+
return `<div class="${e(classes)}">
|
|
29
|
+
${icon ? `<div class="ui-feature-icon" aria-hidden="true">${icon}</div>` : ''}
|
|
30
|
+
${title ? `<${tag} class="ui-feature-title">${e(title)}</${tag}>` : ''}
|
|
31
|
+
${description ? `<p class="ui-feature-desc">${e(description)}</p>` : ''}
|
|
32
|
+
</div>`
|
|
33
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pulse UI — Fieldset
|
|
3
|
+
*
|
|
4
|
+
* Semantic grouping of related form fields. Renders a <fieldset> with an
|
|
5
|
+
* accessible <legend>, styled to match the design system. Use inside a
|
|
6
|
+
* card() or directly inside a <form>.
|
|
7
|
+
*
|
|
8
|
+
* @param {object} opts
|
|
9
|
+
* @param {string} opts.legend - Group label (renders as <legend>)
|
|
10
|
+
* @param {string} opts.content - Raw HTML slot — input(), select(), grid(), etc.
|
|
11
|
+
* @param {'xs'|'sm'|'md'|'lg'} opts.gap - Gap between fields (default: 'md')
|
|
12
|
+
* @param {string} opts.class
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { escHtml as e } from '../html.js'
|
|
16
|
+
|
|
17
|
+
const GAPS = new Set(['xs', 'sm', 'md', 'lg'])
|
|
18
|
+
|
|
19
|
+
export function fieldset({
|
|
20
|
+
legend = '',
|
|
21
|
+
content = '',
|
|
22
|
+
gap = 'md',
|
|
23
|
+
class: cls = '',
|
|
24
|
+
} = {}) {
|
|
25
|
+
if (!GAPS.has(gap)) gap = 'md'
|
|
26
|
+
|
|
27
|
+
const classes = [
|
|
28
|
+
'ui-fieldset',
|
|
29
|
+
gap !== 'md' && `ui-fieldset--gap-${gap}`,
|
|
30
|
+
cls,
|
|
31
|
+
].filter(Boolean).join(' ')
|
|
32
|
+
|
|
33
|
+
return `<fieldset class="${e(classes)}">
|
|
34
|
+
${legend ? `<legend class="ui-fieldset-legend">${e(legend)}</legend>` : ''}
|
|
35
|
+
<div class="ui-fieldset-body">${content}</div>
|
|
36
|
+
</fieldset>`
|
|
37
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pulse UI — File Upload
|
|
3
|
+
*
|
|
4
|
+
* Drag-and-drop file upload zone with a hidden <input type="file">.
|
|
5
|
+
* Clicking the zone opens the file picker. Dragging files over the zone
|
|
6
|
+
* highlights it; dropping triggers the native change event so Pulse can
|
|
7
|
+
* bind a mutation or action via data-event / data-action.
|
|
8
|
+
*
|
|
9
|
+
* The component is fully CSS-driven — no pulse-ui.js required.
|
|
10
|
+
* The drag highlight is handled by a small inline script on the zone element.
|
|
11
|
+
*
|
|
12
|
+
* Submitted in FormData under `name` — works inside <form data-action="...">.
|
|
13
|
+
*
|
|
14
|
+
* @param {object} opts
|
|
15
|
+
* @param {string} opts.name - Field name (submitted in FormData)
|
|
16
|
+
* @param {string} opts.label - Visible label text
|
|
17
|
+
* @param {string} opts.hint - Helper text (e.g. 'PNG, JPG up to 5 MB')
|
|
18
|
+
* @param {string} opts.error - Validation error message
|
|
19
|
+
* @param {string} opts.accept - Accepted MIME types or extensions, e.g. 'image/*'
|
|
20
|
+
* @param {boolean} opts.multiple - Allow multiple files
|
|
21
|
+
* @param {boolean} opts.required
|
|
22
|
+
* @param {boolean} opts.disabled
|
|
23
|
+
* @param {string} opts.id - Override generated id
|
|
24
|
+
* @param {string} opts.event - data-event binding on the <input>, e.g. 'change:fileSelected'
|
|
25
|
+
* @param {string} opts.class
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import { escHtml as e } from '../html.js'
|
|
29
|
+
import { iconUpload } from './icons.js'
|
|
30
|
+
|
|
31
|
+
export function fileUpload({
|
|
32
|
+
name = '',
|
|
33
|
+
label = '',
|
|
34
|
+
hint = '',
|
|
35
|
+
error = '',
|
|
36
|
+
accept = '',
|
|
37
|
+
multiple = false,
|
|
38
|
+
required = false,
|
|
39
|
+
disabled = false,
|
|
40
|
+
id = '',
|
|
41
|
+
event = '',
|
|
42
|
+
class: cls = '',
|
|
43
|
+
} = {}) {
|
|
44
|
+
const fieldId = e(id || `field-${name}`)
|
|
45
|
+
const errorId = `${fieldId}-error`
|
|
46
|
+
const hintId = `${fieldId}-hint`
|
|
47
|
+
const described = [error ? errorId : '', hint ? hintId : ''].filter(Boolean).join(' ')
|
|
48
|
+
|
|
49
|
+
const wrapClasses = ['ui-field', error ? 'ui-field--error' : '', cls].filter(Boolean).join(' ')
|
|
50
|
+
const zoneClasses = ['ui-upload', disabled ? 'ui-upload--disabled' : '', error ? 'ui-upload--error' : ''].filter(Boolean).join(' ')
|
|
51
|
+
|
|
52
|
+
const labelHtml = label
|
|
53
|
+
? `<label for="${fieldId}" class="ui-label">${e(label)}${required ? ' <span class="ui-required" aria-hidden="true">*</span>' : ''}</label>`
|
|
54
|
+
: ''
|
|
55
|
+
|
|
56
|
+
const hintHtml = hint ? `<p id="${hintId}" class="ui-hint">${e(hint)}</p>` : ''
|
|
57
|
+
const errorHtml = error ? `<p id="${errorId}" class="ui-error" role="alert">${e(error)}</p>` : ''
|
|
58
|
+
|
|
59
|
+
const icon = iconUpload({ size: 24, class: 'ui-upload-icon' })
|
|
60
|
+
|
|
61
|
+
// All drag/click/keyboard behaviour is handled by pulse-ui.js via event delegation.
|
|
62
|
+
// No inline handlers needed (and they'd be blocked by CSP anyway).
|
|
63
|
+
|
|
64
|
+
return `<div class="${e(wrapClasses)}">
|
|
65
|
+
${labelHtml}
|
|
66
|
+
<div class="${e(zoneClasses)}" role="button" tabindex="${disabled ? '-1' : '0'}" aria-label="Upload file"
|
|
67
|
+
>
|
|
68
|
+
<div class="ui-upload-body">
|
|
69
|
+
${icon}
|
|
70
|
+
<span class="ui-upload-text">Drag & drop or <span class="ui-upload-browse">browse</span></span>
|
|
71
|
+
</div>
|
|
72
|
+
<input
|
|
73
|
+
type="file"
|
|
74
|
+
id="${fieldId}"
|
|
75
|
+
name="${e(name)}"
|
|
76
|
+
class="ui-upload-input"
|
|
77
|
+
${accept ? `accept="${e(accept)}"` : ''}
|
|
78
|
+
${multiple ? 'multiple' : ''}
|
|
79
|
+
${required ? 'required aria-required="true"' : ''}
|
|
80
|
+
${disabled ? 'disabled' : ''}
|
|
81
|
+
${event ? `data-event="${e(event)}"` : ''}
|
|
82
|
+
${described ? `aria-describedby="${described}"` : ''}
|
|
83
|
+
${error ? 'aria-invalid="true"' : ''}
|
|
84
|
+
>
|
|
85
|
+
</div>
|
|
86
|
+
${hintHtml}
|
|
87
|
+
${errorHtml}
|
|
88
|
+
</div>`
|
|
89
|
+
}
|
package/src/ui/footer.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pulse UI — Footer
|
|
3
|
+
*
|
|
4
|
+
* Site footer with logo slot, navigation links, and optional legal text.
|
|
5
|
+
* Handles accessibility (landmark role, aria-label on nav) and responsive
|
|
6
|
+
* stacking automatically.
|
|
7
|
+
*
|
|
8
|
+
* @param {object} opts
|
|
9
|
+
* @param {string} opts.logo - Raw HTML slot — SVG, img, or text
|
|
10
|
+
* @param {string} opts.logoHref - Logo link destination (default: '/')
|
|
11
|
+
* @param {Array<{label: string, href: string}>} opts.links - Footer nav links
|
|
12
|
+
* @param {string} opts.legal - Copyright / legal text (e.g. '© 2026 Acme Ltd.')
|
|
13
|
+
* @param {string} opts.class
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { escHtml as e } from '../html.js'
|
|
17
|
+
|
|
18
|
+
export function footer({
|
|
19
|
+
logo = '',
|
|
20
|
+
logoHref = '/',
|
|
21
|
+
links = [],
|
|
22
|
+
legal = '',
|
|
23
|
+
class: cls = '',
|
|
24
|
+
} = {}) {
|
|
25
|
+
const classes = ['ui-footer', cls].filter(Boolean).join(' ')
|
|
26
|
+
|
|
27
|
+
const linksHtml = links.map(({ label = '', href = '' }) =>
|
|
28
|
+
`<a href="${e(href)}" class="ui-footer-link">${e(label)}</a>`
|
|
29
|
+
).join('')
|
|
30
|
+
|
|
31
|
+
return `<footer class="${e(classes)}">
|
|
32
|
+
<div class="ui-footer-inner">
|
|
33
|
+
${logo ? `<a href="${e(logoHref)}" class="ui-footer-logo">${logo}</a>` : ''}
|
|
34
|
+
${links.length ? `<nav class="ui-footer-links" aria-label="Footer navigation">${linksHtml}</nav>` : ''}
|
|
35
|
+
${legal ? `<p class="ui-footer-legal">${e(legal)}</p>` : ''}
|
|
36
|
+
</div>
|
|
37
|
+
</footer>`
|
|
38
|
+
}
|
package/src/ui/grid.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pulse UI — Grid
|
|
3
|
+
*
|
|
4
|
+
* Responsive CSS grid. Direct children become grid items.
|
|
5
|
+
* Collapses to a single column on mobile by default.
|
|
6
|
+
*
|
|
7
|
+
* @param {object} opts
|
|
8
|
+
* @param {string} opts.content - Raw HTML slot — direct children are grid items
|
|
9
|
+
* @param {1|2|3|4} opts.cols - Number of columns (default: 3)
|
|
10
|
+
* @param {'sm'|'md'|'lg'} opts.gap - Gap between items (default: 'md')
|
|
11
|
+
* @param {string} opts.class
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { escHtml as e } from '../html.js'
|
|
15
|
+
|
|
16
|
+
const VALID_COLS = new Set([1, 2, 3, 4])
|
|
17
|
+
const GAPS = new Set(['sm', 'md', 'lg'])
|
|
18
|
+
|
|
19
|
+
export function grid({
|
|
20
|
+
content = '',
|
|
21
|
+
cols = 3,
|
|
22
|
+
gap = 'md',
|
|
23
|
+
class: cls = '',
|
|
24
|
+
} = {}) {
|
|
25
|
+
if (!VALID_COLS.has(cols)) cols = 3
|
|
26
|
+
if (!GAPS.has(gap)) gap = 'md'
|
|
27
|
+
|
|
28
|
+
const classes = [
|
|
29
|
+
'ui-grid',
|
|
30
|
+
`ui-grid--cols-${cols}`,
|
|
31
|
+
gap !== 'md' && `ui-grid--gap-${gap}`,
|
|
32
|
+
cls,
|
|
33
|
+
].filter(Boolean).join(' ')
|
|
34
|
+
|
|
35
|
+
return `<div class="${e(classes)}">${content}</div>`
|
|
36
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pulse UI — Heading
|
|
3
|
+
*
|
|
4
|
+
* Styled heading with the correct semantic tag. Use this instead of raw <h1>–<h6>
|
|
5
|
+
* to get consistent typography tokens without needing to remember utility classes.
|
|
6
|
+
*
|
|
7
|
+
* Does NOT add margin — use u-mt-* / u-mb-* utility classes for spacing.
|
|
8
|
+
*
|
|
9
|
+
* @param {object} opts
|
|
10
|
+
* @param {number} opts.level - 1–6, controls the HTML tag
|
|
11
|
+
* @param {string} opts.text - Heading text (escaped)
|
|
12
|
+
* @param {string} [opts.size] - Override visual size: 'xs'|'sm'|'base'|'lg'|'xl'|'2xl'|'3xl'|'4xl'
|
|
13
|
+
* Defaults to the level's natural size.
|
|
14
|
+
* @param {string} [opts.color] - 'default'|'muted'|'accent' (default: 'default')
|
|
15
|
+
* @param {boolean} [opts.balance] - Add text-wrap: balance to prevent orphaned words on the last line
|
|
16
|
+
* @param {string} [opts.class] - Extra classes on the element
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { escHtml as e } from '../html.js'
|
|
20
|
+
|
|
21
|
+
const LEVEL_SIZE = { 1: '4xl', 2: '3xl', 3: '2xl', 4: 'xl', 5: 'base', 6: 'sm' }
|
|
22
|
+
|
|
23
|
+
export function heading({
|
|
24
|
+
level = 2,
|
|
25
|
+
text = '',
|
|
26
|
+
size,
|
|
27
|
+
color = 'default',
|
|
28
|
+
balance = false,
|
|
29
|
+
class: cls = '',
|
|
30
|
+
} = {}) {
|
|
31
|
+
const tag = `h${Math.min(Math.max(Math.floor(level), 1), 6)}`
|
|
32
|
+
const sizeKey = size || LEVEL_SIZE[level] || 'base'
|
|
33
|
+
const weight = level <= 3 ? 'bold' : 'semibold'
|
|
34
|
+
const colorCls = color !== 'default' ? ` u-text-${color}` : ''
|
|
35
|
+
const classes = [
|
|
36
|
+
`u-text-${sizeKey}`,
|
|
37
|
+
`u-font-${weight}`,
|
|
38
|
+
'u-leading-tight',
|
|
39
|
+
colorCls,
|
|
40
|
+
balance ? 'u-text-balance' : '',
|
|
41
|
+
cls,
|
|
42
|
+
].filter(Boolean).join(' ')
|
|
43
|
+
|
|
44
|
+
return `<${tag} class="${classes}">${e(text)}</${tag}>`
|
|
45
|
+
}
|
package/src/ui/hero.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pulse UI — Hero
|
|
3
|
+
*
|
|
4
|
+
* Full-width hero section with eyebrow, headline, subheadline, and action slot.
|
|
5
|
+
*
|
|
6
|
+
* @param {object} opts
|
|
7
|
+
* @param {string} opts.eyebrow - Small label above the title (e.g. "Now available")
|
|
8
|
+
* @param {string} opts.title - Main headline
|
|
9
|
+
* @param {string} opts.subtitle - Supporting text beneath the headline
|
|
10
|
+
* @param {string} opts.actions - Raw HTML slot — typically button() or appBadge() calls
|
|
11
|
+
* @param {'center'|'left'} opts.align - Text alignment (default: 'center')
|
|
12
|
+
* @param {'md'|'sm'} opts.size - Vertical padding size: 'md' (default, 5rem) or 'sm' (2.5rem)
|
|
13
|
+
* @param {string} opts.class
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { escHtml as e } from '../html.js'
|
|
17
|
+
|
|
18
|
+
export function hero({
|
|
19
|
+
eyebrow = '',
|
|
20
|
+
title = '',
|
|
21
|
+
subtitle = '',
|
|
22
|
+
actions = '',
|
|
23
|
+
align = 'center',
|
|
24
|
+
size = 'md',
|
|
25
|
+
class: cls = '',
|
|
26
|
+
} = {}) {
|
|
27
|
+
const classes = ['ui-hero', align === 'left' && 'ui-hero--left', size === 'sm' && 'ui-hero--sm', cls].filter(Boolean).join(' ')
|
|
28
|
+
|
|
29
|
+
return `<section class="${e(classes)}">
|
|
30
|
+
<div class="ui-hero-inner">
|
|
31
|
+
${eyebrow ? `<p class="ui-hero-eyebrow">${e(eyebrow)}</p>` : ''}
|
|
32
|
+
${title ? `<h1 class="ui-hero-title">${e(title)}</h1>` : ''}
|
|
33
|
+
${subtitle ? `<p class="ui-hero-subtitle">${e(subtitle)}</p>` : ''}
|
|
34
|
+
${actions ? `<div class="ui-hero-actions">${actions}</div>` : ''}
|
|
35
|
+
</div>
|
|
36
|
+
</section>`
|
|
37
|
+
}
|
package/src/ui/icons.js
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pulse UI — Icon set
|
|
3
|
+
*
|
|
4
|
+
* ~55 curated icons. All are pure functions returning an SVG string.
|
|
5
|
+
* Style: 24×24 viewBox, stroke="currentColor", 2px stroke-width (Lucide-compatible).
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* import { iconCheck, iconArrowRight } from '@invisibleloop/pulse/ui'
|
|
9
|
+
* feature({ icon: iconZap() })
|
|
10
|
+
* button({ icon: iconArrowRight({ size: 14 }) })
|
|
11
|
+
* iconCheck({ size: 20, bg: 'circle', bgColor: 'success' })
|
|
12
|
+
* iconZap({ size: 20, bg: 'square', bgColor: 'accent' })
|
|
13
|
+
*
|
|
14
|
+
* @param {object} opts
|
|
15
|
+
* @param {number} opts.size - Width and height in px (default: 16)
|
|
16
|
+
* @param {string} opts.class - Extra CSS classes (on wrapper when bg is set, else on SVG)
|
|
17
|
+
* @param {'circle'|'square'} opts.bg - Wrap in a tinted background shape
|
|
18
|
+
* @param {'accent'|'success'|'warning'|'error'|'muted'} opts.bgColor - Background colour (default: 'accent')
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
const BG_SHAPES = new Set(['circle', 'square'])
|
|
22
|
+
const BG_COLORS = new Set(['accent', 'success', 'warning', 'error', 'muted'])
|
|
23
|
+
|
|
24
|
+
// ─── Internal helpers ─────────────────────────────────────────────────────────
|
|
25
|
+
|
|
26
|
+
function wrap(svg, { bg, bgColor, cls }) {
|
|
27
|
+
if (!bg || !BG_SHAPES.has(bg)) return cls ? svg.replace('<svg ', `<svg class="${cls}" `) : svg
|
|
28
|
+
const bgCol = BG_COLORS.has(bgColor) ? bgColor : 'accent'
|
|
29
|
+
const wrapCls = ['ui-icon-wrap', `ui-icon-wrap--${bg}`, `ui-icon-wrap--${bgCol}`, cls].filter(Boolean).join(' ')
|
|
30
|
+
return `<span class="${wrapCls}">${svg}</span>`
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function s(paths, o) {
|
|
34
|
+
const svg = `<svg width="${o.size}" height="${o.size}" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">${paths}</svg>`
|
|
35
|
+
return wrap(svg, o)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function f(paths, o) {
|
|
39
|
+
const svg = `<svg width="${o.size}" height="${o.size}" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">${paths}</svg>`
|
|
40
|
+
return wrap(svg, o)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function p(paths, o) {
|
|
44
|
+
const svg = `<svg width="${o.size}" height="${o.size}" viewBox="0 0 256 256" fill="currentColor" aria-hidden="true">${paths}</svg>`
|
|
45
|
+
return wrap(svg, o)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function opts(o = {}) {
|
|
49
|
+
return { size: o.size ?? 16, cls: o.class ?? '', bg: o.bg ?? '', bgColor: o.bgColor ?? 'accent' }
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// ─── Navigation & Direction ───────────────────────────────────────────────────
|
|
53
|
+
|
|
54
|
+
export const iconArrowLeft = (o) => s('<path d="M19 12H5M12 19l-7-7 7-7"/>', opts(o))
|
|
55
|
+
export const iconArrowRight = (o) => s('<path d="M5 12h14M12 5l7 7-7 7"/>', opts(o))
|
|
56
|
+
export const iconArrowUp = (o) => s('<path d="M12 19V5M5 12l7-7 7 7"/>', opts(o))
|
|
57
|
+
export const iconArrowDown = (o) => s('<path d="M12 5v14M19 12l-7 7-7-7"/>', opts(o))
|
|
58
|
+
export const iconChevronLeft = (o) => s('<polyline points="15 18 9 12 15 6"/>', opts(o))
|
|
59
|
+
export const iconChevronRight = (o) => s('<polyline points="9 18 15 12 9 6"/>', opts(o))
|
|
60
|
+
export const iconChevronUp = (o) => s('<polyline points="18 15 12 9 6 15"/>', opts(o))
|
|
61
|
+
export const iconChevronDown = (o) => s('<polyline points="6 9 12 15 18 9"/>', opts(o))
|
|
62
|
+
export const iconExternalLink = (o) => s('<path d="M18 13v6a2 2 0 01-2 2H5a2 2 0 01-2-2V8a2 2 0 012-2h6"/><polyline points="15 3 21 3 21 9"/><line x1="10" y1="14" x2="21" y2="3"/>', opts(o))
|
|
63
|
+
export const iconMenu = (o) => s('<line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="18" x2="21" y2="18"/>', opts(o))
|
|
64
|
+
export const iconX = (o) => s('<line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/>', opts(o))
|
|
65
|
+
export const iconMoreHorizontal = (o) => s('<circle cx="12" cy="12" r="1"/><circle cx="19" cy="12" r="1"/><circle cx="5" cy="12" r="1"/>', opts(o))
|
|
66
|
+
export const iconMoreVertical = (o) => s('<circle cx="12" cy="12" r="1"/><circle cx="12" cy="5" r="1"/><circle cx="12" cy="19" r="1"/>', opts(o))
|
|
67
|
+
|
|
68
|
+
// ─── Status ───────────────────────────────────────────────────────────────────
|
|
69
|
+
|
|
70
|
+
export const iconCheck = (o) => s('<polyline points="20 6 9 17 4 12"/>', opts(o))
|
|
71
|
+
export const iconCheckCircle = (o) => s('<path d="M22 11.08V12a10 10 0 11-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/>', opts(o))
|
|
72
|
+
export const iconXCircle = (o) => s('<circle cx="12" cy="12" r="10"/><line x1="15" y1="9" x2="9" y2="15"/><line x1="9" y1="9" x2="15" y2="15"/>', opts(o))
|
|
73
|
+
export const iconAlertCircle = (o) => s('<circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/>', opts(o))
|
|
74
|
+
export const iconAlertTriangle = (o) => s('<path d="M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/>', opts(o))
|
|
75
|
+
export const iconInfo = (o) => s('<circle cx="12" cy="12" r="10"/><line x1="12" y1="16" x2="12" y2="12"/><line x1="12" y1="8" x2="12.01" y2="8"/>', opts(o))
|
|
76
|
+
|
|
77
|
+
// ─── Actions ─────────────────────────────────────────────────────────────────
|
|
78
|
+
|
|
79
|
+
export const iconPlus = (o) => s('<line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/>', opts(o))
|
|
80
|
+
export const iconMinus = (o) => s('<line x1="5" y1="12" x2="19" y2="12"/>', opts(o))
|
|
81
|
+
export const iconEdit = (o) => s('<path d="M11 4H4a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 013 3L12 15l-4 1 1-4 9.5-9.5z"/>', opts(o))
|
|
82
|
+
export const iconTrash = (o) => s('<polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14a2 2 0 01-2 2H8a2 2 0 01-2-2L5 6"/><path d="M10 11v6M14 11v6"/><path d="M9 6V4a1 1 0 011-1h4a1 1 0 011 1v2"/>', opts(o))
|
|
83
|
+
export const iconCopy = (o) => s('<rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/>', opts(o))
|
|
84
|
+
export const iconSearch = (o) => s('<circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/>', opts(o))
|
|
85
|
+
export const iconFilter = (o) => s('<polygon points="22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3"/>', opts(o))
|
|
86
|
+
export const iconDownload = (o) => s('<path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/>', opts(o))
|
|
87
|
+
export const iconUpload = (o) => s('<path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/>', opts(o))
|
|
88
|
+
export const iconRefresh = (o) => s('<polyline points="23 4 23 10 17 10"/><polyline points="1 20 1 14 7 14"/><path d="M3.51 9a9 9 0 0114.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0020.49 15"/>', opts(o))
|
|
89
|
+
export const iconSend = (o) => s('<line x1="22" y1="2" x2="11" y2="13"/><polygon points="22 2 15 22 11 13 2 9 22 2"/>', opts(o))
|
|
90
|
+
|
|
91
|
+
// ─── UI Controls ─────────────────────────────────────────────────────────────
|
|
92
|
+
|
|
93
|
+
export const iconEye = (o) => s('<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/>', opts(o))
|
|
94
|
+
export const iconEyeOff = (o) => s('<path d="M17.94 17.94A10.07 10.07 0 0112 20c-7 0-11-8-11-8a18.45 18.45 0 015.06-5.94M9.9 4.24A9.12 9.12 0 0112 4c7 0 11 8 11 8a18.5 18.5 0 01-2.16 3.19m-6.72-1.07a3 3 0 11-4.24-4.24"/><line x1="1" y1="1" x2="23" y2="23"/>', opts(o))
|
|
95
|
+
export const iconLock = (o) => s('<rect x="3" y="11" width="18" height="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0110 0v4"/>', opts(o))
|
|
96
|
+
export const iconUnlock = (o) => s('<rect x="3" y="11" width="18" height="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 019.9-1"/>', opts(o))
|
|
97
|
+
export const iconSettings = (o) => s('<circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 00.33 1.82l.06.06a2 2 0 010 2.83 2 2 0 01-2.83 0l-.06-.06a1.65 1.65 0 00-1.82-.33 1.65 1.65 0 00-1 1.51V21a2 2 0 01-4 0v-.09A1.65 1.65 0 009 19.4a1.65 1.65 0 00-1.82.33l-.06.06a2 2 0 01-2.83-2.83l.06-.06A1.65 1.65 0 004.68 15a1.65 1.65 0 00-1.51-1H3a2 2 0 010-4h.09A1.65 1.65 0 004.6 9a1.65 1.65 0 00-.33-1.82l-.06-.06a2 2 0 012.83-2.83l.06.06A1.65 1.65 0 009 4.68a1.65 1.65 0 001-1.51V3a2 2 0 014 0v.09a1.65 1.65 0 001 1.51 1.65 1.65 0 001.82-.33l.06-.06a2 2 0 012.83 2.83l-.06.06A1.65 1.65 0 0019.4 9a1.65 1.65 0 001.51 1H21a2 2 0 010 4h-.09a1.65 1.65 0 00-1.51 1z"/>', opts(o))
|
|
98
|
+
export const iconBell = (o) => s('<path d="M18 8A6 6 0 006 8c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.73 21a2 2 0 01-3.46 0"/>', opts(o))
|
|
99
|
+
|
|
100
|
+
// ─── People & Communication ───────────────────────────────────────────────────
|
|
101
|
+
|
|
102
|
+
export const iconUser = (o) => s('<path d="M20 21v-2a4 4 0 00-4-4H8a4 4 0 00-4 4v2"/><circle cx="12" cy="7" r="4"/>', opts(o))
|
|
103
|
+
export const iconUsers = (o) => s('<path d="M17 21v-2a4 4 0 00-4-4H5a4 4 0 00-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 00-3-3.87"/><path d="M16 3.13a4 4 0 010 7.75"/>', opts(o))
|
|
104
|
+
export const iconMail = (o) => s('<path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/><polyline points="22 6 12 13 2 6"/>', opts(o))
|
|
105
|
+
export const iconMessageSquare = (o) => s('<path d="M21 15a2 2 0 01-2 2H7l-4 4V5a2 2 0 012-2h14a2 2 0 012 2z"/>', opts(o))
|
|
106
|
+
|
|
107
|
+
// ─── Navigation & Pages ───────────────────────────────────────────────────────
|
|
108
|
+
|
|
109
|
+
export const iconHome = (o) => s('<path d="M3 9l9-7 9 7v11a2 2 0 01-2 2H5a2 2 0 01-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/>', opts(o))
|
|
110
|
+
export const iconLogOut = (o) => s('<path d="M9 21H5a2 2 0 01-2-2V5a2 2 0 012-2h4"/><polyline points="16 17 21 12 16 7"/><line x1="21" y1="12" x2="9" y2="12"/>', opts(o))
|
|
111
|
+
export const iconLogIn = (o) => s('<path d="M15 3h4a2 2 0 012 2v14a2 2 0 01-2 2h-4"/><polyline points="10 17 15 12 10 7"/><line x1="15" y1="12" x2="3" y2="12"/>', opts(o))
|
|
112
|
+
|
|
113
|
+
// ─── Content & Files ─────────────────────────────────────────────────────────
|
|
114
|
+
|
|
115
|
+
export const iconFile = (o) => s('<path d="M13 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V9z"/><polyline points="13 2 13 9 20 9"/>', opts(o))
|
|
116
|
+
export const iconImage = (o) => s('<rect x="3" y="3" width="18" height="18" rx="2" ry="2"/><circle cx="8.5" cy="8.5" r="1.5"/><polyline points="21 15 16 10 5 21"/>', opts(o))
|
|
117
|
+
export const iconLink = (o) => s('<path d="M10 13a5 5 0 007.54.54l3-3a5 5 0 00-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 00-7.54-.54l-3 3a5 5 0 007.07 7.07l1.71-1.71"/>', opts(o))
|
|
118
|
+
export const iconCode = (o) => s('<polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/>', opts(o))
|
|
119
|
+
export const iconCalendar = (o) => s('<rect x="3" y="4" width="18" height="18" rx="2" ry="2"/><line x1="16" y1="2" x2="16" y2="6"/><line x1="8" y1="2" x2="8" y2="6"/><line x1="3" y1="10" x2="21" y2="10"/>', opts(o))
|
|
120
|
+
export const iconClock = (o) => s('<circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/>', opts(o))
|
|
121
|
+
export const iconBookmark = (o) => s('<path d="M19 21l-7-5-7 5V5a2 2 0 012-2h10a2 2 0 012 2z"/>', opts(o))
|
|
122
|
+
export const iconTag = (o) => s('<path d="M20.59 13.41l-7.17 7.17a2 2 0 01-2.83 0L2 12V2h10l8.59 8.59a2 2 0 010 2.82z"/><line x1="7" y1="7" x2="7.01" y2="7"/>', opts(o))
|
|
123
|
+
|
|
124
|
+
// ─── Media & Rating ──────────────────────────────────────────────────────────
|
|
125
|
+
|
|
126
|
+
export const iconPlay = (o) => f('<polygon points="5 3 19 12 5 21 5 3"/>', opts(o))
|
|
127
|
+
export const iconPause = (o) => s('<rect x="6" y="4" width="4" height="16"/><rect x="14" y="4" width="4" height="16"/>', opts(o))
|
|
128
|
+
export const iconVolume = (o) => s('<polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"/><path d="M19.07 4.93a10 10 0 010 14.14M15.54 8.46a5 5 0 010 7.07"/>', opts(o))
|
|
129
|
+
export const iconStar = (o) => f('<path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/>', opts(o))
|
|
130
|
+
export const iconHeart = (o) => s('<path d="M20.84 4.61a5.5 5.5 0 00-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 00-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 000-7.78z"/>', opts(o))
|
|
131
|
+
|
|
132
|
+
// ─── Devices ─────────────────────────────────────────────────────────────────
|
|
133
|
+
|
|
134
|
+
export const iconPhone = (o) => s('<rect x="5" y="2" width="14" height="20" rx="2"/><line x1="12" y1="18" x2="12.01" y2="18"/>', opts(o))
|
|
135
|
+
export const iconGamepad = (o) => s('<line x1="6" x2="10" y1="11" y2="11"/><line x1="8" x2="8" y1="9" y2="13"/><line x1="15" x2="15.01" y1="12" y2="12"/><line x1="18" x2="18.01" y1="10" y2="10"/><path d="M17.32 5H6.68a4 4 0 00-3.978 3.59c-.006.052-.01.101-.017.152C2.604 9.416 2 14.456 2 16a3 3 0 003 3c1 0 1.5-.5 2-1l1.414-1.414A2 2 0 019.828 16h4.344a2 2 0 011.414.586L17 18c.5.5 1 1 2 1a3 3 0 003-3c0-1.545-.604-6.584-.685-7.258-.007-.05-.011-.1-.017-.151A4 4 0 0017.32 5z"/>', opts(o))
|
|
136
|
+
|
|
137
|
+
// ─── Hand Pointers ────────────────────────────────────────────────────────────
|
|
138
|
+
|
|
139
|
+
// Phosphor hand-pointing (MIT) — rotated for each direction
|
|
140
|
+
const _HP = 'M196,88a27.86,27.86,0,0,0-13.35,3.39A28,28,0,0,0,144,74.7V44a28,28,0,0,0-56,0v80l-3.82-6.13A28,28,0,0,0,35.73,146l4.67,8.23C74.81,214.89,89.05,240,136,240a88.1,88.1,0,0,0,88-88V116A28,28,0,0,0,196,88Zm12,64a72.08,72.08,0,0,1-72,72c-37.63,0-47.84-18-81.68-77.68l-4.69-8.27,0-.05A12,12,0,0,1,54,121.61a11.88,11.88,0,0,1,6-1.6,12,12,0,0,1,10.41,6,1.76,1.76,0,0,0,.14.23l18.67,30A8,8,0,0,0,104,152V44a12,12,0,0,1,24,0v68a8,8,0,0,0,16,0V100a12,12,0,0,1,24,0v20a8,8,0,0,0,16,0v-4a12,12,0,0,1,24,0Z'
|
|
141
|
+
export const iconHandPointUp = (o) => p(`<g transform="matrix(-1 0 0 1 256 0)"><path d="${_HP}"/></g>`, opts(o))
|
|
142
|
+
export const iconHandPointDown = (o) => p(`<g transform="rotate(180,128,128)"><path d="${_HP}"/></g>`, opts(o))
|
|
143
|
+
export const iconHandPointLeft = (o) => p(`<g transform="matrix(1 0 0 -1 0 256) rotate(270,128,128)"><path d="${_HP}"/></g>`, opts(o))
|
|
144
|
+
export const iconHandPointRight = (o) => p(`<g transform="rotate(90,128,128)"><path d="${_HP}"/></g>`, opts(o))
|
|
145
|
+
|
|
146
|
+
// ─── Theme ────────────────────────────────────────────────────────────────────
|
|
147
|
+
|
|
148
|
+
export const iconSun = (o) => s('<circle cx="12" cy="12" r="4"/><line x1="12" y1="2" x2="12" y2="6"/><line x1="12" y1="18" x2="12" y2="22"/><line x1="4.93" y1="4.93" x2="7.76" y2="7.76"/><line x1="16.24" y1="16.24" x2="19.07" y2="19.07"/><line x1="2" y1="12" x2="6" y2="12"/><line x1="18" y1="12" x2="22" y2="12"/><line x1="4.93" y1="19.07" x2="7.76" y2="16.24"/><line x1="16.24" y1="7.76" x2="19.07" y2="4.93"/>', opts(o))
|
|
149
|
+
export const iconMoon = (o) => s('<path d="M21 12.79A9 9 0 1111.21 3 7 7 0 0021 12.79z"/>', opts(o))
|
|
150
|
+
|
|
151
|
+
// ─── Misc ─────────────────────────────────────────────────────────────────────
|
|
152
|
+
|
|
153
|
+
export const iconGlobe = (o) => s('<circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/><path d="M12 2a15.3 15.3 0 014 10 15.3 15.3 0 01-4 10 15.3 15.3 0 01-4-10 15.3 15.3 0 014-10z"/>', opts(o))
|
|
154
|
+
export const iconShield = (o) => s('<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/>', opts(o))
|
|
155
|
+
export const iconZap = (o) => s('<polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/>', opts(o))
|
|
156
|
+
export const iconTrendingUp = (o) => s('<polyline points="23 6 13.5 15.5 8.5 10.5 1 18"/><polyline points="17 6 23 6 23 12"/>', opts(o))
|
|
157
|
+
export const iconTrendingDown = (o) => s('<polyline points="23 18 13.5 8.5 8.5 13.5 1 6"/><polyline points="17 18 23 18 23 12"/>', opts(o))
|
|
158
|
+
export const iconLoader = (o) => s('<line x1="12" y1="2" x2="12" y2="6"/><line x1="12" y1="18" x2="12" y2="22"/><line x1="4.93" y1="4.93" x2="7.76" y2="7.76"/><line x1="16.24" y1="16.24" x2="19.07" y2="19.07"/><line x1="2" y1="12" x2="6" y2="12"/><line x1="18" y1="12" x2="22" y2="12"/><line x1="4.93" y1="19.07" x2="7.76" y2="16.24"/><line x1="16.24" y1="7.76" x2="19.07" y2="4.93"/>', opts(o))
|
|
159
|
+
export const iconGrid = (o) => s('<rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/>', opts(o))
|
|
160
|
+
export const iconBug = (o) => s('<rect x="8" y="6" width="8" height="14" rx="4"/><path d="m19 7-3 2"/><path d="m5 7 3 2"/><path d="m19 19-3-2"/><path d="m5 19 3-2"/><path d="M20 13h-4"/><path d="M4 13h4"/><path d="m10 4 1 2"/><path d="m14 4-1 2"/>', opts(o))
|
|
161
|
+
export const iconMapPin = (o) => s('<path d="M20 10c0 6-8 13-8 13s-8-7-8-13a8 8 0 0 1 16 0Z"/><circle cx="12" cy="10" r="3"/>', opts(o))
|