Add admin site settings sections and locale defaults
This commit is contained in:
@@ -0,0 +1,134 @@
|
||||
import {
|
||||
DEFAULT_SITE_PRIMARY_COLOR,
|
||||
normalizeSitePrimaryColor,
|
||||
} from "./site-settings";
|
||||
|
||||
type RgbColor = {
|
||||
r: number;
|
||||
g: number;
|
||||
b: number;
|
||||
};
|
||||
|
||||
type HslColor = {
|
||||
h: number;
|
||||
s: number;
|
||||
l: number;
|
||||
};
|
||||
|
||||
function clamp(value: number, min: number, max: number) {
|
||||
return Math.min(Math.max(value, min), max);
|
||||
}
|
||||
|
||||
function hexToRgb(hex: string): RgbColor {
|
||||
const normalized = normalizeSitePrimaryColor(hex).slice(1);
|
||||
|
||||
return {
|
||||
r: Number.parseInt(normalized.slice(0, 2), 16),
|
||||
g: Number.parseInt(normalized.slice(2, 4), 16),
|
||||
b: Number.parseInt(normalized.slice(4, 6), 16),
|
||||
};
|
||||
}
|
||||
|
||||
function rgbToHsl({ r, g, b }: RgbColor): HslColor {
|
||||
const red = r / 255;
|
||||
const green = g / 255;
|
||||
const blue = b / 255;
|
||||
const max = Math.max(red, green, blue);
|
||||
const min = Math.min(red, green, blue);
|
||||
const delta = max - min;
|
||||
const lightness = (max + min) / 2;
|
||||
|
||||
if (delta === 0) {
|
||||
return {
|
||||
h: 0,
|
||||
s: 0,
|
||||
l: Math.round(lightness * 100),
|
||||
};
|
||||
}
|
||||
|
||||
const saturation =
|
||||
lightness > 0.5
|
||||
? delta / (2 - max - min)
|
||||
: delta / (max + min);
|
||||
|
||||
let hue = 0;
|
||||
|
||||
switch (max) {
|
||||
case red:
|
||||
hue = (green - blue) / delta + (green < blue ? 6 : 0);
|
||||
break;
|
||||
case green:
|
||||
hue = (blue - red) / delta + 2;
|
||||
break;
|
||||
default:
|
||||
hue = (red - green) / delta + 4;
|
||||
break;
|
||||
}
|
||||
|
||||
return {
|
||||
h: Math.round(hue * 60),
|
||||
s: Math.round(saturation * 100),
|
||||
l: Math.round(lightness * 100),
|
||||
};
|
||||
}
|
||||
|
||||
function toChannels(color: HslColor): string {
|
||||
return `${Math.round(color.h)} ${Math.round(color.s)}% ${Math.round(color.l)}%`;
|
||||
}
|
||||
|
||||
function deriveSecondaryColor(color: HslColor, dark = false): HslColor {
|
||||
return {
|
||||
h: (color.h + 10) % 360,
|
||||
s: clamp(color.s - (dark ? 2 : 8), 35, 95),
|
||||
l: clamp(color.l + (dark ? 10 : 8), 20, 80),
|
||||
};
|
||||
}
|
||||
|
||||
function deriveDarkPrimary(color: HslColor): HslColor {
|
||||
return {
|
||||
h: color.h,
|
||||
s: clamp(color.s, 40, 95),
|
||||
l: clamp(color.l + 6, 30, 78),
|
||||
};
|
||||
}
|
||||
|
||||
export function buildSiteThemeTokens(primaryColor: string) {
|
||||
const safePrimary = normalizeSitePrimaryColor(primaryColor || DEFAULT_SITE_PRIMARY_COLOR);
|
||||
const basePrimary = rgbToHsl(hexToRgb(safePrimary));
|
||||
const darkPrimary = deriveDarkPrimary(basePrimary);
|
||||
|
||||
return {
|
||||
light: {
|
||||
primary: toChannels(basePrimary),
|
||||
secondary: toChannels(deriveSecondaryColor(basePrimary)),
|
||||
},
|
||||
dark: {
|
||||
primary: toChannels(darkPrimary),
|
||||
secondary: toChannels(deriveSecondaryColor(darkPrimary, true)),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function buildSiteThemeStyleText(primaryColor: string) {
|
||||
const tokens = buildSiteThemeTokens(primaryColor);
|
||||
|
||||
return `
|
||||
:root {
|
||||
--primary: ${tokens.light.primary};
|
||||
--ring: ${tokens.light.primary};
|
||||
--brand-primary: ${tokens.light.primary};
|
||||
--brand-secondary: ${tokens.light.secondary};
|
||||
--sidebar-primary: ${tokens.light.primary};
|
||||
--sidebar-ring: ${tokens.light.primary};
|
||||
}
|
||||
|
||||
.dark {
|
||||
--primary: ${tokens.dark.primary};
|
||||
--ring: ${tokens.dark.primary};
|
||||
--brand-primary: ${tokens.dark.primary};
|
||||
--brand-secondary: ${tokens.dark.secondary};
|
||||
--sidebar-primary: ${tokens.dark.primary};
|
||||
--sidebar-ring: ${tokens.dark.primary};
|
||||
}
|
||||
`.trim();
|
||||
}
|
||||
Reference in New Issue
Block a user