UNPKG

@aesthetic/system

Version:

Web based building blocks for the Aesthetic design system.

162 lines (130 loc) 3.85 kB
import { ColorScheme, ContrastLevel, ThemeName } from '@aesthetic/types'; import { Theme } from './Theme'; import { ThemeOptions } from './types'; export class ThemeRegistry<Block extends object> { protected darkTheme: string = ''; protected defaultTheme: string = ''; protected lightTheme: string = ''; protected themes: Record<string, Theme<Block>> = {}; /** * Return the default dark theme. */ getDarkTheme(): Theme<Block> { return this.getTheme(this.darkTheme); } /** * Return the default light theme. */ getLightTheme(): Theme<Block> { return this.getTheme(this.lightTheme); } /** * Find an approprite theme based on the user's or device's preferences. * Will check for preferred color schemes and contrast levels. */ getPreferredTheme({ matchColorScheme, matchContrastLevel, }: { matchColorScheme?: (scheme: ColorScheme) => boolean; matchContrastLevel?: (level: ContrastLevel) => boolean; } = {}): Theme<Block> { const prefersDarkScheme = matchColorScheme?.('dark'); const prefersLightScheme = matchColorScheme?.('light'); const prefersHighContrast = matchContrastLevel?.('high'); const prefersLowContrast = matchContrastLevel?.('low'); const schemeCheckOrder: ColorScheme[] = []; if (prefersDarkScheme) { schemeCheckOrder.push('dark'); } else if (prefersLightScheme) { schemeCheckOrder.push('light'); } let possibleTheme: Theme<Block> | undefined; // Find a theme based on device preferences schemeCheckOrder.some((scheme) => { const contrastCheckOrder: ContrastLevel[] = ['normal']; if (prefersHighContrast) { contrastCheckOrder.unshift('high'); } else if (prefersLowContrast) { contrastCheckOrder.unshift('low'); } return contrastCheckOrder.some((contrast) => { possibleTheme = this.query({ contrast, scheme }); return !!possibleTheme; }); }); if (possibleTheme) { return possibleTheme; } if (this.defaultTheme) { return this.getTheme(this.defaultTheme); } throw new Error('No themes have been registered.'); } /** * Return a theme by name or throw an error if not found. */ getTheme(name: ThemeName): Theme<Block> { if (__DEV__ && !name) { throw new Error('Cannot find a theme without a name.'); } const theme = this.themes[name]; if (__DEV__ && !theme) { throw new Error(`Theme "${name}" does not exist. Has it been registered?`); } return theme; } /** * Query for a theme that matches the defined parameters. */ query(params: Partial<ThemeOptions>): Theme<Block> | undefined { return Object.values(this.themes).find((theme) => { const conditions: boolean[] = []; if (params.contrast) { conditions.push(theme.contrast === params.contrast); } if (params.scheme) { conditions.push(theme.scheme === params.scheme); } return conditions.every((c) => !!c); }); } /** * Register a theme with a unique name. Can optionally mark a theme * as default for their defined color scheme. */ register(name: string, theme: Theme<Block>, isDefault: boolean = false): this { if (__DEV__ && !(theme instanceof Theme)) { throw new TypeError('Only a `Theme` object can be registered.'); } if (isDefault) { if (theme.scheme === 'dark') { this.darkTheme = name; } else { this.lightTheme = name; } } if (theme.name && theme.name !== name) { if (__DEV__) { throw new Error(`Theme "${name}" has already been registered under "${theme.name}".`); } } else { // eslint-disable-next-line no-param-reassign theme.name = name; } if (!this.defaultTheme) { this.defaultTheme = name; } this.themes[name] = theme; return this; } /** * Reset the registry to initial state. */ reset() { this.darkTheme = ''; this.lightTheme = ''; this.defaultTheme = ''; this.themes = {}; } }