@muten/shadcn 0.1.14 → 0.2.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 (182) hide show
  1. package/AGENTS.md +88 -1
  2. package/globals.css +1 -0
  3. package/kit.css +329 -124
  4. package/motion.css +3 -0
  5. package/package.json +1 -1
  6. package/registry/accordion.muten +3 -3
  7. package/registry/agenda.js +19 -16
  8. package/registry/agenda.muten +10 -10
  9. package/registry/alert-dialog.muten +2 -2
  10. package/registry/alert.muten +1 -1
  11. package/registry/app-shell.muten +1 -1
  12. package/registry/attachment.muten +1 -1
  13. package/registry/avatar.muten +4 -4
  14. package/registry/badge.muten +14 -3
  15. package/registry/band.muten +2 -2
  16. package/registry/bar-chart.muten +2 -2
  17. package/registry/bio-play.muten +2 -2
  18. package/registry/booking-play.muten +5 -5
  19. package/registry/booking.muten +2 -2
  20. package/registry/breadcrumb.muten +4 -4
  21. package/registry/button.muten +9 -7
  22. package/registry/calendar.js +15 -10
  23. package/registry/calendar.muten +1 -1
  24. package/registry/card.muten +1 -1
  25. package/registry/carousel.muten +1 -1
  26. package/registry/chat-play.muten +8 -8
  27. package/registry/chatbot.muten +5 -4
  28. package/registry/checklist.muten +1 -1
  29. package/registry/choice-list.muten +3 -3
  30. package/registry/code.js +10 -4
  31. package/registry/collapsible.muten +2 -2
  32. package/registry/color-picker.js +37 -36
  33. package/registry/color-picker.muten +1 -1
  34. package/registry/combobox.js +1 -1
  35. package/registry/combobox.muten +2 -2
  36. package/registry/command-palette.muten +2 -2
  37. package/registry/command.js +1 -1
  38. package/registry/command.muten +7 -7
  39. package/registry/context-menu.muten +3 -3
  40. package/registry/count-up.js +33 -31
  41. package/registry/count-up.muten +6 -4
  42. package/registry/date-field.js +1 -1
  43. package/registry/date-field.muten +3 -3
  44. package/registry/day-plan.muten +5 -5
  45. package/registry/device-frame.muten +3 -3
  46. package/registry/dialog.muten +1 -1
  47. package/registry/dirty-bar.js +2 -2
  48. package/registry/dirty-bar.muten +3 -3
  49. package/registry/drawer.muten +1 -1
  50. package/registry/dropdown-menu.muten +4 -4
  51. package/registry/empty.muten +1 -1
  52. package/registry/feature-grid.muten +1 -1
  53. package/registry/feature-rows.muten +2 -2
  54. package/registry/field-behavior.js +13 -3
  55. package/registry/field-behavior.muten +3 -3
  56. package/registry/field.muten +5 -5
  57. package/registry/file-input.js +8 -4
  58. package/registry/file-input.muten +3 -3
  59. package/registry/fixed-card.muten +4 -4
  60. package/registry/folder-picker.js +6 -2
  61. package/registry/folder-picker.muten +2 -2
  62. package/registry/heatmap.muten +5 -5
  63. package/registry/image-upload.js +1 -1
  64. package/registry/image-upload.muten +2 -2
  65. package/registry/inbox-play.muten +5 -5
  66. package/registry/indicator.muten +1 -1
  67. package/registry/info-table.muten +1 -1
  68. package/registry/input-group.muten +11 -11
  69. package/registry/input-otp.muten +2 -2
  70. package/registry/input.muten +1 -1
  71. package/registry/inspector.muten +2 -2
  72. package/registry/item.muten +2 -2
  73. package/registry/kit/board.js +36 -34
  74. package/registry/kit/collections.js +24 -24
  75. package/registry/kit/core.js +197 -114
  76. package/registry/kit/dates.js +29 -27
  77. package/registry/kit/i18n.js +172 -0
  78. package/registry/kit/menus.js +14 -13
  79. package/registry/kit/overlays.js +7 -2
  80. package/registry/kit/structure.js +66 -61
  81. package/registry/kit/table.js +19 -17
  82. package/registry/kit/values.js +36 -28
  83. package/registry/kit-skeleton.muten +1 -1
  84. package/registry/kit-widget.js +9 -4
  85. package/registry/lang-scope.js +8 -0
  86. package/registry/lang-scope.muten +5 -0
  87. package/registry/legal-doc.muten +5 -5
  88. package/registry/link-btn.muten +1 -1
  89. package/registry/list-motion.js +46 -0
  90. package/registry/list-motion.muten +4 -0
  91. package/registry/live-stack.muten +2 -2
  92. package/registry/logo-cloud.muten +2 -2
  93. package/registry/marketing.muten +7 -7
  94. package/registry/member-list.muten +2 -2
  95. package/registry/menubar.muten +3 -3
  96. package/registry/message-scroller.muten +3 -3
  97. package/registry/message.muten +9 -9
  98. package/registry/messaging.muten +1 -1
  99. package/registry/modal.muten +4 -4
  100. package/registry/money-field.js +1 -1
  101. package/registry/money-field.muten +2 -2
  102. package/registry/nav-button.muten +4 -4
  103. package/registry/navigation-menu.muten +4 -4
  104. package/registry/navpill.muten +6 -6
  105. package/registry/new-appointment.js +1 -1
  106. package/registry/new-appointment.muten +4 -4
  107. package/registry/number-field.js +6 -4
  108. package/registry/number-field.muten +1 -1
  109. package/registry/order-board.js +1 -1
  110. package/registry/order-board.muten +2 -2
  111. package/registry/order-play.muten +4 -4
  112. package/registry/pagination-nav.js +1 -1
  113. package/registry/pagination-nav.muten +3 -3
  114. package/registry/pagination.muten +4 -4
  115. package/registry/password-field.muten +2 -2
  116. package/registry/permission-matrix.muten +4 -4
  117. package/registry/phone-bar.muten +6 -6
  118. package/registry/phone-field.js +1 -1
  119. package/registry/phone-field.muten +1 -1
  120. package/registry/phone-shell.js +1 -1
  121. package/registry/pill.muten +2 -2
  122. package/registry/plans.muten +1 -1
  123. package/registry/popover.muten +2 -2
  124. package/registry/pricing.muten +4 -4
  125. package/registry/product-play.muten +3 -3
  126. package/registry/progress.muten +3 -3
  127. package/registry/queue.muten +1 -1
  128. package/registry/radio-group.muten +1 -1
  129. package/registry/rank-item.muten +4 -4
  130. package/registry/resizable.muten +1 -1
  131. package/registry/row-menu.js +1 -1
  132. package/registry/row-menu.muten +2 -2
  133. package/registry/save-button.muten +5 -5
  134. package/registry/scale-slider.js +1 -1
  135. package/registry/scale-slider.muten +2 -2
  136. package/registry/screen-tour.muten +1 -1
  137. package/registry/share-bar.muten +2 -2
  138. package/registry/share-button.muten +4 -4
  139. package/registry/sheet.muten +1 -1
  140. package/registry/sidebar.muten +12 -6
  141. package/registry/site-footer.muten +6 -6
  142. package/registry/sortable-behavior.js +67 -20
  143. package/registry/sortable-list.js +3 -1
  144. package/registry/sortable-list.muten +2 -2
  145. package/registry/sortable.muten +5 -4
  146. package/registry/spinner.muten +2 -2
  147. package/registry/split-button.muten +2 -2
  148. package/registry/stacked-bars.js +3 -3
  149. package/registry/stacked-bars.muten +2 -2
  150. package/registry/stamp-card.muten +2 -2
  151. package/registry/stat-band.muten +3 -3
  152. package/registry/stat.muten +3 -3
  153. package/registry/status-bar.muten +2 -2
  154. package/registry/stepper.muten +4 -4
  155. package/registry/store-qr.js +1 -1
  156. package/registry/store-qr.muten +2 -2
  157. package/registry/swatch.muten +1 -1
  158. package/registry/switch.muten +8 -7
  159. package/registry/table-view.muten +3 -3
  160. package/registry/tabs-behavior.js +9 -1
  161. package/registry/tabs.muten +2 -2
  162. package/registry/tag-input.js +1 -1
  163. package/registry/tag-input.muten +2 -2
  164. package/registry/text.muten +2 -2
  165. package/registry/time-field.js +1 -1
  166. package/registry/time-field.muten +3 -3
  167. package/registry/timeline.muten +4 -4
  168. package/registry/toaster.muten +2 -2
  169. package/registry/toggle-group.muten +1 -1
  170. package/registry/toggle.muten +3 -3
  171. package/registry/toolbar.muten +4 -4
  172. package/registry/tooltip.muten +2 -2
  173. package/registry/tree-view.js +38 -25
  174. package/registry/tree-view.muten +1 -1
  175. package/registry/veil.muten +1 -1
  176. package/registry/virtual-list.js +1 -1
  177. package/registry/virtual-list.muten +2 -2
  178. package/registry/virtual-window.js +4 -4
  179. package/registry/virtual-window.muten +2 -2
  180. package/registry/wordline.muten +1 -1
  181. package/registry/zoom-control.muten +1 -1
  182. package/registry.json +22 -2
@@ -1,11 +1,11 @@
1
1
  # NavigationMenu - a site's top navigation: words that open a panel of links on hover (or tap, or focus) and plain
2
2
  # links. Once a panel is open, resting on the next word switches to its panel.
3
- # NavigationMenu(label: "Principal") {
4
- # NavMenuItem { NavMenuTrigger(label: "Producto") NavMenuContent { NavMenuLink(title: "Agenda", description: "…", to: "/agenda") … } }
5
- # NavMenuItemLink(label: "Precios", to: "/precios")
3
+ # NavigationMenu(label: "Primary") {
4
+ # NavMenuItem { NavMenuTrigger(label: "Product") NavMenuContent { NavMenuLink(title: "Schedule", description: "…", to: "/agenda") … } }
5
+ # NavMenuItemLink(label: "Prices", to: "/precios")
6
6
  # }
7
7
  # shadcn-for-muten · parts + Custom (OverlayBehavior) · Nova (.cn-navigation-menu-*) + kit.css.
8
- part NavigationMenu(label: text = "Principal") { Nav class("cn-navigation-menu cx-navmenu") data(slot: "navigation-menu") aria(label: $label) { slot } }
8
+ part NavigationMenu(label: text = "Main") { Nav class("cn-navigation-menu cx-navmenu") data(slot: "navigation-menu", trl: $label) aria(label: $label) { slot } }
9
9
  part NavMenuItem() { Stack class("cx-ov") data(slot: "navigation-menu-item") { slot OverlayBehavior(kind: "navmenu") } }
10
10
  part NavMenuTrigger(label: text) { Stack class("cx-ov-trigger") { Button class("cn-navigation-menu-trigger cx-navmenu-trigger") data(slot: "navigation-menu-trigger") { Span "{$label}" Icon "lucide:chevron-down" class("cn-navigation-menu-trigger-icon") } } }
11
11
  part NavMenuContent(width: text = "26rem", columns: number = 2) { Stack class("cn-navigation-menu-content cx-ov-content cx-navmenu-panel") data(slot: "navigation-menu-content", align: "start") style(w: "{$width}", cols: "{$columns}") { slot } }
@@ -1,16 +1,16 @@
1
1
  # NavPill - a landing page's floating glass bar: it stays on top as you scroll, firming up (more opaque, a shadow)
2
2
  # once the page moves. The logo on the left, links and the call to action on the right (they lay out apart).
3
- # NavPill(label: "Principal") {
3
+ # NavPill(label: "Primary") {
4
4
  # Stack class("cx-hstack") { Icon "lucide:store" Span "Storio" class("font-semibold") }
5
- # Stack class("cx-hstack") { Link "Precios" -> "/precios" LinkBtn(to: "/signup", size: "sm") { Span "Crear mi tienda" } }
5
+ # Stack class("cx-hstack") { Link "Prices" -> "/precios" LinkBtn(to: "/signup", size: "sm") { Span "Create my shop" } }
6
6
  # }
7
7
  # look "tray": a tray in the muted fill at rest that turns to glass once the page moves; the logo and NavLinks sit
8
8
  # together on the left, a Span class("cx-grow") pushes the rest right, and the call to action (class "cx-nav-cta")
9
9
  # turns primary as you scroll. On a phone NavLinks and anything with class "cx-nav-hide-sm" fold away and a Btn with
10
10
  # class "cx-nav-menu" shows instead.
11
- # NavPill(look: "tray") { Image "~/logo.svg" NavLinks { Link "Precios" -> "/pricing" } Span class("cx-grow")
12
- # Link "Entrar" -> "/login" class("cx-nav-hide-sm") LinkBtn(to: "/signup") class("cx-nav-cta") { Span "Crear mi tienda" }
13
- # Btn(variant: "ghost", size: "icon", label: "Menú", onClick: openMenu) class("cx-nav-menu") { Icon "lucide:menu" } }
11
+ # NavPill(look: "tray") { Image "~/logo.svg" NavLinks { Link "Prices" -> "/pricing" } Span class("cx-grow")
12
+ # Link "Sign in" -> "/login" class("cx-nav-hide-sm") LinkBtn(to: "/signup") class("cx-nav-cta") { Span "Create my shop" }
13
+ # Btn(variant: "ghost", size: "icon", label: "Menu", onClick: openMenu) class("cx-nav-menu") { Icon "lucide:menu" } }
14
14
  # shadcn-for-muten · part · no deps · kit.css (.cx-navpill, scroll-driven where the browser supports it).
15
- part NavPill(label: text = "Principal", look: text = "") { Stack class("cx-navpill-wrap") data(look: $look) { Nav class("cx-navpill") data(look: $look) aria(label: $label) { slot } } }
15
+ part NavPill(label: text = "Main", look: text = "") { Stack class("cx-navpill-wrap") data(look: $look) { Nav class("cx-navpill") data(look: $look, trl: $label) aria(label: $label) { slot } } }
16
16
  part NavLinks() { Stack class("cx-nav-links") { slot } }
@@ -1,4 +1,4 @@
1
- // NewAppointment (muten Custom) - the kit's 4-step «Nueva cita» (kit/structure.js) in the adaptive modal. It opens
1
+ // NewAppointment (muten Custom) - the kit's 4-step “New appointment” (kit/structure.js) in the adaptive modal. It opens
2
2
  // when `open` turns true; closing it in any way fires close, creating fires create(json {client, service, day, hour}).
3
3
  export function mount(el, inputs, on) {
4
4
  let wiz = null, was = false;
@@ -1,10 +1,10 @@
1
- # NewAppointment - «Nueva cita» in 4 steps inside the adaptive modal, built only from atoms: a Stepper under the
1
+ # NewAppointment - “New appointment” in 4 steps inside the adaptive modal, built only from atoms: a Stepper under the
2
2
  # title, the client (search + choice list), the service (choice list with price), the day (tiles) and the hour, and
3
- # the summary. «Siguiente» stays disabled until the step is answered; steps slide sideways and the height eases.
3
+ # the summary. “Next” stays disabled until the step is answered; steps slide sideways and the height eases.
4
4
  # The page owns `open` (true opens it); onClose fires however it closes, onCreate gets json {client, service, day, hour}.
5
- # NewAppointment(open: creating, clients: @clients, services: @services, hours: @slots, closed: @closedDays, note: "Le llega la confirmación por WhatsApp.", onCreate: created, onClose: closeNew)
5
+ # NewAppointment(open: creating, clients: @clients, services: @services, hours: @slots, closed: @closedDays, note: "They get the confirmation on WhatsApp.", onCreate: created, onClose: closeNew)
6
6
  # clients {name, description} · services {name, duration, price, icon} · hours ["09:00", …] · closed [0] (weekdays).
7
7
  # shadcn-for-muten · Custom (kit/structure.js) · the kit's adaptive modal.
8
- part NewAppointment(open: bool, title: text = "Nueva cita", clients: list, services: list, hours: list, closed: list, note: text = "", onCreate: action, onClose: action) {
8
+ part NewAppointment(open: bool, title: text = "New appointment", clients: list, services: list, hours: list, closed: list, note: text = "", onCreate: action, onClose: action) {
9
9
  Custom NewAppointment inputs(open: $open, title: $title, clients: $clients, services: $services, hours: $hours, closed: $closed, note: $note) on(create: $onCreate, close: $onClose) class("cx-wizard-host")
10
10
  }
@@ -1,5 +1,5 @@
1
1
  // NumberField (muten Custom) - a number with − and + inside one Nova InputGroup. inputs: value, min, max, step,
2
- // prefix (a short label inside, like «X»), unit (after the number, like «min»), label (accessible name).
2
+ // prefix (a short label inside, like “X”), unit (after the number, like “min”), label (accessible name).
3
3
  // handlers: input(value). Clamps to [min, max]; buttons, arrow keys and the wheel (while focused) step; holding a
4
4
  // button repeats. The buttons switch off at the ends.
5
5
  const ICON = { minus: '<path d="M5 12h14"/>', plus: '<path d="M5 12h14"/><path d="M12 5v14"/>' };
@@ -8,13 +8,15 @@ export function mount(el, inputs, on) {
8
8
  const num = (v, d) => { const n = Number(v); return Number.isFinite(n) ? n : d; };
9
9
  let min = num(inputs.min, -Infinity), max = num(inputs.max, Infinity), step = num(inputs.step, 1) || 1;
10
10
  const esc = (s) => String(s ?? '').replace(/[&<>"]/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;' })[c]);
11
- const btn = (k, name) => `<button type="button" data-slot="button" class="cn-button cn-button-variant-ghost cn-button-size-icon-xs group/button cx-nf-btn" data-step="${k}" aria-label="${name}">${svg(k)}</button>`;
11
+ const btn = (k, name) => `<button type="button" data-slot="button" class="cn-button cn-button-variant-ghost cn-button-size-icon-xs group/button cx-nf-btn" data-step="${k}" aria-label="${name}" data-trl="${name}">${svg(k)}</button>`;
12
12
  el.innerHTML = `<div data-slot="input-group" role="group" class="cn-input-group group/input-group cx-nf">
13
13
  ${inputs.prefix ? `<div data-slot="input-group-addon" class="cn-input-group-addon cn-input-group-addon-align-inline-start cx-nf-prefix">${esc(inputs.prefix)}</div>` : ''}
14
- <input data-slot="input-group-control" class="cn-input cn-input-group-input cx-num cx-nf-input" inputmode="decimal" aria-label="${esc(inputs.label || inputs.prefix || 'Número')}" role="spinbutton">
14
+ <input data-slot="input-group-control" class="cn-input cn-input-group-input cx-num cx-nf-input" inputmode="decimal" aria-label="${esc(inputs.label || inputs.prefix || 'Number')}" data-trl="${esc(inputs.label || inputs.prefix || 'Number')}" role="spinbutton">
15
15
  ${inputs.unit ? `<div data-slot="input-group-addon" class="cn-input-group-addon cn-input-group-addon-align-inline-end cx-nf-unit">${esc(inputs.unit)}</div>` : ''}
16
- <div data-slot="input-group-addon" class="cn-input-group-addon cn-input-group-addon-align-inline-end cx-nf-steps">${btn('minus', 'Menos')}${btn('plus', 'Más')}</div>
16
+ <div data-slot="input-group-addon" class="cn-input-group-addon cn-input-group-addon-align-inline-end cx-nf-steps">${btn('minus', 'Less')}${btn('plus', 'More')}</div>
17
17
  </div>`;
18
+ if (inputs.lang) el.setAttribute('lang', inputs.lang);
19
+ import('@muten/shadcn/registry/kit/i18n.js').then((m) => m.localizeTree(el));
18
20
  const input = el.querySelector('input'), dec = el.querySelector('[data-step="minus"]'), inc = el.querySelector('[data-step="plus"]');
19
21
  const clamp = (v) => Math.min(max, Math.max(min, v));
20
22
  const decimals = (String(step).split('.')[1] || '').length;
@@ -1,7 +1,7 @@
1
1
  # NumberField - a number you nudge: − and + inside one field, arrows (Shift = ×10), the wheel while focused, and a
2
2
  # held button that repeats. Clamped to [min, max]; the button at an end switches off. The page owns the value.
3
3
  # state { minutes = 30 : number } action setMinutes(v: number) mutates minutes { minutes.set(v) }
4
- # NumberField(value: minutes, min: 5, max: 240, step: 5, unit: "min", label: "Duración", onInput: setMinutes)
4
+ # NumberField(value: minutes, min: 5, max: 240, step: 5, unit: "min", label: "Duration", onInput: setMinutes)
5
5
  # NumberField(value: x, min: 0, max: 1920, prefix: "X", onInput: setX)
6
6
  # shadcn-for-muten · Custom · Nova (.cn-input-group) + kit.css (.cx-nf) · host JS number-field.js.
7
7
  part NumberField(value: number, min: number, max: number, step: number = 1, prefix: text = "", unit: text = "", label: text = "", onInput: action) {
@@ -1,6 +1,6 @@
1
1
  // OrderBoard / OrderCards (muten Custom) - the kit's order Board and card (kit/board.js). inputs: lanes, cards, sizes;
2
2
  // handlers change(json [{id, lane}]) · action(json {id, action}).
3
3
  export function mount(el, inputs, on) {
4
- Promise.all([import('@muten/shadcn/registry/kit/core.js'), import('@muten/shadcn/registry/kit/board.js')]).then(([core, b]) => b.orderBoard(el, inputs, core, on));
4
+ Promise.all([import('@muten/shadcn/registry/kit/core.js'), import('@muten/shadcn/registry/kit/board.js')]).then(([core, b]) => core.relang(el, inputs, () => b.orderBoard(el, inputs, core, on)));
5
5
  return () => {};
6
6
  }
@@ -1,8 +1,8 @@
1
1
  # OrderBoard - lanes and cards are data. Each card groups its information by what it is about and orders it by what
2
2
  # needs you first: who and how much (the contact channel they asked for on the right), what waits for your decision
3
3
  # (tonal icon Buttons with their Indicator; tapping opens it in the modal), the products, the payment (total, short
4
- # state, and the button that fits: «Revisar comprobante», «Ver recibo de Stripe»), the delivery (time, progress,
5
- # «Ver entrega»), the note, and the one next step. Tapping a button never starts a drag. Drag a card with the mouse;
4
+ # state, and the button that fits: “Revisar comprobante”, “See Stripe receipt”), the delivery (time, progress,
5
+ # “See delivery”), the note, and the one next step. Tapping a button never starts a drag. Drag a card with the mouse;
6
6
  # on a phone hold it a moment. Drag the board's background (or swipe) to see the side lanes; carrying a card to the
7
7
  # edge scrolls it. Keyboard: Space lifts, arrows move, Space drops, Esc puts it back.
8
8
  # OrderBoard(lanes: @lanes, cards: @orders, onChange: moved, onAction: act)
@@ -1,10 +1,10 @@
1
1
  # OrderPlay - an order shown, not used: who ordered, the products with their photos, how it is paid and delivered,
2
2
  # and the one next step. In a loop the step is pressed and the order moves on (its badge and the button change).
3
3
  # BookingPlay's twin, for a landing's bento: the same sheet, the same rhythm. The real board is OrderBoard.
4
- # OrderPlay(who: "Diego Paredes", sub: "#1046 · hace 2 min", status: "Nuevo", statusNext: "Preparando",
5
- # pay: "Efectivo al retirar", total: "USD 32", delivery: "Retira hoy · 6:00 PM", cta: "Preparar",
6
- # ctaNext: "Avisar que está listo", label: "Un pedido como te llega") {
7
- # OrderPlayItem(name: "Pomada mate", detail: "1 × USD 14", price: "USD 14", photo: "/img/pomada.webp")
4
+ # OrderPlay(who: "Diego Paredes", sub: "#1046 · hace 2 min", status: "New", statusNext: "Preparing",
5
+ # pay: "Cash on pickup", total: "USD 32", delivery: "Pickup today · 6:00 PM", cta: "Prepare",
6
+ # ctaNext: "Let them know it is ready", label: "An order as it reaches you") {
7
+ # OrderPlayItem(name: "Matte pomade", detail: "1 × USD 14", price: "USD 14", photo: "/img/pomada.webp")
8
8
  # }
9
9
  # photo: the client's face (empty = their initial).
10
10
  # Reduced motion: the order as it arrives. shadcn-for-muten · part · no deps · kit.css (.cx-odp).
@@ -1,6 +1,6 @@
1
1
  // PaginationNav (muten Custom) - server pages' nav (kit/collections.js paginationNav): 7 fixed slots, range line.
2
2
  export function mount(el, inputs, on) {
3
3
  let update = null;
4
- Promise.all([import('@muten/shadcn/registry/kit/core.js'), import('@muten/shadcn/registry/kit/collections.js')]).then(([core, c]) => { update = c.paginationNav(el, inputs, core, on); });
4
+ Promise.all([import('@muten/shadcn/registry/kit/core.js'), import('@muten/shadcn/registry/kit/collections.js')]).then(([core, c]) => { update = core.relang(el, inputs, () => (update = c.paginationNav(el, inputs, core, on))); });
5
5
  return (next) => update?.(next);
6
6
  }
@@ -1,8 +1,8 @@
1
1
  # PaginationNav - pages that come from the server: always 7 slots once there are more than 7 pages (nothing slides),
2
- # «Página n de N» on a phone, and «41–50 de 237 clientes». The page owns the page number and loads the rows; while it
2
+ # “Page n of N” on a phone, and “41–50 of 237 customers”. The page owns the page number and loads the rows; while it
3
3
  # loads, give the list class("cx-pg-list") data(loading: busy) so the old page dims in place (nothing changes size).
4
- # PaginationNav(total: 237, per: 10, page: page, noun: "clientes", onPage: loadPage)
4
+ # PaginationNav(total: 237, per: 10, page: page, noun: "customers", onPage: loadPage)
5
5
  # shadcn-for-muten · Custom (kit collections) · host JS pagination-nav.js.
6
- part PaginationNav(total: number, per: number = 10, page: number, noun: text = "resultados", onPage: action) {
6
+ part PaginationNav(total: number, per: number = 10, page: number, noun: text = "results", onPage: action) {
7
7
  Custom PaginationNav inputs(total: $total, per: $per, page: $page, noun: $noun) on(page: $onPage) class("cx-pgnav-host")
8
8
  }
@@ -1,12 +1,12 @@
1
1
  # Pagination (links) - pages that are routes of their own (a blog, a catalog that can be shared by URL): previous,
2
- # the numbers with the current one outlined, «…», next. For pages loaded from the server inside one screen use
2
+ # the numbers with the current one outlined, “…”, next. For pages loaded from the server inside one screen use
3
3
  # PaginationNav.
4
4
  # Pagination { PaginationPrevious(to: "/blog/1") PaginationLink(label: "1", to: "/blog/1", page: 1, current: 2) PaginationLink(label: "2", to: "/blog/2", page: 2, current: 2) PaginationEllipsis() PaginationNext(to: "/blog/3") }
5
5
  # shadcn-for-muten · parts · no deps · Nova (.cn-pagination-*).
6
- part Pagination(label: text = "Páginas") { Nav class("cn-pagination cx-pagination-links") data(slot: "pagination") aria(label: $label) { Stack class("cn-pagination-content cx-pl-row") data(slot: "pagination-content") { slot } } }
6
+ part Pagination(label: text = "Pages") { Nav class("cn-pagination cx-pagination-links") data(slot: "pagination", trl: $label) aria(label: $label) { Stack class("cn-pagination-content cx-pl-row") data(slot: "pagination-content") { slot } } }
7
7
  part PaginationLink(label: text, to: text, page: number, current: number) {
8
8
  Link "{$label}" -> "{$to}" class("cn-button cn-button-size-icon group/button cx-pl-num", "cn-button-variant-outline" when $page == $current, "cn-button-variant-ghost" when $page != $current) data(slot: "pagination-link") aria(current: $page == $current)
9
9
  }
10
- part PaginationPrevious(to: text, label: text = "Anterior") { Link -> "{$to}" class("cn-button cn-button-variant-ghost cn-button-size-default group/button cn-pagination-previous") data(slot: "pagination-previous") { Icon "lucide:chevron-left" Span "{$label}" class("cn-pagination-previous-text") } }
11
- part PaginationNext(to: text, label: text = "Siguiente") { Link -> "{$to}" class("cn-button cn-button-variant-ghost cn-button-size-default group/button cn-pagination-next") data(slot: "pagination-next") { Span "{$label}" class("cn-pagination-next-text") Icon "lucide:chevron-right" } }
10
+ part PaginationPrevious(to: text, label: text = "Previous") { Link -> "{$to}" class("cn-button cn-button-variant-ghost cn-button-size-default group/button cn-pagination-previous") data(slot: "pagination-previous") { Icon "lucide:chevron-left" Span "{$label}" class("cn-pagination-previous-text") data(tr: $label) } }
11
+ part PaginationNext(to: text, label: text = "Next") { Link -> "{$to}" class("cn-button cn-button-variant-ghost cn-button-size-default group/button cn-pagination-next") data(slot: "pagination-next") { Span "{$label}" class("cn-pagination-next-text") data(tr: $label) Icon "lucide:chevron-right" } }
12
12
  part PaginationEllipsis() { Stack class("cn-pagination-ellipsis cx-pl-more") data(slot: "pagination-ellipsis") aria(hidden: "true") { Icon "lucide:ellipsis" } }
@@ -1,3 +1,3 @@
1
- # PasswordField - a password with show/hide and its strength. PasswordField(label: "Contraseña nueva")
1
+ # PasswordField - a password with show/hide and its strength. PasswordField(label: "New password")
2
2
  # shadcn-for-muten · part over KitWidget (kit core).
3
- part PasswordField(label: text = "Contraseña") { Custom KitWidget inputs(name: "password", label: $label) class("cx-pwd-host") }
3
+ part PasswordField(label: text = "Password") { Custom KitWidget inputs(name: "password", label: $label) class("cx-pwd-host") }
@@ -2,16 +2,16 @@
2
2
  # role locked (always allowed). A real table (muten's Table / Row / Cell): the header names line up over their column and
3
3
  # every checkbox sits in the middle of its cell. On a phone the columns narrow; the table scrolls sideways if it must.
4
4
  # The grants are a list of "permission:role" keys, so the page flips one with `toggle`:
5
- # state { granted = ["Ver:Editor", "Ver:Lector", "Editar:Editor"] : list<text> }
5
+ # state { granted = ["View:Editor", "View:Viewer", "Edit:Editor"] : list<text> }
6
6
  # action flip(key: text) mutates granted { granted.toggle(key) }
7
- # PermissionMatrix(roles: @roles, rows: @permissions, granted: granted, locked: "Propietario", onToggle: flip)
7
+ # PermissionMatrix(roles: @roles, rows: @permissions, granted: granted, locked: "Owner", onToggle: flip)
8
8
  # roles {name} · rows {name, description}. label names the first column.
9
9
  # shadcn-for-muten · part · no deps · kit.css (.cx-perm).
10
- part PermissionMatrix(roles: list, rows: list, granted: list, locked: text = "", label: text = "Permiso", onToggle: action) {
10
+ part PermissionMatrix(roles: list, rows: list, granted: list, locked: text = "", label: text = "Permission", onToggle: action) {
11
11
  Stack class("cx-dt-scroll cx-scroll cx-perms") {
12
12
  Table class("cx-dt cx-perm") {
13
13
  Row head {
14
- Cell "{$label}"
14
+ Cell "{$label}" data(tr: $label)
15
15
  each $roles as role { Cell "{role.name}" class("cx-perm-c") }
16
16
  }
17
17
  each $rows as perm {
@@ -1,17 +1,17 @@
1
1
  # PhoneBar - the app's bar at the bottom of a phone, the shape Storio's app uses: three fixed cells. The left one holds
2
- # the menu (or a back), the middle one the screen's tools as grey icons, the right one the screen's «create» (black),
2
+ # the menu (or a back), the middle one the screen's tools as grey icons, the right one the screen's “create” (black),
3
3
  # or nothing. A screen whose buttons live in its own toolbar on a desktop hands them here on a phone (AgendaLive with
4
4
  # phone: "nav" does), so the page stays clean and the hand finds them in the same place on every screen.
5
- # PhoneBar(label: "Agenda") {
6
- # PhoneBarCell(side: "left") { PhoneBarButton(label: "Menú", onClick: openMenu) { Icon "lucide:menu" } }
7
- # PhoneBarCell() { PhoneBarButton(label: "Día anterior", onClick: prev) { Icon "lucide:chevron-left" } … }
8
- # PhoneBarCell(side: "right") { PhoneBarButton(label: "Nueva cita", look: "primary", onClick: add) { Icon "lucide:plus" } }
5
+ # PhoneBar(label: "Schedule") {
6
+ # PhoneBarCell(side: "left") { PhoneBarButton(label: "Menu", onClick: openMenu) { Icon "lucide:menu" } }
7
+ # PhoneBarCell() { PhoneBarButton(label: "Previous day", onClick: prev) { Icon "lucide:chevron-left" } … }
8
+ # PhoneBarCell(side: "right") { PhoneBarButton(label: "New appointment", look: "primary", onClick: add) { Icon "lucide:plus" } }
9
9
  # }
10
10
  # place: "" (fixed to the bottom of the screen, only below 64rem: a desktop keeps its toolbars) · "inline" (where it
11
11
  # sits, always) · "frame" (the bottom of its positioned box: a phone mockup). The page's scroll keeps room for it with
12
12
  # padding-bottom: var(--cx-pbar-space). Every button needs its label: icon-only on screen, named for a screen reader.
13
13
  # shadcn-for-muten · part · no deps · kit.css (.cx-pbar).
14
- part PhoneBar(label: text = "Acciones", place: text = "") { Nav class("cx-pbar") data(place: $place) aria(label: $label) { slot } }
14
+ part PhoneBar(label: text = "Actions", place: text = "") { Nav class("cx-pbar") data(place: $place, trl: $label) aria(label: $label) { slot } }
15
15
  part PhoneBarCell(side: text = "center") { Stack class("cx-pbar-cell") data(side: $side) { slot } }
16
16
  part PhoneBarButton(label: text = "", onClick: action, look: text = "", current: bool = false) {
17
17
  Button -> $onClick class("cx-pbar-btn") data(look: $look) aria(label: $label, pressed: $current) { slot }
@@ -1,5 +1,5 @@
1
1
  // PhoneField (muten Custom) - a phone with its country (kit/values.js phoneField).
2
2
  export function mount(el, inputs, on) {
3
- Promise.all([import('@muten/shadcn/registry/kit/core.js'), import('@muten/shadcn/registry/kit/values.js')]).then(([core, v]) => v.phoneField(el, inputs, core, on));
3
+ Promise.all([import('@muten/shadcn/registry/kit/core.js'), import('@muten/shadcn/registry/kit/values.js')]).then(([core, v]) => core.relang(el, inputs, () => v.phoneField(el, inputs, core, on)));
4
4
  return () => {};
5
5
  }
@@ -2,6 +2,6 @@
2
2
  # leaving the field, and what is stored (E.164). onChange gets {e164, valid, country}.
3
3
  # PhoneField(label: "WhatsApp", value: "3001234567", country: "CO", onChange: setPhone)
4
4
  # shadcn-for-muten · Custom (kit values) · host JS phone-field.js.
5
- part PhoneField(label: text = "Teléfono", value: text = "", country: text = "CO", onChange: action) {
5
+ part PhoneField(label: text = "Phone", value: text = "", country: text = "CO", onChange: action) {
6
6
  Custom PhoneField inputs(label: $label, value: $value, country: $country) on(change: $onChange) class("cx-ph-host")
7
7
  }
@@ -2,6 +2,6 @@
2
2
  // actions, three fixed bottom tabs with a count. tabs [{label, icon, badge}], rows [{tab, section, title, subtitle,
3
3
  // value}]; handler select(label).
4
4
  export function mount(el, inputs, on) {
5
- Promise.all([import('@muten/shadcn/registry/kit/core.js'), import('@muten/shadcn/registry/kit/structure.js')]).then(([core, s]) => s.phoneShell(el, inputs, core, on));
5
+ Promise.all([import('@muten/shadcn/registry/kit/core.js'), import('@muten/shadcn/registry/kit/structure.js')]).then(([core, s]) => core.relang(el, inputs, () => s.phoneShell(el, inputs, core, on)));
6
6
  return () => {};
7
7
  }
@@ -1,6 +1,6 @@
1
1
  # Pill - the chip over a hero title (what is new, who it is for): rounder and bigger than a Badge, with an icon and a
2
2
  # line of text inside. tone "brand" tints it with the skin's --selected.
3
- # Pill { Icon "lucide:sparkles" Span "Nuevo: cobra con tarjeta" }
4
- # Pill(tone: "brand") { Span "Para barberías y salones" }
3
+ # Pill { Icon "lucide:sparkles" Span "New: charge by card" }
4
+ # Pill(tone: "brand") { Span "For barbershops and salons" }
5
5
  # shadcn-for-muten · part · no deps · kit.css (.cx-pill).
6
6
  part Pill(tone: text = "") { Stack class("cx-pill") data(slot: "pill", tone: $tone) { slot } }
@@ -1,4 +1,4 @@
1
1
  # Plans - plan cards with a monthly/yearly switch; the current plan is marked; going up is one tap, going down asks.
2
- # Plans(plans: @plans, note: "2 meses gratis") plans: {id, name, price {month, year}, desc, features, current}
2
+ # Plans(plans: @plans, note: "2 months free") plans: {id, name, price {month, year}, desc, features, current}
3
3
  # shadcn-for-muten · part over KitWidget (kit core).
4
4
  part Plans(plans: list, note: text = "") { Custom KitWidget inputs(name: "plans", plans: $plans, note: $note) class("cx-plans-host") }
@@ -2,7 +2,7 @@
2
2
  # content is anything: a small form, a filter, a detail. Trigger and content go inside, as in shadcn:
3
3
  # Popover {
4
4
  # PopoverTrigger { TriggerBtn { Span "Filtrar" } }
5
- # PopoverContent(align: "start") { PopoverHeader(title: "Filtrar", description: "Lo que ves en la lista.") … }
5
+ # PopoverContent(align: "start") { PopoverHeader(title: "Filtrar", description: "What you see in the list.") … }
6
6
  # }
7
7
  # PopoverClose is a button inside the content that closes it. align: start | center | end.
8
8
  # shadcn-for-muten · parts + Custom (OverlayBehavior) · Nova (.cn-popover-*) + kit.css (.cx-ov).
@@ -15,4 +15,4 @@ part PopoverHeader(title: text, description: text = "") {
15
15
  Text "{$description}" class("cn-popover-description") data(slot: "popover-description")
16
16
  }
17
17
  }
18
- part PopoverClose(label: text = "Listo", variant: text = "default") { Button "{$label}" class("cn-button cn-button-variant-{$variant} cn-button-size-sm group/button") data(ovclose: "true") }
18
+ part PopoverClose(label: text = "Done", variant: text = "default") { Button "{$label}" class("cn-button cn-button-variant-{$variant} cn-button-size-sm group/button") data(ovclose: "true", tr: $label) }
@@ -1,15 +1,15 @@
1
1
  # Pricing - the plans of a landing page side by side: name, the line under it, the price and its period, what it
2
2
  # includes (PricingFeature) and its button. featured "on" raises the recommended one, with its badge.
3
3
  # Pricing {
4
- # PricingTier(name: "Gratis", price: "$0", period: "/mes", description: "Para empezar") { PricingFeature(label: "Tu tienda") LinkBtn(to: "/signup", variant: "outline") { Span "Empezar" } }
5
- # PricingTier(name: "Pro", price: "$12", period: "/mes", description: "Para crecer", featured: "on") { … }
4
+ # PricingTier(name: "Free", price: "$0", period: "/mes", description: "To start") { PricingFeature(label: "Your shop") LinkBtn(to: "/signup", variant: "outline") { Span "Start" } }
5
+ # PricingTier(name: "Pro", price: "$12", period: "/mes", description: "To grow", featured: "on") { … }
6
6
  # }
7
7
  # was: the old price, struck (a yearly discount) · note: how it is billed · lead: the line over the features.
8
8
  # For the plans inside the app (monthly/yearly, current plan) use Plans. shadcn-for-muten · parts · no deps · kit.css (.cx-pricing).
9
9
  part Pricing() { Stack class("cx-pricing") { slot } }
10
- part PricingTier(name: text, price: text, period: text = "", description: text = "", featured: text = "", badge: text = "Recomendado", was: text = "", note: text = "", lead: text = "") {
10
+ part PricingTier(name: text, price: text, period: text = "", description: text = "", featured: text = "", badge: text = "Recommended", was: text = "", note: text = "", lead: text = "") {
11
11
  Stack class("cx-tier") data(featured: $featured) {
12
- when $featured != "" { Span "{$badge}" class("cx-tier-badge") }
12
+ when $featured != "" { Span "{$badge}" class("cx-tier-badge") data(tr: $badge) }
13
13
  Stack class("cx-tier-head") { Span "{$name}" class("cx-tier-name") Text "{$description}" class("cx-tier-desc") }
14
14
  Stack class("cx-tier-price") {
15
15
  Span "{$price}" class("cx-tier-amount") Span "{$period}" class("cx-tier-period")
@@ -1,7 +1,7 @@
1
1
  # ProductPlay - a product sheet shown, not used: the photo, the shop, the name with its price, a line about it, its
2
- # options (one picked) and the quantity beside «Agregar». For a landing; a picture (aria-hidden), `label` says what.
3
- # ProductPlay(photo: "/pomade.webp", shop: "Bruma Barbería", name: "Pomada mate", price: "USD 14",
4
- # text: "Fijación media.", cta: "Agregar · USD 14", label: "…") {
2
+ # options (one picked) and the quantity beside “Add”. For a landing; a picture (aria-hidden), `label` says what.
3
+ # ProductPlay(photo: "/pomade.webp", shop: "Bruma Barbershop", name: "Matte pomade", price: "USD 14",
4
+ # text: "Medium hold.", cta: "Add · USD 14", label: "…") {
5
5
  # ProductPlayOption(label: "50 g") ProductPlayOption(label: "100 g", on: "yes")
6
6
  # }
7
7
  # shadcn-for-muten · part · no deps · kit.css (.cx-pdp).
@@ -1,10 +1,10 @@
1
- # Progress - a determinate bar, value 0-100; it eases between values. Progress(value: done, label: "Pasos hechos")
1
+ # Progress - a determinate bar, value 0-100; it eases between values. Progress(value: done, label: "Steps done")
2
2
  # tone: the bar's colour from the series palette: brand (the skin's accent) | coral | teal | amber | blue | pink. Empty: the
3
3
  # primary colour. A row of channels or a ranking is a Progress per row, each in its tone.
4
4
  # Progress(value: 46, tone: "coral", label: "Instagram")
5
5
  # shadcn-for-muten · part · no deps · Nova (.cn-progress) + kit.css (the value rides --w).
6
- part Progress(value: number, label: text = "Progreso", tone: text = "") {
7
- Stack class("cn-progress") data(slot: "progress", tone: $tone) aria(role: "progressbar", label: $label, valuemin: 0, valuemax: 100, valuenow: $value) {
6
+ part Progress(value: number, label: text = "Progress", tone: text = "") {
7
+ Stack class("cn-progress") data(slot: "progress", tone: $tone, trl: $label) aria(role: "progressbar", label: $label, valuemin: 0, valuemax: 100, valuenow: $value) {
8
8
  Stack class("cn-progress-indicator") data(slot: "progress-indicator") style(w: "{$value}%")
9
9
  }
10
10
  }
@@ -1,3 +1,3 @@
1
- # Queue - a waitlist: offer a freed slot to the first in line, with a countdown. Queue(title: "Lista de espera · sábado", people: @waiting)
1
+ # Queue - a waitlist: offer a freed slot to the first in line, with a countdown. Queue(title: "Waitlist · Saturday", people: @waiting)
2
2
  # shadcn-for-muten · part over KitWidget (kit core).
3
3
  part Queue(title: text, people: list, minutes: number = 10) { Custom KitWidget inputs(name: "queue", title: $title, people: $people, minutes: $minutes) class("cx-queue-host") }
@@ -2,7 +2,7 @@
2
2
  # choice needs one. A real radio group: arrows move and pick, only the chosen one is in the Tab order. The page
3
3
  # owns the value:
4
4
  # state { plan = "pro" : text } action setPlan(v: text) mutates plan { plan.set(v) }
5
- # RadioGroup(label: "Plan") { RadioGroupItem(value: "free", current: plan, label: "Gratis", onSelect: setPlan) … }
5
+ # RadioGroup(label: "Plan") { RadioGroupItem(value: "free", current: plan, label: "Free", onSelect: setPlan) … }
6
6
  # For choices that carry more (a price, a duration) use ChoiceList. orientation: vertical | horizontal.
7
7
  # shadcn-for-muten · part + Custom (RovingBehavior) · Nova (.cn-radio-group) + kit.css (checked state).
8
8
  part RadioGroup(label: text = "", orientation: text = "vertical") {
@@ -1,8 +1,8 @@
1
- # RankItem - one row of a ranking or a short list of things to act on: a lead (the rank «1», or a count «13»), the title
1
+ # RankItem - one row of a ranking or a short list of things to act on: a lead (the rank “1”, or a count “13”), the title
2
2
  # with an optional tag, a line under it, an optional bar for its share, and the value on the right. Whatever goes in
3
- # the slot sits at the end of the row (an action: «Agregar», «Responder»).
4
- # RankItem(lead: "1", title: "Corte clásico", tag: "Servicio", sub: "38 veces", value: "USD 760", share: 100)
5
- # RankItem(lead: "13", title: "«tinte»", sub: "No está entre tus servicios") { Btn(variant: "outline", size: "sm", onClick: addService) { Span "Agregar" } }
3
+ # the slot sits at the end of the row (an action: “Add”, “Reply”).
4
+ # RankItem(lead: "1", title: "Classic cut", tag: "Service", sub: "38 times", value: "USD 760", share: 100)
5
+ # RankItem(lead: "13", title: "“dye”", sub: "Not among your services") { Btn(variant: "outline", size: "sm", onClick: addService) { Span "Add" } }
6
6
  # share: 0-100 draws a Progress under the title (leave it at -1 for none). tone: the bar's colour (series palette).
7
7
  # busy: true keeps the row's shape with a skeleton.
8
8
  # shadcn-for-muten · part · no deps · kit.css (.cx-rank) + Item, Badge, Progress.
@@ -10,7 +10,7 @@ part ResizablePanelGroup(orientation: text = "horizontal", label: text = "") {
10
10
  }
11
11
  part ResizablePanel(size: number = 50, min: number = 15) { Stack class("cx-rz-panel") data(slot: "resizable-panel", size: $size, min: $min) { slot } }
12
12
  part ResizableHandle(grip: text = "") {
13
- Stack class("cn-resizable-handle cx-rz-handle") data(slot: "resizable-handle", grip: $grip) aria(role: "separator", label: "Cambiar el tamaño") {
13
+ Stack class("cn-resizable-handle cx-rz-handle") data(slot: "resizable-handle", grip: $grip, trl: "Resize") aria(role: "separator", label: "Resize") {
14
14
  when $grip != "" { Span "" class("cn-resizable-handle-icon") }
15
15
  }
16
16
  }
@@ -1,5 +1,5 @@
1
1
  // RowMenu (muten Custom) - the kit's rowMenu (kit/menus.js).
2
2
  export function mount(el, inputs, on) {
3
- Promise.all([import('@muten/shadcn/registry/kit/core.js'), import('@muten/shadcn/registry/kit/menus.js')]).then(([core, m]) => m.rowMenu(el, inputs, core, on));
3
+ Promise.all([import('@muten/shadcn/registry/kit/core.js'), import('@muten/shadcn/registry/kit/menus.js')]).then(([core, m]) => core.relang(el, inputs, () => m.rowMenu(el, inputs, core, on)));
4
4
  return () => {};
5
5
  }
@@ -1,5 +1,5 @@
1
1
  # RowMenu - the ⋯ of a row: a menu with arrows, Home/End, type-ahead, Esc back to the button. Put it in ItemActions.
2
- # RowMenu(label: "Opciones de Corte clásico", items: @rowActions, onPick: act)
2
+ # RowMenu(label: "Classic cut options", items: @rowActions, onPick: act)
3
3
  # items {id, label, icon (a kit icon id), danger, separator (a line before it)}
4
4
  # shadcn-for-muten · Custom (kit menus) · host JS row-menu.js.
5
- part RowMenu(label: text = "Más opciones", items: list, onPick: action) { Custom RowMenu inputs(label: $label, items: $items) on(pick: $onPick) class("cx-rowmenu-host") }
5
+ part RowMenu(label: text = "More options", items: list, onPick: action) { Custom RowMenu inputs(label: $label, items: $items) on(pick: $onPick) class("cx-rowmenu-host") }
@@ -1,11 +1,11 @@
1
1
  # SaveButton - saving takes three moments and the button keeps its width through them, so nothing jumps:
2
- # idle («Guardar cambios») → saving (spinner, «Guardando…») → saved (check, «Guardado», green). The page owns status.
2
+ # idle (“Save changes”) → saving (spinner, “Saving…”) → saved (check, “Saved”, green). The page owns status.
3
3
  # SaveButton(status: saveState, onClick: save) status: idle | saving | saved
4
4
  # shadcn-for-muten · part · no deps · kit.css (.cx-save).
5
- part SaveButton(status: text, onClick: action, label: text = "Guardar cambios") {
5
+ part SaveButton(status: text, onClick: action, label: text = "Save changes") {
6
6
  Button -> $onClick class("cn-button cn-button-variant-default cn-button-size-default group/button cx-save") data(slot: "button", status: $status) {
7
- when $status == "saving" { Stack class("cx-btn-label") { Icon "lucide:loader-circle" class("animate-spin") Span "Guardando…" } }
8
- when $status == "saved" { Stack class("cx-btn-label") { Icon "lucide:check" Span "Guardado" } }
9
- when $status != "saving" and $status != "saved" { Stack class("cx-btn-label") { Span "{$label}" } }
7
+ when $status == "saving" { Stack class("cx-btn-label") { Icon "lucide:loader-circle" class("animate-spin") Span "Saving…" data(tr: "Saving…") } }
8
+ when $status == "saved" { Stack class("cx-btn-label") { Icon "lucide:check" Span "Saved" data(tr: "Saved") } }
9
+ when $status != "saving" and $status != "saved" { Stack class("cx-btn-label") { Span "{$label}" data(tr: $label) } }
10
10
  }
11
11
  }
@@ -2,6 +2,6 @@
2
2
  export function mount(el, inputs, on) {
3
3
  el.dataset.bubble = inputs.bubble || 'always'; el.dataset.ends = inputs.ends || 'on'; // optional value bubble and end labels (kit.css)
4
4
  let update = null;
5
- Promise.all([import('@muten/shadcn/registry/kit/core.js'), import('@muten/shadcn/registry/kit/values.js')]).then(([core, v]) => { update = v.scaleSlider(el, inputs, core, on); });
5
+ Promise.all([import('@muten/shadcn/registry/kit/core.js'), import('@muten/shadcn/registry/kit/values.js')]).then(([core, v]) => { update = core.relang(el, inputs, () => (update = v.scaleSlider(el, inputs, core, on))); });
6
6
  return (next) => update?.(next);
7
7
  }
@@ -1,7 +1,7 @@
1
1
  # ScaleSlider - one or two thumbs; the value rides on each thumb and two bubbles that would touch merge into one;
2
2
  # the ends are written under it. Keys: arrows, Page Up/Down, Home/End. onChange gets "24" or "20000,80000".
3
- # ScaleSlider(label: "Recordatorio antes de la cita", min: 1, max: 72, values: "24", format: "hours", onChange: setHours)
4
- # ScaleSlider(label: "Precio", min: 0, max: 150000, step: 5000, values: "20000,80000", gap: 5000, format: "money", onChange: setPrice)
3
+ # ScaleSlider(label: "Reminder before the appointment", min: 1, max: 72, values: "24", format: "hours", onChange: setHours)
4
+ # ScaleSlider(label: "Price", min: 0, max: 150000, step: 5000, values: "20000,80000", gap: 5000, format: "money", onChange: setPrice)
5
5
  # bubble: always | drag (only while moving or focused) | off · ends: on | off (the min and max written under it).
6
6
  # format: hours | money | percent | "" (+ suffix). shadcn-for-muten · Custom (kit values) · host JS scale-slider.js.
7
7
  part ScaleSlider(label: text, min: number = 0, max: number = 100, step: number = 1, values: text, gap: number = 0, format: text = "", suffix: text = "", bubble: text = "always", ends: text = "on", onChange: action) {
@@ -2,7 +2,7 @@
2
2
  # that shows each step's capture, a tap where the finger would land, and at the end the sheet that confirms it. It
3
3
  # moves on by itself while it is on screen (each step's bar fills for its time); touching a step goes to it and stays.
4
4
  # Several tracks (book · order) get the library's tabs on top. Reduced motion: the first step, still; steps still work.
5
- # ScreenTour(tracks: tour, label: "Así te reservan") tour = a getter or a state list
5
+ # ScreenTour(tracks: tour, label: "This is how they book you") tour = a getter or a state list
6
6
  # tracks [{id, label, steps: [{title, text, shot, tap: [x%, y%], ms, scroll (% of the capture's height), done}],
7
7
  # done: {title, rows: [[label, value]], code, codeLabel, note}}]
8
8
  # shot: an image url (a long capture can scroll: give the first step `scroll`). A step with done: true shows the sheet.
@@ -1,8 +1,8 @@
1
1
  # ShareBar - how one whole splits: a bar at 100 % cut into its parts, each in its tone, and under it each part's value
2
2
  # and name. Citas against pedidos, new clients against returning ones. All parts at 0 leave the empty track. busy: true
3
3
  # shows the bar and the values as a skeleton while they load.
4
- # ShareBar(parts: split, label: "Ganado por tipo")
5
- # parts {label, value (a number: the part's weight), text (what is written: "USD 3.240", "28"), tone}
4
+ # ShareBar(parts: split, label: "Earned by type")
5
+ # parts {label, value (a number: the part's weight), text (what is written: "USD 3,240", "28"), tone}
6
6
  # tone: brand | coral | teal | amber | blue | pink (the series palette; Chart and StackedBars use the same colours).
7
7
  # shadcn-for-muten · part · no deps · kit.css (.cx-split).
8
8
  part ShareBar(parts: list, label: text = "", busy: bool = false) {
@@ -1,10 +1,10 @@
1
1
  # ShareButton - Storio's share sheet made generic, inside the adaptive modal (Dialog wide, Drawer on a phone). On top
2
2
  # what is shared: the link's card (initials, name, address) or the product's (image, price in the corner, name over
3
3
  # it); then the link with Copiar and a row of brand targets you can drag sideways. QR opens the code in the same
4
- # sheet; if the phone has its own share menu, «Compartir en otra app» appears.
5
- # ShareButton(label: "Compartir mi tienda", name: "Barbería El Roble", initials: "ER", url: "storio.ink/el-roble")
6
- # ShareButton(label: "Compartir un producto", variant: "outline", kind: "product", name: "Barbería El Roble", url: "storio.ink/el-roble/p/pomada-mate", productName: "Pomada mate", productMeta: "Fijación media · 100 g", productPrice: 32000, productImage: "linear-gradient(135deg,#c79a6b,#6b4a2e)")
4
+ # sheet; if the phone has its own share menu, “Compartir en otra app” appears.
5
+ # ShareButton(label: "Share my shop", name: "El Roble Barbershop", initials: "ER", url: "storio.ink/el-roble")
6
+ # ShareButton(label: "Share a product", variant: "outline", kind: "product", name: "El Roble Barbershop", url: "storio.ink/el-roble/p/pomada-mate", productName: "Matte pomade", productMeta: "Medium hold · 100 g", productPrice: 32000, productImage: "linear-gradient(135deg,#c79a6b,#6b4a2e)")
7
7
  # shadcn-for-muten · Custom (kit/structure.js) · kit.css (.cx-sh-*).
8
- part ShareButton(label: text = "Compartir", variant: text = "default", kind: text = "link", name: text, initials: text = "", url: text, text: text = "", productName: text = "", productMeta: text = "", productPrice: number = 0, productImage: text = "") {
8
+ part ShareButton(label: text = "Share", variant: text = "default", kind: text = "link", name: text, initials: text = "", url: text, text: text = "", productName: text = "", productMeta: text = "", productPrice: number = 0, productImage: text = "") {
9
9
  Custom ShareButton inputs(label: $label, variant: $variant, kind: $kind, name: $name, initials: $initials, url: $url, text: $text, productName: $productName, productMeta: $productMeta, productPrice: $productPrice, productImage: $productImage) class("cx-share-host")
10
10
  }
@@ -1,6 +1,6 @@
1
1
  # Sheet - a panel that slides from a side and keeps the page behind it in view: a detail, filters, a cart. Full
2
2
  # height from the right (default) or left, full width from the top or bottom. The page owns open.
3
- # Sheet(open: showing, onClose: done, title: "Pedido #1042", side: "right") { … ModalFooter { … } }
3
+ # Sheet(open: showing, onClose: done, title: "Order #1042", side: "right") { … ModalFooter { … } }
4
4
  # shadcn-for-muten · part over Modal (mode sheet).
5
5
  part Sheet(open: bool, onClose: action, title: text, description: text = "", side: text = "right") {
6
6
  Modal(open: $open, onClose: $onClose, title: $title, description: $description, mode: "sheet", side: $side) { slot }
@@ -3,15 +3,21 @@
3
3
  # whether it is folded. (AppShell is the whole frame - sidebar, header, sheet, ⌘K - built from data.)
4
4
  # AppSidebar(collapsed: folded) {
5
5
  # SidebarHeader { … }
6
- # SidebarGroup(label: "Principal") { SidebarItem(label: "Agenda", value: "agenda", current: place, count: "14", onSelect: go) { Icon "lucide:calendar" } }
6
+ # SidebarGroup(label: "Primary") { SidebarItem(label: "Schedule", value: "agenda", current: place, count: "14", onSelect: go) { Icon "lucide:calendar" } }
7
7
  # SidebarFooter { … }
8
8
  # }
9
- # SidebarLink is the same item as a link to a route: the app marks where you are by itself (no `current` to keep).
10
- # SidebarLink(label: "Citas", to: "/schedule", count: "6") { Icon "lucide:calendar-days" }
9
+ # SidebarLink is the same item as a link to a route: the app marks where you are by itself (no `current` to keep), also
10
+ # below it (on /schedule/42 the “Appointments” link stays marked). `match` lists other pages it stands for ("/a /b").
11
+ # SidebarLink(label: "Appointments", to: "/schedule", count: "6") { Icon "lucide:calendar-days" }
11
12
  # The name AppSidebar avoids muten's own Sidebar primitive. shadcn-for-muten · parts · no deps · Nova (.cn-sidebar-*) + kit.css.
12
- part AppSidebar(collapsed: bool, label: text = "Menú") {
13
+ part AppSidebar(collapsed: bool, label: text = "Menu") {
13
14
  Nav class("cx-sidebar") data(slot: "sidebar", collapsed: $collapsed) aria(label: $label) { Stack class("cn-sidebar-inner cx-sb-inner") data(slot: "sidebar-inner") { slot } }
14
15
  }
16
+ # SidebarTrigger - the fold toggle (shadcn's SidebarTrigger): an icon button that lives in the icon column, so it is
17
+ # there folded or open. The page owns the state. SidebarHeader { SidebarTrigger(collapsed: folded, onToggle: toggleFold) }
18
+ part SidebarTrigger(collapsed: bool, onToggle: action) {
19
+ Button -> $onToggle class("cn-button cn-button-variant-ghost cn-button-size-icon-sm group/button cx-sb-trigger") data(slot: "sidebar-trigger", name: "Show or hide the menu", trl: "Show or hide the menu", trn: "Show or hide the menu") aria(label: "Show or hide the menu", expanded: not $collapsed) { Icon "lucide:panel-left" }
20
+ }
15
21
  part SidebarHeader() { Stack class("cn-sidebar-header cx-sb-head") data(slot: "sidebar-header") { slot } }
16
22
  part SidebarFooter() { Stack class("cn-sidebar-footer cx-sb-foot") data(slot: "sidebar-footer") { slot } }
17
23
  part SidebarGroup(label: text = "") {
@@ -27,8 +33,8 @@ part SidebarItem(label: text, value: text, current: text, onSelect: action, coun
27
33
  Span "{$count}" class("cn-sidebar-menu-badge cx-sb-count") data(slot: "sidebar-menu-badge")
28
34
  }
29
35
  }
30
- part SidebarLink(label: text, to: text, count: text = "") {
31
- Link -> "{$to}" class("cn-sidebar-menu-button cn-sidebar-menu-button-variant-default cn-sidebar-menu-button-size-default cx-sb-item") data(slot: "sidebar-menu-button", name: $label) {
36
+ part SidebarLink(label: text, to: text, count: text = "", match: text = "") {
37
+ Link -> "{$to}" class("cn-sidebar-menu-button cn-sidebar-menu-button-variant-default cn-sidebar-menu-button-size-default cx-sb-item") data(slot: "sidebar-menu-button", name: $label, match: $match) {
32
38
  slot
33
39
  Span "{$label}" class("cx-sb-label")
34
40
  Span "{$count}" class("cn-sidebar-menu-badge cx-sb-count") data(slot: "sidebar-menu-badge")