@muten/shadcn 0.0.7 → 0.1.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 (205) hide show
  1. package/AGENTS.md +47 -0
  2. package/README.md +47 -0
  3. package/kit.css +2763 -0
  4. package/motion.css +31 -1
  5. package/nova.css +1387 -0
  6. package/package.json +6 -2
  7. package/registry/accordion.muten +11 -10
  8. package/registry/agenda.js +35 -0
  9. package/registry/agenda.muten +10 -0
  10. package/registry/alert-dialog.muten +9 -12
  11. package/registry/alert.muten +8 -8
  12. package/registry/app-shell.js +9 -0
  13. package/registry/app-shell.muten +12 -0
  14. package/registry/attachment.muten +13 -9
  15. package/registry/avatar.muten +24 -5
  16. package/registry/badge.muten +23 -8
  17. package/registry/band.muten +16 -0
  18. package/registry/bar-chart.muten +7 -0
  19. package/registry/bell.muten +3 -0
  20. package/registry/bento.muten +6 -9
  21. package/registry/booking.js +21 -0
  22. package/registry/booking.muten +13 -0
  23. package/registry/breadcrumb.muten +9 -7
  24. package/registry/button-group.muten +13 -4
  25. package/registry/button.muten +21 -8
  26. package/registry/calendar.js +28 -93
  27. package/registry/calendar.muten +9 -8
  28. package/registry/canvas-viewport.js +12 -5
  29. package/registry/canvas-viewport.muten +9 -9
  30. package/registry/card-field.muten +4 -0
  31. package/registry/card.muten +12 -8
  32. package/registry/carousel.js +5 -36
  33. package/registry/carousel.muten +12 -5
  34. package/registry/chatbot.muten +9 -0
  35. package/registry/checklist.muten +6 -0
  36. package/registry/choice-list.muten +16 -0
  37. package/registry/code.js +14 -12
  38. package/registry/code.muten +6 -7
  39. package/registry/collapsible.muten +14 -10
  40. package/registry/color-picker.js +460 -41
  41. package/registry/color-picker.muten +7 -6
  42. package/registry/combobox.js +5 -0
  43. package/registry/combobox.muten +6 -23
  44. package/registry/command-palette.muten +8 -12
  45. package/registry/command.js +32 -0
  46. package/registry/command.muten +32 -11
  47. package/registry/context-menu.muten +9 -8
  48. package/registry/count-up.muten +5 -10
  49. package/registry/date-field.js +5 -0
  50. package/registry/date-field.muten +7 -0
  51. package/registry/device-frame.muten +8 -0
  52. package/registry/dialog.muten +6 -12
  53. package/registry/dirty-bar.js +19 -0
  54. package/registry/dirty-bar.muten +13 -0
  55. package/registry/drawer.muten +6 -11
  56. package/registry/dropdown-menu.muten +37 -13
  57. package/registry/empty.muten +11 -7
  58. package/registry/feature-grid.muten +10 -12
  59. package/registry/feature-rows.muten +12 -8
  60. package/registry/field-behavior.js +33 -0
  61. package/registry/field-behavior.muten +9 -0
  62. package/registry/field.muten +26 -7
  63. package/registry/file-input.js +36 -21
  64. package/registry/file-input.muten +8 -7
  65. package/registry/fixed-card.muten +25 -0
  66. package/registry/folder-picker.js +13 -15
  67. package/registry/folder-picker.muten +5 -8
  68. package/registry/gradient-blinds.muten +6 -13
  69. package/registry/hover-card.muten +7 -9
  70. package/registry/image-upload.js +5 -0
  71. package/registry/image-upload.muten +8 -0
  72. package/registry/indicator.muten +5 -0
  73. package/registry/input-group.muten +27 -4
  74. package/registry/input-otp.js +8 -35
  75. package/registry/input-otp.muten +6 -6
  76. package/registry/input.muten +8 -7
  77. package/registry/inspector.muten +13 -15
  78. package/registry/island.js +7 -0
  79. package/registry/island.muten +5 -0
  80. package/registry/item.muten +20 -7
  81. package/registry/kbd.muten +3 -3
  82. package/registry/kit/board.js +300 -0
  83. package/registry/kit/collections.js +148 -0
  84. package/registry/kit/core.js +989 -0
  85. package/registry/kit/dates.js +106 -0
  86. package/registry/kit/menus.js +90 -0
  87. package/registry/kit/overlays.js +114 -0
  88. package/registry/kit/paint.js +211 -0
  89. package/registry/kit/qrcode.js +2291 -0
  90. package/registry/kit/structure.js +393 -0
  91. package/registry/kit/table.js +198 -0
  92. package/registry/kit/values.js +179 -0
  93. package/registry/kit-layer.js +2 -0
  94. package/registry/kit-layer.muten +5 -0
  95. package/registry/kit-skeleton.js +24 -0
  96. package/registry/kit-skeleton.muten +16 -0
  97. package/registry/kit-widget.js +79 -0
  98. package/registry/kit-widget.muten +4 -0
  99. package/registry/link-btn.muten +9 -7
  100. package/registry/live-stack.js +29 -0
  101. package/registry/live-stack.muten +7 -0
  102. package/registry/logo-cloud.muten +6 -11
  103. package/registry/member-list.muten +6 -0
  104. package/registry/menubar.muten +10 -14
  105. package/registry/message-scroller.js +12 -0
  106. package/registry/message-scroller.muten +8 -4
  107. package/registry/message.muten +27 -10
  108. package/registry/messaging.muten +11 -0
  109. package/registry/modal-behavior.js +56 -0
  110. package/registry/modal-behavior.muten +4 -0
  111. package/registry/modal.muten +29 -0
  112. package/registry/money-field.js +5 -0
  113. package/registry/money-field.muten +7 -0
  114. package/registry/nav-button.muten +14 -0
  115. package/registry/navigation-menu.muten +16 -14
  116. package/registry/navpill.muten +15 -9
  117. package/registry/new-appointment.js +8 -0
  118. package/registry/new-appointment.muten +10 -0
  119. package/registry/number-field.js +34 -24
  120. package/registry/number-field.muten +8 -6
  121. package/registry/order-board.js +6 -0
  122. package/registry/order-board.muten +21 -0
  123. package/registry/overlay-behavior.js +13 -0
  124. package/registry/overlay-behavior.muten +4 -0
  125. package/registry/pagination-nav.js +6 -0
  126. package/registry/pagination-nav.muten +8 -0
  127. package/registry/pagination.muten +12 -9
  128. package/registry/password-field.muten +3 -0
  129. package/registry/permission-matrix.muten +6 -0
  130. package/registry/phone-field.js +5 -0
  131. package/registry/phone-field.muten +7 -0
  132. package/registry/phone-shell.js +7 -0
  133. package/registry/phone-shell.muten +9 -0
  134. package/registry/pill.muten +6 -5
  135. package/registry/plans.muten +4 -0
  136. package/registry/popover.muten +16 -9
  137. package/registry/pricing.muten +21 -21
  138. package/registry/progress.muten +5 -5
  139. package/registry/queue.muten +3 -0
  140. package/registry/radio-group.muten +16 -8
  141. package/registry/range-slider.js +9 -24
  142. package/registry/range-slider.muten +4 -5
  143. package/registry/resizable.js +34 -27
  144. package/registry/resizable.muten +16 -5
  145. package/registry/roving-behavior.js +16 -0
  146. package/registry/roving-behavior.muten +3 -0
  147. package/registry/row-menu.js +5 -0
  148. package/registry/row-menu.muten +5 -0
  149. package/registry/save-button.muten +11 -0
  150. package/registry/scale-slider.js +7 -0
  151. package/registry/scale-slider.muten +9 -0
  152. package/registry/scroll-area.muten +8 -4
  153. package/registry/segmented-control.muten +8 -8
  154. package/registry/separator.muten +3 -3
  155. package/registry/share-button.js +7 -0
  156. package/registry/share-button.muten +10 -0
  157. package/registry/sheet.muten +6 -10
  158. package/registry/sidebar.muten +23 -15
  159. package/registry/skeleton.muten +6 -4
  160. package/registry/slider.js +73 -27
  161. package/registry/slider.muten +6 -6
  162. package/registry/sortable-behavior.js +43 -0
  163. package/registry/sortable-behavior.muten +2 -0
  164. package/registry/sortable-list.js +10 -26
  165. package/registry/sortable-list.muten +6 -7
  166. package/registry/sortable.muten +15 -0
  167. package/registry/spark.js +14 -0
  168. package/registry/spark.muten +3 -0
  169. package/registry/spinner.muten +4 -3
  170. package/registry/split-button.muten +12 -10
  171. package/registry/stamp-card.muten +7 -0
  172. package/registry/stat-band.muten +6 -9
  173. package/registry/stat.muten +23 -0
  174. package/registry/status-bar.muten +7 -6
  175. package/registry/stepper.muten +17 -14
  176. package/registry/store-qr.js +5 -0
  177. package/registry/store-qr.muten +5 -0
  178. package/registry/swatch.muten +8 -8
  179. package/registry/switch.muten +14 -6
  180. package/registry/table-view.muten +10 -0
  181. package/registry/tabs-behavior.js +27 -0
  182. package/registry/tabs-behavior.muten +3 -0
  183. package/registry/tabs.muten +19 -10
  184. package/registry/tag-input.js +4 -27
  185. package/registry/tag-input.muten +6 -6
  186. package/registry/text.muten +7 -0
  187. package/registry/time-field.js +5 -0
  188. package/registry/time-field.muten +6 -0
  189. package/registry/timeline.muten +14 -14
  190. package/registry/toaster.js +34 -22
  191. package/registry/toaster.muten +10 -8
  192. package/registry/toggle-group.muten +11 -6
  193. package/registry/toggle.muten +8 -5
  194. package/registry/toolbar.muten +11 -6
  195. package/registry/tooltip.muten +6 -9
  196. package/registry/tree-view.js +27 -7
  197. package/registry/tree-view.muten +8 -13
  198. package/registry/vector-field.muten +9 -8
  199. package/registry/virtual-list.js +5 -0
  200. package/registry/virtual-list.muten +6 -0
  201. package/registry/weekly-hours.muten +4 -0
  202. package/registry/wordline.muten +6 -0
  203. package/registry/zoom-control.muten +9 -9
  204. package/registry.json +872 -406
  205. package/registry/context-menu.js +0 -34
@@ -1,9 +1,10 @@
1
- # Calendar - a month grid with variants. The PAGE owns the selected value (text) per mode:
2
- # mode: "single" | "range" | "multiple" months: 1 | 2 caption: "label" | "dropdown"
3
- # selected encodes the value: single -> "yyyy-mm-dd"; range -> "from/to"; multiple -> "d1,d2,...".
4
- # state { date = "" : text } action pick(d: text) mutates date { date.set(d) }
5
- # Calendar(selected: date, mode: "single", months: 1, caption: "label", onSelect: pick)
6
- # shadcn-for-muten · Custom · host JS is src/components/Calendar.js (copied by `muten add calendar`).
7
- part Calendar(selected: text, mode: text, months: number, caption: text, onSelect: action) {
8
- Custom Calendar inputs(selected: $selected, mode: $mode, months: $months, caption: $caption) on(select: $onSelect) class("calendar")
1
+ # Calendar - the month grid: a day or a range, one or two months. The page owns nothing until the person picks:
2
+ # onSelect gets "yyyy-mm-dd" (single) or "from/to" (range).
3
+ # Calendar(selected: "today", min: "today", caption: "on", onSelect: pickDay)
4
+ # Calendar(selected: "-6/0", mode: "range", months: 2, presets: "reports", caption: "on", onSelect: pickRange)
5
+ # selected/min take dates or days from today ("today", "-6/0"). presets "reports" adds Hoy · Últimos 7 días · Este
6
+ # mes · Mes pasado beside a range; caption "on" writes what was chosen under it.
7
+ # shadcn-for-muten · Custom (kit core) · host JS is calendar.js.
8
+ part Calendar(selected: text = "", mode: text = "single", months: number = 1, min: text = "", presets: text = "", caption: text = "", onSelect: action) {
9
+ Custom Calendar inputs(selected: $selected, mode: $mode, months: $months, min: $min, presets: $presets, caption: $caption) on(select: $onSelect) class("cx-cal-host")
9
10
  }
@@ -1,11 +1,13 @@
1
1
  // CanvasHost (muten Custom). A <canvas> with pan (drag) + zoom (wheel). handlers: { view(zoomPercent) }.
2
- // Draws a checkerboard + a sample sprite as a placeholder - replace draw() with your scene render.
2
+ // Draws a checkerboard + a sample square as a placeholder in the skin's colours - replace draw() with your scene.
3
+ // inputs: zoom (percent; the page can set it, e.g. from a ZoomControl). Double-click puts the view back at 100 %.
3
4
  export function mount(el, inputs, handlers) {
4
5
  const canvas = document.createElement('canvas');
5
6
  el.appendChild(canvas);
6
7
  const ctx = canvas.getContext('2d');
7
8
  let scale = 1, ox = 0, oy = 0;
8
9
  const dpr = () => window.devicePixelRatio || 1;
10
+ const token = (name, fallback) => getComputedStyle(el).getPropertyValue(name).trim() || fallback;
9
11
 
10
12
  const draw = () => {
11
13
  const w = canvas.width / dpr(), h = canvas.height / dpr();
@@ -16,11 +18,11 @@ export function mount(el, inputs, handlers) {
16
18
  const sx = Math.floor((-ox / scale) / cell) - 1, sy = Math.floor((-oy / scale) / cell) - 1;
17
19
  const cols = Math.ceil(w / cell / scale) + 3, rows = Math.ceil(h / cell / scale) + 3;
18
20
  for (let i = sx; i < sx + cols; i++) for (let j = sy; j < sy + rows; j++) {
19
- ctx.fillStyle = (i + j) % 2 ? 'rgba(255,255,255,.05)' : 'rgba(255,255,255,.02)';
21
+ ctx.fillStyle = (i + j) % 2 ? token('--muted', '#f4f4f5') : token('--background', '#fff');
20
22
  ctx.fillRect(i * cell, j * cell, cell, cell);
21
23
  }
22
- ctx.fillStyle = '#3b82f6'; ctx.fillRect(-32, -32, 64, 64);
23
- ctx.strokeStyle = 'rgba(255,255,255,.6)'; ctx.lineWidth = 1 / scale; ctx.strokeRect(-32, -32, 64, 64);
24
+ ctx.fillStyle = token('--selected', '#6B46F2'); ctx.fillRect(-32, -32, 64, 64);
25
+ ctx.strokeStyle = token('--foreground', '#111'); ctx.lineWidth = 1 / scale; ctx.strokeRect(-32, -32, 64, 64);
24
26
  ctx.restore();
25
27
  };
26
28
  const resize = () => { const r = el.getBoundingClientRect(); canvas.width = r.width * dpr(); canvas.height = r.height * dpr(); ctx.setTransform(dpr(), 0, 0, dpr(), 0, 0); draw(); };
@@ -40,5 +42,10 @@ export function mount(el, inputs, handlers) {
40
42
 
41
43
  requestAnimationFrame(() => { const r = el.getBoundingClientRect(); ox = r.width / 2; oy = r.height / 2; resize(); });
42
44
  window.addEventListener('resize', resize);
43
- return () => {};
45
+ // the page sets the zoom (a ZoomControl): zoom around the centre of the view
46
+ const zoomTo = (pct) => { const ns = Math.min(8, Math.max(0.2, pct / 100)); if (Math.abs(ns - scale) < 1e-3) return; const r = el.getBoundingClientRect(), cx = r.width / 2, cy = r.height / 2, k = ns / scale; ox = cx - (cx - ox) * k; oy = cy - (cy - oy) * k; scale = ns; draw(); };
47
+ canvas.addEventListener('dblclick', () => { zoomTo(100); handlers.view?.(100); });
48
+ new MutationObserver(draw).observe(document.documentElement, { attributes: true, subtree: true, attributeFilter: ['data-theme', 'data-skin'] });
49
+ if (inputs.zoom) requestAnimationFrame(() => zoomTo(Number(inputs.zoom)));
50
+ return (next) => { if (next.zoom) zoomTo(Number(next.zoom)); };
44
51
  }
@@ -1,11 +1,11 @@
1
- # CanvasViewport - a <canvas> host with pan (drag) + zoom (wheel), and a muten toolbar slot floating on top.
2
- # The PAGE can react to zoom via onView. The canvas draws a placeholder scene - edit src/components/CanvasHost.js.
3
- # action onView(z: number) mutates zoom { zoom.set(z) }
4
- # CanvasViewport(onView: onView) { Toolbar { … } } # the slot renders over the canvas (pointer-events re-enabled)
5
- # shadcn-for-muten · Custom (the canvas) wrapped in a muten part (the overlay). host JS is src/components/CanvasHost.js.
6
- part CanvasViewport(onView: action) {
7
- Stack class("canvas-viewport") {
8
- Custom CanvasHost on(view: $onView) class("canvas-host")
9
- Stack class("canvas-overlay") { slot }
1
+ # CanvasViewport - a canvas you move by dragging and zoom with the wheel (around the pointer), with muten content
2
+ # floating over it (a Toolbar, a ZoomControl). The page can set the zoom and hears it change; a double click goes
3
+ # back to 100 %. The placeholder scene is in canvas-viewport.js: replace its draw() with yours.
4
+ # CanvasViewport(zoom: zoom, onView: setZoom) { Toolbar { … } }
5
+ # shadcn-for-muten · part + Custom (CanvasHost) · kit.css (.cx-canvas) · host JS canvas-viewport.js.
6
+ part CanvasViewport(zoom: number = 100, height: text = "360px", onView: action) {
7
+ Stack class("cx-canvas") data(slot: "canvas-viewport") style(h: "{$height}") {
8
+ Custom CanvasHost inputs(zoom: $zoom) on(view: $onView) class("cx-canvas-host")
9
+ Stack class("cx-canvas-over") { slot }
10
10
  }
11
11
  }
@@ -0,0 +1,4 @@
1
+ # CardField - card number with its brand, expiry, code; validates (Luhn) and says what was saved.
2
+ # CardField(onSave: saved)
3
+ # shadcn-for-muten · part over KitWidget (kit core).
4
+ part CardField(onSave: action) { Custom KitWidget inputs(name: "cardField") on(action: $onSave) class("cx-cardform") }
@@ -1,8 +1,12 @@
1
- # Card + its sub-components. Compose: Card { CardHeader { CardTitle(label) CardDescription(label) } CardContent { … } CardFooter { … } }
2
- # shadcn-for-muten · part · no deps · styles via the semantic .card* classes in globals.css.
3
- part Card() { Section class("card") { slot } }
4
- part CardHeader() { Stack class("card-header") { slot } }
5
- part CardTitle(label: text) { Title "{$label}" h3 class("card-title") }
6
- part CardDescription(label: text) { Span "{$label}" class("card-description") }
7
- part CardContent() { Stack class("card-content") { slot } }
8
- part CardFooter() { Stack class("card-footer") { slot } }
1
+ # Card - a surface that only CONVEYS: header (title, description, an action at the corner), content, footer.
2
+ # Decisions go in a modal, not on the card.
3
+ # Card(size: "sm") { CardHeader { CardTitle(label: "Hoy") CardDescription(label: "3 citas") CardAction { Btn(variant: "ghost", size: "icon-sm", onClick: more) { Icon "lucide:ellipsis" } } } CardContent { … } }
4
+ # size: default | sm (compact: items edge to edge)
5
+ # shadcn-for-muten · part · no deps · Nova (.cn-card) + kit.css (size sm).
6
+ part Card(size: text = "default") { Section class("cn-card group/card") data(slot: "card", size: $size) { slot } }
7
+ part CardHeader() { Stack class("cn-card-header group/card-header") data(slot: "card-header") { slot } }
8
+ part CardTitle(label: text) { Title "{$label}" h3 class("cn-card-title") data(slot: "card-title") }
9
+ part CardDescription(label: text) { Text "{$label}" class("cn-card-description") data(slot: "card-description") }
10
+ part CardAction() { Stack class("cn-card-action") data(slot: "card-action") { slot } }
11
+ part CardContent() { Stack class("cn-card-content") data(slot: "card-content") { slot } }
12
+ part CardFooter() { Stack class("cn-card-footer") data(slot: "card-footer") { slot } }
@@ -1,37 +1,6 @@
1
- // Carousel host component (muten Custom). inputs: { items } (a list). Renders one slide per item with prev/next.
2
- // Slides show the item as text; edit this file for image/rich slides (e.g. an <img> per item).
3
- export function mount(el, inputs, handlers) {
4
- const viewport = document.createElement('div'); viewport.className = 'carousel-viewport';
5
- const track = document.createElement('div'); track.className = 'carousel-track';
6
- viewport.appendChild(track); el.appendChild(viewport);
7
- const SVG = (p) => '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">' + p + '</svg>';
8
- const prev = document.createElement('button'); prev.type = 'button'; prev.className = 'carousel-prev'; prev.innerHTML = SVG('<path d="m15 18-6-6 6-6"/>'); // lucide chevron-left
9
- const next = document.createElement('button'); next.type = 'button'; next.className = 'carousel-next'; next.innerHTML = SVG('<path d="m9 18 6-6-6-6"/>'); // lucide chevron-right
10
- el.appendChild(prev); el.appendChild(next);
11
-
12
- let idx = 0;
13
- let items = [];
14
- // Move by exact viewport widths (px). A `%` translate is relative to the multi-slide track, not the viewport.
15
- const paint = () => {
16
- const w = viewport.clientWidth || el.clientWidth || 0;
17
- for (const s of track.children) s.style.width = w + 'px';
18
- track.style.transform = 'translateX(' + (-idx * w) + 'px)';
19
- };
20
- const build = (list) => {
21
- items = Array.isArray(list) ? list : [];
22
- track.innerHTML = '';
23
- for (const it of items) {
24
- const s = document.createElement('div'); s.className = 'carousel-slide';
25
- s.textContent = (it && typeof it === 'object') ? (it.label ?? it.title ?? '') : String(it);
26
- track.appendChild(s);
27
- }
28
- if (idx > items.length - 1) idx = Math.max(0, items.length - 1);
29
- paint();
30
- };
31
- window.addEventListener('resize', paint);
32
- prev.addEventListener('click', () => { if (items.length) { idx = (idx - 1 + items.length) % items.length; paint(); } });
33
- next.addEventListener('click', () => { if (items.length) { idx = (idx + 1) % items.length; paint(); } });
34
-
35
- build(inputs.items);
36
- return (n) => build(n.items);
1
+ // CarouselBehavior (muten Custom) - the kit's carousel behaviour over the slides muten rendered (kit/collections.js).
2
+ export function mount(el, inputs) {
3
+ const root = el.closest('.cx-car');
4
+ Promise.all([import('@muten/shadcn/registry/kit/core.js'), import('@muten/shadcn/registry/kit/collections.js')]).then(([core, c]) => c.carouselBehavior(root, inputs, core));
5
+ return () => {};
37
6
  }
@@ -1,6 +1,13 @@
1
- # Carousel - a slide carousel over a list. The PAGE owns the slides (list<text>):
2
- # state { slides = ["1", "2", "3", "4", "5"] : list<text> } Carousel(items: slides)
3
- # shadcn-for-muten · Custom · host JS is src/components/Carousel.js. Slides render as text; edit the .js for images/rich slides.
4
- part Carousel(items: list) {
5
- Custom Carousel inputs(items: $items) class("carousel")
1
+ # Carousel - one component, options instead of copies. The slides are your muten content (CarouselSlide).
2
+ # perView "4" or "2,4" (phone, wider) · center "on" (the active one centred, neighbours peeking) · loop "on" ·
3
+ # autoplay ms (pauses on hover, focus, touch, hidden tab; off with reduced motion; its own pause button) ·
4
+ # indicator dots | progress | count | thumbs · arrows bar | overlay
5
+ # Carousel(label: "Productos", perView: "2,4") { each products as p { CarouselSlide { … } } }
6
+ # shadcn-for-muten · part + Custom (CarouselBehavior, kit collections) · kit.css (.cx-car).
7
+ part Carousel(label: text, perView: text = "1", center: text = "", loop: text = "", autoplay: number = 0, indicator: text = "dots", arrows: text = "bar") {
8
+ Section class("cx-car") aria(label: $label, roledescription: "carrusel") {
9
+ Stack class("cx-car-view") { Stack class("cx-car-track") { slot } }
10
+ Custom Carousel inputs(perView: $perView, center: $center, loop: $loop, autoplay: $autoplay, indicator: $indicator, arrows: $arrows) class("cx-car-behavior")
11
+ }
6
12
  }
13
+ part CarouselSlide() { Stack class("cx-car-slide") { slot } }
@@ -0,0 +1,9 @@
1
+ # Chatbot - the assistant: greets, offers suggestions, «thinks», writes the answer live (with Stop), can carry an
2
+ # action button inside the answer (onAction gets its label), rated with 👍 / 👎. answers: {match (regex), text, action,
3
+ # primary}; the one without match is the fallback.
4
+ # Chatbot(title: "Rober", greeting: "Hola, soy Rober…", suggestions: @ideas, answers: @answers, onAction: act)
5
+ # demo "on": it taps its own suggestions in turn and answers them live, for a landing; the first touch stops it.
6
+ # shadcn-for-muten · part over KitWidget (kit core).
7
+ part Chatbot(title: text, greeting: text, suggestions: list, answers: list, demo: text = "", onAction: action) {
8
+ Custom KitWidget inputs(name: "chatbot", title: $title, greeting: $greeting, suggestions: $suggestions, answers: $answers, demo: $demo) on(action: $onAction) class("cx-bot")
9
+ }
@@ -0,0 +1,6 @@
1
+ # Checklist - first steps with progress: done steps checked, the next ones with their action (onAction gets its label).
2
+ # Checklist(title: "Tu tienda en 5 pasos", steps: @steps, onAction: act) steps: {title, desc, done, action}
3
+ # shadcn-for-muten · part over KitWidget (kit core).
4
+ part Checklist(title: text, steps: list, onAction: action) {
5
+ Custom KitWidget inputs(name: "checklist", title: $title, steps: $steps) on(action: $onAction) class("cx-onboard")
6
+ }
@@ -0,0 +1,16 @@
1
+ # ChoiceList - Items you choose one of: a real radio group (arrows move and pick). The page owns the current value.
2
+ # ChoiceList(label: "Servicio") {
3
+ # ChoiceItem(value: "cut", current: service, onPick: pick, title: "Corte clásico", description: "30 min", price: "$25.000")
4
+ # }
5
+ # shadcn-for-muten · part + Custom (RovingBehavior) · kit.css (.cx-radiogroup, .cx-radio).
6
+ part ChoiceList(label: text) { Stack class("cx-radiogroup") aria(role: "radiogroup", label: $label) { slot RovingBehavior() } }
7
+ part ChoiceItem(value: text, current: text, onPick: action, title: text, description: text = "", price: text = "") {
8
+ Button -> $onPick($value) class("cn-item cn-item-variant-outline cn-item-size-sm group/item cx-item cx-choice") data(slot: "item") aria(role: "radio", checked: $value == $current) {
9
+ Span "" class("cx-radio")
10
+ Stack class("cn-item-content") data(slot: "item-content") {
11
+ Span "{$title}" class("cn-item-title") data(slot: "item-title")
12
+ Text "{$description}" class("cn-item-description") data(slot: "item-description")
13
+ }
14
+ Span "{$price}" class("cx-price")
15
+ }
16
+ }
package/registry/code.js CHANGED
@@ -1,14 +1,16 @@
1
- // Code block host (muten Custom). inputs: { code, lang }. Highlights with the muten grammar via the shared
2
- // highlight.js (lazy-loaded, so the token rules live in one place). See code.muten.
1
+ // Code (muten Custom) - a code block: a bar with the language (or a title) and Copiar, then the code highlighted with
2
+ // the muten grammar (any other lang shows plain). inputs: code, lang, title. The highlighter is the shared
3
+ // highlight.js, loaded lazily so the token rules live in one place.
3
4
  export function mount(el, inputs) {
4
- const code = String(inputs.code == null ? "" : inputs.code);
5
- const lang = inputs.lang == null ? "muten" : String(inputs.lang);
6
- el.className = "rounded-lg border bg-muted/40 px-4 py-3 font-mono text-foreground overflow-x-auto";
7
- const pre = document.createElement("pre");
8
- pre.className = "m-0 text-sm leading-relaxed";
9
- pre.textContent = code; // plain until the highlighter loads (progressive)
10
- el.appendChild(pre);
11
- import("@muten/shadcn/registry/highlight.js")
12
- .then((m) => { pre.innerHTML = m.highlight(code, lang); })
13
- .catch(() => { /* keep the plain text */ });
5
+ const code = String(inputs.code ?? ''), lang = String(inputs.lang || 'muten');
6
+ el.innerHTML = '<div class="cx-code-bar"><span></span><button type="button" class="cn-button cn-button-variant-outline cn-button-size-xs">Copiar</button></div><pre class="cx-code-pre"><code></code></pre>';
7
+ el.querySelector('.cx-code-bar span').textContent = inputs.title || lang;
8
+ const box = el.querySelector('code'), copy = el.querySelector('button');
9
+ box.textContent = code;
10
+ if (lang === 'muten') import('@muten/shadcn/registry/highlight.js').then((m) => { box.innerHTML = m.highlight(code, lang); }).catch(() => {});
11
+ copy.onclick = () => {
12
+ const done = (t) => { copy.textContent = t; setTimeout(() => { copy.textContent = 'Copiar'; }, 1400); };
13
+ try { navigator.clipboard.writeText(code).then(() => done('Copiado'), () => { getSelection().selectAllChildren(box); done('Selecciónalo'); }); } catch { getSelection().selectAllChildren(box); done('Selecciónalo'); }
14
+ };
15
+ return (next) => { const c = String(next.code ?? ''); if (c === box.textContent) return; box.textContent = c; if (lang === 'muten') import('@muten/shadcn/registry/highlight.js').then((m) => { box.innerHTML = m.highlight(c, lang); }); };
14
16
  }
@@ -1,7 +1,6 @@
1
- # Code - a syntax-highlighted code block. `lang` defaults to "muten" (the muten grammar, matching the VS Code
2
- # extension); pass another lang to render plain. NOTE: muten strings can't hold `{` or `"`, so feed muten source
3
- # from a .md file (a Markdown component) or a .js snippet, not an inline muten string.
4
- # shadcn-for-muten · Custom · host JS is src/components/Code.js (copied by `muten add code`); shares highlight.js.
5
- part Code(code: text, lang: text) {
6
- Custom Code inputs(code: $code, lang: $lang) class("code-block")
7
- }
1
+ # Code - a code block with a bar (the language or a title) and Copiar; muten code comes highlighted with the same
2
+ # grammar as the editor. muten strings cannot hold quotes or braces, so pass the code from a state (or a store),
3
+ # not inline. For other languages pass lang: it shows plain.
4
+ # state { snippet = "…" : text } Code(code: snippet, title: "src/app.muten")
5
+ # shadcn-for-muten · Custom · kit.css (.cx-code) · host JS code.js (shares highlight.js).
6
+ part Code(code: text, lang: text = "muten", title: text = "") { Custom Code inputs(code: $code, lang: $lang, title: $title) class("cx-code") }
@@ -1,14 +1,18 @@
1
- # Collapsible - a trigger + content that animates open. The PAGE owns the bool + the toggle action.
2
- # state { open = false : bool } action t mutates open { open.toggle() } Collapsible(label: "…", open: open, onToggle: t) { … }
3
- # shadcn-for-muten · part · no deps · the content always renders and animates open via the .collapsible-panel grid trick.
4
- part Collapsible(label: text, open: bool, onToggle: action) {
5
- Stack class("collapsible") {
6
- Button -> $onToggle aria(expanded: $open) class("collapsible-trigger") {
7
- Span "{$label}"
8
- Icon "lucide:chevrons-up-down" class("size-4 text-muted-foreground")
1
+ # Collapsible - one block that opens and closes: a trigger line and the content under it, folding to its exact
2
+ # height (200 ms) while the chevron turns. The page owns the bool. For several stacked questions use Accordion.
3
+ # state { more = false : bool } action toggleMore() mutates more { more.toggle() }
4
+ # Collapsible(label: "Más opciones", open: more, onToggle: toggleMore) { … }
5
+ # Collapsible(label: "3 pedidos entregados", hint: "Ver", open: done, onToggle: toggleDone) { … }
6
+ # shadcn-for-muten · part · no deps · kit.css (.cx-coll, the same fold as Accordion).
7
+ part Collapsible(label: text, open: bool, onToggle: action, hint: text = "") {
8
+ Stack class("cx-coll") data(slot: "collapsible", open: $open) {
9
+ Button -> $onToggle class("cx-coll-trigger") data(slot: "collapsible-trigger") aria(expanded: $open) {
10
+ Span "{$label}" class("cx-coll-label")
11
+ Span "{$hint}" class("cx-coll-hint")
12
+ Icon "lucide:chevron-down" class("cx-acc-chev")
9
13
  }
10
- Stack class("collapsible-panel", "collapsible-panel-open" when $open) {
11
- Stack class("collapsible-content") { slot }
14
+ Stack class("cn-accordion-content cx-acc-fold") data(slot: "collapsible-content", open: $open) aria(role: "region") {
15
+ Stack class("cn-accordion-content-inner cx-coll-inner") { slot }
12
16
  }
13
17
  }
14
18
  }