import { Button, Card, CardContent, CardDescription, CardHeader, CardTitle, Input, Label, Separator, cn, } from "@databricks/appkit-ui/react"; import { brand, object } from "@dbx-tools/shared-core"; import { useBrand } from "@dbx-tools/ui-branding/react"; import { useEffect, useId, useMemo, useState } from "react"; const COLOR_FIELDS = [ ["primary", "Primary"], ["primaryHover", "Primary hover"], ["accent", "Accent"], ["foreground", "Foreground"], ["background", "Background"], ["surface", "Surface"], ["muted", "Muted"], ["border", "Border"], ] as const; type ColorField = (typeof COLOR_FIELDS)[number][0]; /** One complete brand option shown above the editable fields. */ export interface BrandPreset { id: string; label: string; description?: string; context: brand.BrandContextInput; } /** Props for {@link BrandPicker}. */ export interface BrandPickerProps { /** Current brand. Defaults to the nearest `BrandProvider` context. */ value?: brand.BrandContextInput; /** Receives each valid edited or selected brand context. */ onChange: (value: brand.BrandContext) => void; /** Optional complete brand choices rendered as quick-select buttons. */ presets?: readonly BrandPreset[]; /** Value restored by Reset. Defaults to the dbx-tools brand. */ resetValue?: brand.BrandContextInput; /** Show editable icon, logo, and favicon references. */ showAssets?: boolean; className?: string; } const validColor = (value: string, fallback: string): string => /^#(?:[\da-f]{3}|[\da-f]{6}|[\da-f]{8})$/i.test(value) ? value : fallback; /** * Edit a portable `BrandContext` with AppKit-native controls. * * The picker keeps incomplete text locally while emitting only schema-valid * contexts, so controlled hosts can apply changes directly to a * `BrandProvider` without guarding every keystroke. */ export function BrandPicker({ value, onChange, presets = [], resetValue = brand.defaultBrandContext, showAssets = true, className, }: BrandPickerProps) { const { context: inherited } = useBrand(); const current = useMemo(() => brand.parseBrandContext(value ?? inherited), [inherited, value]); const reset = useMemo(() => brand.parseBrandContext(resetValue), [resetValue]); const resolvedPresets = useMemo( () => presets.map((preset) => ({ ...preset, context: brand.parseBrandContext(preset.context) })), [presets], ); const [draft, setDraft] = useState(current); const id = useId(); useEffect(() => setDraft(current), [current]); const update = (next: brand.BrandContext) => { setDraft(next); const parsed = brand.BrandContextSchema.safeParse(next); if (parsed.success) onChange(parsed.data); }; const updateIdentity = (field: "name" | "shortName" | "tagline", next: string) => update({ ...draft, [field]: next }); const updateColor = (field: ColorField, next: string) => update({ ...draft, colors: { ...draft.colors, [field]: next } }); const updateAsset = (group: "icon" | "logo", mode: "light" | "dark", next: string) => update({ ...draft, assets: { ...draft.assets, [group]: { ...draft.assets[group], [mode]: next || undefined }, }, }); return (
Brand picker Update identity, AppKit color tokens, document metadata, and email presentation.
{resolvedPresets.length ? (
{resolvedPresets.map((preset) => { const active = object.deepEqual(current, preset.context); return ( ); })}
) : null}

Identity

Used by navigation, metadata, and email.

updateIdentity("name", event.currentTarget.value)} />
updateIdentity("shortName", event.currentTarget.value)} />
updateIdentity("tagline", event.currentTarget.value)} />

Color system

Valid hex values are emitted immediately to the active brand provider.

{COLOR_FIELDS.map(([field, label]) => (
updateColor(field, event.currentTarget.value)} /> updateColor(field, event.currentTarget.value)} />
))}
{showAssets ? ( <>

Assets

Package paths, data URLs, and hosted URLs are supported.

{(["icon", "logo"] as const).flatMap((group) => (["light", "dark"] as const).map((mode) => (
updateAsset(group, mode, event.currentTarget.value)} />
)), )}
update({ ...draft, assets: { ...draft.assets, favicon: event.currentTarget.value }, }) } />
) : null}
); }