@muten/shadcn 0.1.1 → 0.1.3

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 (48) hide show
  1. package/AGENTS.md +69 -1
  2. package/README.md +7 -0
  3. package/kit.css +2406 -24
  4. package/motion.css +15 -0
  5. package/package.json +3 -2
  6. package/registry/accordion.muten +10 -0
  7. package/registry/agenda.js +72 -24
  8. package/registry/agenda.muten +30 -2
  9. package/registry/band.muten +1 -1
  10. package/registry/bio-play.muten +16 -0
  11. package/registry/booking-play.muten +43 -0
  12. package/registry/button.muten +3 -2
  13. package/registry/chat-play.muten +77 -0
  14. package/registry/day-plan.muten +40 -0
  15. package/registry/device-frame.muten +25 -7
  16. package/registry/gradient-blinds.js +88 -89
  17. package/registry/gradient-blinds.muten +11 -4
  18. package/registry/heatmap.muten +30 -0
  19. package/registry/inbox-play.muten +56 -0
  20. package/registry/input-group.muten +3 -1
  21. package/registry/kit/collections.js +10 -2
  22. package/registry/kit/core.js +519 -99
  23. package/registry/kit/structure.js +3 -3
  24. package/registry/kit-widget.js +1 -1
  25. package/registry/legal-doc.muten +27 -0
  26. package/registry/modal-behavior.js +11 -0
  27. package/registry/modal.muten +1 -1
  28. package/registry/order-play.muten +33 -0
  29. package/registry/permission-matrix.muten +31 -5
  30. package/registry/phone-bar.muten +18 -0
  31. package/registry/phone-shell.muten +1 -1
  32. package/registry/product-play.muten +20 -0
  33. package/registry/progress.muten +5 -2
  34. package/registry/rank-item.muten +32 -0
  35. package/registry/screen-tour.js +59 -0
  36. package/registry/screen-tour.muten +10 -0
  37. package/registry/share-bar.muten +22 -0
  38. package/registry/sidebar.muten +9 -0
  39. package/registry/site-footer.muten +37 -0
  40. package/registry/stacked-bars.js +75 -0
  41. package/registry/stacked-bars.muten +13 -0
  42. package/registry/stamp-card.muten +5 -2
  43. package/registry/stat-band.muten +5 -1
  44. package/registry/stat.muten +19 -7
  45. package/registry/veil.muten +18 -0
  46. package/registry/zoom-control.muten +3 -3
  47. package/registry.json +116 -0
  48. package/scripts/reuse-audit.mjs +37 -0
@@ -0,0 +1,18 @@
1
+ # Veil - a calm note over a card's content while it has nothing to show yet: the content stays behind, faded (ghost
2
+ # rows, an empty chart), and a title and a line say what will appear. With `on` false it is the content alone.
3
+ # Veil(on: noSales, title: "Nada vendido todavía", text: "Lo que más te reserven y te compren aparece aquí.") { … }
4
+ # StackedBars and Heatmap carry their own (empty / emptyTitle); use Veil for anything else.
5
+ # shadcn-for-muten · part · no deps · kit.css (.cx-veil).
6
+ part Veil(on: bool, title: text = "", text: text = "") {
7
+ Stack class("cx-veil") data(on: $on) {
8
+ Stack class("cx-veil-body") aria(hidden: $on) { slot }
9
+ when $on {
10
+ Stack class("cx-veil-note") {
11
+ Stack class("cx-veil-box") {
12
+ Span "{$title}" class("cx-veil-t")
13
+ Span "{$text}" class("cx-veil-s")
14
+ }
15
+ }
16
+ }
17
+ }
18
+ }
@@ -4,8 +4,8 @@
4
4
  # shadcn-for-muten · part · no deps · kit.css (.cx-zoom).
5
5
  part ZoomControl(value: number, onOut: action, onIn: action, onReset: action) {
6
6
  Stack class("cx-zoom") data(slot: "zoom-control") aria(role: "group", label: "Zoom") {
7
- Button -> $onOut class("cn-button cn-button-variant-ghost cn-button-size-icon-sm group/button") data(name: "Alejar") aria(label: "Alejar") { Icon "lucide:minus" }
8
- Button "{$value} %" -> $onReset class("cn-button cn-button-variant-ghost cn-button-size-sm group/button cx-zoom-value") data(name: "Volver a 100 %") aria(label: "Zoom {$value} %, volver a 100")
9
- Button -> $onIn class("cn-button cn-button-variant-ghost cn-button-size-icon-sm group/button") data(name: "Acercar") aria(label: "Acercar") { Icon "lucide:plus" }
7
+ Btn(variant: "ghost", size: "icon-sm", label: "Alejar", onClick: $onOut) { Icon "lucide:minus" }
8
+ Button "{$value} %" -> $onReset class("cn-button cn-button-variant-ghost cn-button-size-sm group/button cx-zoom-value") data(name: "Volver a 100 %") aria(label: "Zoom {$value} %, volver a 100") # its words carry $value: a part arg cannot interpolate an outer param, so it stays a Button
9
+ Btn(variant: "ghost", size: "icon-sm", label: "Acercar", onClick: $onIn) { Icon "lucide:plus" }
10
10
  }
11
11
  }
package/registry.json CHANGED
@@ -93,6 +93,20 @@
93
93
  "file": "registry/bento.muten",
94
94
  "description": "BLOCK: fixed-row-height tiling gallery; cells pick their col-span. Parts: BentoGallery, BentoCell."
95
95
  },
96
+ {
97
+ "name": "bio-play",
98
+ "deps": [],
99
+ "part": "BioPlay",
100
+ "file": "registry/bio-play.muten",
101
+ "description": "BioPlay - a social profile shown, not used: the photo, the handle, the followers, the bio with the store's link"
102
+ },
103
+ {
104
+ "name": "booking-play",
105
+ "deps": [],
106
+ "part": "BookingPlay",
107
+ "file": "registry/booking-play.muten",
108
+ "description": "BookingPlay - a booking shown, not used: one service, a few days and hours, and a booking that plays itself in a"
109
+ },
96
110
  {
97
111
  "name": "booking",
98
112
  "deps": [],
@@ -162,6 +176,13 @@
162
176
  "component": "Carousel",
163
177
  "description": "Carousel(label, perView, center, loop, autoplay, indicator, arrows) { CarouselSlide }: one component, options instead of copies."
164
178
  },
179
+ {
180
+ "name": "chat-play",
181
+ "deps": [],
182
+ "part": "ChatPlay",
183
+ "file": "registry/chat-play.muten",
184
+ "description": "ChatPlay - a conversation shown, not used: the messages come in one by one (in `step` order, 1-6) and the"
185
+ },
165
186
  {
166
187
  "name": "chatbot",
167
188
  "deps": [
@@ -258,6 +279,13 @@
258
279
  "component": "DateField",
259
280
  "description": "DateField(label, value, min, years, onChange): the long date; opens the date wheels."
260
281
  },
282
+ {
283
+ "name": "day-plan",
284
+ "deps": [],
285
+ "part": "DayPlan",
286
+ "file": "registry/day-plan.muten",
287
+ "description": "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"
288
+ },
261
289
  {
262
290
  "name": "device-frame",
263
291
  "deps": [],
@@ -386,6 +414,13 @@
386
414
  "component": "ImageUpload",
387
415
  "description": "ImageUpload(title, hint, maxMb, size, uploadUrl, sample, onReady): validate, decode, crop, shrink, upload with progress."
388
416
  },
417
+ {
418
+ "name": "inbox-play",
419
+ "deps": [],
420
+ "part": "InboxPlay",
421
+ "file": "registry/inbox-play.muten",
422
+ "description": "InboxPlay - a messages list shown, not used: the shop's inbox as the channel's app draws it (skin: instagram |"
423
+ },
389
424
  {
390
425
  "name": "indicator",
391
426
  "deps": [],
@@ -578,6 +613,20 @@
578
613
  "file": "registry/navpill.muten",
579
614
  "description": "BLOCK: the floating glass navbar (sticky wrapper + glass pill + scroll animation baked in). Drop logo/links/CTA inside."
580
615
  },
616
+ {
617
+ "name": "legal-doc",
618
+ "deps": [],
619
+ "part": "DocHead",
620
+ "file": "registry/legal-doc.muten",
621
+ "description": "A legal document (terms, privacy): the title and its line, the contents that follow the reading, the sections at reading width."
622
+ },
623
+ {
624
+ "name": "phone-bar",
625
+ "deps": [],
626
+ "part": "PhoneBar",
627
+ "file": "registry/phone-bar.muten",
628
+ "description": "The app's bar at the bottom of a phone: three fixed cells (menu · the screen's tools · its create). Screens hand it their toolbar buttons on a phone."
629
+ },
581
630
  {
582
631
  "name": "new-appointment",
583
632
  "deps": [],
@@ -602,6 +651,13 @@
602
651
  "component": "OrderBoard",
603
652
  "description": "OrderBoard(lanes, cards, onChange, onAction) / OrderCards(cards, sizes): order board and card with its modals."
604
653
  },
654
+ {
655
+ "name": "order-play",
656
+ "deps": [],
657
+ "part": "OrderPlay",
658
+ "file": "registry/order-play.muten",
659
+ "description": "OrderPlay - an order shown, not used: who ordered, the products with their photos, how it is paid and delivered,"
660
+ },
605
661
  {
606
662
  "name": "overlay-behavior",
607
663
  "deps": [],
@@ -689,6 +745,13 @@
689
745
  "file": "registry/pricing.muten",
690
746
  "description": "BLOCK: a row of pricing tiers (name/price/features/CTA), featured highlight. Parts: Pricing, PricingTier, PricingFeature."
691
747
  },
748
+ {
749
+ "name": "product-play",
750
+ "deps": [],
751
+ "part": "ProductPlay",
752
+ "file": "registry/product-play.muten",
753
+ "description": "ProductPlay - a product sheet shown, not used: the photo, the shop, the name with its price, a line about it, its"
754
+ },
692
755
  {
693
756
  "name": "progress",
694
757
  "deps": [],
@@ -759,6 +822,14 @@
759
822
  "component": "ScaleSlider",
760
823
  "description": "ScaleSlider(label, min, max, step, values, gap, format, onChange): value rides on the thumb; two bubbles merge."
761
824
  },
825
+ {
826
+ "name": "screen-tour",
827
+ "deps": [],
828
+ "part": "ScreenTour",
829
+ "file": "registry/screen-tour.muten",
830
+ "component": "ScreenTour",
831
+ "description": "ScreenTour - how a product is used, played on its real screens: numbered steps on one side, a phone on the other"
832
+ },
762
833
  {
763
834
  "name": "scroll-area",
764
835
  "deps": [],
@@ -1066,6 +1137,51 @@
1066
1137
  "part": "ZoomControl",
1067
1138
  "file": "registry/zoom-control.muten",
1068
1139
  "description": "A - 100% + zoom widget; page owns the zoom number."
1140
+ },
1141
+ {
1142
+ "name": "site-footer",
1143
+ "deps": [],
1144
+ "part": "SiteFooter",
1145
+ "file": "registry/site-footer.muten",
1146
+ "description": "A site's footer: the brand and its two ways in over a hairline, every group of links on one row, then a bar with the company and contacts (FooterHead, FooterBrand, FooterActions, FooterLinks, FooterCol, FooterBar)."
1147
+ },
1148
+ {
1149
+ "name": "share-bar",
1150
+ "deps": [],
1151
+ "part": "ShareBar",
1152
+ "file": "registry/share-bar.muten",
1153
+ "description": "How one whole splits: a bar at 100 % cut into its parts, each in its tone, with each part's value and name under it."
1154
+ },
1155
+ {
1156
+ "name": "heatmap",
1157
+ "deps": [],
1158
+ "part": "Heatmap",
1159
+ "file": "registry/heatmap.muten",
1160
+ "description": "Rows × columns (weekday × hour), each cell darker the more it holds, a less…more scale, a note while empty."
1161
+ },
1162
+ {
1163
+ "name": "stacked-bars",
1164
+ "deps": [],
1165
+ "part": "StackedBars",
1166
+ "file": "registry/stacked-bars.muten",
1167
+ "description": "Bars over time with series stacked, an axis in the page's unit, a bar you can pick, an empty note and a skeleton.",
1168
+ "component": "StackedBars"
1169
+ },
1170
+ {
1171
+ "name": "rank-item",
1172
+ "deps": [
1173
+ "progress"
1174
+ ],
1175
+ "part": "RankItem",
1176
+ "file": "registry/rank-item.muten",
1177
+ "description": "A ranking row or a thing to act on: a lead (rank or count), title, tag, a line, an optional share bar, the value, and an action in the slot."
1178
+ },
1179
+ {
1180
+ "name": "veil",
1181
+ "deps": [],
1182
+ "part": "Veil",
1183
+ "file": "registry/veil.muten",
1184
+ "description": "A calm note (title and line) over a card's content while it has nothing to show yet; the content stays faded behind."
1069
1185
  }
1070
1186
  ],
1071
1187
  "classComponents": {
@@ -0,0 +1,37 @@
1
+ // reuse-audit.mjs - atomic design kept honest: before something is written by hand, the library may already have it.
2
+ // node scripts/reuse-audit.mjs the library's own parts (from plugins/shadcn)
3
+ // node node_modules/@muten/shadcn/scripts/reuse-audit.mjs src an app's .muten files (from the app)
4
+ // It lists the markup that re-draws an existing piece: a Button wearing cn-button classes where Btn would do, a
5
+ // SearchField wearing cn-input where Input/GroupInput exist, a hand-made avatar, a phone or device frame of its own
6
+ // (DeviceFrame is the one phone of the library). A finding is not always wrong (a tab passes its value to the action,
7
+ // a close button carries its own role): each one is a question to answer before adding more.
8
+ import fs from 'node:fs';
9
+ import path from 'node:path';
10
+
11
+ const REG = path.join(import.meta.dirname, '..', 'registry');
12
+ const OWN = { 'button.muten': true, 'input.muten': true, 'input-group.muten': true, 'avatar.muten': true, 'device-frame.muten': true, 'skeleton.muten': true, 'kit-skeleton.muten': true };
13
+ const RULES = [
14
+ { test: (l) => /\bButton\b/.test(l) && /->\s*[\w.$]+\s+class\("cn-button/.test(l) && !/aria\((pressed|role)|status:|marker:|"\{\$/.test(l), says: 'a Button with cn-button classes and a plain action: Btn(variant, size, label, onClick) does it' },
15
+ { re: /SearchField\b[^\n]*class\("cn-input/, says: 'a SearchField with cn-input classes: Input / GroupInput do it' },
16
+ { re: /class\("cn-avatar[ "]/, says: 'a hand-made avatar: Avatar / AvatarImage do it' },
17
+ { test: (l) => !/\bDeviceFrame\(/.test(l) && /class\("[^"]*\b(cx-phone-frame|cx-sphone[\w-]*|plt-phone|phone-frame|device-frame|iphone|bezel|notch)\b/.test(l), says: 'a phone frame of its own: DeviceFrame is the one phone of the library (island, href, shot, width)' },
18
+ { re: /class\("[^"]*\b(cx-sk|cn-skeleton)(?=[\s"])/, says: 'a skeleton drawn by hand: Skeleton / Skeletons(kind) / Loading do it' },
19
+ ];
20
+
21
+ const walk = (dir) => fs.readdirSync(dir, { withFileTypes: true }).flatMap((e) => {
22
+ if (e.name === 'node_modules' || e.name.startsWith('.')) return [];
23
+ const full = path.join(dir, e.name);
24
+ return e.isDirectory() ? walk(full) : e.name.endsWith('.muten') ? [full] : [];
25
+ });
26
+ const roots = process.argv.slice(2);
27
+ const files = roots.length ? roots.flatMap((r) => walk(path.resolve(r))) : fs.readdirSync(REG).filter((f) => f.endsWith('.muten') && !OWN[f]).map((f) => path.join(REG, f));
28
+
29
+ let total = 0;
30
+ for (const file of files.sort()) {
31
+ const lines = fs.readFileSync(file, 'utf8').split('\n');
32
+ lines.forEach((line, i) => {
33
+ if (line.trim().startsWith('#')) return;
34
+ for (const r of RULES) if (r.test ? r.test(line) : r.re.test(line)) { total++; console.log(`${path.relative(process.cwd(), file)}:${i + 1} ${r.says}\n ${line.trim().slice(0, 140)}`); }
35
+ });
36
+ }
37
+ console.log(total ? `\n${total} place(s) to look at.` : 'Nothing re-drawn by hand.');