tembro 3.1.16 → 3.1.17

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 (37) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/components/calendar/date-picker.d.ts +2 -1
  3. package/dist/components/display/statistic.d.ts +4 -2
  4. package/dist/components/modern/calendar-scheduler.d.ts +24 -1
  5. package/dist/components/ui/button/index.d.ts +2 -1
  6. package/dist/showcase/package-meta.d.ts +2 -2
  7. package/dist/src/components/calendar/date-picker.cjs +1 -1
  8. package/dist/src/components/calendar/date-picker.js +24 -22
  9. package/dist/src/components/display/statistic.cjs +1 -1
  10. package/dist/src/components/display/statistic.js +27 -23
  11. package/dist/src/components/modern/calendar-scheduler.cjs +1 -1
  12. package/dist/src/components/modern/calendar-scheduler.js +125 -59
  13. package/dist/src/components/ui/button/index.cjs +1 -1
  14. package/dist/src/components/ui/button/index.js +10 -9
  15. package/dist/src/showcase/package-meta.cjs +1 -1
  16. package/dist/src/showcase/package-meta.js +1 -1
  17. package/dist/src/showcase/premium/calendar-scheduler/mock.cjs +8 -4
  18. package/dist/src/showcase/premium/calendar-scheduler/mock.js +4 -4
  19. package/dist/src/showcase/premium/calendar-scheduler/showcase.cjs +1 -1
  20. package/dist/src/showcase/premium/calendar-scheduler/showcase.js +29 -5
  21. package/dist/src/showcase/site-data.cjs +1 -1
  22. package/dist/src/showcase/site-data.js +1 -1
  23. package/dist/src/showcase/tembro-registry.json.cjs +1 -1
  24. package/dist/src/showcase/tembro-registry.json.js +1 -1
  25. package/package.json +1 -1
  26. package/packages/cli/dist/index.cjs +1 -1
  27. package/packages/cli/vendor/src/components/calendar/date-picker.tsx +11 -7
  28. package/packages/cli/vendor/src/components/display/statistic.tsx +20 -15
  29. package/packages/cli/vendor/src/components/modern/calendar-scheduler.tsx +108 -48
  30. package/packages/cli/vendor/src/components/ui/button/index.tsx +3 -1
  31. package/packages/cli/vendor/src/showcase/package-meta.ts +1 -1
  32. package/packages/cli/vendor/src/showcase/premium/calendar-scheduler/mock.ts +10 -7
  33. package/packages/cli/vendor/src/showcase/premium/calendar-scheduler/showcase.tsx +7 -5
  34. package/packages/cli/vendor/src/showcase/tembro-registry.json +1 -1
  35. package/packages/cli/vendor/templates/showcase/src/showcase/layout/HeroSection.tsx +2 -2
  36. package/packages/cli/vendor/templates/showcase/src/showcase/layout/WorkbenchSidebar.tsx +1 -1
  37. package/registry.json +1 -1
@@ -1988,4 +1988,4 @@ ${t}`),i.forEach(n=>{let o=R[n],s=or(n),a=jn(n,o.category),c=o.registryDependenc
1988
1988
  ${s}`),a.sort().forEach(c=>{let d=R[c],l=or(c),u=jn(c,d.category),h=d.registryDependencies?.length?` -> ${d.registryDependencies.join(", ")}`:"";console.log(` ${c} [${l}/${u}]${h}`);});console.log(`
1989
1989
  Status:`),console.log(" stable = safe public API"),console.log(" preview = usable but API may change"),console.log(" experimental = not ready for stable app contracts"),console.log(" internal = helper metadata"),console.log(`
1990
1990
  Distribution:`),console.log(" foundation = small runtime-safe primitives, hooks, and helpers"),console.log(" source-copy = copy into app source with init/add and edit locally"),console.log(" system = larger product surfaces and patterns; prefer source-copy only"),console.log(`
1991
- Usage:`),console.log(` ${re("add button input data-table")}`),console.log(` ${re("add form --overwrite")}`),console.log(` ${re("list --category data-table")}`),console.log(` ${re("list --json")}`);}var mt=be(qe());function Ja(t){try{return mt.default.readJsonSync(t)}catch{return null}}function ow(t){try{return mt.default.readFileSync(t,"utf8")}catch{return null}}function FP(t,e){return e?L__default.default.resolve(t,e):null}function sw(t){let e=t.paths?.components??t.componentsPath??"src/components",r=t.paths?.ui??t.componentsPath??L__default.default.join(e,"ui"),i=t.paths?.lib??L__default.default.dirname(t.utilsPath??"src/lib/utils.ts"),n=t.paths?.hooks??"src/hooks",o=t.utilsPath??L__default.default.join(i,"utils.ts");return {componentsRoot:e,uiPath:r,libPath:i,hooksPath:n,utilsPath:o}}function jP(t,e){let r=sw(e);return t.replace("{components}",r.componentsRoot).replace("{ui}",r.uiPath).replace("{hooks}",r.hooksPath).replace("{lib}",r.libPath).replace("{utils}",r.utilsPath)}function iw(t,e){return ["tsconfig.app.json","tsconfig.json"].map(i=>L__default.default.join(t,i)).some(i=>!!Ja(i)?.compilerOptions?.paths?.[`${e}/*`])}function nw(t,e){return ["vite.config.ts","vite.config.js","vite.config.mjs"].map(i=>L__default.default.join(t,i)).some(i=>{let n=ow(i);return !!(n?.includes(`"${e}"`)||n?.includes(`'${e}'`))})}function aw(t,e){let r=Ja(L__default.default.join(t,"package.json")),i=new Set([...Object.keys(r?.dependencies??{}),...Object.keys(r?.devDependencies??{})]);return e.filter(n=>!i.has(n))}function Ya(t,e){return (t.files??[]).map(r=>({source:r.source,target:jP(r.target,e)}))}function NP(t,e){return At.map(r=>R[r]).filter(r=>r.category!=="lib").filter(r=>!r.migrationAliasFor).filter(r=>r.files?.length).filter(r=>Ya(r,e).some(i=>mt.default.existsSync(L__default.default.join(t,i.target))))}function qP(t,e){let r=NP(t,e),i=[],n=[],o=new Set;for(let s of r){for(let a of Ya(s,e))mt.default.existsSync(L__default.default.join(t,a.target))||i.push(`${s.name}: ${a.target}`);for(let a of s.registryDependencies??[]){let c=R[a];if(!c?.files?.length)continue;Ya(c,e).some(l=>mt.default.existsSync(L__default.default.join(t,l.target)))||n.push(`${s.name} -> ${a}`);}for(let a of s.dependencies??[])o.add(a);}return {copiedItems:r,missingFiles:i,missingRegistryDependencies:n,missingPackages:aw(t,Array.from(o).sort())}}function Ze(t,e){t.push(e);}function LP(t){return t?t.includes("/* tembro:start */")||t.includes("--aui-")||t.includes("--az-"):false}function cw(t={}){let e=process.cwd(),r=[],i=L__default.default.join(e,"package.json"),n=L__default.default.join(e,"tembro.json"),o=mt.default.existsSync(i),s=Ja(n);if(Ze(r,{name:"package.json",status:o?"pass":"fail",message:o?"package.json topildi.":"package.json topilmadi. Commandni React, Vite yoki Next project ichida ishlating."}),Ze(r,{name:"tembro.json",status:s?"pass":"fail",message:s?"tembro.json topildi.":`tembro.json topilmadi. Avval ${re("init")} ishlating.`}),s){let d=s.alias??"@",{componentsRoot:l,uiPath:u,libPath:h,hooksPath:p}=sw(s),f=s.cssPath??"src/index.css",b=[["components",l],["ui",u],["lib",h],["hooks",p]].filter(([,C])=>{let M=FP(e,C);return !M||!mt.default.existsSync(M)});Ze(r,{name:"paths",status:b.length?"warn":"pass",message:b.length?`Yetishmayotgan pathlar: ${b.map(([C,M])=>`${C}=${M}`).join(", ")}`:"Configured source paths mavjud."}),Ze(r,{name:"alias",status:iw(e,d)||nw(e,d)?"pass":"warn",message:iw(e,d)||nw(e,d)?`${d} alias tsconfig yoki vite configda topildi.`:`${d} alias topilmadi. Import pathlar ishlamasligi mumkin.`});let y=ow(L__default.default.join(e,f)),w=LP(y);Ze(r,{name:"theme-css",status:w?"pass":"warn",message:w?`${f} ichida Tembro tokenlari bor.`:`${f} ichida Tembro tokenlari topilmadi. ${re(`theme ${f}`)} ishlating.`});let S=qP(e,s);Ze(r,{name:"component-files",status:S.missingFiles.length?"warn":"pass",message:S.missingFiles.length?`Yetishmayotgan component fayllari: ${S.missingFiles.slice(0,8).join(", ")}${S.missingFiles.length>8?` (+${S.missingFiles.length-8})`:""}`:`${S.copiedItems.length} ta copied component file set tekshirildi.`}),Ze(r,{name:"registry-dependencies",status:S.missingRegistryDependencies.length?"warn":"pass",message:S.missingRegistryDependencies.length?`Yetishmayotgan registry dependencylar: ${S.missingRegistryDependencies.slice(0,8).join(", ")}${S.missingRegistryDependencies.length>8?` (+${S.missingRegistryDependencies.length-8})`:""}`:"Copied componentlar uchun registry dependencylar joyida."}),Ze(r,{name:"component-dependencies",status:S.missingPackages.length?"warn":"pass",message:S.missingPackages.length?`Copied componentlar uchun yetishmayotgan package dependencylar: ${S.missingPackages.join(", ")}`:"Copied component package dependencylari package.json ichida bor."});}if(o){let d=aw(e,["@fontsource-variable/geist","clsx","tailwind-merge","tw-animate-css"]);Ze(r,{name:"base-dependencies",status:d.length?"warn":"pass",message:d.length?`Yetishmayotgan dependencylar: ${d.join(", ")}`:"Base dependencylar package.json ichida bor."});}let a=r.filter(d=>d.status==="fail"),c=r.filter(d=>d.status==="warn");if(t.json){console.log(JSON.stringify({ok:a.length===0,checks:r},null,2));return}for(let d of r){let l=`${d.status.toUpperCase()} ${d.name}: ${d.message}`;d.status==="pass"&&x.success(l),d.status==="warn"&&x.warn(l),d.status==="fail"&&x.error(l);}if(a.length&&(x.error(`Doctor failed: ${a.length} fail, ${c.length} warn.`),process.exit(1)),c.length){x.warn(`Doctor completed with ${c.length} warning.`);return}x.success("Doctor passed. Project Tembro setup looks ready.");}var Ka=be(qe());async function lw(t){let e=process.cwd(),r=L__default.default.join(e,"tembro.json"),i=Ka.default.existsSync(r)?await Ka.default.readJson(r):{},n=t??i.cssPath??i.globalCssPath??"src/index.css",o=i.paths?.components??i.componentsPath??"src/components",s=await Mn({cwd:e,cssPath:n,componentsPath:o});x.success(`Tembro theme CSS yangilandi: ${s}`);}var dw={minimal:["button","input","card","badge"],dashboard:["button","input","card","badge","dropdown-menu","skeleton","tabs","page-state","filter-bar","description-list","info-card","data-table"]};async function uw(t,e={}){let r=dw[t];r||(x.error(`Preset topilmadi: ${t}`),process.exit(1)),await Dt(r,e);}var Qe=new $t;Qe.name("tembro").description("Tembro source-copy CLI").version("3.1.16");Qe.command("init").description("Initialize local Tembro source files in your project").option("--template <template>","Project defaults: vite or next").option("--skip-install","Do not install base dependencies").option("--showcase","Add every component and write a local Tembro workbench").option("-y, --defaults","Use template defaults without interactive prompts").action(Ky);Qe.command("preset").description("Add a source-copy preset to your project").argument("<name>","preset name: minimal or dashboard").option("-o, --overwrite","overwrite existing files").option("--dry-run","show files without writing").option("--skip-install","do not install package dependencies").action(uw);Qe.command("list").description("List available registry components").option("--category <category>","Filter by registry category").option("--status <status>","Filter by status: stable, preview, experimental, internal").option("--json","Print machine-readable JSON").action(rw);Qe.command("doctor").description("Check local project setup for Tembro source-copy usage").option("--json","Print machine-readable JSON").action(cw);Qe.command("add").description("Copy component source files into your project").argument("[components...]","components to add").option("-o, --overwrite","overwrite existing files").option("--dry-run","show files without writing").option("--plan","print machine-readable copy plan without writing").option("--skip-install","do not install package dependencies").action(Dt);Qe.command("showcase").description("Add every component and write a local Tembro workbench").option("-o, --overwrite","overwrite existing files").option("--dry-run","show files without writing").option("--skip-install","do not install package dependencies").action(t=>Dt(["showcase"],t));Qe.command("theme").description("Write or update Tembro theme CSS in your global CSS file").argument("[cssPath]","global CSS path, default from tembro.json or src/index.css").action(lw);Qe.parse();
1991
+ Usage:`),console.log(` ${re("add button input data-table")}`),console.log(` ${re("add form --overwrite")}`),console.log(` ${re("list --category data-table")}`),console.log(` ${re("list --json")}`);}var mt=be(qe());function Ja(t){try{return mt.default.readJsonSync(t)}catch{return null}}function ow(t){try{return mt.default.readFileSync(t,"utf8")}catch{return null}}function FP(t,e){return e?L__default.default.resolve(t,e):null}function sw(t){let e=t.paths?.components??t.componentsPath??"src/components",r=t.paths?.ui??t.componentsPath??L__default.default.join(e,"ui"),i=t.paths?.lib??L__default.default.dirname(t.utilsPath??"src/lib/utils.ts"),n=t.paths?.hooks??"src/hooks",o=t.utilsPath??L__default.default.join(i,"utils.ts");return {componentsRoot:e,uiPath:r,libPath:i,hooksPath:n,utilsPath:o}}function jP(t,e){let r=sw(e);return t.replace("{components}",r.componentsRoot).replace("{ui}",r.uiPath).replace("{hooks}",r.hooksPath).replace("{lib}",r.libPath).replace("{utils}",r.utilsPath)}function iw(t,e){return ["tsconfig.app.json","tsconfig.json"].map(i=>L__default.default.join(t,i)).some(i=>!!Ja(i)?.compilerOptions?.paths?.[`${e}/*`])}function nw(t,e){return ["vite.config.ts","vite.config.js","vite.config.mjs"].map(i=>L__default.default.join(t,i)).some(i=>{let n=ow(i);return !!(n?.includes(`"${e}"`)||n?.includes(`'${e}'`))})}function aw(t,e){let r=Ja(L__default.default.join(t,"package.json")),i=new Set([...Object.keys(r?.dependencies??{}),...Object.keys(r?.devDependencies??{})]);return e.filter(n=>!i.has(n))}function Ya(t,e){return (t.files??[]).map(r=>({source:r.source,target:jP(r.target,e)}))}function NP(t,e){return At.map(r=>R[r]).filter(r=>r.category!=="lib").filter(r=>!r.migrationAliasFor).filter(r=>r.files?.length).filter(r=>Ya(r,e).some(i=>mt.default.existsSync(L__default.default.join(t,i.target))))}function qP(t,e){let r=NP(t,e),i=[],n=[],o=new Set;for(let s of r){for(let a of Ya(s,e))mt.default.existsSync(L__default.default.join(t,a.target))||i.push(`${s.name}: ${a.target}`);for(let a of s.registryDependencies??[]){let c=R[a];if(!c?.files?.length)continue;Ya(c,e).some(l=>mt.default.existsSync(L__default.default.join(t,l.target)))||n.push(`${s.name} -> ${a}`);}for(let a of s.dependencies??[])o.add(a);}return {copiedItems:r,missingFiles:i,missingRegistryDependencies:n,missingPackages:aw(t,Array.from(o).sort())}}function Ze(t,e){t.push(e);}function LP(t){return t?t.includes("/* tembro:start */")||t.includes("--aui-")||t.includes("--az-"):false}function cw(t={}){let e=process.cwd(),r=[],i=L__default.default.join(e,"package.json"),n=L__default.default.join(e,"tembro.json"),o=mt.default.existsSync(i),s=Ja(n);if(Ze(r,{name:"package.json",status:o?"pass":"fail",message:o?"package.json topildi.":"package.json topilmadi. Commandni React, Vite yoki Next project ichida ishlating."}),Ze(r,{name:"tembro.json",status:s?"pass":"fail",message:s?"tembro.json topildi.":`tembro.json topilmadi. Avval ${re("init")} ishlating.`}),s){let d=s.alias??"@",{componentsRoot:l,uiPath:u,libPath:h,hooksPath:p}=sw(s),f=s.cssPath??"src/index.css",b=[["components",l],["ui",u],["lib",h],["hooks",p]].filter(([,C])=>{let M=FP(e,C);return !M||!mt.default.existsSync(M)});Ze(r,{name:"paths",status:b.length?"warn":"pass",message:b.length?`Yetishmayotgan pathlar: ${b.map(([C,M])=>`${C}=${M}`).join(", ")}`:"Configured source paths mavjud."}),Ze(r,{name:"alias",status:iw(e,d)||nw(e,d)?"pass":"warn",message:iw(e,d)||nw(e,d)?`${d} alias tsconfig yoki vite configda topildi.`:`${d} alias topilmadi. Import pathlar ishlamasligi mumkin.`});let y=ow(L__default.default.join(e,f)),w=LP(y);Ze(r,{name:"theme-css",status:w?"pass":"warn",message:w?`${f} ichida Tembro tokenlari bor.`:`${f} ichida Tembro tokenlari topilmadi. ${re(`theme ${f}`)} ishlating.`});let S=qP(e,s);Ze(r,{name:"component-files",status:S.missingFiles.length?"warn":"pass",message:S.missingFiles.length?`Yetishmayotgan component fayllari: ${S.missingFiles.slice(0,8).join(", ")}${S.missingFiles.length>8?` (+${S.missingFiles.length-8})`:""}`:`${S.copiedItems.length} ta copied component file set tekshirildi.`}),Ze(r,{name:"registry-dependencies",status:S.missingRegistryDependencies.length?"warn":"pass",message:S.missingRegistryDependencies.length?`Yetishmayotgan registry dependencylar: ${S.missingRegistryDependencies.slice(0,8).join(", ")}${S.missingRegistryDependencies.length>8?` (+${S.missingRegistryDependencies.length-8})`:""}`:"Copied componentlar uchun registry dependencylar joyida."}),Ze(r,{name:"component-dependencies",status:S.missingPackages.length?"warn":"pass",message:S.missingPackages.length?`Copied componentlar uchun yetishmayotgan package dependencylar: ${S.missingPackages.join(", ")}`:"Copied component package dependencylari package.json ichida bor."});}if(o){let d=aw(e,["@fontsource-variable/geist","clsx","tailwind-merge","tw-animate-css"]);Ze(r,{name:"base-dependencies",status:d.length?"warn":"pass",message:d.length?`Yetishmayotgan dependencylar: ${d.join(", ")}`:"Base dependencylar package.json ichida bor."});}let a=r.filter(d=>d.status==="fail"),c=r.filter(d=>d.status==="warn");if(t.json){console.log(JSON.stringify({ok:a.length===0,checks:r},null,2));return}for(let d of r){let l=`${d.status.toUpperCase()} ${d.name}: ${d.message}`;d.status==="pass"&&x.success(l),d.status==="warn"&&x.warn(l),d.status==="fail"&&x.error(l);}if(a.length&&(x.error(`Doctor failed: ${a.length} fail, ${c.length} warn.`),process.exit(1)),c.length){x.warn(`Doctor completed with ${c.length} warning.`);return}x.success("Doctor passed. Project Tembro setup looks ready.");}var Ka=be(qe());async function lw(t){let e=process.cwd(),r=L__default.default.join(e,"tembro.json"),i=Ka.default.existsSync(r)?await Ka.default.readJson(r):{},n=t??i.cssPath??i.globalCssPath??"src/index.css",o=i.paths?.components??i.componentsPath??"src/components",s=await Mn({cwd:e,cssPath:n,componentsPath:o});x.success(`Tembro theme CSS yangilandi: ${s}`);}var dw={minimal:["button","input","card","badge"],dashboard:["button","input","card","badge","dropdown-menu","skeleton","tabs","page-state","filter-bar","description-list","info-card","data-table"]};async function uw(t,e={}){let r=dw[t];r||(x.error(`Preset topilmadi: ${t}`),process.exit(1)),await Dt(r,e);}var Qe=new $t;Qe.name("tembro").description("Tembro source-copy CLI").version("3.1.17");Qe.command("init").description("Initialize local Tembro source files in your project").option("--template <template>","Project defaults: vite or next").option("--skip-install","Do not install base dependencies").option("--showcase","Add every component and write a local Tembro workbench").option("-y, --defaults","Use template defaults without interactive prompts").action(Ky);Qe.command("preset").description("Add a source-copy preset to your project").argument("<name>","preset name: minimal or dashboard").option("-o, --overwrite","overwrite existing files").option("--dry-run","show files without writing").option("--skip-install","do not install package dependencies").action(uw);Qe.command("list").description("List available registry components").option("--category <category>","Filter by registry category").option("--status <status>","Filter by status: stable, preview, experimental, internal").option("--json","Print machine-readable JSON").action(rw);Qe.command("doctor").description("Check local project setup for Tembro source-copy usage").option("--json","Print machine-readable JSON").action(cw);Qe.command("add").description("Copy component source files into your project").argument("[components...]","components to add").option("-o, --overwrite","overwrite existing files").option("--dry-run","show files without writing").option("--plan","print machine-readable copy plan without writing").option("--skip-install","do not install package dependencies").action(Dt);Qe.command("showcase").description("Add every component and write a local Tembro workbench").option("-o, --overwrite","overwrite existing files").option("--dry-run","show files without writing").option("--skip-install","do not install package dependencies").action(t=>Dt(["showcase"],t));Qe.command("theme").description("Write or update Tembro theme CSS in your global CSS file").argument("[cssPath]","global CSS path, default from tembro.json or src/index.css").action(lw);Qe.parse();
@@ -22,6 +22,7 @@ export type DatePickerProps = Omit<
22
22
  labels?: DatePickerLabels
23
23
  disabled?: boolean
24
24
  clearable?: boolean
25
+ triggerVariant?: "default" | "compact"
25
26
  formatValue?: (value: string) => React.ReactNode
26
27
  triggerClassName?: string
27
28
  contentClassName?: string
@@ -40,6 +41,7 @@ function DatePicker({
40
41
  labels,
41
42
  disabled = false,
42
43
  clearable = true,
44
+ triggerVariant = "default",
43
45
  formatValue = defaultFormatValue,
44
46
  triggerClassName,
45
47
  contentClassName,
@@ -66,16 +68,18 @@ function DatePicker({
66
68
  }
67
69
 
68
70
  return (
69
- <div data-slot="date-picker" className={cn("w-full", className)}>
71
+ <div data-slot="date-picker" data-trigger-variant={triggerVariant} className={cn("w-full", className)}>
70
72
  <Popover open={open} onOpenChange={setOpen}>
71
73
  <PopoverTrigger
72
74
  render={
73
75
  <Button
74
76
  type="button"
75
77
  variant="outline"
76
- disabled={disabled}
78
+ disabled={disabled}
79
+ labelClassName="flex-1"
77
80
  className={cn(
78
- "group min-h-11 w-full justify-start gap-3 rounded-[var(--aui-control-radius,var(--radius-md))] border-border/80 bg-background/96 px-3 text-left font-normal shadow-[var(--aui-control-shadow,0_1px_2px_rgba(15,23,42,0.04))]",
81
+ "group w-full justify-start rounded-[var(--aui-control-radius,var(--radius-md))] border-border/80 bg-background/96 text-left font-normal shadow-[var(--aui-control-shadow,0_1px_2px_rgba(15,23,42,0.04))]",
82
+ triggerVariant === "default" ? "min-h-11 gap-3 px-3" : "min-h-9 gap-2 px-2.5",
79
83
  !hasValue && "text-muted-foreground",
80
84
  triggerClassName
81
85
  )}
@@ -83,10 +87,10 @@ function DatePicker({
83
87
  }
84
88
  >
85
89
  <CalendarIcon data-icon="inline-start" className={cn(hasValue && "text-primary")} />
86
- <span className="grid min-w-0 flex-1 gap-0.5">
87
- <span className="text-[11px] font-semibold uppercase tracking-[0.16em] text-muted-foreground">
90
+ <span className={cn("grid min-w-0 flex-1", triggerVariant === "default" && "gap-0.5")}>
91
+ {triggerVariant === "default" ? <span className="text-[11px] font-semibold uppercase tracking-[0.16em] text-muted-foreground">
88
92
  {labels?.selected ?? "Date"}
89
- </span>
93
+ </span> : null}
90
94
  <span className={cn("truncate text-sm", hasValue && "font-semibold text-foreground")}>
91
95
  {hasValue ? formatValue(String(value)) : placeholder ?? labels?.placeholder ?? "Select date"}
92
96
  </span>
@@ -96,7 +100,7 @@ function DatePicker({
96
100
  role="button"
97
101
  tabIndex={0}
98
102
  aria-label={labels?.clear ?? "Clear date"}
99
- className="ml-auto inline-flex size-7 shrink-0 items-center justify-center rounded-[var(--radius-sm)] text-muted-foreground opacity-80 transition hover:bg-muted hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring"
103
+ className={cn("ml-auto inline-flex shrink-0 items-center justify-center rounded-[var(--radius-sm)] text-muted-foreground opacity-80 transition hover:bg-muted hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring", triggerVariant === "default" ? "size-7" : "size-6")}
100
104
  onClick={handleClear}
101
105
  onKeyDown={(event) => {
102
106
  if (event.key === "Enter" || event.key === " ") {
@@ -58,18 +58,19 @@ function Statistic({ label, value, prefix, suffix, description, trend = "neutral
58
58
  )
59
59
  }
60
60
 
61
- export type StatisticCardProps = React.ComponentProps<typeof Card> & StatisticProps & {
62
- action?: React.ReactNode
63
- }
64
-
65
- function StatisticCard({ action, label, value, prefix, suffix, description, trend, change, loading, className, ...props }: StatisticCardProps) {
66
- return (
67
- <Card data-slot="statistic-card" className={className} {...props}>
68
- <CardHeader data-slot="statistic-card-header" className="flex flex-row items-center justify-between gap-3 pb-2">
61
+ export type StatisticCardProps = React.ComponentProps<typeof Card> & StatisticProps & {
62
+ action?: React.ReactNode
63
+ density?: "compact" | "default"
64
+ }
65
+
66
+ function StatisticCard({ action, density = "default", label, value, prefix, suffix, description, trend, change, loading, className, ...props }: StatisticCardProps) {
67
+ return (
68
+ <Card data-slot="statistic-card" data-density={density} className={className} {...props}>
69
+ <CardHeader data-slot="statistic-card-header" className={cn("flex flex-row items-center justify-between gap-3 pb-2", density === "compact" && "px-4 pt-4 pb-1")}>
69
70
  <CardTitle className="text-sm font-medium text-muted-foreground">{label}</CardTitle>
70
71
  {action ? <div data-slot="statistic-card-action">{action}</div> : null}
71
72
  </CardHeader>
72
- <CardContent>
73
+ <CardContent className={cn(density === "compact" && "px-4 pb-4")}>
73
74
  <Statistic
74
75
  label={<span className="sr-only">{label}</span>}
75
76
  value={value}
@@ -85,16 +86,20 @@ function StatisticCard({ action, label, value, prefix, suffix, description, tren
85
86
  )
86
87
  }
87
88
 
88
- export type StatisticGridProps = React.ComponentProps<"div"> & {
89
- columns?: 1 | 2 | 3 | 4
90
- }
91
-
92
- function StatisticGrid({ columns = 4, className, ...props }: StatisticGridProps) {
89
+ export type StatisticGridProps = React.ComponentProps<"div"> & {
90
+ columns?: 1 | 2 | 3 | 4
91
+ gap?: "sm" | "default" | "lg"
92
+ }
93
+
94
+ function StatisticGrid({ columns = 4, gap = "default", className, ...props }: StatisticGridProps) {
93
95
  return (
94
96
  <div
95
97
  data-slot="statistic-grid"
96
98
  className={cn(
97
- "grid gap-4",
99
+ "grid",
100
+ gap === "sm" && "gap-3",
101
+ gap === "default" && "gap-4",
102
+ gap === "lg" && "gap-6",
98
103
  columns === 1 && "grid-cols-1",
99
104
  columns === 2 && "grid-cols-1 sm:grid-cols-2",
100
105
  columns === 3 && "grid-cols-1 sm:grid-cols-2 xl:grid-cols-3",
@@ -9,15 +9,36 @@ export type CalendarSchedulerEvent = {
9
9
  time?: string
10
10
  tone?: "default" | "success" | "warning" | "danger"
11
11
  durationMinutes?: number
12
+ description?: React.ReactNode
13
+ meta?: React.ReactNode
14
+ badge?: React.ReactNode
15
+ icon?: React.ReactNode
16
+ disabled?: boolean
17
+ hidden?: boolean
18
+ className?: string
12
19
  }
13
20
 
14
21
  export type CalendarSchedulerProps = React.ComponentProps<"div"> & {
15
22
  events: CalendarSchedulerEvent[]
16
23
  days?: string[]
17
24
  view?: "day" | "week" | "month"
25
+ variant?: "board" | "agenda"
26
+ density?: "compact" | "default" | "comfortable"
27
+ title?: React.ReactNode
28
+ description?: React.ReactNode
29
+ actions?: React.ReactNode
18
30
  empty?: React.ReactNode
31
+ selectedEventId?: string
32
+ defaultSelectedEventId?: string
33
+ onSelectedEventChange?: (eventId: string, event: CalendarSchedulerEvent) => void
19
34
  onCreateEvent?: (date: string) => void
20
35
  onEventClick?: (event: CalendarSchedulerEvent) => void
36
+ renderEvent?: (event: CalendarSchedulerEvent, state: { selected: boolean }) => React.ReactNode
37
+ createLabel?: React.ReactNode
38
+ showCount?: boolean
39
+ dayClassName?: string
40
+ eventClassName?: string
41
+ formatDay?: (day: string) => React.ReactNode
21
42
  }
22
43
 
23
44
  const toneClassName = {
@@ -31,39 +52,52 @@ function CalendarScheduler({
31
52
  events,
32
53
  days,
33
54
  view = "week",
55
+ variant = "board",
56
+ density = "default",
57
+ title,
58
+ description,
59
+ actions,
34
60
  empty = "No events scheduled.",
61
+ selectedEventId,
62
+ defaultSelectedEventId,
63
+ onSelectedEventChange,
35
64
  onCreateEvent,
36
65
  onEventClick,
66
+ renderEvent,
67
+ createLabel = "Create event",
68
+ showCount = true,
69
+ dayClassName,
70
+ eventClassName,
71
+ formatDay,
37
72
  className,
38
73
  ...props
39
74
  }: CalendarSchedulerProps) {
40
- const visibleDays = days ?? Array.from(new Set(events.map((event) => event.date)))
75
+ const [internalSelectedEventId, setInternalSelectedEventId] = React.useState(defaultSelectedEventId)
76
+ const currentSelectedEventId = selectedEventId ?? internalSelectedEventId
77
+ const visibleEvents = events.filter((event) => !event.hidden)
78
+ const visibleDays = days ?? Array.from(new Set(visibleEvents.map((event) => event.date)))
79
+
80
+ const selectEvent = (event: CalendarSchedulerEvent) => {
81
+ if (event.disabled) return
82
+ if (selectedEventId === undefined) setInternalSelectedEventId(event.id)
83
+ onSelectedEventChange?.(event.id, event)
84
+ onEventClick?.(event)
85
+ }
41
86
 
42
87
  if (visibleDays.length === 0) {
43
88
  return (
44
89
  <div
45
90
  data-slot="calendar-scheduler"
46
91
  data-view={view}
47
- className={cn(
48
- "rounded-[var(--aui-card-radius,var(--radius-xl))] border border-[color:var(--aui-card-border,var(--border))] bg-card p-5 text-sm text-muted-foreground shadow-[var(--aui-card-shadow,var(--aui-control-shadow,none))]",
49
- className
50
- )}
92
+ data-variant={variant}
93
+ data-density={density}
94
+ className={cn("rounded-[var(--aui-card-radius,var(--radius-xl))] border border-[color:var(--aui-card-border,var(--border))] bg-card p-5 text-sm text-muted-foreground shadow-[var(--aui-card-shadow,var(--aui-control-shadow,none))]", className)}
51
95
  {...props}
52
96
  >
53
97
  <div className="flex min-h-32 items-center justify-center rounded-[var(--radius-lg)] border border-dashed border-[color:var(--aui-card-border,var(--border))] bg-muted/20 px-4 py-6 text-center">
54
98
  <div className="space-y-1.5">
55
- <p data-empty="true" className="text-sm font-medium text-foreground">
56
- {empty}
57
- </p>
58
- {onCreateEvent ? (
59
- <button
60
- type="button"
61
- className="rounded-md border border-dashed px-3 py-2 text-sm text-muted-foreground hover:bg-muted/50"
62
- onClick={() => onCreateEvent("today")}
63
- >
64
- Create event
65
- </button>
66
- ) : null}
99
+ <p data-empty="true" className="text-sm font-medium text-foreground">{empty}</p>
100
+ {onCreateEvent ? <button type="button" className="rounded-md border border-dashed px-3 py-2 text-sm text-muted-foreground hover:bg-muted/50" onClick={() => onCreateEvent("today")}>{createLabel}</button> : null}
67
101
  </div>
68
102
  </div>
69
103
  </div>
@@ -74,53 +108,79 @@ function CalendarScheduler({
74
108
  <div
75
109
  data-slot="calendar-scheduler"
76
110
  data-view={view}
77
- className={cn("grid gap-3", view === "month" && "md:grid-cols-2 xl:grid-cols-3", className)}
111
+ data-variant={variant}
112
+ data-density={density}
113
+ className={cn(
114
+ "grid",
115
+ density === "compact" ? "gap-2" : density === "comfortable" ? "gap-5" : "gap-3",
116
+ view === "month" && variant === "board" && "md:grid-cols-2 xl:grid-cols-3",
117
+ variant === "agenda" && "rounded-[var(--aui-card-radius,var(--radius-xl))] border border-[color:var(--aui-card-border,var(--border))] bg-card p-4 shadow-[var(--aui-card-shadow,var(--aui-control-shadow,none))]",
118
+ className
119
+ )}
78
120
  {...props}
79
121
  >
122
+ {(title || description || actions) ? (
123
+ <div data-slot="calendar-scheduler-header" className="flex flex-wrap items-start justify-between gap-3">
124
+ <div className="min-w-[min(100%,16rem)] flex-1 space-y-1">
125
+ {title ? <h2 className="text-base font-semibold text-foreground">{title}</h2> : null}
126
+ {description ? <p className="text-sm text-muted-foreground">{description}</p> : null}
127
+ </div>
128
+ {actions ? <div className="flex shrink-0 flex-wrap items-center gap-2">{actions}</div> : null}
129
+ </div>
130
+ ) : null}
131
+
80
132
  {visibleDays.map((day) => {
81
- const dayEvents = events.filter((event) => event.date === day)
133
+ const dayEvents = visibleEvents.filter((event) => event.date === day)
82
134
 
83
135
  return (
84
- <section
85
- key={day}
86
- className="rounded-[var(--aui-card-radius,var(--radius-xl))] border border-[color:var(--aui-card-border,var(--border))] bg-card p-4 shadow-[var(--aui-card-shadow,var(--aui-control-shadow,none))]"
87
- >
136
+ <section key={day} data-slot="calendar-scheduler-day" className={cn(variant === "board" && "rounded-[var(--aui-card-radius,var(--radius-xl))] border border-[color:var(--aui-card-border,var(--border))] bg-card p-4 shadow-[var(--aui-card-shadow,var(--aui-control-shadow,none))]", dayClassName)}>
88
137
  <div className="mb-3 flex items-center justify-between gap-3">
89
- <div className="text-sm font-semibold text-foreground">{day}</div>
90
- <div className="text-xs text-muted-foreground">{dayEvents.length} items</div>
138
+ <div className="text-sm font-semibold text-foreground">{formatDay?.(day) ?? day}</div>
139
+ {showCount ? <div className="text-xs text-muted-foreground">{dayEvents.length} items</div> : null}
91
140
  </div>
92
- <div className="grid gap-2">
141
+ <div className={cn("grid", density === "compact" ? "gap-1.5" : "gap-2")}>
93
142
  {dayEvents.length === 0 ? (
94
- <div className="rounded-[var(--radius-md)] border border-dashed border-[color:var(--aui-card-border,var(--border))] px-3 py-4 text-sm text-muted-foreground">
95
- {empty}
96
- </div>
97
- ) : (
98
- dayEvents.map((event) => (
143
+ <div className="rounded-[var(--radius-md)] border border-dashed border-[color:var(--aui-card-border,var(--border))] px-3 py-4 text-sm text-muted-foreground">{empty}</div>
144
+ ) : dayEvents.map((event) => {
145
+ const selected = currentSelectedEventId === event.id
146
+ return (
99
147
  <button
100
148
  key={event.id}
101
149
  type="button"
150
+ disabled={event.disabled}
151
+ data-slot="calendar-scheduler-event"
152
+ data-selected={selected || undefined}
102
153
  className={cn(
103
- "rounded-[var(--radius-md)] border px-3 py-2.5 text-left text-sm shadow-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/40",
104
- toneClassName[event.tone ?? "default"],
154
+ "border text-left text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/40 disabled:cursor-not-allowed disabled:opacity-50",
155
+ variant === "board" ? "rounded-[var(--radius-md)] px-3 py-2.5 shadow-sm" : "grid w-full grid-cols-[auto_minmax(0,1fr)_auto] items-start gap-3 rounded-[var(--radius-md)] bg-background px-3 py-3 hover:bg-muted/35 data-[selected=true]:border-primary/40 data-[selected=true]:bg-primary/5",
156
+ variant === "board" && toneClassName[event.tone ?? "default"], eventClassName, event.className
105
157
  )}
106
- onClick={() => onEventClick?.(event)}
158
+ onClick={() => selectEvent(event)}
107
159
  >
108
- <div className="font-medium">{event.title}</div>
109
- {event.time && <div className="mt-1 text-xs opacity-70">{event.time}</div>}
110
- {event.durationMinutes && <div className="mt-1 text-xs opacity-70">{event.durationMinutes} min</div>}
160
+ {renderEvent ? renderEvent(event, { selected }) : variant === "agenda" ? (
161
+ <>
162
+ <span className="inline-flex min-w-14 items-center gap-1.5 font-medium">{event.icon}{event.time ?? "—"}</span>
163
+ <span className="min-w-0">
164
+ <span className="block truncate font-medium text-foreground">{event.title}</span>
165
+ {event.description ? <span className="mt-0.5 block text-xs text-muted-foreground">{event.description}</span> : null}
166
+ {event.meta ? <span className="mt-1 block text-xs text-muted-foreground">{event.meta}</span> : null}
167
+ </span>
168
+ {event.badge ? <span className="shrink-0">{event.badge}</span> : event.durationMinutes ? <span className="shrink-0 text-xs opacity-70">{event.durationMinutes} min</span> : null}
169
+ </>
170
+ ) : (
171
+ <>
172
+ <div className="font-medium">{event.title}</div>
173
+ {event.description ? <div className="mt-1 text-xs opacity-80">{event.description}</div> : null}
174
+ {event.time ? <div className="mt-1 text-xs opacity-70">{event.time}</div> : null}
175
+ {event.durationMinutes ? <div className="mt-1 text-xs opacity-70">{event.durationMinutes} min</div> : null}
176
+ {event.meta ? <div className="mt-1 text-xs opacity-70">{event.meta}</div> : null}
177
+ </>
178
+ )}
111
179
  </button>
112
- ))
113
- )}
180
+ )
181
+ })}
114
182
  </div>
115
- {onCreateEvent && (
116
- <button
117
- type="button"
118
- className="mt-3 w-full rounded-md border border-dashed px-3 py-2 text-sm text-muted-foreground hover:bg-muted/50"
119
- onClick={() => onCreateEvent(day)}
120
- >
121
- Create event
122
- </button>
123
- )}
183
+ {onCreateEvent ? <button type="button" className="mt-3 w-full rounded-md border border-dashed px-3 py-2 text-sm text-muted-foreground hover:bg-muted/50" onClick={() => onCreateEvent(day)}>{createLabel}</button> : null}
124
184
  </section>
125
185
  )
126
186
  })}
@@ -46,6 +46,7 @@ export type ButtonProps = ButtonPrimitive.Props &
46
46
  iconOnly?: boolean
47
47
  fullWidth?: boolean
48
48
  pressed?: boolean
49
+ labelClassName?: string
49
50
  }
50
51
 
51
52
  function Button({
@@ -60,6 +61,7 @@ function Button({
60
61
  iconOnly = false,
61
62
  fullWidth = false,
62
63
  pressed = false,
64
+ labelClassName,
63
65
  children,
64
66
  "aria-label": ariaLabel,
65
67
  ...props
@@ -96,7 +98,7 @@ function Button({
96
98
  {leftIcon}
97
99
  </span>
98
100
  ) : null}
99
- {children ? <span data-slot="button-label">{loading ? loadingLabel : children}</span> : null}
101
+ {children ? <span data-slot="button-label" className={cn("inline-flex min-w-0 items-center gap-[inherit]", labelClassName)}>{loading ? loadingLabel : children}</span> : null}
100
102
  {!loading && rightIcon ? (
101
103
  <span data-icon="inline-end" data-slot="button-icon" className="inline-flex shrink-0 items-center justify-center">
102
104
  {rightIcon}
@@ -1,4 +1,4 @@
1
- export const PACKAGE_LATEST_VERSION = "3.1.16"
1
+ export const PACKAGE_LATEST_VERSION = "3.1.17"
2
2
  export const PACKAGE_LATEST_RELEASE_DATE = "July 15, 2026"
3
3
  export const PACKAGE_DEPENDENCY_RANGE = PACKAGE_LATEST_VERSION
4
4
 
@@ -6,23 +6,26 @@ export const calendarSchedulerMock: ComponentDemoMock = {
6
6
  export function Example() {
7
7
  return (
8
8
  <CalendarScheduler
9
- days={["Mon, Jul 6", "Tue, Jul 7"]}
9
+ variant="agenda"
10
+ density="compact"
11
+ title="Delivery schedule"
12
+ defaultSelectedEventId="1"
13
+ days={["Mon, Jul 6"]}
10
14
  events={[
11
- { id: "1", title: "Design review", date: "Mon, Jul 6", time: "10:00", tone: "warning" },
12
- { id: "2", title: "Release freeze", date: "Tue, Jul 7", time: "14:00", tone: "danger" },
15
+ { id: "1", title: "Design review", description: "API review", date: "Mon, Jul 6", time: "10:00" },
16
+ { id: "2", title: "Release freeze", description: "Production gate", date: "Mon, Jul 6", time: "14:00" },
13
17
  ]}
14
18
  />
15
19
  )
16
20
  }`,
17
21
  cliCommand: "npx tembro add calendar-scheduler",
18
22
  highlights: [
19
- "Compact scheduler timeline by day",
20
- "Tone variants for different event priorities",
21
- "Works with custom day labels",
23
+ "Board and agenda variants with compact or comfortable density",
24
+ "Rich metadata, badges, hidden and disabled event states",
25
+ "Controlled or uncontrolled selection and custom event rendering",
22
26
  ],
23
27
  scenarios: [
24
28
  { title: "Ops dashboard", description: "Show day tasks by lane without heavy calendar widgets." },
25
29
  { title: "Sprint board", description: "Keep event density low and readable." },
26
30
  ],
27
31
  }
28
-
@@ -7,13 +7,14 @@ const defaultDays = ["Mon, Jul 6", "Tue, Jul 7", "Wed, Jul 8"]
7
7
 
8
8
  export function CalendarSchedulerShowcase() {
9
9
  const [showWarning, setShowWarning] = React.useState(true)
10
+ const [variant, setVariant] = React.useState<"board" | "agenda">("agenda")
10
11
 
11
12
  const events = React.useMemo(
12
13
  () => ([
13
- { id: "1", title: "Design review", date: "Mon, Jul 6", time: "10:00", tone: "warning" },
14
- { id: "2", title: "Release freeze", date: "Tue, Jul 7", time: "14:00", tone: "danger" },
15
- { id: "3", title: "Daily standup", date: "Wed, Jul 8", time: "09:30", tone: "default" },
16
- ...(showWarning ? [{ id: "4", title: "Priority follow-up", date: "Mon, Jul 6", time: "16:00", tone: "warning" }] : []),
14
+ { id: "1", title: "Design review", description: "Component API and release readiness", meta: "Product room", badge: <span className="rounded bg-amber-100 px-2 py-0.5 text-xs text-amber-800">Review</span>, date: "Mon, Jul 6", time: "10:00", tone: "warning" },
15
+ { id: "2", title: "Release freeze", description: "Production deployment checkpoint", meta: "Platform team", badge: <span className="rounded bg-red-100 px-2 py-0.5 text-xs text-red-800">Required</span>, date: "Tue, Jul 7", time: "14:00", tone: "danger" },
16
+ { id: "3", title: "Daily standup", description: "Owners, blockers and delivery status", date: "Wed, Jul 8", time: "09:30", tone: "default" },
17
+ ...(showWarning ? [{ id: "4", title: "Priority follow-up", description: "Customer escalation review", date: "Mon, Jul 6", time: "16:00", tone: "warning" as const }] : []),
17
18
  ] as CalendarSchedulerEvent[]),
18
19
  [showWarning]
19
20
  )
@@ -21,6 +22,7 @@ export function CalendarSchedulerShowcase() {
21
22
  return (
22
23
  <div className="space-y-3">
23
24
  <div className="flex gap-2">
25
+ <button type="button" className="inline-flex items-center rounded-md border px-3 py-1.5 text-sm" onClick={() => setVariant((value) => value === "agenda" ? "board" : "agenda")}>Use {variant === "agenda" ? "board" : "agenda"}</button>
24
26
  <button
25
27
  type="button"
26
28
  className="inline-flex items-center rounded-md border px-3 py-1.5 text-sm"
@@ -29,7 +31,7 @@ export function CalendarSchedulerShowcase() {
29
31
  {showWarning ? "Hide warning" : "Show warning"}
30
32
  </button>
31
33
  </div>
32
- <CalendarScheduler days={defaultDays} events={events} />
34
+ <CalendarScheduler title="Delivery schedule" description="Production agenda with rich event metadata and selection." variant={variant} density="compact" days={defaultDays} events={events} defaultSelectedEventId="1" />
33
35
  </div>
34
36
  )
35
37
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://tembro.dev/registry.schema.json",
3
3
  "name": "tembro",
4
- "version": "3.1.16",
4
+ "version": "3.1.17",
5
5
  "description": "Reusable React + TypeScript UI kit registry",
6
6
  "groups": {
7
7
  "ui": [
@@ -25,7 +25,7 @@ export function HeroSection() {
25
25
  <div className="flex flex-wrap gap-2">
26
26
  <Button leftIcon={<PlusIcon className="size-4" />}>Create</Button>
27
27
  <Button variant="outline" leftIcon={<SettingsIcon className="size-4" />}>Settings</Button>
28
- <CopyButton value="npx tembro@3.1.16 list --json">Copy list command</CopyButton>
28
+ <CopyButton value="npx tembro@3.1.17 list --json">Copy list command</CopyButton>
29
29
  </div>
30
30
  </div>
31
31
 
@@ -33,7 +33,7 @@ export function HeroSection() {
33
33
  <StatisticCard label="Registry components" value={moduleCount} change="visible below" trend="up" description="tembro add <name>" />
34
34
  <StatisticCard label="Local source files" value="152" change="with hooks/lib" trend="up" description="installed by CLI" />
35
35
  <StatisticCard label="Categories" value={registryGroups.length} change="all shown" trend="up" description="actions to wizard" />
36
- <StatisticCard label="Build" value="Pass" change="doctor pass" trend="up" description="tembro@3.1.16" />
36
+ <StatisticCard label="Build" value="Pass" change="doctor pass" trend="up" description="tembro@3.1.17" />
37
37
  </StatisticGrid>
38
38
  </div>
39
39
  </section>
@@ -72,7 +72,7 @@ export function WorkbenchSidebar({ selectedKey, onSelect }: WorkbenchSidebarProp
72
72
  }),
73
73
  ]}
74
74
  footerAccount={{
75
- label: "tembro@3.1.16",
75
+ label: "tembro@3.1.17",
76
76
  description: `${moduleCount} registry components`,
77
77
  }}
78
78
  activeIndicator="bar"
package/registry.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://tembro.dev/registry.schema.json",
3
3
  "name": "tembro",
4
- "version": "3.1.16",
4
+ "version": "3.1.17",
5
5
  "description": "Reusable React + TypeScript UI kit registry",
6
6
  "groups": {
7
7
  "ui": [