Wire guest branding theme

This commit is contained in:
Codex Agent
2026-01-15 08:06:21 +01:00
parent 53096fbf29
commit a1f37bb491
14 changed files with 478 additions and 123 deletions

View File

@@ -0,0 +1,56 @@
import { describe, expect, it } from 'vitest';
import { extractBrandingForm } from '../brandingForm';
const defaults = {
primary: '#111111',
accent: '#222222',
background: '#ffffff',
surface: '#f0f0f0',
mode: 'auto' as const,
};
describe('extractBrandingForm', () => {
it('prefers palette values when available', () => {
const settings = {
branding: {
palette: {
primary: '#aa0000',
secondary: '#00aa00',
background: '#000000',
surface: '#111111',
},
primary_color: '#bbbbbb',
secondary_color: '#cccccc',
background_color: '#dddddd',
surface_color: '#eeeeee',
mode: 'dark',
},
};
const result = extractBrandingForm(settings, defaults);
expect(result.primary).toBe('#aa0000');
expect(result.accent).toBe('#00aa00');
expect(result.background).toBe('#000000');
expect(result.surface).toBe('#111111');
expect(result.mode).toBe('dark');
});
it('falls back to legacy keys and defaults', () => {
const settings = {
branding: {
accent_color: '#123456',
background_color: '#abcdef',
mode: 'light',
},
};
const result = extractBrandingForm(settings, defaults);
expect(result.primary).toBe(defaults.primary);
expect(result.accent).toBe('#123456');
expect(result.background).toBe('#abcdef');
expect(result.surface).toBe('#abcdef');
expect(result.mode).toBe('light');
});
});

View File

@@ -0,0 +1,56 @@
export type BrandingFormValues = {
primary: string;
accent: string;
background: string;
surface: string;
headingFont: string;
bodyFont: string;
logoDataUrl: string;
mode: 'light' | 'dark' | 'auto';
};
export type BrandingFormDefaults = Pick<BrandingFormValues, 'primary' | 'accent' | 'background' | 'surface' | 'mode'>;
type BrandingRecord = Record<string, unknown>;
const isRecord = (value: unknown): value is BrandingRecord =>
Boolean(value) && typeof value === 'object' && !Array.isArray(value);
const readHexColor = (value: unknown, fallback: string): string => {
if (typeof value === 'string' && value.trim().startsWith('#')) {
return value.trim();
}
return fallback;
};
export function extractBrandingForm(settings: unknown, defaults: BrandingFormDefaults): BrandingFormValues {
const settingsRecord = isRecord(settings) ? settings : {};
const branding = isRecord(settingsRecord.branding) ? (settingsRecord.branding as BrandingRecord) : settingsRecord;
const palette = isRecord(branding.palette) ? (branding.palette as BrandingRecord) : {};
const typography = isRecord(branding.typography) ? (branding.typography as BrandingRecord) : {};
const primary = readHexColor(palette.primary, readHexColor(branding.primary_color, defaults.primary));
const accent = readHexColor(
palette.secondary,
readHexColor(branding.secondary_color, readHexColor(branding.accent_color, defaults.accent))
);
const background = readHexColor(palette.background, readHexColor(branding.background_color, defaults.background));
const surface = readHexColor(palette.surface, readHexColor(branding.surface_color, background));
const headingFont = typeof typography.heading === 'string' ? typography.heading : (branding.heading_font as string | undefined);
const bodyFont = typeof typography.body === 'string' ? typography.body : (branding.body_font as string | undefined);
const mode = branding.mode === 'light' || branding.mode === 'dark' || branding.mode === 'auto'
? branding.mode
: defaults.mode;
return {
primary,
accent,
background,
surface,
headingFont: headingFont ?? '',
bodyFont: bodyFont ?? '',
logoDataUrl: typeof branding.logo_data_url === 'string' ? branding.logo_data_url : '',
mode,
};
}