diff --git a/src/Theme/Colors/index.ts b/src/Theme/Colors/index.ts index 2fd255b..fdfa75d 100644 --- a/src/Theme/Colors/index.ts +++ b/src/Theme/Colors/index.ts @@ -1,20 +1,33 @@ import { countDecimals } from "Utils"; import { fromString } from "css-color-converter"; +export interface ColorsInput { + [key: string]: string; +} + +export interface ShadesOptions { + keys?: string[]; + keyPrefix?: string; + keySuffix?: string; + opacities?: number[]; + structure?: "flat" | "nested"; + format?: "default" | "css-variable"; +} + const DEFAULT_OPACITIES = [0.1, 0.25, 0.5, 0.75]; const DEFAULT_STRUCTURE = "flat"; const formatColor = ({ - prefix, + key, rgb, opacity, }: { - prefix?: string; + key?: string; rgb: string; opacity?: number; }) => { return { - [`${prefix ? prefix + "-" : ""}${ + [`${key ? key + "-" : ""}${ opacity !== undefined ? countDecimals(opacity) === 1 ? (opacity + "").split(".")[1] + "0" @@ -24,16 +37,7 @@ const formatColor = ({ }; }; -const generateShades = ( - colors: { [key: string]: string }, - options?: { - keys?: string[]; - keyPrefix?: string; - opacities?: number[]; - structure?: "flat" | "nested"; - format?: "default" | "css-variable"; - } -) => { +const generateShades = (colors: ColorsInput, options?: ShadesOptions) => { const colorKeys = options?.keys ? Object.keys(colors).filter((key) => options.keys?.includes(key)) : Object.keys(colors); @@ -47,7 +51,7 @@ const generateShades = ( const generatedColorShadesArr: object[] = []; colorKeys.forEach((_key) => { - const key = (options?.keyPrefix || "") + _key; + const key = (options?.keyPrefix || "") + _key + (options?.keySuffix || ""); const colorString = colors[_key]; const opacities = options?.opacities @@ -57,9 +61,7 @@ const generateShades = ( if (structure === "flat") { opacities.forEach((opacity) => { const rgba = fromString(colorString).toRgbaArray(); - generatedColorShadesArr.push( - formatColor({ prefix: key, rgb: rgba, opacity }) - ); + generatedColorShadesArr.push(formatColor({ key, rgb: rgba, opacity })); }); } else { generatedColorShadesArr.push({ diff --git a/src/Theme/index.test.ts b/src/Theme/index.test.ts new file mode 100644 index 0000000..54cb9ff --- /dev/null +++ b/src/Theme/index.test.ts @@ -0,0 +1,74 @@ +import { + generateThemeOverrideClass, + generateThemeRoot, + generateTheme, + ThemeInput, +} from "."; + +const exampleProperties = { + "color-main-10": "black", + "color-main-50": "grey", +}; + +const exampleThemes: ThemeInput[] = [ + { + name: "default", + colors: { "color-main": "black" }, + }, + { + name: "dark", + colors: { "color-main": "white", "color-secondary": "blue" }, + }, + { name: "light", colors: { "color-secondary": "grey" } }, +]; + +test("it generates css based on input properties", () => { + const themeClass = generateThemeOverrideClass({ + properties: exampleProperties, + className: "dark-theme", + }); + + expect(themeClass).toBeDefined(); +}); + +test("no input so it returns empty string", () => { + const themeClass = generateThemeOverrideClass({ + properties: {}, + className: "dark-theme", + }); + + expect(themeClass).toBeDefined(); + expect(themeClass).toEqual(""); +}); + +test("it generates the theme root", () => { + const themeRoot = generateThemeRoot({ properties: exampleProperties }); + console.log(themeRoot); + expect(themeRoot).toBeDefined(); +}); + +test("it generates the complete theme with overrides", () => { + const theme = generateTheme({ + themes: exampleThemes, + output: "CSS", + }); + + console.log(theme); + + expect(theme).toBeDefined(); +}); + +test("it generates the complete theme with overrides and color shades", () => { + const theme = generateTheme({ + themes: exampleThemes, + output: "CSS", + generateColorShades: { + structure: "flat", + format: "css-variable", + }, + }); + + console.log(theme); + + expect(theme).toBeDefined(); +}); diff --git a/src/Theme/index.ts b/src/Theme/index.ts index 7b2d6b8..8b161ce 100644 --- a/src/Theme/index.ts +++ b/src/Theme/index.ts @@ -1,3 +1,85 @@ import * as Colors from "./Colors"; +import { generateShades, ColorsInput, ShadesOptions } from "./Colors"; -export default { Colors }; +export interface ThemeInput { + name: string; + colors: ColorsInput; +} + +const generateThemeOverrideClass = (input: { + properties: any; + className: string; +}) => { + const propKeys = Object.keys(input.properties); + + if (propKeys.length > 0) { + return ` + .${input.className} {${propKeys + .map((key) => `\t--${key}: ${input.properties[key]};`) + .join("\n")}}`; + } else return ""; +}; + +const generateThemeRoot = (input: { properties: any }) => { + const propKeys = Object.keys(input.properties); + + if (propKeys.length > 0) { + return ` + :root {${propKeys + .map((key) => `\t--${key}: ${input.properties[key]};`) + .join("\n")}}`; + } else return ""; +}; + +const generateTheme = (options: { + output?: "CSS"; + themes: ThemeInput[]; + generateColorShades?: boolean | ShadesOptions; +}) => { + if ( + options.generateColorShades !== undefined && + options.generateColorShades !== false + ) { + options.themes = options.themes.map((theme) => { + theme.colors = { + ...generateShades( + theme.colors, + typeof options.generateColorShades !== "boolean" + ? options.generateColorShades + : undefined + ), + ...theme.colors, + }; + + return theme; + }); + } + + const themeRoot = generateThemeRoot({ + properties: { ...options.themes[0].colors }, + }); + + if (options.themes.length > 1) { + const themeOverrides = options.themes + .slice(1) + .map((theme) => { + const overrideClass = generateThemeOverrideClass({ + properties: { ...theme.colors }, + className: theme.name, + }); + + return overrideClass; + }) + .join("\n"); + + return themeRoot + themeOverrides; + } else return themeRoot; +}; + +export { generateThemeOverrideClass, generateThemeRoot, generateTheme }; +export default { + Colors, + generateThemeOverrideClass, + generateThemeRoot, + generateTheme, +};