120 lines
3.4 KiB
TypeScript
120 lines
3.4 KiB
TypeScript
import { ThemeConfig, ThemeColors } from "./ThemeConfig";
|
|
|
|
// Maps CSS variable names to our ThemeConfig keys
|
|
const cssVarMap: Record<string, keyof ThemeColors> = {
|
|
"--background": "background",
|
|
"--foreground": "foreground",
|
|
"--card": "card",
|
|
"--card-foreground": "cardForeground",
|
|
"--popover": "popover",
|
|
"--popover-foreground": "popoverForeground",
|
|
"--primary": "primary",
|
|
"--primary-foreground": "primaryForeground",
|
|
"--secondary": "secondary",
|
|
"--secondary-foreground": "secondaryForeground",
|
|
"--muted": "muted",
|
|
"--muted-foreground": "mutedForeground",
|
|
"--accent": "accent",
|
|
"--accent-foreground": "accentForeground",
|
|
"--destructive": "destructive",
|
|
"--destructive-foreground": "destructiveForeground",
|
|
"--border": "border",
|
|
"--input": "input",
|
|
"--ring": "ring",
|
|
"--sidebar": "sidebar",
|
|
"--sidebar-background": "sidebarBackground",
|
|
"--sidebar-foreground": "sidebarForeground",
|
|
"--sidebar-primary": "sidebarPrimary",
|
|
"--sidebar-primary-foreground": "sidebarPrimaryForeground",
|
|
"--sidebar-accent": "sidebarAccent",
|
|
"--sidebar-accent-foreground": "sidebarAccentForeground",
|
|
"--sidebar-border": "sidebarBorder",
|
|
"--sidebar-ring": "sidebarRing",
|
|
};
|
|
|
|
export function parseCssConfig(cssText: string): Partial<ThemeConfig> {
|
|
const config: Partial<ThemeConfig> = {
|
|
colors: {},
|
|
darkColors: {},
|
|
};
|
|
|
|
// Parse :root section for light mode colors
|
|
const rootMatch = cssText.match(/:root\s*{([^}]*)}/);
|
|
if (rootMatch) {
|
|
const rootContent = rootMatch[1];
|
|
config.colors = parseColorSection(rootContent);
|
|
|
|
// Parse radius
|
|
const radiusMatch = rootContent.match(/--radius:\s*([^;]+);/);
|
|
if (radiusMatch) {
|
|
const radiusValue = radiusMatch[1].trim();
|
|
// Convert px to rem (assuming 16px = 1rem)
|
|
if (radiusValue.endsWith("px")) {
|
|
const px = parseFloat(radiusValue);
|
|
config.radius = `${px / 16}rem`;
|
|
} else if (radiusValue.endsWith("rem")) {
|
|
config.radius = radiusValue;
|
|
} else {
|
|
config.radius = radiusValue;
|
|
}
|
|
}
|
|
|
|
// Parse font-sans (use as default font family)
|
|
const fontMatch = rootContent.match(/--font-sans:\s*([^;]+);/);
|
|
if (fontMatch) {
|
|
config.fontFamily = fontMatch[1].trim();
|
|
}
|
|
}
|
|
|
|
// Parse .dark section for dark mode colors
|
|
const darkMatch = cssText.match(/\.dark\s*{([^}]*)}/);
|
|
if (darkMatch) {
|
|
const darkContent = darkMatch[1];
|
|
config.darkColors = parseColorSection(darkContent);
|
|
}
|
|
|
|
return config;
|
|
}
|
|
|
|
function parseColorSection(content: string): ThemeColors {
|
|
const colors: ThemeColors = {};
|
|
|
|
// Extract all CSS variables
|
|
const varRegex = /(--[a-zA-Z-]+):\s*([^;]+);/g;
|
|
let match;
|
|
|
|
while ((match = varRegex.exec(content)) !== null) {
|
|
const cssVar = match[1];
|
|
const value = match[2].trim();
|
|
|
|
// Check if this CSS variable maps to one of our theme colors
|
|
const configKey = cssVarMap[cssVar];
|
|
if (configKey) {
|
|
colors[configKey] = value;
|
|
}
|
|
}
|
|
|
|
return colors;
|
|
}
|
|
|
|
export function formatCssValue(value: string): string {
|
|
// If it's already a valid CSS color format, return as is
|
|
if (
|
|
value.startsWith("hsl(") ||
|
|
value.startsWith("hsla(") ||
|
|
value.startsWith("oklch(") ||
|
|
value.startsWith("rgb(") ||
|
|
value.startsWith("rgba(") ||
|
|
value.startsWith("#")
|
|
) {
|
|
return value;
|
|
}
|
|
|
|
// Otherwise, assume it's a hex color without the #
|
|
if (/^[0-9A-Fa-f]{6}$/.test(value)) {
|
|
return `#${value}`;
|
|
}
|
|
|
|
return value;
|
|
}
|