UNPKG

@thangk/easythemer

Version:

Easily generate shades from a colour palette for use in your app

199 lines (198 loc) 7.85 kB
import validateHex from "./classes/validateHex"; import { _HSL, defaultShadeMultipliers, defaultThemeOption, defaultThemeParams } from "./constants"; function limiter(input, limitStringArray) { const mode = limitStringArray.split("-")[0]; const limit = parseInt(limitStringArray.split("-")[1]); // for max mode if (mode === "max") return input > limit ? limit : input; if (mode === "min") return input < limit ? limit : input; // for neither mode return input; } function getChannelValue({ shadeMultiplier, HSL_channel, HSL_letter, workingObj }) { const { upperboundDivider, lowerboundDivider, upperboundPadding, lowerboundPadding } = workingObj; let HSL_letter_MAX; switch (HSL_letter) { case "h": HSL_letter_MAX = _HSL.MAX_H; break; case "s": HSL_letter_MAX = _HSL.MAX_S; break; case "l": HSL_letter_MAX = _HSL.MAX_L; break; default: HSL_letter_MAX = 0; break; } const upperbound = HSL_letter_MAX - HSL_channel; const lowerbound = HSL_channel; const upperboundStep = upperboundDivider && upperboundPadding ? Math.round((upperbound / upperboundDivider) * upperboundPadding) : 0; const lowerboundStep = lowerboundDivider && lowerboundPadding ? Math.round((lowerbound / lowerboundDivider) * lowerboundPadding) : 0; if (shadeMultiplier > 0) HSL_channel = limiter(HSL_channel + upperboundStep * shadeMultiplier, `max-${HSL_letter_MAX - 5}`); if (shadeMultiplier < 0) HSL_channel = limiter(HSL_channel + lowerboundStep * shadeMultiplier, `min-${0 + 15}`); return HSL_channel; } export function merger(customOptions, defaultOptions) { const customOptionsKeys = Object.keys(customOptions); const defaultOptionsKeys = Object.keys(defaultOptions); // look for same keys to check if they're both the same object type (not the best method but for this 1 case it works) const hasSameKeys = customOptionsKeys.every((key) => { if (defaultOptionsKeys.includes(key)) return true; }); if (hasSameKeys) return { ...defaultOptions, ...customOptions }; const mergedOptions = {}; for (const key in customOptions) { mergedOptions[key] = { ...defaultThemeOption, ...customOptions[key] }; } return mergedOptions; } function getBoundSteps({ h, s, l }, inputThemeOption, shadeMultiplier) { if (inputThemeOption.params_h?.useDefault === false) { const params = inputThemeOption.params_s?.params; const workingObj = Object.assign(defaultThemeParams, params); h = getChannelValue({ shadeMultiplier, HSL_channel: h, HSL_letter: "h", workingObj }); } if (inputThemeOption.params_s?.useDefault === false) { const params = inputThemeOption.params_s?.params; const workingObj = Object.assign(defaultThemeParams, params); s = getChannelValue({ shadeMultiplier, HSL_channel: s, HSL_letter: "s", workingObj }); } if (inputThemeOption.params_l?.useDefault === false) { const params = inputThemeOption.params_l?.params; const workingObj = Object.assign(defaultThemeParams, params); l = getChannelValue({ shadeMultiplier, HSL_channel: l, HSL_letter: "l", workingObj }); } if (inputThemeOption.params_h?.useDefault === true || undefined) { h = getChannelValue({ shadeMultiplier, HSL_channel: h, HSL_letter: "h", workingObj: defaultThemeParams }); } if (inputThemeOption.params_s?.useDefault === true || undefined) { s = getChannelValue({ shadeMultiplier, HSL_channel: s, HSL_letter: "s", workingObj: defaultThemeParams }); } if (inputThemeOption.params_l?.useDefault === true || undefined) { l = getChannelValue({ shadeMultiplier, HSL_channel: l, HSL_letter: "l", workingObj: defaultThemeParams }); } return { h, s, l }; } function generateShades(input, shades) { let shadeSet = {}; const { hex, generateShades } = input; if (!generateShades) return { normal: hex }; if (!shades) shades = defaultShadeMultipliers; for (const [key, value] of Object.entries(shades)) { if (!value) { const { h, s, l } = HexToHSL(hex); shadeSet[key] = hex; continue; } const { h, s, l } = getBoundSteps(HexToHSL(hex), input, value); shadeSet[key] = HSLToHex({ h, s, l }); } return shadeSet; } // inspired by https://www.jameslmilner.com/posts/converting-rgb-hex-hsl-colors/ function HexToHSL(hex) { const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); if (!result) { throw new Error("Could not parse Hex Color"); } const rHex = parseInt(result[1], 16); const gHex = parseInt(result[2], 16); const bHex = parseInt(result[3], 16); const r = rHex / 255; const g = gHex / 255; const b = bHex / 255; const max = Math.max(r, g, b); const min = Math.min(r, g, b); let h = (max + min) / 2; let s = h; let l = h; if (max === min) { // Achromatic return { h: 0, s: 0, l }; } const d = max - min; s = l > 0.5 ? d / (2 - max - min) : d / (max + min); switch (max) { case r: h = (g - b) / d + (g < b ? 6 : 0); break; case g: h = (b - r) / d + 2; break; case b: h = (r - g) / d + 4; break; } h /= 6; h = Math.round(360 * h); s = Math.round(s * 100); l = Math.round(l * 100); return { h, s, l }; } // inspired by https://www.jameslmilner.com/posts/converting-rgb-hex-hsl-colors/ function HSLToHex(source) { let { h, s, l } = source; l /= 100; const a = (s * Math.min(l, 1 - l)) / 100; const f = (n) => { const k = (n + h / 30) % 12; const color = l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1); return Math.round(255 * color) .toString(16) .padStart(2, "0"); // convert to Hex and prefix "0" if needed }; return `#${f(0)}${f(8)}${f(4)}`; } export function useThemer({ customOptions, customShades, }) { let error = null; if (!customOptions) { error = "ERROR: You must provide at least one colour option."; return { error }; } const validateHexLog = {}; let isValidationErrorsExist = false; let counter = 0; // validate hex code first for (const [key, value] of Object.entries(customOptions)) { console.log(`value ${counter++}`, value); const value1 = new validateHex(value.hex); if (!value1.isError) { validateHexLog[value1.getInput] = value1.errorsList; isValidationErrorsExist = true; } } if (isValidationErrorsExist) { console.log("validateHexLog", validateHexLog); return { error: "ERROR: There are some invalid hex code." }; } const mergedOptions = merger(customOptions, defaultThemeOption); const mergedShades = customShades ? merger(customShades, defaultShadeMultipliers) : defaultShadeMultipliers; let myTheme = {}; if (mergedOptions) for (const [key, value] of Object.entries(mergedOptions)) { if (!value.generateShades) { myTheme[key] = { normal: value.hex }; continue; } if (value.generateShades) myTheme[key] = generateShades(value, mergedShades); } return { themeOptions: mergedOptions, themeShades: mergedShades, myTheme, error, }; }