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,178 @@
|
|
|
1
|
+
import { css } from 'decantr/css';
|
|
2
|
+
import { tags } from 'decantr/tags';
|
|
3
|
+
import { icon, Separator, Slider, Switch } from 'decantr/components';
|
|
4
|
+
import { createSignal, createEffect } from 'decantr/state';
|
|
5
|
+
import { getIconPath } from 'decantr/icons';
|
|
6
|
+
import { injectExplorerCSS } from './styles.js';
|
|
7
|
+
injectExplorerCSS();
|
|
8
|
+
|
|
9
|
+
const { div, h2, h3, p, span, section, pre, label } = tags;
|
|
10
|
+
|
|
11
|
+
// ─── Icon Groups (loaded from registry) ──────────────────────────
|
|
12
|
+
let ICON_GROUPS = [];
|
|
13
|
+
const GROUP_MAP = {};
|
|
14
|
+
|
|
15
|
+
async function ensureGroups() {
|
|
16
|
+
if (ICON_GROUPS.length) return;
|
|
17
|
+
try {
|
|
18
|
+
const resp = await fetch('/__decantr/registry/icons.json');
|
|
19
|
+
const reg = await resp.json();
|
|
20
|
+
const groups = reg.groups || {};
|
|
21
|
+
ICON_GROUPS = Object.entries(groups).map(([id, g]) => ({
|
|
22
|
+
id, label: g.label, desc: g.description, icons: g.icons,
|
|
23
|
+
}));
|
|
24
|
+
} catch {
|
|
25
|
+
ICON_GROUPS = [];
|
|
26
|
+
}
|
|
27
|
+
for (const g of ICON_GROUPS) for (const name of g.icons) GROUP_MAP[name] = g.id;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Structural icons used internally by framework components
|
|
31
|
+
const STRUCTURAL = new Set([
|
|
32
|
+
'chevron-down', 'chevron-up', 'chevron-left', 'chevron-right',
|
|
33
|
+
'check', 'x', 'calendar', 'clock', 'search', 'arrow-up',
|
|
34
|
+
'grip-vertical', 'info', 'check-circle', 'alert-triangle', 'x-circle',
|
|
35
|
+
]);
|
|
36
|
+
|
|
37
|
+
// ─── Weight/Fill Control Bar ────────────────────────────────────
|
|
38
|
+
function iconControlBar(weightSignal, filledSignal) {
|
|
39
|
+
const [weight, setWeight] = weightSignal;
|
|
40
|
+
const [filled, setFilled] = filledSignal;
|
|
41
|
+
return div({ class: css('_flex _aic _gap4 _p3 _r2 _mb2') },
|
|
42
|
+
div({ class: css('_flex _aic _gap2 _flex1 _maxw[400px]') },
|
|
43
|
+
label({ class: css('_caption _fgmutedfg') }, 'Weight'),
|
|
44
|
+
Slider({ min: 0.5, max: 4, step: 0.5, value: weight, showValue: true, onchange: setWeight })
|
|
45
|
+
),
|
|
46
|
+
div({ class: css('_flex _aic _gap2') },
|
|
47
|
+
Switch({ label: 'Filled', checked: filled, onchange: setFilled })
|
|
48
|
+
)
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// ─── Icon Cell ──────────────────────────────────────────────────
|
|
53
|
+
function iconCell(name, navigateTo, weight, filled) {
|
|
54
|
+
const groupId = GROUP_MAP[name] || ICON_GROUPS[0].id;
|
|
55
|
+
return div({
|
|
56
|
+
class: 'de-icon-cell' + (STRUCTURAL.has(name) ? ' de-structural' : ''),
|
|
57
|
+
onclick: () => navigateTo(`/icons/${groupId}/${name}`),
|
|
58
|
+
title: name,
|
|
59
|
+
},
|
|
60
|
+
icon(name, { size: '1.25rem', weight, filled }),
|
|
61
|
+
span({ class: 'de-icon-cell-name' }, name)
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ─── Icon Group View ────────────────────────────────────────────
|
|
66
|
+
export function IconGroupView(groupId, navigateTo) {
|
|
67
|
+
const group = ICON_GROUPS.find(g => g.id === groupId);
|
|
68
|
+
if (!group) return div({}, p({ class: css('_fgmutedfg') }, 'Group not found.'));
|
|
69
|
+
|
|
70
|
+
const weightSignal = createSignal(2);
|
|
71
|
+
const filledSignal = createSignal(false);
|
|
72
|
+
const [weight] = weightSignal;
|
|
73
|
+
const [filled] = filledSignal;
|
|
74
|
+
|
|
75
|
+
const gridContainer = div({ class: 'de-icon-grid' });
|
|
76
|
+
|
|
77
|
+
function rebuildGrid() {
|
|
78
|
+
gridContainer.innerHTML = '';
|
|
79
|
+
const w = weight();
|
|
80
|
+
const f = filled();
|
|
81
|
+
for (const name of group.icons) {
|
|
82
|
+
gridContainer.appendChild(iconCell(name, navigateTo, w, f));
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
rebuildGrid();
|
|
87
|
+
createEffect(() => { weight(); filled(); rebuildGrid(); });
|
|
88
|
+
|
|
89
|
+
return section({ class: css('_flex _col _gap4') },
|
|
90
|
+
h2({ class: css('_heading4') }, `Icons \u2014 ${group.label}`),
|
|
91
|
+
p({ class: css('_body _fgmutedfg') }, `${group.icons.length} icons. ${group.desc}`),
|
|
92
|
+
iconControlBar(weightSignal, filledSignal),
|
|
93
|
+
gridContainer
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// ─── Icon Detail View ───────────────────────────────────────────
|
|
98
|
+
export function IconDetail(iconName) {
|
|
99
|
+
const svgPath = getIconPath(iconName);
|
|
100
|
+
if (!svgPath) return div({}, p({ class: css('_fgmutedfg') }, `Unknown icon: ${iconName}`));
|
|
101
|
+
|
|
102
|
+
const isStructural = STRUCTURAL.has(iconName);
|
|
103
|
+
const weightSignal = createSignal(2);
|
|
104
|
+
const filledSignal = createSignal(false);
|
|
105
|
+
const [weight] = weightSignal;
|
|
106
|
+
const [filled] = filledSignal;
|
|
107
|
+
|
|
108
|
+
// Reactive preview + sizes
|
|
109
|
+
const previewBox = div({ class: 'de-demo-box' });
|
|
110
|
+
const sizesRow = div({ class: 'de-icon-preview-sizes' });
|
|
111
|
+
const usageBlock = pre({ class: 'de-icon-code' });
|
|
112
|
+
|
|
113
|
+
function rebuildPreview() {
|
|
114
|
+
const w = weight();
|
|
115
|
+
const f = filled();
|
|
116
|
+
previewBox.innerHTML = '';
|
|
117
|
+
previewBox.appendChild(icon(iconName, { size: '3rem', weight: w, filled: f }));
|
|
118
|
+
sizesRow.innerHTML = '';
|
|
119
|
+
for (const size of ['1rem', '1.25rem', '1.5rem', '2rem', '3rem']) {
|
|
120
|
+
const cell = div({ class: 'de-icon-preview-size' },
|
|
121
|
+
icon(iconName, { size, weight: w, filled: f }),
|
|
122
|
+
span({ class: 'de-icon-preview-label' }, size)
|
|
123
|
+
);
|
|
124
|
+
sizesRow.appendChild(cell);
|
|
125
|
+
}
|
|
126
|
+
// Update usage snippets
|
|
127
|
+
const wOpt = w !== 2 ? `, weight: ${w}` : '';
|
|
128
|
+
const fOpt = f ? `, filled: true` : '';
|
|
129
|
+
const optsStr = (wOpt || fOpt) ? `, {${wOpt}${fOpt} }` : '';
|
|
130
|
+
usageBlock.textContent =
|
|
131
|
+
`import { icon } from 'decantr/components';\n\n` +
|
|
132
|
+
`// Default size (1.25em)\nicon('${iconName}'${optsStr})\n\n` +
|
|
133
|
+
`// Custom size\nicon('${iconName}', { size: '2rem'${wOpt}${fOpt} })\n\n` +
|
|
134
|
+
`// In a button\nButton({ 'aria-label': '${iconName}' }, icon('${iconName}'${optsStr}))`;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
rebuildPreview();
|
|
138
|
+
createEffect(() => { weight(); filled(); rebuildPreview(); });
|
|
139
|
+
|
|
140
|
+
// SVG source
|
|
141
|
+
const svgSource = div({ class: css('_flex _col _gap3') },
|
|
142
|
+
h3({ class: css('_heading6') }, 'SVG Source'),
|
|
143
|
+
pre({ class: 'de-icon-code' }, svgPath)
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
return div({ class: css('_flex _col _gap6') },
|
|
147
|
+
div({ class: css('_flex _aic _gap4 _mb2') },
|
|
148
|
+
previewBox,
|
|
149
|
+
div({ class: css('_flex _col _gap1') },
|
|
150
|
+
h2({ class: css('_heading4') }, iconName),
|
|
151
|
+
isStructural
|
|
152
|
+
? span({ class: css('_caption _fgprimary') }, 'Structural \u2014 used by framework components')
|
|
153
|
+
: null
|
|
154
|
+
)
|
|
155
|
+
),
|
|
156
|
+
iconControlBar(weightSignal, filledSignal),
|
|
157
|
+
div({ class: css('_flex _col _gap3') },
|
|
158
|
+
h3({ class: css('_heading6') }, 'Sizes'),
|
|
159
|
+
sizesRow
|
|
160
|
+
),
|
|
161
|
+
Separator({}),
|
|
162
|
+
div({ class: css('_flex _col _gap3') },
|
|
163
|
+
h3({ class: css('_heading6') }, 'Usage'),
|
|
164
|
+
usageBlock
|
|
165
|
+
),
|
|
166
|
+
Separator({}),
|
|
167
|
+
svgSource
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// ─── Sidebar Data Loader ────────────────────────────────────────
|
|
172
|
+
export async function loadIconItems() {
|
|
173
|
+
await ensureGroups();
|
|
174
|
+
return ICON_GROUPS.map(g => ({
|
|
175
|
+
id: g.id,
|
|
176
|
+
label: g.label,
|
|
177
|
+
}));
|
|
178
|
+
}
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
import { css } from 'decantr/css';
|
|
2
|
+
import { tags } from 'decantr/tags';
|
|
3
|
+
import { Chip, Separator, Tabs, CodeBlock } from 'decantr/components';
|
|
4
|
+
import { renderPatternExample, hasPatternExample } from './shared/pattern-examples.js';
|
|
5
|
+
import { injectExplorerCSS } from './styles.js';
|
|
6
|
+
injectExplorerCSS();
|
|
7
|
+
|
|
8
|
+
const { div, h2, h3, h4, p, span, section, code, strong } = tags;
|
|
9
|
+
|
|
10
|
+
let patternsData = {};
|
|
11
|
+
let patternsLoaded = false;
|
|
12
|
+
|
|
13
|
+
async function loadPatterns() {
|
|
14
|
+
if (patternsLoaded) return patternsData;
|
|
15
|
+
try {
|
|
16
|
+
const indexResp = await fetch('/__decantr/registry/patterns/index.json');
|
|
17
|
+
const index = await indexResp.json();
|
|
18
|
+
const entries = Object.entries(index.patterns || {});
|
|
19
|
+
for (const [id, meta] of entries) {
|
|
20
|
+
try {
|
|
21
|
+
const resp = await fetch(`/__decantr/registry/patterns/${meta.file || id + '.json'}`);
|
|
22
|
+
patternsData[id] = await resp.json();
|
|
23
|
+
} catch { patternsData[id] = { id, name: id, components: [], default_blend: {} }; }
|
|
24
|
+
}
|
|
25
|
+
patternsLoaded = true;
|
|
26
|
+
} catch {
|
|
27
|
+
patternsLoaded = true;
|
|
28
|
+
}
|
|
29
|
+
return patternsData;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function PatternDetail(patternId, navigateTo) {
|
|
33
|
+
// Normalize title-cased URL slugs back to kebab-case keys (e.g. "Card Grid" → "card-grid", "Hero" → "hero")
|
|
34
|
+
const normalizedId = decodeURIComponent(patternId).toLowerCase().replace(/\s+/g, '-');
|
|
35
|
+
|
|
36
|
+
const container = div({ class: css('_flex _col _gap4') },
|
|
37
|
+
p({ class: css('_body _fgmutedfg') }, 'Loading pattern...')
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
loadPatterns().then(patterns => {
|
|
41
|
+
const pattern = patterns[normalizedId] || patterns[patternId];
|
|
42
|
+
container.innerHTML = '';
|
|
43
|
+
|
|
44
|
+
if (!pattern) {
|
|
45
|
+
container.appendChild(p({ class: css('_fgmutedfg _body') }, `Pattern "${patternId}" not found.`));
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const tabs = Tabs({
|
|
50
|
+
tabs: [
|
|
51
|
+
{
|
|
52
|
+
id: 'features',
|
|
53
|
+
label: 'Features',
|
|
54
|
+
content: () => featuresTab(pattern)
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
id: 'api',
|
|
58
|
+
label: 'API',
|
|
59
|
+
content: () => apiTab(pattern, patternId, navigateTo)
|
|
60
|
+
}
|
|
61
|
+
]
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
container.appendChild(
|
|
65
|
+
div({ class: css('_flex _col _gap1 _mb3') },
|
|
66
|
+
h2({ class: css('_heading4') }, pattern.name || patternId),
|
|
67
|
+
p({ class: css('_body _fgmutedfg') }, pattern.description || 'Composable UI building block.')
|
|
68
|
+
)
|
|
69
|
+
);
|
|
70
|
+
container.appendChild(tabs);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
return container;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function featuresTab(pattern) {
|
|
77
|
+
const sections = [];
|
|
78
|
+
|
|
79
|
+
// Presets section (v2 patterns)
|
|
80
|
+
if (pattern.presets && Object.keys(pattern.presets).length > 0) {
|
|
81
|
+
const presetEntries = Object.entries(pattern.presets);
|
|
82
|
+
sections.push(
|
|
83
|
+
div({ class: css('_flex _col _gap4') },
|
|
84
|
+
h4({ class: css('_heading5 _mb2') }, 'Presets'),
|
|
85
|
+
p({ class: css('_caption _fgmutedfg _mb3') }, `${presetEntries.length} variants. Default: ${pattern.default_preset || 'default'}`),
|
|
86
|
+
...presetEntries.map(([presetId, preset]) =>
|
|
87
|
+
div({ class: css('_flex _col _gap2 _p3 _b1 _r4 _mb2') },
|
|
88
|
+
div({ class: css('_flex _aic _gap2') },
|
|
89
|
+
strong({ class: css('_heading6') }, presetId),
|
|
90
|
+
presetId === pattern.default_preset ? Chip({ label: 'default', variant: 'primary', size: 'sm' }) : null
|
|
91
|
+
),
|
|
92
|
+
p({ class: css('_caption _fgmutedfg') }, preset.description || ''),
|
|
93
|
+
preset.components ? div({ class: css('_flex _gap1 _wrap _mt1') },
|
|
94
|
+
...(preset.components || []).map(c => Chip({ label: c, variant: 'outline', size: 'sm' }))
|
|
95
|
+
) : null,
|
|
96
|
+
preset.blend?.atoms ? code({ class: css('_caption _fgmutedfg _mt1') }, `Atoms: ${preset.blend.atoms}`) : null
|
|
97
|
+
)
|
|
98
|
+
)
|
|
99
|
+
)
|
|
100
|
+
);
|
|
101
|
+
sections.push(Separator({}));
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Live preview
|
|
105
|
+
if (hasPatternExample(pattern.id)) {
|
|
106
|
+
sections.push(
|
|
107
|
+
div({ class: css('_flex _col _gap4') },
|
|
108
|
+
h4({ class: css('_heading5 _mb3') }, 'Live Preview'),
|
|
109
|
+
div({ class: css('_border _bcborder _radius _p4 _overflow[auto]') },
|
|
110
|
+
renderPatternExample(pattern.id)
|
|
111
|
+
)
|
|
112
|
+
)
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Source code
|
|
117
|
+
if (pattern.code?.example) {
|
|
118
|
+
sections.push(Separator({}));
|
|
119
|
+
sections.push(
|
|
120
|
+
div({ class: css('_flex _col _gap4') },
|
|
121
|
+
h4({ class: css('_heading5 _mb3') }, 'Source Code'),
|
|
122
|
+
CodeBlock({ language: 'javascript' }, pattern.code.example)
|
|
123
|
+
)
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return div({ class: css('_flex _col _gap8') }, ...sections);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function apiTab(pattern, patternId, navigateTo) {
|
|
131
|
+
const sections = [];
|
|
132
|
+
|
|
133
|
+
// Components list
|
|
134
|
+
sections.push(
|
|
135
|
+
div({ class: css('_flex _col _gap2') },
|
|
136
|
+
h4({ class: css('_heading6') }, 'Components'),
|
|
137
|
+
(pattern.components || []).length > 0
|
|
138
|
+
? div({ class: css('_flex _gap2 _wrap') },
|
|
139
|
+
...(pattern.components || []).map(c => Chip({ label: c, variant: 'outline', size: 'sm' }))
|
|
140
|
+
)
|
|
141
|
+
: span({ class: css('_fgmutedfg _caption') }, 'No components listed.')
|
|
142
|
+
)
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
// Default blend spec
|
|
146
|
+
if (pattern.default_blend) {
|
|
147
|
+
sections.push(
|
|
148
|
+
div({ class: css('_flex _col _gap2') },
|
|
149
|
+
h4({ class: css('_heading6') }, 'Default Blend'),
|
|
150
|
+
div({ class: css('_flex _col _gap1') },
|
|
151
|
+
code({ class: css('_caption _fgmutedfg') }, `Layout: ${pattern.default_blend.layout || 'stack'}`),
|
|
152
|
+
code({ class: css('_caption _fgmutedfg') }, `Atoms: ${pattern.default_blend.atoms || '(none)'}`)
|
|
153
|
+
),
|
|
154
|
+
pattern.default_blend.slots ? div({ class: css('_flex _col _gap1 _mt2') },
|
|
155
|
+
h4({ class: css('_heading6') }, 'Slots'),
|
|
156
|
+
...Object.entries(pattern.default_blend.slots).map(([name, desc]) =>
|
|
157
|
+
div({ class: css('_flex _gap2') },
|
|
158
|
+
strong({ class: css('_caption') }, name + ':'),
|
|
159
|
+
span({ class: css('_caption _fgmutedfg') }, desc)
|
|
160
|
+
)
|
|
161
|
+
)
|
|
162
|
+
) : null
|
|
163
|
+
)
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return div({ class: css('_flex _col _gap6') }, ...sections);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export function PatternListView(navigateTo) {
|
|
171
|
+
const container = div({ class: css('_flex _col _gap4') },
|
|
172
|
+
h2({ class: css('_heading4') }, 'Patterns'),
|
|
173
|
+
p({ class: css('_body _fgmutedfg') }, 'Loading patterns...')
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
loadPatterns().then(patterns => {
|
|
177
|
+
container.innerHTML = '';
|
|
178
|
+
container.appendChild(h2({ class: css('_heading4 _mb2') }, 'Patterns'));
|
|
179
|
+
container.appendChild(p({ class: css('_body _fgmutedfg _mb3') }, `${Object.keys(patterns).length} composable UI building blocks.`));
|
|
180
|
+
|
|
181
|
+
const grid = div({ class: 'de-card-grid' });
|
|
182
|
+
for (const [id, pat] of Object.entries(patterns)) {
|
|
183
|
+
const cat = patternToCategory[id];
|
|
184
|
+
const href = cat ? `/patterns/${cat}/${titleCase(id)}` : `/patterns/${id}`;
|
|
185
|
+
grid.appendChild(div({
|
|
186
|
+
class: 'de-card-item',
|
|
187
|
+
onclick: () => navigateTo(href)
|
|
188
|
+
},
|
|
189
|
+
div({ class: css('_flex _col _gap2') },
|
|
190
|
+
h3({ class: css('_heading6') }, pat.name || id),
|
|
191
|
+
p({ class: css('_caption _fgmutedfg') }, pat.description || ''),
|
|
192
|
+
div({ class: css('_flex _gap1 _wrap') },
|
|
193
|
+
...(pat.components || []).slice(0, 4).map(c => Chip({ label: c, variant: 'outline', size: 'sm' }))
|
|
194
|
+
)
|
|
195
|
+
)
|
|
196
|
+
));
|
|
197
|
+
}
|
|
198
|
+
container.appendChild(grid);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
return container;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Reverse map: pattern id → category id
|
|
205
|
+
const patternToCategory = {};
|
|
206
|
+
|
|
207
|
+
const PATTERN_CATEGORIES = [
|
|
208
|
+
{ id: 'layout', label: 'Layout', ids: ['hero', 'cta-section', 'detail-header', 'detail-panel'] },
|
|
209
|
+
{ id: 'data', label: 'Data Display', ids: ['kpi-grid', 'data-table', 'chart-grid', 'scorecard', 'comparison-panel', 'stat-card', 'stats-bar'] },
|
|
210
|
+
{ id: 'content', label: 'Content', ids: ['article-content', 'post-list', 'testimonials', 'author-card', 'media-gallery', 'checklist-card', 'steps-card'] },
|
|
211
|
+
{ id: 'navigation', label: 'Navigation', ids: ['category-nav', 'table-of-contents', 'pagination', 'search-bar', 'filter-bar', 'filter-sidebar'] },
|
|
212
|
+
{ id: 'forms', label: 'Forms', ids: ['auth-form', 'contact-form', 'form-sections', 'wizard'] },
|
|
213
|
+
{ id: 'commerce', label: 'Commerce', ids: ['card-grid', 'pricing-table', 'order-history'] },
|
|
214
|
+
{ id: 'activity', label: 'Activity', ids: ['activity-feed', 'timeline', 'goal-tracker', 'pipeline-tracker'] },
|
|
215
|
+
{ id: 'social', label: 'Social', ids: ['chat-interface', 'photo-to-recipe'] },
|
|
216
|
+
{ id: 'meta', label: 'Meta / Docs', ids: ['component-showcase', 'specimen-grid', 'token-inspector', 'explorer-shell'] },
|
|
217
|
+
];
|
|
218
|
+
|
|
219
|
+
for (const cat of PATTERN_CATEGORIES) {
|
|
220
|
+
for (const id of cat.ids) patternToCategory[id] = cat.id;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function titleCase(id) {
|
|
224
|
+
return id.split('-').map(w => w[0].toUpperCase() + w.slice(1)).join(' ');
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export async function loadPatternItems() {
|
|
228
|
+
try {
|
|
229
|
+
const resp = await fetch('/__decantr/registry/patterns/index.json');
|
|
230
|
+
const data = await resp.json();
|
|
231
|
+
const available = new Set(Object.keys(data.patterns || {}));
|
|
232
|
+
|
|
233
|
+
return PATTERN_CATEGORIES
|
|
234
|
+
.map(cat => {
|
|
235
|
+
const children = cat.ids.filter(id => available.has(id));
|
|
236
|
+
if (children.length === 0) return null;
|
|
237
|
+
return {
|
|
238
|
+
id: cat.id,
|
|
239
|
+
label: cat.label,
|
|
240
|
+
children: children.map(id => titleCase(id))
|
|
241
|
+
};
|
|
242
|
+
})
|
|
243
|
+
.filter(Boolean);
|
|
244
|
+
} catch {
|
|
245
|
+
return [];
|
|
246
|
+
}
|
|
247
|
+
}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { css } from 'decantr/css';
|
|
2
|
+
import { tags } from 'decantr/tags';
|
|
3
|
+
import { Separator, Tabs } from 'decantr/components';
|
|
4
|
+
import { injectExplorerCSS } from './styles.js';
|
|
5
|
+
injectExplorerCSS();
|
|
6
|
+
|
|
7
|
+
const { div, h2, h3, h4, p, span, section, code, strong } = tags;
|
|
8
|
+
|
|
9
|
+
let recipesData = {};
|
|
10
|
+
let recipesLoaded = false;
|
|
11
|
+
|
|
12
|
+
async function loadRecipes() {
|
|
13
|
+
if (recipesLoaded) return recipesData;
|
|
14
|
+
try {
|
|
15
|
+
const indexResp = await fetch('/__decantr/registry/index.json');
|
|
16
|
+
const index = await indexResp.json();
|
|
17
|
+
const recipeEntries = index.recipes?.entries || {};
|
|
18
|
+
for (const [id, meta] of Object.entries(recipeEntries)) {
|
|
19
|
+
try {
|
|
20
|
+
const resp = await fetch(`/__decantr/registry/${meta.file}`);
|
|
21
|
+
recipesData[id] = await resp.json();
|
|
22
|
+
} catch { recipesData[id] = { id, name: id, decorators: {}, compositions: {} }; }
|
|
23
|
+
}
|
|
24
|
+
recipesLoaded = true;
|
|
25
|
+
} catch {
|
|
26
|
+
recipesLoaded = true;
|
|
27
|
+
}
|
|
28
|
+
return recipesData;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function RecipeDetail(recipeId, navigateTo) {
|
|
32
|
+
const container = div({ class: css('_flex _col _gap4') },
|
|
33
|
+
p({ class: css('_body _fgmutedfg') }, 'Loading recipe...')
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
loadRecipes().then(recipes => {
|
|
37
|
+
const recipe = recipes[recipeId];
|
|
38
|
+
container.innerHTML = '';
|
|
39
|
+
|
|
40
|
+
if (!recipe) {
|
|
41
|
+
container.appendChild(p({ class: css('_fgmutedfg _body') }, `Recipe "${recipeId}" not found.`));
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const tabs = Tabs({
|
|
46
|
+
tabs: [
|
|
47
|
+
{
|
|
48
|
+
id: 'features',
|
|
49
|
+
label: 'Features',
|
|
50
|
+
content: () => featuresTab(recipe)
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
id: 'api',
|
|
54
|
+
label: 'API',
|
|
55
|
+
content: () => apiTab(recipe)
|
|
56
|
+
}
|
|
57
|
+
]
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
container.appendChild(
|
|
61
|
+
div({ class: css('_flex _col _gap1 _mb3') },
|
|
62
|
+
h2({ class: css('_heading4') }, recipe.name || recipeId),
|
|
63
|
+
p({ class: css('_body _fgmutedfg') }, recipe.description || 'Visual identity overlay.')
|
|
64
|
+
)
|
|
65
|
+
);
|
|
66
|
+
container.appendChild(tabs);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
return container;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function featuresTab(recipe) {
|
|
73
|
+
const sections = [];
|
|
74
|
+
const decorators = recipe.decorators || {};
|
|
75
|
+
const compositions = recipe.compositions || {};
|
|
76
|
+
|
|
77
|
+
// Decorators
|
|
78
|
+
if (Object.keys(decorators).length > 0) {
|
|
79
|
+
sections.push(
|
|
80
|
+
div({ class: css('_flex _col _gap4') },
|
|
81
|
+
h4({ class: css('_heading5 _mb3') }, `${Object.keys(decorators).length} Decorators`),
|
|
82
|
+
...Object.entries(decorators).map(([name, desc]) =>
|
|
83
|
+
div({ class: css('_flex _gap4 _aic _py2 _bb[var(--d-border-width)_solid_var(--d-border)]') },
|
|
84
|
+
div({ class: 'de-decorator-preview' },
|
|
85
|
+
div({ class: css(name + ' _p3') },
|
|
86
|
+
code({ class: css('_caption') }, `.${name}`)
|
|
87
|
+
)
|
|
88
|
+
),
|
|
89
|
+
span({ class: css('_body _fgmutedfg _flex1') }, typeof desc === 'string' ? desc : (desc.description || ''))
|
|
90
|
+
)
|
|
91
|
+
)
|
|
92
|
+
)
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Compositions
|
|
97
|
+
if (Object.keys(compositions).length > 0) {
|
|
98
|
+
if (sections.length > 0) sections.push(Separator({}));
|
|
99
|
+
sections.push(
|
|
100
|
+
div({ class: css('_flex _col _gap4') },
|
|
101
|
+
h4({ class: css('_heading5 _mb3') }, 'Compositions'),
|
|
102
|
+
...Object.entries(compositions).map(([name, comp]) =>
|
|
103
|
+
div({ class: css('_flex _col _gap2 _p3 _border _bcborder _radius') },
|
|
104
|
+
strong({ class: css('_heading6') }, name),
|
|
105
|
+
p({ class: css('_caption _fgmutedfg') }, comp.description || ''),
|
|
106
|
+
comp.code ? div({ class: css('_bgmuted/10 _p3 _radius') },
|
|
107
|
+
code({ class: css('_caption _wsprewrap _fontmono') }, comp.code)
|
|
108
|
+
) : null
|
|
109
|
+
)
|
|
110
|
+
)
|
|
111
|
+
)
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (sections.length === 0) {
|
|
116
|
+
return div({ class: css('_fgmutedfg _body') }, 'No decorators or compositions defined.');
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return div({ class: css('_flex _col _gap8') }, ...sections);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function apiTab(recipe) {
|
|
123
|
+
const sections = [];
|
|
124
|
+
|
|
125
|
+
// Setup code
|
|
126
|
+
sections.push(
|
|
127
|
+
div({ class: css('_flex _col _gap2') },
|
|
128
|
+
h4({ class: css('_heading6') }, 'Setup'),
|
|
129
|
+
div({ class: css('_bgmuted/10 _p4 _radius') },
|
|
130
|
+
code({ class: css('_body _wspre _fontmono') },
|
|
131
|
+
recipe.setup || `import { setStyle, setMode } from 'decantr/css';\nsetStyle('${recipe.style || recipe.id}');\nsetMode('${recipe.mode || 'dark'}');`
|
|
132
|
+
)
|
|
133
|
+
)
|
|
134
|
+
)
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
// Style/mode metadata
|
|
138
|
+
if (recipe.style || recipe.mode) {
|
|
139
|
+
sections.push(
|
|
140
|
+
div({ class: css('_flex _col _gap1') },
|
|
141
|
+
recipe.style ? div({}, strong({ class: css('_caption') }, 'Style: '), span({ class: css('_caption _fgmutedfg') }, recipe.style)) : null,
|
|
142
|
+
recipe.mode ? div({}, strong({ class: css('_caption') }, 'Mode: '), span({ class: css('_caption _fgmutedfg') }, recipe.mode)) : null
|
|
143
|
+
)
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return div({ class: css('_flex _col _gap6') }, ...sections);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export function RecipeListView(navigateTo) {
|
|
151
|
+
const container = div({ class: css('_flex _col _gap4') },
|
|
152
|
+
h2({ class: css('_heading4') }, 'Recipes'),
|
|
153
|
+
p({ class: css('_body _fgmutedfg') }, 'Loading recipes...')
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
loadRecipes().then(recipes => {
|
|
157
|
+
container.innerHTML = '';
|
|
158
|
+
container.appendChild(h2({ class: css('_heading4 _mb2') }, 'Recipes'));
|
|
159
|
+
container.appendChild(p({ class: css('_body _fgmutedfg _mb3') }, 'Visual identity overlays — composition rules for drastic visual transformations.'));
|
|
160
|
+
|
|
161
|
+
const grid = div({ class: 'de-card-grid' });
|
|
162
|
+
for (const [id, recipe] of Object.entries(recipes)) {
|
|
163
|
+
grid.appendChild(div({
|
|
164
|
+
class: 'de-card-item',
|
|
165
|
+
onclick: () => navigateTo(`/recipes/${id}`)
|
|
166
|
+
},
|
|
167
|
+
div({ class: css('_flex _col _gap2') },
|
|
168
|
+
h3({ class: css('_heading6') }, recipe.name || id),
|
|
169
|
+
p({ class: css('_caption _fgmutedfg') }, recipe.description || ''),
|
|
170
|
+
div({ class: css('_flex _gap2') },
|
|
171
|
+
span({ class: css('_caption _fgmutedfg') }, `${Object.keys(recipe.decorators || {}).length} decorators`),
|
|
172
|
+
span({ class: css('_caption _fgmutedfg') }, `${Object.keys(recipe.compositions || {}).length} compositions`)
|
|
173
|
+
)
|
|
174
|
+
)
|
|
175
|
+
));
|
|
176
|
+
}
|
|
177
|
+
container.appendChild(grid);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
return container;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export async function loadRecipeItems() {
|
|
184
|
+
try {
|
|
185
|
+
const resp = await fetch('/__decantr/registry/index.json');
|
|
186
|
+
const data = await resp.json();
|
|
187
|
+
const entries = data.recipes?.entries || {};
|
|
188
|
+
return Object.entries(entries).map(([id, meta]) => ({
|
|
189
|
+
id, label: meta.description?.split(' — ')[0] || id.split('-').map(w => w[0].toUpperCase() + w.slice(1)).join(' ')
|
|
190
|
+
}));
|
|
191
|
+
} catch {
|
|
192
|
+
return [];
|
|
193
|
+
}
|
|
194
|
+
}
|