@muten/shadcn 0.0.7 → 0.1.1
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 +47 -0
- package/README.md +47 -0
- package/kit.css +2767 -0
- package/motion.css +31 -1
- package/nova.css +1387 -0
- package/package.json +6 -2
- package/registry/accordion.muten +11 -10
- package/registry/agenda.js +35 -0
- package/registry/agenda.muten +10 -0
- package/registry/alert-dialog.muten +9 -12
- package/registry/alert.muten +8 -8
- package/registry/app-shell.js +9 -0
- package/registry/app-shell.muten +12 -0
- package/registry/attachment.muten +13 -9
- package/registry/avatar.muten +24 -5
- package/registry/badge.muten +23 -8
- package/registry/band.muten +16 -0
- package/registry/bar-chart.muten +7 -0
- package/registry/bell.muten +3 -0
- package/registry/bento.muten +6 -9
- package/registry/booking.js +21 -0
- package/registry/booking.muten +13 -0
- package/registry/breadcrumb.muten +9 -7
- package/registry/button-group.muten +13 -4
- package/registry/button.muten +21 -8
- package/registry/calendar.js +28 -93
- package/registry/calendar.muten +9 -8
- package/registry/canvas-viewport.js +12 -5
- package/registry/canvas-viewport.muten +9 -9
- package/registry/card-field.muten +4 -0
- package/registry/card.muten +12 -8
- package/registry/carousel.js +5 -36
- package/registry/carousel.muten +12 -5
- package/registry/chatbot.muten +9 -0
- package/registry/checklist.muten +6 -0
- package/registry/choice-list.muten +16 -0
- package/registry/code.js +14 -12
- package/registry/code.muten +6 -7
- package/registry/collapsible.muten +14 -10
- package/registry/color-picker.js +460 -41
- package/registry/color-picker.muten +7 -6
- package/registry/combobox.js +5 -0
- package/registry/combobox.muten +6 -23
- package/registry/command-palette.muten +8 -12
- package/registry/command.js +32 -0
- package/registry/command.muten +32 -11
- package/registry/context-menu.muten +9 -8
- package/registry/count-up.muten +5 -10
- package/registry/date-field.js +5 -0
- package/registry/date-field.muten +7 -0
- package/registry/device-frame.muten +8 -0
- package/registry/dialog.muten +6 -12
- package/registry/dirty-bar.js +19 -0
- package/registry/dirty-bar.muten +13 -0
- package/registry/drawer.muten +6 -11
- package/registry/dropdown-menu.muten +37 -13
- package/registry/empty.muten +11 -7
- package/registry/feature-grid.muten +10 -12
- package/registry/feature-rows.muten +12 -8
- package/registry/field-behavior.js +33 -0
- package/registry/field-behavior.muten +9 -0
- package/registry/field.muten +26 -7
- package/registry/file-input.js +36 -21
- package/registry/file-input.muten +8 -7
- package/registry/fixed-card.muten +25 -0
- package/registry/folder-picker.js +13 -15
- package/registry/folder-picker.muten +5 -8
- package/registry/gradient-blinds.muten +6 -13
- package/registry/hover-card.muten +7 -9
- package/registry/image-upload.js +5 -0
- package/registry/image-upload.muten +8 -0
- package/registry/indicator.muten +5 -0
- package/registry/input-group.muten +27 -4
- package/registry/input-otp.js +8 -35
- package/registry/input-otp.muten +6 -6
- package/registry/input.muten +8 -7
- package/registry/inspector.muten +13 -15
- package/registry/island.js +7 -0
- package/registry/island.muten +5 -0
- package/registry/item.muten +20 -7
- package/registry/kbd.muten +3 -3
- package/registry/kit/board.js +300 -0
- package/registry/kit/collections.js +148 -0
- package/registry/kit/core.js +989 -0
- package/registry/kit/dates.js +106 -0
- package/registry/kit/menus.js +90 -0
- package/registry/kit/overlays.js +114 -0
- package/registry/kit/paint.js +211 -0
- package/registry/kit/qrcode.js +2291 -0
- package/registry/kit/structure.js +395 -0
- package/registry/kit/table.js +198 -0
- package/registry/kit/values.js +179 -0
- package/registry/kit-layer.js +2 -0
- package/registry/kit-layer.muten +5 -0
- package/registry/kit-skeleton.js +24 -0
- package/registry/kit-skeleton.muten +16 -0
- package/registry/kit-widget.js +79 -0
- package/registry/kit-widget.muten +4 -0
- package/registry/link-btn.muten +9 -7
- package/registry/live-stack.js +29 -0
- package/registry/live-stack.muten +7 -0
- package/registry/logo-cloud.muten +6 -11
- package/registry/member-list.muten +6 -0
- package/registry/menubar.muten +10 -14
- package/registry/message-scroller.js +12 -0
- package/registry/message-scroller.muten +8 -4
- package/registry/message.muten +27 -10
- package/registry/messaging.muten +11 -0
- package/registry/modal-behavior.js +56 -0
- package/registry/modal-behavior.muten +4 -0
- package/registry/modal.muten +29 -0
- package/registry/money-field.js +5 -0
- package/registry/money-field.muten +7 -0
- package/registry/nav-button.muten +14 -0
- package/registry/navigation-menu.muten +16 -14
- package/registry/navpill.muten +15 -9
- package/registry/new-appointment.js +8 -0
- package/registry/new-appointment.muten +10 -0
- package/registry/number-field.js +34 -24
- package/registry/number-field.muten +8 -6
- package/registry/order-board.js +6 -0
- package/registry/order-board.muten +21 -0
- package/registry/overlay-behavior.js +13 -0
- package/registry/overlay-behavior.muten +4 -0
- package/registry/pagination-nav.js +6 -0
- package/registry/pagination-nav.muten +8 -0
- package/registry/pagination.muten +12 -9
- package/registry/password-field.muten +3 -0
- package/registry/permission-matrix.muten +6 -0
- package/registry/phone-field.js +5 -0
- package/registry/phone-field.muten +7 -0
- package/registry/phone-shell.js +7 -0
- package/registry/phone-shell.muten +9 -0
- package/registry/pill.muten +6 -5
- package/registry/plans.muten +4 -0
- package/registry/popover.muten +16 -9
- package/registry/pricing.muten +21 -21
- package/registry/progress.muten +5 -5
- package/registry/queue.muten +3 -0
- package/registry/radio-group.muten +16 -8
- package/registry/range-slider.js +9 -24
- package/registry/range-slider.muten +4 -5
- package/registry/resizable.js +34 -27
- package/registry/resizable.muten +16 -5
- package/registry/roving-behavior.js +16 -0
- package/registry/roving-behavior.muten +3 -0
- package/registry/row-menu.js +5 -0
- package/registry/row-menu.muten +5 -0
- package/registry/save-button.muten +11 -0
- package/registry/scale-slider.js +7 -0
- package/registry/scale-slider.muten +9 -0
- package/registry/scroll-area.muten +8 -4
- package/registry/segmented-control.muten +8 -8
- package/registry/separator.muten +3 -3
- package/registry/share-button.js +7 -0
- package/registry/share-button.muten +10 -0
- package/registry/sheet.muten +6 -10
- package/registry/sidebar.muten +23 -15
- package/registry/skeleton.muten +6 -4
- package/registry/slider.js +73 -27
- package/registry/slider.muten +6 -6
- package/registry/sortable-behavior.js +43 -0
- package/registry/sortable-behavior.muten +2 -0
- package/registry/sortable-list.js +10 -26
- package/registry/sortable-list.muten +6 -7
- package/registry/sortable.muten +15 -0
- package/registry/spark.js +14 -0
- package/registry/spark.muten +3 -0
- package/registry/spinner.muten +4 -3
- package/registry/split-button.muten +12 -10
- package/registry/stamp-card.muten +7 -0
- package/registry/stat-band.muten +6 -9
- package/registry/stat.muten +23 -0
- package/registry/status-bar.muten +7 -6
- package/registry/stepper.muten +17 -14
- package/registry/store-qr.js +5 -0
- package/registry/store-qr.muten +5 -0
- package/registry/swatch.muten +8 -8
- package/registry/switch.muten +14 -6
- package/registry/table-view.muten +10 -0
- package/registry/tabs-behavior.js +27 -0
- package/registry/tabs-behavior.muten +3 -0
- package/registry/tabs.muten +19 -10
- package/registry/tag-input.js +4 -27
- package/registry/tag-input.muten +6 -6
- package/registry/text.muten +7 -0
- package/registry/time-field.js +5 -0
- package/registry/time-field.muten +6 -0
- package/registry/timeline.muten +14 -14
- package/registry/toaster.js +34 -22
- package/registry/toaster.muten +10 -8
- package/registry/toggle-group.muten +11 -6
- package/registry/toggle.muten +8 -5
- package/registry/toolbar.muten +11 -6
- package/registry/tooltip.muten +6 -9
- package/registry/tree-view.js +27 -7
- package/registry/tree-view.muten +8 -13
- package/registry/vector-field.muten +9 -8
- package/registry/virtual-list.js +5 -0
- package/registry/virtual-list.muten +6 -0
- package/registry/weekly-hours.muten +4 -0
- package/registry/wordline.muten +6 -0
- package/registry/zoom-control.muten +9 -9
- package/registry.json +872 -406
- package/registry/context-menu.js +0 -34
package/registry/tabs.muten
CHANGED
|
@@ -1,12 +1,21 @@
|
|
|
1
|
-
# Tabs -
|
|
2
|
-
#
|
|
3
|
-
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
|
|
7
|
-
part
|
|
8
|
-
|
|
1
|
+
# Tabs - the indicator slides from one tab to the next (260 ms). variant "" = segmented (a pill on a muted track),
|
|
2
|
+
# "line" = an underline with a count per tab that scrolls sideways on a phone. Arrows, Home and End move between tabs.
|
|
3
|
+
# The page owns the current value; TabsContent shows the panel of the current one.
|
|
4
|
+
# Tabs(label: "Periodo") { TabsTrigger(value: "day", current: period, label: "Hoy", onSelect: setPeriod) TabsTrigger(value: "week", current: period, label: "Semana", onSelect: setPeriod) }
|
|
5
|
+
# Tabs(label: "Pedidos", variant: "line") { TabsTrigger(value: "new", current: lane, label: "Nuevos", count: "4", onSelect: setLane) … }
|
|
6
|
+
# TabsContent(value: "new", current: lane) { … }
|
|
7
|
+
# shadcn-for-muten · part + Custom (TabsBehavior: the indicator + keys) · kit.css (.cx-tabs).
|
|
8
|
+
part Tabs(label: text, variant: text = "") {
|
|
9
|
+
Stack class("cx-tabs {$variant}") data(slot: "tabs") aria(role: "tablist", label: $label) {
|
|
10
|
+
slot
|
|
11
|
+
TabsBehavior()
|
|
12
|
+
}
|
|
9
13
|
}
|
|
10
|
-
part
|
|
11
|
-
|
|
14
|
+
part TabsList() { Stack class("cx-tabs") data(slot: "tabs") aria(role: "tablist") { slot TabsBehavior() } }
|
|
15
|
+
part TabsTrigger(value: text, current: text, label: text, onSelect: action, count: text = "") {
|
|
16
|
+
Button -> $onSelect($value) class("cn-button cn-button-variant-ghost cn-button-size-sm cx-tab") aria(role: "tab", selected: $value == $current) {
|
|
17
|
+
Span "{$label}"
|
|
18
|
+
when $count != "" { Span "{$count}" class("cx-count") }
|
|
19
|
+
}
|
|
12
20
|
}
|
|
21
|
+
part TabsContent(value: text, current: text) { Stack class("cx-tab-panel", "hidden" when $value != $current) aria(role: "tabpanel") { slot } }
|
package/registry/tag-input.js
CHANGED
|
@@ -1,28 +1,5 @@
|
|
|
1
|
-
// TagInput
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
let tags = parse(inputs.value);
|
|
6
|
-
const input = document.createElement('input'); input.className = 'tag-input-field';
|
|
7
|
-
input.placeholder = inputs.placeholder == null ? 'Add tag…' : String(inputs.placeholder);
|
|
8
|
-
|
|
9
|
-
const emit = () => { if (handlers.change) handlers.change(tags.join(',')); render(); };
|
|
10
|
-
const render = () => {
|
|
11
|
-
el.innerHTML = '';
|
|
12
|
-
for (const t of tags) {
|
|
13
|
-
const chip = document.createElement('span'); chip.className = 'tag-chip'; chip.textContent = t;
|
|
14
|
-
const x = document.createElement('span'); x.className = 'tag-chip-x'; x.innerHTML = '<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>'; // lucide x
|
|
15
|
-
x.addEventListener('click', (e) => { e.stopPropagation(); tags = tags.filter((z) => z !== t); emit(); });
|
|
16
|
-
chip.appendChild(x); el.appendChild(chip);
|
|
17
|
-
}
|
|
18
|
-
el.appendChild(input);
|
|
19
|
-
};
|
|
20
|
-
input.addEventListener('keydown', (e) => {
|
|
21
|
-
if (e.key === 'Enter' && input.value.trim()) { e.preventDefault(); const v = input.value.trim(); if (!tags.includes(v)) tags.push(v); input.value = ''; emit(); }
|
|
22
|
-
else if (e.key === 'Backspace' && !input.value && tags.length) { tags.pop(); emit(); }
|
|
23
|
-
});
|
|
24
|
-
el.addEventListener('click', () => input.focus());
|
|
25
|
-
|
|
26
|
-
render();
|
|
27
|
-
return (n) => { tags = parse(n.value); render(); };
|
|
1
|
+
// TagInput (muten Custom) - the kit's tags field (kit/collections.js tagInput): chips in/out, height eases, a cap.
|
|
2
|
+
export function mount(el, inputs, on) {
|
|
3
|
+
Promise.all([import('@muten/shadcn/registry/kit/core.js'), import('@muten/shadcn/registry/kit/collections.js')]).then(([core, c]) => c.tagInput(el, { ...inputs, tags: inputs.value ?? inputs.tags }, core, on));
|
|
4
|
+
return () => {};
|
|
28
5
|
}
|
package/registry/tag-input.muten
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
#
|
|
2
|
-
#
|
|
3
|
-
# TagInput(value: tags,
|
|
4
|
-
# shadcn-for-muten · Custom · host JS
|
|
5
|
-
part TagInput(value: text, placeholder: text, onChange: action) {
|
|
6
|
-
Custom TagInput inputs(value: $value, placeholder: $placeholder) on(change: $onChange) class("
|
|
1
|
+
# TagInput - tags as chips: Enter or a comma adds, Backspace marks then removes the last, a repeated tag flashes the
|
|
2
|
+
# one already there, suggestions to tap, a cap with its count. The value is comma-joined text.
|
|
3
|
+
# TagInput(value: tags, label: "Etiquetas del servicio", suggestions: "Fade,Navaja,Niños", max: 8, hint: "Ayudan a que te encuentren.", onChange: setTags)
|
|
4
|
+
# shadcn-for-muten · Custom (kit collections) · host JS tag-input.js.
|
|
5
|
+
part TagInput(value: text, placeholder: text = "", label: text = "Etiquetas", suggestions: text = "", max: number = 8, hint: text = "", onChange: action) {
|
|
6
|
+
Custom TagInput inputs(value: $value, placeholder: $placeholder, label: $label, suggestions: $suggestions, max: $max, hint: $hint) on(change: $onChange) class("cx-tags-host")
|
|
7
7
|
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Text (part name Txt - `Text` is a muten primitive) - the one text atom, in a few roles. Everything that shows words
|
|
2
|
+
# in the kit is built from it, so a role changes in one place.
|
|
3
|
+
# Txt(value: "Corte clásico", variant: "heading")
|
|
4
|
+
# Txt(value: "{count} citas", variant: "muted")
|
|
5
|
+
# variant: body | title | heading | strong | label | muted | caption (default: body)
|
|
6
|
+
# shadcn-for-muten · part · no deps · styled by kit.css (.cx-text).
|
|
7
|
+
part Txt(value: text, variant: text = "body") { Span "{$value}" class("cx-text") data(slot: "text", variant: $variant) }
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// TimeField (muten Custom) - the kit's timeField (kit/dates.js).
|
|
2
|
+
export function mount(el, inputs, on) {
|
|
3
|
+
Promise.all([import('@muten/shadcn/registry/kit/core.js'), import('@muten/shadcn/registry/kit/dates.js')]).then(([core, m]) => m.timeField(el, inputs, core, on));
|
|
4
|
+
return () => {};
|
|
5
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# TimeField - a field-like button with the time (and mañana/tarde); it opens the hour / minute wheels.
|
|
2
|
+
# TimeField(label: "Hora", value: "10:30", from: 9, to: 19, step: 15, onChange: setTime) → "HH:MM"
|
|
3
|
+
# shadcn-for-muten · Custom (kit dates) · host JS time-field.js.
|
|
4
|
+
part TimeField(label: text = "Hora", value: text = "", from: number = 9, to: number = 19, step: number = 15, onChange: action) {
|
|
5
|
+
Custom TimeField inputs(label: $label, value: $value, from: $from, to: $to, step: $step) on(change: $onChange) class("cx-tf-host")
|
|
6
|
+
}
|
package/registry/timeline.muten
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
# Timeline -
|
|
2
|
-
#
|
|
3
|
-
# Timeline
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
}
|
|
1
|
+
# Timeline - what happened, in order: a dot and a line join the events; the last one (where things stand now) is
|
|
2
|
+
# marked. Each event has what happened, when, and an optional detail.
|
|
3
|
+
# Timeline(label: "Historial del pedido") {
|
|
4
|
+
# TimelineItem(title: "Pedido recibido", time: "hoy · 10:38")
|
|
5
|
+
# TimelineItem(title: "Esperando tu revisión", time: "ahora", state: "now")
|
|
6
|
+
# }
|
|
7
|
+
# state: "" (past) | now (where it stands) | next (still to come, faded).
|
|
8
|
+
# shadcn-for-muten · part · no deps · kit.css (.cx-timeline).
|
|
9
|
+
part Timeline(label: text = "") { Stack class("cx-timeline cx-tl") aria(role: "list", label: $label) { slot } }
|
|
10
|
+
part TimelineItem(title: text, time: text = "", description: text = "", state: text = "") {
|
|
11
|
+
Stack class("cx-tl-item") data(state: $state) aria(role: "listitem") {
|
|
12
|
+
Span "{$title}" class("cx-text cx-tl-title") data(slot: "text", variant: "body")
|
|
13
|
+
Text "{$description}" class("cx-tl-desc")
|
|
14
|
+
Span "{$time}" class("cx-text") data(slot: "text", variant: "caption")
|
|
15
15
|
}
|
|
16
16
|
}
|
package/registry/toaster.js
CHANGED
|
@@ -1,25 +1,37 @@
|
|
|
1
|
-
// Toaster
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
1
|
+
// Toaster (muten Custom) - shadcn's Sonner stack, from the reference artifact: toasts pile at the bottom right, the
|
|
2
|
+
// newest in front and up to three peeking behind; hovering the pile fans them out and pauses their timers; each one
|
|
3
|
+
// leaves on its own after `duration` ms or with its ×. inputs: trigger (bump it to show one), title, description,
|
|
4
|
+
// action (a button label, optional), duration. handlers: action() when its button is tapped.
|
|
5
|
+
export function mount(el, inputs, on) {
|
|
6
|
+
const viewport = document.createElement('section'); viewport.className = 'pg-toasts'; viewport.setAttribute('aria-label', 'Avisos'); viewport.setAttribute('aria-live', 'polite');
|
|
7
|
+
document.body.appendChild(viewport);
|
|
8
|
+
const toasts = [];
|
|
9
|
+
let last = Number(inputs.trigger) || 0;
|
|
10
|
+
const esc = (s) => String(s ?? '').replace(/[&<>"]/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"' })[c]);
|
|
11
|
+
const layout = () => {
|
|
12
|
+
let off = 0; const frontH = toasts[0] ? toasts[0].h : 0;
|
|
13
|
+
toasts.forEach((t, i) => {
|
|
14
|
+
t.el.style.setProperty('--i', i); t.el.style.setProperty('--h', t.h + 'px'); t.el.style.setProperty('--front-h', frontH + 'px'); t.el.style.setProperty('--off', off + 'px');
|
|
15
|
+
off += t.h;
|
|
16
|
+
if (i > 0) t.el.dataset.behind = ''; else delete t.el.dataset.behind;
|
|
17
|
+
if (i >= 3) t.el.dataset.limited = ''; else delete t.el.dataset.limited;
|
|
18
|
+
});
|
|
18
19
|
};
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
const dismiss = (t) => { if (t.gone) return; t.gone = true; clearTimeout(t.timer); t.el.dataset.end = ''; setTimeout(() => { t.el.remove(); const k = toasts.indexOf(t); if (k >= 0) toasts.splice(k, 1); layout(); }, 500); };
|
|
21
|
+
const arm = (t, ms) => { clearTimeout(t.timer); t.timer = setTimeout(() => dismiss(t), ms); };
|
|
22
|
+
viewport.addEventListener('pointerenter', () => { viewport.dataset.expanded = ''; toasts.forEach((t) => clearTimeout(t.timer)); });
|
|
23
|
+
viewport.addEventListener('pointerleave', () => { delete viewport.dataset.expanded; toasts.forEach((t) => arm(t, t.ms)); });
|
|
24
|
+
const show = (i) => {
|
|
25
|
+
const node = document.createElement('div');
|
|
26
|
+
node.className = 'pg-toast'; node.setAttribute('role', 'status'); node.dataset.start = '';
|
|
27
|
+
node.innerHTML = `<div class="pg-toast-c"><div style="flex:1;min-width:0"><div class="pg-toast-t">${esc(i.title)}</div>${i.description ? `<div class="pg-toast-d">${esc(i.description)}</div>` : ''}</div>${i.action ? `<button data-slot="button" class="cn-button cn-button-variant-outline cn-button-size-sm group/button" data-toast-act>${esc(i.action)}</button>` : ''}<button data-slot="button" class="cn-button cn-button-variant-ghost cn-button-size-icon-sm group/button" aria-label="Cerrar" style="color:var(--muted-foreground)"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><path d="M18 6 6 18M6 6l12 12"/></svg></button></div>`;
|
|
28
|
+
node.style.setProperty('--i', 0); node.style.visibility = 'hidden';
|
|
29
|
+
viewport.prepend(node);
|
|
30
|
+
const t = { el: node, h: node.querySelector('.pg-toast-c').scrollHeight + 2, ms: Number(i.duration) || 5000 };
|
|
31
|
+
toasts.unshift(t); layout(); node.style.visibility = '';
|
|
32
|
+
requestAnimationFrame(() => requestAnimationFrame(() => delete node.dataset.start));
|
|
33
|
+
node.querySelectorAll('button').forEach((b) => b.addEventListener('click', () => { if (b.hasAttribute('data-toast-act')) on.action?.(); dismiss(t); }));
|
|
34
|
+
arm(t, t.ms);
|
|
24
35
|
};
|
|
36
|
+
return (next) => { const tr = Number(next.trigger) || 0; if (tr > last) { last = tr; show(next); } };
|
|
25
37
|
}
|
package/registry/toaster.muten
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
# Toaster -
|
|
2
|
-
#
|
|
3
|
-
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
# Toaster - shadcn's Sonner: toasts that pile at the bottom right, the newest in front and three peeking behind;
|
|
2
|
+
# hovering the pile fans them out and pauses them; each one leaves on its own (duration) or with its ×, and can carry
|
|
3
|
+
# one action (Deshacer). The page bumps a counter to show one, with the title it has at that moment:
|
|
4
|
+
# state { toasts = 0 : number toastTitle = "" : text }
|
|
5
|
+
# action saved() mutates toasts, toastTitle { toastTitle.set("Cita creada") toasts.set(toasts + 1) }
|
|
6
|
+
# Toaster(trigger: toasts, title: toastTitle, description: "Jueves 9 a las 10:30", action: "Deshacer", onAction: undo)
|
|
7
|
+
# For what the person just did on this screen (saving, saved) the Island is lighter; a toast is for news that can wait.
|
|
8
|
+
# shadcn-for-muten · Custom · kit.css (.pg-toasts, .pg-toast) · host JS toaster.js.
|
|
9
|
+
part Toaster(trigger: number, title: text, description: text = "", action: text = "", duration: number = 5000, onAction: action) {
|
|
10
|
+
Custom Toaster inputs(trigger: $trigger, title: $title, description: $description, action: $action, duration: $duration) on(action: $onAction) class("cx-toaster-host")
|
|
9
11
|
}
|
|
@@ -1,8 +1,13 @@
|
|
|
1
|
-
#
|
|
1
|
+
# ToggleGroup - toggles in a row (shadcn Nova). One choice: ToggleGroupItem with the page's text state. Several
|
|
2
|
+
# choices: put Toggle parts inside, each with its own bool. spacing "0" joins them into one control; "2" separates.
|
|
2
3
|
# state { align = "left" : text } action setAlign(v: text) mutates align { align.set(v) }
|
|
3
|
-
# ToggleGroup { ToggleGroupItem(value: "left", current: align, onSelect: setAlign) { Icon "lucide:align-left" } … }
|
|
4
|
-
#
|
|
5
|
-
|
|
6
|
-
part
|
|
7
|
-
|
|
4
|
+
# ToggleGroup(label: "Alineación") { ToggleGroupItem(value: "left", current: align, onSelect: setAlign, label: "Izquierda") { Icon "lucide:align-left" } … }
|
|
5
|
+
# ToggleGroup(variant: "outline", spacing: "2") { Toggle(on: bold, onToggle: b, variant: "outline") { Icon "lucide:bold" } … }
|
|
6
|
+
# variant: default | outline · size: sm | default | lg · spacing: 0 | 1 | 2 · orientation: horizontal | vertical
|
|
7
|
+
# shadcn-for-muten · part · no deps · Nova (.cn-toggle-group) + kit.css (joined/vertical).
|
|
8
|
+
part ToggleGroup(variant: text = "default", size: text = "default", spacing: text = "0", orientation: text = "horizontal", label: text = "") {
|
|
9
|
+
Stack class("cn-toggle-group group/toggle-group") data(slot: "toggle-group", variant: $variant, size: $size, spacing: $spacing, orientation: $orientation) aria(role: "group", label: $label) { slot }
|
|
10
|
+
}
|
|
11
|
+
part ToggleGroupItem(value: text, current: text, onSelect: action, variant: text = "default", size: text = "default", label: text = "") {
|
|
12
|
+
Button -> $onSelect($value) class("cn-toggle cn-toggle-variant-{$variant} cn-toggle-size-{$size} cn-toggle-group-item") data(slot: "toggle-group-item", variant: $variant, size: $size, name: $label) aria(pressed: $value == $current, label: $label) { slot }
|
|
8
13
|
}
|
package/registry/toggle.muten
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
# Toggle - a
|
|
2
|
-
# state { bold = false : bool } action
|
|
3
|
-
#
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
# Toggle - a button that stays pressed (bold, favourite, «solo hoy»). The page owns the bool and the action.
|
|
2
|
+
# state { bold = false : bool } action toggleBold() mutates bold { bold.toggle() }
|
|
3
|
+
# Toggle(on: bold, onToggle: toggleBold, label: "Negrita") { Icon "lucide:bold" }
|
|
4
|
+
# Toggle(on: today, onToggle: toggleToday, variant: "outline") { Icon "lucide:calendar" Span "Solo hoy" }
|
|
5
|
+
# variant: default | outline · size: sm | default | lg · label: the name of an icon-only toggle.
|
|
6
|
+
# shadcn-for-muten · part · no deps · Nova (.cn-toggle, pressed = aria-pressed).
|
|
7
|
+
part Toggle(on: bool, onToggle: action, variant: text = "default", size: text = "default", label: text = "") {
|
|
8
|
+
Button -> $onToggle class("cn-toggle cn-toggle-variant-{$variant} cn-toggle-size-{$size} cn-toggle-group-item") data(slot: "toggle", variant: $variant, size: $size, name: $label) aria(pressed: $on, label: $label) { slot }
|
|
6
9
|
}
|
package/registry/toolbar.muten
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
|
-
# Toolbar -
|
|
2
|
-
#
|
|
3
|
-
#
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
# Toolbar - the tools of an editor in one bar: groups of icon buttons (Btn, Toggle, ToggleGroup) split by thin lines.
|
|
2
|
+
# It floats over a canvas (CanvasViewport) or sits on top of a panel.
|
|
3
|
+
# Toolbar(label: "Herramientas") {
|
|
4
|
+
# ToolbarGroup { ToggleGroup(label: "Herramienta") { ToggleGroupItem(value: "select", current: tool, onSelect: setTool, label: "Seleccionar") { Icon "lucide:mouse-pointer-2" } … } }
|
|
5
|
+
# ToolbarSeparator()
|
|
6
|
+
# ToolbarGroup { Btn(variant: "ghost", size: "icon-sm", label: "Deshacer", onClick: undo) { Icon "lucide:undo-2" } }
|
|
7
|
+
# }
|
|
8
|
+
# shadcn-for-muten · parts · no deps · kit.css (.cx-toolbar).
|
|
9
|
+
part Toolbar(label: text = "Herramientas") { Stack class("cx-toolbar") data(slot: "toolbar") aria(role: "toolbar", label: $label) { slot } }
|
|
10
|
+
part ToolbarGroup() { Stack class("cx-toolbar-group") data(slot: "toolbar-group") { slot } }
|
|
11
|
+
part ToolbarSeparator() { Stack class("cx-toolbar-sep") data(slot: "toolbar-separator") aria(role: "separator", orientation: "vertical") }
|
package/registry/tooltip.muten
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
|
-
# Tooltip - shows on
|
|
2
|
-
#
|
|
3
|
-
#
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
Span "{$label}" class("tooltip-content")
|
|
8
|
-
}
|
|
9
|
-
}
|
|
1
|
+
# Tooltip - a short name that shows while the pointer rests on something (or it gets the focus): what an icon-only
|
|
2
|
+
# button does, the full text of a cut-off line. Only a label: never a place for something the person must read.
|
|
3
|
+
# The kit's single tooltip (KitLayer) does it for anything with a name; Tooltip just gives that name.
|
|
4
|
+
# Tooltip(text: "Editar servicio") { Btn(variant: "ghost", size: "icon", onClick: edit) { Icon "lucide:pencil" } }
|
|
5
|
+
# A Btn with label already has one. shadcn-for-muten · part · the kit's tooltip (.cx-tip).
|
|
6
|
+
part Tooltip(text: text) { Stack class("cx-tip-wrap") data(slot: "tooltip-trigger", name: $text) { slot } }
|
package/registry/tree-view.js
CHANGED
|
@@ -67,14 +67,14 @@ export function mount(el, inputs, handlers) {
|
|
|
67
67
|
const path = parent + '/' + node.name;
|
|
68
68
|
const isFolder = Array.isArray(node.children);
|
|
69
69
|
const id = idOf(node, path);
|
|
70
|
-
const row = document.createElement('button'); row.type = 'button'; row.className = 'tree-row';
|
|
71
|
-
row.style.paddingLeft = (depth * 16 +
|
|
72
|
-
if (!isFolder && id === selected) row.
|
|
73
|
-
const chev = document.createElement('span'); chev.className = 'tree-
|
|
70
|
+
const row = document.createElement('button'); row.type = 'button'; row.className = 'cx-tree-row'; row.setAttribute('role', 'treeitem'); row.tabIndex = -1; row.dataset.path = path; if (isFolder) row.setAttribute('aria-expanded', expanded.has(path)); row.setAttribute('aria-level', depth + 1);
|
|
71
|
+
row.style.paddingLeft = (depth * 16 + 8) + 'px';
|
|
72
|
+
if (!isFolder && id === selected) { row.setAttribute('aria-selected', 'true'); row.tabIndex = 0; }
|
|
73
|
+
const chev = document.createElement('span'); chev.className = 'cx-tree-chev' + (isFolder ? '' : ' cx-tree-chev-none');
|
|
74
74
|
// lucide chevron-down (open) / chevron-right (closed)
|
|
75
75
|
chev.innerHTML = isFolder ? '<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="' + (expanded.has(path) ? 'm6 9 6 6 6-6' : 'm9 18 6-6-6-6') + '"/></svg>' : '';
|
|
76
|
-
const ic = document.createElement('span'); ic.className = 'tree-
|
|
77
|
-
const label = document.createElement('span'); label.className = 'tree-label'; label.textContent = node.name;
|
|
76
|
+
const ic = document.createElement('span'); ic.className = 'cx-tree-ic'; ic.innerHTML = isFolder ? FOLDER : FILE;
|
|
77
|
+
const label = document.createElement('span'); label.className = 'cx-tree-label'; label.textContent = node.name;
|
|
78
78
|
row.appendChild(chev); row.appendChild(ic); row.appendChild(label);
|
|
79
79
|
row.addEventListener('click', () => {
|
|
80
80
|
if (isFolder) { expanded.has(path) ? expanded.delete(path) : expanded.add(path); } // folders: expand only
|
|
@@ -85,7 +85,27 @@ export function mount(el, inputs, handlers) {
|
|
|
85
85
|
if (isFolder && expanded.has(path)) build(node.children, container, depth + 1, path);
|
|
86
86
|
}
|
|
87
87
|
};
|
|
88
|
-
|
|
88
|
+
let focusPath = null;
|
|
89
|
+
const render = () => {
|
|
90
|
+
el.innerHTML = ''; const root = document.createElement('div'); root.className = 'cx-tree'; root.setAttribute('role', 'tree'); root.setAttribute('aria-label', inputs.label || 'Archivos');
|
|
91
|
+
build(data, root, 0, ''); el.appendChild(root);
|
|
92
|
+
const rows = [...root.querySelectorAll('.cx-tree-row')];
|
|
93
|
+
if (!rows.some((r) => r.tabIndex === 0) && rows[0]) rows[0].tabIndex = 0;
|
|
94
|
+
if (focusPath) { const r = rows.find((x) => x.dataset.path === focusPath); if (r) { rows.forEach((x) => { x.tabIndex = -1; }); r.tabIndex = 0; r.focus({ preventScroll: true }); } }
|
|
95
|
+
};
|
|
96
|
+
// keyboard (WAI tree): Up/Down move, Right opens a folder (or steps in), Left closes it (or goes to its parent), Enter/Space pick
|
|
97
|
+
el.addEventListener('keydown', (e) => {
|
|
98
|
+
const rows = [...el.querySelectorAll('.cx-tree-row')], k = rows.indexOf(document.activeElement); if (k < 0) return;
|
|
99
|
+
const row = rows[k], folder = row.hasAttribute('aria-expanded'), open = row.getAttribute('aria-expanded') === 'true';
|
|
100
|
+
const go = (r) => { if (!r) return; rows.forEach((x) => { x.tabIndex = -1; }); r.tabIndex = 0; r.focus(); focusPath = r.dataset.path; };
|
|
101
|
+
if (e.key === 'ArrowDown') { e.preventDefault(); go(rows[k + 1]); }
|
|
102
|
+
else if (e.key === 'ArrowUp') { e.preventDefault(); go(rows[k - 1]); }
|
|
103
|
+
else if (e.key === 'Home') { e.preventDefault(); go(rows[0]); }
|
|
104
|
+
else if (e.key === 'End') { e.preventDefault(); go(rows[rows.length - 1]); }
|
|
105
|
+
else if (e.key === 'ArrowRight' && folder) { e.preventDefault(); if (!open) { focusPath = row.dataset.path; row.click(); } else go(rows[k + 1]); }
|
|
106
|
+
else if (e.key === 'ArrowLeft') { e.preventDefault(); if (folder && open) { focusPath = row.dataset.path; row.click(); } else { const parent = row.dataset.path.split('/').slice(0, -1).join('/'); go(rows.find((x) => x.dataset.path === parent)); } }
|
|
107
|
+
});
|
|
108
|
+
el.addEventListener('click', (e) => { const r = e.target.closest('.cx-tree-row'); if (r) focusPath = r.dataset.path; }, true);
|
|
89
109
|
|
|
90
110
|
render();
|
|
91
111
|
return (n) => {
|
package/registry/tree-view.muten
CHANGED
|
@@ -1,14 +1,9 @@
|
|
|
1
|
-
#
|
|
2
|
-
#
|
|
3
|
-
#
|
|
4
|
-
# flat
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
# `selected` highlights + auto-opens that leaf. Folders only expand; only leaves emit select. (muten parts need
|
|
10
|
-
# every arg, so pass "" for the keys you don't use - an empty key = nested mode.)
|
|
11
|
-
# shadcn-for-muten · Custom · host JS is src/components/TreeView.js (copied by `muten add tree-view`).
|
|
12
|
-
part TreeView(data: list, labelKey: text, valueKey: text, groupKey: text, selected: text, onSelect: action) {
|
|
13
|
-
Custom TreeView inputs(data: $data, labelKey: $labelKey, valueKey: $valueKey, groupKey: $groupKey, selected: $selected) on(select: $onSelect) class("tree-view")
|
|
1
|
+
# TreeView - folders and files: a folder opens and closes, a file is picked. Keyboard as a real tree: Up/Down move,
|
|
2
|
+
# Right opens (or steps in), Left closes (or goes to the parent), Enter picks. Two ways to feed it:
|
|
3
|
+
# nested: data [{name, children}] - a file emits its "/path". TreeView(data: @files, onSelect: open)
|
|
4
|
+
# flat: a list of rows grouped by a key - groupKey makes the folders, labelKey the text, valueKey what a file emits.
|
|
5
|
+
# TreeView(data: @tiles, labelKey: "name", valueKey: "url", groupKey: "folder", selected: tileUrl, onSelect: pick)
|
|
6
|
+
# selected highlights that file and opens the folders above it. shadcn-for-muten · Custom · kit.css (.cx-tree) · tree-view.js.
|
|
7
|
+
part TreeView(data: list, labelKey: text = "", valueKey: text = "", groupKey: text = "", selected: text = "", label: text = "Archivos", onSelect: action) {
|
|
8
|
+
Custom TreeView inputs(data: $data, labelKey: $labelKey, valueKey: $valueKey, groupKey: $groupKey, selected: $selected, label: $label) on(select: $onSelect) class("cx-tree-host")
|
|
14
9
|
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
#
|
|
2
|
-
#
|
|
3
|
-
# VectorField(x: px, y: py, min:
|
|
4
|
-
#
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
# VectorField - two numbers that go together: a position (X, Y) or a size (W, H), each a NumberField with its axis
|
|
2
|
+
# inside. The page owns both values and both actions.
|
|
3
|
+
# VectorField(x: px, y: py, min: 0, max: 1920, onX: setX, onY: setY)
|
|
4
|
+
# VectorField(x: w, y: h, min: 1, max: 4096, xLabel: "W", yLabel: "H", unit: "px", onX: setW, onY: setH)
|
|
5
|
+
# shadcn-for-muten · part over NumberField · kit.css (.cx-vector).
|
|
6
|
+
part VectorField(x: number, y: number, min: number, max: number, step: number = 1, xLabel: text = "X", yLabel: text = "Y", unit: text = "", onX: action, onY: action) {
|
|
7
|
+
Stack class("cx-vector") {
|
|
8
|
+
NumberField(value: $x, min: $min, max: $max, step: $step, prefix: $xLabel, unit: $unit, onInput: $onX)
|
|
9
|
+
NumberField(value: $y, min: $min, max: $max, step: $step, prefix: $yLabel, unit: $unit, onInput: $onY)
|
|
9
10
|
}
|
|
10
11
|
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// VirtualList (muten Custom) - only the visible rows exist in the DOM (kit/collections.js virtualList).
|
|
2
|
+
export function mount(el, inputs) {
|
|
3
|
+
Promise.all([import('@muten/shadcn/registry/kit/core.js'), import('@muten/shadcn/registry/kit/collections.js')]).then(([core, c]) => c.virtualList(el, inputs, core));
|
|
4
|
+
return () => {};
|
|
5
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# VirtualList - thousands of rows, only the visible ones (+ a margin) in the DOM; arrows, Page Up/Down, Home/End.
|
|
2
|
+
# VirtualList(rows: @clients, label: "Clientes", jump: 5000) rows: {title, subtitle, value}
|
|
3
|
+
# total > rows.length repeats the rows up to it (a long demo). shadcn-for-muten · Custom (kit collections).
|
|
4
|
+
part VirtualList(rows: list, total: number = 0, rowHeight: number = 52, label: text = "Lista", jump: number = 0) {
|
|
5
|
+
Custom VirtualList inputs(rows: $rows, total: $total, rowHeight: $rowHeight, label: $label, jump: $jump) class("cx-vl-host")
|
|
6
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
# WeeklyHours - when the business opens: a switch per day, ranges with the wheel time picker, add a range, copy to all.
|
|
2
|
+
# WeeklyHours(days: @week, onChange: saveHours) days: {name, open, ranges [["09:00", "13:00"], ["14:00", "19:00"]]}
|
|
3
|
+
# shadcn-for-muten · part over KitWidget (kit core).
|
|
4
|
+
part WeeklyHours(days: list, onChange: action) { Custom KitWidget inputs(name: "weeklyHours", days: $days) on(action: $onChange) class("cx-wh-host") }
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# WordLine - a line of words set as big type: who a product is for, what a shop does. The words sit in a soft grey,
|
|
2
|
+
# joined by dots in ink; a Word with tone "strong" stands out in ink too. It wraps like text.
|
|
3
|
+
# WordLine(label: "Para quién es") { Word(label: "Barberías") Word(label: "Clases", tone: "strong") Word(label: "Spa") }
|
|
4
|
+
# shadcn-for-muten · parts · no deps · kit.css (.cx-wordline).
|
|
5
|
+
part WordLine(label: text = "") { Stack class("cx-wordline") aria(label: $label) { slot } }
|
|
6
|
+
part Word(label: text, tone: text = "") { Span "{$label}" class("cx-word") data(tone: $tone) }
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
#
|
|
2
|
-
#
|
|
3
|
-
# ZoomControl(value: zoom, onOut:
|
|
4
|
-
# shadcn-for-muten · part · no deps ·
|
|
5
|
-
part ZoomControl(value: number, onOut: action, onIn: action) {
|
|
6
|
-
Stack class("zoom-control") {
|
|
7
|
-
Button -> $onOut class("
|
|
8
|
-
|
|
9
|
-
Button -> $onIn class("
|
|
1
|
+
# ZoomControl - minus, the zoom in percent, plus. Tapping the number puts it back to 100 %. The page owns the number
|
|
2
|
+
# and the three actions (and the limits: switch a side off at its end with Btn's own rules).
|
|
3
|
+
# ZoomControl(value: zoom, onOut: zoomOut, onIn: zoomIn, onReset: zoomReset)
|
|
4
|
+
# shadcn-for-muten · part · no deps · kit.css (.cx-zoom).
|
|
5
|
+
part ZoomControl(value: number, onOut: action, onIn: action, onReset: action) {
|
|
6
|
+
Stack class("cx-zoom") data(slot: "zoom-control") aria(role: "group", label: "Zoom") {
|
|
7
|
+
Button -> $onOut class("cn-button cn-button-variant-ghost cn-button-size-icon-sm group/button") data(name: "Alejar") aria(label: "Alejar") { Icon "lucide:minus" }
|
|
8
|
+
Button "{$value} %" -> $onReset class("cn-button cn-button-variant-ghost cn-button-size-sm group/button cx-zoom-value") data(name: "Volver a 100 %") aria(label: "Zoom {$value} %, volver a 100")
|
|
9
|
+
Button -> $onIn class("cn-button cn-button-variant-ghost cn-button-size-icon-sm group/button") data(name: "Acercar") aria(label: "Acercar") { Icon "lucide:plus" }
|
|
10
10
|
}
|
|
11
11
|
}
|