decantr 0.9.0
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/AGENTS.md +868 -0
- package/CHANGELOG.md +255 -0
- package/CLAUDE.md +178 -0
- package/LICENSE +21 -0
- package/README.md +229 -0
- package/cli/art.js +127 -0
- package/cli/commands/a11y.js +61 -0
- package/cli/commands/audit.js +225 -0
- package/cli/commands/build.js +38 -0
- package/cli/commands/dev.js +18 -0
- package/cli/commands/doctor.js +197 -0
- package/cli/commands/figma-sync.js +48 -0
- package/cli/commands/figma-tokens.js +55 -0
- package/cli/commands/generate.js +26 -0
- package/cli/commands/init.js +116 -0
- package/cli/commands/lint.js +209 -0
- package/cli/commands/mcp.js +530 -0
- package/cli/commands/migrate.js +175 -0
- package/cli/commands/test.js +38 -0
- package/cli/commands/validate.js +354 -0
- package/cli/index.js +113 -0
- package/package.json +95 -0
- package/reference/atoms.md +517 -0
- package/reference/behaviors.md +384 -0
- package/reference/build-tooling.md +275 -0
- package/reference/color-guidelines.md +965 -0
- package/reference/component-lifecycle.md +137 -0
- package/reference/compound-spacing.md +95 -0
- package/reference/decantation-process.md +499 -0
- package/reference/dev-server-routes.md +93 -0
- package/reference/form-system.md +253 -0
- package/reference/i18n.md +336 -0
- package/reference/icons.md +576 -0
- package/reference/llm-primer.md +953 -0
- package/reference/plugins.md +252 -0
- package/reference/registry-consumption.md +76 -0
- package/reference/router.md +217 -0
- package/reference/shells.md +116 -0
- package/reference/spatial-guidelines.md +541 -0
- package/reference/ssr.md +234 -0
- package/reference/state-data.md +215 -0
- package/reference/state-patterns.md +166 -0
- package/reference/state.md +194 -0
- package/reference/style-system.md +110 -0
- package/reference/tokens.md +460 -0
- package/src/app.js +19 -0
- package/src/chart/_animate.js +266 -0
- package/src/chart/_base.js +109 -0
- package/src/chart/_data.js +209 -0
- package/src/chart/_format.js +106 -0
- package/src/chart/_interact.js +364 -0
- package/src/chart/_palette.js +105 -0
- package/src/chart/_renderer.js +52 -0
- package/src/chart/_scene.js +262 -0
- package/src/chart/_shared.js +371 -0
- package/src/chart/index.js +637 -0
- package/src/chart/layouts/_layout-base.js +328 -0
- package/src/chart/layouts/cartesian.js +148 -0
- package/src/chart/layouts/hierarchy.js +562 -0
- package/src/chart/layouts/polar.js +101 -0
- package/src/chart/renderers/canvas.js +179 -0
- package/src/chart/renderers/svg.js +256 -0
- package/src/chart/renderers/webgpu.js +715 -0
- package/src/chart/types/_type-base.js +26 -0
- package/src/chart/types/area.js +134 -0
- package/src/chart/types/bar.js +173 -0
- package/src/chart/types/box-plot.js +125 -0
- package/src/chart/types/bubble.js +63 -0
- package/src/chart/types/candlestick.js +115 -0
- package/src/chart/types/chord.js +85 -0
- package/src/chart/types/combination.js +108 -0
- package/src/chart/types/funnel.js +68 -0
- package/src/chart/types/gauge.js +163 -0
- package/src/chart/types/heatmap.js +98 -0
- package/src/chart/types/histogram.js +71 -0
- package/src/chart/types/line.js +111 -0
- package/src/chart/types/org-chart.js +93 -0
- package/src/chart/types/pie.js +81 -0
- package/src/chart/types/radar.js +96 -0
- package/src/chart/types/radial.js +68 -0
- package/src/chart/types/range-area.js +55 -0
- package/src/chart/types/range-bar.js +61 -0
- package/src/chart/types/sankey.js +73 -0
- package/src/chart/types/scatter.js +66 -0
- package/src/chart/types/sparkline.js +81 -0
- package/src/chart/types/sunburst.js +69 -0
- package/src/chart/types/swimlane.js +88 -0
- package/src/chart/types/treemap.js +62 -0
- package/src/chart/types/waterfall.js +100 -0
- package/src/components/_base.js +1658 -0
- package/src/components/_behaviors.js +1140 -0
- package/src/components/_primitives.js +534 -0
- package/src/components/_qr-encoder.js +539 -0
- package/src/components/accordion.js +207 -0
- package/src/components/affix.js +62 -0
- package/src/components/alert-dialog.js +75 -0
- package/src/components/alert.js +47 -0
- package/src/components/aspect-ratio.js +24 -0
- package/src/components/avatar-group.js +55 -0
- package/src/components/avatar.js +38 -0
- package/src/components/back-top.js +75 -0
- package/src/components/badge.js +74 -0
- package/src/components/banner.js +68 -0
- package/src/components/breadcrumb.js +162 -0
- package/src/components/button.js +115 -0
- package/src/components/calendar.js +131 -0
- package/src/components/card.js +192 -0
- package/src/components/carousel.js +98 -0
- package/src/components/cascader.js +261 -0
- package/src/components/checkbox.js +80 -0
- package/src/components/chip.js +81 -0
- package/src/components/code-block.js +82 -0
- package/src/components/collapsible.js +50 -0
- package/src/components/color-palette.js +438 -0
- package/src/components/color-picker.js +314 -0
- package/src/components/combobox.js +181 -0
- package/src/components/command.js +174 -0
- package/src/components/comment.js +206 -0
- package/src/components/context-menu.js +76 -0
- package/src/components/data-table.js +724 -0
- package/src/components/date-picker.js +217 -0
- package/src/components/date-range-picker.js +244 -0
- package/src/components/datetime-picker.js +271 -0
- package/src/components/descriptions.js +68 -0
- package/src/components/drawer.js +179 -0
- package/src/components/dropdown.js +88 -0
- package/src/components/empty.js +41 -0
- package/src/components/float-button.js +90 -0
- package/src/components/form.js +106 -0
- package/src/components/hover-card.js +49 -0
- package/src/components/icon.js +87 -0
- package/src/components/image.js +97 -0
- package/src/components/index.js +117 -0
- package/src/components/input-group.js +75 -0
- package/src/components/input-number.js +155 -0
- package/src/components/input-otp.js +178 -0
- package/src/components/input.js +91 -0
- package/src/components/kbd.js +36 -0
- package/src/components/label.js +25 -0
- package/src/components/list.js +118 -0
- package/src/components/masked-input.js +236 -0
- package/src/components/mentions.js +165 -0
- package/src/components/menu.js +259 -0
- package/src/components/message.js +80 -0
- package/src/components/modal.js +147 -0
- package/src/components/navigation-menu.js +166 -0
- package/src/components/notification.js +84 -0
- package/src/components/pagination.js +104 -0
- package/src/components/placeholder.js +132 -0
- package/src/components/popconfirm.js +70 -0
- package/src/components/popover.js +58 -0
- package/src/components/progress.js +61 -0
- package/src/components/qrcode.js +251 -0
- package/src/components/radiogroup.js +120 -0
- package/src/components/range-slider.js +176 -0
- package/src/components/rate.js +186 -0
- package/src/components/resizable.js +83 -0
- package/src/components/result.js +57 -0
- package/src/components/scroll-area.js +43 -0
- package/src/components/segmented.js +97 -0
- package/src/components/select.js +165 -0
- package/src/components/separator.js +31 -0
- package/src/components/shell.js +407 -0
- package/src/components/skeleton.js +39 -0
- package/src/components/slider.js +141 -0
- package/src/components/sortable-list.js +176 -0
- package/src/components/space.js +42 -0
- package/src/components/spinner.js +112 -0
- package/src/components/splitter.js +147 -0
- package/src/components/statistic.js +136 -0
- package/src/components/steps.js +99 -0
- package/src/components/switch.js +95 -0
- package/src/components/table.js +44 -0
- package/src/components/tabs.js +216 -0
- package/src/components/tag.js +115 -0
- package/src/components/textarea.js +82 -0
- package/src/components/time-picker.js +153 -0
- package/src/components/time-range-picker.js +170 -0
- package/src/components/timeline.js +226 -0
- package/src/components/toast.js +71 -0
- package/src/components/toggle.js +213 -0
- package/src/components/tooltip.js +57 -0
- package/src/components/tour.js +159 -0
- package/src/components/transfer.js +163 -0
- package/src/components/tree-select.js +274 -0
- package/src/components/tree.js +141 -0
- package/src/components/typography.js +136 -0
- package/src/components/upload.js +118 -0
- package/src/components/visually-hidden.js +20 -0
- package/src/components/watermark.js +124 -0
- package/src/core/index.js +539 -0
- package/src/core/lifecycle.js +69 -0
- package/src/css/atoms.js +651 -0
- package/src/css/components.js +940 -0
- package/src/css/derive.js +1296 -0
- package/src/css/index.js +265 -0
- package/src/css/runtime.js +268 -0
- package/src/css/styles/addons/bioluminescent.js +93 -0
- package/src/css/styles/addons/clay.js +70 -0
- package/src/css/styles/addons/clean.js +57 -0
- package/src/css/styles/addons/command-center.js +143 -0
- package/src/css/styles/addons/dopamine.js +83 -0
- package/src/css/styles/addons/editorial.js +80 -0
- package/src/css/styles/addons/glassmorphism.js +99 -0
- package/src/css/styles/addons/liquid-glass.js +105 -0
- package/src/css/styles/addons/prismatic.js +100 -0
- package/src/css/styles/addons/retro.js +63 -0
- package/src/css/styles/auradecantism.js +96 -0
- package/src/css/theme-registry.js +444 -0
- package/src/data/entity.js +281 -0
- package/src/data/index.js +13 -0
- package/src/data/persist.js +225 -0
- package/src/data/query.js +839 -0
- package/src/data/realtime.js +299 -0
- package/src/data/url.js +177 -0
- package/src/data/worker.js +134 -0
- package/src/explorer/archetypes.js +243 -0
- package/src/explorer/atoms.js +228 -0
- package/src/explorer/charts.js +497 -0
- package/src/explorer/components.js +129 -0
- package/src/explorer/foundations.js +949 -0
- package/src/explorer/icons.js +178 -0
- package/src/explorer/patterns.js +247 -0
- package/src/explorer/recipes.js +194 -0
- package/src/explorer/shared/pattern-examples.js +1337 -0
- package/src/explorer/shared/showcase-renderer.js +958 -0
- package/src/explorer/shared/spec-table.js +41 -0
- package/src/explorer/shared/usage-links.js +87 -0
- package/src/explorer/shell-config.js +10 -0
- package/src/explorer/shells.js +551 -0
- package/src/explorer/styles.js +161 -0
- package/src/explorer/tokens.js +262 -0
- package/src/explorer/tools.js +525 -0
- package/src/form/index.js +804 -0
- package/src/i18n/index.js +251 -0
- package/src/icons/essential.js +479 -0
- package/src/icons/index.js +53 -0
- package/src/plugins/index.js +282 -0
- package/src/registry/archetypes/content-site.json +71 -0
- package/src/registry/archetypes/docs-explorer.json +23 -0
- package/src/registry/archetypes/ecommerce.json +104 -0
- package/src/registry/archetypes/financial-dashboard.json +77 -0
- package/src/registry/archetypes/index.json +41 -0
- package/src/registry/archetypes/portfolio.json +82 -0
- package/src/registry/archetypes/recipe-community.json +159 -0
- package/src/registry/archetypes/saas-dashboard.json +86 -0
- package/src/registry/architect/cross-cutting.json +45 -0
- package/src/registry/architect/domains/ecommerce.json +294 -0
- package/src/registry/architect/domains/financial-services.json +302 -0
- package/src/registry/architect/index.json +26 -0
- package/src/registry/architect/traits.json +379 -0
- package/src/registry/atoms.json +16 -0
- package/src/registry/chart-showcase.json +160 -0
- package/src/registry/chart.json +136 -0
- package/src/registry/components.json +8616 -0
- package/src/registry/core.json +216 -0
- package/src/registry/css.json +319 -0
- package/src/registry/data.json +135 -0
- package/src/registry/foundations.json +11 -0
- package/src/registry/icons.json +463 -0
- package/src/registry/index.json +101 -0
- package/src/registry/patterns/activity-feed.json +37 -0
- package/src/registry/patterns/article-content.json +27 -0
- package/src/registry/patterns/auth-form.json +37 -0
- package/src/registry/patterns/author-card.json +20 -0
- package/src/registry/patterns/card-grid.json +127 -0
- package/src/registry/patterns/category-nav.json +26 -0
- package/src/registry/patterns/chart-grid.json +36 -0
- package/src/registry/patterns/chat-interface.json +37 -0
- package/src/registry/patterns/checklist-card.json +55 -0
- package/src/registry/patterns/comparison-panel.json +27 -0
- package/src/registry/patterns/component-showcase.json +24 -0
- package/src/registry/patterns/contact-form.json +31 -0
- package/src/registry/patterns/cta-section.json +20 -0
- package/src/registry/patterns/data-table.json +37 -0
- package/src/registry/patterns/detail-header.json +83 -0
- package/src/registry/patterns/detail-panel.json +27 -0
- package/src/registry/patterns/explorer-shell.json +22 -0
- package/src/registry/patterns/filter-bar.json +33 -0
- package/src/registry/patterns/filter-sidebar.json +27 -0
- package/src/registry/patterns/form-sections.json +110 -0
- package/src/registry/patterns/goal-tracker.json +27 -0
- package/src/registry/patterns/hero.json +107 -0
- package/src/registry/patterns/index.json +47 -0
- package/src/registry/patterns/kpi-grid.json +36 -0
- package/src/registry/patterns/media-gallery.json +20 -0
- package/src/registry/patterns/order-history.json +20 -0
- package/src/registry/patterns/pagination.json +19 -0
- package/src/registry/patterns/photo-to-recipe.json +36 -0
- package/src/registry/patterns/pipeline-tracker.json +28 -0
- package/src/registry/patterns/post-list.json +27 -0
- package/src/registry/patterns/pricing-table.json +32 -0
- package/src/registry/patterns/scorecard.json +28 -0
- package/src/registry/patterns/search-bar.json +20 -0
- package/src/registry/patterns/specimen-grid.json +19 -0
- package/src/registry/patterns/stat-card.json +55 -0
- package/src/registry/patterns/stats-bar.json +55 -0
- package/src/registry/patterns/steps-card.json +55 -0
- package/src/registry/patterns/table-of-contents.json +19 -0
- package/src/registry/patterns/testimonials.json +21 -0
- package/src/registry/patterns/timeline.json +27 -0
- package/src/registry/patterns/token-inspector.json +21 -0
- package/src/registry/patterns/wizard.json +27 -0
- package/src/registry/recipe-auradecantism.json +69 -0
- package/src/registry/recipe-clean.json +65 -0
- package/src/registry/recipe-command-center.json +78 -0
- package/src/registry/router.json +73 -0
- package/src/registry/schema/README.md +197 -0
- package/src/registry/skeletons.json +259 -0
- package/src/registry/state.json +137 -0
- package/src/registry/tokens.json +40 -0
- package/src/router/hash.js +17 -0
- package/src/router/history.js +18 -0
- package/src/router/index.js +598 -0
- package/src/ssr/index.js +922 -0
- package/src/state/arrays.js +181 -0
- package/src/state/devtools.js +647 -0
- package/src/state/index.js +498 -0
- package/src/state/middleware.js +288 -0
- package/src/state/scheduler.js +206 -0
- package/src/state/store.js +300 -0
- package/src/tags/index.js +19 -0
- package/src/tannins/auth.js +396 -0
- package/src/test/dom.js +352 -0
- package/src/test/index.js +62 -0
- package/src/test/state.js +306 -0
- package/tools/a11y-audit.js +487 -0
- package/tools/analyzer.js +315 -0
- package/tools/audit.js +706 -0
- package/tools/builder.js +1422 -0
- package/tools/css-extract.js +188 -0
- package/tools/dev-server.js +316 -0
- package/tools/dts-gen.js +1260 -0
- package/tools/figma-components.js +329 -0
- package/tools/figma-patterns.js +516 -0
- package/tools/figma-plugin/code.js +453 -0
- package/tools/figma-plugin/manifest.json +14 -0
- package/tools/figma-plugin/ui.html +268 -0
- package/tools/figma-render.js +293 -0
- package/tools/figma-tokens.js +712 -0
- package/tools/figma-upload.js +318 -0
- package/tools/generate.js +738 -0
- package/tools/icons.js +133 -0
- package/tools/init-templates.js +265 -0
- package/tools/install-hooks.sh +5 -0
- package/tools/migrations/0.5.0.js +53 -0
- package/tools/migrations/0.6.0.js +95 -0
- package/tools/minify.js +170 -0
- package/tools/pre-commit +4 -0
- package/tools/registry.js +662 -0
- package/tools/reset-playground.js +61 -0
- package/tools/starter-templates/content-site/app.js +49 -0
- package/tools/starter-templates/content-site/essence.js +19 -0
- package/tools/starter-templates/content-site/pages.js +31 -0
- package/tools/starter-templates/ecommerce/app.js +50 -0
- package/tools/starter-templates/ecommerce/essence.js +19 -0
- package/tools/starter-templates/ecommerce/pages.js +31 -0
- package/tools/starter-templates/landing-page/app.js +38 -0
- package/tools/starter-templates/landing-page/essence.js +18 -0
- package/tools/starter-templates/landing-page/pages.js +21 -0
- package/tools/starter-templates/portfolio/app.js +45 -0
- package/tools/starter-templates/portfolio/essence.js +19 -0
- package/tools/starter-templates/portfolio/pages.js +33 -0
- package/tools/starter-templates/saas-dashboard/app.js +70 -0
- package/tools/starter-templates/saas-dashboard/essence.js +19 -0
- package/tools/starter-templates/saas-dashboard/pages.js +31 -0
- package/tools/verify-pack.js +203 -0
- package/types/chart.d.ts +77 -0
- package/types/components.d.ts +587 -0
- package/types/core.d.ts +89 -0
- package/types/css.d.ts +149 -0
- package/types/data.d.ts +238 -0
- package/types/form.d.ts +164 -0
- package/types/i18n.d.ts +51 -0
- package/types/icons.d.ts +27 -0
- package/types/index.d.ts +13 -0
- package/types/router.d.ts +116 -0
- package/types/ssr.d.ts +102 -0
- package/types/state.d.ts +83 -0
- package/types/tags.d.ts +62 -0
- package/types/tannins.d.ts +63 -0
- package/types/test.d.ts +48 -0
|
@@ -0,0 +1,953 @@
|
|
|
1
|
+
# Decantr LLM Primer
|
|
2
|
+
|
|
3
|
+
Single-file reference for code generation. Covers the 20% that handles 80% of scaffolding needs.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. All Imports
|
|
8
|
+
|
|
9
|
+
```js
|
|
10
|
+
import { tags } from 'decantr/tags'; // HTML elements
|
|
11
|
+
import { h, text, cond, list, mount, onMount, onDestroy } from 'decantr/core';
|
|
12
|
+
import { createSignal, createEffect, createMemo, createStore, batch } from 'decantr/state';
|
|
13
|
+
import { createRouter, link, navigate, useRoute, back, forward, isNavigating } from 'decantr/router';
|
|
14
|
+
import { css, setStyle, setMode } from 'decantr/css';
|
|
15
|
+
import { Button, Input, Card, Modal, Tabs, Select, ... } from 'decantr/components';
|
|
16
|
+
import { Chart, Sparkline, chartSpec, createStream } from 'decantr/chart';
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
**Usage pattern:**
|
|
20
|
+
```js
|
|
21
|
+
const { div, span, h1, p, a, nav, main, header, aside, section, ul, li } = tags;
|
|
22
|
+
div({ class: css('_flex _col _gap4 _p6') }, child1, child2);
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## 2. Common Atoms (with Tailwind equivalences)
|
|
28
|
+
|
|
29
|
+
Both naming styles work. Decantr terse names are canonical.
|
|
30
|
+
|
|
31
|
+
| Decantr Atom | Tailwind Alias | CSS Output |
|
|
32
|
+
|-------------|---------------|-----------|
|
|
33
|
+
| `_flex` | — | `display:flex` |
|
|
34
|
+
| `_grid` | — | `display:grid` |
|
|
35
|
+
| `_col` | `_flex-col` | `flex-direction:column` |
|
|
36
|
+
| `_row` | `_flex-row` | `flex-direction:row` |
|
|
37
|
+
| `_aic` | `_items-center` | `align-items:center` |
|
|
38
|
+
| `_aifs` | `_items-start` | `align-items:flex-start` |
|
|
39
|
+
| `_aife` | `_items-end` | `align-items:flex-end` |
|
|
40
|
+
| `_ais` | `_items-stretch` | `align-items:stretch` |
|
|
41
|
+
| `_aibs` | `_items-baseline` | `align-items:baseline` |
|
|
42
|
+
| `_jcc` | `_justify-center` | `justify-content:center` |
|
|
43
|
+
| `_jcsb` | `_justify-between` | `justify-content:space-between` |
|
|
44
|
+
| `_jcsa` | `_justify-around` | `justify-content:space-around` |
|
|
45
|
+
| `_jcse` | `_justify-evenly` | `justify-content:space-evenly` |
|
|
46
|
+
| `_jcfs` | `_justify-start` | `justify-content:flex-start` |
|
|
47
|
+
| `_jcfe` | `_justify-end` | `justify-content:flex-end` |
|
|
48
|
+
| `_center` | — | `align-items:center;justify-content:center` |
|
|
49
|
+
| `_wrap` | `_flex-wrap` | `flex-wrap:wrap` |
|
|
50
|
+
| `_flex1` | `_flex-1` | `flex:1 1 0%` |
|
|
51
|
+
| `_gap4` | `_gap-4` | `gap:1rem` |
|
|
52
|
+
| `_p4` | `_p-4` | `padding:1rem` |
|
|
53
|
+
| `_px6` | `_px-6` | `padding-inline:1.5rem` |
|
|
54
|
+
| `_py3` | `_py-3` | `padding-block:0.75rem` |
|
|
55
|
+
| `_m0` | `_m-0` | `margin:0` |
|
|
56
|
+
| `_mt4` | — | `margin-top:1rem` |
|
|
57
|
+
| `_mxa` | — | `margin-inline:auto` |
|
|
58
|
+
| `_gc2` | `_grid-cols-2` | `grid-template-columns:repeat(2,minmax(0,1fr))` |
|
|
59
|
+
| `_gc4` | `_grid-cols-4` | `grid-template-columns:repeat(4,minmax(0,1fr))` |
|
|
60
|
+
| `_span2` | `_col-span-2` | `grid-column:span 2/span 2` |
|
|
61
|
+
| `_wfull` | `_w-full` | `width:100%` |
|
|
62
|
+
| `_hfull` | `_h-full` | `height:100%` |
|
|
63
|
+
| `_tc` | `_text-center` | `text-align:center` |
|
|
64
|
+
| `_bold` | `_font-bold` | `font-weight:700` |
|
|
65
|
+
| `_fgfg` | — | `color:var(--d-fg)` |
|
|
66
|
+
| `_fgmuted` | `_text-muted` | `color:var(--d-muted)` |
|
|
67
|
+
| `_fgmutedfg` | `_text-muted-foreground` | `color:var(--d-muted-fg)` |
|
|
68
|
+
| `_bgmuted` | `_bg-muted` | `background:var(--d-muted)` |
|
|
69
|
+
| `_bgprimary` | `_bg-primary` | `background:var(--d-primary)` |
|
|
70
|
+
| `_b1` | `_border` | `border:1px solid` |
|
|
71
|
+
| `_r4` | `_rounded-lg` | `border-radius:1rem` |
|
|
72
|
+
| `_rfull` | `_rounded-full` | `border-radius:9999px` |
|
|
73
|
+
| `_shadow` | `_shadow` | `box-shadow:0 1px 3px ...` |
|
|
74
|
+
| `_shadowmd` | `_shadow-md` | `box-shadow:0 4px 6px ...` |
|
|
75
|
+
| `_trans` | `_transition` | `transition:all 0.2s ease` |
|
|
76
|
+
| `_pointer` | `_cursor-pointer` | `cursor:pointer` |
|
|
77
|
+
| `_ohidden` | `_overflow-hidden` | `overflow:hidden` |
|
|
78
|
+
| `_relative` | `_relative` | `position:relative` |
|
|
79
|
+
| `_absolute` | `_absolute` | `position:absolute` |
|
|
80
|
+
| `_none` | — | `display:none` |
|
|
81
|
+
| `_truncate` | `_truncate` | `overflow:hidden;text-overflow:ellipsis;white-space:nowrap` |
|
|
82
|
+
| `_nounder` | `_no-underline` | `text-decoration:none` |
|
|
83
|
+
| `_borderB` | — | `border-bottom:1px solid var(--d-border)` |
|
|
84
|
+
| `_bcborder` | `_border-border` | `border-color:var(--d-border)` |
|
|
85
|
+
|
|
86
|
+
### Spacing Scale
|
|
87
|
+
`0`=0, `1`=0.25rem, `2`=0.5rem, `3`=0.75rem, `4`=1rem, `5`=1.25rem, `6`=1.5rem, `8`=2rem, `10`=2.5rem, `12`=3rem, `16`=4rem, `20`=5rem, `24`=6rem
|
|
88
|
+
|
|
89
|
+
> For spacing decision rules beyond defaults, see `reference/spatial-guidelines.md`.
|
|
90
|
+
|
|
91
|
+
### Opacity Modifiers
|
|
92
|
+
Append `/N` to any semantic color atom for alpha transparency (uses `color-mix()`):
|
|
93
|
+
`_bgprimary/50` (50% opacity bg), `_fgaccent/30` (30% opacity text), `_bcborder/80` (80% opacity border).
|
|
94
|
+
Valid opacities: 5, 10, 15, 20, 25, 30, 40, 50, 60, 70, 75, 80, 90, 95. Works with responsive prefixes: `_sm:bgprimary/20`.
|
|
95
|
+
|
|
96
|
+
### Arbitrary Transitions
|
|
97
|
+
Use `_trans[...]` for custom transitions: `_trans[color_0.15s_ease]` (underscores become spaces).
|
|
98
|
+
Standard shortcuts: `_trans` (all 0.2s ease), `_transfast` (0.1s), `_transslow` (0.4s), `_transnone`.
|
|
99
|
+
|
|
100
|
+
### Custom values
|
|
101
|
+
Use bracket syntax: `_w[300px]`, `_h[100vh]`, `_p[2px]`, `_bg[#ff0000]`, `_grid-template-columns[240px_1fr]`
|
|
102
|
+
|
|
103
|
+
### Typography Presets
|
|
104
|
+
`_heading1`–`_heading6`, `_body`, `_bodylg`, `_caption`, `_label`, `_overline`
|
|
105
|
+
|
|
106
|
+
Font sizes: `_textxs` (0.625rem) → `_textsm` → `_textbase` → `_textmd` → `_textlg` → `_textxl` → `_text2xl` → `_text3xl` → `_text4xl` (2.5rem)
|
|
107
|
+
|
|
108
|
+
### Semantic Colors
|
|
109
|
+
| Role | Background | Foreground | Border |
|
|
110
|
+
|------|-----------|-----------|--------|
|
|
111
|
+
| Primary | `_bgprimary` | `_fgprimary` | `_bcprimary` |
|
|
112
|
+
| Success | `_bgsuccess` | `_fgsuccess` | `_bcsuccess` |
|
|
113
|
+
| Warning | `_bgwarning` | `_fgwarning` | `_bcwarning` |
|
|
114
|
+
| Error | `_bgerror` | `_fgerror` | `_bcerror` |
|
|
115
|
+
| Subtle | `_bgprimarysub` | `_fgprimarysub` | — |
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## 3. Top 15 Component Signatures
|
|
120
|
+
|
|
121
|
+
```js
|
|
122
|
+
// Button
|
|
123
|
+
Button({ variant: 'primary'|'outline'|'ghost'|'destructive'|'link', size: 'sm'|'md'|'lg',
|
|
124
|
+
disabled, loading, onclick, class }, ...children)
|
|
125
|
+
|
|
126
|
+
// Input
|
|
127
|
+
Input({ label, type, placeholder, value, onchange, oninput, disabled, class })
|
|
128
|
+
|
|
129
|
+
// Card (with sub-components)
|
|
130
|
+
Card({ title, extra, hoverable, bordered, loading, size, type, cover, actions, class },
|
|
131
|
+
Card.Header({ extra }, ...), Card.Body({}, ...), Card.Footer({}, ...),
|
|
132
|
+
Card.Cover({}, img), Card.Meta({ avatar, title, description }),
|
|
133
|
+
Card.Grid({ hoverable }, ...), Card.Actions({}, ...)
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
// Select
|
|
137
|
+
Select({ label, value, onchange, options: [{ label, value }], placeholder, class })
|
|
138
|
+
|
|
139
|
+
// Modal
|
|
140
|
+
Modal({ visible: signal, onClose: fn, class }, ...children)
|
|
141
|
+
|
|
142
|
+
// Tabs
|
|
143
|
+
Tabs({ items: [{ label, content: () => node }], class })
|
|
144
|
+
|
|
145
|
+
// DataTable
|
|
146
|
+
DataTable({ columns: [{ key, label, sortable, render }], data, sortable, paginate, pageSize, class })
|
|
147
|
+
|
|
148
|
+
// Statistic
|
|
149
|
+
Statistic({ label, value, prefix, suffix, trend: 'up'|'down', trendValue, icon, class })
|
|
150
|
+
|
|
151
|
+
// Badge
|
|
152
|
+
Badge({ variant: 'default'|'primary'|'success'|'warning'|'error', size }, ...children)
|
|
153
|
+
|
|
154
|
+
// Alert
|
|
155
|
+
Alert({ variant: 'default'|'info'|'success'|'warning'|'error' }, ...children)
|
|
156
|
+
|
|
157
|
+
// Dropdown
|
|
158
|
+
Dropdown({ trigger, items: [{ label, onclick }], class })
|
|
159
|
+
|
|
160
|
+
// Breadcrumb
|
|
161
|
+
Breadcrumb({ items: [{ label, href }] })
|
|
162
|
+
|
|
163
|
+
// Pagination
|
|
164
|
+
Pagination({ current, total, onChange })
|
|
165
|
+
|
|
166
|
+
// Progress
|
|
167
|
+
Progress({ value: 0-100, class })
|
|
168
|
+
|
|
169
|
+
// Skeleton
|
|
170
|
+
Skeleton({ width, height, class }) // Loading placeholder
|
|
171
|
+
|
|
172
|
+
// Avatar
|
|
173
|
+
Avatar({ src, name, size: 'sm'|'md'|'lg' })
|
|
174
|
+
|
|
175
|
+
// Separator
|
|
176
|
+
Separator() // Horizontal divider
|
|
177
|
+
|
|
178
|
+
// Chip
|
|
179
|
+
Chip({ label, onClose, variant })
|
|
180
|
+
|
|
181
|
+
// icon
|
|
182
|
+
icon('icon-name') // Returns SVG icon element
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
## 4. Chart API
|
|
188
|
+
|
|
189
|
+
```js
|
|
190
|
+
import { Chart, Sparkline } from 'decantr/chart';
|
|
191
|
+
|
|
192
|
+
// Full chart
|
|
193
|
+
Chart({
|
|
194
|
+
type: 'line'|'bar'|'area'|'pie'|'scatter'|'bubble'|'histogram'|'funnel'|
|
|
195
|
+
'radar'|'gauge'|'heatmap'|'treemap'|'candlestick'|'waterfall'|
|
|
196
|
+
'box-plot'|'range-bar'|'range-area'|'radial'|'sunburst'|'sankey'|
|
|
197
|
+
'chord'|'swimlane'|'org-chart'|'combination',
|
|
198
|
+
data: arrayOrSignal,
|
|
199
|
+
x: 'fieldName', // x-axis data key
|
|
200
|
+
y: 'fieldName', // y-axis data key (or array for multi-series)
|
|
201
|
+
title: 'Chart Title',
|
|
202
|
+
height: 300, // pixels
|
|
203
|
+
live: false, // enable streaming updates
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
// Inline sparkline (no axes, no labels)
|
|
207
|
+
Sparkline({ data: [1, 4, 2, 8, 3], height: 32 })
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### Working Examples
|
|
211
|
+
|
|
212
|
+
**Line chart:**
|
|
213
|
+
```js
|
|
214
|
+
Chart({
|
|
215
|
+
type: 'line',
|
|
216
|
+
data: [
|
|
217
|
+
{ date: '2024-01', revenue: 4200 },
|
|
218
|
+
{ date: '2024-02', revenue: 5100 },
|
|
219
|
+
{ date: '2024-03', revenue: 4800 },
|
|
220
|
+
],
|
|
221
|
+
x: 'date', y: 'revenue', title: 'Monthly Revenue', height: 300
|
|
222
|
+
})
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
**Bar chart:**
|
|
226
|
+
```js
|
|
227
|
+
Chart({
|
|
228
|
+
type: 'bar',
|
|
229
|
+
data: [
|
|
230
|
+
{ category: 'Electronics', sales: 1200 },
|
|
231
|
+
{ category: 'Clothing', sales: 800 },
|
|
232
|
+
{ category: 'Books', sales: 600 },
|
|
233
|
+
],
|
|
234
|
+
x: 'category', y: 'sales', title: 'Sales by Category', height: 280
|
|
235
|
+
})
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
**Pie chart:**
|
|
239
|
+
```js
|
|
240
|
+
Chart({
|
|
241
|
+
type: 'pie',
|
|
242
|
+
data: [
|
|
243
|
+
{ name: 'Desktop', value: 65 },
|
|
244
|
+
{ name: 'Mobile', value: 30 },
|
|
245
|
+
{ name: 'Tablet', value: 5 },
|
|
246
|
+
],
|
|
247
|
+
x: 'name', y: 'value', title: 'Traffic Sources', height: 280
|
|
248
|
+
})
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
---
|
|
252
|
+
|
|
253
|
+
## 5. Skeleton Code (5 layouts)
|
|
254
|
+
|
|
255
|
+
### sidebar-main
|
|
256
|
+
```js
|
|
257
|
+
function SidebarMain({ nav, children }) {
|
|
258
|
+
const { div, aside, main, header, span } = tags;
|
|
259
|
+
const [collapsed, setCollapsed] = createSignal(false);
|
|
260
|
+
|
|
261
|
+
return div({ class: css('_grid _h[100vh]'),
|
|
262
|
+
style: () => `grid-template-columns:${collapsed() ? '64px' : '240px'} 1fr;grid-template-rows:auto 1fr` },
|
|
263
|
+
aside({ class: css('_flex _col _gap1 _p3 _bgmuted _overflow[auto] _borderR'), style: 'grid-row:1/3' },
|
|
264
|
+
div({ class: css('_flex _aic _jcsb _mb4') },
|
|
265
|
+
cond(() => !collapsed(), () => span({ class: css('_heading5') }, 'App')),
|
|
266
|
+
Button({ variant: 'ghost', size: 'sm', onclick: () => setCollapsed(!collapsed()) }, icon('panel-left'))
|
|
267
|
+
),
|
|
268
|
+
...nav.map(item =>
|
|
269
|
+
link({ href: item.href, class: css('_flex _aic _gap2 _p2 _px3 _r2 _trans _fgfg') },
|
|
270
|
+
icon(item.icon), cond(() => !collapsed(), () => text(item.label))
|
|
271
|
+
)
|
|
272
|
+
)
|
|
273
|
+
),
|
|
274
|
+
header({ class: css('_flex _aic _jcsb _px6 _py3 _borderB') },
|
|
275
|
+
span({ class: css('_heading4') }, 'Page Title')
|
|
276
|
+
),
|
|
277
|
+
main({ class: css('_flex _col _gap4 _p6 _overflow[auto] _flex1') }, ...children)
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
### Children Rules
|
|
283
|
+
|
|
284
|
+
> **Function children = reactive TEXT only.** When an element receives a function as a child, the framework calls `String(fn())` and creates a reactive text node. It does **not** expect DOM elements from that function.
|
|
285
|
+
|
|
286
|
+
Use `cond()` for conditional DOM elements:
|
|
287
|
+
|
|
288
|
+
```js
|
|
289
|
+
// WRONG — returns "[object HTMLSpanElement]"
|
|
290
|
+
div({}, () => show() ? span({}, 'Hello') : null)
|
|
291
|
+
|
|
292
|
+
// CORRECT — use cond() for conditional DOM
|
|
293
|
+
div({}, cond(() => show(), () => span({}, 'Hello')))
|
|
294
|
+
|
|
295
|
+
// CORRECT — function children work for reactive text
|
|
296
|
+
span({}, () => `Count: ${count()}`)
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
### top-nav-main
|
|
300
|
+
```js
|
|
301
|
+
function TopNavMain({ brand, nav, children }) {
|
|
302
|
+
const { div, header, main, nav: navEl } = tags;
|
|
303
|
+
return div({ class: css('_flex _col _h[100vh]') },
|
|
304
|
+
header({ class: css('_flex _aic _jcsb _px6 _py3 _borderB _bgbg') },
|
|
305
|
+
link({ href: '/', class: css('_heading5 _nounder _fgfg') }, brand),
|
|
306
|
+
navEl({ class: css('_flex _aic _gap6') },
|
|
307
|
+
...nav.map(item => link({ href: item.href, class: css('_fgmuted _nounder _trans') }, item.label))
|
|
308
|
+
),
|
|
309
|
+
div({ class: css('_flex _aic _gap2') },
|
|
310
|
+
Button({ variant: 'ghost', size: 'sm' }, icon('search')),
|
|
311
|
+
Button({ variant: 'ghost', size: 'sm' }, icon('user'))
|
|
312
|
+
)
|
|
313
|
+
),
|
|
314
|
+
main({ class: css('_flex _col _gap4 _p6 _overflow[auto] _flex1') }, ...children)
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
### centered
|
|
320
|
+
```js
|
|
321
|
+
function Centered({ children, width = '400px' }) {
|
|
322
|
+
const { div } = tags;
|
|
323
|
+
return div({ class: css('_flex _center _h[100vh] _bgmuted _p4') },
|
|
324
|
+
Card({ class: css(`_w[${width}] _mw[100%]`) }, ...children)
|
|
325
|
+
);
|
|
326
|
+
}
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
### full-bleed
|
|
330
|
+
```js
|
|
331
|
+
function FullBleed({ children }) {
|
|
332
|
+
const { div, header, main, nav: navEl } = tags;
|
|
333
|
+
return div({ class: css('_flex _col') },
|
|
334
|
+
header({ class: css('_fixed _top0 _left0 _wfull _flex _aic _jcsb _px8 _py4 _z[40]') },
|
|
335
|
+
link({ href: '/', class: css('_heading5 _nounder _fgfg') }, 'Brand'),
|
|
336
|
+
navEl({ class: css('_flex _aic _gap6') })
|
|
337
|
+
),
|
|
338
|
+
main({ class: css('_flex _col') }, ...children)
|
|
339
|
+
);
|
|
340
|
+
}
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
### minimal-header
|
|
344
|
+
```js
|
|
345
|
+
function MinimalHeader({ brand, children }) {
|
|
346
|
+
const { div, header, main } = tags;
|
|
347
|
+
return div({ class: css('_flex _col _h[100vh]') },
|
|
348
|
+
header({ class: css('_flex _aic _jcc _py3 _borderB') },
|
|
349
|
+
link({ href: '/', class: css('_flex _aic _gap2 _nounder _fgfg') }, icon('arrow-left'), brand)
|
|
350
|
+
),
|
|
351
|
+
main({ class: css('_flex _col _aic _overflow[auto] _flex1 _py8') },
|
|
352
|
+
div({ class: css('_w[720px] _mw[100%] _px4 _flex _col _gap6') }, ...children)
|
|
353
|
+
)
|
|
354
|
+
);
|
|
355
|
+
}
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
---
|
|
359
|
+
|
|
360
|
+
## 6. Top 15 Pattern Code Snippets
|
|
361
|
+
|
|
362
|
+
> **Spacing note:** Snippets below use comfortable-density defaults (`_gap4`, `_p4`).
|
|
363
|
+
> Actual spacing must match the project's Clarity profile — see `reference/spatial-guidelines.md` §17.
|
|
364
|
+
|
|
365
|
+
### kpi-grid
|
|
366
|
+
```js
|
|
367
|
+
function KpiGrid() {
|
|
368
|
+
const { div, h2 } = tags;
|
|
369
|
+
return div({ class: css('_flex _col _gap4') },
|
|
370
|
+
h2({ class: css('_heading4') }, 'Key Metrics'),
|
|
371
|
+
div({ class: css('_grid _gc4 _gap4') },
|
|
372
|
+
Statistic({ label: 'Revenue', value: 1248500, prefix: '$', trend: 'up', trendValue: '+12.5%' }),
|
|
373
|
+
Statistic({ label: 'Users', value: 84230, trend: 'up', trendValue: '+8.1%' }),
|
|
374
|
+
Statistic({ label: 'Orders', value: 6420, trend: 'down', trendValue: '-2.3%' }),
|
|
375
|
+
Statistic({ label: 'Conversion', value: 3.24, suffix: '%', trend: 'up', trendValue: '+0.5%' })
|
|
376
|
+
)
|
|
377
|
+
);
|
|
378
|
+
}
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
### data-table
|
|
382
|
+
```js
|
|
383
|
+
function DataTablePattern({ columns, data }) {
|
|
384
|
+
const { div } = tags;
|
|
385
|
+
const [search, setSearch] = createSignal('');
|
|
386
|
+
return div({ class: css('_flex _col _gap4') },
|
|
387
|
+
div({ class: css('_flex _gap3 _aic _jcsb') },
|
|
388
|
+
Input({ placeholder: 'Search...', value: search, onchange: e => setSearch(e.target.value) }),
|
|
389
|
+
Button({ variant: 'outline' }, 'Export')
|
|
390
|
+
),
|
|
391
|
+
DataTable({ columns, data, sortable: true, paginate: true, pageSize: 10 })
|
|
392
|
+
);
|
|
393
|
+
}
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
### chart-grid
|
|
397
|
+
```js
|
|
398
|
+
function ChartGrid({ data }) {
|
|
399
|
+
const { div, h2 } = tags;
|
|
400
|
+
return div({ class: css('_flex _col _gap4') },
|
|
401
|
+
h2({ class: css('_heading4') }, 'Analytics'),
|
|
402
|
+
div({ class: css('_grid _gc2 _gap4') },
|
|
403
|
+
Chart({ type: 'line', data: data.revenue, x: 'date', y: 'value', title: 'Revenue', height: 280 }),
|
|
404
|
+
Chart({ type: 'bar', data: data.orders, x: 'month', y: 'count', title: 'Orders', height: 280 }),
|
|
405
|
+
Chart({ type: 'pie', data: data.categories, x: 'name', y: 'value', title: 'Categories', height: 280 }),
|
|
406
|
+
Chart({ type: 'area', data: data.traffic, x: 'date', y: 'visits', title: 'Traffic', height: 280 })
|
|
407
|
+
)
|
|
408
|
+
);
|
|
409
|
+
}
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
### hero
|
|
413
|
+
```js
|
|
414
|
+
function Hero() {
|
|
415
|
+
const { div, h1, p } = tags;
|
|
416
|
+
return div({ class: css('_flex _col _aic _tc _gap6 _py16 _px6') },
|
|
417
|
+
h1({ class: css('_heading1') }, 'Build Faster, Ship Smarter'),
|
|
418
|
+
p({ class: css('_body _fgmuted _mw[640px]') }, 'The AI-first framework that turns ideas into production-ready apps.'),
|
|
419
|
+
div({ class: css('_flex _gap3') },
|
|
420
|
+
Button({ variant: 'primary', size: 'lg' }, 'Get Started'),
|
|
421
|
+
Button({ variant: 'outline', size: 'lg' }, 'View Source')
|
|
422
|
+
)
|
|
423
|
+
);
|
|
424
|
+
}
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
### auth-form
|
|
428
|
+
```js
|
|
429
|
+
function AuthForm() {
|
|
430
|
+
const { div, h2, p, span } = tags;
|
|
431
|
+
const [email, setEmail] = createSignal('');
|
|
432
|
+
const [password, setPassword] = createSignal('');
|
|
433
|
+
return div({ class: css('_flex _col _aic _jcc _minhscreen _p6') },
|
|
434
|
+
Card({ class: css('_w[400px] _mw[100%]') },
|
|
435
|
+
Card.Header({},
|
|
436
|
+
h2({ class: css('_heading4 _tc') }, 'Sign In'),
|
|
437
|
+
p({ class: css('_fgmuted _tc _mt1') }, 'Enter your credentials')
|
|
438
|
+
),
|
|
439
|
+
Card.Body({ class: css('_flex _col _gap3') },
|
|
440
|
+
Input({ label: 'Email', type: 'email', value: email, onchange: e => setEmail(e.target.value) }),
|
|
441
|
+
Input({ label: 'Password', type: 'password', value: password, onchange: e => setPassword(e.target.value) }),
|
|
442
|
+
Button({ variant: 'primary', class: css('_wfull _mt2') }, 'Sign In')
|
|
443
|
+
),
|
|
444
|
+
Card.Footer({ class: css('_tc') },
|
|
445
|
+
span({ class: css('_fgmuted _textsm') }, 'No account? ', link({ href: '/register' }, 'Sign Up'))
|
|
446
|
+
)
|
|
447
|
+
)
|
|
448
|
+
);
|
|
449
|
+
}
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
### filter-bar
|
|
453
|
+
```js
|
|
454
|
+
function FilterBar() {
|
|
455
|
+
const { div } = tags;
|
|
456
|
+
const [search, setSearch] = createSignal('');
|
|
457
|
+
return div({ class: css('_flex _gap3 _aic _wrap _py3') },
|
|
458
|
+
Input({ placeholder: 'Search...', value: search, onchange: e => setSearch(e.target.value), class: css('_w[240px]') }),
|
|
459
|
+
Select({ value: 'all', options: [
|
|
460
|
+
{ label: 'All', value: 'all' }, { label: 'Active', value: 'active' }
|
|
461
|
+
] }),
|
|
462
|
+
DatePicker({ placeholder: 'Date range' }),
|
|
463
|
+
Button({ variant: 'ghost', size: 'sm' }, 'Clear')
|
|
464
|
+
);
|
|
465
|
+
}
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
### activity-feed
|
|
469
|
+
```js
|
|
470
|
+
function ActivityFeed({ items }) {
|
|
471
|
+
const { div, span, h3 } = tags;
|
|
472
|
+
return div({ class: css('_flex _col _gap2 _p4') },
|
|
473
|
+
h3({ class: css('_heading5') }, 'Recent Activity'),
|
|
474
|
+
...items.map(item =>
|
|
475
|
+
div({ class: css('_flex _gap3 _aic _py2 _borderB') },
|
|
476
|
+
Avatar({ src: item.avatar, name: item.user, size: 'sm' }),
|
|
477
|
+
div({ class: css('_flex _col _flex1') },
|
|
478
|
+
span({ class: css('_textsm') }, span({ class: css('_bold') }, item.user), ` ${item.action}`),
|
|
479
|
+
span({ class: css('_textxs _fgmuted') }, item.time)
|
|
480
|
+
),
|
|
481
|
+
Badge({ variant: 'default' }, item.type)
|
|
482
|
+
)
|
|
483
|
+
)
|
|
484
|
+
);
|
|
485
|
+
}
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
### card-grid (preset: product)
|
|
489
|
+
```js
|
|
490
|
+
function ProductGrid({ products }) {
|
|
491
|
+
const { div, span, h3 } = tags;
|
|
492
|
+
return div({ class: css('_grid _gcaf280 _gap4 _p4') },
|
|
493
|
+
...products.map(p =>
|
|
494
|
+
Card({},
|
|
495
|
+
Image({ src: p.image, alt: p.name, class: css('_wfull _h[200px] _object[cover]') }),
|
|
496
|
+
Card.Body({ class: css('_flex _col _gap2') },
|
|
497
|
+
h3({ class: css('_heading5') }, p.name),
|
|
498
|
+
span({ class: css('_fgmuted _textsm') }, p.description),
|
|
499
|
+
div({ class: css('_flex _aic _jcsb _mt2') },
|
|
500
|
+
span({ class: css('_heading4') }, `$${p.price}`),
|
|
501
|
+
Button({ variant: 'primary', size: 'sm' }, 'Add to Cart')
|
|
502
|
+
)
|
|
503
|
+
)
|
|
504
|
+
)
|
|
505
|
+
)
|
|
506
|
+
);
|
|
507
|
+
}
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
### pricing-table
|
|
511
|
+
```js
|
|
512
|
+
function PricingTable({ plans }) {
|
|
513
|
+
const { div, span, h3, ul, li } = tags;
|
|
514
|
+
return div({ class: css('_grid _gc3 _gap6 _p4 _aic') },
|
|
515
|
+
...plans.map(plan =>
|
|
516
|
+
Card({ class: css(plan.featured ? '_b2 _bcprimary' : '') },
|
|
517
|
+
Card.Header({ class: css('_tc') },
|
|
518
|
+
h3({ class: css('_heading4') }, plan.name),
|
|
519
|
+
div({ class: css('_mt2') },
|
|
520
|
+
span({ class: css('_heading1') }, `$${plan.price}`),
|
|
521
|
+
span({ class: css('_fgmuted') }, '/month')
|
|
522
|
+
)
|
|
523
|
+
),
|
|
524
|
+
Card.Body({},
|
|
525
|
+
ul({ class: css('_flex _col _gap2') },
|
|
526
|
+
...plan.features.map(f => li({ class: css('_flex _aic _gap2 _textsm') }, icon('check'), f))
|
|
527
|
+
)
|
|
528
|
+
),
|
|
529
|
+
Card.Footer({},
|
|
530
|
+
Button({ variant: plan.featured ? 'primary' : 'outline', class: css('_wfull') }, 'Get Started')
|
|
531
|
+
)
|
|
532
|
+
)
|
|
533
|
+
)
|
|
534
|
+
);
|
|
535
|
+
}
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
### contact-form
|
|
539
|
+
```js
|
|
540
|
+
function ContactForm() {
|
|
541
|
+
const { div, h2, p } = tags;
|
|
542
|
+
return Card({ class: css('_mw[600px] _mxa') },
|
|
543
|
+
Card.Header({},
|
|
544
|
+
h2({ class: css('_heading4') }, 'Get in Touch'),
|
|
545
|
+
p({ class: css('_fgmuted _mt1') }, 'We\'ll respond within 24 hours.')
|
|
546
|
+
),
|
|
547
|
+
Card.Body({ class: css('_flex _col _gap4') },
|
|
548
|
+
div({ class: css('_grid _gc2 _gap4') },
|
|
549
|
+
Input({ label: 'First Name' }), Input({ label: 'Last Name' })
|
|
550
|
+
),
|
|
551
|
+
Input({ label: 'Email', type: 'email' }),
|
|
552
|
+
Textarea({ label: 'Message', rows: 4 }),
|
|
553
|
+
Button({ variant: 'primary', class: css('_wfull') }, 'Send Message')
|
|
554
|
+
)
|
|
555
|
+
);
|
|
556
|
+
}
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
### card-grid (preset: content)
|
|
560
|
+
```js
|
|
561
|
+
function RecipeCardGrid({ recipes }) {
|
|
562
|
+
const { div, span, h3, img } = tags;
|
|
563
|
+
return div({ class: css('_grid _gc3 _gap6 _p4') },
|
|
564
|
+
...recipes.map(r =>
|
|
565
|
+
Card({},
|
|
566
|
+
img({ src: r.image, alt: r.title, class: css('_wfull _h[200px] _object[cover]') }),
|
|
567
|
+
Card.Body({ class: css('_flex _col _gap2') },
|
|
568
|
+
h3({ class: css('_heading5') }, r.title),
|
|
569
|
+
span({ class: css('_caption _fgmuted') }, r.description),
|
|
570
|
+
div({ class: css('_flex _aic _gap3 _fgmuted _textsm') },
|
|
571
|
+
span({}, icon('clock'), ` ${r.time} min`),
|
|
572
|
+
span({}, icon('users'), ` ${r.servings} servings`)
|
|
573
|
+
),
|
|
574
|
+
div({ class: css('_flex _gap1 _wrap') },
|
|
575
|
+
...r.tags.map(t => Chip({ size: 'sm' }, t))
|
|
576
|
+
),
|
|
577
|
+
div({ class: css('_flex _aic _jcsb _mt2') },
|
|
578
|
+
div({ class: css('_flex _aic _gap2') },
|
|
579
|
+
Avatar({ src: r.author.avatar, size: 'xs' }),
|
|
580
|
+
span({ class: css('_textsm') }, r.author.name)
|
|
581
|
+
),
|
|
582
|
+
span({ class: css('_textsm _fgmuted') }, icon('git-fork'), ` ${r.forks}`)
|
|
583
|
+
)
|
|
584
|
+
)
|
|
585
|
+
)
|
|
586
|
+
)
|
|
587
|
+
);
|
|
588
|
+
}
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
### form-sections (preset: creation)
|
|
592
|
+
```js
|
|
593
|
+
function RecipeFormSimple() {
|
|
594
|
+
const { div, h3 } = tags;
|
|
595
|
+
const [ingredients, setIngredients] = createSignal(['']);
|
|
596
|
+
const [instructions, setInstructions] = createSignal(['']);
|
|
597
|
+
|
|
598
|
+
return div({ class: css('_flex _col _gap6 _mw[720px] _mxAuto _p4') },
|
|
599
|
+
Upload({ accept: 'image/*', variant: 'dragger' }, 'Upload recipe photo'),
|
|
600
|
+
Card({},
|
|
601
|
+
Card.Header({}, h3({ class: css('_heading5') }, 'Basic Info')),
|
|
602
|
+
Card.Body({ class: css('_flex _col _gap3') },
|
|
603
|
+
Input({ label: 'Title', placeholder: 'Recipe name' }),
|
|
604
|
+
Textarea({ label: 'Description', rows: 3 })
|
|
605
|
+
)
|
|
606
|
+
),
|
|
607
|
+
Card({},
|
|
608
|
+
Card.Header({}, h3({ class: css('_heading5') }, 'Details')),
|
|
609
|
+
Card.Body({ class: css('_flex _col _gap3') },
|
|
610
|
+
div({ class: css('_grid _gc3 _gap3') },
|
|
611
|
+
InputNumber({ label: 'Prep (min)', min: 0 }),
|
|
612
|
+
InputNumber({ label: 'Cook (min)', min: 0 }),
|
|
613
|
+
InputNumber({ label: 'Servings', min: 1 })
|
|
614
|
+
),
|
|
615
|
+
Segmented({ label: 'Difficulty', options: ['Easy', 'Medium', 'Hard'] })
|
|
616
|
+
)
|
|
617
|
+
),
|
|
618
|
+
Card({},
|
|
619
|
+
Card.Header({}, h3({ class: css('_heading5') }, 'Ingredients')),
|
|
620
|
+
Card.Body({ class: css('_flex _col _gap2') },
|
|
621
|
+
...ingredients().map((_, i) => Input({ placeholder: `Ingredient ${i + 1}` })),
|
|
622
|
+
Button({ variant: 'outline', size: 'sm' }, icon('plus'), 'Add Ingredient')
|
|
623
|
+
)
|
|
624
|
+
),
|
|
625
|
+
Card({},
|
|
626
|
+
Card.Header({}, h3({ class: css('_heading5') }, 'Instructions')),
|
|
627
|
+
Card.Body({ class: css('_flex _col _gap2') },
|
|
628
|
+
...instructions().map((_, i) => Textarea({ placeholder: `Step ${i + 1}`, rows: 2 })),
|
|
629
|
+
Button({ variant: 'outline', size: 'sm' }, icon('plus'), 'Add Step')
|
|
630
|
+
)
|
|
631
|
+
),
|
|
632
|
+
div({ class: css('_flex _jce _gap3') },
|
|
633
|
+
Button({ variant: 'outline' }, 'Save Draft'),
|
|
634
|
+
Button({ variant: 'primary' }, 'Publish Recipe')
|
|
635
|
+
)
|
|
636
|
+
);
|
|
637
|
+
}
|
|
638
|
+
```
|
|
639
|
+
|
|
640
|
+
### chat-interface
|
|
641
|
+
```js
|
|
642
|
+
function ChatInterface() {
|
|
643
|
+
const { div, p } = tags;
|
|
644
|
+
const [input, setInput] = createSignal('');
|
|
645
|
+
const [messages, setMessages] = createSignal([
|
|
646
|
+
{ role: 'assistant', text: 'Hi! I\'m your AI chef assistant.' }
|
|
647
|
+
]);
|
|
648
|
+
const suggestions = ['What can I make with chicken?', 'Suggest a quick dinner'];
|
|
649
|
+
|
|
650
|
+
const send = () => {
|
|
651
|
+
if (!input()) return;
|
|
652
|
+
setMessages([...messages(), { role: 'user', text: input() }]);
|
|
653
|
+
setInput('');
|
|
654
|
+
};
|
|
655
|
+
|
|
656
|
+
return div({ class: css('_flex _col _h[calc(100vh-200px)] _b1 _r4 _overflow[hidden]') },
|
|
657
|
+
ScrollArea({ class: css('_flex1 _p4') },
|
|
658
|
+
div({ class: css('_flex _col _gap3') },
|
|
659
|
+
...messages().map(m =>
|
|
660
|
+
div({ class: css(m.role === 'user' ? '_flex _jce' : '_flex _gap2') },
|
|
661
|
+
m.role === 'assistant' ? Avatar({ size: 'sm', fallback: 'AI' }) : null,
|
|
662
|
+
div({ class: css(`_p3 _r4 _mw[70%] ${m.role === 'user' ? '_bgprimary' : '_bgmuted'}`) },
|
|
663
|
+
p({}, m.text)
|
|
664
|
+
)
|
|
665
|
+
)
|
|
666
|
+
)
|
|
667
|
+
)
|
|
668
|
+
),
|
|
669
|
+
div({ class: css('_flex _gap2 _p3 _borderb') },
|
|
670
|
+
...suggestions.map(s => Chip({ variant: 'outline', onclick: () => setInput(s) }, s))
|
|
671
|
+
),
|
|
672
|
+
div({ class: css('_flex _gap2 _p3 _bordert') },
|
|
673
|
+
Input({ placeholder: 'Ask your chef assistant...', value: input, class: css('_flex1') }),
|
|
674
|
+
Button({ variant: 'primary', onclick: send }, icon('send'))
|
|
675
|
+
)
|
|
676
|
+
);
|
|
677
|
+
}
|
|
678
|
+
```
|
|
679
|
+
|
|
680
|
+
### photo-to-recipe
|
|
681
|
+
```js
|
|
682
|
+
function PhotoToRecipe() {
|
|
683
|
+
const { div, h3, p } = tags;
|
|
684
|
+
const [analyzing, setAnalyzing] = createSignal(false);
|
|
685
|
+
const [result, setResult] = createSignal(null);
|
|
686
|
+
|
|
687
|
+
return div({ class: css('_grid _gc2 _gap6 _p4') },
|
|
688
|
+
Card({},
|
|
689
|
+
Card.Header({}, h3({ class: css('_heading5') }, icon('camera'), ' Upload Photo')),
|
|
690
|
+
Card.Body({ class: css('_flex _col _gap4 _aic') },
|
|
691
|
+
Upload({ accept: 'image/*', variant: 'dragger', class: css('_wfull') }, 'Drop a food photo here'),
|
|
692
|
+
Button({ variant: 'primary', class: css('_wfull'), onclick: () => setAnalyzing(true) },
|
|
693
|
+
analyzing() ? Spinner({ size: 'sm' }) : icon('sparkles'),
|
|
694
|
+
analyzing() ? ' Analyzing...' : ' Generate Recipe'
|
|
695
|
+
)
|
|
696
|
+
)
|
|
697
|
+
),
|
|
698
|
+
Card({},
|
|
699
|
+
Card.Header({}, h3({ class: css('_heading5') }, icon('sparkles'), ' AI Generated Recipe')),
|
|
700
|
+
Card.Body({},
|
|
701
|
+
result()
|
|
702
|
+
? div({ class: css('_flex _col _gap3') }, h3({ class: css('_heading4') }, result().title))
|
|
703
|
+
: p({ class: css('_fgmuted _tc') }, 'Upload a photo to generate a recipe')
|
|
704
|
+
)
|
|
705
|
+
)
|
|
706
|
+
);
|
|
707
|
+
}
|
|
708
|
+
```
|
|
709
|
+
|
|
710
|
+
### card-grid (preset: icon)
|
|
711
|
+
```js
|
|
712
|
+
function FeatureGrid({ features }) {
|
|
713
|
+
const { div, h3, p } = tags;
|
|
714
|
+
return div({ class: css('_grid _gc3 _gap6 _p4') },
|
|
715
|
+
...features.map(f =>
|
|
716
|
+
Card({},
|
|
717
|
+
Card.Body({ class: css('_flex _col _gap3 _aic _tc') },
|
|
718
|
+
div({ class: css('_w[48px] _h[48px] _r4 _bgmuted _flex _aic _jcc _fgprimary') }, icon(f.icon)),
|
|
719
|
+
h3({ class: css('_heading5') }, f.title),
|
|
720
|
+
p({ class: css('_caption _fgmuted') }, f.description)
|
|
721
|
+
)
|
|
722
|
+
)
|
|
723
|
+
)
|
|
724
|
+
);
|
|
725
|
+
}
|
|
726
|
+
```
|
|
727
|
+
|
|
728
|
+
---
|
|
729
|
+
|
|
730
|
+
## 7. Recipe Application Guide
|
|
731
|
+
|
|
732
|
+
Recipes overlay decorative classes onto standard components. The pattern is always: **standard component + recipe wrapper classes**.
|
|
733
|
+
|
|
734
|
+
### Without recipe (plain):
|
|
735
|
+
```js
|
|
736
|
+
div({ class: css('_b1 _r4 _p4') },
|
|
737
|
+
Statistic({ label: 'Revenue', value: 125000, prefix: '$' })
|
|
738
|
+
)
|
|
739
|
+
```
|
|
740
|
+
|
|
741
|
+
### With auradecantism recipe:
|
|
742
|
+
```js
|
|
743
|
+
div({ class: css('d-glass _r4 _p4') }, // glass panel wrapper
|
|
744
|
+
Statistic({ label: 'Revenue', value: 125000, prefix: '$' }),
|
|
745
|
+
span({ class: css('d-gradient-text _textxs') }, 'ALL TIME HIGH') // gradient accent text
|
|
746
|
+
)
|
|
747
|
+
```
|
|
748
|
+
|
|
749
|
+
### With command-center recipe:
|
|
750
|
+
```js
|
|
751
|
+
div({ class: css('cc-frame-sm cc-glow') }, // beveled frame + glow
|
|
752
|
+
Statistic({ label: 'ACTIVE THREATS', value: 42, icon: 'alert-triangle', class: css('_bg[transparent]') })
|
|
753
|
+
)
|
|
754
|
+
```
|
|
755
|
+
|
|
756
|
+
### With clean recipe:
|
|
757
|
+
```js
|
|
758
|
+
div({ class: css('_b1 _r4 _p4') }, // simple border + radius (tokens handle the rest)
|
|
759
|
+
Statistic({ label: 'Revenue', value: 125000, prefix: '$' })
|
|
760
|
+
)
|
|
761
|
+
```
|
|
762
|
+
|
|
763
|
+
**Key principle:** Recipes don't change which components you use — they change how you wrap and decorate them.
|
|
764
|
+
|
|
765
|
+
---
|
|
766
|
+
|
|
767
|
+
## 8. Styles & Modes
|
|
768
|
+
|
|
769
|
+
```js
|
|
770
|
+
import { setStyle, setMode, registerStyle } from 'decantr/css';
|
|
771
|
+
|
|
772
|
+
// Core style (always available, no import needed)
|
|
773
|
+
setStyle('auradecantism'); // Default: glass, gradients, vibrant (dark)
|
|
774
|
+
|
|
775
|
+
// Add-on styles (require import + registerStyle before use)
|
|
776
|
+
// Available: clean, retro, glassmorphism, command-center,
|
|
777
|
+
// bioluminescent, clay, dopamine, editorial, liquid-glass, prismatic
|
|
778
|
+
import { clean as cleanStyle } from 'decantr/styles/clean';
|
|
779
|
+
registerStyle(cleanStyle);
|
|
780
|
+
setStyle('clean');
|
|
781
|
+
|
|
782
|
+
// Available modes
|
|
783
|
+
setMode('light');
|
|
784
|
+
setMode('dark');
|
|
785
|
+
setMode('auto'); // Follows system preference
|
|
786
|
+
```
|
|
787
|
+
|
|
788
|
+
**11 styles total:** `auradecantism` (core, default), `clean`, `retro`, `glassmorphism`, `command-center`, `bioluminescent`, `clay`, `dopamine`, `editorial`, `liquid-glass`, `prismatic` (all add-on).
|
|
789
|
+
|
|
790
|
+
### Interactive State Atoms (Pseudo-Class Prefixes)
|
|
791
|
+
|
|
792
|
+
```js
|
|
793
|
+
// Hover, focus, focus-visible, active, focus-within — compose with ANY atom
|
|
794
|
+
css('_h:bgprimary') // background on hover
|
|
795
|
+
css('_f:bcprimary') // border-color on focus
|
|
796
|
+
css('_fv:ring2') // ring on keyboard focus
|
|
797
|
+
css('_a:bgmuted') // background on press
|
|
798
|
+
css('_fw:bcprimary') // border when child focused
|
|
799
|
+
css('_sm:h:bgmuted') // responsive + hover
|
|
800
|
+
css('_h:bgprimary/50') // hover + opacity modifier
|
|
801
|
+
```
|
|
802
|
+
|
|
803
|
+
### Ring, Transition, Prose, Divide
|
|
804
|
+
|
|
805
|
+
```js
|
|
806
|
+
css('_ring2 _ringPrimary') // 2px primary ring
|
|
807
|
+
css('_fv:ring2 _ringAccent') // ring on keyboard focus
|
|
808
|
+
css('_transColors') // smooth color transitions
|
|
809
|
+
css('_prose') // rich text typography
|
|
810
|
+
css('_divideY') // borders between stacked children
|
|
811
|
+
css('_textBalance') // balanced line wrapping
|
|
812
|
+
```
|
|
813
|
+
|
|
814
|
+
### Opacity Modifiers
|
|
815
|
+
Works on all semantic color atoms: `_bgprimary/50`, `_fgaccent/30`, `_bcborder/80`
|
|
816
|
+
|
|
817
|
+
---
|
|
818
|
+
|
|
819
|
+
## 9. State Management Quick Reference
|
|
820
|
+
|
|
821
|
+
```js
|
|
822
|
+
// Signal (reactive value)
|
|
823
|
+
const [count, setCount] = createSignal(0);
|
|
824
|
+
count(); // Read (triggers tracking)
|
|
825
|
+
setCount(5); // Write (triggers updates)
|
|
826
|
+
setCount(c => c + 1); // Updater function
|
|
827
|
+
|
|
828
|
+
// Effect (runs when dependencies change)
|
|
829
|
+
createEffect(() => {
|
|
830
|
+
console.log('Count is now:', count()); // auto-tracks count
|
|
831
|
+
});
|
|
832
|
+
|
|
833
|
+
// Memo (cached derived value)
|
|
834
|
+
const doubled = createMemo(() => count() * 2);
|
|
835
|
+
|
|
836
|
+
// Store (reactive object)
|
|
837
|
+
const [user, setUser] = createStore({ name: 'Alice', settings: { theme: 'dark' } });
|
|
838
|
+
setUser('name', 'Bob'); // Update field
|
|
839
|
+
setUser('settings', 'theme', 'light'); // Nested update
|
|
840
|
+
```
|
|
841
|
+
|
|
842
|
+
---
|
|
843
|
+
|
|
844
|
+
## 10. Routing Quick Reference
|
|
845
|
+
|
|
846
|
+
```js
|
|
847
|
+
const router = createRouter({
|
|
848
|
+
mode: 'hash', // or 'history'
|
|
849
|
+
base: '/app', // optional — for subdirectory deployments
|
|
850
|
+
routes: [
|
|
851
|
+
{ path: '/', component: HomePage },
|
|
852
|
+
{ path: '/products', component: ProductsPage },
|
|
853
|
+
{ path: '/product/:id', component: ProductPage },
|
|
854
|
+
{ path: '/admin', component: AdminPage, meta: { requiresAuth: true, title: 'Admin' } },
|
|
855
|
+
{ path: '/:404', component: NotFoundPage },
|
|
856
|
+
],
|
|
857
|
+
beforeEach: (to, from) => {
|
|
858
|
+
// Guard can read to.meta (merged parent→child)
|
|
859
|
+
if (to.meta.requiresAuth && !isLoggedIn()) return '/login';
|
|
860
|
+
},
|
|
861
|
+
});
|
|
862
|
+
|
|
863
|
+
// Navigation listener (subscribe/unsubscribe)
|
|
864
|
+
const unsub = router.onNavigate((to, from) => {
|
|
865
|
+
analytics.track('page_view', { path: to.path, from: from.path });
|
|
866
|
+
});
|
|
867
|
+
// Later: unsub();
|
|
868
|
+
|
|
869
|
+
// Standalone variant:
|
|
870
|
+
// import { onNavigate } from 'decantr/router';
|
|
871
|
+
// const unsub = onNavigate((to, from) => { /* track */ });
|
|
872
|
+
|
|
873
|
+
// Navigation
|
|
874
|
+
navigate('/products');
|
|
875
|
+
navigate('/product/42');
|
|
876
|
+
back(); // history.back() — fires guards
|
|
877
|
+
forward(); // history.forward() — fires guards
|
|
878
|
+
|
|
879
|
+
// Loading indicator for lazy routes
|
|
880
|
+
// isNavigating() → reactive boolean, true while lazy components resolve
|
|
881
|
+
|
|
882
|
+
// Route params + meta
|
|
883
|
+
const route = useRoute();
|
|
884
|
+
route.params.id; // '42'
|
|
885
|
+
route.query.sort; // from ?sort=price
|
|
886
|
+
route.meta; // { requiresAuth: true, title: 'Admin' }
|
|
887
|
+
|
|
888
|
+
// Link component
|
|
889
|
+
link({ href: '/products', class: css('_fgprimary') }, 'Products')
|
|
890
|
+
```
|
|
891
|
+
|
|
892
|
+
### Complete SPA Entry Point
|
|
893
|
+
|
|
894
|
+
```js
|
|
895
|
+
import { mount } from 'decantr/core';
|
|
896
|
+
import { createRouter } from 'decantr/router';
|
|
897
|
+
|
|
898
|
+
const router = createRouter({
|
|
899
|
+
mode: 'hash',
|
|
900
|
+
routes: [
|
|
901
|
+
{ path: '/', component: HomePage },
|
|
902
|
+
{ path: '/about', component: AboutPage },
|
|
903
|
+
]
|
|
904
|
+
});
|
|
905
|
+
|
|
906
|
+
// mount(target, renderFn) — target is a DOM element, renderFn returns the root node
|
|
907
|
+
mount(document.getElementById('app'), () => router.outlet());
|
|
908
|
+
```
|
|
909
|
+
|
|
910
|
+
> `createRouter()` returns a plain object with `{ navigate, outlet, current, path, destroy }`. Call `router.outlet()` to get the DOM element that reactively renders the matched route. Do **not** pass the router object itself to `mount()`.
|
|
911
|
+
|
|
912
|
+
---
|
|
913
|
+
|
|
914
|
+
## 11. Common Layout Patterns
|
|
915
|
+
|
|
916
|
+
### Dashboard page (sidebar-main skeleton)
|
|
917
|
+
```js
|
|
918
|
+
// Grid: 240px sidebar + 1fr main
|
|
919
|
+
div({ class: css('_grid _h[100vh]'), style: 'grid-template-columns:240px 1fr' },
|
|
920
|
+
aside({ class: css('_bgmuted _borderR _p3 _flex _col _gap1') }, /* nav */),
|
|
921
|
+
main({ class: css('_flex _col _overflow[hidden]') },
|
|
922
|
+
header({ class: css('_flex _aic _jcsb _px6 _py3 _borderB') }, /* title + actions */),
|
|
923
|
+
div({ class: css('_flex _col _gap4 _p6 _overflow[auto] _flex1') }, /* content */)
|
|
924
|
+
)
|
|
925
|
+
)
|
|
926
|
+
```
|
|
927
|
+
|
|
928
|
+
### Two-column layout (content + sidebar)
|
|
929
|
+
```js
|
|
930
|
+
div({ class: css('_grid _gap6'), style: 'grid-template-columns:1fr 300px' },
|
|
931
|
+
main(/* primary content */),
|
|
932
|
+
aside(/* sidebar */)
|
|
933
|
+
)
|
|
934
|
+
```
|
|
935
|
+
|
|
936
|
+
### Responsive card grid
|
|
937
|
+
```js
|
|
938
|
+
div({ class: css('_grid _gcaf280 _gap4 _p4') }, // auto-fit columns, min 280px
|
|
939
|
+
...items.map(item => Card(/* ... */))
|
|
940
|
+
)
|
|
941
|
+
```
|
|
942
|
+
|
|
943
|
+
### Form layout
|
|
944
|
+
```js
|
|
945
|
+
Card({ class: css('_mw[600px] _mxa') },
|
|
946
|
+
Card.Header({}, h2({ class: css('_heading4') }, 'Form Title')),
|
|
947
|
+
Card.Body({ class: css('_flex _col _gap4') },
|
|
948
|
+
div({ class: css('_grid _gc2 _gap4') }, Input({ label: 'First' }), Input({ label: 'Last' })),
|
|
949
|
+
Input({ label: 'Email', type: 'email' }),
|
|
950
|
+
Button({ variant: 'primary', class: css('_wfull') }, 'Submit')
|
|
951
|
+
)
|
|
952
|
+
)
|
|
953
|
+
```
|