themer
Version:
Customizable theme creator for editors, terminals, wallpaper, and more.
167 lines (153 loc) • 4.36 kB
text/typescript
import Color from 'color';
import capitalize from 'lodash/capitalize.js';
import camelCase from 'lodash/camelCase.js';
import kebabCase from 'lodash/kebabCase.js';
import snakeCase from 'lodash/snakeCase.js';
import upperFirst from 'lodash/upperFirst.js';
type FullShades = {
shade0: string;
shade1: string;
shade2: string;
shade3: string;
shade4: string;
shade5: string;
shade6: string;
shade7: string;
};
type PartialShades = {
shade0: string;
shade7: string;
};
export type Accents = {
accent0: string;
accent1: string;
accent2: string;
accent3: string;
accent4: string;
accent5: string;
accent6: string;
accent7: string;
};
type LightVariantBase<T extends Variant> = { light: T };
type DarkVariantBase<T extends Variant> = { dark: T };
type VariantBase<T extends Variant> =
| LightVariantBase<T>
| DarkVariantBase<T>
| (LightVariantBase<T> & DarkVariantBase<T>);
type ColorSetBase<T extends Variant> = {
name: string;
variants: VariantBase<T>;
};
type PartialVariant = PartialShades & Accents;
export type FullVariant = FullShades & Accents;
export type Variant = PartialVariant | FullVariant;
export type PartialColorSet = ColorSetBase<PartialVariant>;
export type FullColorSet = ColorSetBase<FullVariant>;
export type ColorSet = PartialColorSet | FullColorSet;
export type AnnotatedVariant = {
name: string;
colors: FullVariant;
isDark: boolean;
title: {
human: string;
kebab: string;
snake: string;
upperCamel: string;
};
};
export type AnnotatedColorSet = FullColorSet & {
title: {
human: string;
kebab: string;
snake: string;
upperCamel: string;
};
};
export function colorSetToVariants(
colorSet: AnnotatedColorSet,
): AnnotatedVariant[] {
return Object.entries(colorSet.variants).map(([name, colors]) => ({
name,
colors,
isDark: name === 'dark',
title: {
human: `${colorSet.title.human} ${capitalize(name)}`,
kebab: `${colorSet.title.kebab}-${name}`,
snake: `${colorSet.title.snake}_${name}`,
upperCamel: `${colorSet.title.upperCamel}${capitalize(name)}`,
},
}));
}
export function prepareColorSet(colorSet: ColorSet): AnnotatedColorSet {
const variants: FullColorSet['variants'] =
'dark' in colorSet.variants && 'light' in colorSet.variants
? {
dark: prepareVariant(colorSet.variants.dark),
light: prepareVariant(colorSet.variants.light),
}
: 'dark' in colorSet.variants
? { dark: prepareVariant(colorSet.variants.dark) }
: { light: prepareVariant(colorSet.variants.light) };
return {
...colorSet,
variants,
title: {
human: `Themer ${colorSet.name}`,
kebab: kebabCase(`themer-${colorSet.name}`),
snake: snakeCase(`themer_${colorSet.name}`),
upperCamel: upperFirst(camelCase(`Themer ${colorSet.name}`)),
},
};
}
export function prepareVariant(variant: Variant): FullVariant {
if ('shade1' in variant) {
return variant;
}
const start = new Color(variant.shade0);
const end = new Color(variant.shade7);
const rDelta = (end.red() - start.red()) / 7;
const gDelta = (end.green() - start.green()) / 7;
const bDelta = (end.blue() - start.blue()) / 7;
return {
...variant,
shade1: new Color({
r: start.red() + rDelta * 1,
g: start.green() + gDelta * 1,
b: start.blue() + bDelta * 1,
}).hex(),
shade2: new Color({
r: start.red() + rDelta * 2,
g: start.green() + gDelta * 2,
b: start.blue() + bDelta * 2,
}).hex(),
shade3: new Color({
r: start.red() + rDelta * 3,
g: start.green() + gDelta * 3,
b: start.blue() + bDelta * 3,
}).hex(),
shade4: new Color({
r: start.red() + rDelta * 4,
g: start.green() + gDelta * 4,
b: start.blue() + bDelta * 4,
}).hex(),
shade5: new Color({
r: start.red() + rDelta * 5,
g: start.green() + gDelta * 5,
b: start.blue() + bDelta * 5,
}).hex(),
shade6: new Color({
r: start.red() + rDelta * 6,
g: start.green() + gDelta * 6,
b: start.blue() + bDelta * 6,
}).hex(),
};
}
export function brightMix(
colors: FullVariant,
key: keyof FullVariant,
isDark: boolean,
): string {
return Color(colors[key])
.mix(isDark ? Color(colors.shade7) : Color(colors.shade0), 0.2)
.hex();
}