3.0 KiB
<BrandingProvider> and tenant branding hooks
Purpose
branding-context.tsx ships the <BrandingProvider> React context provider
together with the hooks useBranding, useTenantBranding, and useLogo.
The provider's job is to resolve a TenantBranding payload (either static
via prop or fetched from a configurable endpoint) and to project that
payload onto two surfaces: (1) the @suaveplan/react-ui runtime
theme-token layer for every --brand-* CSS custom property, and (2) the
non-CSS DOM chrome — document.title, <link rel="icon">, and the tenant
custom-CSS <style> injection. The CSS path goes through
useThemeMutator().setExtensionTokens(...) so per-tenant tokens stack on
top of the active chassis theme exactly like any other mutator layer, with
automatic cleanup on unmount.
Features
- Static branding via prop OR endpoint fetch via
brandingEndpoint(+ optional/tenantIdsegment). - Injectable
fetch: FetchLikefor testability. - Zod validation (
TenantBrandingSchema.safeParse) on every fetched payload — Zod failures surface asBRANDING_INVALID_PAYLOAD. - Network failures (transport throw or non-ok response) surface as
BRANDING_FETCH_FAILED. --brand-primary,--brand-secondary,--brand-accent,--brand-fontwritten viasetExtensionTokens— cleanup is automatic on unmount.document.title, favicon<link>, and tenant<style>injection are direct DOM writes (not CSS variables; no conflict with the mutator).
Usage: Basic
import { ThemeProvider, lightTheme } from "@suaveplan/react-ui/theme";
import { BrandingProvider } from "@suaveplan/react-auth-whitelabel";
<ThemeProvider themes={[lightTheme]} defaultTheme="light">
<BrandingProvider staticBranding={tenantBranding}>
<App />
</BrandingProvider>
</ThemeProvider>
Usage: Advanced
<BrandingProvider
brandingEndpoint="/api/branding"
tenantId={tenantSlug}
fetch={customFetch}
>
<App />
</BrandingProvider>
const branding = useTenantBranding();
const lightLogo = useLogo(false);
const darkLogo = useLogo(true);
API Reference
BrandingProvider(props)— props includebrandingEndpoint?,tenantId?,staticBranding?,fetch?,children.useBranding()— returns{ branding, isLoading, error, refresh }.useTenantBranding()— returns the currentTenantBranding.useLogo(preferDark?)— returns the most-appropriate logo URL orundefined.
Implementation Notes
The mutator effect runs on every branding change AND on every
mutator identity change. The mutator handle is stable across re-renders
(it's useMemo-wrapped inside @suaveplan/react-ui), so the effect only
re-fires when the branding payload actually changes. The cleanup
contract for --brand-* is delegated to useThemeMutator — when the
provider unmounts, the runtime invokes the mutator's clear() automatically,
removing every --brand-* declaration from document.documentElement.style.