@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.
- package/AGENTS.md +88 -1
- package/globals.css +1 -0
- package/kit.css +329 -124
- package/motion.css +3 -0
- package/package.json +1 -1
- package/registry/accordion.muten +3 -3
- package/registry/agenda.js +19 -16
- package/registry/agenda.muten +10 -10
- package/registry/alert-dialog.muten +2 -2
- package/registry/alert.muten +1 -1
- package/registry/app-shell.muten +1 -1
- package/registry/attachment.muten +1 -1
- package/registry/avatar.muten +4 -4
- package/registry/badge.muten +14 -3
- package/registry/band.muten +2 -2
- package/registry/bar-chart.muten +2 -2
- package/registry/bio-play.muten +2 -2
- package/registry/booking-play.muten +5 -5
- package/registry/booking.muten +2 -2
- package/registry/breadcrumb.muten +4 -4
- package/registry/button.muten +9 -7
- package/registry/calendar.js +15 -10
- package/registry/calendar.muten +1 -1
- package/registry/card.muten +1 -1
- package/registry/carousel.muten +1 -1
- package/registry/chat-play.muten +8 -8
- package/registry/chatbot.muten +5 -4
- package/registry/checklist.muten +1 -1
- package/registry/choice-list.muten +3 -3
- package/registry/code.js +10 -4
- package/registry/collapsible.muten +2 -2
- package/registry/color-picker.js +37 -36
- package/registry/color-picker.muten +1 -1
- package/registry/combobox.js +1 -1
- package/registry/combobox.muten +2 -2
- package/registry/command-palette.muten +2 -2
- package/registry/command.js +1 -1
- package/registry/command.muten +7 -7
- package/registry/context-menu.muten +3 -3
- package/registry/count-up.js +33 -31
- package/registry/count-up.muten +6 -4
- package/registry/date-field.js +1 -1
- package/registry/date-field.muten +3 -3
- package/registry/day-plan.muten +5 -5
- package/registry/device-frame.muten +3 -3
- package/registry/dialog.muten +1 -1
- package/registry/dirty-bar.js +2 -2
- package/registry/dirty-bar.muten +3 -3
- package/registry/drawer.muten +1 -1
- package/registry/dropdown-menu.muten +4 -4
- package/registry/empty.muten +1 -1
- package/registry/feature-grid.muten +1 -1
- package/registry/feature-rows.muten +2 -2
- package/registry/field-behavior.js +13 -3
- package/registry/field-behavior.muten +3 -3
- package/registry/field.muten +5 -5
- package/registry/file-input.js +8 -4
- package/registry/file-input.muten +3 -3
- package/registry/fixed-card.muten +4 -4
- package/registry/folder-picker.js +6 -2
- package/registry/folder-picker.muten +2 -2
- package/registry/heatmap.muten +5 -5
- package/registry/image-upload.js +1 -1
- package/registry/image-upload.muten +2 -2
- package/registry/inbox-play.muten +5 -5
- package/registry/indicator.muten +1 -1
- package/registry/info-table.muten +1 -1
- package/registry/input-group.muten +11 -11
- package/registry/input-otp.muten +2 -2
- package/registry/input.muten +1 -1
- package/registry/inspector.muten +2 -2
- package/registry/item.muten +2 -2
- package/registry/kit/board.js +36 -34
- package/registry/kit/collections.js +24 -24
- package/registry/kit/core.js +197 -114
- package/registry/kit/dates.js +29 -27
- package/registry/kit/i18n.js +172 -0
- package/registry/kit/menus.js +14 -13
- package/registry/kit/overlays.js +7 -2
- package/registry/kit/structure.js +66 -61
- package/registry/kit/table.js +19 -17
- package/registry/kit/values.js +36 -28
- package/registry/kit-skeleton.muten +1 -1
- package/registry/kit-widget.js +9 -4
- package/registry/lang-scope.js +8 -0
- package/registry/lang-scope.muten +5 -0
- package/registry/legal-doc.muten +5 -5
- package/registry/link-btn.muten +1 -1
- package/registry/list-motion.js +46 -0
- package/registry/list-motion.muten +4 -0
- package/registry/live-stack.muten +2 -2
- package/registry/logo-cloud.muten +2 -2
- package/registry/marketing.muten +7 -7
- package/registry/member-list.muten +2 -2
- package/registry/menubar.muten +3 -3
- package/registry/message-scroller.muten +3 -3
- package/registry/message.muten +9 -9
- package/registry/messaging.muten +1 -1
- package/registry/modal.muten +4 -4
- package/registry/money-field.js +1 -1
- package/registry/money-field.muten +2 -2
- package/registry/nav-button.muten +4 -4
- package/registry/navigation-menu.muten +4 -4
- package/registry/navpill.muten +6 -6
- package/registry/new-appointment.js +1 -1
- package/registry/new-appointment.muten +4 -4
- package/registry/number-field.js +6 -4
- package/registry/number-field.muten +1 -1
- package/registry/order-board.js +1 -1
- package/registry/order-board.muten +2 -2
- package/registry/order-play.muten +4 -4
- package/registry/pagination-nav.js +1 -1
- package/registry/pagination-nav.muten +3 -3
- package/registry/pagination.muten +4 -4
- package/registry/password-field.muten +2 -2
- package/registry/permission-matrix.muten +4 -4
- package/registry/phone-bar.muten +6 -6
- package/registry/phone-field.js +1 -1
- package/registry/phone-field.muten +1 -1
- package/registry/phone-shell.js +1 -1
- package/registry/pill.muten +2 -2
- package/registry/plans.muten +1 -1
- package/registry/popover.muten +2 -2
- package/registry/pricing.muten +4 -4
- package/registry/product-play.muten +3 -3
- package/registry/progress.muten +3 -3
- package/registry/queue.muten +1 -1
- package/registry/radio-group.muten +1 -1
- package/registry/rank-item.muten +4 -4
- package/registry/resizable.muten +1 -1
- package/registry/row-menu.js +1 -1
- package/registry/row-menu.muten +2 -2
- package/registry/save-button.muten +5 -5
- package/registry/scale-slider.js +1 -1
- package/registry/scale-slider.muten +2 -2
- package/registry/screen-tour.muten +1 -1
- package/registry/share-bar.muten +2 -2
- package/registry/share-button.muten +4 -4
- package/registry/sheet.muten +1 -1
- package/registry/sidebar.muten +12 -6
- package/registry/site-footer.muten +6 -6
- package/registry/sortable-behavior.js +67 -20
- package/registry/sortable-list.js +3 -1
- package/registry/sortable-list.muten +2 -2
- package/registry/sortable.muten +5 -4
- package/registry/spinner.muten +2 -2
- package/registry/split-button.muten +2 -2
- package/registry/stacked-bars.js +3 -3
- package/registry/stacked-bars.muten +2 -2
- package/registry/stamp-card.muten +2 -2
- package/registry/stat-band.muten +3 -3
- package/registry/stat.muten +3 -3
- package/registry/status-bar.muten +2 -2
- package/registry/stepper.muten +4 -4
- package/registry/store-qr.js +1 -1
- package/registry/store-qr.muten +2 -2
- package/registry/swatch.muten +1 -1
- package/registry/switch.muten +8 -7
- package/registry/table-view.muten +3 -3
- package/registry/tabs-behavior.js +9 -1
- package/registry/tabs.muten +2 -2
- package/registry/tag-input.js +1 -1
- package/registry/tag-input.muten +2 -2
- package/registry/text.muten +2 -2
- package/registry/time-field.js +1 -1
- package/registry/time-field.muten +3 -3
- package/registry/timeline.muten +4 -4
- package/registry/toaster.muten +2 -2
- package/registry/toggle-group.muten +1 -1
- package/registry/toggle.muten +3 -3
- package/registry/toolbar.muten +4 -4
- package/registry/tooltip.muten +2 -2
- package/registry/tree-view.js +38 -25
- package/registry/tree-view.muten +1 -1
- package/registry/veil.muten +1 -1
- package/registry/virtual-list.js +1 -1
- package/registry/virtual-list.muten +2 -2
- package/registry/virtual-window.js +4 -4
- package/registry/virtual-window.muten +2 -2
- package/registry/wordline.muten +1 -1
- package/registry/zoom-control.muten +1 -1
- 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: "
|
|
4
|
-
# NavMenuItem { NavMenuTrigger(label: "
|
|
5
|
-
# NavMenuItemLink(label: "
|
|
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 = "
|
|
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 } }
|
package/registry/navpill.muten
CHANGED
|
@@ -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: "
|
|
3
|
+
# NavPill(label: "Primary") {
|
|
4
4
|
# Stack class("cx-hstack") { Icon "lucide:store" Span "Storio" class("font-semibold") }
|
|
5
|
-
# Stack class("cx-hstack") { Link "
|
|
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 "
|
|
12
|
-
# Link "
|
|
13
|
-
# Btn(variant: "ghost", size: "icon", label: "
|
|
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 = "
|
|
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
|
|
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 -
|
|
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.
|
|
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: "
|
|
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 = "
|
|
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
|
}
|
package/registry/number-field.js
CHANGED
|
@@ -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
|
|
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) => ({ '&': '&', '<': '<', '>': '>', '"': '"' })[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 || '
|
|
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', '
|
|
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: "
|
|
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) {
|
package/registry/order-board.js
CHANGED
|
@@ -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:
|
|
5
|
-
#
|
|
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: "
|
|
5
|
-
# pay: "
|
|
6
|
-
# ctaNext: "
|
|
7
|
-
# OrderPlayItem(name: "
|
|
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
|
-
#
|
|
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: "
|
|
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 = "
|
|
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,
|
|
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 = "
|
|
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 = "
|
|
11
|
-
part PaginationNext(to: text, label: text = "
|
|
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: "
|
|
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 = "
|
|
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 = ["
|
|
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: "
|
|
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 = "
|
|
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 {
|
package/registry/phone-bar.muten
CHANGED
|
@@ -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
|
|
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: "
|
|
6
|
-
# PhoneBarCell(side: "left") { PhoneBarButton(label: "
|
|
7
|
-
# PhoneBarCell() { PhoneBarButton(label: "
|
|
8
|
-
# PhoneBarCell(side: "right") { PhoneBarButton(label: "
|
|
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 = "
|
|
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 }
|
package/registry/phone-field.js
CHANGED
|
@@ -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 = "
|
|
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
|
}
|
package/registry/phone-shell.js
CHANGED
|
@@ -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
|
}
|
package/registry/pill.muten
CHANGED
|
@@ -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 "
|
|
4
|
-
# Pill(tone: "brand") { Span "
|
|
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 } }
|
package/registry/plans.muten
CHANGED
|
@@ -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
|
|
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") }
|
package/registry/popover.muten
CHANGED
|
@@ -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: "
|
|
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 = "
|
|
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) }
|
package/registry/pricing.muten
CHANGED
|
@@ -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: "
|
|
5
|
-
# PricingTier(name: "Pro", price: "$12", period: "/mes", description: "
|
|
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 = "
|
|
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
|
|
3
|
-
# ProductPlay(photo: "/pomade.webp", shop: "Bruma
|
|
4
|
-
# text: "
|
|
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).
|
package/registry/progress.muten
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
# Progress - a determinate bar, value 0-100; it eases between values. Progress(value: done, label: "
|
|
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 = "
|
|
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
|
}
|
package/registry/queue.muten
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
# Queue - a waitlist: offer a freed slot to the first in line, with a countdown. Queue(title: "
|
|
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: "
|
|
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") {
|
package/registry/rank-item.muten
CHANGED
|
@@ -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
|
+
# 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:
|
|
4
|
-
# RankItem(lead: "1", title: "
|
|
5
|
-
# RankItem(lead: "13", title: "
|
|
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.
|
package/registry/resizable.muten
CHANGED
|
@@ -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: "
|
|
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
|
}
|
package/registry/row-menu.js
CHANGED
|
@@ -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
|
}
|
package/registry/row-menu.muten
CHANGED
|
@@ -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: "
|
|
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 = "
|
|
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 (
|
|
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 = "
|
|
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 "
|
|
8
|
-
when $status == "saved" { Stack class("cx-btn-label") { Icon "lucide:check" Span "
|
|
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
|
}
|
package/registry/scale-slider.js
CHANGED
|
@@ -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: "
|
|
4
|
-
# ScaleSlider(label: "
|
|
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: "
|
|
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.
|
package/registry/share-bar.muten
CHANGED
|
@@ -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: "
|
|
5
|
-
# parts {label, value (a number: the part's weight), text (what is written: "USD 3
|
|
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,
|
|
5
|
-
# ShareButton(label: "
|
|
6
|
-
# ShareButton(label: "
|
|
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 = "
|
|
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
|
}
|
package/registry/sheet.muten
CHANGED
|
@@ -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: "
|
|
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 }
|
package/registry/sidebar.muten
CHANGED
|
@@ -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: "
|
|
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
|
-
#
|
|
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 = "
|
|
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")
|