@motor-hero/ui-kit 0.18.0 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -43,6 +43,47 @@ function App() {
43
43
  }
44
44
  ```
45
45
 
46
+ ## Temas e Multi-tenant
47
+
48
+ Por padrão o kit usa o tema **zinc** (claro/escuro), alternado pelo `ThemeProvider`.
49
+ Para servir várias marcas (tenants) no mesmo app, passe a prop opcional `tenantTheme`.
50
+ **Sem ela, nada muda** — apps existentes seguem com o tema padrão.
51
+
52
+ Pense em dois eixos independentes: a **marca** (qual tenant) e o **modo** (claro/escuro).
53
+ Só o grupo de cores da **marca** é por tenant; neutros (`background`, `card`, `border`…) e
54
+ status (`destructive`, `success`) são compartilhados e respondem só ao modo.
55
+
56
+ ```tsx
57
+ import { ThemeProvider } from "@motor-hero/ui-kit"
58
+ import type { TenantTheme } from "@motor-hero/ui-kit"
59
+
60
+ const tenantTheme: TenantTheme = {
61
+ light: {
62
+ primary: "hsl(145 63% 32%)",
63
+ primaryForeground: "hsl(0 0% 100%)",
64
+ secondary: "hsl(0 0% 96%)",
65
+ secondaryForeground: "hsl(0 0% 12%)",
66
+ accent: "hsl(45 95% 55%)",
67
+ accentForeground: "hsl(0 0% 8%)",
68
+ },
69
+ dark: { primary: "hsl(145 50% 55%)" }, // opcional e parcial: só o que muda no escuro
70
+ }
71
+
72
+ <ThemeProvider defaultTheme="dark" tenantTheme={tenantTheme}>
73
+ <App />
74
+ </ThemeProvider>
75
+ ```
76
+
77
+ **Regras:**
78
+
79
+ - Mínimo por tenant: `primary` + `primaryForeground` (2 cores). A marca completa tem três cores — `primary`, `secondary` e `accent` — cada uma com seu foreground.
80
+ - `dark` é opcional e parcial — informe apenas os tokens que mudam; o resto herda do `light`.
81
+ - Valores são qualquer cor CSS válida (`hsl(...)`, `#hex`, `oklch(...)`); prefira `hsl(...)`.
82
+ - Os `*Foreground` são responsabilidade sua — garanta contraste AA, o kit não deriva.
83
+ - As cores entram como estilo inline no `<html>`, vencendo o `.dark` e qualquer `@theme` do app (sem conflito de cascata). Memoize o objeto `tenantTheme`.
84
+
85
+ Guia completo em [ui.motorhero.com.br](https://ui.motorhero.com.br) → **Temas Multi-tenant**.
86
+
46
87
  ## Desenvolvimento
47
88
 
48
89
  ```bash
@@ -1,14 +1,16 @@
1
+ import { type TenantTheme } from "../lib/tenant-theme";
1
2
  type Theme = "dark" | "light" | "system";
2
3
  type ThemeProviderProps = {
3
4
  children: React.ReactNode;
4
5
  defaultTheme?: Theme;
5
6
  storageKey?: string;
7
+ tenantTheme?: TenantTheme;
6
8
  };
7
9
  type ThemeProviderState = {
8
10
  theme: Theme;
9
11
  setTheme: (theme: Theme) => void;
10
12
  };
11
- export declare function ThemeProvider({ children, defaultTheme, storageKey, ...props }: ThemeProviderProps): import("react").JSX.Element;
13
+ export declare function ThemeProvider({ children, defaultTheme, storageKey, tenantTheme, ...props }: ThemeProviderProps): import("react").JSX.Element;
12
14
  export declare function useTheme(): ThemeProviderState;
13
15
  export type { Theme, ThemeProviderProps, ThemeProviderState };
14
16
  //# sourceMappingURL=theme-provider.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"theme-provider.d.ts","sourceRoot":"","sources":["../../src/components/theme-provider.tsx"],"names":[],"mappings":"AAEA,KAAK,KAAK,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,CAAA;AAExC,KAAK,kBAAkB,GAAG;IACxB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;IACzB,YAAY,CAAC,EAAE,KAAK,CAAA;IACpB,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB,CAAA;AAED,KAAK,kBAAkB,GAAG;IACxB,KAAK,EAAE,KAAK,CAAA;IACZ,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAA;CACjC,CAAA;AASD,wBAAgB,aAAa,CAAC,EAC5B,QAAQ,EACR,YAAuB,EACvB,UAAuB,EACvB,GAAG,KAAK,EACT,EAAE,kBAAkB,+BA+BpB;AAED,wBAAgB,QAAQ,uBAMvB;AAED,YAAY,EAAE,KAAK,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,CAAA"}
1
+ {"version":3,"file":"theme-provider.d.ts","sourceRoot":"","sources":["../../src/components/theme-provider.tsx"],"names":[],"mappings":"AACA,OAAO,EAAoB,KAAK,WAAW,EAAE,MAAM,qBAAqB,CAAA;AAExE,KAAK,KAAK,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,CAAA;AAExC,KAAK,kBAAkB,GAAG;IACxB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;IACzB,YAAY,CAAC,EAAE,KAAK,CAAA;IACpB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,WAAW,CAAC,EAAE,WAAW,CAAA;CAC1B,CAAA;AAED,KAAK,kBAAkB,GAAG;IACxB,KAAK,EAAE,KAAK,CAAA;IACZ,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAA;CACjC,CAAA;AASD,wBAAgB,aAAa,CAAC,EAC5B,QAAQ,EACR,YAAuB,EACvB,UAAuB,EACvB,WAAW,EACX,GAAG,KAAK,EACT,EAAE,kBAAkB,+BAsCpB;AAED,wBAAgB,QAAQ,uBAMvB;AAED,YAAY,EAAE,KAAK,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,CAAA"}
package/dist/index.cjs CHANGED
@@ -56,6 +56,7 @@ __export(index_exports, {
56
56
  ThemeProvider: () => ThemeProvider,
57
57
  Toaster: () => Toaster,
58
58
  UserMenu: () => UserMenu,
59
+ applyTenantTheme: () => applyTenantTheme,
59
60
  cn: () => cn,
60
61
  defaultRenderLink: () => defaultRenderLink,
61
62
  extractApiError: () => extractApiError,
@@ -69,6 +70,27 @@ module.exports = __toCommonJS(index_exports);
69
70
 
70
71
  // src/components/theme-provider.tsx
71
72
  var import_react = require("react");
73
+
74
+ // src/lib/tenant-theme.ts
75
+ var VAR_MAP = {
76
+ primary: "--color-primary",
77
+ primaryForeground: "--color-primary-foreground",
78
+ secondary: "--color-secondary",
79
+ secondaryForeground: "--color-secondary-foreground",
80
+ accent: "--color-accent",
81
+ accentForeground: "--color-accent-foreground",
82
+ ring: "--color-ring"
83
+ };
84
+ function applyTenantTheme(root, tenantTheme, mode) {
85
+ const tokens = tenantTheme ? mode === "dark" ? { ...tenantTheme.light, ...tenantTheme.dark } : tenantTheme.light : {};
86
+ for (const key of Object.keys(VAR_MAP)) {
87
+ const value = tokens[key];
88
+ if (value) root.style.setProperty(VAR_MAP[key], value);
89
+ else root.style.removeProperty(VAR_MAP[key]);
90
+ }
91
+ }
92
+
93
+ // src/components/theme-provider.tsx
72
94
  var import_jsx_runtime = require("react/jsx-runtime");
73
95
  var initialState = {
74
96
  theme: "system",
@@ -79,6 +101,7 @@ function ThemeProvider({
79
101
  children,
80
102
  defaultTheme = "system",
81
103
  storageKey = "ui-theme",
104
+ tenantTheme,
82
105
  ...props
83
106
  }) {
84
107
  const [theme, setTheme] = (0, import_react.useState)(
@@ -86,14 +109,20 @@ function ThemeProvider({
86
109
  );
87
110
  (0, import_react.useEffect)(() => {
88
111
  const root = window.document.documentElement;
89
- root.classList.remove("light", "dark");
112
+ const applyResolved = (resolved) => {
113
+ root.classList.remove("light", "dark");
114
+ root.classList.add(resolved);
115
+ applyTenantTheme(root, tenantTheme, resolved);
116
+ };
90
117
  if (theme === "system") {
91
- const systemTheme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
92
- root.classList.add(systemTheme);
93
- return;
118
+ const mql = window.matchMedia("(prefers-color-scheme: dark)");
119
+ applyResolved(mql.matches ? "dark" : "light");
120
+ const onChange = (e) => applyResolved(e.matches ? "dark" : "light");
121
+ mql.addEventListener("change", onChange);
122
+ return () => mql.removeEventListener("change", onChange);
94
123
  }
95
- root.classList.add(theme);
96
- }, [theme]);
124
+ applyResolved(theme);
125
+ }, [theme, tenantTheme]);
97
126
  const value = {
98
127
  theme,
99
128
  setTheme: (theme2) => {
@@ -1447,6 +1476,7 @@ function useCustomToast() {
1447
1476
  ThemeProvider,
1448
1477
  Toaster,
1449
1478
  UserMenu,
1479
+ applyTenantTheme,
1450
1480
  cn,
1451
1481
  defaultRenderLink,
1452
1482
  extractApiError,