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.
Files changed (382) hide show
  1. package/AGENTS.md +868 -0
  2. package/CHANGELOG.md +255 -0
  3. package/CLAUDE.md +178 -0
  4. package/LICENSE +21 -0
  5. package/README.md +229 -0
  6. package/cli/art.js +127 -0
  7. package/cli/commands/a11y.js +61 -0
  8. package/cli/commands/audit.js +225 -0
  9. package/cli/commands/build.js +38 -0
  10. package/cli/commands/dev.js +18 -0
  11. package/cli/commands/doctor.js +197 -0
  12. package/cli/commands/figma-sync.js +48 -0
  13. package/cli/commands/figma-tokens.js +55 -0
  14. package/cli/commands/generate.js +26 -0
  15. package/cli/commands/init.js +116 -0
  16. package/cli/commands/lint.js +209 -0
  17. package/cli/commands/mcp.js +530 -0
  18. package/cli/commands/migrate.js +175 -0
  19. package/cli/commands/test.js +38 -0
  20. package/cli/commands/validate.js +354 -0
  21. package/cli/index.js +113 -0
  22. package/package.json +95 -0
  23. package/reference/atoms.md +517 -0
  24. package/reference/behaviors.md +384 -0
  25. package/reference/build-tooling.md +275 -0
  26. package/reference/color-guidelines.md +965 -0
  27. package/reference/component-lifecycle.md +137 -0
  28. package/reference/compound-spacing.md +95 -0
  29. package/reference/decantation-process.md +499 -0
  30. package/reference/dev-server-routes.md +93 -0
  31. package/reference/form-system.md +253 -0
  32. package/reference/i18n.md +336 -0
  33. package/reference/icons.md +576 -0
  34. package/reference/llm-primer.md +953 -0
  35. package/reference/plugins.md +252 -0
  36. package/reference/registry-consumption.md +76 -0
  37. package/reference/router.md +217 -0
  38. package/reference/shells.md +116 -0
  39. package/reference/spatial-guidelines.md +541 -0
  40. package/reference/ssr.md +234 -0
  41. package/reference/state-data.md +215 -0
  42. package/reference/state-patterns.md +166 -0
  43. package/reference/state.md +194 -0
  44. package/reference/style-system.md +110 -0
  45. package/reference/tokens.md +460 -0
  46. package/src/app.js +19 -0
  47. package/src/chart/_animate.js +266 -0
  48. package/src/chart/_base.js +109 -0
  49. package/src/chart/_data.js +209 -0
  50. package/src/chart/_format.js +106 -0
  51. package/src/chart/_interact.js +364 -0
  52. package/src/chart/_palette.js +105 -0
  53. package/src/chart/_renderer.js +52 -0
  54. package/src/chart/_scene.js +262 -0
  55. package/src/chart/_shared.js +371 -0
  56. package/src/chart/index.js +637 -0
  57. package/src/chart/layouts/_layout-base.js +328 -0
  58. package/src/chart/layouts/cartesian.js +148 -0
  59. package/src/chart/layouts/hierarchy.js +562 -0
  60. package/src/chart/layouts/polar.js +101 -0
  61. package/src/chart/renderers/canvas.js +179 -0
  62. package/src/chart/renderers/svg.js +256 -0
  63. package/src/chart/renderers/webgpu.js +715 -0
  64. package/src/chart/types/_type-base.js +26 -0
  65. package/src/chart/types/area.js +134 -0
  66. package/src/chart/types/bar.js +173 -0
  67. package/src/chart/types/box-plot.js +125 -0
  68. package/src/chart/types/bubble.js +63 -0
  69. package/src/chart/types/candlestick.js +115 -0
  70. package/src/chart/types/chord.js +85 -0
  71. package/src/chart/types/combination.js +108 -0
  72. package/src/chart/types/funnel.js +68 -0
  73. package/src/chart/types/gauge.js +163 -0
  74. package/src/chart/types/heatmap.js +98 -0
  75. package/src/chart/types/histogram.js +71 -0
  76. package/src/chart/types/line.js +111 -0
  77. package/src/chart/types/org-chart.js +93 -0
  78. package/src/chart/types/pie.js +81 -0
  79. package/src/chart/types/radar.js +96 -0
  80. package/src/chart/types/radial.js +68 -0
  81. package/src/chart/types/range-area.js +55 -0
  82. package/src/chart/types/range-bar.js +61 -0
  83. package/src/chart/types/sankey.js +73 -0
  84. package/src/chart/types/scatter.js +66 -0
  85. package/src/chart/types/sparkline.js +81 -0
  86. package/src/chart/types/sunburst.js +69 -0
  87. package/src/chart/types/swimlane.js +88 -0
  88. package/src/chart/types/treemap.js +62 -0
  89. package/src/chart/types/waterfall.js +100 -0
  90. package/src/components/_base.js +1658 -0
  91. package/src/components/_behaviors.js +1140 -0
  92. package/src/components/_primitives.js +534 -0
  93. package/src/components/_qr-encoder.js +539 -0
  94. package/src/components/accordion.js +207 -0
  95. package/src/components/affix.js +62 -0
  96. package/src/components/alert-dialog.js +75 -0
  97. package/src/components/alert.js +47 -0
  98. package/src/components/aspect-ratio.js +24 -0
  99. package/src/components/avatar-group.js +55 -0
  100. package/src/components/avatar.js +38 -0
  101. package/src/components/back-top.js +75 -0
  102. package/src/components/badge.js +74 -0
  103. package/src/components/banner.js +68 -0
  104. package/src/components/breadcrumb.js +162 -0
  105. package/src/components/button.js +115 -0
  106. package/src/components/calendar.js +131 -0
  107. package/src/components/card.js +192 -0
  108. package/src/components/carousel.js +98 -0
  109. package/src/components/cascader.js +261 -0
  110. package/src/components/checkbox.js +80 -0
  111. package/src/components/chip.js +81 -0
  112. package/src/components/code-block.js +82 -0
  113. package/src/components/collapsible.js +50 -0
  114. package/src/components/color-palette.js +438 -0
  115. package/src/components/color-picker.js +314 -0
  116. package/src/components/combobox.js +181 -0
  117. package/src/components/command.js +174 -0
  118. package/src/components/comment.js +206 -0
  119. package/src/components/context-menu.js +76 -0
  120. package/src/components/data-table.js +724 -0
  121. package/src/components/date-picker.js +217 -0
  122. package/src/components/date-range-picker.js +244 -0
  123. package/src/components/datetime-picker.js +271 -0
  124. package/src/components/descriptions.js +68 -0
  125. package/src/components/drawer.js +179 -0
  126. package/src/components/dropdown.js +88 -0
  127. package/src/components/empty.js +41 -0
  128. package/src/components/float-button.js +90 -0
  129. package/src/components/form.js +106 -0
  130. package/src/components/hover-card.js +49 -0
  131. package/src/components/icon.js +87 -0
  132. package/src/components/image.js +97 -0
  133. package/src/components/index.js +117 -0
  134. package/src/components/input-group.js +75 -0
  135. package/src/components/input-number.js +155 -0
  136. package/src/components/input-otp.js +178 -0
  137. package/src/components/input.js +91 -0
  138. package/src/components/kbd.js +36 -0
  139. package/src/components/label.js +25 -0
  140. package/src/components/list.js +118 -0
  141. package/src/components/masked-input.js +236 -0
  142. package/src/components/mentions.js +165 -0
  143. package/src/components/menu.js +259 -0
  144. package/src/components/message.js +80 -0
  145. package/src/components/modal.js +147 -0
  146. package/src/components/navigation-menu.js +166 -0
  147. package/src/components/notification.js +84 -0
  148. package/src/components/pagination.js +104 -0
  149. package/src/components/placeholder.js +132 -0
  150. package/src/components/popconfirm.js +70 -0
  151. package/src/components/popover.js +58 -0
  152. package/src/components/progress.js +61 -0
  153. package/src/components/qrcode.js +251 -0
  154. package/src/components/radiogroup.js +120 -0
  155. package/src/components/range-slider.js +176 -0
  156. package/src/components/rate.js +186 -0
  157. package/src/components/resizable.js +83 -0
  158. package/src/components/result.js +57 -0
  159. package/src/components/scroll-area.js +43 -0
  160. package/src/components/segmented.js +97 -0
  161. package/src/components/select.js +165 -0
  162. package/src/components/separator.js +31 -0
  163. package/src/components/shell.js +407 -0
  164. package/src/components/skeleton.js +39 -0
  165. package/src/components/slider.js +141 -0
  166. package/src/components/sortable-list.js +176 -0
  167. package/src/components/space.js +42 -0
  168. package/src/components/spinner.js +112 -0
  169. package/src/components/splitter.js +147 -0
  170. package/src/components/statistic.js +136 -0
  171. package/src/components/steps.js +99 -0
  172. package/src/components/switch.js +95 -0
  173. package/src/components/table.js +44 -0
  174. package/src/components/tabs.js +216 -0
  175. package/src/components/tag.js +115 -0
  176. package/src/components/textarea.js +82 -0
  177. package/src/components/time-picker.js +153 -0
  178. package/src/components/time-range-picker.js +170 -0
  179. package/src/components/timeline.js +226 -0
  180. package/src/components/toast.js +71 -0
  181. package/src/components/toggle.js +213 -0
  182. package/src/components/tooltip.js +57 -0
  183. package/src/components/tour.js +159 -0
  184. package/src/components/transfer.js +163 -0
  185. package/src/components/tree-select.js +274 -0
  186. package/src/components/tree.js +141 -0
  187. package/src/components/typography.js +136 -0
  188. package/src/components/upload.js +118 -0
  189. package/src/components/visually-hidden.js +20 -0
  190. package/src/components/watermark.js +124 -0
  191. package/src/core/index.js +539 -0
  192. package/src/core/lifecycle.js +69 -0
  193. package/src/css/atoms.js +651 -0
  194. package/src/css/components.js +940 -0
  195. package/src/css/derive.js +1296 -0
  196. package/src/css/index.js +265 -0
  197. package/src/css/runtime.js +268 -0
  198. package/src/css/styles/addons/bioluminescent.js +93 -0
  199. package/src/css/styles/addons/clay.js +70 -0
  200. package/src/css/styles/addons/clean.js +57 -0
  201. package/src/css/styles/addons/command-center.js +143 -0
  202. package/src/css/styles/addons/dopamine.js +83 -0
  203. package/src/css/styles/addons/editorial.js +80 -0
  204. package/src/css/styles/addons/glassmorphism.js +99 -0
  205. package/src/css/styles/addons/liquid-glass.js +105 -0
  206. package/src/css/styles/addons/prismatic.js +100 -0
  207. package/src/css/styles/addons/retro.js +63 -0
  208. package/src/css/styles/auradecantism.js +96 -0
  209. package/src/css/theme-registry.js +444 -0
  210. package/src/data/entity.js +281 -0
  211. package/src/data/index.js +13 -0
  212. package/src/data/persist.js +225 -0
  213. package/src/data/query.js +839 -0
  214. package/src/data/realtime.js +299 -0
  215. package/src/data/url.js +177 -0
  216. package/src/data/worker.js +134 -0
  217. package/src/explorer/archetypes.js +243 -0
  218. package/src/explorer/atoms.js +228 -0
  219. package/src/explorer/charts.js +497 -0
  220. package/src/explorer/components.js +129 -0
  221. package/src/explorer/foundations.js +949 -0
  222. package/src/explorer/icons.js +178 -0
  223. package/src/explorer/patterns.js +247 -0
  224. package/src/explorer/recipes.js +194 -0
  225. package/src/explorer/shared/pattern-examples.js +1337 -0
  226. package/src/explorer/shared/showcase-renderer.js +958 -0
  227. package/src/explorer/shared/spec-table.js +41 -0
  228. package/src/explorer/shared/usage-links.js +87 -0
  229. package/src/explorer/shell-config.js +10 -0
  230. package/src/explorer/shells.js +551 -0
  231. package/src/explorer/styles.js +161 -0
  232. package/src/explorer/tokens.js +262 -0
  233. package/src/explorer/tools.js +525 -0
  234. package/src/form/index.js +804 -0
  235. package/src/i18n/index.js +251 -0
  236. package/src/icons/essential.js +479 -0
  237. package/src/icons/index.js +53 -0
  238. package/src/plugins/index.js +282 -0
  239. package/src/registry/archetypes/content-site.json +71 -0
  240. package/src/registry/archetypes/docs-explorer.json +23 -0
  241. package/src/registry/archetypes/ecommerce.json +104 -0
  242. package/src/registry/archetypes/financial-dashboard.json +77 -0
  243. package/src/registry/archetypes/index.json +41 -0
  244. package/src/registry/archetypes/portfolio.json +82 -0
  245. package/src/registry/archetypes/recipe-community.json +159 -0
  246. package/src/registry/archetypes/saas-dashboard.json +86 -0
  247. package/src/registry/architect/cross-cutting.json +45 -0
  248. package/src/registry/architect/domains/ecommerce.json +294 -0
  249. package/src/registry/architect/domains/financial-services.json +302 -0
  250. package/src/registry/architect/index.json +26 -0
  251. package/src/registry/architect/traits.json +379 -0
  252. package/src/registry/atoms.json +16 -0
  253. package/src/registry/chart-showcase.json +160 -0
  254. package/src/registry/chart.json +136 -0
  255. package/src/registry/components.json +8616 -0
  256. package/src/registry/core.json +216 -0
  257. package/src/registry/css.json +319 -0
  258. package/src/registry/data.json +135 -0
  259. package/src/registry/foundations.json +11 -0
  260. package/src/registry/icons.json +463 -0
  261. package/src/registry/index.json +101 -0
  262. package/src/registry/patterns/activity-feed.json +37 -0
  263. package/src/registry/patterns/article-content.json +27 -0
  264. package/src/registry/patterns/auth-form.json +37 -0
  265. package/src/registry/patterns/author-card.json +20 -0
  266. package/src/registry/patterns/card-grid.json +127 -0
  267. package/src/registry/patterns/category-nav.json +26 -0
  268. package/src/registry/patterns/chart-grid.json +36 -0
  269. package/src/registry/patterns/chat-interface.json +37 -0
  270. package/src/registry/patterns/checklist-card.json +55 -0
  271. package/src/registry/patterns/comparison-panel.json +27 -0
  272. package/src/registry/patterns/component-showcase.json +24 -0
  273. package/src/registry/patterns/contact-form.json +31 -0
  274. package/src/registry/patterns/cta-section.json +20 -0
  275. package/src/registry/patterns/data-table.json +37 -0
  276. package/src/registry/patterns/detail-header.json +83 -0
  277. package/src/registry/patterns/detail-panel.json +27 -0
  278. package/src/registry/patterns/explorer-shell.json +22 -0
  279. package/src/registry/patterns/filter-bar.json +33 -0
  280. package/src/registry/patterns/filter-sidebar.json +27 -0
  281. package/src/registry/patterns/form-sections.json +110 -0
  282. package/src/registry/patterns/goal-tracker.json +27 -0
  283. package/src/registry/patterns/hero.json +107 -0
  284. package/src/registry/patterns/index.json +47 -0
  285. package/src/registry/patterns/kpi-grid.json +36 -0
  286. package/src/registry/patterns/media-gallery.json +20 -0
  287. package/src/registry/patterns/order-history.json +20 -0
  288. package/src/registry/patterns/pagination.json +19 -0
  289. package/src/registry/patterns/photo-to-recipe.json +36 -0
  290. package/src/registry/patterns/pipeline-tracker.json +28 -0
  291. package/src/registry/patterns/post-list.json +27 -0
  292. package/src/registry/patterns/pricing-table.json +32 -0
  293. package/src/registry/patterns/scorecard.json +28 -0
  294. package/src/registry/patterns/search-bar.json +20 -0
  295. package/src/registry/patterns/specimen-grid.json +19 -0
  296. package/src/registry/patterns/stat-card.json +55 -0
  297. package/src/registry/patterns/stats-bar.json +55 -0
  298. package/src/registry/patterns/steps-card.json +55 -0
  299. package/src/registry/patterns/table-of-contents.json +19 -0
  300. package/src/registry/patterns/testimonials.json +21 -0
  301. package/src/registry/patterns/timeline.json +27 -0
  302. package/src/registry/patterns/token-inspector.json +21 -0
  303. package/src/registry/patterns/wizard.json +27 -0
  304. package/src/registry/recipe-auradecantism.json +69 -0
  305. package/src/registry/recipe-clean.json +65 -0
  306. package/src/registry/recipe-command-center.json +78 -0
  307. package/src/registry/router.json +73 -0
  308. package/src/registry/schema/README.md +197 -0
  309. package/src/registry/skeletons.json +259 -0
  310. package/src/registry/state.json +137 -0
  311. package/src/registry/tokens.json +40 -0
  312. package/src/router/hash.js +17 -0
  313. package/src/router/history.js +18 -0
  314. package/src/router/index.js +598 -0
  315. package/src/ssr/index.js +922 -0
  316. package/src/state/arrays.js +181 -0
  317. package/src/state/devtools.js +647 -0
  318. package/src/state/index.js +498 -0
  319. package/src/state/middleware.js +288 -0
  320. package/src/state/scheduler.js +206 -0
  321. package/src/state/store.js +300 -0
  322. package/src/tags/index.js +19 -0
  323. package/src/tannins/auth.js +396 -0
  324. package/src/test/dom.js +352 -0
  325. package/src/test/index.js +62 -0
  326. package/src/test/state.js +306 -0
  327. package/tools/a11y-audit.js +487 -0
  328. package/tools/analyzer.js +315 -0
  329. package/tools/audit.js +706 -0
  330. package/tools/builder.js +1422 -0
  331. package/tools/css-extract.js +188 -0
  332. package/tools/dev-server.js +316 -0
  333. package/tools/dts-gen.js +1260 -0
  334. package/tools/figma-components.js +329 -0
  335. package/tools/figma-patterns.js +516 -0
  336. package/tools/figma-plugin/code.js +453 -0
  337. package/tools/figma-plugin/manifest.json +14 -0
  338. package/tools/figma-plugin/ui.html +268 -0
  339. package/tools/figma-render.js +293 -0
  340. package/tools/figma-tokens.js +712 -0
  341. package/tools/figma-upload.js +318 -0
  342. package/tools/generate.js +738 -0
  343. package/tools/icons.js +133 -0
  344. package/tools/init-templates.js +265 -0
  345. package/tools/install-hooks.sh +5 -0
  346. package/tools/migrations/0.5.0.js +53 -0
  347. package/tools/migrations/0.6.0.js +95 -0
  348. package/tools/minify.js +170 -0
  349. package/tools/pre-commit +4 -0
  350. package/tools/registry.js +662 -0
  351. package/tools/reset-playground.js +61 -0
  352. package/tools/starter-templates/content-site/app.js +49 -0
  353. package/tools/starter-templates/content-site/essence.js +19 -0
  354. package/tools/starter-templates/content-site/pages.js +31 -0
  355. package/tools/starter-templates/ecommerce/app.js +50 -0
  356. package/tools/starter-templates/ecommerce/essence.js +19 -0
  357. package/tools/starter-templates/ecommerce/pages.js +31 -0
  358. package/tools/starter-templates/landing-page/app.js +38 -0
  359. package/tools/starter-templates/landing-page/essence.js +18 -0
  360. package/tools/starter-templates/landing-page/pages.js +21 -0
  361. package/tools/starter-templates/portfolio/app.js +45 -0
  362. package/tools/starter-templates/portfolio/essence.js +19 -0
  363. package/tools/starter-templates/portfolio/pages.js +33 -0
  364. package/tools/starter-templates/saas-dashboard/app.js +70 -0
  365. package/tools/starter-templates/saas-dashboard/essence.js +19 -0
  366. package/tools/starter-templates/saas-dashboard/pages.js +31 -0
  367. package/tools/verify-pack.js +203 -0
  368. package/types/chart.d.ts +77 -0
  369. package/types/components.d.ts +587 -0
  370. package/types/core.d.ts +89 -0
  371. package/types/css.d.ts +149 -0
  372. package/types/data.d.ts +238 -0
  373. package/types/form.d.ts +164 -0
  374. package/types/i18n.d.ts +51 -0
  375. package/types/icons.d.ts +27 -0
  376. package/types/index.d.ts +13 -0
  377. package/types/router.d.ts +116 -0
  378. package/types/ssr.d.ts +102 -0
  379. package/types/state.d.ts +83 -0
  380. package/types/tags.d.ts +62 -0
  381. package/types/tannins.d.ts +63 -0
  382. package/types/test.d.ts +48 -0
@@ -0,0 +1,497 @@
1
+ import { css } from 'decantr/css';
2
+ import { tags } from 'decantr/tags';
3
+ import { onDestroy } from 'decantr/core';
4
+ import { Chart, Sparkline, colorScale, createStream } from 'decantr/chart';
5
+ import { Separator, Tabs } from 'decantr/components';
6
+ import { SpecTable } from './shared/spec-table.js';
7
+ import { injectExplorerCSS } from './styles.js';
8
+ injectExplorerCSS();
9
+
10
+ const { div, h2, h3, p, span, section } = tags;
11
+
12
+ // ─── Chart Groups (loaded from registry) ──────────────────────
13
+ let CHART_GROUPS = [];
14
+ const GROUP_MAP = {};
15
+
16
+ async function ensureChartGroups() {
17
+ if (CHART_GROUPS.length) return;
18
+ try {
19
+ const resp = await fetch('/__decantr/registry/chart-showcase.json');
20
+ const reg = await resp.json();
21
+ const groups = reg.groups || {};
22
+ CHART_GROUPS = Object.entries(groups).map(([id, g]) => ({
23
+ id, label: g.label, desc: g.description, types: g.types,
24
+ }));
25
+ } catch {
26
+ CHART_GROUPS = [];
27
+ }
28
+ for (const g of CHART_GROUPS) for (const t of g.types) GROUP_MAP[t] = g.id;
29
+ }
30
+
31
+ // ─── Sample Data ───────────────────────────────────────────────
32
+ const MONTHLY = [
33
+ { month: 'Jan', desktop: 186, mobile: 80 },
34
+ { month: 'Feb', desktop: 305, mobile: 200 },
35
+ { month: 'Mar', desktop: 237, mobile: 120 },
36
+ { month: 'Apr', desktop: 73, mobile: 190 },
37
+ { month: 'May', desktop: 209, mobile: 130 },
38
+ { month: 'Jun', desktop: 214, mobile: 140 },
39
+ ];
40
+
41
+ const MONTHLY_NEG = [
42
+ { month: 'Jan', value: 186 },
43
+ { month: 'Feb', value: -120 },
44
+ { month: 'Mar', value: 237 },
45
+ { month: 'Apr', value: -73 },
46
+ { month: 'May', value: 209 },
47
+ { month: 'Jun', value: -50 },
48
+ ];
49
+
50
+ const PIE_DATA = [
51
+ { label: 'Chrome', value: 275 },
52
+ { label: 'Safari', value: 200 },
53
+ { label: 'Firefox', value: 187 },
54
+ { label: 'Edge', value: 173 },
55
+ { label: 'Other', value: 90 },
56
+ ];
57
+
58
+ const RADAR_DATA = [
59
+ { axis: 'Speed', a: 80, b: 65 },
60
+ { axis: 'Reliability', a: 90, b: 70 },
61
+ { axis: 'Comfort', a: 60, b: 85 },
62
+ { axis: 'Safety', a: 75, b: 90 },
63
+ { axis: 'Efficiency', a: 85, b: 60 },
64
+ { axis: 'Design', a: 70, b: 75 },
65
+ ];
66
+
67
+ const RADIAL_DATA = [
68
+ { label: 'Progress', value: 72 },
69
+ { label: 'Goals', value: 85 },
70
+ { label: 'Tasks', value: 60 },
71
+ { label: 'Reviews', value: 45 },
72
+ { label: 'Bugs', value: 30 },
73
+ ];
74
+
75
+ const FUNNEL_DATA = [
76
+ { stage: 'Visitors', value: 1000 },
77
+ { stage: 'Leads', value: 750 },
78
+ { stage: 'Prospects', value: 500 },
79
+ { stage: 'Customers', value: 200 },
80
+ { stage: 'Retained', value: 150 },
81
+ ];
82
+
83
+ const SCATTER_DATA = Array.from({ length: 40 }, (_, i) => ({
84
+ x: ((i * 7 + 3) % 100),
85
+ y: ((i * 13 + 7) % 100),
86
+ size: (i % 5 + 1) * 6,
87
+ group: i % 2 === 0 ? 'A' : 'B',
88
+ }));
89
+
90
+ const HIST_DATA = Array.from({ length: 100 }, (_, i) => ({
91
+ value: Math.round(20 + Math.sin(i * 0.3) * 30 + (i % 7) * 5),
92
+ }));
93
+
94
+ const WATERFALL_DATA = [
95
+ { category: 'Revenue', value: 420 },
96
+ { category: 'Services', value: 120 },
97
+ { category: 'Costs', value: -180 },
98
+ { category: 'Tax', value: -60 },
99
+ { category: 'Total', value: 0, total: true },
100
+ ];
101
+
102
+ const HEATMAP_DATA = (() => {
103
+ const days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'];
104
+ const hours = ['9am', '10am', '11am', '12pm', '1pm', '2pm', '3pm', '4pm'];
105
+ const out = [];
106
+ for (let di = 0; di < days.length; di++)
107
+ for (let hi = 0; hi < hours.length; hi++)
108
+ out.push({ hour: hours[hi], day: days[di], value: Math.abs(Math.round(Math.sin((di + 1) * (hi + 1) * 0.7) * 80 + 20)) });
109
+ return out;
110
+ })();
111
+
112
+ const OHLC_DATA = Array.from({ length: 20 }, (_, i) => {
113
+ const base = 100 + Math.sin(i * 0.5) * 20;
114
+ const open = Math.round(base + (i % 3 - 1) * 3);
115
+ const close = Math.round(base + (i % 4 - 2) * 4);
116
+ return {
117
+ date: `Mar ${i + 1}`, open, close,
118
+ high: Math.round(Math.max(open, close) + (i % 5) * 2),
119
+ low: Math.round(Math.min(open, close) - (i % 4) * 2),
120
+ volume: 1000 + i * 100,
121
+ };
122
+ });
123
+
124
+ const BOXPLOT_DATA = [
125
+ ...Array.from({ length: 20 }, (_, i) => ({ group: 'A', value: 15 + (i * 7 + 3) % 30 })),
126
+ ...Array.from({ length: 20 }, (_, i) => ({ group: 'B', value: 10 + (i * 11 + 5) % 40 })),
127
+ ...Array.from({ length: 20 }, (_, i) => ({ group: 'C', value: 20 + (i * 5 + 2) % 25 })),
128
+ ];
129
+
130
+ const RANGE_DATA = [
131
+ { month: 'Jan', min: 2, max: 12 },
132
+ { month: 'Feb', min: 4, max: 14 },
133
+ { month: 'Mar', min: 6, max: 18 },
134
+ { month: 'Apr', min: 10, max: 22 },
135
+ { month: 'May', min: 14, max: 26 },
136
+ { month: 'Jun', min: 16, max: 28 },
137
+ ];
138
+
139
+ const TREE_DATA = [
140
+ { id: 'root', parent: null, value: 100, label: 'All' },
141
+ { id: 'a', parent: 'root', value: 60, label: 'Group A' },
142
+ { id: 'b', parent: 'root', value: 40, label: 'Group B' },
143
+ { id: 'a1', parent: 'a', value: 35, label: 'Item A1' },
144
+ { id: 'a2', parent: 'a', value: 25, label: 'Item A2' },
145
+ { id: 'b1', parent: 'b', value: 22, label: 'Item B1' },
146
+ { id: 'b2', parent: 'b', value: 18, label: 'Item B2' },
147
+ ];
148
+
149
+ const SANKEY_DATA = {
150
+ nodes: [
151
+ { id: 'Budget' }, { id: 'Engineering' }, { id: 'Marketing' }, { id: 'Sales' },
152
+ { id: 'Frontend' }, { id: 'Backend' }, { id: 'Social' }, { id: 'Content' },
153
+ ],
154
+ links: [
155
+ { source: 'Budget', target: 'Engineering', value: 40 },
156
+ { source: 'Budget', target: 'Marketing', value: 30 },
157
+ { source: 'Budget', target: 'Sales', value: 30 },
158
+ { source: 'Engineering', target: 'Frontend', value: 20 },
159
+ { source: 'Engineering', target: 'Backend', value: 20 },
160
+ { source: 'Marketing', target: 'Social', value: 15 },
161
+ { source: 'Marketing', target: 'Content', value: 15 },
162
+ ],
163
+ };
164
+
165
+ const CHORD_DATA = {
166
+ nodes: [{ id: 'A' }, { id: 'B' }, { id: 'C' }, { id: 'D' }],
167
+ links: [
168
+ { source: 'A', target: 'B', value: 5 },
169
+ { source: 'A', target: 'C', value: 6 },
170
+ { source: 'B', target: 'C', value: 3 },
171
+ { source: 'B', target: 'D', value: 4 },
172
+ { source: 'C', target: 'D', value: 7 },
173
+ { source: 'D', target: 'A', value: 2 },
174
+ ],
175
+ };
176
+
177
+ const ORG_DATA = [
178
+ { id: 'ceo', parent: null, label: 'CEO' },
179
+ { id: 'cto', parent: 'ceo', label: 'CTO' },
180
+ { id: 'cfo', parent: 'ceo', label: 'CFO' },
181
+ { id: 'cmo', parent: 'ceo', label: 'CMO' },
182
+ { id: 'eng1', parent: 'cto', label: 'Lead Eng' },
183
+ { id: 'eng2', parent: 'cto', label: 'Sr Eng' },
184
+ { id: 'fin1', parent: 'cfo', label: 'Controller' },
185
+ ];
186
+
187
+ const SWIMLANE_DATA = {
188
+ lanes: ['Backlog', 'In Progress', 'Review', 'Done'],
189
+ items: [
190
+ { id: '1', lane: 'Backlog', label: 'Design system' },
191
+ { id: '2', lane: 'Backlog', label: 'Auth flow' },
192
+ { id: '3', lane: 'In Progress', label: 'Dashboard' },
193
+ { id: '4', lane: 'In Progress', label: 'API layer' },
194
+ { id: '5', lane: 'Review', label: 'Charts' },
195
+ { id: '6', lane: 'Done', label: 'Router' },
196
+ { id: '7', lane: 'Done', label: 'State mgmt' },
197
+ ],
198
+ };
199
+
200
+ const SPARK_VALUES = [4, 7, 5, 9, 3, 8, 6, 10, 7, 5, 8, 12, 9, 6, 11, 8, 5, 7, 10, 13];
201
+
202
+ // ─── Type Metadata ─────────────────────────────────────────────
203
+ const TYPE_META = {
204
+ line: { label: 'Line', desc: 'Track trends over categories or time', base: { data: MONTHLY, x: 'month', y: 'desktop' } },
205
+ bar: { label: 'Bar', desc: 'Compare values across categories', base: { data: MONTHLY, x: 'month', y: 'desktop' } },
206
+ area: { label: 'Area', desc: 'Show volume and trends with filled regions', base: { data: MONTHLY, x: 'month', y: 'desktop' } },
207
+ combination: { label: 'Combination', desc: 'Mix chart types on shared axes', base: { data: MONTHLY, x: 'month' } },
208
+ histogram: { label: 'Histogram', desc: 'Visualize frequency distribution of values', base: { data: HIST_DATA, x: 'value' } },
209
+ waterfall: { label: 'Waterfall', desc: 'Show cumulative effect of sequential values', base: { data: WATERFALL_DATA, x: 'category', y: 'value' } },
210
+ pie: { label: 'Pie', desc: 'Show proportions of a whole', base: { data: PIE_DATA, x: 'label', y: 'value' } },
211
+ radar: { label: 'Radar', desc: 'Compare multi-dimensional data on radial axes', base: { data: RADAR_DATA, x: 'axis', y: 'a' } },
212
+ radial: { label: 'Radial', desc: 'Display values as radial bars', base: { data: RADIAL_DATA, x: 'label', y: 'value' } },
213
+ gauge: { label: 'Gauge', desc: 'Show a single metric against a range', base: { data: [{ value: 72 }], value: 72, min: 0, max: 100 } },
214
+ funnel: { label: 'Funnel', desc: 'Show progressive reduction through stages', base: { data: FUNNEL_DATA, x: 'stage', y: 'value' } },
215
+ scatter: { label: 'Scatter', desc: 'Reveal correlations between two variables', base: { data: SCATTER_DATA, x: 'x', y: 'y' } },
216
+ bubble: { label: 'Bubble', desc: 'Scatter plot with size dimension', base: { data: SCATTER_DATA, x: 'x', y: 'y' } },
217
+ 'box-plot': { label: 'Box Plot', desc: 'Show statistical distribution with quartiles', base: { data: BOXPLOT_DATA, x: 'group', y: 'value' } },
218
+ candlestick: { label: 'Candlestick', desc: 'Visualize OHLC financial data', base: { data: OHLC_DATA, x: 'date', y: 'close' } },
219
+ 'range-bar': { label: 'Range Bar', desc: 'Display value ranges as bars', base: { data: RANGE_DATA, x: 'month', y: ['min', 'max'] } },
220
+ 'range-area': { label: 'Range Area', desc: 'Show value ranges with filled bands', base: { data: RANGE_DATA, x: 'month', y: ['min', 'max'] } },
221
+ heatmap: { label: 'Heatmap', desc: 'Represent values in a color-coded matrix', base: { data: HEATMAP_DATA, x: 'hour', y: 'day', value: 'value' } },
222
+ treemap: { label: 'Treemap', desc: 'Show hierarchical data as nested rectangles', base: { data: TREE_DATA, id: 'id', parent: 'parent', x: 'label', y: 'value' } },
223
+ sunburst: { label: 'Sunburst', desc: 'Show hierarchical data as concentric rings', base: { data: TREE_DATA, id: 'id', parent: 'parent', x: 'label', y: 'value' } },
224
+ sankey: { label: 'Sankey', desc: 'Visualize flow between nodes', base: { data: SANKEY_DATA } },
225
+ chord: { label: 'Chord', desc: 'Show inter-relationships between groups', base: { data: CHORD_DATA } },
226
+ 'org-chart': { label: 'Org Chart', desc: 'Display organizational hierarchy', base: { data: ORG_DATA, id: 'id', parent: 'parent', label: 'label' } },
227
+ swimlane: { label: 'Swimlane', desc: 'Kanban-style lane layout', base: { data: SWIMLANE_DATA, lane: 'lane', label: 'label' } },
228
+ sparkline: { label: 'Sparkline', desc: 'Compact inline chart for trends', base: {} },
229
+ };
230
+
231
+ // ─── Card Defaults ─────────────────────────────────────────────
232
+ const CARD = { height: '250px', legend: false, title: null, tooltip: true, tableAlt: false };
233
+
234
+ // ─── Variations Per Type ───────────────────────────────────────
235
+ const VARIATIONS = {
236
+ line: [
237
+ { name: 'Default', desc: 'Smooth curve with dots and grid', props: {} },
238
+ { name: 'Linear', desc: 'Straight line interpolation', props: { smooth: false } },
239
+ { name: 'Multiple Series', desc: 'Compare desktop vs mobile', props: { y: ['desktop', 'mobile'], legend: true } },
240
+ { name: 'With Labels', desc: 'Data point values displayed on chart', props: { labels: true, dots: false } },
241
+ { name: 'No Dots', desc: 'Clean line without data point markers', props: { dots: false } },
242
+ { name: 'Interactive', desc: 'Crosshair guides on hover', props: { crosshair: true } },
243
+ {
244
+ name: 'Live Stream',
245
+ desc: 'Real-time data feed with createStream() rolling buffer',
246
+ render: () => {
247
+ const stream = createStream({ maxPoints: 30 });
248
+ let tick = 0;
249
+ for (let i = 0; i < 20; i++) {
250
+ stream.append({ time: `${i}s`, value: Math.round(40 + Math.sin(i * 0.5) * 25 + (i % 3) * 5) });
251
+ tick = i + 1;
252
+ }
253
+ const chart = Chart({
254
+ ...CARD, type: 'line', data: stream.data, x: 'time', y: 'value',
255
+ live: true, smooth: true, dots: false,
256
+ 'aria-label': 'Live streaming line chart',
257
+ });
258
+ const interval = setInterval(() => {
259
+ stream.append({ time: `${tick}s`, value: Math.round(40 + Math.sin(tick * 0.5) * 25 + Math.random() * 15) });
260
+ tick++;
261
+ }, 1000);
262
+ onDestroy(() => { clearInterval(interval); stream.destroy(); });
263
+ return chart;
264
+ },
265
+ },
266
+ ],
267
+ bar: [
268
+ { name: 'Default', desc: 'Vertical bars for category comparison', props: {} },
269
+ { name: 'Horizontal', desc: 'Bars oriented horizontally', props: { horizontal: true } },
270
+ { name: 'Stacked', desc: 'Series stacked to show total', props: { y: ['desktop', 'mobile'], stacked: true, legend: true } },
271
+ { name: 'Multiple Series', desc: 'Grouped bars side by side', props: { y: ['desktop', 'mobile'], legend: true } },
272
+ { name: 'With Labels', desc: 'Value labels on each bar', props: { labels: true } },
273
+ { name: 'Negative Values', desc: 'Bars extending in both directions', props: { data: MONTHLY_NEG, y: 'value' } },
274
+ ],
275
+ area: [
276
+ { name: 'Default', desc: 'Smooth filled area under curve', props: {} },
277
+ { name: 'Linear', desc: 'Straight-line area fill', props: { smooth: false } },
278
+ { name: 'Stacked', desc: 'Multiple series stacked', props: { y: ['desktop', 'mobile'], stacked: true, legend: true } },
279
+ { name: 'Multiple Series', desc: 'Overlapping transparent areas', props: { y: ['desktop', 'mobile'], legend: true } },
280
+ { name: 'With Labels', desc: 'Data labels on area peaks', props: { labels: true } },
281
+ ],
282
+ combination: [
283
+ { name: 'Bar + Line', desc: 'Bar for volume, line for trend', props: { layers: [{ type: 'bar', y: 'desktop' }, { type: 'line', y: 'mobile' }], legend: true } },
284
+ { name: 'Area + Line', desc: 'Filled area with overlaid line', props: { layers: [{ type: 'area', y: 'desktop' }, { type: 'line', y: 'mobile' }], legend: true } },
285
+ { name: 'Multi-layer', desc: 'Three series with mixed types', props: { layers: [{ type: 'bar', y: 'desktop' }, { type: 'area', y: 'mobile' }, { type: 'line', y: 'desktop' }], legend: true } },
286
+ ],
287
+ histogram: [
288
+ { name: 'Default', desc: 'Auto-binned frequency distribution', props: {} },
289
+ { name: 'Custom Bins', desc: 'Increased bin count for finer resolution', props: { bins: 20 } },
290
+ { name: 'With Labels', desc: 'Count labels on each bin', props: { labels: true } },
291
+ ],
292
+ waterfall: [
293
+ { name: 'Default', desc: 'Running total with positive and negative deltas', props: {} },
294
+ { name: 'Horizontal', desc: 'Horizontal waterfall layout', props: { horizontal: true } },
295
+ { name: 'With Labels', desc: 'Value labels displayed on bars', props: { labels: true } },
296
+ ],
297
+ pie: [
298
+ { name: 'Donut', desc: 'Ring chart with center space', props: { donut: true } },
299
+ { name: 'Full Pie', desc: 'Classic full circle pie chart', props: { donut: false } },
300
+ { name: 'With Labels', desc: 'Slice labels showing values', props: { labels: true } },
301
+ { name: 'With Legend', desc: 'External legend for slice identification', props: { legend: true } },
302
+ ],
303
+ radar: [
304
+ { name: 'Default', desc: 'Filled radar with area shading', props: {} },
305
+ { name: 'Multiple Series', desc: 'Overlay two data profiles', props: { y: ['a', 'b'], legend: true } },
306
+ { name: 'With Dots', desc: 'Data point markers on vertices', props: { dots: true } },
307
+ { name: 'No Fill', desc: 'Line-only radar without area fill', props: { smooth: false } },
308
+ ],
309
+ radial: [
310
+ { name: 'Default', desc: 'Radial bars showing relative values', props: {} },
311
+ { name: 'With Labels', desc: 'Value labels on each bar', props: { labels: true } },
312
+ { name: 'Three Items', desc: 'Compact radial with fewer slices', props: { data: RADIAL_DATA.slice(0, 3) } },
313
+ ],
314
+ gauge: [
315
+ { name: 'Radial', desc: 'Arc gauge showing progress to target', props: { variant: 'radial' } },
316
+ { name: 'Bullet', desc: 'Compact horizontal bullet gauge', props: { variant: 'bullet', target: 80 } },
317
+ { name: 'With Segments', desc: 'Color-coded performance zones', props: { variant: 'radial', target: 80, segments: [{ from: 0, to: 40, color: 'var(--d-error)' }, { from: 40, to: 70, color: 'var(--d-warning)' }, { from: 70, to: 100, color: 'var(--d-success)' }] } },
318
+ ],
319
+ funnel: [
320
+ { name: 'Default', desc: 'Progressive funnel narrowing down', props: {} },
321
+ { name: 'Pyramid', desc: 'Inverted triangle shape', props: { pyramid: true } },
322
+ ],
323
+ scatter: [
324
+ { name: 'Default', desc: 'Point cloud showing correlation', props: {} },
325
+ { name: 'With Labels', desc: 'Labels on data points', props: { labels: true } },
326
+ { name: 'Multiple Series', desc: 'Color-coded groups', props: { series: 'group', legend: true } },
327
+ ],
328
+ bubble: [
329
+ { name: 'Default', desc: 'Scatter with variable point sizes', props: {} },
330
+ { name: 'Multiple Series', desc: 'Grouped bubbles by category', props: { series: 'group', legend: true } },
331
+ ],
332
+ 'box-plot': [
333
+ { name: 'Default', desc: 'Quartile distribution with 1.5 IQR whiskers', props: {} },
334
+ { name: 'Min-Max Whiskers', desc: 'Whiskers extend to data extremes', props: { whiskerType: 'min-max' } },
335
+ ],
336
+ candlestick: [
337
+ { name: 'Default', desc: 'OHLC price movement bars', props: {} },
338
+ { name: 'With Grid', desc: 'Grid lines for price reference', props: { grid: true } },
339
+ ],
340
+ 'range-bar': [
341
+ { name: 'Default', desc: 'Bars showing min-max range', props: {} },
342
+ { name: 'Horizontal', desc: 'Horizontal range bars', props: { horizontal: true } },
343
+ ],
344
+ 'range-area': [
345
+ { name: 'Default', desc: 'Filled band between min and max', props: {} },
346
+ { name: 'Linear', desc: 'Straight-line range band', props: { smooth: false } },
347
+ ],
348
+ heatmap: [
349
+ { name: 'Default', desc: 'Color-coded matrix of values', props: {} },
350
+ { name: 'Custom Colors', desc: 'Orange-to-purple color scale', props: { colorScale: ['#f97316', '#7c3aed'] } },
351
+ { name: 'With Labels', desc: 'Cell values displayed in grid', props: { labels: true } },
352
+ ],
353
+ treemap: [
354
+ { name: 'Default', desc: 'Nested rectangles proportional to value', props: {} },
355
+ { name: 'With Labels', desc: 'Labels inside each rectangle', props: { labels: true } },
356
+ ],
357
+ sunburst: [
358
+ { name: 'Default', desc: 'Hierarchical data as concentric rings', props: {} },
359
+ { name: 'With Labels', desc: 'Segment labels on each ring', props: { labels: true } },
360
+ ],
361
+ sankey: [
362
+ { name: 'Default', desc: 'Flow diagram from source to destination', props: {} },
363
+ ],
364
+ chord: [
365
+ { name: 'Default', desc: 'Circular relationship diagram', props: {} },
366
+ ],
367
+ 'org-chart': [
368
+ { name: 'Top-Down', desc: 'Standard top-down hierarchy', props: { orientation: 'top-down' } },
369
+ { name: 'Left-Right', desc: 'Horizontal hierarchy layout', props: { orientation: 'left-right' } },
370
+ ],
371
+ swimlane: [
372
+ { name: 'Default', desc: 'Kanban board with lane columns', props: {} },
373
+ ],
374
+ sparkline: [
375
+ { name: 'Default', desc: 'Compact inline trend line', props: {} },
376
+ { name: 'Dense', desc: 'More data points for detailed view', props: { data: [...SPARK_VALUES, ...SPARK_VALUES] } },
377
+ ],
378
+ };
379
+
380
+ // ─── Registry Loading ──────────────────────────────────────────
381
+ let chartRegistry = null;
382
+ async function loadChartRegistry() {
383
+ if (chartRegistry) return chartRegistry;
384
+ try {
385
+ const resp = await fetch('/__decantr/registry/chart.json');
386
+ chartRegistry = await resp.json();
387
+ } catch {
388
+ chartRegistry = { exports: {} };
389
+ }
390
+ return chartRegistry;
391
+ }
392
+
393
+ // ─── Chart Card ────────────────────────────────────────────────
394
+ function chartCard(name, desc, chartEl) {
395
+ return div({ class: 'de-chart-card' },
396
+ div({ class: 'de-chart-demo' }, chartEl),
397
+ div({ class: 'de-chart-meta' },
398
+ span({ class: css('_label _fgfg') }, name),
399
+ p({ class: css('_caption _fgmutedfg') }, desc)
400
+ )
401
+ );
402
+ }
403
+
404
+ // ─── Chart Detail View ─────────────────────────────────────────
405
+ export function ChartDetail(chartType, navigateTo) {
406
+ const meta = TYPE_META[chartType];
407
+ if (!meta) return div({}, p({ class: css('_fgmutedfg') }, `Unknown chart type: ${chartType}`));
408
+
409
+ const variations = VARIATIONS[chartType] || [];
410
+
411
+ const tabs = Tabs({
412
+ tabs: [
413
+ {
414
+ id: 'variations',
415
+ label: `Variations (${variations.length})`,
416
+ content: () => {
417
+ const cards = variations.map(v => {
418
+ let chartEl;
419
+ if (v.render) {
420
+ chartEl = v.render();
421
+ } else if (chartType === 'sparkline') {
422
+ chartEl = Sparkline({
423
+ data: v.props.data || SPARK_VALUES,
424
+ height: '64', width: '100%',
425
+ 'aria-label': v.name,
426
+ });
427
+ } else {
428
+ chartEl = Chart({
429
+ ...CARD, type: chartType, ...meta.base, ...v.props,
430
+ 'aria-label': `${meta.label} chart — ${v.name}`,
431
+ });
432
+ }
433
+ return chartCard(v.name, v.desc, chartEl);
434
+ });
435
+ return div({ class: 'de-chart-grid' }, ...cards);
436
+ },
437
+ },
438
+ {
439
+ id: 'api',
440
+ label: 'API',
441
+ content: () => {
442
+ const apiContainer = div({}, p({ class: css('_fgmutedfg') }, 'Loading API...'));
443
+ loadChartRegistry().then(reg => {
444
+ const chartExport = reg?.exports?.Chart;
445
+ const props = chartExport?.params?.[0]?.properties || {};
446
+ apiContainer.replaceChildren(SpecTable({ props }));
447
+ });
448
+ return apiContainer;
449
+ },
450
+ },
451
+ ],
452
+ });
453
+
454
+ return div({ class: css('_flex _col _gap4') },
455
+ div({ class: css('_flex _col _gap1 _mb3') },
456
+ h2({ class: css('_heading4') }, meta.label),
457
+ p({ class: css('_body _fgmutedfg') }, meta.desc)
458
+ ),
459
+ tabs
460
+ );
461
+ }
462
+
463
+ // ─── Chart Group View ──────────────────────────────────────────
464
+ export function ChartGroupView(groupId, navigateTo) {
465
+ const group = CHART_GROUPS.find(g => g.id === groupId);
466
+ if (!group) return div({}, p({}, 'Group not found.'));
467
+
468
+ return section({ class: css('_flex _col _gap4') },
469
+ h2({ class: css('_heading4') }, `Charts — ${group.label}`),
470
+ p({ class: css('_body _fgmutedfg') }, `${group.types.length} chart types in this group.`),
471
+ div({ class: 'de-card-grid' },
472
+ ...group.types.map(type => {
473
+ const meta = TYPE_META[type] || {};
474
+ const varCount = (VARIATIONS[type] || []).length;
475
+ return div({
476
+ class: 'de-card-item',
477
+ onclick: () => navigateTo(`/charts/${groupId}/${type}`)
478
+ },
479
+ div({ class: css('_flex _col _gap2') },
480
+ h3({ class: css('_heading6') }, meta.label || type),
481
+ p({ class: css('_caption _fgmutedfg') }, `${varCount} variation${varCount !== 1 ? 's' : ''}`)
482
+ )
483
+ );
484
+ })
485
+ )
486
+ );
487
+ }
488
+
489
+ // ─── Sidebar Data Loader ───────────────────────────────────────
490
+ export async function loadChartItems() {
491
+ await ensureChartGroups();
492
+ return CHART_GROUPS.map(g => ({
493
+ id: g.id,
494
+ label: g.label,
495
+ children: g.types,
496
+ }));
497
+ }
@@ -0,0 +1,129 @@
1
+ import { css } from 'decantr/css';
2
+ import { tags } from 'decantr/tags';
3
+ import { Separator, Tabs } from 'decantr/components';
4
+ import { SpecTable } from './shared/spec-table.js';
5
+ import { ComponentUsageLinks } from './shared/usage-links.js';
6
+ import { renderShowcase, getShowcaseGridClass } from './shared/showcase-renderer.js';
7
+ import { injectExplorerCSS } from './styles.js';
8
+ injectExplorerCSS();
9
+
10
+ const { div, h2, h3, p, section } = tags;
11
+
12
+ // Component groups — loaded from registry, cached locally
13
+ let componentGroups = null;
14
+
15
+ // Registry data (loaded async)
16
+ let registryData = null;
17
+ let registryLoaded = false;
18
+
19
+ async function loadRegistry() {
20
+ if (registryLoaded) return registryData;
21
+ try {
22
+ const resp = await fetch('/__decantr/registry/components.json');
23
+ registryData = await resp.json();
24
+ componentGroups = registryData.groups || {};
25
+ registryLoaded = true;
26
+ } catch {
27
+ registryData = { components: {}, groups: {} };
28
+ componentGroups = {};
29
+ registryLoaded = true;
30
+ }
31
+ return registryData;
32
+ }
33
+
34
+ /**
35
+ * Component detail view with Features | API tabs.
36
+ */
37
+ export function ComponentDetail(componentName, navigateTo, groupId) {
38
+ const container = div({ class: css('_flex _col _gap4') },
39
+ div({ class: css('_flex _col _gap1') },
40
+ h2({ class: css('_heading4') }, componentName),
41
+ p({ class: css('_body _fgmutedfg') }, 'Loading component data...')
42
+ )
43
+ );
44
+
45
+ loadRegistry().then(registry => {
46
+ const compData = registry.components?.[componentName] || registry.components?.[componentName.toLowerCase()] || {};
47
+ const props = compData.props || {};
48
+ const gridClass = getShowcaseGridClass(componentName, groupId);
49
+
50
+ container.innerHTML = '';
51
+
52
+ const tabs = Tabs({
53
+ tabs: [
54
+ {
55
+ id: 'features',
56
+ label: 'Features',
57
+ content: () => renderShowcase(componentName, compData, navigateTo, gridClass)
58
+ },
59
+ {
60
+ id: 'api',
61
+ label: 'API',
62
+ content: () => div({ class: css('_flex _col _gap6') },
63
+ SpecTable({ props }),
64
+ Separator({}),
65
+ ComponentUsageLinks(componentName, navigateTo)
66
+ )
67
+ }
68
+ ]
69
+ });
70
+
71
+ container.appendChild(
72
+ div({ class: css('_flex _col _gap1 _mb3') },
73
+ h2({ class: css('_heading4') }, componentName),
74
+ p({ class: css('_body _fgmutedfg') }, compData.description || `UI component returning HTMLElement.`)
75
+ )
76
+ );
77
+ container.appendChild(tabs);
78
+ });
79
+
80
+ return container;
81
+ }
82
+
83
+ /**
84
+ * Component group listing (when no specific component selected).
85
+ */
86
+ export function ComponentGroupView(groupId, navigateTo) {
87
+ const container = div({ id: 'components-' + groupId, class: css('_flex _col _gap4') },
88
+ p({ class: css('_body _fgmutedfg') }, 'Loading...')
89
+ );
90
+
91
+ loadRegistry().then(() => {
92
+ const group = componentGroups?.[groupId];
93
+ if (!group) {
94
+ container.innerHTML = '';
95
+ container.appendChild(p({}, 'Group not found.'));
96
+ return;
97
+ }
98
+
99
+ container.innerHTML = '';
100
+ container.appendChild(
101
+ section({ class: css('_flex _col _gap4') },
102
+ h2({ class: css('_heading4') }, `Components — ${group.label}`),
103
+ p({ class: css('_body _fgmutedfg') }, `${group.items.length} components in this group.`),
104
+ div({ class: 'de-card-grid' },
105
+ ...group.items.map(name =>
106
+ div({
107
+ class: 'de-card-item',
108
+ onclick: () => navigateTo(`/components/${groupId}/${name}`)
109
+ },
110
+ h3({ class: css('_heading6') }, name),
111
+ )
112
+ )
113
+ )
114
+ )
115
+ );
116
+ });
117
+
118
+ return container;
119
+ }
120
+
121
+ export async function loadComponentItems() {
122
+ const registry = await loadRegistry();
123
+ const groups = registry.groups || {};
124
+ return Object.entries(groups).map(([id, group]) => ({
125
+ id,
126
+ label: group.label,
127
+ children: group.items,
128
+ }));
129
+ }