improved a lot of things
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
// ── White-label Theming ───────────────────────────────────────────────────────
|
||||
// Enterprise workspaces can override the Originmain UI with their own brand.
|
||||
// The theming pipeline:
|
||||
// 1. WorkspaceBrand (stored in workspace.metadata_jsonb) → BrandTokens
|
||||
// 2. BrandTokens → Fluent 2 BrandVariants (via createLightTheme / createDarkTheme)
|
||||
// 3. BrandVariants → FluentProvider theme prop
|
||||
//
|
||||
// Note: Actual Fluent 2 createLightTheme / createDarkTheme calls happen in
|
||||
// packages/app/src/lib/workspace-theme.ts to avoid a @fluentui dep here.
|
||||
|
||||
export const BrandTokensSchema = z.object({
|
||||
/** Primary brand colour in 6-digit hex */
|
||||
primaryColor: z.string().regex(/^#[0-9a-f]{6}$/i),
|
||||
/** Optional secondary accent */
|
||||
secondaryColor: z.string().regex(/^#[0-9a-f]{6}$/i).optional(),
|
||||
/** Brand logo URL (SVG or PNG, ≤512KB) */
|
||||
logoUrl: z.string().url().optional(),
|
||||
/** Tab title prefix: e.g. "Acme Design" → "Acme Design — Originmain" */
|
||||
productName: z.string().max(40).optional(),
|
||||
/** Favicon URL (ICO, PNG, or SVG) */
|
||||
faviconUrl: z.string().url().optional(),
|
||||
});
|
||||
|
||||
export type BrandTokens = z.infer<typeof BrandTokensSchema>;
|
||||
|
||||
/**
|
||||
* Derives the 10-shade Fluent 2 BrandVariants record from a single hex colour.
|
||||
* Shades are generated by interpolating HSL lightness across the standard scale.
|
||||
* The returned object is passed directly to createLightTheme / createDarkTheme.
|
||||
*/
|
||||
export function brandVariantsFromHex(hex: string): Record<`shade${10 | 20 | 30 | 40 | 50 | 60 | 70 | 80 | 90 | 100 | 110 | 120 | 130 | 140 | 150 | 160}`, string> {
|
||||
const [r, g, b] = hexToRgb(hex);
|
||||
const [h, s] = rgbToHsl(r, g, b);
|
||||
|
||||
// Fluent 2 uses shades 10–160 in 10-step increments (16 shades).
|
||||
// shade10 = lightest, shade160 = darkest.
|
||||
const shadeNumbers = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160] as const;
|
||||
const result = {} as Record<string, string>;
|
||||
|
||||
for (const shade of shadeNumbers) {
|
||||
// Map shade index to lightness: shade10 ≈ 95%, shade160 ≈ 10%
|
||||
const lightness = 95 - ((shade - 10) / 150) * 85;
|
||||
result[`shade${shade}`] = hslToHex(h, s, lightness);
|
||||
}
|
||||
|
||||
return result as ReturnType<typeof brandVariantsFromHex>;
|
||||
}
|
||||
|
||||
// ── HSL ↔ RGB ↔ Hex helpers ──────────────────────────────────────────────────
|
||||
|
||||
function hexToRgb(hex: string): [number, number, number] {
|
||||
const clean = hex.replace('#', '');
|
||||
const r = parseInt(clean.slice(0, 2), 16);
|
||||
const g = parseInt(clean.slice(2, 4), 16);
|
||||
const b = parseInt(clean.slice(4, 6), 16);
|
||||
if (Number.isNaN(r) || Number.isNaN(g) || Number.isNaN(b)) {
|
||||
throw new Error(`Invalid hex color: "${hex}"`);
|
||||
}
|
||||
return [r, g, b];
|
||||
}
|
||||
|
||||
function rgbToHsl(r: number, g: number, b: number): [number, number, number] {
|
||||
const rn = r / 255, gn = g / 255, bn = b / 255;
|
||||
const max = Math.max(rn, gn, bn), min = Math.min(rn, gn, bn);
|
||||
const l = (max + min) / 2;
|
||||
if (max === min) return [0, 0, l * 100];
|
||||
|
||||
const d = max - min;
|
||||
const s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
||||
let h = 0;
|
||||
if (max === rn) h = ((gn - bn) / d + (gn < bn ? 6 : 0)) / 6;
|
||||
else if (max === gn) h = ((bn - rn) / d + 2) / 6;
|
||||
else h = ((rn - gn) / d + 4) / 6;
|
||||
|
||||
return [h * 360, s * 100, l * 100];
|
||||
}
|
||||
|
||||
function hslToHex(h: number, s: number, l: number): string {
|
||||
const sn = s / 100, ln = l / 100;
|
||||
const a = sn * Math.min(ln, 1 - ln);
|
||||
const f = (n: number): string => {
|
||||
const k = (n + h / 30) % 12;
|
||||
const color = ln - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);
|
||||
return Math.round(255 * color).toString(16).padStart(2, '0');
|
||||
};
|
||||
return `#${f(0)}${f(8)}${f(4)}`;
|
||||
}
|
||||
Reference in New Issue
Block a user