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