import * as React from "react"; import { cn } from "@/lib/utils"; import { getContrastText } from "@/lib/colors"; import { RadioGroup, RadioGroupCard } from "@/components/ui/radio-group"; import { ColorPicker, isValidHex, normalizeHex, } from "@/components/ui/color-picker"; import { Field, FieldLabel, FieldDescription } from "@/components/ui/field"; /** * BrandColorField — WealthX DS (Molecule) * * Lets an admin either inherit the tenant theme color or pick a specific one, * and shows the resolved color together with the text color that will be * computed against it — so the readability outcome is visible before saving. * * Custom mode uses the same popover `ColorPicker` as the onboarding branding * step and company settings, so brand-color entry looks identical everywhere. * * Pure display component. Persistence is owned by the app layer. */ export type BrandColorMode = "theme" | "custom"; /** * The color that a given mode resolves to. `"theme"` always wins back to the * tenant color, so switching to Custom and back never strands a stale value. */ export function resolveBrandColor( mode: BrandColorMode, themeColor: string, customColor: string, ): string { return mode === "custom" ? customColor : themeColor; } export interface BrandColorFieldProps { mode: BrandColorMode; onModeChange: (mode: BrandColorMode) => void; /** Tenant brand color from company settings — the value `"theme"` mode resolves to. */ themeColor: string; customColor: string; onCustomColorChange: (color: string) => void; disabled?: boolean; label?: string; description?: string; className?: string; } export function BrandColorField({ mode, onModeChange, themeColor, customColor, onCustomColorChange, disabled, label = "Widget color", description = "Choose how the chat widget picks up your brand color.", className, }: BrandColorFieldProps) { const resolved = resolveBrandColor(mode, themeColor, customColor); return (
{label} {description}
onModeChange(value as BrandColorMode)} disabled={disabled} className="sm:grid-cols-2" > {mode === "custom" && ( Brand color )}
); } // --------------------------------------------------------------------------- // BrandColorPreviewChip // --------------------------------------------------------------------------- export interface BrandColorPreviewChipProps { color: string; mode?: BrandColorMode; className?: string; } /** * Shows the resolved brand color filled behind sample text tinted with the * contrast color the widget will actually compute — the readability preview. */ export function BrandColorPreviewChip({ color, mode, className, }: BrandColorPreviewChipProps) { const normalized = normalizeHex(color); const valid = isValidHex(normalized); const foreground = valid ? getContrastText(normalized) : undefined; return (
Aa
{valid ? normalized : "Invalid color"} {!valid ? "Enter a valid hex color" : mode === "theme" ? "Inherited from Company Settings" : `Text will render ${foreground === "#FFFFFF" ? "light" : "dark"} for contrast`}
); }