@muten/shadcn 0.0.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/README.md +164 -0
- package/globals.css +379 -0
- package/package.json +22 -0
- package/registry/accordion.muten +16 -0
- package/registry/alert-dialog.muten +13 -0
- package/registry/alert.muten +8 -0
- package/registry/attachment.muten +12 -0
- package/registry/avatar.muten +7 -0
- package/registry/badge.muten +9 -0
- package/registry/breadcrumb.muten +7 -0
- package/registry/calendar.js +93 -0
- package/registry/calendar.muten +9 -0
- package/registry/card.muten +8 -0
- package/registry/carousel.js +36 -0
- package/registry/carousel.muten +6 -0
- package/registry/chart.js +34 -0
- package/registry/chart.muten +7 -0
- package/registry/checkbox.muten +8 -0
- package/registry/collapsible.muten +14 -0
- package/registry/combobox.muten +24 -0
- package/registry/command.muten +16 -0
- package/registry/context-menu.js +34 -0
- package/registry/context-menu.muten +8 -0
- package/registry/dialog.muten +13 -0
- package/registry/drawer.muten +12 -0
- package/registry/dropdown-menu.muten +15 -0
- package/registry/empty.muten +9 -0
- package/registry/field.muten +7 -0
- package/registry/hover-card.muten +9 -0
- package/registry/input-otp.js +36 -0
- package/registry/input-otp.muten +7 -0
- package/registry/item.muten +7 -0
- package/registry/menubar.muten +14 -0
- package/registry/message-scroller.muten +4 -0
- package/registry/message.muten +11 -0
- package/registry/navigation-menu.muten +16 -0
- package/registry/pagination.muten +9 -0
- package/registry/popover.muten +11 -0
- package/registry/progress.muten +7 -0
- package/registry/radio-group.muten +11 -0
- package/registry/resizable.js +29 -0
- package/registry/resizable.muten +6 -0
- package/registry/scroll-area.muten +4 -0
- package/registry/select.muten +21 -0
- package/registry/separator.muten +3 -0
- package/registry/sheet.muten +11 -0
- package/registry/slider.js +31 -0
- package/registry/slider.muten +7 -0
- package/registry/spinner.muten +3 -0
- package/registry/switch.muten +8 -0
- package/registry/tabs.muten +12 -0
- package/registry/toaster.js +25 -0
- package/registry/toaster.muten +9 -0
- package/registry/toggle-group.muten +8 -0
- package/registry/toggle.muten +6 -0
- package/registry/tooltip.muten +9 -0
- package/registry.json +63 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Sheet - a panel that slides in from the right edge. The PAGE owns the bool + open/close actions:
|
|
2
|
+
# state { open = false : bool } action close mutates open { open.set(false) } Sheet(open: open, onClose: close) { … }
|
|
3
|
+
# shadcn-for-muten · part · no deps · backdrop click closes; styles via the semantic .sheet* classes.
|
|
4
|
+
part Sheet(open: bool, onClose: action) {
|
|
5
|
+
Stack class("sheet-overlay", "sheet-overlay-open" when $open) {
|
|
6
|
+
Button -> $onClose class("sheet-backdrop")
|
|
7
|
+
Stack class("sheet-content") { slot }
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
part SheetTitle(label: text) { Title "{$label}" h2 class("sheet-title") }
|
|
11
|
+
part SheetDescription(label: text) { Span "{$label}" class("sheet-description") }
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// Slider host component (muten Custom). inputs: { value, min, max }. handlers: { input(value) }.
|
|
2
|
+
// Returns a reactive updater so the thumb follows the page's value when it changes elsewhere.
|
|
3
|
+
export function mount(el, inputs, handlers) {
|
|
4
|
+
const min = Number(inputs.min ?? 0);
|
|
5
|
+
const max = Number(inputs.max ?? 100);
|
|
6
|
+
const range = document.createElement('div'); range.className = 'slider-range';
|
|
7
|
+
const thumb = document.createElement('div'); thumb.className = 'slider-thumb';
|
|
8
|
+
el.append(range, thumb);
|
|
9
|
+
|
|
10
|
+
let value = Number(inputs.value ?? min);
|
|
11
|
+
const pct = (v) => (max === min ? 0 : ((v - min) / (max - min)) * 100);
|
|
12
|
+
const paint = (v) => { const p = pct(v); range.style.width = p + '%'; thumb.style.left = p + '%'; };
|
|
13
|
+
paint(value);
|
|
14
|
+
|
|
15
|
+
const setFromX = (clientX) => {
|
|
16
|
+
const r = el.getBoundingClientRect();
|
|
17
|
+
if (!r.width) return;
|
|
18
|
+
const ratio = Math.min(1, Math.max(0, (clientX - r.left) / r.width));
|
|
19
|
+
const v = Math.round(min + ratio * (max - min));
|
|
20
|
+
if (v !== value) { value = v; paint(v); if (handlers.input) handlers.input(v); }
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
let dragging = false;
|
|
24
|
+
el.addEventListener('pointerdown', (e) => { dragging = true; el.setPointerCapture(e.pointerId); setFromX(e.clientX); });
|
|
25
|
+
el.addEventListener('pointermove', (e) => { if (dragging) setFromX(e.clientX); });
|
|
26
|
+
const stop = () => { dragging = false; };
|
|
27
|
+
el.addEventListener('pointerup', stop);
|
|
28
|
+
el.addEventListener('pointercancel', stop);
|
|
29
|
+
|
|
30
|
+
return (next) => { const v = Number(next.value ?? value); if (v !== value) { value = v; paint(v); } };
|
|
31
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Slider - a draggable range control. The PAGE owns the value (number) + the action that sets it:
|
|
2
|
+
# state { vol = 50 : number } action setVol(v: number) mutates vol { vol.set(v) }
|
|
3
|
+
# Slider(value: vol, min: 0, max: 100, onInput: setVol)
|
|
4
|
+
# shadcn-for-muten · Custom · the host JS is src/components/Slider.js (copied by `muten add slider`).
|
|
5
|
+
part Slider(value: number, min: number, max: number, onInput: action) {
|
|
6
|
+
Custom Slider inputs(value: $value, min: $min, max: $max) on(input: $onInput) class("slider")
|
|
7
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# Switch - a toggle. The PAGE owns the bool + the action that flips it (container/presentational):
|
|
2
|
+
# state { on = false : bool } action toggle mutates on { on.toggle() } Switch(on: on, onToggle: toggle)
|
|
3
|
+
# shadcn-for-muten · part · no deps · styles via the semantic .switch* classes in globals.css.
|
|
4
|
+
part Switch(on: bool, onToggle: action) {
|
|
5
|
+
Button -> $onToggle aria(pressed: $on) class("switch", "switch-on" when $on, "switch-off" when not $on) {
|
|
6
|
+
Stack class("switch-thumb", "switch-thumb-on" when $on, "switch-thumb-off" when not $on)
|
|
7
|
+
}
|
|
8
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Tabs - a tab bar + panels. The PAGE owns a `text` state (the active tab) + an action that sets it:
|
|
2
|
+
# state { tab = "account" : text } action setTab(v: text) mutates tab { tab.set(v) }
|
|
3
|
+
# TabsList { TabsTrigger(value: "account", current: tab, label: "Account", onSelect: setTab) … }
|
|
4
|
+
# TabsContent(value: "account", current: tab) { … }
|
|
5
|
+
# shadcn-for-muten · part · no deps · styles via the semantic .tabs* classes in globals.css.
|
|
6
|
+
part TabsList() { Stack class("tabs-list") { slot } }
|
|
7
|
+
part TabsTrigger(value: text, current: text, label: text, onSelect: action) {
|
|
8
|
+
Button "{$label}" -> $onSelect($value) aria(selected: $value == $current) class("tabs-trigger", "tabs-trigger-active" when $value == $current)
|
|
9
|
+
}
|
|
10
|
+
part TabsContent(value: text, current: text) {
|
|
11
|
+
Stack class("tabs-content", "hidden" when $value != $current) { slot }
|
|
12
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// Toaster host component (muten Custom). inputs: { trigger, title, description, variant, duration }.
|
|
2
|
+
// The page bumps `trigger` (a counter) to fire a toast with the current title/description; the host shows it,
|
|
3
|
+
// slides it in, and auto-dismisses it (click to dismiss early). No id bookkeeping on the page.
|
|
4
|
+
export function mount(el, inputs, handlers) {
|
|
5
|
+
let last = Number(inputs.trigger ?? 0);
|
|
6
|
+
|
|
7
|
+
const show = (title, description, variant, duration) => {
|
|
8
|
+
const node = document.createElement('div');
|
|
9
|
+
node.className = 'toast' + (variant === 'destructive' ? ' toast-destructive' : '');
|
|
10
|
+
const t = document.createElement('div'); t.className = 'toast-title'; t.textContent = title || 'Notification';
|
|
11
|
+
node.appendChild(t);
|
|
12
|
+
if (description) { const d = document.createElement('div'); d.className = 'toast-description'; d.textContent = description; node.appendChild(d); }
|
|
13
|
+
el.appendChild(node);
|
|
14
|
+
requestAnimationFrame(() => node.classList.add('toast-show'));
|
|
15
|
+
const remove = () => { node.classList.remove('toast-show'); setTimeout(() => node.remove(), 200); };
|
|
16
|
+
const timer = setTimeout(remove, Number(duration ?? 3500));
|
|
17
|
+
node.addEventListener('click', () => { clearTimeout(timer); remove(); });
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
// The effect re-runs this whenever a reactive input changes; a higher trigger means "show a new toast".
|
|
21
|
+
return (next) => {
|
|
22
|
+
const tr = Number(next.trigger ?? 0);
|
|
23
|
+
if (tr > last) { last = tr; show(next.title, next.description, next.variant, next.duration); }
|
|
24
|
+
};
|
|
25
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Toaster - fixed toast notifications (bottom-right). The PAGE bumps a `trigger` counter to fire one:
|
|
2
|
+
# state { toastN = 0 : number } action notify mutates toastN { toastN.set(toastN + 1) }
|
|
3
|
+
# Button "Notify" -> notify
|
|
4
|
+
# Toaster(trigger: toastN, title: "Saved", description: "Your changes are saved.", variant: "default")
|
|
5
|
+
# Auto-dismisses; click a toast to close it early. Use state title/description and change them before bumping for dynamic messages.
|
|
6
|
+
# shadcn-for-muten · Custom · host JS is src/components/Toaster.js (copied by `muten add toaster`).
|
|
7
|
+
part Toaster(trigger: number, title: text, description: text, variant: text) {
|
|
8
|
+
Custom Toaster inputs(trigger: $trigger, title: $title, description: $description, variant: $variant) class("toaster")
|
|
9
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# Toggle Group - single-select toolbar. The PAGE owns a `text` state + an action that sets it:
|
|
2
|
+
# 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
|
+
# shadcn-for-muten · part · no deps · styles via the semantic .toggle-group + .toggle* classes in globals.css.
|
|
5
|
+
part ToggleGroup() { Stack class("toggle-group") { slot } }
|
|
6
|
+
part ToggleGroupItem(value: text, current: text, onSelect: action) {
|
|
7
|
+
Button -> $onSelect($value) aria(pressed: $value == $current) class("toggle", "toggle-on" when $value == $current) { slot }
|
|
8
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# Toggle - a two-state button. The PAGE owns the bool + the toggle action. Put an Icon or text in the slot.
|
|
2
|
+
# state { bold = false : bool } action b mutates bold { bold.toggle() } Toggle(on: bold, onToggle: b) { Icon "lucide:bold" }
|
|
3
|
+
# shadcn-for-muten · part · no deps · styles via the semantic .toggle* classes in globals.css.
|
|
4
|
+
part Toggle(on: bool, onToggle: action) {
|
|
5
|
+
Button -> $onToggle aria(pressed: $on) class("toggle", "toggle-on" when $on) { slot }
|
|
6
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Tooltip - shows on hover (pure CSS, no state needed). The slot is the trigger.
|
|
2
|
+
# Tooltip(label: "Add to library") { Button "Hover me" class("btn btn-outline") }
|
|
3
|
+
# shadcn-for-muten · part · no deps · styles via the semantic .tooltip* classes in globals.css.
|
|
4
|
+
part Tooltip(label: text) {
|
|
5
|
+
Stack class("tooltip-wrap") {
|
|
6
|
+
slot
|
|
7
|
+
Span "{$label}" class("tooltip-content")
|
|
8
|
+
}
|
|
9
|
+
}
|
package/registry.json
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://muten.dev/registry.schema.json",
|
|
3
|
+
"name": "shadcn-for-muten",
|
|
4
|
+
"style": "shadcn zinc dark (tokens shipped by globals.css: bg-card / bg-primary / text-muted-foreground / border-border / …)",
|
|
5
|
+
"components": [
|
|
6
|
+
{ "name": "card", "part": "Card", "file": "registry/card.muten", "description": "A surface with CardHeader / CardTitle / CardDescription / CardContent / CardFooter sub-components.", "deps": [] },
|
|
7
|
+
{ "name": "badge", "part": "Badge", "file": "registry/badge.muten", "description": "A small status descriptor. variant: default | secondary | destructive | outline.", "deps": [] },
|
|
8
|
+
{ "name": "alert", "part": "Alert", "file": "registry/alert.muten", "description": "A callout with AlertTitle / AlertDescription. variant: default | destructive.", "deps": [] },
|
|
9
|
+
{ "name": "avatar", "part": "Avatar", "file": "registry/avatar.muten", "description": "A user's initials in a circle (AvatarFallback).", "deps": [] },
|
|
10
|
+
{ "name": "switch", "part": "Switch", "file": "registry/switch.muten", "description": "A toggle; the page owns the bool + the action.", "deps": [] },
|
|
11
|
+
{ "name": "spinner", "part": "Spinner", "file": "registry/spinner.muten", "description": "A loading indicator (animate-spin loader).", "deps": [] },
|
|
12
|
+
{ "name": "progress", "part": "Progress", "file": "registry/progress.muten", "description": "A determinate progress bar; value is 0-100.", "deps": [] },
|
|
13
|
+
{ "name": "separator", "part": "Separator", "file": "registry/separator.muten", "description": "A thin divider line.", "deps": [] },
|
|
14
|
+
{ "name": "empty", "part": "Empty", "file": "registry/empty.muten", "description": "An empty-state placeholder (title + description in a dashed border).", "deps": [] },
|
|
15
|
+
{ "name": "breadcrumb", "part": "Breadcrumb", "file": "registry/breadcrumb.muten", "description": "A breadcrumb trail with BreadcrumbLink / BreadcrumbSeparator / BreadcrumbPage.", "deps": [] },
|
|
16
|
+
{ "name": "pagination", "part": "Pagination", "file": "registry/pagination.muten", "description": "Page navigation with PaginationLink / Previous / Next / Ellipsis.", "deps": [] },
|
|
17
|
+
{ "name": "field", "part": "Field", "file": "registry/field.muten", "description": "A labelled form control: FieldLabel / FieldDescription / FieldError.", "deps": [] },
|
|
18
|
+
{ "name": "item", "part": "Item", "file": "registry/item.muten", "description": "A bordered row: ItemContent / ItemTitle / ItemDescription.", "deps": [] },
|
|
19
|
+
{ "name": "checkbox", "part": "Checkbox", "file": "registry/checkbox.muten", "description": "A checkbox; the page owns the bool + the toggle action.", "deps": [] },
|
|
20
|
+
{ "name": "toggle", "part": "Toggle", "file": "registry/toggle.muten", "description": "A two-state button; the page owns the bool + the toggle action.", "deps": [] },
|
|
21
|
+
{ "name": "toggle-group", "part": "ToggleGroup", "file": "registry/toggle-group.muten", "description": "Single-select toolbar with ToggleGroupItem; page owns a text state.", "deps": [] },
|
|
22
|
+
{ "name": "radio-group", "part": "RadioGroup", "file": "registry/radio-group.muten", "description": "Single choice with RadioGroupItem; page owns a text state.", "deps": [] },
|
|
23
|
+
{ "name": "collapsible", "part": "Collapsible", "file": "registry/collapsible.muten", "description": "A trigger + content shown when open; page owns the bool.", "deps": [] },
|
|
24
|
+
{ "name": "accordion", "part": "Accordion", "file": "registry/accordion.muten", "description": "Stacked expandable sections with AccordionItem; page owns a bool per item.", "deps": [] },
|
|
25
|
+
{ "name": "tabs", "part": "TabsList", "file": "registry/tabs.muten", "description": "Tab bar (TabsList/TabsTrigger) + TabsContent panels; page owns the active-tab text.", "deps": [] },
|
|
26
|
+
{ "name": "dialog", "part": "Dialog", "file": "registry/dialog.muten", "description": "A modal with DialogTitle/Description/Footer; page owns the open bool. Backdrop click closes.", "deps": [] },
|
|
27
|
+
{ "name": "tooltip", "part": "Tooltip", "file": "registry/tooltip.muten", "description": "A hover tooltip (pure CSS, no state). The slot is the trigger.", "deps": [] },
|
|
28
|
+
{ "name": "dropdown-menu","part": "DropdownMenu", "file": "registry/dropdown-menu.muten","description": "A menu below a trigger with DropdownItem/Label/Separator; page owns the open bool.", "deps": [] },
|
|
29
|
+
{ "name": "popover", "part": "Popover", "file": "registry/popover.muten", "description": "A floating panel below a trigger; page owns the open bool.", "deps": [] },
|
|
30
|
+
{ "name": "alert-dialog", "part": "AlertDialog", "file": "registry/alert-dialog.muten", "description": "A modal requiring an explicit choice (no backdrop dismiss); page owns the open bool.", "deps": [] },
|
|
31
|
+
{ "name": "sheet", "part": "Sheet", "file": "registry/sheet.muten", "description": "A panel that slides in from the right; page owns the open bool.", "deps": [] },
|
|
32
|
+
{ "name": "drawer", "part": "Drawer", "file": "registry/drawer.muten", "description": "A panel that slides up from the bottom; page owns the open bool.", "deps": [] },
|
|
33
|
+
{ "name": "hover-card", "part": "HoverCard", "file": "registry/hover-card.muten", "description": "Richer content shown on hover (pure CSS, no state).", "deps": [] },
|
|
34
|
+
{ "name": "select", "part": "Select", "file": "registry/select.muten", "description": "A value picker with SelectItem; page owns the value text + open bool.", "deps": [] },
|
|
35
|
+
{ "name": "scroll-area", "part": "ScrollArea", "file": "registry/scroll-area.muten", "description": "A scrollable region with styled scrollbars.", "deps": [] },
|
|
36
|
+
{ "name": "message", "part": "Message", "file": "registry/message.muten", "description": "Chat Message + Bubble + Marker (variant: sent | received).", "deps": [] },
|
|
37
|
+
{ "name": "message-scroller","part": "MessageScroller","file": "registry/message-scroller.muten","description": "A scrollable column of chat messages.", "deps": [] },
|
|
38
|
+
{ "name": "attachment", "part": "Attachment", "file": "registry/attachment.muten", "description": "A file attachment chip (icon + name + size).", "deps": [] },
|
|
39
|
+
{ "name": "menubar", "part": "Menubar", "file": "registry/menubar.muten", "description": "A bar of menus (File/Edit/View); page owns which is open via a text state.", "deps": [] },
|
|
40
|
+
{ "name": "navigation-menu","part": "NavigationMenu","file": "registry/navigation-menu.muten","description": "A nav bar with hover-revealed panels (pure CSS, no state).", "deps": [] },
|
|
41
|
+
{ "name": "combobox", "part": "Combobox", "file": "registry/combobox.muten", "description": "A Select with a search box; page filters items with `each … where`.", "deps": [] },
|
|
42
|
+
{ "name": "command", "part": "Command", "file": "registry/command.muten", "description": "A command palette (search + grouped CommandItem list).", "deps": [] },
|
|
43
|
+
{ "name": "slider", "part": "Slider", "file": "registry/slider.muten", "component": "Slider", "description": "A draggable range slider (Custom). Page owns the value number.", "deps": [] },
|
|
44
|
+
{ "name": "input-otp", "part": "InputOtp", "file": "registry/input-otp.muten", "component": "InputOtp", "description": "N single-character boxes synced to one value (Custom). Page owns the value text.", "deps": [] },
|
|
45
|
+
{ "name": "calendar", "part": "Calendar", "file": "registry/calendar.muten", "component": "Calendar", "description": "A month-grid date picker (Custom). Page owns the selected ISO date text.", "deps": [] },
|
|
46
|
+
{ "name": "toaster", "part": "Toaster", "file": "registry/toaster.muten", "component": "Toaster", "description": "Fixed toast notifications (Custom). Page bumps a trigger counter to fire one.", "deps": [] },
|
|
47
|
+
{ "name": "carousel", "part": "Carousel", "file": "registry/carousel.muten", "component": "Carousel", "description": "A slide carousel over a list (Custom). Page owns the slides list.", "deps": [] },
|
|
48
|
+
{ "name": "chart", "part": "Chart", "file": "registry/chart.muten", "component": "Chart", "description": "An SVG bar chart over a list of { label, value } (Custom).", "deps": [] },
|
|
49
|
+
{ "name": "resizable", "part": "Resizable", "file": "registry/resizable.muten", "component": "Resizable", "description": "Two panels with a draggable splitter (Custom).", "deps": [] },
|
|
50
|
+
{ "name": "context-menu", "part": "ContextMenu", "file": "registry/context-menu.muten", "component": "ContextMenu", "description": "Right-click an area to open a menu of items (Custom).", "deps": [] }
|
|
51
|
+
],
|
|
52
|
+
"classComponents": {
|
|
53
|
+
"note": "Size/spacing-driven primitives ship as classes in globals.css (no `muten add` needed once you @import it). Style the muten primitive with these:",
|
|
54
|
+
"button": "Button \"Save\" -> save class(\"btn btn-default\") (variants: btn-default|btn-secondary|btn-outline|btn-ghost|btn-destructive|btn-link · sizes: btn-sm|btn-lg|btn-icon)",
|
|
55
|
+
"input": "SearchField bind(q) class(\"input\")",
|
|
56
|
+
"textarea": "class(\"textarea\") · label: class(\"label\") · native select: class(\"native-select\")",
|
|
57
|
+
"skeleton": "Stack class(\"skeleton h-4 w-32\") (add the size utilities)",
|
|
58
|
+
"kbd": "Span \"Ctrl\" class(\"kbd\")",
|
|
59
|
+
"typography": "Title \"Docs\" h1 class(\"prose-h1\") (prose-h1..h4 · prose-p|lead|large|small|muted|blockquote|code|list)",
|
|
60
|
+
"button-group": "Stack class(\"btn-group\") { Button \"A\" class(\"btn btn-outline\") Button \"B\" class(\"btn btn-outline\") }",
|
|
61
|
+
"input-group": "Stack class(\"input-group\") { Icon \"lucide:search\" SearchField bind(q) class(\"input-group-control\") }"
|
|
62
|
+
}
|
|
63
|
+
}
|