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,958 @@
|
|
|
1
|
+
import { tags } from 'decantr/tags';
|
|
2
|
+
import { css } from 'decantr/css';
|
|
3
|
+
import { createSignal } from 'decantr/state';
|
|
4
|
+
import * as Components from 'decantr/components';
|
|
5
|
+
import { Chart, Sparkline } from 'decantr/chart';
|
|
6
|
+
import { injectExplorerCSS } from '../styles.js';
|
|
7
|
+
injectExplorerCSS();
|
|
8
|
+
|
|
9
|
+
const { div, h4, p, span, code, button: btn } = tags;
|
|
10
|
+
|
|
11
|
+
/** Sample data for chart examples. */
|
|
12
|
+
const CHART_SAMPLE_DATA = [
|
|
13
|
+
{ month: 'Jan', value: 40 },
|
|
14
|
+
{ month: 'Feb', value: 55 },
|
|
15
|
+
{ month: 'Mar', value: 70 },
|
|
16
|
+
{ month: 'Apr', value: 62 },
|
|
17
|
+
{ month: 'May', value: 85 },
|
|
18
|
+
{ month: 'Jun', value: 78 }
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
/** Components that require a `visible` signal getter to show/hide. */
|
|
22
|
+
const VISIBLE_SIGNAL = new Set(['Modal', 'Drawer', 'AlertDialog', 'Command']);
|
|
23
|
+
|
|
24
|
+
/** Components that require a `trigger` prop (function returning element). */
|
|
25
|
+
const TRIGGER_PROP = new Set(['Popover', 'Popconfirm', 'Dropdown', 'HoverCard']);
|
|
26
|
+
|
|
27
|
+
/** Components that use children as the trigger element. */
|
|
28
|
+
const CHILDREN_TRIGGER = new Set(['Tooltip']);
|
|
29
|
+
|
|
30
|
+
/** Components that require a `target` element. */
|
|
31
|
+
const TARGET_PROP = new Set(['ContextMenu']);
|
|
32
|
+
|
|
33
|
+
/** Imperative functions — call them to show ephemeral UI (both casings for group lookup). */
|
|
34
|
+
const IMPERATIVE_FN = new Set(['toast', 'message', 'notification', 'Toast', 'Message', 'Notification']);
|
|
35
|
+
|
|
36
|
+
/** Components returning controller objects, not elements. */
|
|
37
|
+
const CONTROLLER_FN = new Set(['Tour']);
|
|
38
|
+
|
|
39
|
+
/** Override showcase rendering for specific component+state combinations. */
|
|
40
|
+
const SHOWCASE_PROP_OVERRIDES = {
|
|
41
|
+
'Button:loading': (defaults) => ({
|
|
42
|
+
props: { ...defaults, disabled: true, iconLeft: Components.Spinner({ size: 'xs' }) },
|
|
43
|
+
children: 'Loading...'
|
|
44
|
+
}),
|
|
45
|
+
'Button:Loading state': (defaults) => ({
|
|
46
|
+
props: { ...defaults, variant: 'primary', disabled: true, iconLeft: Components.Spinner({ size: 'xs' }) },
|
|
47
|
+
children: 'Loading...'
|
|
48
|
+
})
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/** Components with fully custom showcase rendering. */
|
|
52
|
+
const CUSTOM_EXAMPLES = new Set(['Card', 'Placeholder', 'Timeline', 'Comment', 'QRCode']);
|
|
53
|
+
|
|
54
|
+
/** Map group IDs to grid classes for adaptive layout. */
|
|
55
|
+
const STACK_GROUPS = new Set(['layout', 'navigation', 'chart', 'data']);
|
|
56
|
+
const MD_GROUPS = new Set(['form', 'form-basic', 'form-advanced']);
|
|
57
|
+
const WIDE_COMPONENTS = new Set(['ToggleGroup']);
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Get the showcase grid class for a component based on its group.
|
|
61
|
+
* @param {string} componentName
|
|
62
|
+
* @param {string} [groupId]
|
|
63
|
+
* @returns {string}
|
|
64
|
+
*/
|
|
65
|
+
export function getShowcaseGridClass(componentName, groupId) {
|
|
66
|
+
if (groupId && STACK_GROUPS.has(groupId)) return 'de-showcase-stack';
|
|
67
|
+
if (groupId && MD_GROUPS.has(groupId)) return 'de-showcase-grid-md';
|
|
68
|
+
if (WIDE_COMPONENTS.has(componentName)) return 'de-showcase-grid-md';
|
|
69
|
+
return 'de-showcase-grid';
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Render a full component showcase from registry data.
|
|
74
|
+
* @param {string} componentName
|
|
75
|
+
* @param {object} registryEntry
|
|
76
|
+
* @param {Function} navigateTo
|
|
77
|
+
* @param {string} [gridClass='de-showcase-grid']
|
|
78
|
+
* @returns {HTMLElement}
|
|
79
|
+
*/
|
|
80
|
+
export function renderShowcase(componentName, registryEntry, navigateTo, gridClass) {
|
|
81
|
+
gridClass = gridClass || 'de-showcase-grid';
|
|
82
|
+
const { Separator, icon } = Components;
|
|
83
|
+
const chartType = registryEntry?.chartType;
|
|
84
|
+
// Resolve component function: chart types → Chart/Sparkline, else lookup by name (try exact, then lowercase)
|
|
85
|
+
const resolvedName = Components[componentName] ? componentName
|
|
86
|
+
: Components[componentName.toLowerCase()] ? componentName.toLowerCase()
|
|
87
|
+
: componentName;
|
|
88
|
+
const ComponentFn = chartType
|
|
89
|
+
? (chartType === 'sparkline' ? (props) => Sparkline({ data: CHART_SAMPLE_DATA.map(d => d.value), ...props }) : (props) => Chart({ data: CHART_SAMPLE_DATA, ...props }))
|
|
90
|
+
: Components[resolvedName];
|
|
91
|
+
const props = registryEntry?.props || {};
|
|
92
|
+
const showcase = registryEntry?.showcase;
|
|
93
|
+
const defaults = showcase?.defaults || {};
|
|
94
|
+
const labelProp = showcase?.labelProp || null;
|
|
95
|
+
|
|
96
|
+
if (!ComponentFn) {
|
|
97
|
+
return div({ class: css('_fgmutedfg _body') }, `Component "${componentName}" not found in exports.`);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Custom showcase rendering for complex components
|
|
101
|
+
if (CUSTOM_EXAMPLES.has(componentName)) {
|
|
102
|
+
if (componentName === 'Placeholder') return renderPlaceholderExamples();
|
|
103
|
+
if (componentName === 'Timeline') return renderTimelineExamples();
|
|
104
|
+
if (componentName === 'Comment') return renderCommentExamples();
|
|
105
|
+
if (componentName === 'QRCode') return renderQRCodeExamples();
|
|
106
|
+
return renderCardExamples();
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const sections = [];
|
|
110
|
+
|
|
111
|
+
// ── Variant Grid ──────────────────────────────────────────
|
|
112
|
+
const variantEnum = props.variant?.enum;
|
|
113
|
+
if (showcase?.sections?.includes('variants') && variantEnum?.length > 0) {
|
|
114
|
+
const children = getDefaultChildren(componentName, showcase);
|
|
115
|
+
sections.push(
|
|
116
|
+
showcaseSection('Variants',
|
|
117
|
+
div({ class: gridClass },
|
|
118
|
+
...variantEnum.map(v => {
|
|
119
|
+
try {
|
|
120
|
+
return labeledItem(renderWithVariant(ComponentFn, componentName, { ...defaults, variant: v }, children, labelProp), v);
|
|
121
|
+
} catch { return span({ class: css('_fgerror _caption') }, `Error: ${v}`); }
|
|
122
|
+
})
|
|
123
|
+
)
|
|
124
|
+
)
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// ── Size Grid ─────────────────────────────────────────────
|
|
129
|
+
const sizeEnum = props.size?.enum?.filter(s => !s.startsWith('icon'));
|
|
130
|
+
if (showcase?.sections?.includes('sizes') && sizeEnum?.length > 0) {
|
|
131
|
+
const children = getDefaultChildren(componentName, showcase);
|
|
132
|
+
const allSizes = [{ label: 'default', value: undefined }, ...sizeEnum.map(s => ({ label: s, value: s }))];
|
|
133
|
+
sections.push(
|
|
134
|
+
showcaseSection('Sizes',
|
|
135
|
+
div({ class: gridClass },
|
|
136
|
+
...allSizes.map(({ label, value }) => {
|
|
137
|
+
try {
|
|
138
|
+
const sizeProps = { ...defaults };
|
|
139
|
+
if (value) sizeProps.size = value;
|
|
140
|
+
return labeledItem(renderWithVariant(ComponentFn, componentName, sizeProps, children, labelProp), label);
|
|
141
|
+
} catch { return span({ class: css('_fgerror _caption') }, `Error: ${label}`); }
|
|
142
|
+
})
|
|
143
|
+
)
|
|
144
|
+
)
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// ── Icon Grid ─────────────────────────────────────────────
|
|
149
|
+
if (showcase?.sections?.includes('icons') && showcase?.icons?.samples?.length > 0) {
|
|
150
|
+
sections.push(
|
|
151
|
+
showcaseSection('With Icons',
|
|
152
|
+
div({ class: gridClass },
|
|
153
|
+
...showcase.icons.samples.map(iconName => {
|
|
154
|
+
try {
|
|
155
|
+
return ComponentFn(
|
|
156
|
+
{ ...defaults, variant: 'primary', iconLeft: icon(iconName, { size: '1em' }) },
|
|
157
|
+
iconName
|
|
158
|
+
);
|
|
159
|
+
} catch { return span({ class: css('_fgerror _caption') }, `Error: ${iconName}`); }
|
|
160
|
+
})
|
|
161
|
+
)
|
|
162
|
+
)
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// ── State Grid ────────────────────────────────────────────
|
|
167
|
+
if (showcase?.sections?.includes('states') && showcase?.states) {
|
|
168
|
+
const children = getDefaultChildren(componentName, showcase);
|
|
169
|
+
const stateItems = [];
|
|
170
|
+
for (const [stateProp, defaultVal] of Object.entries(showcase.states)) {
|
|
171
|
+
try {
|
|
172
|
+
const overrideKey = componentName + ':' + stateProp;
|
|
173
|
+
if (SHOWCASE_PROP_OVERRIDES[overrideKey]) {
|
|
174
|
+
const ov = SHOWCASE_PROP_OVERRIDES[overrideKey](defaults);
|
|
175
|
+
const el = renderWithVariant(ComponentFn, componentName, ov.props, ov.children, labelProp);
|
|
176
|
+
stateItems.push(labeledItem(el, stateProp));
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
const propObj = { ...defaults, [stateProp]: defaultVal };
|
|
180
|
+
if (props.variant) propObj.variant = propObj.variant || 'primary';
|
|
181
|
+
const el = renderWithVariant(ComponentFn, componentName, propObj, children, labelProp);
|
|
182
|
+
stateItems.push(labeledItem(el, stateProp));
|
|
183
|
+
} catch { /* skip */ }
|
|
184
|
+
}
|
|
185
|
+
if (stateItems.length > 0) {
|
|
186
|
+
sections.push(
|
|
187
|
+
showcaseSection('States',
|
|
188
|
+
div({ class: gridClass }, ...stateItems)
|
|
189
|
+
)
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// ── Examples ──────────────────────────────────────────────
|
|
195
|
+
if (showcase?.sections?.includes('examples') && showcase?.examples?.length > 0) {
|
|
196
|
+
const children = getDefaultChildren(componentName, showcase);
|
|
197
|
+
sections.push(
|
|
198
|
+
showcaseSection('Examples',
|
|
199
|
+
div({ class: css('_flex _col _gap4') },
|
|
200
|
+
...showcase.examples.map(ex => {
|
|
201
|
+
try {
|
|
202
|
+
const overrideKey = componentName + ':' + ex.name;
|
|
203
|
+
let el;
|
|
204
|
+
if (SHOWCASE_PROP_OVERRIDES[overrideKey]) {
|
|
205
|
+
const ov = SHOWCASE_PROP_OVERRIDES[overrideKey](defaults);
|
|
206
|
+
el = renderExample(ComponentFn, componentName, ov.props, ov.children, labelProp);
|
|
207
|
+
} else {
|
|
208
|
+
const mergedProps = { ...defaults, ...ex.props };
|
|
209
|
+
el = renderExample(ComponentFn, componentName, mergedProps, children, labelProp);
|
|
210
|
+
}
|
|
211
|
+
return div({ class: 'de-example-card' },
|
|
212
|
+
div({ class: 'de-example-demo' }, el),
|
|
213
|
+
div({ class: 'de-example-meta' },
|
|
214
|
+
span({ class: css('_label _fgfg') }, ex.name),
|
|
215
|
+
ex.description ? p({ class: css('_caption _fgmutedfg') }, ex.description) : null
|
|
216
|
+
)
|
|
217
|
+
);
|
|
218
|
+
} catch { return span({ class: css('_fgerror _caption') }, `Error: ${ex.name}`); }
|
|
219
|
+
})
|
|
220
|
+
)
|
|
221
|
+
)
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (sections.length === 0) {
|
|
226
|
+
return div({ class: css('_fgmutedfg _body') }, 'No showcase sections available. See the API tab for props and usage.');
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Strip leading separator from first section — it's only useful as a divider between sections
|
|
230
|
+
if (sections.length > 0) {
|
|
231
|
+
const first = sections[0];
|
|
232
|
+
const firstChild = first.firstElementChild;
|
|
233
|
+
if (firstChild && firstChild.classList.contains('d-separator')) {
|
|
234
|
+
firstChild.remove();
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return div({ class: css('_flex _col _gap8') }, ...sections);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Render a single showcase section with heading.
|
|
243
|
+
*/
|
|
244
|
+
function showcaseSection(title, content) {
|
|
245
|
+
const { Separator } = Components;
|
|
246
|
+
return div({ class: css('_flex _col _gap4') },
|
|
247
|
+
Separator ? Separator({}) : div({ class: css('_bt[var(--d-border-width)_solid_var(--d-border)]') }),
|
|
248
|
+
h4({ class: css('_heading5 _mb3') }, title),
|
|
249
|
+
content
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Wrap an element with a label underneath.
|
|
255
|
+
*/
|
|
256
|
+
function labeledItem(el, label) {
|
|
257
|
+
return div({ class: 'de-specimen' }, el,
|
|
258
|
+
span({ class: 'de-specimen-label' }, label)
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Get default children for a component based on showcase spec.
|
|
264
|
+
*/
|
|
265
|
+
function getDefaultChildren(componentName, showcase) {
|
|
266
|
+
return showcase?.children ?? componentName;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Render a component with specific props, handling children properly.
|
|
271
|
+
*/
|
|
272
|
+
function renderWithVariant(ComponentFn, componentName, mergedProps, children, labelProp) {
|
|
273
|
+
if (labelProp && children) {
|
|
274
|
+
mergedProps[labelProp] = children;
|
|
275
|
+
return ComponentFn(mergedProps);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (children) {
|
|
279
|
+
try {
|
|
280
|
+
const el = ComponentFn(mergedProps, children);
|
|
281
|
+
if (el instanceof HTMLElement) return el;
|
|
282
|
+
} catch { /* fall through */ }
|
|
283
|
+
}
|
|
284
|
+
return ComponentFn(mergedProps);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Render an example, with special handling for overlays and trigger components.
|
|
289
|
+
* - Visible-signal components get a toggle button + signal wiring.
|
|
290
|
+
* - Trigger-prop components get a trigger button injected.
|
|
291
|
+
* - Children-trigger components get a button as children.
|
|
292
|
+
* - Target-prop components get a target zone element.
|
|
293
|
+
* - Imperative functions get a trigger button that calls the function.
|
|
294
|
+
* - Controller functions get a trigger button that calls start().
|
|
295
|
+
* - Everything else delegates to renderWithVariant.
|
|
296
|
+
*/
|
|
297
|
+
function renderExample(ComponentFn, componentName, mergedProps, children, labelProp) {
|
|
298
|
+
const { Button } = Components;
|
|
299
|
+
const triggerBtn = (label) => Button
|
|
300
|
+
? Button({ variant: 'outline', size: 'sm' }, label)
|
|
301
|
+
: btn({ class: css('_body _p3 _r2 _b1 _bcborder _bgmuted _fgfg _pointer') }, label);
|
|
302
|
+
|
|
303
|
+
// ── Visible-signal overlays (Modal, Drawer, AlertDialog, Command) ──
|
|
304
|
+
if (VISIBLE_SIGNAL.has(componentName)) {
|
|
305
|
+
const [visible, setVisible] = createSignal(false);
|
|
306
|
+
const label = mergedProps.title || componentName;
|
|
307
|
+
const toggle = triggerBtn(`Open ${label}`);
|
|
308
|
+
toggle.onclick = () => setVisible(true);
|
|
309
|
+
const overlay = ComponentFn({
|
|
310
|
+
...mergedProps,
|
|
311
|
+
visible,
|
|
312
|
+
onClose: () => setVisible(false),
|
|
313
|
+
onCancel: () => setVisible(false)
|
|
314
|
+
}, ...(children ? [children] : ['Example content']));
|
|
315
|
+
return div({ class: css('_flex _aic _gap3') }, toggle, overlay);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// ── Trigger-prop components (Popover, Popconfirm, Dropdown, HoverCard) ──
|
|
319
|
+
if (TRIGGER_PROP.has(componentName)) {
|
|
320
|
+
const btnLabel = componentName === 'Popconfirm' ? 'Delete'
|
|
321
|
+
: componentName === 'Dropdown' ? 'Menu'
|
|
322
|
+
: componentName;
|
|
323
|
+
mergedProps.trigger = () => triggerBtn(btnLabel);
|
|
324
|
+
if (componentName === 'Popover' || componentName === 'HoverCard') {
|
|
325
|
+
return ComponentFn(mergedProps, children || 'Example content');
|
|
326
|
+
}
|
|
327
|
+
return ComponentFn(mergedProps);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// ── Children-trigger components (Tooltip) ──
|
|
331
|
+
if (CHILDREN_TRIGGER.has(componentName)) {
|
|
332
|
+
return ComponentFn(mergedProps, triggerBtn(mergedProps.content || componentName));
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// ── Target-prop components (ContextMenu) ──
|
|
336
|
+
if (TARGET_PROP.has(componentName)) {
|
|
337
|
+
const zone = div(
|
|
338
|
+
{ class: css('_p6 _r2 _b1 _bcborder _fgmutedfg _body _tac _pointer') },
|
|
339
|
+
'Right-click here'
|
|
340
|
+
);
|
|
341
|
+
mergedProps.target = zone;
|
|
342
|
+
const menu = ComponentFn(mergedProps);
|
|
343
|
+
return div({ class: css('_flex _col _gap2') }, zone, menu);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// ── Imperative functions (toast, message, notification) ──
|
|
347
|
+
if (IMPERATIVE_FN.has(componentName)) {
|
|
348
|
+
const label = mergedProps.title || mergedProps.content || componentName;
|
|
349
|
+
const trigger = triggerBtn(`Show ${label}`);
|
|
350
|
+
trigger.onclick = () => {
|
|
351
|
+
try { ComponentFn(mergedProps); } catch { /* ignore */ }
|
|
352
|
+
};
|
|
353
|
+
return trigger;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// ── Controller components (Tour) — returns {start, next, ...} ──
|
|
357
|
+
if (CONTROLLER_FN.has(componentName)) {
|
|
358
|
+
const trigger = triggerBtn('Start Tour');
|
|
359
|
+
trigger.onclick = () => {
|
|
360
|
+
try {
|
|
361
|
+
const ctrl = ComponentFn(mergedProps);
|
|
362
|
+
if (ctrl && typeof ctrl.start === 'function') ctrl.start();
|
|
363
|
+
} catch { /* ignore */ }
|
|
364
|
+
};
|
|
365
|
+
return trigger;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// ── Default rendering ──
|
|
369
|
+
return renderWithVariant(ComponentFn, componentName, mergedProps, children, labelProp);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Custom showcase rendering for Placeholder component.
|
|
374
|
+
*/
|
|
375
|
+
function renderPlaceholderExamples() {
|
|
376
|
+
const { Placeholder, Card, Avatar } = Components;
|
|
377
|
+
if (!Placeholder) return div({ class: css('_fgmutedfg _body') }, 'Placeholder not found');
|
|
378
|
+
|
|
379
|
+
const example = (title, description, el) =>
|
|
380
|
+
div({ class: 'de-example-card' },
|
|
381
|
+
div({ class: 'de-example-demo' }, el),
|
|
382
|
+
div({ class: 'de-example-meta' },
|
|
383
|
+
span({ class: css('_label _fgfg') }, title),
|
|
384
|
+
description ? p({ class: css('_caption _fgmutedfg') }, description) : null
|
|
385
|
+
)
|
|
386
|
+
);
|
|
387
|
+
|
|
388
|
+
const examples = [];
|
|
389
|
+
|
|
390
|
+
// 1. Default 16:9
|
|
391
|
+
examples.push(example('Default (16:9)', 'Image placeholder with brand watermark',
|
|
392
|
+
Placeholder()
|
|
393
|
+
));
|
|
394
|
+
|
|
395
|
+
// 2. With Label
|
|
396
|
+
examples.push(example('With Label', 'Text overlay above watermark',
|
|
397
|
+
Placeholder({ label: 'Cover Image' })
|
|
398
|
+
));
|
|
399
|
+
|
|
400
|
+
// 3. In Card Cover
|
|
401
|
+
if (Card) {
|
|
402
|
+
examples.push(example('In Card Cover', 'Replaces emoji hack in Card.Cover',
|
|
403
|
+
Card({ cover: Placeholder({ variant: 'image', label: 'Cover Image' }) },
|
|
404
|
+
Card.Body({},
|
|
405
|
+
Card.Meta({
|
|
406
|
+
avatar: Avatar ? Avatar({ name: 'Jane Doe', size: 'md' }) : span({}, 'JD'),
|
|
407
|
+
title: 'Jane Doe',
|
|
408
|
+
description: 'Senior Product Designer'
|
|
409
|
+
})
|
|
410
|
+
)
|
|
411
|
+
)
|
|
412
|
+
));
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// 4. Avatar variants at different sizes
|
|
416
|
+
examples.push(example('Avatar Variants', 'Circular placeholders at multiple sizes',
|
|
417
|
+
div({ class: css('_flex _aic _gap4') },
|
|
418
|
+
Placeholder({ variant: 'avatar', width: '32px' }),
|
|
419
|
+
Placeholder({ variant: 'avatar', width: '48px' }),
|
|
420
|
+
Placeholder({ variant: 'avatar', width: '64px' }),
|
|
421
|
+
Placeholder({ variant: 'avatar', width: '96px' })
|
|
422
|
+
)
|
|
423
|
+
));
|
|
424
|
+
|
|
425
|
+
// 5. Icon variant
|
|
426
|
+
examples.push(example('Icon Variant', 'Square placeholder for app icons',
|
|
427
|
+
div({ class: css('_flex _aic _gap4') },
|
|
428
|
+
Placeholder({ variant: 'icon', width: '32px' }),
|
|
429
|
+
Placeholder({ variant: 'icon', width: '48px' }),
|
|
430
|
+
Placeholder({ variant: 'icon', width: '64px' })
|
|
431
|
+
)
|
|
432
|
+
));
|
|
433
|
+
|
|
434
|
+
// 6. Custom aspect ratios
|
|
435
|
+
examples.push(example('Custom Aspect Ratios', 'Override default 16:9',
|
|
436
|
+
div({ class: css('_flex _gap4') },
|
|
437
|
+
div({ class: css('_flex1') }, Placeholder({ aspectRatio: '1/1', label: '1:1' })),
|
|
438
|
+
div({ class: css('_flex1') }, Placeholder({ aspectRatio: '4/3', label: '4:3' })),
|
|
439
|
+
div({ class: css('_flex1') }, Placeholder({ aspectRatio: '21/9', label: '21:9' }))
|
|
440
|
+
)
|
|
441
|
+
));
|
|
442
|
+
|
|
443
|
+
// 7. Shimmer animation
|
|
444
|
+
examples.push(example('Animated', 'Shimmer loading effect',
|
|
445
|
+
Placeholder({ animate: true, label: 'Loading...' })
|
|
446
|
+
));
|
|
447
|
+
|
|
448
|
+
return div({ class: css('_flex _col _gap8') },
|
|
449
|
+
showcaseSection('Examples',
|
|
450
|
+
div({ class: css('_flex _col _gap4') }, ...examples)
|
|
451
|
+
)
|
|
452
|
+
);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
/**
|
|
456
|
+
* Custom showcase rendering for Card component with rich composed examples.
|
|
457
|
+
*/
|
|
458
|
+
function renderCardExamples() {
|
|
459
|
+
const { Card, Avatar, Button, Tabs, Skeleton, icon, Separator } = Components;
|
|
460
|
+
if (!Card) return div({ class: css('_fgmutedfg _body') }, 'Card not found');
|
|
461
|
+
|
|
462
|
+
const example = (title, description, el) =>
|
|
463
|
+
div({ class: 'de-example-card' },
|
|
464
|
+
div({ class: 'de-example-demo' }, el),
|
|
465
|
+
div({ class: 'de-example-meta' },
|
|
466
|
+
span({ class: css('_label _fgfg') }, title),
|
|
467
|
+
description ? p({ class: css('_caption _fgmutedfg') }, description) : null
|
|
468
|
+
)
|
|
469
|
+
);
|
|
470
|
+
|
|
471
|
+
const placeholderCover = () =>
|
|
472
|
+
Components.Placeholder ? Components.Placeholder({ variant: 'image', height: '180px', label: 'Cover Image' })
|
|
473
|
+
: div({ class: css('_h[180px] _bgmuted _flex _aic _jcc _fgmutedfg _textlg') }, 'Cover Image');
|
|
474
|
+
|
|
475
|
+
const examples = [];
|
|
476
|
+
|
|
477
|
+
// 1. Basic Card
|
|
478
|
+
examples.push(example('Basic Card', 'Title + paragraph body',
|
|
479
|
+
Card({ title: 'Card Title' },
|
|
480
|
+
Card.Body({}, p({}, 'Some quick example text to build on the card title and make up the bulk of the card\'s content.'))
|
|
481
|
+
)
|
|
482
|
+
));
|
|
483
|
+
|
|
484
|
+
// 2. Card with Extra
|
|
485
|
+
examples.push(example('Card with Extra', 'Title + "More" link in header extra',
|
|
486
|
+
Card({ title: 'Card Title', extra: span({ class: css('_fgprimary _textsm _pointer') }, 'More →') },
|
|
487
|
+
Card.Body({}, p({}, 'Card content with an action link in the header.'))
|
|
488
|
+
)
|
|
489
|
+
));
|
|
490
|
+
|
|
491
|
+
// 3. Cover + Avatar + Meta
|
|
492
|
+
examples.push(example('Cover + Meta', 'Cover image with avatar meta section',
|
|
493
|
+
Card({ cover: placeholderCover() },
|
|
494
|
+
Card.Body({},
|
|
495
|
+
Card.Meta({
|
|
496
|
+
avatar: Avatar ? Avatar({ name: 'Jane Doe', size: 'md' }) : span({}, 'JD'),
|
|
497
|
+
title: 'Jane Doe',
|
|
498
|
+
description: 'Senior Product Designer at Acme Inc.'
|
|
499
|
+
})
|
|
500
|
+
)
|
|
501
|
+
)
|
|
502
|
+
));
|
|
503
|
+
|
|
504
|
+
// 4. Loading Card
|
|
505
|
+
examples.push(example('Loading', 'Skeleton loading state',
|
|
506
|
+
Card({ loading: true })
|
|
507
|
+
));
|
|
508
|
+
|
|
509
|
+
// 5. Inner Card
|
|
510
|
+
examples.push(example('Inner Card', 'Parent card containing nested inner cards',
|
|
511
|
+
Card({ title: 'Group Title' },
|
|
512
|
+
Card.Body({ class: css('_flex _col _gap3') },
|
|
513
|
+
Card({ type: 'inner', title: 'Inner Card 1' },
|
|
514
|
+
Card.Body({}, p({}, 'Inner card content.'))
|
|
515
|
+
),
|
|
516
|
+
Card({ type: 'inner', title: 'Inner Card 2' },
|
|
517
|
+
Card.Body({}, p({}, 'Another inner card.'))
|
|
518
|
+
)
|
|
519
|
+
)
|
|
520
|
+
)
|
|
521
|
+
));
|
|
522
|
+
|
|
523
|
+
// 6. Borderless Card
|
|
524
|
+
examples.push(example('Borderless', 'Card without visible border',
|
|
525
|
+
div({ class: css('_bgmuted _p4 _r2') },
|
|
526
|
+
Card({ title: 'Borderless', bordered: false },
|
|
527
|
+
Card.Body({}, p({}, 'On muted background to show the effect.'))
|
|
528
|
+
)
|
|
529
|
+
)
|
|
530
|
+
));
|
|
531
|
+
|
|
532
|
+
// 7. Small Size
|
|
533
|
+
examples.push(example('Small Size', 'Compact card with reduced padding',
|
|
534
|
+
Card({ title: 'Compact Card', size: 'sm' },
|
|
535
|
+
Card.Body({}, p({}, 'Smaller padding and font size for dense layouts.'))
|
|
536
|
+
)
|
|
537
|
+
));
|
|
538
|
+
|
|
539
|
+
// 8. Card with Actions
|
|
540
|
+
examples.push(example('Card with Actions', 'Bottom action bar with icons',
|
|
541
|
+
Card({
|
|
542
|
+
title: 'Actions Card',
|
|
543
|
+
actions: [
|
|
544
|
+
span({}, icon ? icon('settings', { size: '1em' }) : '⚙'),
|
|
545
|
+
span({}, icon ? icon('edit', { size: '1em' }) : '✏'),
|
|
546
|
+
span({}, icon ? icon('more-horizontal', { size: '1em' }) : '⋯')
|
|
547
|
+
]
|
|
548
|
+
},
|
|
549
|
+
Card.Body({}, p({}, 'Card content with an action bar below.'))
|
|
550
|
+
)
|
|
551
|
+
));
|
|
552
|
+
|
|
553
|
+
// 9. Grid Card
|
|
554
|
+
examples.push(example('Grid Card', 'Card.Grid with multiple cells',
|
|
555
|
+
Card({},
|
|
556
|
+
Card.Grid({ hoverable: true },
|
|
557
|
+
...[1, 2, 3, 4, 5, 6].map(n =>
|
|
558
|
+
div({ class: css('_p4 _tac') }, `Cell ${n}`)
|
|
559
|
+
)
|
|
560
|
+
)
|
|
561
|
+
)
|
|
562
|
+
));
|
|
563
|
+
|
|
564
|
+
// 10. Hoverable Card
|
|
565
|
+
examples.push(example('Hoverable', 'Interactive hover elevation',
|
|
566
|
+
Card({ title: 'Hover Me', hoverable: true },
|
|
567
|
+
Card.Body({}, p({}, 'Try hovering over this card.'))
|
|
568
|
+
)
|
|
569
|
+
));
|
|
570
|
+
|
|
571
|
+
// 11. Profile Card
|
|
572
|
+
examples.push(example('Profile Card', 'Full composition: cover, avatar meta, bio, footer',
|
|
573
|
+
Card({ cover: placeholderCover() },
|
|
574
|
+
Card.Body({ class: css('_flex _col _gap3') },
|
|
575
|
+
Card.Meta({
|
|
576
|
+
avatar: Avatar ? Avatar({ name: 'Alex Rivera', size: 'lg' }) : span({}, 'AR'),
|
|
577
|
+
title: 'Alex Rivera',
|
|
578
|
+
description: '@alexrivera'
|
|
579
|
+
}),
|
|
580
|
+
p({ class: css('_textsm _fgmutedfg') }, 'Full-stack engineer. Open source enthusiast. Coffee ☕ and code.')
|
|
581
|
+
),
|
|
582
|
+
Card.Footer({ class: css('_flex _jcc') },
|
|
583
|
+
Button ? Button({ variant: 'primary', size: 'sm' }, 'Follow') : btn({}, 'Follow')
|
|
584
|
+
)
|
|
585
|
+
)
|
|
586
|
+
));
|
|
587
|
+
|
|
588
|
+
// 12. Product Card
|
|
589
|
+
examples.push(example('Product Card', 'Cover image, title + price, CTA button',
|
|
590
|
+
Card({ cover: placeholderCover() },
|
|
591
|
+
Card.Body({ class: css('_flex _col _gap2') },
|
|
592
|
+
h4({ class: css('_heading5') }, 'Premium Headphones'),
|
|
593
|
+
p({ class: css('_textsm _fgmutedfg') }, 'Wireless noise-cancelling over-ear headphones.'),
|
|
594
|
+
div({ class: css('_flex _aic _jcsb _mt2') },
|
|
595
|
+
span({ class: css('_heading4') }, '$299'),
|
|
596
|
+
Button ? Button({ variant: 'primary', size: 'sm' }, 'Add to Cart') : btn({}, 'Add to Cart')
|
|
597
|
+
)
|
|
598
|
+
)
|
|
599
|
+
)
|
|
600
|
+
));
|
|
601
|
+
|
|
602
|
+
// 13. Pricing Card
|
|
603
|
+
examples.push(example('Pricing Card', 'Header with price, feature list, CTA footer',
|
|
604
|
+
Card({},
|
|
605
|
+
Card.Header({ class: css('_tac') },
|
|
606
|
+
h4({ class: css('_heading4') }, 'Pro Plan'),
|
|
607
|
+
div({ class: css('_mt2') },
|
|
608
|
+
span({ class: css('_heading2') }, '$29'),
|
|
609
|
+
span({ class: css('_fgmutedfg') }, '/month')
|
|
610
|
+
)
|
|
611
|
+
),
|
|
612
|
+
Card.Body({},
|
|
613
|
+
div({ class: css('_flex _col _gap2') },
|
|
614
|
+
...['Unlimited projects', 'Priority support', 'Advanced analytics', 'Custom domains'].map(f =>
|
|
615
|
+
div({ class: css('_flex _aic _gap2 _textsm') },
|
|
616
|
+
icon ? icon('check', { size: '1em', class: css('_fg[var(--d-success)]') }) : span({}, '✓'),
|
|
617
|
+
f
|
|
618
|
+
)
|
|
619
|
+
)
|
|
620
|
+
)
|
|
621
|
+
),
|
|
622
|
+
Card.Footer({},
|
|
623
|
+
Button ? Button({ variant: 'primary', class: css('_wfull') }, 'Get Started') : btn({}, 'Get Started')
|
|
624
|
+
)
|
|
625
|
+
)
|
|
626
|
+
));
|
|
627
|
+
|
|
628
|
+
return div({ class: css('_flex _col _gap8') },
|
|
629
|
+
showcaseSection('Examples',
|
|
630
|
+
div({ class: css('_flex _col _gap4') }, ...examples)
|
|
631
|
+
)
|
|
632
|
+
);
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
/**
|
|
636
|
+
* Custom showcase rendering for Timeline component.
|
|
637
|
+
*/
|
|
638
|
+
function renderTimelineExamples() {
|
|
639
|
+
const { Timeline, icon: iconFn } = Components;
|
|
640
|
+
if (!Timeline) return div({ class: css('_fgmutedfg _body') }, 'Timeline not found');
|
|
641
|
+
|
|
642
|
+
const example = (title, description, el) =>
|
|
643
|
+
div({ class: 'de-example-card' },
|
|
644
|
+
div({ class: 'de-example-demo' }, el),
|
|
645
|
+
div({ class: 'de-example-meta' },
|
|
646
|
+
span({ class: css('_label _fgfg') }, title),
|
|
647
|
+
description ? p({ class: css('_caption _fgmutedfg') }, description) : null
|
|
648
|
+
)
|
|
649
|
+
);
|
|
650
|
+
|
|
651
|
+
const examples = [];
|
|
652
|
+
|
|
653
|
+
examples.push(example('Basic', 'Simple left-aligned timeline',
|
|
654
|
+
Timeline({
|
|
655
|
+
items: [
|
|
656
|
+
{ content: 'Created project', time: 'Jan 1' },
|
|
657
|
+
{ content: 'First release', time: 'Mar 15' },
|
|
658
|
+
{ content: 'Reached 1k users', time: 'Jun 1' },
|
|
659
|
+
{ content: 'v2.0 launched', time: 'Sep 20' }
|
|
660
|
+
]
|
|
661
|
+
})
|
|
662
|
+
));
|
|
663
|
+
|
|
664
|
+
examples.push(example('Status Colors', 'Success, info, warning, error indicators',
|
|
665
|
+
Timeline({
|
|
666
|
+
items: [
|
|
667
|
+
{ content: 'Build succeeded', status: 'success', time: '10:00 AM' },
|
|
668
|
+
{ content: 'Tests passed', status: 'info', time: '10:05 AM' },
|
|
669
|
+
{ content: 'Deployment warning', status: 'warning', time: '10:10 AM' },
|
|
670
|
+
{ content: 'Rollback triggered', status: 'error', time: '10:15 AM' }
|
|
671
|
+
]
|
|
672
|
+
})
|
|
673
|
+
));
|
|
674
|
+
|
|
675
|
+
examples.push(example('Alternate with Labels', 'Labels on opposite sides',
|
|
676
|
+
Timeline({
|
|
677
|
+
mode: 'alternate',
|
|
678
|
+
items: [
|
|
679
|
+
{ content: 'Research phase', label: 'Q1 2024' },
|
|
680
|
+
{ content: 'Design phase', label: 'Q2 2024' },
|
|
681
|
+
{ content: 'Development', label: 'Q3 2024' },
|
|
682
|
+
{ content: 'Launch', label: 'Q4 2024' }
|
|
683
|
+
]
|
|
684
|
+
})
|
|
685
|
+
));
|
|
686
|
+
|
|
687
|
+
examples.push(example('Right-aligned', 'Content on the right side',
|
|
688
|
+
Timeline({
|
|
689
|
+
mode: 'right',
|
|
690
|
+
items: [
|
|
691
|
+
{ content: 'Step one', time: '9:00 AM' },
|
|
692
|
+
{ content: 'Step two', time: '10:00 AM' },
|
|
693
|
+
{ content: 'Step three', time: '11:00 AM' }
|
|
694
|
+
]
|
|
695
|
+
})
|
|
696
|
+
));
|
|
697
|
+
|
|
698
|
+
let iconItems;
|
|
699
|
+
try {
|
|
700
|
+
iconItems = [
|
|
701
|
+
{ content: 'Task completed', icon: iconFn('check', { size: '14' }) },
|
|
702
|
+
{ content: 'In progress', icon: iconFn('clock', { size: '14' }) },
|
|
703
|
+
{ content: 'Issue found', icon: iconFn('alert-triangle', { size: '14' }) }
|
|
704
|
+
];
|
|
705
|
+
} catch {
|
|
706
|
+
iconItems = [
|
|
707
|
+
{ content: 'Task completed', icon: '\u2713' },
|
|
708
|
+
{ content: 'In progress', icon: '\u25F7' },
|
|
709
|
+
{ content: 'Issue found', icon: '\u26A0' }
|
|
710
|
+
];
|
|
711
|
+
}
|
|
712
|
+
examples.push(example('With Icons', 'Custom icons as dots', Timeline({ items: iconItems })));
|
|
713
|
+
|
|
714
|
+
examples.push(example('Branded', 'Gradient connector + glass dots',
|
|
715
|
+
Timeline({
|
|
716
|
+
variant: 'branded',
|
|
717
|
+
items: [
|
|
718
|
+
{ content: 'Premium feature A' },
|
|
719
|
+
{ content: 'Premium feature B' },
|
|
720
|
+
{ content: 'Premium feature C' }
|
|
721
|
+
]
|
|
722
|
+
})
|
|
723
|
+
));
|
|
724
|
+
|
|
725
|
+
examples.push(example('Pending State', 'Pulsing pending dot at the tail',
|
|
726
|
+
Timeline({
|
|
727
|
+
pending: 'Processing...',
|
|
728
|
+
items: [
|
|
729
|
+
{ content: 'Order placed', status: 'success' },
|
|
730
|
+
{ content: 'Payment confirmed', status: 'success' },
|
|
731
|
+
{ content: 'Shipping', status: 'info' }
|
|
732
|
+
]
|
|
733
|
+
})
|
|
734
|
+
));
|
|
735
|
+
|
|
736
|
+
examples.push(example('Horizontal', 'Left-to-right process flow',
|
|
737
|
+
Timeline({
|
|
738
|
+
direction: 'horizontal',
|
|
739
|
+
items: [
|
|
740
|
+
{ content: 'Step 1' },
|
|
741
|
+
{ content: 'Step 2' },
|
|
742
|
+
{ content: 'Step 3' },
|
|
743
|
+
{ content: 'Step 4' }
|
|
744
|
+
]
|
|
745
|
+
})
|
|
746
|
+
));
|
|
747
|
+
|
|
748
|
+
examples.push(example('Collapsible Items', 'Expandable long content',
|
|
749
|
+
Timeline({
|
|
750
|
+
items: [
|
|
751
|
+
{ content: 'This is a long detailed description that can be collapsed.', collapsible: true, defaultOpen: false, time: 'Event 1' },
|
|
752
|
+
{ content: 'Another detailed item with lots of text.', collapsible: true, defaultOpen: true, time: 'Event 2' },
|
|
753
|
+
{ content: 'Short item', time: 'Event 3' }
|
|
754
|
+
]
|
|
755
|
+
})
|
|
756
|
+
));
|
|
757
|
+
|
|
758
|
+
examples.push(example('Clickable + Disabled', 'Interactive items with disabled state',
|
|
759
|
+
Timeline({
|
|
760
|
+
items: [
|
|
761
|
+
{ content: 'Click me', onclick: () => {} },
|
|
762
|
+
{ content: 'Disabled item', disabled: true },
|
|
763
|
+
{ content: 'Also clickable', onclick: () => {} }
|
|
764
|
+
]
|
|
765
|
+
})
|
|
766
|
+
));
|
|
767
|
+
|
|
768
|
+
examples.push(example('Small Size', 'Compact variant',
|
|
769
|
+
Timeline({ size: 'sm', items: [{ content: 'A', time: '1:00' }, { content: 'B', time: '2:00' }, { content: 'C', time: '3:00' }] })
|
|
770
|
+
));
|
|
771
|
+
|
|
772
|
+
examples.push(example('Large Size', 'Spacious variant',
|
|
773
|
+
Timeline({ size: 'lg', items: [{ content: 'A', time: 'AM' }, { content: 'B', time: 'PM' }, { content: 'C', time: 'EVE' }] })
|
|
774
|
+
));
|
|
775
|
+
|
|
776
|
+
examples.push(example('Custom Mode', 'Per-item left/right positioning',
|
|
777
|
+
Timeline({
|
|
778
|
+
mode: 'custom',
|
|
779
|
+
items: [
|
|
780
|
+
{ content: 'Left side', position: 'left' },
|
|
781
|
+
{ content: 'Right side', position: 'right' },
|
|
782
|
+
{ content: 'Left again', position: 'left' },
|
|
783
|
+
{ content: 'Right again', position: 'right' }
|
|
784
|
+
]
|
|
785
|
+
})
|
|
786
|
+
));
|
|
787
|
+
|
|
788
|
+
examples.push(example('Reverse Order', 'Items rendered bottom-to-top',
|
|
789
|
+
Timeline({
|
|
790
|
+
reverse: true,
|
|
791
|
+
items: [
|
|
792
|
+
{ content: 'First (now last)', time: 'Jan' },
|
|
793
|
+
{ content: 'Second', time: 'Feb' },
|
|
794
|
+
{ content: 'Third (now first)', time: 'Mar' }
|
|
795
|
+
]
|
|
796
|
+
})
|
|
797
|
+
));
|
|
798
|
+
|
|
799
|
+
return div({ class: css('_flex _col _gap8') },
|
|
800
|
+
showcaseSection('Examples',
|
|
801
|
+
div({ class: css('_flex _col _gap4') }, ...examples)
|
|
802
|
+
)
|
|
803
|
+
);
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
/**
|
|
807
|
+
* Custom showcase rendering for Comment component.
|
|
808
|
+
*/
|
|
809
|
+
function renderCommentExamples() {
|
|
810
|
+
const { Comment, Button: BtnComp } = Components;
|
|
811
|
+
if (!Comment) return div({ class: css('_fgmutedfg _body') }, 'Comment not found');
|
|
812
|
+
|
|
813
|
+
const example = (title, description, el) =>
|
|
814
|
+
div({ class: 'de-example-card' },
|
|
815
|
+
div({ class: 'de-example-demo' }, el),
|
|
816
|
+
div({ class: 'de-example-meta' },
|
|
817
|
+
span({ class: css('_label _fgfg') }, title),
|
|
818
|
+
description ? p({ class: css('_caption _fgmutedfg') }, description) : null
|
|
819
|
+
)
|
|
820
|
+
);
|
|
821
|
+
|
|
822
|
+
const examples = [];
|
|
823
|
+
|
|
824
|
+
examples.push(example('Basic', 'Single comment with avatar, author, content, datetime',
|
|
825
|
+
Comment({ author: 'Jane Doe', avatar: 'JD', content: 'This is a great feature! Really love the design.', datetime: '2 hours ago' })
|
|
826
|
+
));
|
|
827
|
+
|
|
828
|
+
const [likeCount, setLikeCount] = createSignal(3);
|
|
829
|
+
const [liked, setLiked] = createSignal(false);
|
|
830
|
+
examples.push(example('With Actions', 'Like, dislike, and reply action buttons',
|
|
831
|
+
Comment({
|
|
832
|
+
author: 'Alice Chen', avatar: 'AC',
|
|
833
|
+
content: 'The new dashboard looks amazing.',
|
|
834
|
+
datetime: '5 min ago',
|
|
835
|
+
actions: [
|
|
836
|
+
{ label: 'Like', icon: 'thumbs-up', count: likeCount, active: liked, onclick: () => { setLiked(!liked()); setLikeCount(liked() ? likeCount() + 1 : likeCount() - 1); } },
|
|
837
|
+
{ label: 'Dislike', icon: 'thumbs-down', count: 0 }
|
|
838
|
+
]
|
|
839
|
+
})
|
|
840
|
+
));
|
|
841
|
+
|
|
842
|
+
examples.push(example('Nested Thread', '3-level deep reply chain',
|
|
843
|
+
Comment(
|
|
844
|
+
{ author: 'Bob Smith', avatar: 'BS', content: 'What do you think about the new API?', datetime: '1 day ago' },
|
|
845
|
+
Comment(
|
|
846
|
+
{ author: 'Carol Lee', avatar: 'CL', content: 'I think it looks solid. The pagination is well designed.', datetime: '20 hours ago' },
|
|
847
|
+
Comment({ author: 'Bob Smith', avatar: 'BS', content: 'Agreed, much better than the previous version.', datetime: '18 hours ago' })
|
|
848
|
+
)
|
|
849
|
+
)
|
|
850
|
+
));
|
|
851
|
+
|
|
852
|
+
examples.push(example('With Reply Editor', 'Inline textarea with submit/cancel',
|
|
853
|
+
Comment({ author: 'David Kim', avatar: 'DK', content: 'Has anyone tested this with the new data layer?', datetime: '3 hours ago', onReply: () => {} })
|
|
854
|
+
));
|
|
855
|
+
|
|
856
|
+
examples.push(example('Bordered Variant', 'Card-style container',
|
|
857
|
+
Comment({ author: 'Eve Wilson', avatar: 'EW', content: 'Card-style container with border, surface background, and panel radius.', datetime: '1 hour ago', variant: 'bordered' })
|
|
858
|
+
));
|
|
859
|
+
|
|
860
|
+
examples.push(example('Minimal Variant', 'Tighter spacing, no borders',
|
|
861
|
+
Comment({ author: 'Frank Zhang', avatar: 'FZ', content: 'Compact layout for dense comment feeds.', datetime: '30 min ago', variant: 'minimal' })
|
|
862
|
+
));
|
|
863
|
+
|
|
864
|
+
const [count2, setCount2] = createSignal(42);
|
|
865
|
+
examples.push(example('Reactive Counts', 'Live updating counter \u2014 click to increment',
|
|
866
|
+
Comment({
|
|
867
|
+
author: 'Grace Park', avatar: 'GP',
|
|
868
|
+
content: 'Click the like button to see the reactive counter update.',
|
|
869
|
+
datetime: 'Just now',
|
|
870
|
+
actions: [{ label: 'Like', icon: 'thumbs-up', count: count2, onclick: () => setCount2(count2() + 1) }]
|
|
871
|
+
})
|
|
872
|
+
));
|
|
873
|
+
|
|
874
|
+
examples.push(example('Multiple Comments', 'List of comments with nested replies',
|
|
875
|
+
div({ class: css('_flex _col _gap4') },
|
|
876
|
+
Comment(
|
|
877
|
+
{ author: 'User A', avatar: 'UA', content: 'First top-level comment.', datetime: '5 hours ago' },
|
|
878
|
+
Comment({ author: 'User B', avatar: 'UB', content: 'Reply to first comment.', datetime: '4 hours ago' })
|
|
879
|
+
),
|
|
880
|
+
Comment({ author: 'User C', avatar: 'UC', content: 'Second top-level comment.', datetime: '3 hours ago' }),
|
|
881
|
+
Comment({ author: 'User D', avatar: 'UD', content: 'Third top-level comment.', datetime: '1 hour ago' })
|
|
882
|
+
)
|
|
883
|
+
));
|
|
884
|
+
|
|
885
|
+
return div({ class: css('_flex _col _gap8') },
|
|
886
|
+
showcaseSection('Examples',
|
|
887
|
+
div({ class: css('_flex _col _gap4') }, ...examples)
|
|
888
|
+
)
|
|
889
|
+
);
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
/**
|
|
893
|
+
* Custom showcase rendering for QRCode component.
|
|
894
|
+
*/
|
|
895
|
+
function renderQRCodeExamples() {
|
|
896
|
+
const { QRCode } = Components;
|
|
897
|
+
if (!QRCode) return div({ class: css('_fgmutedfg _body') }, 'QRCode not found');
|
|
898
|
+
|
|
899
|
+
const example = (title, description, el) =>
|
|
900
|
+
div({ class: 'de-example-card' },
|
|
901
|
+
div({ class: 'de-example-demo' }, el),
|
|
902
|
+
div({ class: 'de-example-meta' },
|
|
903
|
+
span({ class: css('_label _fgfg') }, title),
|
|
904
|
+
description ? p({ class: css('_caption _fgmutedfg') }, description) : null
|
|
905
|
+
)
|
|
906
|
+
);
|
|
907
|
+
|
|
908
|
+
const examples = [];
|
|
909
|
+
|
|
910
|
+
examples.push(example('Basic', 'Encode a URL',
|
|
911
|
+
QRCode({ value: 'https://decantr.ai' })
|
|
912
|
+
));
|
|
913
|
+
|
|
914
|
+
examples.push(example('Custom Sizes', '80px vs 160px vs 240px',
|
|
915
|
+
div({ class: css('_flex _aic _gap4') },
|
|
916
|
+
QRCode({ value: 'https://decantr.ai', size: 80 }),
|
|
917
|
+
QRCode({ value: 'https://decantr.ai', size: 160 }),
|
|
918
|
+
QRCode({ value: 'https://decantr.ai', size: 240 })
|
|
919
|
+
)
|
|
920
|
+
));
|
|
921
|
+
|
|
922
|
+
examples.push(example('SVG Mode', 'Scalable vector output',
|
|
923
|
+
QRCode({ value: 'https://decantr.ai', type: 'svg' })
|
|
924
|
+
));
|
|
925
|
+
|
|
926
|
+
examples.push(example('Loading Status', 'Spinner overlay',
|
|
927
|
+
QRCode({ value: 'https://decantr.ai', status: 'loading' })
|
|
928
|
+
));
|
|
929
|
+
|
|
930
|
+
examples.push(example('Expired Status', 'Blur + refresh button',
|
|
931
|
+
QRCode({ value: 'https://decantr.ai', status: 'expired', onRefresh: () => {} })
|
|
932
|
+
));
|
|
933
|
+
|
|
934
|
+
examples.push(example('Scanned Status', 'Checkmark overlay',
|
|
935
|
+
QRCode({ value: 'https://decantr.ai', status: 'scanned' })
|
|
936
|
+
));
|
|
937
|
+
|
|
938
|
+
examples.push(example('Error Correction Levels', 'L / M / Q / H',
|
|
939
|
+
div({ class: css('_flex _aic _gap4 _wrap') },
|
|
940
|
+
...['L', 'M', 'Q', 'H'].map(lv =>
|
|
941
|
+
div({ class: css('_flex _col _aic _gap1') },
|
|
942
|
+
QRCode({ value: 'https://decantr.ai', level: lv, size: 120 }),
|
|
943
|
+
span({ class: css('_caption _fgmutedfg') }, 'Level ' + lv)
|
|
944
|
+
)
|
|
945
|
+
)
|
|
946
|
+
)
|
|
947
|
+
));
|
|
948
|
+
|
|
949
|
+
examples.push(example('No Border', 'Borderless QR code',
|
|
950
|
+
QRCode({ value: 'https://decantr.ai', bordered: false })
|
|
951
|
+
));
|
|
952
|
+
|
|
953
|
+
return div({ class: css('_flex _col _gap8') },
|
|
954
|
+
showcaseSection('Examples',
|
|
955
|
+
div({ class: css('_flex _col _gap4') }, ...examples)
|
|
956
|
+
)
|
|
957
|
+
);
|
|
958
|
+
}
|