@motor-hero/ui-kit 0.18.0 → 0.20.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 +41 -0
- package/dist/components/theme-provider.d.ts +3 -1
- package/dist/components/theme-provider.d.ts.map +1 -1
- package/dist/hooks/use-company-subdomain.d.ts +10 -0
- package/dist/hooks/use-company-subdomain.d.ts.map +1 -0
- package/dist/index.cjs +104 -28
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +88 -15
- package/dist/index.js.map +1 -1
- package/dist/lib/company-theme.d.ts +15 -0
- package/dist/lib/company-theme.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/components/theme-provider.tsx +18 -8
- package/src/hooks/use-company-subdomain.ts +51 -0
- package/src/index.ts +7 -0
- package/src/lib/company-theme.ts +54 -0
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 CompanyTheme } from "../lib/company-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
|
+
companyTheme?: CompanyTheme;
|
|
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, companyTheme, ...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":"
|
|
1
|
+
{"version":3,"file":"theme-provider.d.ts","sourceRoot":"","sources":["../../src/components/theme-provider.tsx"],"names":[],"mappings":"AACA,OAAO,EAAqB,KAAK,YAAY,EAAE,MAAM,sBAAsB,CAAA;AAE3E,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,YAAY,CAAC,EAAE,YAAY,CAAA;CAC5B,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,YAAY,EACZ,GAAG,KAAK,EACT,EAAE,kBAAkB,+BAsCpB;AAED,wBAAgB,QAAQ,uBAMvB;AAED,YAAY,EAAE,KAAK,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,CAAA"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export type UseCompanySubdomainOptions = {
|
|
2
|
+
enableOverride?: boolean;
|
|
3
|
+
overrideStorageKey?: string;
|
|
4
|
+
};
|
|
5
|
+
export type UseCompanySubdomainResult = {
|
|
6
|
+
subdomain: string | undefined;
|
|
7
|
+
};
|
|
8
|
+
export declare function getSubdomain(hostname: string): string | undefined;
|
|
9
|
+
export declare function useCompanySubdomain(opts?: UseCompanySubdomainOptions): UseCompanySubdomainResult;
|
|
10
|
+
//# sourceMappingURL=use-company-subdomain.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-company-subdomain.d.ts","sourceRoot":"","sources":["../../src/hooks/use-company-subdomain.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,0BAA0B,GAAG;IACvC,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB,kBAAkB,CAAC,EAAE,MAAM,CAAA;CAC5B,CAAA;AAED,MAAM,MAAM,yBAAyB,GAAG;IACtC,SAAS,EAAE,MAAM,GAAG,SAAS,CAAA;CAC9B,CAAA;AAED,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAMjE;AAiBD,wBAAgB,mBAAmB,CACjC,IAAI,GAAE,0BAA+B,GACpC,yBAAyB,CAc3B"}
|
package/dist/index.cjs
CHANGED
|
@@ -56,10 +56,13 @@ __export(index_exports, {
|
|
|
56
56
|
ThemeProvider: () => ThemeProvider,
|
|
57
57
|
Toaster: () => Toaster,
|
|
58
58
|
UserMenu: () => UserMenu,
|
|
59
|
+
applyCompanyTheme: () => applyCompanyTheme,
|
|
59
60
|
cn: () => cn,
|
|
60
61
|
defaultRenderLink: () => defaultRenderLink,
|
|
61
62
|
extractApiError: () => extractApiError,
|
|
63
|
+
getSubdomain: () => getSubdomain,
|
|
62
64
|
toast: () => import_sonner2.toast,
|
|
65
|
+
useCompanySubdomain: () => useCompanySubdomain,
|
|
63
66
|
useCustomToast: () => useCustomToast,
|
|
64
67
|
useDisclosure: () => useDisclosure,
|
|
65
68
|
useIsDesktop: () => useIsDesktop,
|
|
@@ -69,6 +72,33 @@ module.exports = __toCommonJS(index_exports);
|
|
|
69
72
|
|
|
70
73
|
// src/components/theme-provider.tsx
|
|
71
74
|
var import_react = require("react");
|
|
75
|
+
|
|
76
|
+
// src/lib/company-theme.ts
|
|
77
|
+
var VAR_MAP = {
|
|
78
|
+
primary: "--color-primary",
|
|
79
|
+
primaryForeground: "--color-primary-foreground",
|
|
80
|
+
secondary: "--color-secondary",
|
|
81
|
+
secondaryForeground: "--color-secondary-foreground",
|
|
82
|
+
accent: "--color-accent",
|
|
83
|
+
accentForeground: "--color-accent-foreground",
|
|
84
|
+
ring: "--color-ring"
|
|
85
|
+
};
|
|
86
|
+
function definedTokens(tokens) {
|
|
87
|
+
if (!tokens) return {};
|
|
88
|
+
return Object.fromEntries(
|
|
89
|
+
Object.entries(tokens).filter(([, value]) => value != null)
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
function applyCompanyTheme(root, companyTheme, mode) {
|
|
93
|
+
const tokens = companyTheme ? mode === "dark" ? { ...companyTheme.light, ...definedTokens(companyTheme.dark) } : companyTheme.light : {};
|
|
94
|
+
for (const key of Object.keys(VAR_MAP)) {
|
|
95
|
+
const value = tokens[key];
|
|
96
|
+
if (value) root.style.setProperty(VAR_MAP[key], value);
|
|
97
|
+
else root.style.removeProperty(VAR_MAP[key]);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// src/components/theme-provider.tsx
|
|
72
102
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
73
103
|
var initialState = {
|
|
74
104
|
theme: "system",
|
|
@@ -79,6 +109,7 @@ function ThemeProvider({
|
|
|
79
109
|
children,
|
|
80
110
|
defaultTheme = "system",
|
|
81
111
|
storageKey = "ui-theme",
|
|
112
|
+
companyTheme,
|
|
82
113
|
...props
|
|
83
114
|
}) {
|
|
84
115
|
const [theme, setTheme] = (0, import_react.useState)(
|
|
@@ -86,14 +117,20 @@ function ThemeProvider({
|
|
|
86
117
|
);
|
|
87
118
|
(0, import_react.useEffect)(() => {
|
|
88
119
|
const root = window.document.documentElement;
|
|
89
|
-
|
|
120
|
+
const applyResolved = (resolved) => {
|
|
121
|
+
root.classList.remove("light", "dark");
|
|
122
|
+
root.classList.add(resolved);
|
|
123
|
+
applyCompanyTheme(root, companyTheme, resolved);
|
|
124
|
+
};
|
|
90
125
|
if (theme === "system") {
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
126
|
+
const mql = window.matchMedia("(prefers-color-scheme: dark)");
|
|
127
|
+
applyResolved(mql.matches ? "dark" : "light");
|
|
128
|
+
const onChange = (e) => applyResolved(e.matches ? "dark" : "light");
|
|
129
|
+
mql.addEventListener("change", onChange);
|
|
130
|
+
return () => mql.removeEventListener("change", onChange);
|
|
94
131
|
}
|
|
95
|
-
|
|
96
|
-
}, [theme]);
|
|
132
|
+
applyResolved(theme);
|
|
133
|
+
}, [theme, companyTheme]);
|
|
97
134
|
const value = {
|
|
98
135
|
theme,
|
|
99
136
|
setTheme: (theme2) => {
|
|
@@ -111,6 +148,42 @@ function useTheme() {
|
|
|
111
148
|
return context;
|
|
112
149
|
}
|
|
113
150
|
|
|
151
|
+
// src/hooks/use-company-subdomain.ts
|
|
152
|
+
var import_react2 = require("react");
|
|
153
|
+
function getSubdomain(hostname) {
|
|
154
|
+
const labels = hostname.split(".");
|
|
155
|
+
if (labels.length < 2) return void 0;
|
|
156
|
+
const first = labels[0];
|
|
157
|
+
if (!first || first === "www" || first === "localhost") return void 0;
|
|
158
|
+
return first;
|
|
159
|
+
}
|
|
160
|
+
function resolveOverride(storageKey) {
|
|
161
|
+
if (typeof window === "undefined") return void 0;
|
|
162
|
+
const params = new URLSearchParams(window.location.search);
|
|
163
|
+
if (params.has("company")) {
|
|
164
|
+
const value = params.get("company");
|
|
165
|
+
if (value) {
|
|
166
|
+
window.localStorage.setItem(storageKey, value);
|
|
167
|
+
return value;
|
|
168
|
+
}
|
|
169
|
+
window.localStorage.removeItem(storageKey);
|
|
170
|
+
return void 0;
|
|
171
|
+
}
|
|
172
|
+
return window.localStorage.getItem(storageKey) ?? void 0;
|
|
173
|
+
}
|
|
174
|
+
function useCompanySubdomain(opts = {}) {
|
|
175
|
+
const { enableOverride = false, overrideStorageKey = "company-subdomain" } = opts;
|
|
176
|
+
const [subdomain] = (0, import_react2.useState)(() => {
|
|
177
|
+
if (enableOverride) {
|
|
178
|
+
const override = resolveOverride(overrideStorageKey);
|
|
179
|
+
if (override) return override;
|
|
180
|
+
}
|
|
181
|
+
if (typeof window === "undefined") return void 0;
|
|
182
|
+
return getSubdomain(window.location.hostname);
|
|
183
|
+
});
|
|
184
|
+
return { subdomain };
|
|
185
|
+
}
|
|
186
|
+
|
|
114
187
|
// src/components/mode-toggle.tsx
|
|
115
188
|
var DropdownMenu = __toESM(require("@radix-ui/react-dropdown-menu"), 1);
|
|
116
189
|
var import_lucide_react = require("lucide-react");
|
|
@@ -340,11 +413,11 @@ function ProgressBar({
|
|
|
340
413
|
}
|
|
341
414
|
|
|
342
415
|
// src/components/form-field.tsx
|
|
343
|
-
var
|
|
416
|
+
var import_react3 = require("react");
|
|
344
417
|
var import_jsx_runtime9 = require("react/jsx-runtime");
|
|
345
418
|
function FormField({ label, htmlFor, error, required, children, className }) {
|
|
346
|
-
const errorId = (0,
|
|
347
|
-
const field = error && (0,
|
|
419
|
+
const errorId = (0, import_react3.useId)();
|
|
420
|
+
const field = error && (0, import_react3.isValidElement)(children) ? (0, import_react3.cloneElement)(children, {
|
|
348
421
|
"aria-invalid": true,
|
|
349
422
|
"aria-describedby": [
|
|
350
423
|
children.props["aria-describedby"],
|
|
@@ -815,7 +888,7 @@ function DataTableWrapper({
|
|
|
815
888
|
}
|
|
816
889
|
|
|
817
890
|
// src/components/mobile-card-list.tsx
|
|
818
|
-
var
|
|
891
|
+
var import_react4 = require("react");
|
|
819
892
|
var import_jsx_runtime19 = require("react/jsx-runtime");
|
|
820
893
|
var SCROLL_THRESHOLD = 8;
|
|
821
894
|
function MobileCardList({
|
|
@@ -826,8 +899,8 @@ function MobileCardList({
|
|
|
826
899
|
loadingCount = 5,
|
|
827
900
|
className
|
|
828
901
|
}) {
|
|
829
|
-
const start = (0,
|
|
830
|
-
const scrolled = (0,
|
|
902
|
+
const start = (0, import_react4.useRef)(null);
|
|
903
|
+
const scrolled = (0, import_react4.useRef)(false);
|
|
831
904
|
const onTouchStart = (e) => {
|
|
832
905
|
const t = e.touches[0];
|
|
833
906
|
start.current = { x: t.clientX, y: t.clientY };
|
|
@@ -929,10 +1002,10 @@ function Toaster(props) {
|
|
|
929
1002
|
|
|
930
1003
|
// src/components/app-shell.tsx
|
|
931
1004
|
var import_lucide_react6 = require("lucide-react");
|
|
932
|
-
var
|
|
1005
|
+
var import_react7 = require("react");
|
|
933
1006
|
|
|
934
1007
|
// src/components/sidebar-nav.tsx
|
|
935
|
-
var
|
|
1008
|
+
var import_react5 = require("react");
|
|
936
1009
|
|
|
937
1010
|
// src/components/types.tsx
|
|
938
1011
|
var import_jsx_runtime22 = require("react/jsx-runtime");
|
|
@@ -971,14 +1044,14 @@ function SidebarNav({
|
|
|
971
1044
|
!isCollapsed && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { className: "truncate", children: item.label })
|
|
972
1045
|
] })
|
|
973
1046
|
});
|
|
974
|
-
return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
|
|
1047
|
+
return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_react5.Fragment, { children: link }, item.href);
|
|
975
1048
|
}) });
|
|
976
1049
|
}
|
|
977
1050
|
|
|
978
1051
|
// src/components/user-menu.tsx
|
|
979
1052
|
var DropdownMenu2 = __toESM(require("@radix-ui/react-dropdown-menu"), 1);
|
|
980
1053
|
var import_lucide_react5 = require("lucide-react");
|
|
981
|
-
var
|
|
1054
|
+
var import_react6 = require("react");
|
|
982
1055
|
var import_vaul2 = require("vaul");
|
|
983
1056
|
var import_jsx_runtime24 = require("react/jsx-runtime");
|
|
984
1057
|
function initials(user) {
|
|
@@ -999,7 +1072,7 @@ function UserMenu({
|
|
|
999
1072
|
align = "end"
|
|
1000
1073
|
}) {
|
|
1001
1074
|
const isDesktop = useIsDesktop();
|
|
1002
|
-
const [open, setOpen] = (0,
|
|
1075
|
+
const [open, setOpen] = (0, import_react6.useState)(false);
|
|
1003
1076
|
const label = initials(user);
|
|
1004
1077
|
const trigger = /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
|
|
1005
1078
|
"button",
|
|
@@ -1164,8 +1237,8 @@ function AppShell({
|
|
|
1164
1237
|
defaultCollapsed = false,
|
|
1165
1238
|
children
|
|
1166
1239
|
}) {
|
|
1167
|
-
const [collapsed, setCollapsed] = (0,
|
|
1168
|
-
const [mobileOpen, setMobileOpen] = (0,
|
|
1240
|
+
const [collapsed, setCollapsed] = (0, import_react7.useState)(defaultCollapsed);
|
|
1241
|
+
const [mobileOpen, setMobileOpen] = (0, import_react7.useState)(false);
|
|
1169
1242
|
const nav = (isCollapsed, onNavigate) => /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
|
|
1170
1243
|
SidebarNav,
|
|
1171
1244
|
{
|
|
@@ -1254,7 +1327,7 @@ function AppShell({
|
|
|
1254
1327
|
// src/components/row-actions-menu.tsx
|
|
1255
1328
|
var DropdownMenu3 = __toESM(require("@radix-ui/react-dropdown-menu"), 1);
|
|
1256
1329
|
var import_lucide_react7 = require("lucide-react");
|
|
1257
|
-
var
|
|
1330
|
+
var import_react8 = require("react");
|
|
1258
1331
|
var import_vaul3 = require("vaul");
|
|
1259
1332
|
var import_jsx_runtime26 = require("react/jsx-runtime");
|
|
1260
1333
|
var triggerClass = "inline-flex h-8 w-8 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50";
|
|
@@ -1268,7 +1341,7 @@ function RowActionsMenu({
|
|
|
1268
1341
|
renderLink = defaultRenderLink
|
|
1269
1342
|
}) {
|
|
1270
1343
|
const isDesktop = useIsDesktop();
|
|
1271
|
-
const [open, setOpen] = (0,
|
|
1344
|
+
const [open, setOpen] = (0, import_react8.useState)(false);
|
|
1272
1345
|
if (actions.length === 0) return null;
|
|
1273
1346
|
const trigger = /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
|
|
1274
1347
|
"button",
|
|
@@ -1385,20 +1458,20 @@ function extractApiError(err, fallbackMessage = "Ocorreu um erro inesperado.") {
|
|
|
1385
1458
|
}
|
|
1386
1459
|
|
|
1387
1460
|
// src/hooks/use-disclosure.ts
|
|
1388
|
-
var
|
|
1461
|
+
var import_react9 = require("react");
|
|
1389
1462
|
function useDisclosure(initial = false) {
|
|
1390
|
-
const [open, setOpen] = (0,
|
|
1391
|
-
const onOpen = (0,
|
|
1392
|
-
const onClose = (0,
|
|
1393
|
-
const onToggle = (0,
|
|
1463
|
+
const [open, setOpen] = (0, import_react9.useState)(initial);
|
|
1464
|
+
const onOpen = (0, import_react9.useCallback)(() => setOpen(true), []);
|
|
1465
|
+
const onClose = (0, import_react9.useCallback)(() => setOpen(false), []);
|
|
1466
|
+
const onToggle = (0, import_react9.useCallback)(() => setOpen((v) => !v), []);
|
|
1394
1467
|
return { open, onOpen, onClose, onToggle, setOpen };
|
|
1395
1468
|
}
|
|
1396
1469
|
|
|
1397
1470
|
// src/hooks/use-toast.ts
|
|
1398
1471
|
var import_sonner2 = require("sonner");
|
|
1399
|
-
var
|
|
1472
|
+
var import_react10 = require("react");
|
|
1400
1473
|
function useCustomToast() {
|
|
1401
|
-
const showToast = (0,
|
|
1474
|
+
const showToast = (0, import_react10.useCallback)(
|
|
1402
1475
|
(title, description, status = "success") => {
|
|
1403
1476
|
switch (status) {
|
|
1404
1477
|
case "success":
|
|
@@ -1447,10 +1520,13 @@ function useCustomToast() {
|
|
|
1447
1520
|
ThemeProvider,
|
|
1448
1521
|
Toaster,
|
|
1449
1522
|
UserMenu,
|
|
1523
|
+
applyCompanyTheme,
|
|
1450
1524
|
cn,
|
|
1451
1525
|
defaultRenderLink,
|
|
1452
1526
|
extractApiError,
|
|
1527
|
+
getSubdomain,
|
|
1453
1528
|
toast,
|
|
1529
|
+
useCompanySubdomain,
|
|
1454
1530
|
useCustomToast,
|
|
1455
1531
|
useDisclosure,
|
|
1456
1532
|
useIsDesktop,
|