tembro 4.0.0 → 4.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.
@@ -1 +1 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("../../../_virtual/_rolldown/runtime.cjs"),t=require("../../lib/utils.cjs");let n=require("react");n=e.__toESM(n,1);let r=require("react/jsx-runtime");function i({viewport:e=!0,className:n,...i}){return(0,r.jsx)(`div`,{"data-slot":`workspace-layout`,"data-viewport":e||void 0,className:t.cn(`flex min-h-0 min-w-0 bg-[color:var(--aui-page-bg,var(--background))] text-foreground`,e&&`h-dvh overflow-hidden`,n),...i})}function a({className:e,...n}){return(0,r.jsx)(`div`,{"data-slot":`workspace-content`,className:t.cn(`flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden`,e),...n})}function o({scrollable:e=!0,padded:n=!1,className:i,...a}){return(0,r.jsx)(`main`,{"data-slot":`workspace-main`,"data-scrollable":e||void 0,className:t.cn(`min-h-0 min-w-0 flex-1`,e&&`overflow-x-hidden overflow-y-auto overscroll-contain`,n&&`p-4 md:p-6`,i),...a})}exports.WorkspaceContent=a,exports.WorkspaceLayout=i,exports.WorkspaceMain=o;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("../../../_virtual/_rolldown/runtime.cjs"),t=require("../../lib/utils.cjs");let n=require("react");n=e.__toESM(n,1);let r=require("react/jsx-runtime");function i({viewport:e=!0,className:n,...i}){return(0,r.jsx)(`div`,{"data-slot":`workspace-layout`,"data-viewport":e||void 0,className:t.cn(`flex min-h-0 min-w-0 bg-[color:var(--aui-page-bg,var(--background))] text-foreground`,e&&`h-dvh max-h-dvh w-full overflow-hidden overscroll-none`,n),...i})}function a({className:e,...n}){return(0,r.jsx)(`div`,{"data-slot":`workspace-content`,className:t.cn(`isolate flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden`,e),...n})}function o({scrollable:e=!0,padded:n=!1,className:i,...a}){return(0,r.jsx)(`main`,{"data-slot":`workspace-main`,"data-scrollable":e||void 0,className:t.cn(`min-h-0 min-w-0 flex-1`,e&&`overflow-x-hidden overflow-y-auto overscroll-contain [scrollbar-gutter:stable]`,n&&`p-4 md:p-6`,i),...a})}exports.WorkspaceContent=a,exports.WorkspaceLayout=i,exports.WorkspaceMain=o;
@@ -6,14 +6,14 @@ function n({ viewport: n = !0, className: r, ...i }) {
6
6
  return /* @__PURE__ */ t("div", {
7
7
  "data-slot": "workspace-layout",
8
8
  "data-viewport": n || void 0,
9
- className: e("flex min-h-0 min-w-0 bg-[color:var(--aui-page-bg,var(--background))] text-foreground", n && "h-dvh overflow-hidden", r),
9
+ className: e("flex min-h-0 min-w-0 bg-[color:var(--aui-page-bg,var(--background))] text-foreground", n && "h-dvh max-h-dvh w-full overflow-hidden overscroll-none", r),
10
10
  ...i
11
11
  });
12
12
  }
13
13
  function r({ className: n, ...r }) {
14
14
  return /* @__PURE__ */ t("div", {
15
15
  "data-slot": "workspace-content",
16
- className: e("flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden", n),
16
+ className: e("isolate flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden", n),
17
17
  ...r
18
18
  });
19
19
  }
@@ -21,7 +21,7 @@ function i({ scrollable: n = !0, padded: r = !1, className: i, ...a }) {
21
21
  return /* @__PURE__ */ t("main", {
22
22
  "data-slot": "workspace-main",
23
23
  "data-scrollable": n || void 0,
24
- className: e("min-h-0 min-w-0 flex-1", n && "overflow-x-hidden overflow-y-auto overscroll-contain", r && "p-4 md:p-6", i),
24
+ className: e("min-h-0 min-w-0 flex-1", n && "overflow-x-hidden overflow-y-auto overscroll-contain [scrollbar-gutter:stable]", r && "p-4 md:p-6", i),
25
25
  ...a
26
26
  });
27
27
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tembro",
3
- "version": "4.0.0",
3
+ "version": "4.0.1",
4
4
  "description": "Reusable React UI kit with dashboard-ready components, CLI registry helpers, and TypeScript support.",
5
5
  "keywords": [
6
6
  "react",
@@ -2002,4 +2002,4 @@ ${t}`),i.forEach(o=>{let n=R[o],s=nr(o),a=jo(o,n.category),c=n.registryDependenc
2002
2002
  ${s}`),a.sort().forEach(c=>{let d=R[c],l=nr(c),u=jo(c,d.category),h=d.registryDependencies?.length?` -> ${d.registryDependencies.join(", ")}`:"";console.log(` ${c} [${l}/${u}]${h}`);});console.log(`
2003
2003
  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(`
2004
2004
  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(`
2005
- 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 nw(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"),o=t.paths?.hooks??"src/hooks",n=t.utilsPath??L__default.default.join(i,"utils.ts");return {componentsRoot:e,uiPath:r,libPath:i,hooksPath:o,utilsPath:n}}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 ow(t,e){return ["vite.config.ts","vite.config.js","vite.config.mjs"].map(i=>L__default.default.join(t,i)).some(i=>{let o=nw(i);return !!(o?.includes(`"${e}"`)||o?.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(o=>!i.has(o))}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=[],o=[],n=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)))||o.push(`${s.name} -> ${a}`);}for(let a of s.dependencies??[])n.add(a);}return {copiedItems:r,missingFiles:i,missingRegistryDependencies:o,missingPackages:aw(t,Array.from(n).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"),o=L__default.default.join(e,"tembro.json"),n=mt.default.existsSync(i),s=Ja(o);if(Ze(r,{name:"package.json",status:n?"pass":"fail",message:n?"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)||ow(e,d)?"pass":"warn",message:iw(e,d)||ow(e,d)?`${d} alias tsconfig yoki vite configda topildi.`:`${d} alias topilmadi. Import pathlar ishlamasligi mumkin.`});let y=nw(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(n){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):{},o=t??i.cssPath??i.globalCssPath??"src/index.css",n=i.paths?.components??i.componentsPath??"src/components",s=await Mo({cwd:e,cssPath:o,componentsPath:n});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("4.0.0");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();
2005
+ 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 nw(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"),o=t.paths?.hooks??"src/hooks",n=t.utilsPath??L__default.default.join(i,"utils.ts");return {componentsRoot:e,uiPath:r,libPath:i,hooksPath:o,utilsPath:n}}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 ow(t,e){return ["vite.config.ts","vite.config.js","vite.config.mjs"].map(i=>L__default.default.join(t,i)).some(i=>{let o=nw(i);return !!(o?.includes(`"${e}"`)||o?.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(o=>!i.has(o))}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=[],o=[],n=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)))||o.push(`${s.name} -> ${a}`);}for(let a of s.dependencies??[])n.add(a);}return {copiedItems:r,missingFiles:i,missingRegistryDependencies:o,missingPackages:aw(t,Array.from(n).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"),o=L__default.default.join(e,"tembro.json"),n=mt.default.existsSync(i),s=Ja(o);if(Ze(r,{name:"package.json",status:n?"pass":"fail",message:n?"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)||ow(e,d)?"pass":"warn",message:iw(e,d)||ow(e,d)?`${d} alias tsconfig yoki vite configda topildi.`:`${d} alias topilmadi. Import pathlar ishlamasligi mumkin.`});let y=nw(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(n){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):{},o=t??i.cssPath??i.globalCssPath??"src/index.css",n=i.paths?.components??i.componentsPath??"src/components",s=await Mo({cwd:e,cssPath:o,componentsPath:n});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("4.0.1");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();
@@ -36,6 +36,8 @@ export type ChartSeries = {
36
36
  color?: string
37
37
  }
38
38
 
39
+ export type ChartCurve = "linear" | "monotone" | "natural" | "step"
40
+
39
41
  export type ChartAxisLabel = string | number | React.ReactNode
40
42
  export type ChartSize = "sm" | "md" | "lg"
41
43
  export type ChartState = "ready" | "loading" | "empty"
@@ -77,6 +79,18 @@ function ChartDataTable({ data, valueFormatter, caption }: { data: ChartDatum[];
77
79
  )
78
80
  }
79
81
 
82
+ function ChartSeriesDataTable({ labels, series, valueFormatter, caption }: { labels: ChartAxisLabel[]; series: ChartSeries[]; valueFormatter?: (value: number) => React.ReactNode; caption: string }) {
83
+ return (
84
+ <div className="sr-only">
85
+ <table>
86
+ <caption>{caption}</caption>
87
+ <thead><tr><th scope="col">Label</th>{series.map((item) => <th scope="col" key={item.key}>{item.label}</th>)}</tr></thead>
88
+ <tbody>{labels.map((label, index) => <tr key={index}><th scope="row">{label}</th>{series.map((item) => <td key={item.key}>{formatChartValue(item.data[index] ?? 0, valueFormatter)}</td>)}</tr>)}</tbody>
89
+ </table>
90
+ </div>
91
+ )
92
+ }
93
+
80
94
  export type ChartTooltipContentProps = {
81
95
  active?: boolean
82
96
  label?: React.ReactNode
@@ -129,7 +143,8 @@ function ChartFrame({ title, description, action, state = "ready", emptyLabel =
129
143
  }
130
144
 
131
145
  export type BarChartProps = React.ComponentProps<"div"> & {
132
- data: ChartDatum[]
146
+ data?: ChartDatum[]
147
+ series?: ChartSeries[]
133
148
  size?: ChartSize
134
149
  max?: number
135
150
  showLabels?: boolean
@@ -142,13 +157,23 @@ export type BarChartProps = React.ComponentProps<"div"> & {
142
157
  barClassName?: string
143
158
  domain?: ChartDomain
144
159
  ariaLabel?: string
160
+ stacked?: boolean
161
+ showLegend?: boolean
162
+ barRadius?: number
145
163
  }
146
164
 
147
- function BarChart({ data, size = "md", max, showLabels = true, showValues = false, showGrid = true, showTooltip = true, valueFormatter, state = "ready", emptyLabel = "No chart data.", barClassName, domain, ariaLabel = "Bar chart", className, ...props }: BarChartProps) {
165
+ function BarChart({ data = [], series, size = "md", max, showLabels = true, showValues = false, showGrid = true, showTooltip = true, valueFormatter, state = "ready", emptyLabel = "No chart data.", barClassName, domain, ariaLabel = "Bar chart", stacked = false, showLegend = Boolean(series?.length), barRadius = 6, className, ...props }: BarChartProps) {
148
166
  const height = chartHeightBySize[size]
149
- const status = state === "ready" && data.length === 0 ? "empty" : state
167
+ const activeSeries = series?.length ? series : [{ key: "value", label: "Value", data: data.map((item) => item.value) }]
168
+ const itemCount = Math.max(data.length, ...activeSeries.map((item) => item.data.length))
169
+ const status = state === "ready" && itemCount === 0 ? "empty" : state
150
170
  if (status !== "ready") return <ChartStatus state={status} emptyLabel={emptyLabel} height={height} />
151
- const chartData = data.map((item, index) => ({ name: toText(item.label, `Item ${index + 1}`), value: item.value, fill: getChartColor(index, item.color), originalLabel: item.label, description: item.description }))
171
+ const labels = Array.from({ length: itemCount }, (_, index) => data[index]?.label ?? `Item ${index + 1}`)
172
+ const chartData = labels.map((label, index) => Object.fromEntries([
173
+ ["name", toText(label, `Item ${index + 1}`)],
174
+ ["originalLabel", label],
175
+ ...activeSeries.map((item) => [item.key, item.data[index] ?? 0]),
176
+ ]))
152
177
  const resolvedDomain: ChartDomain = domain ?? (max !== undefined ? ["auto", max] : ["auto", "auto"])
153
178
 
154
179
  return (
@@ -160,19 +185,23 @@ function BarChart({ data, size = "md", max, showLabels = true, showValues = fals
160
185
  <XAxis dataKey="name" hide={!showLabels} axisLine={false} tickLine={false} tick={{ fill: "var(--muted-foreground)", fontSize: 12 }} interval="preserveStartEnd" />
161
186
  <YAxis hide domain={resolvedDomain} />
162
187
  {showTooltip ? <RechartsTooltip cursor={{ fill: "var(--muted)", opacity: 0.35 }} content={<ChartTooltipContent valueFormatter={valueFormatter} />} /> : null}
163
- <Bar dataKey="value" name="Value" radius={[6, 6, 2, 2]} className={barClassName} isAnimationActive={false} label={showValues ? { position: "top", fill: "var(--muted-foreground)", fontSize: 11, formatter: (value: unknown) => String(formatChartValue(Number(value), valueFormatter)) } : false}>
164
- {chartData.map((item) => <Cell key={item.name} fill={item.fill} />)}
165
- </Bar>
188
+ {activeSeries.map((item, seriesIndex) => (
189
+ <Bar key={item.key} dataKey={item.key} name={toText(item.label, item.key)} fill={getChartColor(seriesIndex, item.color)} stackId={stacked ? "total" : undefined} radius={[barRadius, barRadius, 2, 2]} className={barClassName} isAnimationActive={false} label={showValues && (!stacked || seriesIndex === activeSeries.length - 1) ? { position: "top", fill: "var(--muted-foreground)", fontSize: 11, formatter: (value: unknown) => String(formatChartValue(Number(value), valueFormatter)) } : false}>
190
+ {!series?.length ? data.map((datum, index) => <Cell key={index} fill={getChartColor(index, datum.color)} />) : null}
191
+ </Bar>
192
+ ))}
166
193
  </RechartsBarChart>
167
194
  </ResponsiveContainer>
168
195
  </div>
169
- <ChartDataTable data={data} valueFormatter={valueFormatter} caption={ariaLabel} />
196
+ {showLegend && activeSeries.length > 1 ? <ChartLegend className="mt-3" data={activeSeries.map((item, index) => ({ label: item.label, value: 0, color: getChartColor(index, item.color) }))} /> : null}
197
+ {series?.length ? <ChartSeriesDataTable labels={labels} series={activeSeries} valueFormatter={valueFormatter} caption={ariaLabel} /> : <ChartDataTable data={data} valueFormatter={valueFormatter} caption={ariaLabel} />}
170
198
  </div>
171
199
  )
172
200
  }
173
201
 
174
202
  export type LineChartProps = Omit<React.ComponentProps<"div">, "children"> & {
175
- values: number[]
203
+ values?: number[]
204
+ series?: ChartSeries[]
176
205
  size?: ChartSize
177
206
  width?: number
178
207
  height?: number
@@ -187,13 +216,22 @@ export type LineChartProps = Omit<React.ComponentProps<"div">, "children"> & {
187
216
  emptyLabel?: React.ReactNode
188
217
  domain?: ChartDomain
189
218
  ariaLabel?: string
219
+ curve?: ChartCurve
220
+ showDots?: boolean
221
+ showLegend?: boolean
222
+ gradient?: boolean
223
+ strokeWidth?: number
190
224
  }
191
225
 
192
- function LineChart({ values, size = "md", width = 560, height: heightProp, showArea = false, showGrid = true, showAxis = false, showTooltip = true, labels, valueFormatter, stroke = "var(--primary)", state = "ready", emptyLabel = "No line data.", domain = ["auto", "auto"], ariaLabel = showArea ? "Area chart" : "Line chart", className, style, ...props }: LineChartProps) {
226
+ function LineChart({ values = [], series, size = "md", width = 560, height: heightProp, showArea = false, showGrid = true, showAxis = false, showTooltip = true, labels, valueFormatter, stroke = "var(--primary)", state = "ready", emptyLabel = "No line data.", domain = ["auto", "auto"], ariaLabel = showArea ? "Area chart" : "Line chart", curve = "monotone", showDots = !showArea, showLegend = Boolean(series?.length), gradient = showArea, strokeWidth = 2.5, className, style, ...props }: LineChartProps) {
193
227
  const height = heightProp ?? chartHeightBySize[size]
194
- const status = state === "ready" && values.length === 0 ? "empty" : state
228
+ const gradientId = React.useId().replace(/:/g, "")
229
+ const activeSeries = series?.length ? series : [{ key: "value", label: "Value", data: values, color: stroke }]
230
+ const itemCount = Math.max(...activeSeries.map((item) => item.data.length), 0)
231
+ const status = state === "ready" && itemCount === 0 ? "empty" : state
195
232
  if (status !== "ready") return <ChartStatus state={status} emptyLabel={emptyLabel} height={height} />
196
- const data = values.map((value, index) => ({ name: toText(labels?.[index], String(index + 1)), value, originalLabel: labels?.[index] ?? String(index + 1) }))
233
+ const resolvedLabels = Array.from({ length: itemCount }, (_, index) => labels?.[index] ?? String(index + 1))
234
+ const data = resolvedLabels.map((label, index) => Object.fromEntries([["name", toText(label, String(index + 1))], ["originalLabel", label], ...activeSeries.map((item) => [item.key, item.data[index] ?? 0])]))
197
235
  const Engine = showArea ? RechartsAreaChart : RechartsLineChart
198
236
 
199
237
  return (
@@ -201,22 +239,24 @@ function LineChart({ values, size = "md", width = 560, height: heightProp, showA
201
239
  <div style={{ height }}>
202
240
  <ResponsiveContainer width="100%" height="100%" minWidth={1} minHeight={1}>
203
241
  <Engine data={data} margin={{ top: 10, right: 10, bottom: showAxis ? 8 : 0, left: showAxis ? -12 : 0 }}>
242
+ {showArea && gradient ? <defs>{activeSeries.map((item, index) => { const color = getChartColor(index, item.color); return <linearGradient key={item.key} id={`${gradientId}-${item.key}`} x1="0" y1="0" x2="0" y2="1"><stop offset="0%" stopColor={color} stopOpacity={0.34} /><stop offset="92%" stopColor={color} stopOpacity={0.02} /></linearGradient> })}</defs> : null}
204
243
  {showGrid ? <CartesianGrid vertical={false} stroke="var(--border)" strokeDasharray="4 6" /> : null}
205
244
  <XAxis dataKey="name" hide={!showAxis} axisLine={false} tickLine={false} tick={{ fill: "var(--muted-foreground)", fontSize: 12 }} interval="preserveStartEnd" />
206
245
  <YAxis hide={!showAxis} domain={domain} axisLine={false} tickLine={false} width={44} tick={{ fill: "var(--muted-foreground)", fontSize: 11 }} />
207
246
  {showTooltip ? <RechartsTooltip content={<ChartTooltipContent valueFormatter={valueFormatter} />} /> : null}
208
- {showArea ? <Area type="monotone" dataKey="value" name="Value" stroke={stroke} strokeWidth={2.5} fill={stroke} fillOpacity={0.14} activeDot={{ r: 5 }} isAnimationActive={false} /> : <Line type="monotone" dataKey="value" name="Value" stroke={stroke} strokeWidth={2.5} dot={{ r: 3.5, fill: stroke }} activeDot={{ r: 5 }} isAnimationActive={false} />}
247
+ {activeSeries.map((item, index) => { const color = getChartColor(index, item.color); return showArea ? <Area key={item.key} type={curve} dataKey={item.key} name={toText(item.label, item.key)} stroke={color} strokeWidth={strokeWidth} fill={gradient ? `url(#${gradientId}-${item.key})` : color} fillOpacity={gradient ? 1 : 0.12} dot={showDots ? { r: 3, fill: color, strokeWidth: 2, stroke: "var(--background)" } : false} activeDot={{ r: 5, strokeWidth: 2, stroke: "var(--background)" }} isAnimationActive={false} /> : <Line key={item.key} type={curve} dataKey={item.key} name={toText(item.label, item.key)} stroke={color} strokeWidth={strokeWidth} dot={showDots ? { r: 3, fill: color, strokeWidth: 2, stroke: "var(--background)" } : false} activeDot={{ r: 5, strokeWidth: 2, stroke: "var(--background)" }} isAnimationActive={false} /> })}
209
248
  </Engine>
210
249
  </ResponsiveContainer>
211
250
  </div>
212
- <ChartDataTable data={values.map((value, index) => ({ label: labels?.[index] ?? index + 1, value }))} valueFormatter={valueFormatter} caption={ariaLabel} />
251
+ {showLegend && activeSeries.length > 1 ? <ChartLegend className="mt-3" data={activeSeries.map((item, index) => ({ label: item.label, value: 0, color: getChartColor(index, item.color) }))} /> : null}
252
+ <ChartSeriesDataTable labels={resolvedLabels} series={activeSeries} valueFormatter={valueFormatter} caption={ariaLabel} />
213
253
  </div>
214
254
  )
215
255
  }
216
256
 
217
257
  function AreaChart(props: Omit<LineChartProps, "showArea">) { return <LineChart showArea {...props} /> }
218
258
 
219
- export type SparklineProps = Omit<LineChartProps, "size" | "showArea" | "showAxis"> & { values: number[]; positive?: boolean }
259
+ export type SparklineProps = Omit<LineChartProps, "size" | "showArea" | "showAxis" | "series" | "showLegend"> & { values: number[]; positive?: boolean }
220
260
 
221
261
  function Sparkline({ values, positive = true, stroke, className, showTooltip = false, ...props }: SparklineProps) {
222
262
  return <LineChart values={values} size="sm" stroke={stroke ?? (positive ? "var(--aui-success,var(--primary))" : "var(--destructive)")} showGrid={false} showAxis={false} showTooltip={showTooltip} className={cn("max-w-44", className)} ariaLabel="Sparkline" {...props} />
@@ -13,7 +13,7 @@ function WorkspaceLayout({ viewport = true, className, ...props }: WorkspaceLayo
13
13
  data-viewport={viewport || undefined}
14
14
  className={cn(
15
15
  "flex min-h-0 min-w-0 bg-[color:var(--aui-page-bg,var(--background))] text-foreground",
16
- viewport && "h-dvh overflow-hidden",
16
+ viewport && "h-dvh max-h-dvh w-full overflow-hidden overscroll-none",
17
17
  className
18
18
  )}
19
19
  {...props}
@@ -27,7 +27,7 @@ function WorkspaceContent({ className, ...props }: WorkspaceContentProps) {
27
27
  return (
28
28
  <div
29
29
  data-slot="workspace-content"
30
- className={cn("flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden", className)}
30
+ className={cn("isolate flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden", className)}
31
31
  {...props}
32
32
  />
33
33
  )
@@ -45,7 +45,7 @@ function WorkspaceMain({ scrollable = true, padded = false, className, ...props
45
45
  data-scrollable={scrollable || undefined}
46
46
  className={cn(
47
47
  "min-h-0 min-w-0 flex-1",
48
- scrollable && "overflow-x-hidden overflow-y-auto overscroll-contain",
48
+ scrollable && "overflow-x-hidden overflow-y-auto overscroll-contain [scrollbar-gutter:stable]",
49
49
  padded && "p-4 md:p-6",
50
50
  className
51
51
  )}
@@ -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@4.0.0 list --json">Copy list command</CopyButton>
28
+ <CopyButton value="npx tembro@4.0.1 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@4.0.0" />
36
+ <StatisticCard label="Build" value="Pass" change="doctor pass" trend="up" description="tembro@4.0.1" />
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@4.0.0",
75
+ label: "tembro@4.0.1",
76
76
  description: `${moduleCount} registry components`,
77
77
  }}
78
78
  activeIndicator="bar"
@@ -128,17 +128,17 @@
128
128
  --aui-ease-standard: cubic-bezier(0.2, 0, 0, 1);
129
129
  --aui-disabled-opacity: 0.52;
130
130
  --aui-shadow-xs: 0 1px 2px oklch(0.24 0.02 255 / 5%);
131
- --aui-shadow-sm: 0 1px 3px oklch(0.24 0.02 255 / 7%);
132
- --aui-shadow-md: 0 8px 24px oklch(0.24 0.02 255 / 8%);
133
- --aui-shadow-lg: 0 16px 36px oklch(0.24 0.02 255 / 10%);
131
+ --aui-shadow-sm: 0 1px 2px oklch(0.24 0.02 255 / 6%), 0 1px 6px oklch(0.24 0.02 255 / 4%);
132
+ --aui-shadow-md: 0 10px 28px -12px oklch(0.24 0.02 255 / 18%), 0 2px 8px oklch(0.24 0.02 255 / 5%);
133
+ --aui-shadow-lg: 0 24px 56px -20px oklch(0.24 0.02 255 / 26%), 0 8px 18px oklch(0.24 0.02 255 / 6%);
134
134
  --aui-surface-base: var(--background);
135
135
  --aui-surface-raised: color-mix(in oklch, var(--background), white 42%);
136
136
  --aui-surface-muted: color-mix(in oklch, var(--muted), var(--background) 52%);
137
137
  --aui-border-strong: color-mix(in oklch, var(--border), var(--foreground) 7%);
138
138
 
139
139
  --aui-control-radius: var(--aui-radius-control);
140
- --aui-focus-ring: color-mix(in oklch, var(--ring), transparent 44%);
141
- --aui-focus-ring-soft: color-mix(in oklch, var(--ring), transparent 82%);
140
+ --aui-focus-ring: color-mix(in oklch, var(--ring), transparent 22%);
141
+ --aui-focus-ring-soft: color-mix(in oklch, var(--ring), transparent 78%);
142
142
  --aui-danger-ring: color-mix(in oklch, var(--destructive), transparent 48%);
143
143
  --aui-danger-ring-soft: color-mix(in oklch, var(--destructive), transparent 86%);
144
144
  --aui-warning-ring: color-mix(in oklch, var(--aui-warning), transparent 48%);
@@ -150,13 +150,13 @@
150
150
  --aui-control-surface-readonly: color-mix(in oklch, var(--muted), var(--background) 78%);
151
151
  --aui-control-shadow: var(--aui-shadow-sm);
152
152
  --aui-control-border-strong: var(--aui-border-strong);
153
- --aui-control-hover-border: color-mix(in oklch, var(--ring), transparent 68%);
153
+ --aui-control-hover-border: color-mix(in oklch, var(--ring), transparent 42%);
154
154
  --aui-control-panel-bg: var(--aui-surface-raised);
155
155
  --aui-control-panel-shadow: var(--aui-shadow-md);
156
156
  --aui-card-radius: var(--aui-radius-surface);
157
157
  --aui-card-border: var(--border);
158
158
  --aui-card-shadow: var(--aui-shadow-xs);
159
- --aui-card-shadow-hover: var(--aui-shadow-md);
159
+ --aui-card-shadow-hover: 0 18px 42px -18px oklch(0.24 0.02 255 / 24%), 0 3px 10px oklch(0.24 0.02 255 / 6%);
160
160
  --aui-card-soft-bg: color-mix(in oklch, var(--muted), var(--background) 34%);
161
161
  --aui-card-outline-bg: var(--aui-surface-raised);
162
162
  --aui-popover-shadow: var(--aui-shadow-lg);
@@ -234,8 +234,8 @@
234
234
  --aui-danger: var(--destructive);
235
235
  --aui-danger-foreground: oklch(0.19 0.012 255);
236
236
 
237
- --aui-focus-ring: color-mix(in oklch, var(--ring), transparent 40%);
238
- --aui-focus-ring-soft: color-mix(in oklch, var(--ring), transparent 80%);
237
+ --aui-focus-ring: color-mix(in oklch, var(--ring), transparent 18%);
238
+ --aui-focus-ring-soft: color-mix(in oklch, var(--ring), transparent 72%);
239
239
  --aui-danger-ring: color-mix(in oklch, var(--destructive), transparent 46%);
240
240
  --aui-danger-ring-soft: color-mix(in oklch, var(--destructive), transparent 84%);
241
241
  --aui-warning-ring: color-mix(in oklch, var(--aui-warning), transparent 46%);
@@ -243,9 +243,9 @@
243
243
  --aui-radius-control: var(--radius-lg);
244
244
  --aui-radius-surface: var(--radius-xl);
245
245
  --aui-shadow-xs: 0 1px 2px oklch(0 0 0 / 12%);
246
- --aui-shadow-sm: 0 1px 3px oklch(0 0 0 / 16%);
247
- --aui-shadow-md: 0 12px 30px oklch(0 0 0 / 26%);
248
- --aui-shadow-lg: 0 22px 54px oklch(0 0 0 / 36%);
246
+ --aui-shadow-sm: 0 1px 2px oklch(0 0 0 / 20%), 0 2px 8px oklch(0 0 0 / 12%);
247
+ --aui-shadow-md: 0 16px 36px -16px oklch(0 0 0 / 62%), 0 3px 12px oklch(0 0 0 / 20%);
248
+ --aui-shadow-lg: 0 28px 64px -20px oklch(0 0 0 / 72%), 0 10px 24px oklch(0 0 0 / 24%);
249
249
  --aui-surface-base: var(--background);
250
250
  --aui-surface-raised: oklch(1 0 0 / 4%);
251
251
  --aui-surface-muted: oklch(1 0 0 / 6%);
@@ -286,7 +286,7 @@
286
286
  --aui-table-border: oklch(1 0 0 / 8%);
287
287
  --aui-table-selection-surface: oklch(1 0 0 / 4%);
288
288
  --aui-control-border-strong: var(--aui-border-strong);
289
- --aui-control-hover-border: color-mix(in oklch, var(--ring), transparent 72%);
289
+ --aui-control-hover-border: color-mix(in oklch, var(--ring), transparent 44%);
290
290
  --aui-control-panel-bg: var(--aui-surface-raised);
291
291
  --aui-control-panel-shadow: var(--aui-shadow-md);
292
292
  --aui-button-primary-shadow: 0 1px 0 color-mix(in oklch, white, transparent 82%), 0 18px 40px color-mix(in oklch, var(--primary), transparent 82%);
@@ -317,6 +317,20 @@
317
317
 
318
318
  body {
319
319
  @apply bg-background text-foreground antialiased;
320
+ margin: 0;
321
+ }
322
+
323
+ html:has([data-slot="workspace-layout"][data-viewport="true"]),
324
+ body:has([data-slot="workspace-layout"][data-viewport="true"]) {
325
+ height: 100%;
326
+ overflow: hidden;
327
+ overscroll-behavior: none;
328
+ }
329
+
330
+ body:has([data-slot="workspace-layout"][data-viewport="true"]) > #root {
331
+ height: 100%;
332
+ min-height: 0;
333
+ overflow: hidden;
320
334
  }
321
335
  }
322
336
 
@@ -552,7 +566,9 @@
552
566
  }
553
567
 
554
568
  [data-slot="button"][data-slot="button"]:focus-visible {
555
- box-shadow: var(--aui-control-shadow), 0 0 0 1px var(--aui-focus-ring), 0 0 0 5px var(--aui-focus-ring-soft);
569
+ position: relative;
570
+ z-index: 1;
571
+ box-shadow: var(--aui-control-shadow), 0 0 0 2px var(--background), 0 0 0 4px var(--aui-focus-ring);
556
572
  }
557
573
 
558
574
  [data-slot="button"][data-slot="button"]:not(:disabled):not([aria-disabled="true"]):hover {
@@ -568,8 +584,9 @@
568
584
  }
569
585
 
570
586
  [data-slot="button"][data-variant="default"]:not(:disabled):not([aria-disabled="true"]):hover {
571
- background: color-mix(in oklch, var(--primary), white 10%);
572
- box-shadow: 0 1px 0 color-mix(in oklch, white, transparent 18%), 0 20px 42px color-mix(in oklch, var(--primary), transparent 74%);
587
+ border-color: color-mix(in oklch, var(--primary), black 16%);
588
+ background: color-mix(in oklch, var(--primary), white 7%);
589
+ box-shadow: 0 8px 20px -10px color-mix(in oklch, var(--primary), transparent 35%);
573
590
  }
574
591
 
575
592
  [data-slot="button"][data-variant="secondary"] {
@@ -585,7 +602,9 @@
585
602
  }
586
603
 
587
604
  [data-slot="button"][data-variant="outline"]:not(:disabled):not([aria-disabled="true"]):hover {
588
- background: color-mix(in oklch, var(--background), transparent 14%);
605
+ border-color: var(--aui-control-hover-border);
606
+ background: var(--accent);
607
+ color: var(--accent-foreground);
589
608
  }
590
609
 
591
610
  [data-slot="button"][data-variant="ghost"] {
@@ -596,8 +615,9 @@
596
615
  }
597
616
 
598
617
  [data-slot="button"][data-variant="ghost"]:not(:disabled):not([aria-disabled="true"]):hover {
599
- border-color: color-mix(in oklch, var(--border), var(--foreground) 8%);
600
- background: color-mix(in oklch, var(--accent), transparent 22%);
618
+ border-color: transparent;
619
+ background: var(--accent);
620
+ color: var(--accent-foreground);
601
621
  }
602
622
 
603
623
  [data-slot="button"][data-variant="destructive"] {
@@ -688,7 +708,8 @@
688
708
  [data-slot="input"][data-slot="input"]:focus-visible,
689
709
  [data-slot="textarea"][data-slot="textarea"]:focus-visible,
690
710
  [data-slot="select-trigger"][data-slot="select-trigger"]:focus-visible {
691
- box-shadow: var(--aui-control-shadow), 0 0 0 1px var(--aui-focus-ring), 0 0 0 5px var(--aui-focus-ring-soft);
711
+ border-color: var(--ring);
712
+ box-shadow: var(--aui-control-shadow), 0 0 0 1px var(--background), 0 0 0 3px var(--aui-focus-ring);
692
713
  }
693
714
 
694
715
  [data-slot="input"][data-slot="input"]:disabled,
@@ -867,7 +888,7 @@
867
888
 
868
889
  [data-slot="tabs-trigger"][data-slot="tabs-trigger"]:hover {
869
890
  color: var(--foreground);
870
- background: color-mix(in oklch, var(--background), transparent 18%);
891
+ background: var(--accent);
871
892
  }
872
893
 
873
894
  [data-slot="tabs-trigger"][data-slot="tabs-trigger"][data-selected] {
@@ -878,13 +899,13 @@
878
899
  }
879
900
 
880
901
  [data-slot="tabs-trigger"][data-slot="tabs-trigger"]:focus-visible {
881
- box-shadow: var(--aui-control-shadow), 0 0 0 1px var(--aui-focus-ring), 0 0 0 5px var(--aui-focus-ring-soft);
902
+ box-shadow: 0 0 0 2px var(--background), 0 0 0 4px var(--aui-focus-ring);
882
903
  }
883
904
 
884
905
  :is([data-slot="card"], [data-card]) {
885
906
  border: 1px solid var(--aui-card-border);
886
907
  border-radius: var(--aui-card-radius);
887
- background: linear-gradient(180deg, color-mix(in oklch, var(--card), white 8%), var(--card));
908
+ background: var(--card);
888
909
  box-shadow: var(--aui-card-shadow);
889
910
  }
890
911
 
@@ -945,6 +966,7 @@
945
966
  :is([data-slot="card"], [data-card])[data-interactive="true"]:hover {
946
967
  border-color: var(--aui-control-hover-border);
947
968
  box-shadow: var(--aui-card-shadow-hover);
969
+ transform: translateY(-1px);
948
970
  }
949
971
 
950
972
  :is([data-slot="card"], [data-card])[data-interactive="true"]:focus-visible {
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": "4.0.0",
4
+ "version": "4.0.1",
5
5
  "description": "Reusable React + TypeScript UI kit registry",
6
6
  "groups": {
7
7
  "ui": [