@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.
Files changed (57) hide show
  1. package/README.md +164 -0
  2. package/globals.css +379 -0
  3. package/package.json +22 -0
  4. package/registry/accordion.muten +16 -0
  5. package/registry/alert-dialog.muten +13 -0
  6. package/registry/alert.muten +8 -0
  7. package/registry/attachment.muten +12 -0
  8. package/registry/avatar.muten +7 -0
  9. package/registry/badge.muten +9 -0
  10. package/registry/breadcrumb.muten +7 -0
  11. package/registry/calendar.js +93 -0
  12. package/registry/calendar.muten +9 -0
  13. package/registry/card.muten +8 -0
  14. package/registry/carousel.js +36 -0
  15. package/registry/carousel.muten +6 -0
  16. package/registry/chart.js +34 -0
  17. package/registry/chart.muten +7 -0
  18. package/registry/checkbox.muten +8 -0
  19. package/registry/collapsible.muten +14 -0
  20. package/registry/combobox.muten +24 -0
  21. package/registry/command.muten +16 -0
  22. package/registry/context-menu.js +34 -0
  23. package/registry/context-menu.muten +8 -0
  24. package/registry/dialog.muten +13 -0
  25. package/registry/drawer.muten +12 -0
  26. package/registry/dropdown-menu.muten +15 -0
  27. package/registry/empty.muten +9 -0
  28. package/registry/field.muten +7 -0
  29. package/registry/hover-card.muten +9 -0
  30. package/registry/input-otp.js +36 -0
  31. package/registry/input-otp.muten +7 -0
  32. package/registry/item.muten +7 -0
  33. package/registry/menubar.muten +14 -0
  34. package/registry/message-scroller.muten +4 -0
  35. package/registry/message.muten +11 -0
  36. package/registry/navigation-menu.muten +16 -0
  37. package/registry/pagination.muten +9 -0
  38. package/registry/popover.muten +11 -0
  39. package/registry/progress.muten +7 -0
  40. package/registry/radio-group.muten +11 -0
  41. package/registry/resizable.js +29 -0
  42. package/registry/resizable.muten +6 -0
  43. package/registry/scroll-area.muten +4 -0
  44. package/registry/select.muten +21 -0
  45. package/registry/separator.muten +3 -0
  46. package/registry/sheet.muten +11 -0
  47. package/registry/slider.js +31 -0
  48. package/registry/slider.muten +7 -0
  49. package/registry/spinner.muten +3 -0
  50. package/registry/switch.muten +8 -0
  51. package/registry/tabs.muten +12 -0
  52. package/registry/toaster.js +25 -0
  53. package/registry/toaster.muten +9 -0
  54. package/registry/toggle-group.muten +8 -0
  55. package/registry/toggle.muten +6 -0
  56. package/registry/tooltip.muten +9 -0
  57. package/registry.json +63 -0
package/README.md ADDED
@@ -0,0 +1,164 @@
1
+ # @muten/shadcn
2
+
3
+ [shadcn/ui](https://ui.shadcn.com) for [muten](https://muten.dev) - the authentic shadcn components, ported to
4
+ muten's declarative DSL. Every component ships the exact shadcn class strings as **semantic classes** (`.card`,
5
+ `.badge`, `.btn`, ...), so your `.muten` stays readable (`class("card")`, not a pile of utilities) while the look
6
+ is 100% shadcn (zinc dark theme, the same tokens, the same animations).
7
+
8
+ This is a **muten plugin**: a registry of components you either **import** (use as-is) or **eject** (`muten add`,
9
+ copy the source and own it, the shadcn way). It is not a runtime - the components compile away with your app.
10
+
11
+ ## Requirements
12
+
13
+ A muten app using **Tailwind CSS v4** (scaffold with `npm create muten@latest` and pick Tailwind, or add it). The
14
+ plugin ships its theme + component classes as a Tailwind `@theme` / `@layer`, so Tailwind does the work.
15
+
16
+ ## Install
17
+
18
+ ```sh
19
+ npm install @muten/shadcn
20
+ ```
21
+
22
+ Then wire two things:
23
+
24
+ **1. Import the styles** in your `src/styles.css` (after Tailwind):
25
+
26
+ ```css
27
+ @import "tailwindcss";
28
+ @import "@muten/shadcn/globals.css";
29
+ ```
30
+
31
+ `globals.css` brings the shadcn `@theme` tokens (colors + radius), the base border reset, and every class-driven
32
+ component. It also `@source`s its own parts, so any utility used inside a component is generated.
33
+
34
+ **2. Let the plugin own the theme.** Empty your `theme.muten` colors so they do not override the shadcn `@theme`:
35
+
36
+ ```
37
+ # theme.muten
38
+ theme { scheme { mode "dark" } }
39
+ ```
40
+
41
+ ## Two ways to use a component
42
+
43
+ ### Import (use as-is)
44
+
45
+ Declare the plugin in `muten.config`. Its parts become available across the app, no copying:
46
+
47
+ ```
48
+ # muten.config
49
+ plugins {
50
+ shadcn {}
51
+ }
52
+ ```
53
+
54
+ ```
55
+ # any page
56
+ Card {
57
+ CardHeader { CardTitle(label: "Create project") }
58
+ CardContent { Span "Deploy in one click." class("text-sm text-muted-foreground") }
59
+ }
60
+ ```
61
+
62
+ ### Eject (own the source)
63
+
64
+ `muten add` copies the component's `.muten` into `src/parts/` so you can edit it (the shadcn philosophy):
65
+
66
+ ```sh
67
+ muten add card badge dialog
68
+ ```
69
+
70
+ The two approaches mix freely. **Custom components (see below) are eject-only** - their host `.js` must live in
71
+ your `src/components/`, so `muten add` copies both files.
72
+
73
+ ---
74
+
75
+ ## Components
76
+
77
+ ### Class-driven (in `globals.css`, no `muten add` needed)
78
+
79
+ Style any muten primitive with these - the exact shadcn utility strings:
80
+
81
+ | Component | Usage |
82
+ |---|---|
83
+ | **Button** | `Button "Save" -> save class("btn btn-default")` - variants `btn-default\|secondary\|outline\|ghost\|destructive\|link`, sizes `btn-sm\|btn-lg\|btn-icon` |
84
+ | **Input / Textarea / Label** | `SearchField bind(q) class("input")` - also `.textarea`, `.label`, `.native-select` |
85
+ | **Skeleton** | `Stack class("skeleton h-4 w-32")` (size it at the call site) |
86
+ | **Separator / Kbd** | `Stack class("separator")` - `Span "Ctrl" class("kbd")` |
87
+ | **Typography** | `Title "Docs" h1 class("prose-h1")` - `prose-h1..h4`, `prose-p\|lead\|large\|small\|muted\|blockquote\|code\|list` |
88
+ | **Button Group / Input Group** | `Stack class("btn-group") { … }` - `Stack class("input-group") { Icon … SearchField class("input-group-control") }` |
89
+ | **Table** | `DataTable @rows columns(a, b) class("data-table")` (styles muten's DataTable) |
90
+
91
+ ### Parts (import via `plugins {}`, or `muten add <name>`)
92
+
93
+ Display: **card** (Card / CardHeader / CardTitle / CardDescription / CardContent / CardFooter), **badge**,
94
+ **alert** (Alert / AlertTitle / AlertDescription), **avatar**, **spinner**, **progress**, **empty**,
95
+ **breadcrumb**, **pagination**, **field**, **item**.
96
+
97
+ Stateful (the page owns the state, the part receives it - see the pattern below): **switch**, **checkbox**,
98
+ **toggle**, **toggle-group**, **radio-group**, **collapsible**, **accordion**, **tabs**.
99
+
100
+ Overlays (the page owns an `open` bool): **dialog**, **alert-dialog**, **tooltip** (pure hover, no state),
101
+ **dropdown-menu**, **popover**, **sheet**, **drawer**, **hover-card** (hover), **select**, **scroll-area**,
102
+ **menubar**, **navigation-menu** (hover), **combobox**, **command**.
103
+
104
+ Chat: **message** (Message / Bubble / Marker), **message-scroller**, **attachment**.
105
+
106
+ ### Custom (eject-only - `muten add` copies a host `.js` into `src/components/`)
107
+
108
+ The genuinely interactive 20%: **slider**, **input-otp**, **calendar**, **toaster**, **carousel**, **chart**,
109
+ **resizable**, **context-menu**. Each is a thin muten part over a small vanilla-JS host you own and can edit.
110
+
111
+ `calendar` ships all the shadcn variants: `mode` (`single` / `range` / `multiple`), `months` (1-2 side by side),
112
+ `caption` (`label` / `dropdown`). `selected` encodes the value (`"yyyy-mm-dd"`, `"from/to"`, or `"d1,d2,..."`).
113
+ A **date picker** is just `Popover { Calendar … }`.
114
+
115
+ ---
116
+
117
+ ## The container / presentational pattern
118
+
119
+ shadcn components in React hold their own state; in muten the **page owns the state** and passes it down, so the
120
+ oracle can see and check it. Interactive parts take a value + an action callback:
121
+
122
+ ```
123
+ state { dark = false : bool }
124
+ action toggle mutates dark { dark.toggle() }
125
+
126
+ Switch(on: dark, onToggle: toggle)
127
+ ```
128
+
129
+ Single-select groups pass the current value + each item's value (the part highlights the match):
130
+
131
+ ```
132
+ state { align = "left" : text }
133
+ action setAlign(v: text) mutates align { align.set(v) }
134
+
135
+ ToggleGroup {
136
+ ToggleGroupItem(value: "left", current: align, onSelect: setAlign) { Icon "lucide:align-left" }
137
+ ToggleGroupItem(value: "right", current: align, onSelect: setAlign) { Icon "lucide:align-right" }
138
+ }
139
+ ```
140
+
141
+ Overlays own an `open` bool; the backdrop click closes:
142
+
143
+ ```
144
+ state { open = false : bool }
145
+ action show mutates open { open.set(true) }
146
+ action close mutates open { open.set(false) }
147
+
148
+ Button "Open" -> show class("btn btn-outline")
149
+ Dialog(open: open, onClose: close) {
150
+ DialogTitle(label: "Are you sure?")
151
+ DialogFooter { Button "Cancel" -> close class("btn btn-outline") }
152
+ }
153
+ ```
154
+
155
+ Each component's `.muten` file has a usage snippet in its header comment.
156
+
157
+ ## Theming
158
+
159
+ Everything is driven by the shadcn `@theme` tokens in `globals.css` (`--color-primary`, `--color-card`,
160
+ `--radius`, ...). To re-theme, edit those CSS variables - the components follow. The default is shadcn's zinc dark.
161
+
162
+ ## License
163
+
164
+ MIT.
package/globals.css ADDED
@@ -0,0 +1,379 @@
1
+ /* @muten/shadcn - the authentic shadcn "zinc" dark tokens (Tailwind v4 @theme) + every component as a
2
+ SEMANTIC class (shadcn-named: .card, .badge, .alert, .btn, ...), each @apply-ing the exact shadcn utility
3
+ string. The parts (Card, Badge, ...) and your .muten pages reference these semantic names - no utility soup
4
+ in the markup. In your app's stylesheet: @import "tailwindcss"; @import "@muten/shadcn/globals.css"; */
5
+
6
+ /* Scan THIS plugin's parts so any utility used inside them is generated, even though they're imported from
7
+ node_modules (which Tailwind's auto-scan skips). Relative to this file. */
8
+ @source "./registry";
9
+ @theme {
10
+ --color-background: hsl(0 0% 3.9%);
11
+ --color-foreground: hsl(0 0% 98%);
12
+ --color-card: hsl(0 0% 3.9%);
13
+ --color-card-foreground: hsl(0 0% 98%);
14
+ --color-popover: hsl(0 0% 3.9%);
15
+ --color-popover-foreground: hsl(0 0% 98%);
16
+ --color-primary: hsl(0 0% 98%);
17
+ --color-primary-foreground: hsl(0 0% 9%);
18
+ --color-secondary: hsl(0 0% 14.9%);
19
+ --color-secondary-foreground: hsl(0 0% 98%);
20
+ --color-muted: hsl(0 0% 14.9%);
21
+ --color-muted-foreground: hsl(0 0% 63.9%);
22
+ --color-accent: hsl(0 0% 14.9%);
23
+ --color-accent-foreground: hsl(0 0% 98%);
24
+ --color-destructive: hsl(0 62.8% 30.6%);
25
+ --color-destructive-foreground: hsl(0 0% 98%);
26
+ --color-border: hsl(0 0% 14.9%);
27
+ --color-input: hsl(0 0% 14.9%);
28
+ --color-ring: hsl(0 0% 83.1%);
29
+
30
+ /* shadcn radius scale: rounded-sm/md/lg derive from --radius, exactly like shadcn's tailwind config. */
31
+ --radius: 0.5rem;
32
+ --radius-sm: calc(var(--radius) - 4px);
33
+ --radius-md: calc(var(--radius) - 2px);
34
+ --radius-lg: var(--radius);
35
+ }
36
+
37
+ /* shadcn's base: every border/outline defaults to the token (so a bare `border` is subtle, not currentColor). */
38
+ @layer base {
39
+ * { border-color: var(--color-border); outline-color: color-mix(in srgb, var(--color-ring) 50%, transparent); }
40
+ }
41
+
42
+ @layer components {
43
+ /* ---- Button (buttonVariants) - sizes default in .btn; .btn-sm/-lg/-icon override ---- */
44
+ .btn { @apply inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium outline-none transition-all duration-200 active:scale-[0.98] disabled:pointer-events-none disabled:opacity-50 focus-visible:ring-[3px] focus-visible:ring-ring/50 h-9 px-4 py-2; }
45
+ .btn-default { @apply bg-primary text-primary-foreground shadow hover:bg-primary/90; }
46
+ .btn-destructive { @apply bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90; }
47
+ .btn-outline { @apply border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground; }
48
+ .btn-secondary { @apply bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80; }
49
+ .btn-ghost { @apply hover:bg-accent hover:text-accent-foreground; }
50
+ .btn-link { @apply text-primary underline-offset-4 hover:underline; }
51
+ .btn-sm { @apply h-8 rounded-md px-3 text-xs; }
52
+ .btn-lg { @apply h-10 rounded-md px-8; }
53
+ .btn-icon { @apply h-9 w-9 px-0; }
54
+
55
+ /* ---- Form controls ---- */
56
+ .input { @apply flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm outline-none transition-[color,box-shadow] duration-200 placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50; }
57
+ .textarea { @apply flex min-h-16 w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm outline-none transition-[color,box-shadow] duration-200 placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50; }
58
+ .label { @apply text-sm font-medium leading-none; }
59
+ .native-select { @apply flex h-9 w-full items-center rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm outline-none transition-[color,box-shadow] duration-200 focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50; }
60
+
61
+ /* ---- Card ---- */
62
+ .card { @apply rounded-xl border bg-card text-card-foreground shadow; }
63
+ .card-header { @apply flex flex-col space-y-1.5 p-6; }
64
+ .card-title { @apply font-semibold leading-none tracking-tight; }
65
+ .card-description { @apply text-sm text-muted-foreground; }
66
+ .card-content { @apply p-6 pt-0; }
67
+ .card-footer { @apply flex flex-row items-center p-6 pt-0; }
68
+
69
+ /* ---- Badge (badgeVariants) ---- */
70
+ .badge { @apply inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-semibold w-fit whitespace-nowrap shrink-0 gap-1 transition-colors duration-200; }
71
+ .badge-default { @apply border-transparent bg-primary text-primary-foreground; }
72
+ .badge-secondary { @apply border-transparent bg-secondary text-secondary-foreground; }
73
+ .badge-destructive { @apply border-transparent bg-destructive text-destructive-foreground; }
74
+ .badge-outline { @apply text-foreground; }
75
+
76
+ /* ---- Alert ---- */
77
+ .alert { @apply relative w-full rounded-lg border px-4 py-3 text-sm flex flex-row gap-3 items-start; }
78
+ .alert-default { @apply bg-background text-foreground; }
79
+ .alert-destructive { @apply border-destructive/50 text-destructive; }
80
+ .alert-title { @apply font-medium leading-none tracking-tight; }
81
+ .alert-description { @apply text-sm opacity-90; }
82
+
83
+ /* ---- Avatar ---- */
84
+ .avatar { @apply relative flex size-10 shrink-0 overflow-hidden rounded-full; }
85
+ .avatar-fallback { @apply flex size-full items-center justify-center rounded-full bg-muted text-sm font-medium select-none; }
86
+
87
+ /* ---- Switch (toggle the -on/-off classes from the page state) ---- */
88
+ .switch { @apply inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent outline-none transition-all duration-200 focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:ring-offset-2 focus-visible:ring-offset-background; }
89
+ .switch-on { @apply bg-primary; }
90
+ .switch-off { @apply bg-input; }
91
+ .switch-thumb { @apply pointer-events-none block size-5 rounded-full bg-background shadow-lg ring-0 transition-transform duration-200; }
92
+ .switch-thumb-on { @apply translate-x-5; }
93
+ .switch-thumb-off { @apply translate-x-0; }
94
+
95
+ /* ---- Progress (the fill width is the one runtime value, set via style(w) -> --w) ---- */
96
+ .progress { @apply relative h-2 w-full overflow-hidden rounded-full bg-primary/20; }
97
+ .progress-indicator { @apply h-full bg-primary transition-all duration-200; width: var(--w); }
98
+
99
+ /* ---- Spinner / Skeleton / Separator / Kbd ---- */
100
+ .spinner { @apply animate-spin size-4 text-muted-foreground; }
101
+ .skeleton { @apply animate-pulse rounded-md bg-muted; }
102
+ .separator { @apply shrink-0 bg-border h-px w-full; }
103
+ .separator-vertical { @apply h-full w-px; }
104
+ .kbd { @apply pointer-events-none inline-flex h-5 select-none items-center gap-1 rounded border bg-muted px-1.5 font-mono text-[10px] font-medium text-muted-foreground; }
105
+
106
+ /* ---- Empty ---- */
107
+ .empty { @apply flex flex-col items-center justify-center gap-2 rounded-lg border border-dashed p-12 text-center; }
108
+ .empty-title { @apply text-lg font-medium; }
109
+ .empty-description { @apply text-sm text-muted-foreground; }
110
+
111
+ /* ---- Breadcrumb ---- */
112
+ .breadcrumb { @apply flex flex-row flex-wrap items-center gap-1.5 text-sm text-muted-foreground; }
113
+ .breadcrumb-link { @apply rounded-sm outline-none transition-colors duration-200 hover:text-foreground focus-visible:text-foreground focus-visible:ring-[3px] focus-visible:ring-ring/50; }
114
+ .breadcrumb-page { @apply font-normal text-foreground; }
115
+ .breadcrumb-separator { @apply size-3.5; }
116
+
117
+ /* ---- Pagination (links reuse .btn btn-icon) ---- */
118
+ .pagination { @apply flex flex-row items-center justify-center gap-1; }
119
+ .pagination-ellipsis { @apply flex size-9 items-center justify-center; }
120
+
121
+ /* ---- Field (label reuses .label) ---- */
122
+ .field { @apply flex flex-col gap-2; }
123
+ .field-description { @apply text-sm text-muted-foreground; }
124
+ .field-error { @apply text-sm font-medium text-destructive; }
125
+
126
+ /* ---- Item ---- */
127
+ .item { @apply flex flex-row items-center gap-3 rounded-md border p-3; }
128
+ .item-content { @apply flex flex-col gap-0.5 min-w-0 flex-1; }
129
+ .item-title { @apply text-sm font-medium leading-none; }
130
+ .item-description { @apply text-sm text-muted-foreground; }
131
+
132
+ /* ---- Checkbox (toggle .checkbox-checked from the page state; the check Icon shows only when checked) ---- */
133
+ .checkbox { @apply inline-flex items-center justify-center size-4 shrink-0 rounded-sm border border-primary shadow outline-none transition-[color,box-shadow,background-color] duration-200 focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50; }
134
+ .checkbox-checked { @apply bg-primary text-primary-foreground; }
135
+ .checkbox-icon { @apply size-3.5 transition-all duration-200; transform: scale(0); opacity: 0; }
136
+ .checkbox-icon-on { transform: scale(1); opacity: 1; }
137
+
138
+ /* ---- Toggle / Toggle Group (toggle .toggle-on from the page state) ---- */
139
+ .toggle { @apply inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium h-9 min-w-9 px-2 outline-none transition-all duration-200 active:scale-[0.98] hover:bg-muted hover:text-muted-foreground focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50; }
140
+ .toggle-on { @apply bg-accent text-accent-foreground; }
141
+ .toggle-group { @apply inline-flex flex-row items-center gap-1; }
142
+
143
+ /* ---- Radio Group ---- */
144
+ .radio-group { @apply flex flex-col gap-3; }
145
+ .radio-row { @apply flex flex-row items-center gap-2 outline-none cursor-pointer; }
146
+ .radio { @apply flex items-center justify-center aspect-square size-4 shrink-0 rounded-full border border-primary text-primary shadow outline-none transition-[color,box-shadow] duration-200 focus-visible:ring-[3px] focus-visible:ring-ring/50; }
147
+ .radio-dot { @apply size-2 rounded-full bg-primary transition-transform duration-200; transform: scale(0); }
148
+ .radio-dot-on { transform: scale(1); }
149
+ .radio-label { @apply text-sm font-medium leading-none; }
150
+
151
+ /* ---- Collapsible (content wrapped in `when open`) ---- */
152
+ .collapsible { @apply flex flex-col; }
153
+ .collapsible-trigger { @apply flex flex-row items-center justify-between gap-2 rounded-md text-sm font-medium outline-none transition-colors duration-200 focus-visible:ring-[3px] focus-visible:ring-ring/50; }
154
+ .collapsible-panel { @apply overflow-hidden; max-height: 0; transition: max-height 250ms ease; }
155
+ .collapsible-panel-open { max-height: 24rem; }
156
+ .collapsible-content { @apply flex flex-col gap-2 pt-2; }
157
+
158
+ /* ---- Accordion (chevron rotates via .accordion-chevron-open; content wrapped in `when open`) ---- */
159
+ .accordion { @apply flex flex-col; }
160
+ .accordion-item { @apply flex flex-col border-b; }
161
+ .accordion-trigger { @apply flex flex-row flex-1 items-center justify-between gap-2 py-4 text-sm font-medium outline-none transition-all duration-200 hover:underline focus-visible:ring-[3px] focus-visible:ring-ring/50; }
162
+ .accordion-chevron { @apply size-4 shrink-0 text-muted-foreground transition-transform duration-200; }
163
+ .accordion-chevron-open { @apply rotate-180; }
164
+ /* Smooth height: panel clips + animates max-height (works on the flex container; grid 0fr is overridden by
165
+ mu-stack's display:flex). 24rem covers typical accordion content; bump it for very long panels. */
166
+ .accordion-panel { @apply overflow-hidden; max-height: 0; transition: max-height 250ms ease; }
167
+ .accordion-panel-open { max-height: 24rem; }
168
+ .accordion-content { @apply pb-4 text-sm text-muted-foreground; }
169
+
170
+ /* ---- Tabs (active panel via .hidden toggle so panel state survives switching) ---- */
171
+ .tabs-list { @apply inline-flex flex-row items-center justify-center gap-1 rounded-lg bg-muted p-1 text-muted-foreground; }
172
+ .tabs-trigger { @apply inline-flex items-center justify-center gap-1.5 rounded-md px-3 py-1 text-sm font-medium whitespace-nowrap outline-none transition-all duration-200 focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50; }
173
+ .tabs-trigger-active { @apply bg-background text-foreground shadow-sm; }
174
+ .tabs-content { @apply mt-2 outline-none; }
175
+
176
+ /* ---- Dialog (modal: page owns `open`; overlay always renders, fades+zooms via .dialog-overlay-open) ---- */
177
+ .dialog-overlay { @apply fixed inset-0 z-50 flex items-center justify-center p-4 opacity-0 invisible; transition: opacity 200ms ease, visibility 200ms ease; }
178
+ .dialog-overlay-open { @apply opacity-100 visible; }
179
+ .dialog-backdrop { @apply fixed inset-0 bg-black/80; }
180
+ .dialog-content { @apply relative z-10 w-full max-w-lg rounded-lg border bg-background p-6 shadow-lg flex flex-col gap-4 scale-95 transition-transform duration-200; }
181
+ .dialog-overlay-open .dialog-content { @apply scale-100; }
182
+ .dialog-title { @apply text-lg font-semibold leading-none tracking-tight; }
183
+ .dialog-description { @apply text-sm text-muted-foreground; }
184
+ .dialog-footer { @apply flex flex-row justify-end gap-2; }
185
+
186
+ /* ---- Tooltip (pure CSS :hover, no state) ---- */
187
+ .tooltip-wrap { @apply relative inline-flex; }
188
+ .tooltip-content { @apply pointer-events-none absolute bottom-full left-1/2 z-50 mb-2 -translate-x-1/2 whitespace-nowrap rounded-md bg-primary px-3 py-1.5 text-xs font-medium text-primary-foreground shadow-md opacity-0 invisible; transition: opacity 200ms ease, visibility 200ms ease; }
189
+ .tooltip-wrap:hover .tooltip-content { @apply opacity-100 visible; }
190
+
191
+ /* ---- Dropdown Menu (page owns `open`; absolute panel below the trigger; backdrop catches outside clicks) ---- */
192
+ .dropdown { @apply relative inline-flex; }
193
+ .dropdown-backdrop { @apply fixed inset-0 z-40; }
194
+ .dropdown-content { @apply absolute top-full right-0 z-50 mt-1 min-w-48 rounded-md border bg-popover p-1 text-popover-foreground shadow-md flex flex-col origin-top-right opacity-0 invisible scale-95; transition: opacity 150ms ease, transform 150ms ease, visibility 150ms ease; }
195
+ .dropdown-content-open { @apply opacity-100 visible scale-100; }
196
+ .dropdown-item { @apply flex flex-row items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors duration-200 hover:bg-accent hover:text-accent-foreground; }
197
+ .dropdown-label { @apply px-2 py-1.5 text-sm font-semibold; }
198
+ .dropdown-separator { @apply my-1 h-px bg-muted; }
199
+
200
+ /* ---- Popover (page owns `open`; absolute panel below the trigger) ---- */
201
+ .popover { @apply relative inline-flex; }
202
+ .popover-backdrop { @apply fixed inset-0 z-40; }
203
+ .popover-content { @apply absolute top-full left-0 z-50 mt-1 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md flex flex-col gap-2 origin-top opacity-0 invisible scale-95; transition: opacity 150ms ease, transform 150ms ease, visibility 150ms ease; }
204
+ .popover-content-open { @apply opacity-100 visible scale-100; }
205
+
206
+ /* ---- Sheet (side panel; slides in from the right via translate) ---- */
207
+ .sheet-overlay { @apply fixed inset-0 z-50 opacity-0 invisible; transition: opacity 300ms ease, visibility 300ms ease; }
208
+ .sheet-overlay-open { @apply opacity-100 visible; }
209
+ .sheet-backdrop { @apply fixed inset-0 bg-black/80; }
210
+ .sheet-content { @apply fixed inset-y-0 right-0 z-10 h-full w-3/4 max-w-sm border-l bg-background p-6 shadow-lg flex flex-col gap-4 translate-x-full; transition: transform 300ms ease; }
211
+ .sheet-overlay-open .sheet-content { @apply translate-x-0; }
212
+ .sheet-title { @apply text-lg font-semibold text-foreground; }
213
+ .sheet-description { @apply text-sm text-muted-foreground; }
214
+
215
+ /* ---- Drawer (bottom sheet; slides up via translate) ---- */
216
+ .drawer-overlay { @apply fixed inset-0 z-50 opacity-0 invisible; transition: opacity 300ms ease, visibility 300ms ease; }
217
+ .drawer-overlay-open { @apply opacity-100 visible; }
218
+ .drawer-backdrop { @apply fixed inset-0 bg-black/80; }
219
+ .drawer-content { @apply fixed inset-x-0 bottom-0 z-10 mt-24 flex flex-col gap-4 rounded-t-lg border bg-background p-6 translate-y-full; transition: transform 300ms ease; }
220
+ .drawer-overlay-open .drawer-content { @apply translate-y-0; }
221
+ .drawer-handle { @apply mx-auto h-2 w-24 rounded-full bg-muted; }
222
+
223
+ /* ---- Hover Card (pure CSS :hover, richer than a tooltip) ---- */
224
+ .hover-card { @apply relative inline-flex; }
225
+ .hover-card-trigger { @apply underline underline-offset-4 cursor-pointer; }
226
+ .hover-card-content { @apply pointer-events-none absolute top-full left-0 z-50 mt-2 w-64 rounded-md border bg-popover p-4 text-popover-foreground shadow-md flex flex-col gap-2 opacity-0 invisible; transition: opacity 200ms ease, visibility 200ms ease; }
227
+ .hover-card:hover .hover-card-content { @apply opacity-100 visible; }
228
+
229
+ /* ---- Select (trigger shows the value; items check when selected) ---- */
230
+ .select-wrap { @apply relative inline-flex flex-col w-full max-w-xs; }
231
+ .select-backdrop { @apply fixed inset-0 z-40; }
232
+ .select-trigger { @apply flex h-9 w-full items-center justify-between gap-2 rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm outline-none transition-[color,box-shadow] duration-200 focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50; }
233
+ .select-content { @apply absolute top-full left-0 z-50 mt-1 w-full min-w-32 rounded-md border bg-popover p-1 text-popover-foreground shadow-md flex flex-col origin-top opacity-0 invisible scale-95; transition: opacity 150ms ease, transform 150ms ease, visibility 150ms ease; }
234
+ .select-content-open { @apply opacity-100 visible scale-100; }
235
+ .select-item { @apply flex flex-row items-center justify-between gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors duration-200 hover:bg-accent hover:text-accent-foreground; }
236
+
237
+ /* ---- Scroll Area (styled scrollbars; max-h-72 default - eject the part to change the height) ---- */
238
+ .scroll-area { @apply relative overflow-auto max-h-72; }
239
+ .scroll-area::-webkit-scrollbar { width: 8px; height: 8px; }
240
+ .scroll-area::-webkit-scrollbar-thumb { border-radius: 9999px; background: var(--color-border); }
241
+ .scroll-area::-webkit-scrollbar-track { background: transparent; }
242
+
243
+ /* ---- Chat: Message / Bubble / Marker / Attachment / Message Scroller ---- */
244
+ .message-scroller { @apply flex flex-col gap-3 overflow-auto max-h-96 p-4; }
245
+ .message { @apply flex flex-row items-end gap-2; }
246
+ .message-sent { @apply flex-row-reverse; }
247
+ .bubble { @apply max-w-[80%] rounded-lg px-3 py-2 text-sm; }
248
+ .bubble-sent { @apply bg-primary text-primary-foreground; }
249
+ .bubble-received { @apply bg-muted text-foreground; }
250
+ .marker { @apply flex flex-row items-center justify-center gap-2 my-1 text-xs text-muted-foreground; }
251
+ .attachment { @apply inline-flex flex-row items-center gap-2 rounded-md border bg-muted/40 px-3 py-2 text-sm; }
252
+ .attachment-name { @apply font-medium leading-tight; }
253
+ .attachment-size { @apply text-xs text-muted-foreground leading-tight; }
254
+
255
+ /* ---- Menubar (a bar of menus; page owns which is open via a text state) ---- */
256
+ .menubar { @apply inline-flex flex-row items-center gap-1 rounded-md border bg-background p-1 shadow-sm; }
257
+ .menubar-item { @apply relative; }
258
+ .menubar-trigger { @apply flex flex-row items-center rounded-sm px-3 py-1 text-sm font-medium outline-none transition-colors duration-200 hover:bg-accent hover:text-accent-foreground; }
259
+ .menubar-trigger-active { @apply bg-accent text-accent-foreground; }
260
+ .menubar-content { @apply absolute top-full left-0 z-50 mt-1 min-w-48 rounded-md border bg-popover p-1 text-popover-foreground shadow-md flex flex-col origin-top opacity-0 invisible scale-95; transition: opacity 150ms ease, transform 150ms ease, visibility 150ms ease; }
261
+ .menubar-content-open { @apply opacity-100 visible scale-100; }
262
+
263
+ /* ---- Navigation Menu (hover-driven, pure CSS) ---- */
264
+ .nav-menu { @apply flex flex-row items-center gap-1; }
265
+ .nav-menu-item { @apply relative; }
266
+ .nav-menu-trigger { @apply flex flex-row items-center gap-1 rounded-md px-3 py-2 text-sm font-medium transition-colors duration-200 hover:bg-accent hover:text-accent-foreground cursor-default; }
267
+ .nav-menu-content { @apply absolute top-full left-0 z-50 mt-1.5 min-w-56 rounded-md border bg-popover p-2 text-popover-foreground shadow-md flex flex-col gap-1 opacity-0 invisible translate-y-1; transition: opacity 200ms ease, transform 200ms ease, visibility 200ms ease; }
268
+ .nav-menu-item:hover .nav-menu-content { @apply opacity-100 visible translate-y-0; }
269
+ .nav-menu-link { @apply flex flex-col gap-1 rounded-md p-3 outline-none transition-colors duration-200 hover:bg-accent; }
270
+ .nav-menu-link-title { @apply text-sm font-medium leading-none; }
271
+ .nav-menu-link-text { @apply text-sm text-muted-foreground leading-snug; }
272
+
273
+ /* ---- Combobox (a Select with a search box; page filters items with `where`) ---- */
274
+ .combobox-trigger { @apply flex h-9 w-full items-center justify-between gap-2 rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm outline-none transition-[color,box-shadow] duration-200 hover:bg-accent/40 focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50; }
275
+ .combobox-content { @apply absolute top-full left-0 z-50 mt-1 w-full rounded-md border bg-popover text-popover-foreground shadow-md flex flex-col origin-top opacity-0 invisible scale-95; transition: opacity 150ms ease, transform 150ms ease, visibility 150ms ease; }
276
+ .combobox-content-open { @apply opacity-100 visible scale-100; }
277
+ .combobox-search { @apply flex h-9 w-full border-b border-border bg-transparent px-3 text-sm outline-none placeholder:text-muted-foreground; }
278
+ .combobox-list { @apply flex flex-col p-1 overflow-auto max-h-60; }
279
+ .combobox-empty { @apply py-6 text-center text-sm text-muted-foreground; }
280
+
281
+ /* ---- Command (a command palette: search + grouped item list) ---- */
282
+ .command { @apply flex flex-col rounded-lg border bg-popover text-popover-foreground shadow-md overflow-hidden; }
283
+ .command-input { @apply flex h-11 w-full border-b border-border bg-transparent px-3 text-sm outline-none placeholder:text-muted-foreground; }
284
+ .command-list { @apply flex flex-col p-1 overflow-auto max-h-72; }
285
+ .command-group-label { @apply px-2 py-1.5 text-xs font-medium text-muted-foreground; }
286
+ .command-item { @apply flex flex-row items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors duration-200 hover:bg-accent hover:text-accent-foreground cursor-pointer; }
287
+ .command-shortcut { @apply ml-auto text-xs tracking-widest text-muted-foreground; }
288
+
289
+ /* ---- Table - style muten's DataTable to look shadcn: DataTable @rows columns(...) class("data-table") ---- */
290
+ .data-table { @apply w-full caption-bottom text-sm; }
291
+ .data-table thead tr { @apply border-b; }
292
+ .data-table th { @apply h-11 px-3 text-left align-middle font-medium text-muted-foreground whitespace-nowrap; }
293
+ .data-table tbody tr { @apply border-b transition-colors duration-200 hover:bg-muted/50; }
294
+ .data-table tbody tr:last-child { @apply border-0; }
295
+ .data-table td { @apply p-3 align-middle whitespace-nowrap; }
296
+
297
+ /* ---- Slider (Custom; the host JS builds .slider-range + .slider-thumb inside .slider) ---- */
298
+ .slider { @apply relative h-2 w-full rounded-full bg-primary/20 cursor-pointer select-none; touch-action: none; }
299
+ .slider-range { @apply absolute top-0 left-0 h-full rounded-full bg-primary; }
300
+ .slider-thumb { @apply absolute top-1/2 size-4 -translate-x-1/2 -translate-y-1/2 rounded-full border-2 border-primary bg-background shadow transition-transform duration-100; }
301
+
302
+ /* ---- Input OTP (Custom; host JS builds N .otp-slot inputs) ---- */
303
+ .otp { @apply flex flex-row items-center gap-2; }
304
+ .otp-slot { @apply size-10 rounded-md border border-input bg-transparent text-center text-sm shadow-sm outline-none transition-[color,box-shadow] duration-200 focus:border-ring focus:ring-[3px] focus:ring-ring/50; }
305
+
306
+ /* ---- Calendar (Custom; variants: mode single|range|multiple, months 1-2, caption label|dropdown) ---- */
307
+ .calendar { @apply relative inline-flex flex-col rounded-lg border bg-card p-3 text-card-foreground; }
308
+ .calendar-nav-prev { @apply absolute top-3 left-3 z-10 inline-flex size-7 items-center justify-center rounded-md border border-input text-sm outline-none transition-colors duration-200 hover:bg-accent; }
309
+ .calendar-nav-next { @apply absolute top-3 right-3 z-10 inline-flex size-7 items-center justify-center rounded-md border border-input text-sm outline-none transition-colors duration-200 hover:bg-accent; }
310
+ .calendar-months { @apply flex flex-row gap-4; }
311
+ .calendar-month { @apply flex flex-col gap-3; }
312
+ .calendar-caption { @apply flex h-7 flex-row items-center justify-center gap-1; }
313
+ .calendar-label { @apply text-sm font-medium; }
314
+ .calendar-select { @apply rounded-md border border-input bg-transparent px-2 py-1 text-sm outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50; }
315
+ .calendar-grid { @apply grid grid-cols-7 gap-1; }
316
+ .calendar-dow { @apply flex size-9 items-center justify-center text-xs font-medium text-muted-foreground; }
317
+ .calendar-day { @apply flex size-9 items-center justify-center rounded-md text-sm outline-none transition-colors duration-200 hover:bg-accent; }
318
+ .calendar-day-today { @apply bg-accent text-accent-foreground; }
319
+ .calendar-day-range { @apply bg-accent text-accent-foreground rounded-none; }
320
+ .calendar-day-selected { @apply bg-primary text-primary-foreground hover:bg-primary; }
321
+
322
+ /* ---- Toaster / Toast (Custom; page bumps a trigger counter, the host shows + auto-dismisses) ---- */
323
+ .toaster { @apply fixed bottom-4 right-4 z-[100] flex flex-col gap-2 w-80 max-w-[calc(100vw-2rem)]; }
324
+ .toast { @apply rounded-md border bg-background p-4 shadow-lg flex flex-col gap-1 translate-x-full opacity-0; transition: transform 200ms ease, opacity 200ms ease; }
325
+ .toast-show { @apply translate-x-0 opacity-100; }
326
+ .toast-destructive { @apply border-destructive/50 bg-destructive text-destructive-foreground; }
327
+ .toast-title { @apply text-sm font-semibold; }
328
+ .toast-description { @apply text-sm opacity-90; }
329
+
330
+ /* ---- Carousel (Custom; host renders slides from a list + prev/next) ---- */
331
+ .carousel { @apply relative w-full max-w-xs; }
332
+ .carousel-viewport { @apply overflow-hidden rounded-lg; }
333
+ .carousel-track { @apply flex flex-row; transition: transform 300ms ease; }
334
+ .carousel-slide { @apply flex min-w-full aspect-square items-center justify-center rounded-lg border bg-card text-4xl font-semibold select-none; }
335
+ .carousel-prev { @apply absolute top-1/2 -left-4 -translate-y-1/2 inline-flex size-8 items-center justify-center rounded-full border border-input bg-background shadow outline-none transition-colors duration-200 hover:bg-accent; }
336
+ .carousel-next { @apply absolute top-1/2 -right-4 -translate-y-1/2 inline-flex size-8 items-center justify-center rounded-full border border-input bg-background shadow outline-none transition-colors duration-200 hover:bg-accent; }
337
+
338
+ /* ---- Chart (Custom; host renders an SVG bar chart from a list of { label, value }) ---- */
339
+ .chart { @apply w-full max-w-lg; }
340
+ .chart-svg { @apply w-full h-auto; }
341
+ .chart-bar { fill: var(--color-primary); transition: opacity 200ms ease; }
342
+ .chart-bar:hover { opacity: 0.8; }
343
+ .chart-label { fill: var(--color-muted-foreground); font-size: 11px; }
344
+
345
+ /* ---- Resizable (Custom; two panels + a draggable splitter) ---- */
346
+ .resizable { @apply flex flex-row w-full h-48 rounded-lg border overflow-hidden; }
347
+ .resizable-panel { @apply flex items-center justify-center bg-card text-sm font-medium; }
348
+ .resizable-handle { @apply w-1.5 shrink-0 bg-border cursor-col-resize transition-colors duration-200 hover:bg-primary; }
349
+
350
+ /* ---- Context Menu (Custom; right-click an area to open a menu of items) ---- */
351
+ .context-menu-wrap { @apply relative; }
352
+ .context-area { @apply flex h-40 w-full items-center justify-center rounded-lg border border-dashed text-sm text-muted-foreground select-none; }
353
+ .context-menu { @apply absolute z-50 min-w-40 rounded-md border bg-popover p-1 text-popover-foreground shadow-md flex flex-col opacity-0 invisible; transition: opacity 150ms ease, visibility 150ms ease; }
354
+ .context-menu-open { @apply opacity-100 visible; }
355
+ .context-item { @apply flex rounded-sm px-2 py-1.5 text-sm text-left outline-none transition-colors duration-200 hover:bg-accent hover:text-accent-foreground; }
356
+
357
+ /* ---- Typography (prose scale) ---- */
358
+ .prose-h1 { @apply scroll-m-20 text-4xl font-extrabold tracking-tight lg:text-5xl; }
359
+ .prose-h2 { @apply scroll-m-20 border-b pb-2 text-3xl font-semibold tracking-tight first:mt-0; }
360
+ .prose-h3 { @apply scroll-m-20 text-2xl font-semibold tracking-tight; }
361
+ .prose-h4 { @apply scroll-m-20 text-xl font-semibold tracking-tight; }
362
+ .prose-p { @apply leading-7; }
363
+ .prose-lead { @apply text-xl text-muted-foreground; }
364
+ .prose-large { @apply text-lg font-semibold; }
365
+ .prose-small { @apply text-sm font-medium leading-none; }
366
+ .prose-muted { @apply text-sm text-muted-foreground; }
367
+ .prose-blockquote { @apply mt-6 border-l-2 pl-6 italic; }
368
+ .prose-code { @apply relative rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-sm font-semibold; }
369
+ .prose-list { @apply my-6 ml-6 list-disc; }
370
+
371
+ /* ---- Button Group / Input Group (flex-row explicit: they sit on a Stack, a flex-column landmark) ---- */
372
+ .btn-group { @apply inline-flex flex-row items-center; }
373
+ .btn-group > * { @apply rounded-none shadow-none; }
374
+ .btn-group > *:first-child { @apply rounded-l-md; }
375
+ .btn-group > *:last-child { @apply rounded-r-md; }
376
+ .btn-group > * + * { @apply -ml-px; }
377
+ .input-group { @apply flex flex-row h-9 w-full items-center gap-2 rounded-md border border-input bg-transparent px-3 text-sm shadow-sm outline-none transition-[color,box-shadow] duration-200 focus-within:border-ring focus-within:ring-[3px] focus-within:ring-ring/50; }
378
+ .input-group-control { @apply h-full flex-1 border-0 bg-transparent p-0 text-sm shadow-none outline-none placeholder:text-muted-foreground; }
379
+ }
package/package.json ADDED
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "@muten/shadcn",
3
+ "version": "0.0.1",
4
+ "description": "shadcn-style component registry for muten - own-the-source parts you add with `muten add`.",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/mutenorg/muten.git",
8
+ "directory": "plugins/shadcn"
9
+ },
10
+ "publishConfig": {
11
+ "access": "public"
12
+ },
13
+ "type": "module",
14
+ "files": ["registry", "registry.json", "globals.css", "README.md"],
15
+ "exports": {
16
+ "./globals.css": "./globals.css",
17
+ "./registry.json": "./registry.json",
18
+ "./registry/*": "./registry/*"
19
+ },
20
+ "keywords": ["muten", "muten-plugin", "muten-registry", "components", "ui", "shadcn"],
21
+ "license": "MIT"
22
+ }
@@ -0,0 +1,16 @@
1
+ # Accordion - stacked expandable sections. The PAGE owns a bool per item + a toggle action per item:
2
+ # state { a1 = true : bool a2 = false : bool } action t1 mutates a1 { a1.toggle() }
3
+ # Accordion { AccordionItem(title: "…", open: a1, onToggle: t1) { … } AccordionItem(title: "…", open: a2, onToggle: t2) { … } }
4
+ # shadcn-for-muten · part · no deps · the content always renders and animates open via the .accordion-panel grid trick.
5
+ part Accordion() { Stack class("accordion") { slot } }
6
+ part AccordionItem(title: text, open: bool, onToggle: action) {
7
+ Stack class("accordion-item") {
8
+ Button -> $onToggle aria(expanded: $open) class("accordion-trigger") {
9
+ Span "{$title}"
10
+ Icon "lucide:chevron-down" class("accordion-chevron", "accordion-chevron-open" when $open)
11
+ }
12
+ Stack class("accordion-panel", "accordion-panel-open" when $open) {
13
+ Stack class("accordion-content") { slot }
14
+ }
15
+ }
16
+ }
@@ -0,0 +1,13 @@
1
+ # Alert Dialog - a modal that REQUIRES an explicit choice (no backdrop dismiss). The PAGE owns the bool:
2
+ # state { open = false : bool } action confirm mutates open { open.set(false) … } action cancel mutates open { open.set(false) }
3
+ # AlertDialog(open: open) { AlertDialogTitle(label) AlertDialogDescription(label) AlertDialogFooter { Button "Cancel" -> cancel … } }
4
+ # shadcn-for-muten · part · no deps · reuses the .dialog* classes; the backdrop is a plain Stack (not clickable).
5
+ part AlertDialog(open: bool) {
6
+ Stack class("dialog-overlay", "dialog-overlay-open" when $open) {
7
+ Stack class("dialog-backdrop")
8
+ Stack class("dialog-content") { slot }
9
+ }
10
+ }
11
+ part AlertDialogTitle(label: text) { Title "{$label}" h2 class("dialog-title") }
12
+ part AlertDialogDescription(label: text) { Span "{$label}" class("dialog-description") }
13
+ part AlertDialogFooter() { Stack class("dialog-footer") { slot } }
@@ -0,0 +1,8 @@
1
+ # Alert + sub-components. variant: default | destructive.
2
+ # Compose: Alert(variant) { Icon … ; Stack { AlertTitle(label) AlertDescription(label) } }
3
+ # shadcn-for-muten · part · no deps · styles via the semantic .alert* classes in globals.css.
4
+ part Alert(variant: text) {
5
+ Section class("alert", "alert-default" when $variant == "default", "alert-destructive" when $variant == "destructive") { slot }
6
+ }
7
+ part AlertTitle(label: text) { Span "{$label}" class("alert-title") }
8
+ part AlertDescription(label: text) { Span "{$label}" class("alert-description") }
@@ -0,0 +1,12 @@
1
+ # Attachment - a file attachment chip (icon + name + size).
2
+ # Attachment(name: "report.pdf", size: "2.4 MB")
3
+ # shadcn-for-muten · part · no deps · styles via the semantic .attachment* classes in globals.css.
4
+ part Attachment(name: text, size: text) {
5
+ Stack class("attachment") {
6
+ Icon "lucide:paperclip" class("size-4 text-muted-foreground shrink-0")
7
+ Stack class("flex flex-col") {
8
+ Span "{$name}" class("attachment-name")
9
+ Span "{$size}" class("attachment-size")
10
+ }
11
+ }
12
+ }
@@ -0,0 +1,7 @@
1
+ # Avatar - a user's initials in a circle (AvatarFallback). Pass a name; built-in initial() takes the first letter.
2
+ # shadcn-for-muten · part · no deps · styles via the semantic .avatar* classes. For a photo, swap the inner Span for `Image "{$src}" alt("{$name}")`.
3
+ part Avatar(name: text) {
4
+ Stack class("avatar") {
5
+ Stack class("avatar-fallback") { Span "{initial($name)}" }
6
+ }
7
+ }
@@ -0,0 +1,9 @@
1
+ # Badge - a small status descriptor. variant: default | secondary | destructive | outline.
2
+ # shadcn-for-muten · part · no deps · styles via the semantic .badge* classes in globals.css.
3
+ part Badge(label: text, variant: text) {
4
+ Span "{$label}" class("badge",
5
+ "badge-default" when $variant == "default",
6
+ "badge-secondary" when $variant == "secondary",
7
+ "badge-destructive" when $variant == "destructive",
8
+ "badge-outline" when $variant == "outline")
9
+ }