@muten/shadcn 0.1.15 → 0.2.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 +88 -1
- package/globals.css +1 -0
- package/kit.css +348 -126
- package/motion.css +3 -0
- package/package.json +1 -1
- package/registry/accordion.muten +3 -3
- package/registry/agenda.js +20 -16
- package/registry/agenda.muten +14 -12
- 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 +231 -118
- 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-behavior.js +24 -0
- 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,9 +1,9 @@
|
|
|
1
1
|
# ChoiceList - Items you choose one of: a real radio group (arrows move and pick). The page owns the current value.
|
|
2
|
-
# ChoiceList(label: "
|
|
3
|
-
# ChoiceItem(value: "cut", current: service, onPick: pick, title: "
|
|
2
|
+
# ChoiceList(label: "Service") {
|
|
3
|
+
# ChoiceItem(value: "cut", current: service, onPick: pick, title: "Classic cut", description: "30 min", price: "$25.000")
|
|
4
4
|
# }
|
|
5
5
|
# A picture before the text: put an ItemMedia in its slot and the radio moves to the end of the row.
|
|
6
|
-
# ChoiceItem(value: "shop", current: role, onPick: pickRole, title: "
|
|
6
|
+
# ChoiceItem(value: "shop", current: role, onPick: pickRole, title: "I have a venue", description: "People come to you") {
|
|
7
7
|
# ItemMedia(variant: "icon") { Icon "lucide:store" }
|
|
8
8
|
# }
|
|
9
9
|
# shadcn-for-muten · part + Custom (RovingBehavior) · kit.css (.cx-radiogroup, .cx-radio).
|
package/registry/code.js
CHANGED
|
@@ -1,16 +1,22 @@
|
|
|
1
|
-
// Code (muten Custom) - a code block: a bar with the language (or a title) and
|
|
1
|
+
// Code (muten Custom) - a code block: a bar with the language (or a title) and Copy, then the code highlighted with
|
|
2
2
|
// the muten grammar (any other lang shows plain). inputs: code, lang, title. The highlighter is the shared
|
|
3
3
|
// highlight.js, loaded lazily so the token rules live in one place.
|
|
4
4
|
export function mount(el, inputs) {
|
|
5
5
|
const code = String(inputs.code ?? ''), lang = String(inputs.lang || 'muten');
|
|
6
|
-
el.innerHTML = '<div class="cx-code-bar"><span></span><button type="button" class="cn-button cn-button-variant-outline cn-button-size-xs">
|
|
6
|
+
el.innerHTML = '<div class="cx-code-bar"><span></span><button type="button" class="cn-button cn-button-variant-outline cn-button-size-xs" data-tr="Copy">Copy</button></div><pre class="cx-code-pre"><code></code></pre>';
|
|
7
7
|
el.querySelector('.cx-code-bar span').textContent = inputs.title || lang;
|
|
8
8
|
const box = el.querySelector('code'), copy = el.querySelector('button');
|
|
9
|
+
// its words: English, said in the language of the nearest lang (or inputs.lang) once kit/i18n.js is in
|
|
10
|
+
let I = null; const T = (k, ...a) => (I ? I.t(I.langOf(el), k, ...a) : a.reduce((s, v, n) => s.replace(`{${n}}`, v), k));
|
|
11
|
+
// (inputs.lang here is the code's language, “muten”, not a human one: the words follow the nearest lang)
|
|
12
|
+
import('@muten/shadcn/registry/kit/i18n.js').then((m) => { I = m; m.localizeTree(el); });
|
|
9
13
|
box.textContent = code;
|
|
10
14
|
if (lang === 'muten') import('@muten/shadcn/registry/highlight.js').then((m) => { box.innerHTML = m.highlight(code, lang); }).catch(() => {});
|
|
11
15
|
copy.onclick = () => {
|
|
12
|
-
|
|
13
|
-
|
|
16
|
+
// the new word comes in with the kit's swap (kit.css .cx-swap-in), the old one does not just blink away
|
|
17
|
+
const say = (k) => { copy.innerHTML = '<span class="cx-swap-in"></span>'; copy.firstChild.textContent = T(k); };
|
|
18
|
+
const done = (t) => { say(t); setTimeout(() => say('Copy'), 1400); };
|
|
19
|
+
try { navigator.clipboard.writeText(code).then(() => done('Copied'), () => { getSelection().selectAllChildren(box); done('Select it'); }); } catch { getSelection().selectAllChildren(box); done('Select it'); }
|
|
14
20
|
};
|
|
15
21
|
return (next) => { const c = String(next.code ?? ''); if (c === box.textContent) return; box.textContent = c; if (lang === 'muten') import('@muten/shadcn/registry/highlight.js').then((m) => { box.innerHTML = m.highlight(c, lang); }); };
|
|
16
22
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# Collapsible - one block that opens and closes: a trigger line and the content under it, folding to its exact
|
|
2
2
|
# height (200 ms) while the chevron turns. The page owns the bool. For several stacked questions use Accordion.
|
|
3
3
|
# state { more = false : bool } action toggleMore() mutates more { more.toggle() }
|
|
4
|
-
# Collapsible(label: "
|
|
5
|
-
# Collapsible(label: "3
|
|
4
|
+
# Collapsible(label: "More options", open: more, onToggle: toggleMore) { … }
|
|
5
|
+
# Collapsible(label: "3 orders delivered", hint: "View", open: done, onToggle: toggleDone) { … }
|
|
6
6
|
# shadcn-for-muten · part · no deps · kit.css (.cx-coll, the same fold as Accordion).
|
|
7
7
|
part Collapsible(label: text, open: bool, onToggle: action, hint: text = "") {
|
|
8
8
|
Stack class("cx-coll") data(slot: "collapsible", open: $open) {
|
package/registry/color-picker.js
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
// ColorPicker (muten Custom) - Storio's one
|
|
1
|
+
// ColorPicker (muten Custom) - Storio's one color picker (solid; gradients and animations when allowed), as the
|
|
2
2
|
// reference artifact mounts it. The value is paint text (a hex, or e.g. "linear 150 | #1a0f0a 0 | #c2410c 100 ~flow 14").
|
|
3
|
-
// THE APP'S ONE COLOUR PICKER. Every place where somebody picks a
|
|
3
|
+
// THE APP'S ONE COLOUR PICKER. Every place where somebody picks a color uses this component (through the
|
|
4
4
|
// ColorPick / PaintPick parts), so it looks and behaves the same everywhere and a fix lands everywhere.
|
|
5
5
|
//
|
|
6
|
-
// Closed it is a small swatch showing the real
|
|
6
|
+
// Closed it is a small swatch showing the real color or gradient (plus a dot when it is animated). Open it
|
|
7
7
|
// is a popover (a bottom sheet on a phone) with:
|
|
8
|
-
// · a hue × lightness map where every
|
|
8
|
+
// · a hue × lightness map where every color is a draggable point, and a SATURATION slider for the
|
|
9
9
|
// selected point only (it is not opacity: the slider says which point it acts on);
|
|
10
10
|
// · the hex code, an eyedropper where the browser has one, and a trash can to delete a gradient stop;
|
|
11
11
|
// · OPTIONALLY, gradients: a bar of up to 4 stops (tap the bar to add one, drag a stop along it, drag it
|
|
12
12
|
// off the bar / press Delete / use the trash can to remove it), linear · radial · conic with an angle
|
|
13
13
|
// dial, and animations (flow, breathe, spin, hue) with a speed.
|
|
14
14
|
//
|
|
15
|
-
// Gradients are OPT-IN: not every
|
|
16
|
-
// plain
|
|
15
|
+
// Gradients are OPT-IN: not every color in the app can be a gradient. `modes: "solid"` (the default) is a
|
|
16
|
+
// plain color picker and only ever emits a hex; `modes: "solid gradient"` adds the gradient controls, and
|
|
17
17
|
// `anims: "flow breathe spin hue"` lists which animations that place allows (none by default).
|
|
18
18
|
//
|
|
19
|
-
// The value is text, read and written by lib/paint.ts: a plain hex is a solid
|
|
19
|
+
// The value is text, read and written by lib/paint.ts: a plain hex is a solid color, a gradient is e.g.
|
|
20
20
|
// "linear 150 | #1a0f0a 0 | #c2410c 100 ~flow 14". Hosts paint with paintCss() from that same lib.
|
|
21
21
|
//
|
|
22
22
|
// DRAG IS LOCAL, RELEASE COMMITS. While a point or stop is dragged only this popover repaints (and an
|
|
@@ -27,7 +27,8 @@
|
|
|
27
27
|
// The popover lives on <body> with fixed coordinates, so a scrolling or clipping panel never hides it, and
|
|
28
28
|
// it opens from anywhere in the swatch's row (the swatch and the hex label next to it).
|
|
29
29
|
export function mount(el, inputs, handlers) {
|
|
30
|
-
|
|
30
|
+
// the language passed, else the nearest lang (the page's); English unless it is Spanish
|
|
31
|
+
const en = String((inputs && inputs.lang) || (el.closest && el.closest('[lang]')?.getAttribute('lang')) || document.documentElement.lang || 'en').slice(0, 2) !== 'es';
|
|
31
32
|
const say = (es, english) => (en ? english : es);
|
|
32
33
|
const modes = String((inputs && inputs.modes) || 'solid').split(/\s+/);
|
|
33
34
|
const allowGradient = modes.includes('gradient');
|
|
@@ -53,7 +54,7 @@ export function mount(el, inputs, handlers) {
|
|
|
53
54
|
const well = document.createElement('button');
|
|
54
55
|
well.type = 'button';
|
|
55
56
|
well.className = 'cp-well pk-well';
|
|
56
|
-
well.setAttribute('aria-label', (inputs && inputs.label) || say('Elegir un color', 'Pick a
|
|
57
|
+
well.setAttribute('aria-label', (inputs && inputs.label) || say('Elegir un color', 'Pick a color'));
|
|
57
58
|
el.appendChild(well);
|
|
58
59
|
|
|
59
60
|
let lib = null; // lib/paint.ts, loaded on mount (a Custom cannot import at the top)
|
|
@@ -119,7 +120,7 @@ export function mount(el, inputs, handlers) {
|
|
|
119
120
|
pop = document.createElement('div');
|
|
120
121
|
pop.className = 'pk-pop';
|
|
121
122
|
pop.setAttribute('role', 'dialog');
|
|
122
|
-
pop.setAttribute('aria-label', (inputs && inputs.label) || say('Color', '
|
|
123
|
+
pop.setAttribute('aria-label', (inputs && inputs.label) || say('Color', 'Color'));
|
|
123
124
|
pop.addEventListener('click', onClick);
|
|
124
125
|
pop.addEventListener('keydown', (event) => { if (event.key === 'Escape') close(); });
|
|
125
126
|
(document.getElementById('lib') || document.body).append(scrim, pop); // inside the skinned area, so it wears the skin's tokens
|
|
@@ -144,7 +145,7 @@ export function mount(el, inputs, handlers) {
|
|
|
144
145
|
// ── the popover ──
|
|
145
146
|
// Lightness 95 (top) to 5 (bottom); pure black or white sit on the edge instead of past it.
|
|
146
147
|
const Y = (light, height) => Math.max(0, Math.min(height, ((95 - light) / 90) * height));
|
|
147
|
-
// The INTENDED saturation of the selected
|
|
148
|
+
// The INTENDED saturation of the selected color lives on the slider, not in the hex: black at saturation 75
|
|
148
149
|
// is still #000000, so reading it back from the hex would drop it to 0 and the map would stay grey.
|
|
149
150
|
const intensity = () => {
|
|
150
151
|
const slider = pop && pop.querySelector('[data-sat]');
|
|
@@ -161,15 +162,15 @@ export function mount(el, inputs, handlers) {
|
|
|
161
162
|
html.push(`<div class="pk-seg" role="tablist"><button type="button" data-mode="solid" class="${gradient ? '' : 'on'}">${say('Sólido', 'Solid')}</button><button type="button" data-mode="gradient" class="${gradient ? 'on' : ''}">${say('Degradado', 'Gradient')}</button></div>`);
|
|
162
163
|
}
|
|
163
164
|
if (gradient) {
|
|
164
|
-
html.push(`<div class="pk-bar" style="background:${lib.barCss(v)}" aria-label="${say('Colores del degradado', 'Gradient
|
|
165
|
+
html.push(`<div class="pk-bar" style="background:${lib.barCss(v)}" aria-label="${say('Colores del degradado', 'Gradient colors')}">${v.stops.map((stop, i) => `<span class="pk-stop ${i === sel ? 'on' : ''}" data-stop="${i}" style="left:${stop.p}%;background:${stop.c}" tabindex="0" role="slider" aria-label="${say('Color', 'Color')} ${i + 1}" aria-valuenow="${Math.round(stop.p)}"></span>`).join('')}</div>`);
|
|
165
166
|
}
|
|
166
|
-
html.push(`<div class="pk-map" aria-label="${say('Mapa de tonos', '
|
|
167
|
+
html.push(`<div class="pk-map" aria-label="${say('Mapa de tonos', 'Color map')}"><canvas></canvas><svg>${gradient ? '<polyline class="pk-ln" fill="none" stroke="#fff" stroke-width="1.6" stroke-dasharray="3 4" opacity=".9"/>' : ''}</svg>${points.map((_, i) => `<span class="pk-pt ${i === sel ? 'on' : ''}">${gradient ? i + 1 : ''}</span>`).join('')}</div>`);
|
|
167
168
|
// Saturation of the SELECTED point, and it says so: in a gradient the label carries that point's number and
|
|
168
|
-
//
|
|
169
|
+
// color, so nobody takes it for opacity or for the whole gradient.
|
|
169
170
|
const satName = say('Saturación', 'Saturation');
|
|
170
171
|
const satOf = gradient ? `${satName} ${sel + 1}` : satName;
|
|
171
172
|
html.push(`<div class="pk-row"><span class="pk-lbl pk-satlbl">${gradient ? `<i class="pk-satdot" style="background:${current.c}">${sel + 1}</i>` : ''}${satName}</span><input class="pk-range" type="range" min="0" max="100" value="${hsl.s}" data-sat aria-label="${satOf}"></div>`);
|
|
172
|
-
html.push(`<div class="pk-row"><label class="pk-hex"><i style="background:${current.c}"></i><input value="${current.c}" maxlength="7" spellcheck="false" data-hex data-clear="on" aria-label="${say('Código del color', '
|
|
173
|
+
html.push(`<div class="pk-row"><label class="pk-hex"><i style="background:${current.c}"></i><input value="${current.c}" maxlength="7" spellcheck="false" data-hex data-clear="on" aria-label="${say('Código del color', 'Color code')}"></label>${'EyeDropper' in window ? `<button type="button" class="pk-ic" data-drop aria-label="${say('Tomar un color de la pantalla', 'Pick a color from the screen')}">${ICON.drop}</button>` : ''}${gradient ? `<button type="button" class="pk-ic" data-del aria-label="${say('Quitar este color', 'Remove this color')}" ${v.stops.length <= 2 ? 'disabled' : ''}>${ICON.trash}</button>` : ''}</div>`);
|
|
173
174
|
if (gradient) {
|
|
174
175
|
html.push(`<div class="pk-row">${['linear', 'radial', 'conic'].map((type) => `<button type="button" class="pk-ic ${v.type === type ? 'on' : ''}" data-type="${type}" aria-label="${TYPE_LABEL[type]}" aria-pressed="${v.type === type}">${ICON[type]}</button>`).join('')}<span class="pk-grow"></span>${v.type !== 'radial' ? `<span class="pk-dial" data-dial tabindex="0" role="slider" aria-label="${say('Ángulo', 'Angle')}" aria-valuenow="${Math.round(v.angle)}"><i style="transform:rotate(${v.angle + 180}deg)"></i></span><span class="pk-deg">${Math.round(v.angle)}°</span>` : ''}</div>`);
|
|
175
176
|
if (allowedAnims.length > 1) {
|
|
@@ -179,12 +180,12 @@ export function mount(el, inputs, handlers) {
|
|
|
179
180
|
}
|
|
180
181
|
}
|
|
181
182
|
}
|
|
182
|
-
// Ready-made gradients in a grid (all visible, nothing to scroll), and the recent
|
|
183
|
+
// Ready-made gradients in a grid (all visible, nothing to scroll), and the recent colors in a row of their own.
|
|
183
184
|
if (gradient) {
|
|
184
|
-
html.push(`<div class="pk-sep"></div><div class="pk-sw">${lib.PAIRS.map((pair) => { const name = en ? pair.name.en : pair.name.es; return `<button type="button" data-pair="${pair.
|
|
185
|
+
html.push(`<div class="pk-sep"></div><div class="pk-sw">${lib.PAIRS.map((pair) => { const name = en ? pair.name.en : pair.name.es; return `<button type="button" data-pair="${pair.colors.join(',')}" style="background:linear-gradient(135deg,${pair.colors.join(',')})" title="${name}" aria-label="${name}"></button>`; }).join('')}</div>`);
|
|
185
186
|
}
|
|
186
187
|
if (recent.length > 0) {
|
|
187
|
-
html.push(`${gradient ? '' : '<div class="pk-sep"></div>'}<div class="pk-recent"><span class="pk-lbl">${say('Recientes', 'Recent')}</span>${recent.map((
|
|
188
|
+
html.push(`${gradient ? '' : '<div class="pk-sep"></div>'}<div class="pk-recent"><span class="pk-lbl">${say('Recientes', 'Recent')}</span>${recent.map((color) => `<button type="button" data-swatch="${color}" style="background:${color}" title="${color}" aria-label="${color}"></button>`).join('')}</div>`);
|
|
188
189
|
}
|
|
189
190
|
pop.innerHTML = html.join('');
|
|
190
191
|
pop.querySelector('[data-sat]').style.background = `linear-gradient(90deg, hsl(${hsl.h} 0% ${hsl.l}%), hsl(${hsl.h} 100% ${hsl.l}%))`;
|
|
@@ -212,7 +213,7 @@ export function mount(el, inputs, handlers) {
|
|
|
212
213
|
canvas.height = height * dpr;
|
|
213
214
|
const ctx = canvas.getContext('2d');
|
|
214
215
|
ctx.scale(dpr, dpr);
|
|
215
|
-
// A grey shows the map in
|
|
216
|
+
// A grey shows the map in color anyway: that is what touching it will give (see the pointerdown).
|
|
216
217
|
const sat = intensity() < GREY ? COLOURFUL : intensity();
|
|
217
218
|
for (let x = 0; x < width; x += 1) {
|
|
218
219
|
const hue = (x / width) * 360;
|
|
@@ -264,9 +265,9 @@ export function mount(el, inputs, handlers) {
|
|
|
264
265
|
paintPreviews();
|
|
265
266
|
emit('input');
|
|
266
267
|
}
|
|
267
|
-
function
|
|
268
|
-
v.stops[i].c =
|
|
269
|
-
if (v.type === 'solid') v.stops.forEach((stop) => { stop.c =
|
|
268
|
+
function setColor(i, color) {
|
|
269
|
+
v.stops[i].c = color;
|
|
270
|
+
if (v.type === 'solid') v.stops.forEach((stop) => { stop.c = color; });
|
|
270
271
|
}
|
|
271
272
|
function selectStop(i) {
|
|
272
273
|
sel = i;
|
|
@@ -277,7 +278,7 @@ export function mount(el, inputs, handlers) {
|
|
|
277
278
|
live();
|
|
278
279
|
}
|
|
279
280
|
let recent = [];
|
|
280
|
-
function remember(
|
|
281
|
+
function remember(color) { recent = [color, ...recent.filter((each) => each !== color)].slice(0, 8); }
|
|
281
282
|
function commit() { remember(v.stops[sel].c); emit('pick'); }
|
|
282
283
|
|
|
283
284
|
function wire() {
|
|
@@ -291,23 +292,23 @@ export function mount(el, inputs, handlers) {
|
|
|
291
292
|
if (dragging === null) return;
|
|
292
293
|
const p = at(event);
|
|
293
294
|
const sat = intensity();
|
|
294
|
-
|
|
295
|
+
setColor(dragging, lib.hslToHex(Math.round((p.x / p.w) * 360) % 360, sat, Math.round(95 - (p.y / p.h) * 90)));
|
|
295
296
|
live();
|
|
296
297
|
};
|
|
297
298
|
map.addEventListener('pointerdown', (event) => {
|
|
298
299
|
const p = at(event);
|
|
299
300
|
const stops = v.type !== 'solid' ? v.stops : [v.stops[0]];
|
|
300
|
-
// Grab the point under the finger; on a tie (two stops of the same
|
|
301
|
-
// one already selected wins, so a drag never jumps to a
|
|
301
|
+
// Grab the point under the finger; on a tie (two stops of the same color sit on the same spot) the
|
|
302
|
+
// one already selected wins, so a drag never jumps to a color you did not choose.
|
|
302
303
|
const far = (i) => { const hsl = lib.hexToHsl(stops[i].c); return Math.hypot((hsl.h / 360) * p.w - p.x, Y(hsl.l, p.h) - p.y); };
|
|
303
304
|
let nearest = sel;
|
|
304
305
|
stops.forEach((_, i) => { if (far(i) < far(nearest) - 2) nearest = i; });
|
|
305
306
|
if (far(nearest) < GRAB && nearest !== sel) selectStop(nearest);
|
|
306
307
|
// A grey (black, white) has no saturation, so the map is drawn grey and dragging would only give greys.
|
|
307
|
-
// Touching the map means
|
|
308
|
+
// Touching the map means “I want a color”: bring the saturation up first. The slider can take it back.
|
|
308
309
|
const hsl = lib.hexToHsl(v.stops[sel].c);
|
|
309
310
|
if (hsl.s < GREY) {
|
|
310
|
-
|
|
311
|
+
setColor(sel, lib.hslToHex(hsl.h, COLOURFUL, hsl.l));
|
|
311
312
|
pop.querySelector('[data-sat]').value = COLOURFUL;
|
|
312
313
|
drawMap();
|
|
313
314
|
}
|
|
@@ -334,7 +335,7 @@ export function mount(el, inputs, handlers) {
|
|
|
334
335
|
moving = stop ? Number(stop.dataset.stop) : near;
|
|
335
336
|
if (moving !== sel) selectStop(moving);
|
|
336
337
|
} else if (v.stops.length < lib.MAX_STOPS) {
|
|
337
|
-
// The new stop takes the
|
|
338
|
+
// The new stop takes the color the gradient already has at that spot, so adding one never
|
|
338
339
|
// changes how it looks until you move it, and its point does not sit on top of another one.
|
|
339
340
|
const p = Math.round(((event.clientX - r.left) / r.width) * 100);
|
|
340
341
|
const sorted = [...v.stops].sort((a, b) => a.p - b.p);
|
|
@@ -409,7 +410,7 @@ export function mount(el, inputs, handlers) {
|
|
|
409
410
|
const sat = pop.querySelector('[data-sat]');
|
|
410
411
|
sat.addEventListener('input', () => {
|
|
411
412
|
const hsl = lib.hexToHsl(v.stops[sel].c);
|
|
412
|
-
|
|
413
|
+
setColor(sel, lib.hslToHex(hsl.h, Number(sat.value), hsl.l));
|
|
413
414
|
drawMap();
|
|
414
415
|
live();
|
|
415
416
|
});
|
|
@@ -418,7 +419,7 @@ export function mount(el, inputs, handlers) {
|
|
|
418
419
|
const typed = event.target.value.trim();
|
|
419
420
|
const hex = typed.startsWith('#') ? typed : `#${typed}`;
|
|
420
421
|
if (!lib.isHex(hex)) return;
|
|
421
|
-
|
|
422
|
+
setColor(sel, hex.toLowerCase());
|
|
422
423
|
drawMap();
|
|
423
424
|
live();
|
|
424
425
|
commit();
|
|
@@ -431,8 +432,8 @@ export function mount(el, inputs, handlers) {
|
|
|
431
432
|
const data = button.dataset;
|
|
432
433
|
if (data.mode) {
|
|
433
434
|
if (data.mode === 'solid') {
|
|
434
|
-
const
|
|
435
|
-
v = lib.parsePaint(
|
|
435
|
+
const color = v.stops[sel].c;
|
|
436
|
+
v = lib.parsePaint(color);
|
|
436
437
|
} else if (v.type === 'solid') {
|
|
437
438
|
const hsl = lib.hexToHsl(v.stops[0].c);
|
|
438
439
|
v = { ...v, type: 'linear', stops: [{ c: lib.hslToHex(hsl.h, hsl.s, Math.max(6, hsl.l - 28)), p: 0 }, { c: v.stops[0].c, p: 100 }] };
|
|
@@ -441,11 +442,11 @@ export function mount(el, inputs, handlers) {
|
|
|
441
442
|
} else if (data.type) v.type = data.type;
|
|
442
443
|
else if (data.anim) v.anim = data.anim;
|
|
443
444
|
else if (data.speed) v.speed = Number(data.speed);
|
|
444
|
-
else if (data.pair) { const
|
|
445
|
-
else if (data.swatch)
|
|
445
|
+
else if (data.pair) { const colors = data.pair.split(','); v.stops = colors.map((c, i) => ({ c, p: Math.round((i / (colors.length - 1)) * 100) })); sel = 0; }
|
|
446
|
+
else if (data.swatch) setColor(sel, data.swatch);
|
|
446
447
|
else if ('del' in data) { if (v.stops.length <= 2) return; v.stops.splice(sel, 1); sel = 0; }
|
|
447
448
|
else if ('drop' in data) {
|
|
448
|
-
try { const picked = await new window.EyeDropper().open();
|
|
449
|
+
try { const picked = await new window.EyeDropper().open(); setColor(sel, picked.sRGBHex.toLowerCase()); } catch { return; }
|
|
449
450
|
} else return;
|
|
450
451
|
render();
|
|
451
452
|
place();
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# ColorPicker - Storio's one colour picker. Closed: a swatch with the real colour (or gradient). Open: a popover (a
|
|
2
2
|
# sheet on a phone) to pick. modes "solid" (default) or "solid gradient"; anims lists the allowed animations.
|
|
3
3
|
# ColorPicker(value: brand, onInput: setBrand)
|
|
4
|
-
# ColorPicker(value: wall, modes: "solid gradient", anims: "flow breathe spin hue", label: "
|
|
4
|
+
# ColorPicker(value: wall, modes: "solid gradient", anims: "flow breathe spin hue", label: "Shop background", onInput: setWall)
|
|
5
5
|
# The value is paint text (a hex, or "linear 150 | #1a0f0a 0 | #c2410c 100 ~flow 14"). shadcn-for-muten · Custom.
|
|
6
6
|
part ColorPicker(value: text, modes: text = "solid", anims: text = "", label: text = "Color", onInput: action) {
|
|
7
7
|
Custom ColorPicker inputs(value: $value, modes: $modes, anims: $anims, label: $label, lang: "es") on(input: $onInput, pick: $onInput) class("cx-cp-host")
|
package/registry/combobox.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Combobox (muten Custom) - the kit's combobox (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.combobox(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.combobox(el, inputs, core, on)));
|
|
4
4
|
return () => {};
|
|
5
5
|
}
|
package/registry/combobox.muten
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
# Combobox - type and the list filters (accent-insensitive) and marks the match; arrows, Enter, Esc;
|
|
1
|
+
# Combobox - type and the list filters (accent-insensitive) and marks the match; arrows, Enter, Esc; “Usar «…”» to
|
|
2
2
|
# keep something that is not in the list (allowNew "off" to forbid it). options is comma-joined text.
|
|
3
|
-
# Combobox(label: "
|
|
3
|
+
# Combobox(label: "City", options: "Bogotá,Medellín,Cali", placeholder: "Type your city", onChange: setCity)
|
|
4
4
|
# shadcn-for-muten · Custom (kit menus) · host JS combobox.js.
|
|
5
5
|
part Combobox(label: text = "", options: text, value: text = "", placeholder: text = "", allowNew: text = "on", hint: text = "on", onChange: action) {
|
|
6
6
|
Custom Combobox inputs(label: $label, options: $options, value: $value, placeholder: $placeholder, allowNew: $allowNew, hint: $hint) on(change: $onChange) class("cx-cb-host")
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# CommandPalette - the ⌘K search: a Command inside the adaptive modal. Open it with a Hotkey and a search button; an
|
|
2
2
|
# item's action does its thing and closes the palette (the page sets open back to false).
|
|
3
3
|
# Hotkey(keys: "k", onPress: openPalette)
|
|
4
|
-
# CommandPalette(open: palette, onClose: closePalette, query: q) { CommandGroup(label: "
|
|
4
|
+
# CommandPalette(open: palette, onClose: closePalette, query: q) { CommandGroup(label: "Go to") { CommandItem(label: "Schedule", onSelect: goAgenda) { Icon "lucide:calendar" } } }
|
|
5
5
|
# shadcn-for-muten · part over Modal + Command.
|
|
6
|
-
part CommandPalette(open: bool, onClose: action, query: text, placeholder: text = "
|
|
6
|
+
part CommandPalette(open: bool, onClose: action, query: text, placeholder: text = "Search or jump to…", title: text = "Search") {
|
|
7
7
|
Modal(open: $open, onClose: $onClose, title: $title, mode: "dialog") class("cx-palette") {
|
|
8
8
|
Command(query: $query, placeholder: $placeholder) { slot }
|
|
9
9
|
}
|
package/registry/command.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// CommandBehavior (muten Custom) - the Command's list: as you type it keeps the items whose label (or keywords)
|
|
2
|
-
// holds what you wrote, accent-insensitive; a group with nothing left hides, and
|
|
2
|
+
// holds what you wrote, accent-insensitive; a group with nothing left hides, and “empty” shows when nothing matches.
|
|
3
3
|
// The arrows move the highlight, Enter picks it, the pointer highlights what it rests on.
|
|
4
4
|
export function mount(el) {
|
|
5
5
|
const root = el.parentElement;
|
package/registry/command.muten
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
# Command - a list you search and pick from with the keyboard (shadcn's cmdk). Typing keeps what matches, accent-
|
|
2
|
-
# insensitive; arrows move, Enter picks; groups with nothing left hide;
|
|
2
|
+
# insensitive; arrows move, Enter picks; groups with nothing left hide; “empty” says when nothing matches. The page
|
|
3
3
|
# owns the query text (read it to search the server too); each item runs its own action.
|
|
4
|
-
# Command(query: q, placeholder: "
|
|
5
|
-
# CommandGroup(label: "
|
|
4
|
+
# Command(query: q, placeholder: "Customer, order or action…") {
|
|
5
|
+
# CommandGroup(label: "Actions") { CommandItem(label: "New appointment", shortcut: "N", onSelect: create) { Icon "lucide:plus" } }
|
|
6
6
|
# CommandSeparator()
|
|
7
|
-
# CommandGroup(label: "
|
|
7
|
+
# CommandGroup(label: "Customers") { CommandItem(label: "Laura Méndez", keywords: "300 112 4410", onSelect: openLaura) { Icon "lucide:user" } }
|
|
8
8
|
# }
|
|
9
9
|
# Inside a Modal it is the ⌘K palette: CommandPalette. shadcn-for-muten · parts + Custom (CommandBehavior) · Nova (.cn-command*).
|
|
10
|
-
part Command(query: text, placeholder: text = "
|
|
10
|
+
part Command(query: text, placeholder: text = "Search…", empty: text = "Nothing matches.") {
|
|
11
11
|
Stack class("cn-command cx-command") data(slot: "command") {
|
|
12
12
|
Stack class("cn-command-input-wrapper cx-command-input") data(slot: "command-input-wrapper") {
|
|
13
13
|
Icon "lucide:search" class("cn-command-input-icon")
|
|
14
|
-
SearchField bind($query) "{$placeholder}" class("cn-command-input") data(slot: "command-input")
|
|
14
|
+
SearchField bind($query) "{$placeholder}" class("cn-command-input") data(slot: "command-input", trp: $placeholder)
|
|
15
15
|
}
|
|
16
16
|
Stack class("cn-command-list cx-command-list") data(slot: "command-list") aria(role: "listbox") {
|
|
17
17
|
slot
|
|
18
|
-
Span "{$empty}" class("cn-command-empty cx-command-empty") data(slot: "command-empty")
|
|
18
|
+
Span "{$empty}" class("cn-command-empty cx-command-empty") data(slot: "command-empty", tr: $empty)
|
|
19
19
|
}
|
|
20
20
|
CommandBehavior()
|
|
21
21
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# ContextMenu - the menu of a zone, opened with a right click (or a long press on a phone, or the keyboard's menu key)
|
|
2
2
|
# right where the pointer is. The zone and the menu go inside; the menu uses DropdownContent and its items.
|
|
3
3
|
# ContextMenu {
|
|
4
|
-
# ContextTrigger { Stack class("…") { Span "
|
|
5
|
-
# DropdownContent { DropdownItem(label: "
|
|
4
|
+
# ContextTrigger { Stack class("…") { Span "Right click here" } }
|
|
5
|
+
# DropdownContent { DropdownItem(label: "Duplicate", onSelect: dup) { Icon "lucide:copy" } … }
|
|
6
6
|
# }
|
|
7
7
|
# shadcn-for-muten · parts + Custom (OverlayBehavior) · Nova (dropdown classes) + kit.css.
|
|
8
8
|
part ContextMenu() { Stack class("cx-ov cx-ov-zone") data(slot: "context-menu") { slot OverlayBehavior(kind: "context") } }
|
|
9
|
-
part ContextTrigger(label: text = "
|
|
9
|
+
part ContextTrigger(label: text = "Menu for this area") { Stack class("cx-ov-trigger cx-ctx-zone") data(slot: "context-menu-trigger", trl: $label) aria(label: $label) { slot } }
|
package/registry/count-up.js
CHANGED
|
@@ -1,36 +1,38 @@
|
|
|
1
|
-
// CountUp - a muten Custom
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
1
|
+
// CountUp - a muten Custom: a formatted number whose digits ROLL to their value (each digit is a 0-9 column that
|
|
2
|
+
// slides, the kit's --t-move rhythm; only the digits that change move). inputs: { to, roll }
|
|
3
|
+
// to the display string, kept EXACTLY ("$4.906.000", "2.4M", "94%", "12,400", "40ms")
|
|
4
|
+
// roll "view" (default) rolls up from zeros the first time it scrolls into view - for a landing
|
|
5
|
+
// "change" stands still on the first paint and rolls each time `to` changes - for a stat on a dashboard
|
|
6
|
+
// When `to` changes later (in either mode) it rolls from the old value to the new one. Screen readers get the plain
|
|
7
|
+
// text; the columns are aria-hidden. Under prefers-reduced-motion the kit's tokens make the roll instant.
|
|
6
8
|
export function mount(el, inputs) {
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const decimals = (numStr.split('.')[1] || '').length;
|
|
13
|
-
const grouped = numStr.indexOf(',') !== -1;
|
|
14
|
-
const fmt = (v) => prefix + (decimals ? v.toFixed(decimals) : (grouped ? Math.round(v).toLocaleString() : String(Math.round(v)))) + suffix;
|
|
9
|
+
const isDigit = (ch) => ch >= '0' && ch <= '9';
|
|
10
|
+
const reader = document.createElement('span'); reader.className = 'cx-roll-sr';
|
|
11
|
+
const face = document.createElement('span'); face.className = 'cx-roll'; face.setAttribute('aria-hidden', 'true');
|
|
12
|
+
el.replaceChildren(reader, face);
|
|
13
|
+
let shown = '';
|
|
15
14
|
|
|
16
|
-
const
|
|
17
|
-
|
|
15
|
+
const column = () => { const d = document.createElement('span'); d.className = 'cx-roll-d'; const s = document.createElement('span'); s.innerHTML = '<em>0</em><em>1</em><em>2</em><em>3</em><em>4</em><em>5</em><em>6</em><em>7</em><em>8</em><em>9</em>'; d.appendChild(s); return d; };
|
|
16
|
+
const glyph = (ch) => { const g = document.createElement('span'); g.className = 'cx-roll-s'; g.textContent = ch; return g; };
|
|
17
|
+
const setDigit = (d, n, animate) => { const s = d.firstChild; if (!animate) { s.style.transition = 'none'; s.style.translate = `0 -${n}em`; void s.offsetHeight; s.style.transition = ''; } else s.style.translate = `0 -${n}em`; };
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
else el.textContent = target; // land on the exact target (keeps "2.4M"/"99.99%" formatting)
|
|
29
|
-
};
|
|
30
|
-
requestAnimationFrame(tick);
|
|
19
|
+
// same shape (same length, digits and signs in the same places): reuse the columns so each digit rolls from where
|
|
20
|
+
// it is; another shape: rebuild, the digits starting from 0 when animating (or from their value when not)
|
|
21
|
+
const paint = (text, animate) => {
|
|
22
|
+
reader.textContent = text;
|
|
23
|
+
const same = shown.length === text.length && [...text].every((ch, k) => isDigit(ch) === isDigit(shown[k]) && (isDigit(ch) || ch === shown[k]));
|
|
24
|
+
if (!same) { face.replaceChildren(...[...text].map((ch) => (isDigit(ch) ? column() : glyph(ch)))); [...face.children].forEach((c, k) => { if (isDigit(text[k])) setDigit(c, animate ? 0 : +text[k], false); }); }
|
|
25
|
+
if (animate) requestAnimationFrame(() => requestAnimationFrame(() => [...face.children].forEach((c, k) => { if (isDigit(text[k])) setDigit(c, +text[k], true); })));
|
|
26
|
+
else [...face.children].forEach((c, k) => { if (isDigit(text[k])) setDigit(c, +text[k], false); });
|
|
27
|
+
shown = text;
|
|
31
28
|
};
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
29
|
+
|
|
30
|
+
const first = String(inputs?.to ?? '0');
|
|
31
|
+
if (inputs?.roll === 'change' || !('IntersectionObserver' in window)) paint(first, false);
|
|
32
|
+
else {
|
|
33
|
+
paint(first.replace(/\d/g, '0'), false); reader.textContent = first;
|
|
34
|
+
const io = new IntersectionObserver((entries) => { if (entries.some((e) => e.isIntersecting)) { io.disconnect(); paint(first, true); } }, { threshold: 0.5 });
|
|
35
|
+
io.observe(el);
|
|
36
|
+
}
|
|
37
|
+
return (next) => { const t = String(next?.to ?? '0'); if (t !== shown) paint(t, true); };
|
|
36
38
|
}
|
package/registry/count-up.muten
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
# CountUp - a number
|
|
2
|
-
#
|
|
3
|
-
#
|
|
1
|
+
# CountUp - a number whose digits roll to their value, keeping its exact format (“2.4M”, “94 %”, “12,400”, “$1.2M”).
|
|
2
|
+
# roll "view" (default): rolls up from zeros the first time it comes into view (a landing). roll "change": still on the
|
|
3
|
+
# first paint, rolls whenever `to` changes (a dashboard; StatCard uses it for its value). It takes the size of what
|
|
4
|
+
# holds it; instant with reduced motion.
|
|
5
|
+
# Stat(label: "appointments a month") { CountUp(to: "12,400") } StatCard(…, value: total) → rolls when total changes
|
|
4
6
|
# shadcn-for-muten · Custom · host JS count-up.js.
|
|
5
|
-
part CountUp(to: text) { Custom CountUp inputs(to: $to) class("cx-countup") }
|
|
7
|
+
part CountUp(to: text, roll: text = "view") { Custom CountUp inputs(to: $to, roll: $roll) class("cx-countup") }
|
package/registry/date-field.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// DateField (muten Custom) - the kit's dateField (kit/dates.js).
|
|
2
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.dateField(el, inputs, core, on));
|
|
3
|
+
Promise.all([import('@muten/shadcn/registry/kit/core.js'), import('@muten/shadcn/registry/kit/dates.js')]).then(([core, m]) => core.relang(el, inputs, () => m.dateField(el, inputs, core, on)));
|
|
4
4
|
return () => {};
|
|
5
5
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# DateField - a field-like button with the long date; it opens the day / month / year wheels in the adaptive modal.
|
|
2
|
-
# min "today" turns the past off (the wheel greys those days and
|
|
3
|
-
# DateField(label: "
|
|
2
|
+
# min "today" turns the past off (the wheel greys those days and “Done” waits).
|
|
3
|
+
# DateField(label: "Date", value: "today", min: "today", onChange: setDay) → "yyyy-mm-dd"
|
|
4
4
|
# shadcn-for-muten · Custom (kit dates) · host JS date-field.js.
|
|
5
|
-
part DateField(label: text = "
|
|
5
|
+
part DateField(label: text = "Date", value: text = "today", min: text = "", years: number = 2, onChange: action) {
|
|
6
6
|
Custom DateField inputs(label: $label, value: $value, min: $min, years: $years) on(change: $onChange) class("cx-df-host")
|
|
7
7
|
}
|
package/registry/day-plan.muten
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
# DayPlan - the owner's day as a list in order: hour, a dot on a line, who and what, a tag on the right. The hours
|
|
2
2
|
# already gone are faded, a red line says what time it is now, and one free slot can fill itself in a loop (a new
|
|
3
|
-
# booking drops in with a green ring and the count steps up) - a picture of
|
|
4
|
-
# DayPlan(title: "
|
|
5
|
-
# DayPlanRow(time: "9:00 AM", title: "Pedro Ruiz", detail: "
|
|
3
|
+
# booking drops in with a green ring and the count steps up) - a picture of “your day keeps itself”, not the Agenda.
|
|
4
|
+
# DayPlan(title: "Today", date: "Friday, October 9", count: "5 appointments", countNext: "6 appointments") {
|
|
5
|
+
# DayPlanRow(time: "9:00 AM", title: "Pedro Ruiz", detail: "Classic cut", tag: "Served", state: "done")
|
|
6
6
|
# DayPlanNow(time: "3:12 PM")
|
|
7
|
-
# DayPlanRow(time: "3:30 PM", title: "Laura Gómez", detail: "
|
|
8
|
-
# DayPlanRow(time: "4:30 PM", title: "Carla Méndez", detail: "
|
|
7
|
+
# DayPlanRow(time: "3:30 PM", title: "Laura Gómez", detail: "Cut and beard", tag: "Confirmed", tone: "ok")
|
|
8
|
+
# DayPlanRow(time: "4:30 PM", title: "Carla Méndez", detail: "Classic cut", tag: "Your assistant booked it", tone: "ai", state: "fill", free: "Free")
|
|
9
9
|
# }
|
|
10
10
|
# state: "" | done (gone, faded) | fill (shows `free` dashed, then fills; one per plan). tone: "" | ok | ai.
|
|
11
11
|
# countNext: the count after the fill (empty = no count change). shadcn-for-muten · part · no deps · kit.css (.cx-dp).
|
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
# dots and, when given, the address). Put an Image, or any content, in the slot; an Image fills the screen from the top.
|
|
4
4
|
# height crops it (the whole device fades out at the bottom), so a tall screenshot can sit beside a short text. Every
|
|
5
5
|
# measure of the phone follows its width, so it keeps the same proportions at any size. ChatPlay wears this same phone.
|
|
6
|
-
# DeviceFrame { Image "~/shots/store.png" alt("
|
|
6
|
+
# DeviceFrame { Image "~/shots/store.png" alt("Your shop on a phone") }
|
|
7
7
|
# DeviceFrame(island: "on", width: "300px") { Image "~/shots/app.png" alt("La app") }
|
|
8
8
|
# DeviceFrame(kind: "browser", url: "storio.ink/elroble", height: "420px") { Image "~/shots/panel.png" alt("Tu panel") }
|
|
9
|
-
# DeviceFrame(href: "/aurora", label: "Estudio Aurora,
|
|
10
|
-
# width: empty (a phone is 280 px, a browser fills its column) or a width like
|
|
9
|
+
# DeviceFrame(href: "/aurora", label: "Estudio Aurora, a sample shop", shot: "/stores/aurora.webp")
|
|
10
|
+
# width: empty (a phone is 280 px, a browser fills its column) or a width like “320px” or “100%”. island: "on" draws the
|
|
11
11
|
# island (leave it off over a page that has its own top bar). kind: phone | browser.
|
|
12
12
|
# href: the whole device is a link there (it lifts on hover); label says where, for a screen reader.
|
|
13
13
|
# shot: a picture of the screen (390 × 844 at twice the pixels) laid as its background. Unlike an Image it is not
|
package/registry/dialog.muten
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Dialog - a window in the middle of the screen for one task (edit, confirm a detail), on every width. It is the
|
|
2
2
|
# adaptive Modal forced to dialog; on a phone prefer Modal (it becomes a Drawer). The page owns open.
|
|
3
|
-
# Dialog(open: editing, onClose: done, title: "
|
|
3
|
+
# Dialog(open: editing, onClose: done, title: "Edit service", description: "…") { … ModalFooter { … } }
|
|
4
4
|
# shadcn-for-muten · part over Modal.
|
|
5
5
|
part Dialog(open: bool, onClose: action, title: text, description: text = "", size: text = "") {
|
|
6
6
|
Modal(open: $open, onClose: $onClose, title: $title, description: $description, mode: "dialog", size: $size) { slot }
|
package/registry/dirty-bar.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
// DirtyBar (muten Custom) - the kit's
|
|
2
|
-
//
|
|
1
|
+
// DirtyBar (muten Custom) - the kit's “unsaved changes” bar over the form it sits in (kit/core.js kit.dirtyBar).
|
|
2
|
+
// “Descartar” restores what muten owns: inputs get their saved value (and an input event, so muten's bind follows),
|
|
3
3
|
// switches that moved are toggled back by a click (so the page's action runs). The weekly hours restore themselves.
|
|
4
4
|
export function mount(el, inputs, on) {
|
|
5
5
|
import('@muten/shadcn/registry/kit/core.js').then(({ kit }) => {
|
package/registry/dirty-bar.muten
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
# DirtyBar -
|
|
2
|
-
# switch or time differs from the last save,
|
|
1
|
+
# DirtyBar - “You have unsaved changes”: a floating footer of the form it sits in. It appears when any field,
|
|
2
|
+
# switch or time differs from the last save, “Descartar” puts everything back, “Save” saves in three moments.
|
|
3
3
|
# Put it as the LAST child of a SettingsForm.
|
|
4
|
-
# SettingsForm { SettingsSection(title: "
|
|
4
|
+
# SettingsForm { SettingsSection(title: "Your business", description: "…") { Field(…) { Input(…) } } DirtyBar(onSave: save) }
|
|
5
5
|
# shadcn-for-muten · part + Custom (DirtyBar, kit core) · kit.css (.cx-dirty, .cx-set-form).
|
|
6
6
|
part SettingsForm() { Stack class("cx-set-form") { slot } }
|
|
7
7
|
part SettingsSection(title: text, description: text = "") {
|
package/registry/drawer.muten
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Drawer - a sheet that rises from the bottom, with a handle you drag down to close (the phone's way), on every width.
|
|
2
2
|
# It is the adaptive Modal forced to drawer. The page owns open.
|
|
3
|
-
# Drawer(open: picking, onClose: done, title: "
|
|
3
|
+
# Drawer(open: picking, onClose: done, title: "Pick the time") { … ModalFooter { … } }
|
|
4
4
|
# shadcn-for-muten · part over Modal.
|
|
5
5
|
part Drawer(open: bool, onClose: action, title: text = "", description: text = "", size: text = "") {
|
|
6
6
|
Modal(open: $open, onClose: $onClose, title: $title, description: $description, mode: "drawer", size: $size) { slot }
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
# DropdownMenu - a list of actions that opens from a button (shadcn Nova). Arrows, Home/End and the first letter move;
|
|
2
2
|
# Enter picks; a picked action closes it, a check or radio item stays open. Trigger and content go inside:
|
|
3
3
|
# DropdownMenu {
|
|
4
|
-
# DropdownTrigger { TriggerBtn { Span "
|
|
4
|
+
# DropdownTrigger { TriggerBtn { Span "Options" Icon "lucide:chevron-down" } }
|
|
5
5
|
# DropdownContent(align: "start") {
|
|
6
|
-
# DropdownLabel(label: "
|
|
7
|
-
# DropdownItem(label: "
|
|
6
|
+
# DropdownLabel(label: "Order")
|
|
7
|
+
# DropdownItem(label: "Edit", shortcut: "⌘E", onSelect: edit) { Icon "lucide:pencil" }
|
|
8
8
|
# DropdownSeparator()
|
|
9
9
|
# DropdownCheckItem(label: "Avisarme", on: notify, onToggle: toggleNotify)
|
|
10
|
-
# DropdownItem(label: "
|
|
10
|
+
# DropdownItem(label: "Delete", tone: "destructive", onSelect: remove) { Icon "lucide:trash-2" }
|
|
11
11
|
# }
|
|
12
12
|
# }
|
|
13
13
|
# The same items and content serve ContextMenu and Menubar. shadcn-for-muten · parts + Custom (OverlayBehavior) · Nova.
|