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,41 @@
|
|
|
1
|
+
import { tags } from 'decantr/tags';
|
|
2
|
+
import { css } from 'decantr/css';
|
|
3
|
+
import { injectExplorerCSS } from '../styles.js';
|
|
4
|
+
injectExplorerCSS();
|
|
5
|
+
|
|
6
|
+
const { div, table, thead, tbody, tr, th, td, span, code } = tags;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Auto-generated props/API table from registry JSON.
|
|
10
|
+
* @param {{ props: Record<string, { type: string, default?: any, description?: string, enum?: string[] }> }} opts
|
|
11
|
+
*/
|
|
12
|
+
export function SpecTable({ props }) {
|
|
13
|
+
if (!props || Object.keys(props).length === 0) {
|
|
14
|
+
return div({ class: css('_fgmutedfg _body') }, 'No props defined in registry.');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const rows = Object.entries(props).map(([name, meta]) => {
|
|
18
|
+
const typeStr = meta.enum ? meta.enum.join(' | ') : (meta.type || 'any');
|
|
19
|
+
const defaultStr = meta.default !== undefined ? String(meta.default) : '—';
|
|
20
|
+
return tr({},
|
|
21
|
+
td({ class: 'de-spec-name' }, code({}, name)),
|
|
22
|
+
td({ class: 'de-spec-type' }, code({}, typeStr)),
|
|
23
|
+
td({ class: 'de-spec-default' }, defaultStr),
|
|
24
|
+
td({ class: 'de-spec-desc' }, meta.description || '—')
|
|
25
|
+
);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
return div({ class: 'de-spec-table-wrap' },
|
|
29
|
+
table({ class: 'de-spec-table' },
|
|
30
|
+
thead({},
|
|
31
|
+
tr({},
|
|
32
|
+
th({}, 'Prop'),
|
|
33
|
+
th({}, 'Type'),
|
|
34
|
+
th({}, 'Default'),
|
|
35
|
+
th({}, 'Description')
|
|
36
|
+
)
|
|
37
|
+
),
|
|
38
|
+
tbody({}, ...rows)
|
|
39
|
+
)
|
|
40
|
+
);
|
|
41
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { tags } from 'decantr/tags';
|
|
2
|
+
import { css } from 'decantr/css';
|
|
3
|
+
import { Chip } from 'decantr/components';
|
|
4
|
+
|
|
5
|
+
const { div, span, h4 } = tags;
|
|
6
|
+
|
|
7
|
+
// Reverse indexes built on init
|
|
8
|
+
let componentToPatterns = {};
|
|
9
|
+
let patternToArchetypes = {};
|
|
10
|
+
let initialized = false;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Build reverse indexes from registry data.
|
|
14
|
+
* Call once after registry is loaded.
|
|
15
|
+
*/
|
|
16
|
+
export function initUsageIndex(patterns, archetypes) {
|
|
17
|
+
componentToPatterns = {};
|
|
18
|
+
patternToArchetypes = {};
|
|
19
|
+
|
|
20
|
+
// Component → Patterns
|
|
21
|
+
for (const [patternId, pattern] of Object.entries(patterns)) {
|
|
22
|
+
for (const comp of (pattern.components || [])) {
|
|
23
|
+
if (!componentToPatterns[comp]) componentToPatterns[comp] = [];
|
|
24
|
+
componentToPatterns[comp].push({ id: patternId, name: pattern.name || patternId });
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Pattern → Archetypes
|
|
29
|
+
for (const [archId, arch] of Object.entries(archetypes)) {
|
|
30
|
+
for (const page of (arch.pages || [])) {
|
|
31
|
+
for (const pat of (page.patterns || [])) {
|
|
32
|
+
if (!patternToArchetypes[pat]) patternToArchetypes[pat] = [];
|
|
33
|
+
patternToArchetypes[pat].push({ archetype: archId, page: page.id });
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
initialized = true;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Render "Used in patterns" chips for a component.
|
|
43
|
+
*/
|
|
44
|
+
export function ComponentUsageLinks(componentName, navigateTo) {
|
|
45
|
+
const patterns = componentToPatterns[componentName] || [];
|
|
46
|
+
if (patterns.length === 0) {
|
|
47
|
+
return span({ class: css('_fgmutedfg _caption') }, 'Not referenced by any patterns.');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return div({ class: css('_flex _col _gap2') },
|
|
51
|
+
h4({ class: css('_heading6') }, `Used in ${patterns.length} pattern${patterns.length > 1 ? 's' : ''}`),
|
|
52
|
+
div({ class: css('_flex _gap2 _wrap') },
|
|
53
|
+
...patterns.map(pat =>
|
|
54
|
+
Chip({
|
|
55
|
+
label: pat.name,
|
|
56
|
+
variant: 'outline',
|
|
57
|
+
size: 'sm',
|
|
58
|
+
onclick: () => navigateTo(`/patterns/${pat.id}`)
|
|
59
|
+
})
|
|
60
|
+
)
|
|
61
|
+
)
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Render "Used in archetypes" chips for a pattern.
|
|
67
|
+
*/
|
|
68
|
+
export function PatternUsageLinks(patternId, navigateTo) {
|
|
69
|
+
const archs = patternToArchetypes[patternId] || [];
|
|
70
|
+
if (archs.length === 0) {
|
|
71
|
+
return span({ class: css('_fgmutedfg _caption') }, 'Not referenced by any archetypes.');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return div({ class: css('_flex _col _gap2') },
|
|
75
|
+
h4({ class: css('_heading6') }, `Used in ${archs.length} archetype page${archs.length > 1 ? 's' : ''}`),
|
|
76
|
+
div({ class: css('_flex _gap2 _wrap') },
|
|
77
|
+
...archs.map(ref =>
|
|
78
|
+
Chip({
|
|
79
|
+
label: `${ref.archetype} / ${ref.page}`,
|
|
80
|
+
variant: 'outline',
|
|
81
|
+
size: 'sm',
|
|
82
|
+
onclick: () => navigateTo(`/archetypes/${ref.archetype}`)
|
|
83
|
+
})
|
|
84
|
+
)
|
|
85
|
+
)
|
|
86
|
+
);
|
|
87
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared signal for "Apply to Workbench" shell config.
|
|
3
|
+
* Separate module to avoid circular imports between app.js and explorer/shells.js.
|
|
4
|
+
*/
|
|
5
|
+
import { createSignal } from 'decantr/state';
|
|
6
|
+
|
|
7
|
+
const storedConfig = typeof localStorage !== 'undefined' ? localStorage.getItem('de-shell-config') : null;
|
|
8
|
+
const [activeShellConfig, setActiveShellConfig] = createSignal(storedConfig ? JSON.parse(storedConfig) : null);
|
|
9
|
+
|
|
10
|
+
export { activeShellConfig, setActiveShellConfig };
|
|
@@ -0,0 +1,551 @@
|
|
|
1
|
+
import { css } from 'decantr/css';
|
|
2
|
+
import { tags } from 'decantr/tags';
|
|
3
|
+
import { createSignal, createEffect } from 'decantr/state';
|
|
4
|
+
import { Tabs, Separator, Chip, Button, Slider, Switch } from 'decantr/components';
|
|
5
|
+
import { resolveShellConfig, buildGridTemplate } from 'decantr/components/shell.js';
|
|
6
|
+
import { activeShellConfig, setActiveShellConfig } from './shell-config.js';
|
|
7
|
+
import { injectExplorerCSS } from './styles.js';
|
|
8
|
+
injectExplorerCSS();
|
|
9
|
+
|
|
10
|
+
const { div, h2, h3, h4, p, span, code, pre, section, button } = tags;
|
|
11
|
+
|
|
12
|
+
// ─── Skeleton data (loaded from registry) ───────────────────────
|
|
13
|
+
|
|
14
|
+
let skeletonsData = {};
|
|
15
|
+
let skeletonsLoaded = false;
|
|
16
|
+
|
|
17
|
+
async function loadSkeletons() {
|
|
18
|
+
if (skeletonsLoaded) return skeletonsData;
|
|
19
|
+
try {
|
|
20
|
+
const resp = await fetch('/__decantr/registry/skeletons.json');
|
|
21
|
+
const data = await resp.json();
|
|
22
|
+
skeletonsData = data.skeletons || {};
|
|
23
|
+
skeletonsLoaded = true;
|
|
24
|
+
} catch {
|
|
25
|
+
skeletonsLoaded = true;
|
|
26
|
+
}
|
|
27
|
+
return skeletonsData;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// ─── CSS Miniature Thumbnail ────────────────────────────────────
|
|
31
|
+
|
|
32
|
+
const REGION_COLORS = {
|
|
33
|
+
header: 'var(--d-surface-1)',
|
|
34
|
+
nav: 'var(--d-surface-1)',
|
|
35
|
+
body: 'var(--d-surface-0)',
|
|
36
|
+
footer: 'var(--d-surface-1)',
|
|
37
|
+
aside: 'var(--d-surface-1)'
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const REGION_LABELS = {
|
|
41
|
+
header: 'Header',
|
|
42
|
+
nav: 'Nav',
|
|
43
|
+
body: 'Body',
|
|
44
|
+
footer: 'Footer',
|
|
45
|
+
aside: 'Aside'
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Proportional grid template for thumbnail previews.
|
|
50
|
+
* Uses fr units instead of absolute px so the layout scales to any container.
|
|
51
|
+
*/
|
|
52
|
+
function buildThumbnailTemplate(cfg) {
|
|
53
|
+
const { grid } = cfg;
|
|
54
|
+
const areas = grid.areas.map(row => `"${row.join(' ')}"`).join(' ');
|
|
55
|
+
|
|
56
|
+
// Columns: nav/aside → 1fr, body/header → 3fr
|
|
57
|
+
const firstRow = grid.areas[0];
|
|
58
|
+
const colDefs = [];
|
|
59
|
+
for (let c = 0; c < firstRow.length; c++) {
|
|
60
|
+
const regionSet = new Set();
|
|
61
|
+
for (const row of grid.areas) regionSet.add(row[c]);
|
|
62
|
+
colDefs.push((regionSet.has('nav') && regionSet.size === 1) || (regionSet.has('aside') && regionSet.size === 1) ? '1fr' : '3fr');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Rows: header/footer → 1fr, body → 3fr
|
|
66
|
+
const rowDefs = [];
|
|
67
|
+
for (let r = 0; r < grid.areas.length; r++) {
|
|
68
|
+
const regionSet = new Set(grid.areas[r]);
|
|
69
|
+
rowDefs.push((regionSet.has('body') && !regionSet.has('header') && !regionSet.has('footer')) ? '3fr' : '1fr');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return { areas, columns: colDefs.join(' '), rows: rowDefs.join(' ') };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function ShellThumbnail(presetId) {
|
|
76
|
+
let cfg;
|
|
77
|
+
try { cfg = resolveShellConfig(presetId); }
|
|
78
|
+
catch { return div({ class: css('_p4 _fgmutedfg _caption') }, 'Invalid'); }
|
|
79
|
+
|
|
80
|
+
const tpl = buildThumbnailTemplate(cfg);
|
|
81
|
+
|
|
82
|
+
// Dynamic grid template — runtime value from config resolution
|
|
83
|
+
const thumb = div({
|
|
84
|
+
class: css('_grid _border _bcborder _radius _overflow[hidden] _minh[140px]'),
|
|
85
|
+
style: () => `grid-template-areas:${tpl.areas};grid-template-columns:${tpl.columns};grid-template-rows:${tpl.rows}`
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// Add region cells
|
|
89
|
+
const rendered = new Set();
|
|
90
|
+
for (const row of cfg.grid.areas) {
|
|
91
|
+
for (const cell of row) {
|
|
92
|
+
if (rendered.has(cell)) continue;
|
|
93
|
+
rendered.add(cell);
|
|
94
|
+
// grid-area + background are dynamic (cell-dependent runtime values)
|
|
95
|
+
thumb.appendChild(div({
|
|
96
|
+
class: css('_flex _center _border _bcborder _textxs _fgmuted _uppercase _ls[0.05em]'),
|
|
97
|
+
style: () => `grid-area:${cell};background:${REGION_COLORS[cell] || 'var(--d-bg)'}`
|
|
98
|
+
}, REGION_LABELS[cell] || cell));
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return thumb;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// ─── Live Preview ───────────────────────────────────────────────
|
|
106
|
+
|
|
107
|
+
function ShellPreview(presetId, configSignals) {
|
|
108
|
+
const container = div({ class: css('_border _bcborder _radius _overflow[hidden] _minh[300px]') });
|
|
109
|
+
|
|
110
|
+
createEffect(() => {
|
|
111
|
+
const navPos = configSignals.navPosition();
|
|
112
|
+
const navMode = configSignals.navMode();
|
|
113
|
+
const showHeader = configSignals.showHeader();
|
|
114
|
+
const showFooter = configSignals.showFooter();
|
|
115
|
+
const showAside = configSignals.showAside();
|
|
116
|
+
const sidebarW = configSignals.sidebarWidth() + 'px';
|
|
117
|
+
const headerH = configSignals.headerHeight() + 'px';
|
|
118
|
+
const asideW = configSignals.asideWidth() + 'px';
|
|
119
|
+
|
|
120
|
+
const isTop = navPos === 'top';
|
|
121
|
+
const hasSidebar = !isTop && navMode !== 'hidden';
|
|
122
|
+
|
|
123
|
+
// Build areas
|
|
124
|
+
const areas = [];
|
|
125
|
+
|
|
126
|
+
if (showHeader) {
|
|
127
|
+
const row = [];
|
|
128
|
+
if (hasSidebar && navPos === 'left') row.push('nav');
|
|
129
|
+
row.push('header');
|
|
130
|
+
if (hasSidebar && navPos === 'right') row.push('nav');
|
|
131
|
+
if (showAside) row.push('aside');
|
|
132
|
+
areas.push(row);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Body row
|
|
136
|
+
const bodyRow = [];
|
|
137
|
+
if (hasSidebar && navPos === 'left') bodyRow.push('nav');
|
|
138
|
+
bodyRow.push('body');
|
|
139
|
+
if (hasSidebar && navPos === 'right') bodyRow.push('nav');
|
|
140
|
+
if (showAside) bodyRow.push('aside');
|
|
141
|
+
areas.push(bodyRow);
|
|
142
|
+
|
|
143
|
+
if (showFooter) {
|
|
144
|
+
const footerRow = [];
|
|
145
|
+
if (hasSidebar && navPos === 'left') footerRow.push('nav');
|
|
146
|
+
footerRow.push('footer');
|
|
147
|
+
if (hasSidebar && navPos === 'right') footerRow.push('nav');
|
|
148
|
+
if (showAside) footerRow.push('aside');
|
|
149
|
+
areas.push(footerRow);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Build template strings
|
|
153
|
+
const areasStr = areas.map(r => `"${r.join(' ')}"`).join(' ');
|
|
154
|
+
|
|
155
|
+
const cols = [];
|
|
156
|
+
if (hasSidebar && navPos === 'left') cols.push(navMode === 'rail' ? 'var(--de-shell-rail-w, 64px)' : sidebarW);
|
|
157
|
+
cols.push('1fr');
|
|
158
|
+
if (hasSidebar && navPos === 'right') cols.push(navMode === 'rail' ? 'var(--de-shell-rail-w, 64px)' : sidebarW);
|
|
159
|
+
if (showAside) cols.push(asideW);
|
|
160
|
+
|
|
161
|
+
const rowDefs = [];
|
|
162
|
+
if (showHeader) rowDefs.push(headerH);
|
|
163
|
+
rowDefs.push('1fr');
|
|
164
|
+
if (showFooter) rowDefs.push('auto');
|
|
165
|
+
|
|
166
|
+
container.innerHTML = '';
|
|
167
|
+
// Dynamic grid template — computed from reactive signals
|
|
168
|
+
const grid = div({
|
|
169
|
+
class: css('_grid _h[300px]'),
|
|
170
|
+
style: () => `grid-template-areas:${areasStr};grid-template-columns:${cols.join(' ')};grid-template-rows:${rowDefs.join(' ')};transition:all var(--d-duration-fast) var(--d-easing-standard)`
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
// Render regions
|
|
174
|
+
const renderedRegions = new Set();
|
|
175
|
+
for (const row of areas) {
|
|
176
|
+
for (const cell of row) {
|
|
177
|
+
if (renderedRegions.has(cell)) continue;
|
|
178
|
+
renderedRegions.add(cell);
|
|
179
|
+
|
|
180
|
+
let content = REGION_LABELS[cell] || cell;
|
|
181
|
+
if (cell === 'header' && isTop) content = 'Header + Nav';
|
|
182
|
+
if (cell === 'body') content = 'Main Content';
|
|
183
|
+
|
|
184
|
+
// grid-area + background are dynamic per-cell
|
|
185
|
+
grid.appendChild(div({
|
|
186
|
+
class: css('_flex _center _border _bcborder _p2 _fgmutedfg _textsm'),
|
|
187
|
+
style: () => `grid-area:${cell};background:${REGION_COLORS[cell] || 'var(--d-bg)'}`
|
|
188
|
+
}, content));
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
container.appendChild(grid);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
return container;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// ─── Configurator Controls ──────────────────────────────────────
|
|
199
|
+
|
|
200
|
+
function ShellConfigurator(presetId, configSignals) {
|
|
201
|
+
const { navPosition, setNavPosition, navMode, setNavMode,
|
|
202
|
+
showHeader, setShowHeader, showFooter, setShowFooter,
|
|
203
|
+
showAside, setShowAside, sidebarWidth, setSidebarWidth,
|
|
204
|
+
headerHeight, setHeaderHeight, asideWidth, setAsideWidth } = configSignals;
|
|
205
|
+
|
|
206
|
+
function segmentedControl(label, options, getter, setter) {
|
|
207
|
+
return div({ class: css('_flex _aic _jcsb _gap4 _py2') },
|
|
208
|
+
span({ class: css('_caption _fgmutedfg') }, label),
|
|
209
|
+
div({ class: css('_flex _gap1') },
|
|
210
|
+
...options.map(opt =>
|
|
211
|
+
button({
|
|
212
|
+
class: () => css('_px3 _py1 _radius _caption _cursor[pointer] _border _bcborder _trans') +
|
|
213
|
+
(getter() === opt.value ? ' ' + css('_bgprimary/20 _fgprimary _bcprimary/40') : ' ' + css('_bgmuted/10 _fgmutedfg')),
|
|
214
|
+
onclick: () => setter(opt.value)
|
|
215
|
+
}, opt.label)
|
|
216
|
+
)
|
|
217
|
+
)
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function toggleControl(label, getter, setter) {
|
|
222
|
+
return div({ class: css('_flex _aic _jcsb _py2') },
|
|
223
|
+
span({ class: css('_caption _fgmutedfg') }, label),
|
|
224
|
+
Switch({ checked: getter, onchange: setter, size: 'sm' })
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function sliderControl(label, getter, setter, min, max, unit = 'px') {
|
|
229
|
+
return div({ class: css('_flex _col _gap1 _py2') },
|
|
230
|
+
div({ class: css('_flex _aic _jcsb') },
|
|
231
|
+
span({ class: css('_caption _fgmutedfg') }, label),
|
|
232
|
+
span({ class: css('_caption _fgmutedfg _fontmono') }, () => getter() + unit)
|
|
233
|
+
),
|
|
234
|
+
Slider({ value: getter, onchange: setter, min, max, step: 4 })
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const isTopNav = () => navPosition() === 'top';
|
|
239
|
+
|
|
240
|
+
return div({ class: css('_flex _col _gap2 _p4 _border _bcborder _radius') },
|
|
241
|
+
h4({ class: css('_heading6 _mb2') }, 'Configuration'),
|
|
242
|
+
|
|
243
|
+
segmentedControl('Nav Position', [
|
|
244
|
+
{ value: 'left', label: 'Left' },
|
|
245
|
+
{ value: 'right', label: 'Right' },
|
|
246
|
+
{ value: 'top', label: 'Top' }
|
|
247
|
+
], navPosition, setNavPosition),
|
|
248
|
+
|
|
249
|
+
segmentedControl('Nav Mode', [
|
|
250
|
+
{ value: 'full', label: 'Full' },
|
|
251
|
+
{ value: 'rail', label: 'Rail' },
|
|
252
|
+
{ value: 'hidden', label: 'Hidden' }
|
|
253
|
+
], navMode, (v) => { if (!isTopNav()) setNavMode(v); }),
|
|
254
|
+
|
|
255
|
+
Separator({}),
|
|
256
|
+
|
|
257
|
+
toggleControl('Header', showHeader, setShowHeader),
|
|
258
|
+
toggleControl('Footer', showFooter, setShowFooter),
|
|
259
|
+
toggleControl('Aside Panel', showAside, setShowAside),
|
|
260
|
+
|
|
261
|
+
Separator({}),
|
|
262
|
+
|
|
263
|
+
sliderControl('Sidebar Width', sidebarWidth, setSidebarWidth, 48, 400),
|
|
264
|
+
sliderControl('Header Height', headerHeight, setHeaderHeight, 36, 80),
|
|
265
|
+
sliderControl('Aside Width', asideWidth, setAsideWidth, 200, 480),
|
|
266
|
+
|
|
267
|
+
Separator({}),
|
|
268
|
+
|
|
269
|
+
// Apply to Workbench toggle
|
|
270
|
+
div({ class: css('_flex _aic _jcsb _py2') },
|
|
271
|
+
span({ class: css('_caption _fgprimary _bold') }, 'Apply to Workbench'),
|
|
272
|
+
Switch({
|
|
273
|
+
checked: () => !!activeShellConfig(),
|
|
274
|
+
onchange: (checked) => {
|
|
275
|
+
if (checked) {
|
|
276
|
+
setActiveShellConfig({
|
|
277
|
+
presetId,
|
|
278
|
+
navPosition: navPosition(),
|
|
279
|
+
navMode: navMode(),
|
|
280
|
+
showHeader: showHeader(),
|
|
281
|
+
showFooter: showFooter(),
|
|
282
|
+
showAside: showAside(),
|
|
283
|
+
sidebarWidth: sidebarWidth(),
|
|
284
|
+
headerHeight: headerHeight(),
|
|
285
|
+
asideWidth: asideWidth()
|
|
286
|
+
});
|
|
287
|
+
} else {
|
|
288
|
+
setActiveShellConfig(null);
|
|
289
|
+
}
|
|
290
|
+
},
|
|
291
|
+
size: 'sm'
|
|
292
|
+
})
|
|
293
|
+
)
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// ─── Code Tab ───────────────────────────────────────────────────
|
|
298
|
+
|
|
299
|
+
function ShellCodeTab(presetId, configSignals) {
|
|
300
|
+
const codeContainer = div({ class: css('_flex _col _gap3') });
|
|
301
|
+
|
|
302
|
+
createEffect(() => {
|
|
303
|
+
const navPos = configSignals.navPosition();
|
|
304
|
+
const navMode = configSignals.navMode();
|
|
305
|
+
const showHeader = configSignals.showHeader();
|
|
306
|
+
const showFooter = configSignals.showFooter();
|
|
307
|
+
const showAside = configSignals.showAside();
|
|
308
|
+
|
|
309
|
+
const subComponents = [];
|
|
310
|
+
if (showHeader) subComponents.push(" Shell.Header({}, 'Header content')");
|
|
311
|
+
if (navPos !== 'top' && navMode !== 'hidden') subComponents.push(" Shell.Nav({}, navContent)");
|
|
312
|
+
subComponents.push(" Shell.Body({}, mainContent)");
|
|
313
|
+
if (showFooter) subComponents.push(" Shell.Footer({}, 'Footer')");
|
|
314
|
+
if (showAside) subComponents.push(" Shell.Aside({}, asideContent)");
|
|
315
|
+
|
|
316
|
+
const codeStr = `import { Shell } from 'decantr/components';
|
|
317
|
+
import { createSignal } from 'decantr/state';
|
|
318
|
+
|
|
319
|
+
const [navState, setNavState] = createSignal('${navMode === 'rail' ? 'rail' : 'expanded'}');
|
|
320
|
+
|
|
321
|
+
Shell({
|
|
322
|
+
config: '${presetId}',
|
|
323
|
+
navState,
|
|
324
|
+
onNavStateChange: setNavState
|
|
325
|
+
},
|
|
326
|
+
${subComponents.join(',\n')}
|
|
327
|
+
)`;
|
|
328
|
+
|
|
329
|
+
codeContainer.innerHTML = '';
|
|
330
|
+
|
|
331
|
+
const copyBtn = Button({
|
|
332
|
+
variant: 'outline', size: 'sm',
|
|
333
|
+
onclick: () => {
|
|
334
|
+
navigator.clipboard?.writeText(codeStr);
|
|
335
|
+
copyBtn.textContent = 'Copied!';
|
|
336
|
+
setTimeout(() => { copyBtn.textContent = 'Copy Code'; }, 1500);
|
|
337
|
+
}
|
|
338
|
+
}, 'Copy Code');
|
|
339
|
+
|
|
340
|
+
codeContainer.appendChild(
|
|
341
|
+
div({ class: css('_flex _jce _mb2') }, copyBtn)
|
|
342
|
+
);
|
|
343
|
+
codeContainer.appendChild(
|
|
344
|
+
pre({ class: css('_p4 _bgmuted/10 _radius _overflow[auto] _caption _fontmono _border _bcborder _whitespace[pre-wrap] _wb[break-word]') }, codeStr)
|
|
345
|
+
);
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
return codeContainer;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// ─── Shell Detail (3 tabs) ──────────────────────────────────────
|
|
352
|
+
|
|
353
|
+
export function ShellDetail(presetId) {
|
|
354
|
+
const container = div({ class: css('_flex _col _gap4') },
|
|
355
|
+
p({ class: css('_body _fgmutedfg') }, 'Loading shell layout...')
|
|
356
|
+
);
|
|
357
|
+
|
|
358
|
+
// Config signals
|
|
359
|
+
const [navPosition, setNavPosition] = createSignal('left');
|
|
360
|
+
const [navMode, setNavMode] = createSignal('full');
|
|
361
|
+
const [showHeader, setShowHeader] = createSignal(true);
|
|
362
|
+
const [showFooter, setShowFooter] = createSignal(false);
|
|
363
|
+
const [showAside, setShowAside] = createSignal(false);
|
|
364
|
+
const [sidebarWidth, setSidebarWidth] = createSignal(240);
|
|
365
|
+
const [headerHeight, setHeaderHeight] = createSignal(52);
|
|
366
|
+
const [asideWidth, setAsideWidth] = createSignal(280);
|
|
367
|
+
|
|
368
|
+
const configSignals = {
|
|
369
|
+
navPosition, setNavPosition, navMode, setNavMode,
|
|
370
|
+
showHeader, setShowHeader, showFooter, setShowFooter,
|
|
371
|
+
showAside, setShowAside, sidebarWidth, setSidebarWidth,
|
|
372
|
+
headerHeight, setHeaderHeight, asideWidth, setAsideWidth
|
|
373
|
+
};
|
|
374
|
+
|
|
375
|
+
loadSkeletons().then(skeletons => {
|
|
376
|
+
container.innerHTML = '';
|
|
377
|
+
const skeleton = skeletons[presetId];
|
|
378
|
+
|
|
379
|
+
if (!skeleton) {
|
|
380
|
+
container.appendChild(p({ class: css('_fgmutedfg _body') }, `Shell layout "${presetId}" not found.`));
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// Initialize config signals from skeleton data
|
|
385
|
+
const cfg = skeleton.config;
|
|
386
|
+
if (cfg) {
|
|
387
|
+
if (cfg.nav?.position) setNavPosition(cfg.nav.position);
|
|
388
|
+
if (cfg.nav?.defaultState === 'hidden') setNavMode('hidden');
|
|
389
|
+
if (cfg.regions?.includes('footer')) setShowFooter(true);
|
|
390
|
+
if (cfg.regions?.includes('aside')) setShowAside(true);
|
|
391
|
+
if (cfg.header?.height) setHeaderHeight(parseInt(cfg.header.height) || 52);
|
|
392
|
+
if (cfg.nav?.width) setSidebarWidth(parseInt(cfg.nav.width) || 240);
|
|
393
|
+
if (cfg.aside?.width) setAsideWidth(parseInt(cfg.aside.width) || 280);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// Title
|
|
397
|
+
container.appendChild(
|
|
398
|
+
div({ class: css('_flex _col _gap1 _mb3') },
|
|
399
|
+
h2({ class: css('_heading4') }, skeleton.name || presetId),
|
|
400
|
+
p({ class: css('_body _fgmutedfg') }, skeleton.description || 'Configurable shell layout.')
|
|
401
|
+
)
|
|
402
|
+
);
|
|
403
|
+
|
|
404
|
+
// Tabs
|
|
405
|
+
container.appendChild(Tabs({
|
|
406
|
+
tabs: [
|
|
407
|
+
{
|
|
408
|
+
id: 'features',
|
|
409
|
+
label: 'Features',
|
|
410
|
+
content: () => featuresTab(skeleton, presetId)
|
|
411
|
+
},
|
|
412
|
+
{
|
|
413
|
+
id: 'configuration',
|
|
414
|
+
label: 'Configuration',
|
|
415
|
+
content: () => div({ class: css('_flex _col _gap4') },
|
|
416
|
+
ShellPreview(presetId, configSignals),
|
|
417
|
+
ShellConfigurator(presetId, configSignals)
|
|
418
|
+
)
|
|
419
|
+
},
|
|
420
|
+
{
|
|
421
|
+
id: 'code',
|
|
422
|
+
label: 'Code',
|
|
423
|
+
content: () => ShellCodeTab(presetId, configSignals)
|
|
424
|
+
}
|
|
425
|
+
]
|
|
426
|
+
}));
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
return container;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
function featuresTab(skeleton, presetId) {
|
|
433
|
+
const sections = [];
|
|
434
|
+
|
|
435
|
+
// Thumbnail preview
|
|
436
|
+
sections.push(
|
|
437
|
+
div({ class: css('_flex _col _gap3') },
|
|
438
|
+
h4({ class: css('_heading5') }, 'Shell Layout'),
|
|
439
|
+
ShellThumbnail(presetId)
|
|
440
|
+
)
|
|
441
|
+
);
|
|
442
|
+
|
|
443
|
+
// Layout type
|
|
444
|
+
sections.push(
|
|
445
|
+
div({ class: css('_flex _col _gap2') },
|
|
446
|
+
h4({ class: css('_heading6') }, 'Details'),
|
|
447
|
+
div({ class: css('_flex _gap2 _wrap') },
|
|
448
|
+
Chip({ label: `Layout: ${skeleton.layout || 'grid'}`, size: 'sm' }),
|
|
449
|
+
Chip({ label: `Atoms: ${skeleton.atoms || ''}`, variant: 'outline', size: 'sm' })
|
|
450
|
+
)
|
|
451
|
+
)
|
|
452
|
+
);
|
|
453
|
+
|
|
454
|
+
// Config regions
|
|
455
|
+
const cfg = skeleton.config;
|
|
456
|
+
if (cfg) {
|
|
457
|
+
sections.push(Separator({}));
|
|
458
|
+
sections.push(
|
|
459
|
+
div({ class: css('_flex _col _gap2') },
|
|
460
|
+
h4({ class: css('_heading6') }, 'Regions'),
|
|
461
|
+
div({ class: css('_flex _gap2 _wrap') },
|
|
462
|
+
...cfg.regions.map(r => Chip({ label: r, variant: 'outline', size: 'sm' }))
|
|
463
|
+
)
|
|
464
|
+
)
|
|
465
|
+
);
|
|
466
|
+
|
|
467
|
+
if (cfg.nav) {
|
|
468
|
+
sections.push(
|
|
469
|
+
div({ class: css('_flex _col _gap1') },
|
|
470
|
+
h4({ class: css('_heading6') }, 'Nav Config'),
|
|
471
|
+
code({ class: css('_caption _fgmutedfg') }, `Position: ${cfg.nav.position || 'left'}`),
|
|
472
|
+
cfg.nav.width ? code({ class: css('_caption _fgmutedfg') }, `Width: ${cfg.nav.width}`) : null,
|
|
473
|
+
cfg.nav.collapseTo ? code({ class: css('_caption _fgmutedfg') }, `Collapse to: ${cfg.nav.collapseTo}`) : null,
|
|
474
|
+
cfg.nav.collapseBelow ? code({ class: css('_caption _fgmutedfg') }, `Auto-collapse below: ${cfg.nav.collapseBelow}`) : null
|
|
475
|
+
)
|
|
476
|
+
);
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// Configurable options
|
|
481
|
+
const configurable = skeleton.configurable;
|
|
482
|
+
if (configurable) {
|
|
483
|
+
sections.push(Separator({}));
|
|
484
|
+
sections.push(
|
|
485
|
+
div({ class: css('_flex _col _gap2') },
|
|
486
|
+
h4({ class: css('_heading6') }, 'Configurable'),
|
|
487
|
+
configurable.nav?.positions ?
|
|
488
|
+
div({}, span({ class: css('_caption _fgmutedfg') }, `Nav positions: ${configurable.nav.positions.join(', ')}`)) : null,
|
|
489
|
+
configurable.nav?.modes ?
|
|
490
|
+
div({}, span({ class: css('_caption _fgmutedfg') }, `Nav modes: ${configurable.nav.modes.join(', ')}`)) : null,
|
|
491
|
+
configurable.footer?.toggleable ?
|
|
492
|
+
div({}, span({ class: css('_caption _fgmutedfg') }, 'Footer: toggleable')) : null,
|
|
493
|
+
configurable.aside?.toggleable ?
|
|
494
|
+
div({}, span({ class: css('_caption _fgmutedfg') }, 'Aside: toggleable')) : null
|
|
495
|
+
)
|
|
496
|
+
);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
return div({ class: css('_flex _col _gap4') }, ...sections);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
// ─── Shell List View (gallery) ──────────────────────────────────
|
|
503
|
+
|
|
504
|
+
export function ShellListView(navigateTo) {
|
|
505
|
+
const container = div({ class: css('_flex _col _gap4') },
|
|
506
|
+
p({ class: css('_body _fgmutedfg') }, 'Loading shell layouts...')
|
|
507
|
+
);
|
|
508
|
+
|
|
509
|
+
loadSkeletons().then(skeletons => {
|
|
510
|
+
container.innerHTML = '';
|
|
511
|
+
const entries = Object.entries(skeletons);
|
|
512
|
+
container.appendChild(h2({ class: css('_heading4 _mb2') }, 'Shells'));
|
|
513
|
+
container.appendChild(p({ class: css('_body _fgmutedfg _mb3') }, `${entries.length} configurable shell layouts.`));
|
|
514
|
+
|
|
515
|
+
const grid = div({ class: 'de-card-grid' });
|
|
516
|
+
for (const [id, skel] of entries) {
|
|
517
|
+
grid.appendChild(div({
|
|
518
|
+
class: 'de-card-item',
|
|
519
|
+
onclick: () => navigateTo(`/shells/${id}`)
|
|
520
|
+
},
|
|
521
|
+
ShellThumbnail(id),
|
|
522
|
+
div({ class: css('_flex _col _gap2') },
|
|
523
|
+
h3({ class: css('_heading6') }, skel.name || id),
|
|
524
|
+
p({ class: css('_caption _fgmutedfg') }, skel.description || ''),
|
|
525
|
+
skel.config ? div({ class: css('_flex _gap1 _wrap') },
|
|
526
|
+
...(skel.config.regions || []).map(r =>
|
|
527
|
+
Chip({ label: r, variant: 'outline', size: 'sm' })
|
|
528
|
+
)
|
|
529
|
+
) : null
|
|
530
|
+
)
|
|
531
|
+
));
|
|
532
|
+
}
|
|
533
|
+
container.appendChild(grid);
|
|
534
|
+
});
|
|
535
|
+
|
|
536
|
+
return container;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
// ─── Sidebar items loader ───────────────────────────────────────
|
|
540
|
+
|
|
541
|
+
export async function loadShellItems() {
|
|
542
|
+
try {
|
|
543
|
+
const resp = await fetch('/__decantr/registry/skeletons.json');
|
|
544
|
+
const data = await resp.json();
|
|
545
|
+
return Object.entries(data.skeletons || {}).map(([id, skel]) => ({
|
|
546
|
+
id, label: skel.name || id.split('-').map(w => w[0].toUpperCase() + w.slice(1)).join(' ')
|
|
547
|
+
}));
|
|
548
|
+
} catch {
|
|
549
|
+
return [];
|
|
550
|
+
}
|
|
551
|
+
}
|