mirror of
https://github.com/jnsahaj/tweakcn.git
synced 2026-09-21 04:46:32 +08:00
replace contents with tweakcn-next
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
export function applyStyleToElement(
|
||||
element: HTMLElement,
|
||||
key: string,
|
||||
value: string
|
||||
) {
|
||||
element.setAttribute(
|
||||
`style`,
|
||||
`${element.getAttribute("style") || ""}--${key}: ${value};`
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import * as culori from "culori";
|
||||
import { ColorFormat } from "../types";
|
||||
|
||||
const formatNumber = (num?: number) => {
|
||||
if (!num) return "0";
|
||||
return num % 1 === 0 ? num : num.toFixed(2);
|
||||
};
|
||||
|
||||
export const colorFormatter = (
|
||||
colorValue: string,
|
||||
format: ColorFormat = "hsl",
|
||||
tailwindVersion: "3" | "4" = "3"
|
||||
): string => {
|
||||
try {
|
||||
const color = culori.parse(colorValue);
|
||||
if (!color) throw new Error("Invalid color input");
|
||||
|
||||
switch (format) {
|
||||
case "hsl": {
|
||||
const hsl = culori.converter("hsl")(color);
|
||||
if (tailwindVersion === "4") {
|
||||
return `hsl(${formatNumber(hsl.h)} ${formatNumber(hsl.s * 100)}% ${formatNumber(hsl.l * 100)}%)`;
|
||||
}
|
||||
return `${formatNumber(hsl.h)} ${formatNumber(hsl.s * 100)}% ${formatNumber(hsl.l * 100)}%`;
|
||||
}
|
||||
case "rgb":
|
||||
return culori.formatRgb(color); // e.g., "rgb(64, 128, 192)"
|
||||
case "oklch": {
|
||||
const oklch = culori.converter("oklch")(color);
|
||||
return `oklch(${formatNumber(oklch.l)} ${formatNumber(oklch.c)} ${formatNumber(oklch.h)})`;
|
||||
}
|
||||
case "hex":
|
||||
return culori.formatHex(color); // e.g., "#4080c0"
|
||||
default:
|
||||
return colorValue;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Failed to convert color: ${colorValue}`, error);
|
||||
return colorValue;
|
||||
}
|
||||
};
|
||||
|
||||
export const convertToHSL = (colorValue: string): string =>
|
||||
colorFormatter(colorValue, "hsl");
|
||||
@@ -0,0 +1,9 @@
|
||||
export function debounce(fn: (...args: any[]) => void, delay: number) {
|
||||
let timeoutId;
|
||||
return function (...args: any[]) {
|
||||
clearTimeout(timeoutId);
|
||||
timeoutId = setTimeout(() => {
|
||||
fn.apply(this, args);
|
||||
}, delay);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
import { ThemeStyleProps } from "@/types/theme";
|
||||
import { colorFormatter } from "./color-converter";
|
||||
|
||||
export const variableNames = [
|
||||
"background",
|
||||
"foreground",
|
||||
"card",
|
||||
"card-foreground",
|
||||
"popover",
|
||||
"popover-foreground",
|
||||
"primary",
|
||||
"primary-foreground",
|
||||
"secondary",
|
||||
"secondary-foreground",
|
||||
"muted",
|
||||
"muted-foreground",
|
||||
"accent",
|
||||
"accent-foreground",
|
||||
"destructive",
|
||||
"destructive-foreground",
|
||||
"border",
|
||||
"input",
|
||||
"ring",
|
||||
"chart-1",
|
||||
"chart-2",
|
||||
"chart-3",
|
||||
"chart-4",
|
||||
"chart-5",
|
||||
"radius",
|
||||
"font-sans",
|
||||
"font-serif",
|
||||
"font-mono",
|
||||
"shadow-color",
|
||||
"shadow-opacity",
|
||||
"shadow-blur",
|
||||
"shadow-spread",
|
||||
"shadow-offset-x",
|
||||
"shadow-offset-y",
|
||||
"shadow",
|
||||
"shadow-2xs",
|
||||
"shadow-xs",
|
||||
"shadow-sm",
|
||||
"shadow-md",
|
||||
"shadow-lg",
|
||||
"shadow-xl",
|
||||
"shadow-2xl",
|
||||
];
|
||||
|
||||
const nonColorVariables = ["font-sans", "font-serif", "font-mono", "radius"];
|
||||
|
||||
const VARIABLE_PREFIX = "--";
|
||||
|
||||
export const parseCssInput = (input: string) => {
|
||||
const lightColors: ThemeStyleProps = {} as ThemeStyleProps;
|
||||
const darkColors: ThemeStyleProps = {} as ThemeStyleProps;
|
||||
|
||||
try {
|
||||
const rootContent = extractCssBlockContent(input, ":root");
|
||||
const darkContent = extractCssBlockContent(input, ".dark");
|
||||
|
||||
if (rootContent) {
|
||||
parseColorVariables(rootContent, lightColors, variableNames);
|
||||
}
|
||||
if (darkContent) {
|
||||
parseColorVariables(darkContent, darkColors, variableNames);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error parsing CSS input:", error);
|
||||
}
|
||||
|
||||
return { lightColors, darkColors };
|
||||
};
|
||||
|
||||
const extractCssBlockContent = (input: string, selector: string): string | null => {
|
||||
const regex = new RegExp(`${escapeRegExp(selector)}\\s*{([^}]+)}`);
|
||||
return input.match(regex)?.[1]?.trim() || null;
|
||||
};
|
||||
|
||||
const parseColorVariables = (
|
||||
cssContent: string,
|
||||
target: ThemeStyleProps,
|
||||
validNames: string[]
|
||||
) => {
|
||||
const variableDeclarations = cssContent.match(/--[^:]+:\s*[^;]+/g) || [];
|
||||
|
||||
variableDeclarations.forEach((declaration) => {
|
||||
const [name, value] = declaration.split(":").map((part) => part.trim());
|
||||
const cleanName = name.replace(VARIABLE_PREFIX, "");
|
||||
|
||||
if (validNames.includes(cleanName)) {
|
||||
if (nonColorVariables.includes(cleanName)) {
|
||||
target[cleanName] = value;
|
||||
return;
|
||||
}
|
||||
|
||||
const colorValue = processColorValue(value);
|
||||
const formattedValue = colorFormatter(colorValue, "hex");
|
||||
target[cleanName] = formattedValue;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const processColorValue = (value: string): string => {
|
||||
return /^\d/.test(value) ? `hsl(${value})` : value;
|
||||
};
|
||||
|
||||
// Helper function to escape regex special characters
|
||||
const escapeRegExp = (string: string): string => {
|
||||
return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||
};
|
||||
@@ -0,0 +1,80 @@
|
||||
import { colorFormatter } from "./color-converter";
|
||||
import { applyStyleToElement } from "./apply-style-to-element";
|
||||
import { ThemeEditorState } from "../types/theme";
|
||||
import { defaultThemeState } from "../config/theme";
|
||||
|
||||
export const getShadowMap = (themeEditorState: ThemeEditorState) => {
|
||||
const mode = themeEditorState.currentMode;
|
||||
const styles = {
|
||||
...defaultThemeState.styles.light,
|
||||
...defaultThemeState.styles[mode],
|
||||
...themeEditorState.styles[mode],
|
||||
};
|
||||
|
||||
const shadowColor = styles["shadow-color"];
|
||||
const hsl = colorFormatter(shadowColor, "hsl", "3");
|
||||
const offsetX = styles["shadow-offset-x"];
|
||||
const offsetY = styles["shadow-offset-y"];
|
||||
const blur = styles["shadow-blur"];
|
||||
const spread = styles["shadow-spread"];
|
||||
const opacity = parseFloat(styles["shadow-opacity"]);
|
||||
const color = (opacityMultiplier: number) =>
|
||||
`hsl(${hsl} / ${(opacity * opacityMultiplier).toFixed(2)})`;
|
||||
|
||||
const secondLayer = (fixedOffsetY: string, fixedBlur: string): string => {
|
||||
// Use the same offsetX as the first layer
|
||||
const offsetX2 = offsetX;
|
||||
// Use the fixed offsetY specific to the shadow size
|
||||
const offsetY2 = fixedOffsetY;
|
||||
// Use the fixed blur specific to the shadow size
|
||||
const blur2 = fixedBlur;
|
||||
// Calculate spread relative to the first layer's spread variable
|
||||
const spread2 =
|
||||
(parseFloat(spread?.replace("px", "") ?? "0") - 1).toString() + "px";
|
||||
// Use the same color function (opacity can still be overridden by --shadow-opacity)
|
||||
const color2 = color(1.0); // Default opacity for second layer is 0.1 in examples
|
||||
|
||||
return `${offsetX2} ${offsetY2} ${blur2} ${spread2} ${color2}`;
|
||||
};
|
||||
|
||||
// Map shadow names to their CSS variable string structures
|
||||
const shadowMap: { [key: string]: string } = {
|
||||
// Single layer shadows - use base variables directly
|
||||
"shadow-2xs": `${offsetX} ${offsetY} ${blur} ${spread} ${color(0.5)}`, // Assumes vars set appropriately (e.g., y=1, blur=0, spread=0)
|
||||
"shadow-xs": `${offsetX} ${offsetY} ${blur} ${spread} ${color(0.5)}`, // Assumes vars set appropriately (e.g., y=1, blur=2, spread=0)
|
||||
"shadow-2xl": `${offsetX} ${offsetY} ${blur} ${spread} ${color(2.5)}`, // Assumes vars set appropriately (e.g., y=25, blur=50, spread=-12)
|
||||
|
||||
// Two layer shadows - use base vars for layer 1, mix fixed/calculated for layer 2
|
||||
"shadow-sm": `${offsetX} ${offsetY} ${blur} ${spread} ${color(
|
||||
1.0
|
||||
)}, ${secondLayer("1px", "2px")}`,
|
||||
shadow: `${offsetX} ${offsetY} ${blur} ${spread} ${color(1.0)}, ${secondLayer(
|
||||
"1px",
|
||||
"2px"
|
||||
)}`, // Alias for the 'shadow:' example line
|
||||
|
||||
"shadow-md": `${offsetX} ${offsetY} ${blur} ${spread} ${color(
|
||||
1.0
|
||||
)}, ${secondLayer("2px", "4px")}`,
|
||||
|
||||
"shadow-lg": `${offsetX} ${offsetY} ${blur} ${spread} ${color(
|
||||
1.0
|
||||
)}, ${secondLayer("4px", "6px")}`,
|
||||
|
||||
"shadow-xl": `${offsetX} ${offsetY} ${blur} ${spread} ${color(
|
||||
1.0
|
||||
)}, ${secondLayer("8px", "10px")}`,
|
||||
};
|
||||
|
||||
return shadowMap;
|
||||
};
|
||||
|
||||
// Function to set shadow CSS variables
|
||||
export function setShadowVariables(themeEditorState: ThemeEditorState) {
|
||||
const root = document.documentElement;
|
||||
|
||||
const shadows = getShadowMap(themeEditorState);
|
||||
Object.entries(shadows).forEach(([name, value]) => {
|
||||
applyStyleToElement(root, name, value);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
import { ThemeEditorState, ThemeStyleProps } from "../types/theme";
|
||||
|
||||
const sansSerifFontNames = [
|
||||
"Inter",
|
||||
"Roboto",
|
||||
"Open Sans",
|
||||
"Poppins",
|
||||
"Montserrat",
|
||||
"Outfit",
|
||||
"Plus Jakarta Sans",
|
||||
"DM Sans",
|
||||
"Geist",
|
||||
"Oxanium",
|
||||
];
|
||||
|
||||
const serifFontNames = [
|
||||
"Merriweather",
|
||||
"Playfair Display",
|
||||
"Lora",
|
||||
"Source Serif Pro",
|
||||
"Libre Baskerville",
|
||||
"Space Grotesk",
|
||||
];
|
||||
|
||||
const monoFontNames = [
|
||||
"JetBrains Mono",
|
||||
"Fira Code",
|
||||
"Source Code Pro",
|
||||
"IBM Plex Mono",
|
||||
"Roboto Mono",
|
||||
"Space Mono",
|
||||
"Geist Mono",
|
||||
];
|
||||
|
||||
export const fonts = {
|
||||
// Sans-serif fonts
|
||||
Inter: "Inter, sans-serif",
|
||||
Roboto: "Roboto, sans-serif",
|
||||
"Open Sans": "Open Sans, sans-serif",
|
||||
Poppins: "Poppins, sans-serif",
|
||||
Montserrat: "Montserrat, sans-serif",
|
||||
Outfit: "Outfit, sans-serif",
|
||||
"Plus Jakarta Sans": "Plus Jakarta Sans, sans-serif",
|
||||
"DM Sans": "DM Sans, sans-serif",
|
||||
"IBM Plex Sans": "IBM Plex Sans, sans-serif",
|
||||
Geist: "Geist, sans-serif",
|
||||
Oxanium: "Oxanium, sans-serif",
|
||||
|
||||
// Serif fonts
|
||||
Merriweather: "Merriweather, serif",
|
||||
"Playfair Display": "Playfair Display, serif",
|
||||
Lora: "Lora, serif",
|
||||
"Source Serif Pro": "Source Serif Pro, serif",
|
||||
"Libre Baskerville": "Libre Baskerville, serif",
|
||||
"Space Grotesk": "Space Grotesk, serif",
|
||||
|
||||
// Monospace fonts
|
||||
"JetBrains Mono": "JetBrains Mono, monospace",
|
||||
"Fira Code": "Fira Code, monospace",
|
||||
"Source Code Pro": "Source Code Pro, monospace",
|
||||
"IBM Plex Mono": "IBM Plex Mono, monospace",
|
||||
"Roboto Mono": "Roboto Mono, monospace",
|
||||
"Space Mono": "Space Mono, monospace",
|
||||
"Geist Mono": "Geist Mono, monospace",
|
||||
};
|
||||
|
||||
export const sansSerifFonts = Object.fromEntries(
|
||||
Object.entries(fonts).filter(([key]) => sansSerifFontNames.includes(key))
|
||||
);
|
||||
export const serifFonts = Object.fromEntries(
|
||||
Object.entries(fonts).filter(([key]) => serifFontNames.includes(key))
|
||||
);
|
||||
export const monoFonts = Object.fromEntries(
|
||||
Object.entries(fonts).filter(([key]) => monoFontNames.includes(key))
|
||||
);
|
||||
|
||||
export const getAppliedThemeFont = (
|
||||
state: ThemeEditorState,
|
||||
fontKey: keyof ThemeStyleProps
|
||||
): string | null => {
|
||||
const fontSans = state.styles.light[fontKey];
|
||||
// find key of font in fonts object based on value
|
||||
const key = Object.keys(fonts).find((key) => fonts[key].includes(fontSans));
|
||||
return key ? key : null;
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,211 @@
|
||||
import { ThemeEditorState, ThemeStyles } from "@/types/theme";
|
||||
import { colorFormatter } from "./color-converter";
|
||||
import { ColorFormat } from "../types";
|
||||
import { getShadowMap } from "./shadows";
|
||||
import { defaultLightThemeStyles } from "@/config/theme";
|
||||
|
||||
type ThemeMode = "light" | "dark";
|
||||
|
||||
const generateColorVariables = (
|
||||
themeStyles: ThemeStyles,
|
||||
mode: ThemeMode,
|
||||
formatColor: (color: string) => string
|
||||
): string => {
|
||||
const styles = themeStyles[mode];
|
||||
return `
|
||||
--background: ${formatColor(styles.background)};
|
||||
--foreground: ${formatColor(styles.foreground)};
|
||||
--card: ${formatColor(styles.card)};
|
||||
--card-foreground: ${formatColor(styles["card-foreground"])};
|
||||
--popover: ${formatColor(styles.popover)};
|
||||
--popover-foreground: ${formatColor(styles["popover-foreground"])};
|
||||
--primary: ${formatColor(styles.primary)};
|
||||
--primary-foreground: ${formatColor(styles["primary-foreground"])};
|
||||
--secondary: ${formatColor(styles.secondary)};
|
||||
--secondary-foreground: ${formatColor(styles["secondary-foreground"])};
|
||||
--muted: ${formatColor(styles.muted)};
|
||||
--muted-foreground: ${formatColor(styles["muted-foreground"])};
|
||||
--accent: ${formatColor(styles.accent)};
|
||||
--accent-foreground: ${formatColor(styles["accent-foreground"])};
|
||||
--destructive: ${formatColor(styles.destructive)};
|
||||
--destructive-foreground: ${formatColor(styles["destructive-foreground"])};
|
||||
--border: ${formatColor(styles.border)};
|
||||
--input: ${formatColor(styles.input)};
|
||||
--ring: ${formatColor(styles.ring)};
|
||||
--chart-1: ${formatColor(styles["chart-1"])};
|
||||
--chart-2: ${formatColor(styles["chart-2"])};
|
||||
--chart-3: ${formatColor(styles["chart-3"])};
|
||||
--chart-4: ${formatColor(styles["chart-4"])};
|
||||
--chart-5: ${formatColor(styles["chart-5"])};
|
||||
--sidebar: ${formatColor(styles.sidebar)};
|
||||
--sidebar-foreground: ${formatColor(styles["sidebar-foreground"])};
|
||||
--sidebar-primary: ${formatColor(styles["sidebar-primary"])};
|
||||
--sidebar-primary-foreground: ${formatColor(styles["sidebar-primary-foreground"])};
|
||||
--sidebar-accent: ${formatColor(styles["sidebar-accent"])};
|
||||
--sidebar-accent-foreground: ${formatColor(styles["sidebar-accent-foreground"])};
|
||||
--sidebar-border: ${formatColor(styles["sidebar-border"])};
|
||||
--sidebar-ring: ${formatColor(styles["sidebar-ring"])};`;
|
||||
};
|
||||
|
||||
const generateFontVariables = (
|
||||
themeStyles: ThemeStyles,
|
||||
mode: ThemeMode
|
||||
): string => {
|
||||
const styles = themeStyles[mode];
|
||||
return `
|
||||
--font-sans: ${styles["font-sans"]};
|
||||
--font-serif: ${styles["font-serif"]};
|
||||
--font-mono: ${styles["font-mono"]};`;
|
||||
};
|
||||
|
||||
const generateShadowVariables = (shadowMap: Record<string, string>): string => {
|
||||
return `
|
||||
--shadow-2xs: ${shadowMap["shadow-2xs"]};
|
||||
--shadow-xs: ${shadowMap["shadow-xs"]};
|
||||
--shadow-sm: ${shadowMap["shadow-sm"]};
|
||||
--shadow: ${shadowMap["shadow"]};
|
||||
--shadow-md: ${shadowMap["shadow-md"]};
|
||||
--shadow-lg: ${shadowMap["shadow-lg"]};
|
||||
--shadow-xl: ${shadowMap["shadow-xl"]};
|
||||
--shadow-2xl: ${shadowMap["shadow-2xl"]};`;
|
||||
};
|
||||
|
||||
const generateTrackingVariables = (themeStyles: ThemeStyles): string => {
|
||||
const styles = themeStyles["light"];
|
||||
if (styles["letter-spacing"] === "0em") {
|
||||
return "";
|
||||
}
|
||||
return `
|
||||
|
||||
--tracking-tighter: calc(var(--tracking-normal) - 0.05em);
|
||||
--tracking-tight: calc(var(--tracking-normal) - 0.025em);
|
||||
--tracking-normal: var(--tracking-normal);
|
||||
--tracking-wide: calc(var(--tracking-normal) + 0.025em);
|
||||
--tracking-wider: calc(var(--tracking-normal) + 0.05em);
|
||||
--tracking-widest: calc(var(--tracking-normal) + 0.1em);`;
|
||||
};
|
||||
|
||||
const generateThemeVariables = (
|
||||
themeStyles: ThemeStyles,
|
||||
mode: ThemeMode,
|
||||
formatColor: (color: string) => string
|
||||
): string => {
|
||||
const selector = mode === "dark" ? ".dark" : ":root";
|
||||
const colorVars = generateColorVariables(themeStyles, mode, formatColor);
|
||||
const fontVars = generateFontVariables(themeStyles, mode);
|
||||
const radiusVar = `\n --radius: ${themeStyles[mode].radius};`;
|
||||
const shadowVars = generateShadowVariables(
|
||||
getShadowMap({ styles: themeStyles, currentMode: mode })
|
||||
);
|
||||
const spacingVar =
|
||||
mode === "light" &&
|
||||
themeStyles["light"].spacing !== defaultLightThemeStyles.spacing
|
||||
? `\n --spacing: ${themeStyles["light"].spacing};`
|
||||
: "";
|
||||
|
||||
const trackingVars =
|
||||
mode === "light" &&
|
||||
themeStyles["light"]["letter-spacing"] !==
|
||||
defaultLightThemeStyles["letter-spacing"]
|
||||
? `\n --tracking-normal: ${themeStyles["light"]["letter-spacing"]};`
|
||||
: "";
|
||||
|
||||
return (
|
||||
selector +
|
||||
" {" +
|
||||
colorVars +
|
||||
fontVars +
|
||||
radiusVar +
|
||||
shadowVars +
|
||||
trackingVars +
|
||||
spacingVar +
|
||||
"\n}"
|
||||
);
|
||||
};
|
||||
|
||||
const generateTailwindV4ThemeInline = (themeStyles: ThemeStyles): string => {
|
||||
return `@theme inline {
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
--color-card: var(--card);
|
||||
--color-card-foreground: var(--card-foreground);
|
||||
--color-popover: var(--popover);
|
||||
--color-popover-foreground: var(--popover-foreground);
|
||||
--color-primary: var(--primary);
|
||||
--color-primary-foreground: var(--primary-foreground);
|
||||
--color-secondary: var(--secondary);
|
||||
--color-secondary-foreground: var(--secondary-foreground);
|
||||
--color-muted: var(--muted);
|
||||
--color-muted-foreground: var(--muted-foreground);
|
||||
--color-accent: var(--accent);
|
||||
--color-accent-foreground: var(--accent-foreground);
|
||||
--color-destructive: var(--destructive);
|
||||
--color-destructive-foreground: var(--destructive-foreground);
|
||||
--color-border: var(--border);
|
||||
--color-input: var(--input);
|
||||
--color-ring: var(--ring);
|
||||
--color-chart-1: var(--chart-1);
|
||||
--color-chart-2: var(--chart-2);
|
||||
--color-chart-3: var(--chart-3);
|
||||
--color-chart-4: var(--chart-4);
|
||||
--color-chart-5: var(--chart-5);
|
||||
--color-sidebar: var(--sidebar);
|
||||
--color-sidebar-foreground: var(--sidebar-foreground);
|
||||
--color-sidebar-primary: var(--sidebar-primary);
|
||||
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
||||
--color-sidebar-accent: var(--sidebar-accent);
|
||||
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
||||
--color-sidebar-border: var(--sidebar-border);
|
||||
--color-sidebar-ring: var(--sidebar-ring);
|
||||
|
||||
--font-sans: var(--font-sans);
|
||||
--font-mono: var(--font-mono);
|
||||
--font-serif: var(--font-serif);
|
||||
|
||||
--radius-sm: calc(var(--radius) - 4px);
|
||||
--radius-md: calc(var(--radius) - 2px);
|
||||
--radius-lg: var(--radius);
|
||||
--radius-xl: calc(var(--radius) + 4px);
|
||||
|
||||
--shadow-2xs: var(--shadow-2xs);
|
||||
--shadow-xs: var(--shadow-xs);
|
||||
--shadow-sm: var(--shadow-sm);
|
||||
--shadow: var(--shadow);
|
||||
--shadow-md: var(--shadow-md);
|
||||
--shadow-lg: var(--shadow-lg);
|
||||
--shadow-xl: var(--shadow-xl);
|
||||
--shadow-2xl: var(--shadow-2xl);${generateTrackingVariables(themeStyles)}
|
||||
}`;
|
||||
};
|
||||
|
||||
export const generateThemeCode = (
|
||||
themeEditorState: ThemeEditorState,
|
||||
colorFormat: ColorFormat = "hsl",
|
||||
tailwindVersion: "3" | "4" = "3"
|
||||
): string => {
|
||||
if (
|
||||
!themeEditorState ||
|
||||
!("light" in themeEditorState.styles) ||
|
||||
!("dark" in themeEditorState.styles)
|
||||
) {
|
||||
throw new Error("Invalid theme styles: missing light or dark mode");
|
||||
}
|
||||
|
||||
const themeStyles = themeEditorState.styles as ThemeStyles;
|
||||
const formatColor = (color: string) =>
|
||||
colorFormatter(color, colorFormat, tailwindVersion);
|
||||
|
||||
const lightTheme = generateThemeVariables(themeStyles, "light", formatColor);
|
||||
const darkTheme = generateThemeVariables(themeStyles, "dark", formatColor);
|
||||
const tailwindV4Theme =
|
||||
tailwindVersion === "4"
|
||||
? `\n\n${generateTailwindV4ThemeInline(themeStyles)}`
|
||||
: "";
|
||||
|
||||
const bodyLetterSpacing =
|
||||
themeStyles["light"]["letter-spacing"] !== "0em"
|
||||
? "\n\nbody {\n letter-spacing: var(--tracking-normal);\n}"
|
||||
: "";
|
||||
|
||||
return `${lightTheme}\n\n${darkTheme}${tailwindV4Theme}${bodyLetterSpacing}`;
|
||||
};
|
||||
Reference in New Issue
Block a user