@bsuite/theme 0.3.0 → 0.3.2
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 +23 -0
- package/dist/react/BrandingProvider.d.ts +8 -34
- package/dist/react/BrandingProvider.d.ts.map +1 -1
- package/dist/react/BrandingProvider.js +15 -7
- package/dist/react/index.d.ts +4 -1
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +3 -1
- package/dist/react/usePlatformLogo.d.ts +29 -0
- package/dist/react/usePlatformLogo.d.ts.map +1 -0
- package/dist/react/usePlatformLogo.js +65 -0
- package/package.json +2 -2
- package/src/preset-v4.css +2 -2
package/README.md
CHANGED
|
@@ -8,6 +8,8 @@ Universal D2C Neon Electric theme for the BSuite monorepo. Ships the canonical p
|
|
|
8
8
|
- **Tailwind v3 preset** — drop-in `presets: [require('@bsuite/theme/tailwind-preset')]`
|
|
9
9
|
- **Tailwind v4 `@theme` block** — `@import '@bsuite/theme/preset-v4.css'`
|
|
10
10
|
- **`<ThemeProvider>`** + **`useTheme()`** with localStorage persistence + system preference
|
|
11
|
+
- **`<BrandingProvider>`** + **`useBranding()`** for runtime tenant branding
|
|
12
|
+
- **`usePlatformLogo()`** and `resolvePlatformLogo()` for shared light/dark/mark/favicon logo fallback selection
|
|
11
13
|
- **`getThemeInitScript()`** — stringified JS for inline `<script>` tags to prevent FOUC
|
|
12
14
|
|
|
13
15
|
## Install
|
|
@@ -118,6 +120,27 @@ export function ThemeToggleButton() {
|
|
|
118
120
|
|
|
119
121
|
The `resolvedTheme` always returns `'light'` or `'dark'` — use this when you need the _effective_ theme (it resolves `'system'` for you).
|
|
120
122
|
|
|
123
|
+
## Runtime branding and platform logos
|
|
124
|
+
|
|
125
|
+
Wrap BSuite apps in `BrandingProvider` when tenant/platform branding should be resolved from Supabase. Braden Corporate can pass `brand="braden"` to keep using static corporate tokens.
|
|
126
|
+
|
|
127
|
+
```tsx
|
|
128
|
+
import { BrandingProvider, usePlatformLogo } from '@bsuite/theme/react'
|
|
129
|
+
|
|
130
|
+
function HeaderLogo() {
|
|
131
|
+
const { src, alt, isLoading } = usePlatformLogo({
|
|
132
|
+
slot: 'header',
|
|
133
|
+
scheme: 'light',
|
|
134
|
+
fallback: '/logos/bsuite.svg',
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
if (isLoading || !src) return null
|
|
138
|
+
return <img src={src} alt={alt} />
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Use `resolvePlatformLogo()` in non-React code or tests when you already have a branding payload and need the same fallback order without mounting a provider.
|
|
143
|
+
|
|
121
144
|
## Palette
|
|
122
145
|
|
|
123
146
|
All 11 canonical electric colours are available via Tailwind classes:
|
|
@@ -1,37 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* BrandingProvider — runtime enterprise white-labelling
|
|
3
|
-
* Version 0.3.0
|
|
4
|
-
*
|
|
5
|
-
* On mount:
|
|
6
|
-
* 1. Calls supabase.rpc('branding_json_for_tenant') using the user's authed session.
|
|
7
|
-
* 2. Validates each colour value is a well-formed oklch() string.
|
|
8
|
-
* 3. Applies each key as a CSS custom property on document.documentElement.
|
|
9
|
-
* 4. Caches the result in localStorage under BRANDING_STORAGE_KEY for FOUC prevention.
|
|
10
|
-
* 5. Subscribes to tenant row changes via Supabase Realtime so branding updates
|
|
11
|
-
* propagate without a page reload (within ~1 second).
|
|
12
|
-
*
|
|
13
|
-
* Security / brand policies:
|
|
14
|
-
* - Only oklch() values are accepted for colour fields — hex/rgb/hsl are rejected.
|
|
15
|
-
* - --role-error and --role-destructive are NOT overridable by tenants.
|
|
16
|
-
* Colourblind policy (purple error) is a system-level requirement, not a brand pref.
|
|
17
|
-
* - brand="braden" short-circuits all Supabase calls — Braden corporate site uses
|
|
18
|
-
* static CSS tokens, not runtime tenant overrides.
|
|
19
|
-
*
|
|
20
|
-
* Environment flags:
|
|
21
|
-
* - VITE_ENABLE_BRANDING_OVERRIDE (default: 'true') — set 'false' as kill switch.
|
|
22
|
-
*
|
|
23
|
-
* Usage:
|
|
24
|
-
* <ThemeProvider>
|
|
25
|
-
* <BrandingProvider supabaseClient={supabase}>
|
|
26
|
-
* {children}
|
|
27
|
-
* </BrandingProvider>
|
|
28
|
-
* </ThemeProvider>
|
|
29
|
-
*
|
|
30
|
-
* For Braden corporate (skips all RPC/Realtime):
|
|
31
|
-
* <BrandingProvider brand="braden" supabaseClient={supabase}>
|
|
32
|
-
* {children}
|
|
33
|
-
* </BrandingProvider>
|
|
34
|
-
*/
|
|
35
1
|
import type { ReactNode } from 'react';
|
|
36
2
|
import type { SupabaseClient } from '@supabase/supabase-js';
|
|
37
3
|
export declare const BRANDING_STORAGE_KEY = "bsuite_tenant_branding";
|
|
@@ -44,8 +10,16 @@ export interface TenantBranding {
|
|
|
44
10
|
accent?: string;
|
|
45
11
|
/** Full logo URL (SVG or raster) */
|
|
46
12
|
logo_url?: string;
|
|
13
|
+
/** Light-mode full logo URL (SVG or raster) */
|
|
14
|
+
logo_light_url?: string;
|
|
15
|
+
/** Dark-mode full logo URL (SVG or raster) */
|
|
16
|
+
logo_dark_url?: string;
|
|
47
17
|
/** Mark / icon logo URL */
|
|
48
18
|
mark_url?: string;
|
|
19
|
+
/** Favicon URL */
|
|
20
|
+
favicon_url?: string;
|
|
21
|
+
/** Display name used for default image alt text */
|
|
22
|
+
company_name?: string | null;
|
|
49
23
|
/** Optional CSS font-family stack string */
|
|
50
24
|
font_stack?: string | null;
|
|
51
25
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BrandingProvider.d.ts","sourceRoot":"","sources":["../../src/react/BrandingProvider.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"BrandingProvider.d.ts","sourceRoot":"","sources":["../../src/react/BrandingProvider.tsx"],"names":[],"mappings":"AAmCA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AACtC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AAE3D,eAAO,MAAM,oBAAoB,2BAA2B,CAAA;AAC5D,eAAO,MAAM,sBAAsB,kCAAkC,CAAA;AAErE,yDAAyD;AACzD,MAAM,WAAW,cAAc;IAC7B,wDAAwD;IACxD,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,4DAA4D;IAC5D,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,oCAAoC;IACpC,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,+CAA+C;IAC/C,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,8CAA8C;IAC9C,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,2BAA2B;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,kBAAkB;IAClB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,mDAAmD;IACnD,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC5B,4CAA4C;IAC5C,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAC3B;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,EAAE,cAAc,GAAG,IAAI,CAAA;IAC/B,SAAS,EAAE,OAAO,CAAA;IAClB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAA;IACnB,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAC7B;AAED,eAAO,MAAM,eAAe,2DAA6D,CAAA;AAEzF,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,QAAQ,CAAA;AAE9C,MAAM,WAAW,qBAAqB;IACpC,QAAQ,EAAE,SAAS,CAAA;IACnB,2DAA2D;IAC3D,cAAc,EAAE,cAAc,CAAA;IAC9B;;;;OAIG;IACH,KAAK,CAAC,EAAE,YAAY,CAAA;IACpB;;;OAGG;IACH,qBAAqB,CAAC,EAAE,OAAO,CAAA;CAChC;AA6JD,wBAAgB,gBAAgB,CAAC,EAC/B,QAAQ,EACR,cAAc,EACd,KAAgB,EAChB,qBAA4B,GAC7B,EAAE,qBAAqB,2CA+EvB"}
|
|
@@ -33,7 +33,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
33
33
|
* {children}
|
|
34
34
|
* </BrandingProvider>
|
|
35
35
|
*/
|
|
36
|
-
import { createContext, useCallback, useEffect, useMemo, useRef, useState
|
|
36
|
+
import { createContext, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
37
37
|
export const BRANDING_STORAGE_KEY = 'bsuite_tenant_branding';
|
|
38
38
|
export const BRANDING_OVERRIDE_FLAG = 'VITE_ENABLE_BRANDING_OVERRIDE';
|
|
39
39
|
export const BrandingContext = createContext(undefined);
|
|
@@ -68,9 +68,17 @@ const BRANDING_CSS_MAP = {
|
|
|
68
68
|
primary: '--primary',
|
|
69
69
|
accent: '--accent',
|
|
70
70
|
logo_url: '--logo-url',
|
|
71
|
+
logo_light_url: '--logo-light-url',
|
|
72
|
+
logo_dark_url: '--logo-dark-url',
|
|
71
73
|
mark_url: '--mark-url',
|
|
74
|
+
favicon_url: '--favicon-url',
|
|
72
75
|
font_stack: '--font-stack',
|
|
73
76
|
};
|
|
77
|
+
function setUrlCssVar(root, cssVar, value) {
|
|
78
|
+
const trimmed = value?.trim();
|
|
79
|
+
if (trimmed)
|
|
80
|
+
root.style.setProperty(cssVar, `url(${trimmed})`);
|
|
81
|
+
}
|
|
74
82
|
function applyBrandingToRoot(branding) {
|
|
75
83
|
if (typeof document === 'undefined')
|
|
76
84
|
return;
|
|
@@ -113,12 +121,11 @@ function applyBrandingToRoot(branding) {
|
|
|
113
121
|
root.style.setProperty('--app-accent', branding.accent);
|
|
114
122
|
}
|
|
115
123
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
}
|
|
124
|
+
setUrlCssVar(root, '--logo-url', branding.logo_url);
|
|
125
|
+
setUrlCssVar(root, '--logo-light-url', branding.logo_light_url);
|
|
126
|
+
setUrlCssVar(root, '--logo-dark-url', branding.logo_dark_url);
|
|
127
|
+
setUrlCssVar(root, '--mark-url', branding.mark_url);
|
|
128
|
+
setUrlCssVar(root, '--favicon-url', branding.favicon_url);
|
|
122
129
|
if (branding.font_stack) {
|
|
123
130
|
root.style.setProperty('--font-stack', branding.font_stack);
|
|
124
131
|
}
|
|
@@ -233,6 +240,7 @@ export function BrandingProvider({ children, supabaseClient, brand = 'bsuite', a
|
|
|
233
240
|
return;
|
|
234
241
|
const channel = supabaseClient
|
|
235
242
|
.channel('tenant-branding-updates')
|
|
243
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
236
244
|
.on('postgres_changes', { event: 'UPDATE', schema: 'public', table: 'tenants' }, (_payload) => { void fetchBranding(); })
|
|
237
245
|
.subscribe();
|
|
238
246
|
channelRef.current = channel;
|
package/dist/react/index.d.ts
CHANGED
|
@@ -3,6 +3,9 @@ export type { ThemeProviderProps } from './ThemeProvider';
|
|
|
3
3
|
export { useTheme } from './useTheme';
|
|
4
4
|
export type { ThemeContextValue, ThemeMode, ResolvedTheme } from '../index';
|
|
5
5
|
export { THEME_STORAGE_KEY } from '../index';
|
|
6
|
-
export { BrandingProvider, BrandingContext, BRANDING_STORAGE_KEY, BRANDING_OVERRIDE_FLAG } from './BrandingProvider';
|
|
6
|
+
export { BrandingProvider, BrandingContext, BRANDING_STORAGE_KEY, BRANDING_OVERRIDE_FLAG, } from './BrandingProvider';
|
|
7
7
|
export type { TenantBranding, BrandingContextValue } from './BrandingProvider';
|
|
8
|
+
export { useBranding } from './useBranding';
|
|
9
|
+
export { resolvePlatformLogo, usePlatformLogo, } from './usePlatformLogo';
|
|
10
|
+
export type { PlatformLogoHookResult, PlatformLogoOptions, PlatformLogoScheme, PlatformLogoSlot, PlatformLogoSource, ResolvedPlatformLogo, } from './usePlatformLogo';
|
|
8
11
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAC/C,YAAY,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAA;AACzD,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AACrC,YAAY,EAAE,iBAAiB,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAC3E,OAAO,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAA;AAC5C,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAC/C,YAAY,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAA;AACzD,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AACrC,YAAY,EAAE,iBAAiB,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAC3E,OAAO,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAA;AAC5C,OAAO,EACL,gBAAgB,EAChB,eAAe,EACf,oBAAoB,EACpB,sBAAsB,GACvB,MAAM,oBAAoB,CAAA;AAC3B,YAAY,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAA;AAC9E,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAC3C,OAAO,EACL,mBAAmB,EACnB,eAAe,GAChB,MAAM,mBAAmB,CAAA;AAC1B,YAAY,EACV,sBAAsB,EACtB,mBAAmB,EACnB,kBAAkB,EAClB,gBAAgB,EAChB,kBAAkB,EAClB,oBAAoB,GACrB,MAAM,mBAAmB,CAAA"}
|
package/dist/react/index.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export { ThemeProvider } from './ThemeProvider';
|
|
2
2
|
export { useTheme } from './useTheme';
|
|
3
3
|
export { THEME_STORAGE_KEY } from '../index';
|
|
4
|
-
export { BrandingProvider, BrandingContext, BRANDING_STORAGE_KEY, BRANDING_OVERRIDE_FLAG } from './BrandingProvider';
|
|
4
|
+
export { BrandingProvider, BrandingContext, BRANDING_STORAGE_KEY, BRANDING_OVERRIDE_FLAG, } from './BrandingProvider';
|
|
5
|
+
export { useBranding } from './useBranding';
|
|
6
|
+
export { resolvePlatformLogo, usePlatformLogo, } from './usePlatformLogo';
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { TenantBranding } from './BrandingProvider';
|
|
2
|
+
export type PlatformLogoSlot = 'header' | 'sidebar' | 'auth' | 'favicon' | 'mark';
|
|
3
|
+
export type PlatformLogoScheme = 'light' | 'dark';
|
|
4
|
+
export type PlatformLogoSource = 'branding' | 'fallback' | 'none';
|
|
5
|
+
export interface PlatformLogoOptions {
|
|
6
|
+
slot?: PlatformLogoSlot;
|
|
7
|
+
scheme?: PlatformLogoScheme;
|
|
8
|
+
fallback?: string | null;
|
|
9
|
+
fallbackBySlot?: Partial<Record<PlatformLogoSlot, string | null>>;
|
|
10
|
+
alt?: string;
|
|
11
|
+
}
|
|
12
|
+
export interface ResolvedPlatformLogo {
|
|
13
|
+
src: string | null;
|
|
14
|
+
alt: string;
|
|
15
|
+
slot: PlatformLogoSlot;
|
|
16
|
+
scheme: PlatformLogoScheme;
|
|
17
|
+
source: PlatformLogoSource;
|
|
18
|
+
isTenantLogo: boolean;
|
|
19
|
+
cssVariable: string;
|
|
20
|
+
}
|
|
21
|
+
export interface PlatformLogoHookResult extends ResolvedPlatformLogo {
|
|
22
|
+
branding: TenantBranding | null;
|
|
23
|
+
isLoading: boolean;
|
|
24
|
+
error: Error | null;
|
|
25
|
+
refresh: () => Promise<void>;
|
|
26
|
+
}
|
|
27
|
+
export declare function resolvePlatformLogo(branding: TenantBranding | null | undefined, options?: PlatformLogoOptions): ResolvedPlatformLogo;
|
|
28
|
+
export declare function usePlatformLogo(options?: PlatformLogoOptions): PlatformLogoHookResult;
|
|
29
|
+
//# sourceMappingURL=usePlatformLogo.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"usePlatformLogo.d.ts","sourceRoot":"","sources":["../../src/react/usePlatformLogo.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AAGxD,MAAM,MAAM,gBAAgB,GAAG,QAAQ,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,GAAG,MAAM,CAAA;AACjF,MAAM,MAAM,kBAAkB,GAAG,OAAO,GAAG,MAAM,CAAA;AACjD,MAAM,MAAM,kBAAkB,GAAG,UAAU,GAAG,UAAU,GAAG,MAAM,CAAA;AAEjE,MAAM,WAAW,mBAAmB;IAClC,IAAI,CAAC,EAAE,gBAAgB,CAAA;IACvB,MAAM,CAAC,EAAE,kBAAkB,CAAA;IAC3B,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,cAAc,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC,CAAA;IACjE,GAAG,CAAC,EAAE,MAAM,CAAA;CACb;AAED,MAAM,WAAW,oBAAoB;IACnC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;IAClB,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,gBAAgB,CAAA;IACtB,MAAM,EAAE,kBAAkB,CAAA;IAC1B,MAAM,EAAE,kBAAkB,CAAA;IAC1B,YAAY,EAAE,OAAO,CAAA;IACrB,WAAW,EAAE,MAAM,CAAA;CACpB;AAED,MAAM,WAAW,sBAAuB,SAAQ,oBAAoB;IAClE,QAAQ,EAAE,cAAc,GAAG,IAAI,CAAA;IAC/B,SAAS,EAAE,OAAO,CAAA;IAClB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAA;IACnB,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAC7B;AA+CD,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,cAAc,GAAG,IAAI,GAAG,SAAS,EAC3C,OAAO,GAAE,mBAAwB,GAChC,oBAAoB,CAiBtB;AAED,wBAAgB,eAAe,CAAC,OAAO,GAAE,mBAAwB,GAAG,sBAAsB,CAczF"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
|
+
import { useBranding } from './useBranding';
|
|
3
|
+
function cleanUrl(value) {
|
|
4
|
+
const trimmed = value?.trim();
|
|
5
|
+
return trimmed ? trimmed : null;
|
|
6
|
+
}
|
|
7
|
+
function getSlotFallback(options, slot) {
|
|
8
|
+
if (options.fallbackBySlot && Object.prototype.hasOwnProperty.call(options.fallbackBySlot, slot)) {
|
|
9
|
+
return cleanUrl(options.fallbackBySlot[slot] ?? null);
|
|
10
|
+
}
|
|
11
|
+
return cleanUrl(options.fallback ?? null);
|
|
12
|
+
}
|
|
13
|
+
function getCssVariable(slot, scheme) {
|
|
14
|
+
if (slot === 'favicon')
|
|
15
|
+
return '--favicon-url';
|
|
16
|
+
if (slot === 'mark')
|
|
17
|
+
return '--mark-url';
|
|
18
|
+
return scheme === 'dark' ? '--logo-dark-url' : '--logo-light-url';
|
|
19
|
+
}
|
|
20
|
+
function getCompanyName(branding) {
|
|
21
|
+
const name = branding?.company_name?.trim();
|
|
22
|
+
return name ? name : null;
|
|
23
|
+
}
|
|
24
|
+
function getLogoCandidates(branding, slot, scheme) {
|
|
25
|
+
if (!branding)
|
|
26
|
+
return [];
|
|
27
|
+
if (slot === 'favicon') {
|
|
28
|
+
return [branding.favicon_url, branding.mark_url, branding.logo_url, branding.logo_light_url, branding.logo_dark_url];
|
|
29
|
+
}
|
|
30
|
+
if (slot === 'mark') {
|
|
31
|
+
return [branding.mark_url, branding.logo_url, branding.logo_light_url, branding.logo_dark_url];
|
|
32
|
+
}
|
|
33
|
+
if (scheme === 'dark') {
|
|
34
|
+
return [branding.logo_dark_url, branding.logo_light_url, branding.logo_url, branding.mark_url];
|
|
35
|
+
}
|
|
36
|
+
return [branding.logo_light_url, branding.logo_url, branding.logo_dark_url, branding.mark_url];
|
|
37
|
+
}
|
|
38
|
+
export function resolvePlatformLogo(branding, options = {}) {
|
|
39
|
+
const slot = options.slot ?? 'header';
|
|
40
|
+
const scheme = options.scheme ?? 'light';
|
|
41
|
+
const brandingLogo = getLogoCandidates(branding, slot, scheme).map(cleanUrl).find(Boolean) ?? null;
|
|
42
|
+
const fallbackLogo = brandingLogo ? null : getSlotFallback(options, slot);
|
|
43
|
+
const source = brandingLogo ? 'branding' : fallbackLogo ? 'fallback' : 'none';
|
|
44
|
+
const companyName = getCompanyName(branding);
|
|
45
|
+
return {
|
|
46
|
+
src: brandingLogo ?? fallbackLogo,
|
|
47
|
+
alt: options.alt ?? (slot === 'favicon' ? '' : companyName ? `${companyName} logo` : 'Platform logo'),
|
|
48
|
+
slot,
|
|
49
|
+
scheme,
|
|
50
|
+
source,
|
|
51
|
+
isTenantLogo: source === 'branding',
|
|
52
|
+
cssVariable: getCssVariable(slot, scheme),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
export function usePlatformLogo(options = {}) {
|
|
56
|
+
const { branding, isLoading, error, refresh } = useBranding();
|
|
57
|
+
const resolved = useMemo(() => resolvePlatformLogo(branding, options), [branding, options.alt, options.fallback, options.fallbackBySlot, options.scheme, options.slot]);
|
|
58
|
+
return {
|
|
59
|
+
...resolved,
|
|
60
|
+
branding,
|
|
61
|
+
isLoading,
|
|
62
|
+
error,
|
|
63
|
+
refresh,
|
|
64
|
+
};
|
|
65
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bsuite/theme",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"publishConfig": {
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"url": "https://github.com/GaryOcean428/bsuite.git",
|
|
12
12
|
"directory": "packages/theme"
|
|
13
13
|
},
|
|
14
|
-
"description": "BSuite Universal theme
|
|
14
|
+
"description": "BSuite Universal theme — D2C Neon Electric + Braden Corporate baselines. Tailwind v4 preset, CSS vars (5-layer), role aliases, colourblind-safe tokens, eye-strain-safe text scale, BrandingProvider (runtime white-label). Used by BSU, CRM7, conduit, R80.3, throughput (D2C) and braden.com.au (Corporate).",
|
|
15
15
|
"files": [
|
|
16
16
|
"dist",
|
|
17
17
|
"src/css",
|
package/src/preset-v4.css
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* @bsuite/theme — Tailwind v4 @theme block
|
|
3
|
-
* Version 0.3.
|
|
3
|
+
* Version 0.3.1
|
|
4
4
|
*
|
|
5
5
|
* Tailwind v4 exposes design tokens to the engine via CSS @theme blocks.
|
|
6
6
|
* Import this before any app-specific @theme overrides:
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* @import '@bsuite/theme/preset-v4.css';
|
|
10
10
|
*
|
|
11
11
|
* @theme {
|
|
12
|
-
* --color-app-primary: oklch(0.45 0.18 142);
|
|
12
|
+
* --color-app-primary: oklch(0.45 0.18 142); -- app-specific override
|
|
13
13
|
* }
|
|
14
14
|
*
|
|
15
15
|
* Token naming: `--color-{name}` → Tailwind generates `bg-{name}`,
|