Porównaj commity

...

5 Commity

Autor SHA1 Wiadomość Data
Alex Gleason 6f9de037ed
Switch to hsluv 2022-09-10 15:56:47 -05:00
Alex Gleason 0f43ae091c
Colors: get basic normalization from brandColor working 2022-09-10 14:53:13 -05:00
Alex Gleason 9758958c35
HSL: simplify, fix tests 2022-09-10 13:11:22 -05:00
Alex Gleason b3ad112b30
Merge remote-tracking branch 'origin/develop' into color-improvements 2022-09-10 12:19:08 -05:00
Alex Gleason 0fd8209df6
Colors: add HSL utils module 2022-09-05 23:43:10 -05:00
6 zmienionych plików z 274 dodań i 31 usunięć

Wyświetl plik

@ -1,3 +1,4 @@
import { convert } from 'chromatism';
import {
Map as ImmutableMap,
List as ImmutableList,
@ -6,7 +7,7 @@ import {
} from 'immutable';
import trimStart from 'lodash/trimStart';
import { toTailwind } from 'soapbox/utils/tailwind';
import { generatePalette, HSLDelta, HSLPaletteDelta, hslShift } from 'soapbox/utils/hsl';
import { generateAccent } from 'soapbox/utils/theme';
import { normalizeAd } from './ad';
@ -18,33 +19,73 @@ import type {
CryptoAddress,
} from 'soapbox/types/soapbox';
const DEFAULT_COLORS = ImmutableMap<string, any>({
success: ImmutableMap({
50: '#f0fdf4',
100: '#dcfce7',
200: '#bbf7d0',
300: '#86efac',
400: '#4ade80',
500: '#22c55e',
600: '#16a34a',
700: '#15803d',
800: '#166534',
900: '#14532d',
}),
danger: ImmutableMap({
50: '#fef2f2',
100: '#fee2e2',
200: '#fecaca',
300: '#fca5a5',
400: '#f87171',
500: '#ef4444',
600: '#dc2626',
700: '#b91c1c',
800: '#991b1b',
900: '#7f1d1d',
}),
'sea-blue': '#2feecc',
});
const SEED_COLOR_DELTAS: Record<string, HSLDelta> = {
gray: [ 5.640168899709693, -71.25227844367963, 42.13578614093887 ],
primary: [ 0, 0, 0 ],
secondary: [ -264.41907148380454, 3.47034415017923, 16.72498531826924 ],
success: [ -132.16963284744344, 22.729414313887375, 27.93609003256976 ],
danger: [ -254.94219506629216, -0.22042936848366423, 12.675221477923778 ],
};
const PALETTE_DELTAS: Record<string, HSLPaletteDelta> = {
gray: {
'50': [ 0.22937683270356501, -1.5864743831017971, 15.567851989991112 ],
'100': [ 34.311838173845615, -1.2141376862436761, 13.938662622153828 ],
'200': [ -6.726925619450583, -1.3474032449266322, 12.477917572764795 ],
'300': [ -6.803051539716591, -1.1039007790831639, 9.366828937389187 ],
'400': [ 7.251025534431051, -0.7564434934117528, 6.308652406934627 ],
'500': [ 0, 0, 0 ],
'600': [ 0.528273854081192, 5.024155622767128, -28.942510078414657 ],
'700': [ 0.753585022750201, 9.244324145778112, -42.27720854061048 ],
'800': [ -3.38885080806142, 38.377108591613, -72.05591766747844 ],
'900': [ -3.6531980550540197, 49.0899586185372, -77.8873736703074 ],
},
primary: {
'50': [ -12.328127784192901, -67.24292824683431, 53.24283923089697 ],
'100': [ -11.411261541571491, -62.439158441446395, 49.48672631248416 ],
'200': [ -9.416727076537427, -53.876459261840154, 42.38848417160097 ],
'300': [ -3.767637832203661, -27.858121091970574, 19.530562906640746 ],
'400': [ -1.6746167286863738, -15.676431605871016, 7.808121295055564 ],
'500': [ 0, 0, 0 ],
'600': [ -0.003909458369605545, 2.5576939049011287, -7.724998167194286 ],
'700': [ 0.11738125411591227, -5.255248276711185, -21.911667783093854 ],
'800': [ 0.1328770612169592, -9.311121602273253, -29.182323283856825 ],
'900': [ 1.9869708446556729, -22.162319825142433, -35.75158752936853 ],
},
// @ts-ignore
secondary: {
'100': [ 347.880182793043, -68.47214895215055, 32.867057572173536 ],
'200': [ 350.05034589809117, -52.65112125949534, 21.087557709426832 ],
'300': [ 354.031215223099, -23.94959581070235, 7.6397685025461755 ],
'400': [ -1.739008662838304, -13.175216574546795, 3.6494520365984613 ],
'500': [ 0, 0, 0 ],
'600': [ 0.27516723567895696, -0.06197773826696107, -0.02163844988602648 ],
},
success: {
'50': [ 6.272318640784931, -87.82006472610146, 27.923961374235645 ],
'100': [ 7.515812986311346, -76.24726912121697, 26.015113902196305 ],
'200': [ 6.682412508442582, -58.98301894933322, 22.183456696291486 ],
'300': [ 5.042191328515884, -32.36248565174746, 16.65634548562005 ],
'400': [ 2.176996549793728, -9.407758928036145, 8.956568283122635 ],
'500': [ 0, 0, 0 ],
'600': [ -0.372610730302938, 1.0253878418950535, -11.388518702503106 ],
'700': [ 0.5145707207430519, -0.9085081381356162, -23.343759036504466 ],
'800': [ 1.8284285283559427, -4.840289897227791, -32.88658597113674 ],
'900': [ 3.143760008542955, -7.518152045294627, -39.533036861110965 ],
},
danger: {
'50': [ -0.12913498368561527, -70.44886946123935, 41.43265384536481 ],
'100': [ -0.0488135435926349, -67.15618734554207, 37.13987057891991 ],
'200': [ -0.02122364058786097, -61.17995574356704, 30.822488851023962 ],
'300': [ -0.008445121602528971, -49.40898982365067, 21.29509643080908 ],
'400': [ -0.0021433776363899426, -25.474902071013723, 9.156088139819957 ],
'500': [ 0, 0, 0 ],
'600': [ 0.0006646718484049075, 14.619377779600981, -7.0385269603369665 ],
'700': [ 0.0007147927161721412, 15.964147857357688, -15.008669041883302 ],
'800': [ 0.0005467342060985203, 11.610574809571574, -21.760068390290982 ],
'900': [ 0.00015621259826481548, 2.9773216728644343, -26.91230815141926 ],
},
};
export const PromoPanelItemRecord = ImmutableRecord({
icon: '',
@ -147,8 +188,23 @@ const normalizeAccentColor = (soapboxConfig: SoapboxConfigMap): SoapboxConfigMap
};
const normalizeColors = (soapboxConfig: SoapboxConfigMap): SoapboxConfigMap => {
const colors = DEFAULT_COLORS.mergeDeep(soapboxConfig.get('colors'));
return toTailwind(soapboxConfig.set('colors', colors));
const hsluv = convert(soapboxConfig.get('brandColor') || '#0482d8').hsluv;
const brandColor = { h: hsluv.hu, s: hsluv.s, l: hsluv.l };
const colors = Object.keys(SEED_COLOR_DELTAS).reduce((acc, curr) => {
const seed = hslShift(brandColor, SEED_COLOR_DELTAS[curr]);
const hslPalette = generatePalette(seed, PALETTE_DELTAS[curr]);
const hexColors = Object.keys(hslPalette).reduce((acc, curr) => {
const hsl = (hslPalette as any)[curr as any];
const hex = convert({ hu: hsl.h, s: hsl.s, l: hsl.l }).hex;
acc[curr] = hex;
return acc;
}, {} as any);
acc[curr] = hexColors;
return acc;
}, {} as any);
return soapboxConfig.set('colors', fromJS(colors));
};
const maybeAddMissingColors = (soapboxConfig: SoapboxConfigMap): SoapboxConfigMap => {
@ -156,7 +212,7 @@ const maybeAddMissingColors = (soapboxConfig: SoapboxConfigMap): SoapboxConfigMa
const missing = ImmutableMap({
'gradient-start': colors.getIn(['primary', '500']),
'gradient-end': colors.getIn(['accent', '500']),
'gradient-end': colors.getIn(['primary', '500']),
'accent-blue': colors.getIn(['primary', '600']),
});

Wyświetl plik

@ -0,0 +1,88 @@
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 });
expect(hslShift({ h: 50, s: 50, l: 50 }, [-360, 10, 0]))
.toEqual({ h: 50, s: 60, l: 50 });
expect(hslShift({ h: 50, s: 50, l: 50 }, [200, 10, 0]))
.toEqual({ h: 250, s: 60, l: 50 });
});
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,84 @@
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 => {
return (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] = [
color.h - seed.h,
minmax(color.s - seed.s, -100, 100),
minmax(color.l - seed.l, -100, 100),
];
return result;
}, {} as HSLPaletteDelta);
};
export {
hslShift,
generatePalette,
expandPalette,
paletteToDelta,
HSLDelta,
HSLPaletteDelta,
};

Wyświetl plik

@ -1,3 +1,4 @@
import { convert } from 'chromatism';
import { Map as ImmutableMap, fromJS } from 'immutable';
import tintify from 'soapbox/utils/colors';
@ -54,3 +55,11 @@ export const toTailwind = (soapboxConfig: SoapboxConfig): SoapboxConfig => {
return soapboxConfig.set('colors', legacyColors.mergeDeep(colors));
};
export const convertPalette = (palette: TailwindColorPalette, type: string) => {
return Object.keys(palette).reduce((acc, curr) => {
// @ts-ignore
acc[curr] = convert(palette[curr])[type];
return acc;
}, {} as any);
};

Wyświetl plik

@ -105,6 +105,7 @@
"bowser": "^2.11.0",
"browserslist": "^4.16.6",
"cheerio": "^1.0.0-rc.10",
"chromatism": "^3.0.0",
"clsx": "^1.2.1",
"copy-webpack-plugin": "^9.0.1",
"core-js": "^3.15.2",

Wyświetl plik

@ -4141,6 +4141,11 @@ chokidar@^3.5.2, chokidar@^3.5.3:
optionalDependencies:
fsevents "~2.3.2"
chromatism@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/chromatism/-/chromatism-3.0.0.tgz#a7249d353c1e4f3577e444ac41171c4e2e624b12"
integrity sha512-slVGC45odKFB6KzD/hpXP8XgS/Y+x72X1ckAhxU/9YZecCy8VwCJUSZsn0O4gQUwaTogun6IfrSiK3YuQaADFw==
chrome-trace-event@^1.0.2:
version "1.0.3"
resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz#1015eced4741e15d06664a957dbbf50d041e26ac"