import { createContext, useState, useContext, useMemo, useEffect, RefObject, } from "react"; import contrast from "get-contrast"; import { blendWithBlack, blendWithWhite, getColorPaletteByVariation, getHighContrastColor, } from "../util/widgetUtil"; /** * the provider type, typically with a children prop only */ export type BorderRadiusType = "Square" | "Rounded" | "Round"; export type FontType = | "Overpass" | "TimesNewRoman" | "Arial" | "CourierNew" | "TrebuchetMS" | "Verdana"; type ThemeProviderProps = { children: React.ReactNode; primaryColor?: string; secondaryColor?: string; borderRadius?: BorderRadiusType; fontType?: FontType; alternativeRoot?: RefObject; }; /** * the context type, which provides the props and methods we can use in the components */ type ThemeProviderContextType = { currentPrimaryColor?: string; currentSecondaryColor?: string; }; /** * the context type defaults */ const defaultAccordionContextType: ThemeProviderContextType = { currentPrimaryColor: undefined, currentSecondaryColor: undefined, }; /** * create a new context of type {@link ThemeProviderContextType} using reacts {@link createContext} */ const ThemeContext = createContext( defaultAccordionContextType ); /** * the provider which defines all the methods and properties, which are declared in {@link ThemeProviderContextType} * @param children the inner component, which can use the methods and properties * @returns an instance of {@link ThemeContext.Provider} */ export default function ThemeProvider({ children, primaryColor, borderRadius, fontType, secondaryColor, alternativeRoot, }: ThemeProviderProps) { const [currentPrimaryColor, setCurrentPrimaryColor] = useState(primaryColor); const [currentSecondaryColor, setCurrentSecondaryColor] = useState(secondaryColor); const [documentToManipulate, setDocumentToManipulate] = useState(); useEffect(() => { setDocumentToManipulate( alternativeRoot?.current ? alternativeRoot.current : document.documentElement ); }, [alternativeRoot?.current]); useEffect(() => { switch (fontType) { case "Arial": documentToManipulate?.style.setProperty(`--font`, "Arial"); return; case "CourierNew": documentToManipulate?.style.setProperty(`--font`, "Courier New"); return; case "TimesNewRoman": documentToManipulate?.style.setProperty(`--font`, "Times New Roman"); return; case "TrebuchetMS": documentToManipulate?.style.setProperty(`--font`, "Trebuchet MS"); return; case "Verdana": documentToManipulate?.style.setProperty(`--font`, "Verdana"); return; case "Overpass": default: documentToManipulate?.style.setProperty(`--font`, "Overpass"); return; } }, [fontType, documentToManipulate]); useEffect(() => { switch (borderRadius) { case "Square": documentToManipulate?.style.setProperty(`--border-radius`, "0px"); return; case "Round": documentToManipulate?.style.setProperty(`--border-radius`, "1.5rem"); return; case "Rounded": default: documentToManipulate?.style.setProperty(`--border-radius`, "0.5rem"); return; } }, [borderRadius, documentToManipulate]); useEffect(() => { if (!primaryColor) return; const contrastToWhite = contrast.ratio("#fff", primaryColor); let primaryColorToUse = primaryColor; let secondaryColorToUse = secondaryColor; if (contrastToWhite < 3 && !secondaryColor) { primaryColorToUse = getHighContrastColor(primaryColor, 3.7); secondaryColorToUse = primaryColor; } if (!secondaryColorToUse) { const standardSecondaryColor = `#${ getColorPaletteByVariation(primaryColor ?? "#ffffff")[2] }`; secondaryColorToUse = standardSecondaryColor; const contrastRatioPrimarySecondary = contrast.ratio( standardSecondaryColor, primaryColorToUse ); if (contrastRatioPrimarySecondary < 2) { secondaryColorToUse = getHighContrastColor(primaryColorToUse, 3.7); } } setCurrentPrimaryColor(primaryColorToUse); setCurrentSecondaryColor(secondaryColorToUse); }, [primaryColor, secondaryColor]); const primaryColorPalette = useMemo(() => { if (!currentPrimaryColor) return; return { primary: currentPrimaryColor, hover: blendWithBlack(currentPrimaryColor, 48), active: blendWithBlack(currentPrimaryColor, 64), }; }, [currentPrimaryColor]); const secondaryColorPalette = useMemo(() => { if (!currentSecondaryColor) return; return { primary: currentSecondaryColor, hover: blendWithWhite(currentSecondaryColor, 40), active: blendWithWhite(currentSecondaryColor, 50), }; }, [currentSecondaryColor]); const hex60 = useMemo(() => { if (!currentPrimaryColor) return; return blendWithWhite(currentPrimaryColor, 42); }, [currentPrimaryColor]); const hex40 = useMemo(() => { if (!currentPrimaryColor) return; return blendWithWhite(currentPrimaryColor, 62); }, [currentPrimaryColor]); const hex20 = useMemo(() => { if (!currentPrimaryColor) return; return blendWithWhite(currentPrimaryColor, 82); }, [currentPrimaryColor]); const hex10 = useMemo(() => { if (!currentPrimaryColor) return; return blendWithWhite(currentPrimaryColor, 92); }, [currentPrimaryColor]); const akzentZwei = useMemo(() => { if (!currentPrimaryColor) return; return blendWithBlack(currentPrimaryColor, 72); }, [currentPrimaryColor]); const akzentEins = useMemo(() => { if (!currentPrimaryColor) return; return blendWithWhite(currentPrimaryColor, 85); }, [currentPrimaryColor]); useEffect(() => { if ( !currentPrimaryColor || !primaryColor || !primaryColorPalette || !secondaryColorPalette || !hex10 || !hex20 || !hex40 || !hex60 || !akzentZwei || !akzentEins ) { documentToManipulate?.style.setProperty(`--color-primary`, "#0066ff"); documentToManipulate?.style.setProperty( `--color-primary-hover`, "#0005a3" ); documentToManipulate?.style.setProperty( `--color-primary-active`, "#000068" ); documentToManipulate?.style.setProperty(`--color-primary-60`, "#66a3ff"); documentToManipulate?.style.setProperty(`--color-primary-40`, "#99c2ff"); documentToManipulate?.style.setProperty(`--color-primary-20`, "#cce0ff"); documentToManipulate?.style.setProperty(`--color-primary-10`, "#e6f0ff"); documentToManipulate?.style.setProperty(`--color-akzent-zwei`, "#0036a3"); documentToManipulate?.style.setProperty(`--color-akzent-eins`, "#3abedb"); documentToManipulate?.style.setProperty(`--color-secondary`, "#d7dfff"); documentToManipulate?.style.setProperty( `--color-secondary-hover`, "#b4c4fd" ); documentToManipulate?.style.setProperty( `--color-secondary-active`, "#99abf8" ); return; } documentToManipulate?.style.setProperty( `--color-primary`, primaryColorPalette.primary ); documentToManipulate?.style.setProperty( `--color-primary-hover`, primaryColorPalette.hover ); documentToManipulate?.style.setProperty( `--color-primary-active`, primaryColorPalette.active ); documentToManipulate?.style.setProperty(`--color-primary-60`, hex60); documentToManipulate?.style.setProperty(`--color-primary-40`, hex40); documentToManipulate?.style.setProperty(`--color-primary-20`, hex20); documentToManipulate?.style.setProperty(`--color-primary-10`, hex10); documentToManipulate?.style.setProperty(`--color-akzent-zwei`, akzentZwei); documentToManipulate?.style.setProperty(`--color-akzent-eins`, akzentEins); documentToManipulate?.style.setProperty( `--color-secondary`, secondaryColorPalette.primary ); documentToManipulate?.style.setProperty( `--color-secondary-hover`, secondaryColorPalette.hover ); documentToManipulate?.style.setProperty( `--color-secondary-active`, secondaryColorPalette.active ); }, [ primaryColorPalette, secondaryColorPalette, hex60, hex40, hex20, hex10, documentToManipulate, primaryColor, ]); const value = useMemo(() => { return { currentPrimaryColor: currentPrimaryColor, currentSecondaryColor: currentSecondaryColor, }; }, [currentSecondaryColor, currentPrimaryColor]); return ( {children} ); } /** * the custom context hook, which is used to access the methods and props defined in the provider * within the children components * @returns a context hook */ export function useThemeContext() { return useContext(ThemeContext); }