Colors: add HSL utils module

color-improvements
Alex Gleason 2022-09-05 23:43:10 -05:00
rodzic 4ee9419402
commit 0fd8209df6
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 7211D1F99744FBB7
2 zmienionych plików z 170 dodań i 0 usunięć

Wyświetl plik

@ -0,0 +1,82 @@
import {
hslShift,
expandPalette,
// paletteToDelta,
HSLPaletteDelta,
} from '../hsl';
test('hslShift()', () => {
expect(hslShift({ h: 50, s: 50, l: 50 }, [-200, 10, 0]))
.toEqual({ h: 210, s: 60, l: 50 });
expect(hslShift({ h: 1, s: 2, l: 3 }, [0, 0, 0]))
.toEqual({ h: 1, s: 2, l: 3 });
expect(hslShift({ h: 0, s: 100, l: 0 }, [-1, 1, -1]))
.toEqual({ h: 359, s: 100, l: 0 });
});
test('expandPalette()', () => {
const palette = {
200: { h: 7, s: 7, l: 7 },
500: { h: 50, s: 50, l: 50 },
};
const paletteDelta: HSLPaletteDelta = {
50: [-10, -100, 7],
100: [0, 0, 0],
200: [0, 0, 0],
300: [0, 0, 0],
400: [0, 0, 0],
500: [0, 0, 0],
600: [0, 0, 0],
700: [0, 0, 0],
800: [0, 0, 0],
900: [10, 100, -7],
};
const expected = {
50: { h: 40, s: 0, l: 57 },
100: { h: 50, s: 50, l: 50 },
200: { h: 7, s: 7, l: 7 },
300: { h: 50, s: 50, l: 50 },
400: { h: 50, s: 50, l: 50 },
500: { h: 50, s: 50, l: 50 },
600: { h: 50, s: 50, l: 50 },
700: { h: 50, s: 50, l: 50 },
800: { h: 50, s: 50, l: 50 },
900: { h: 60, s: 100, l: 43 },
};
expect(expandPalette(palette, paletteDelta)).toEqual(expected);
});
// test('paletteToDelta()', () => {
// const palette = {
// 50: { h: 40, s: 0, l: 57 },
// 100: { h: 50, s: 50, l: 50 },
// 200: { h: 7, s: 7, l: 7 },
// 300: { h: 50, s: 50, l: 50 },
// 400: { h: 50, s: 50, l: 50 },
// 500: { h: 50, s: 50, l: 50 },
// 600: { h: 50, s: 50, l: 50 },
// 700: { h: 50, s: 50, l: 50 },
// 800: { h: 50, s: 50, l: 50 },
// 900: { h: 60, s: 100, l: 43 },
// };
// const expected = {
// 50: [-10, -50, 7],
// 100: [0, 0, 0],
// 200: [-43, -43, -43],
// 300: [0, 0, 0],
// 400: [0, 0, 0],
// 500: [0, 0, 0],
// 600: [0, 0, 0],
// 700: [0, 0, 0],
// 800: [0, 0, 0],
// 900: [10, 50, -7],
// };
// expect(paletteToDelta(palette)).toEqual(expected);
// });

Wyświetl plik

@ -0,0 +1,88 @@
import type { Hsl } from 'soapbox/types/colors';
/** A neutral color. */
const GRAY: Hsl = { h: 0, s: 50, l: 50 };
/** Modulo (`%`) in both directions. */
// https://stackoverflow.com/a/39740009
const wrapAround = (value: number, delta: number, max: number): number => {
if (delta >= 0) {
return (value + delta) % max;
} else {
return max - ((max - (value + delta)) % max);
}
};
/** Clamp the value within the range of `min` and `max`. */
// https://stackoverflow.com/a/47837835
const minmax = (
value: number,
min: number,
max: number,
) => Math.min(max, Math.max(min, value));
/**
* Represents an HSL color shift.
*
* For example, `[-20, 10, 0]` means "-20deg hue, +10% saturation, unchanged lightness".
*/
type HSLDelta = [hDelta: number, sDelta: number, lDelta: number];
/** Tailwind color shade. */
type Shade = '50' | '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900';
/** Tailwind color palette with HSL. */
type HSLPalette = Record<Shade, Hsl>;
/** Tailwind color palette delta map (in HSL). */
type HSLPaletteDelta = Record<Shade, HSLDelta>;
/** Alter the color by the given delta. */
const hslShift = (seed: Hsl, delta: HSLDelta): Hsl => {
return {
h: wrapAround(seed.h, delta[0], 360),
s: minmax(seed.s + delta[1], 0, 100),
l: minmax(seed.l + delta[2], 0, 100),
};
};
/** Generate a color palette from a single color. */
const generatePalette = (seed: Hsl, paletteDelta: HSLPaletteDelta): HSLPalette => {
const shades = Object.keys(paletteDelta) as Shade[];
return shades.reduce((result: HSLPalette, shade: Shade) => {
const delta = paletteDelta[shade];
result[shade] = hslShift(seed, delta);
return result;
}, {} as HSLPalette);
};
/** Expands a partial color palette, filling in the gaps. */
const expandPalette = (palette: Partial<HSLPalette>, paletteDelta: HSLPaletteDelta): HSLPalette => {
const seed = palette['500'] || GRAY;
const generated = generatePalette(seed, paletteDelta);
return Object.assign(generated, palette);
};
/** Convert a complete color palette into a delta map. */
const paletteToDelta = (palette: HSLPalette): HSLPaletteDelta => {
const seed = palette['500'];
const shades = Object.keys(palette) as Shade[];
return shades.reduce((result: HSLPaletteDelta, shade: Shade) => {
const color = palette[shade];
result[shade] = [
wrapAround(color.h, -seed.h, 360),
minmax(color.s - seed.s, -100, 100),
minmax(color.l - seed.l, -100, 100),
];
return result;
}, {} as HSLPaletteDelta);
};
export {
hslShift,
expandPalette,
paletteToDelta,
HSLDelta,
HSLPaletteDelta,
};