Skip to content

Instantly share code, notes, and snippets.

@sarahbethfederman
Last active April 21, 2021 10:43
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sarahbethfederman/3c0ae82fff88fd2f40a759edd8e12566 to your computer and use it in GitHub Desktop.
Save sarahbethfederman/3c0ae82fff88fd2f40a759edd8e12566 to your computer and use it in GitHub Desktop.
// // LUI Next Colors
// // LUI Now colors live in @lendi-ui/color package
import hexToRgba from 'hex-to-rgba';
export const isHex = (str: string) => str.match(/^#(?:[0-9a-fA-F]{3}){1,2}$/);
type Shade = Partial<950 | 900 | 800 | 700 | 600 | 500 | 400 | 300 | 200 | 100 | 50 | 25 | 0>;
type Format = 'hexadecimal' | 'rgba';
type Category = 'primary' | 'secondary' | 'success' | 'error' | 'warning' | 'info' | 'shade' | 'focus';
type ColorObject = Partial<{ [key in Shade]: { value: string; format: Format } }>;
type ColorDictionary = Partial<
{
[key in Category]: ColorObject & { default?: Shade };
}
>;
const primary = {
900: { value: '#003248', format: 'hexadecimal' },
800: { value: '#004760', format: 'hexadecimal' },
700: { value: '#005A76', format: 'hexadecimal' },
600: { value: '#006E8C', format: 'hexadecimal' },
500: { value: '#1B7C9E', format: 'hexadecimal' },
400: { value: '#4490B0', format: 'hexadecimal' },
300: { value: '#61A5C3', format: 'hexadecimal' },
200: { value: '#86C1DB', format: 'hexadecimal' },
100: { value: '#A7DCF3', format: 'hexadecimal' },
50: { value: '#CBF3FF', format: 'hexadecimal' },
default: 600,
} as const;
const secondary = {
900: { value: '#0C5444', format: 'hexadecimal' },
800: { value: '#127061', format: 'hexadecimal' },
700: { value: '#16806F', format: 'hexadecimal' },
600: { value: '#199180', format: 'hexadecimal' },
500: { value: '#1C9E8D', format: 'hexadecimal' },
400: { value: '#33AD9E', format: 'hexadecimal' },
300: { value: '#55BCAF', format: 'hexadecimal' },
200: { value: '#85D0C6', format: 'hexadecimal' },
100: { value: '#B5E2DC', format: 'hexadecimal' },
50: { value: '#E1F3F2', format: 'hexadecimal' },
default: 500,
} as const;
const success = {
900: { value: '#005B0E', format: 'hexadecimal' },
800: { value: '#007A23', format: 'hexadecimal' },
700: { value: '#148B2D', format: 'hexadecimal' },
600: { value: '#229D38', format: 'hexadecimal' },
500: { value: '#2CAC41', format: 'hexadecimal' },
400: { value: '#51B85E', format: 'hexadecimal' },
300: { value: '#72C57B', format: 'hexadecimal' },
200: { value: '#9CD4A1', format: 'hexadecimal' },
100: { value: '#C3E5C5', format: 'hexadecimal' },
50: { value: '#E6F5E7', format: 'hexadecimal' },
default: 800,
} as const;
const error = {
900: { value: '#C80013', format: 'hexadecimal' },
800: { value: '#D60022', format: 'hexadecimal' },
700: { value: '#E3002A', format: 'hexadecimal' },
600: { value: '#F51830', format: 'hexadecimal' },
500: { value: '#FF282F', format: 'hexadecimal' },
400: { value: '#FE434D', format: 'hexadecimal' },
300: { value: '#F26B71', format: 'hexadecimal' },
200: { value: '#F99699', format: 'hexadecimal' },
100: { value: '#FFCBD3', format: 'hexadecimal' },
50: { value: '#FFEAEE', format: 'hexadecimal' },
0: { value: 'asdf', format: 'hexadecimal' },
default: 700,
} as const;
const warning = {
900: { value: '#AB3B00', format: 'hexadecimal' },
800: { value: '#C44800', format: 'hexadecimal' },
700: { value: '#D24F02', format: 'hexadecimal' },
600: { value: '#DF5607', format: 'hexadecimal' },
500: { value: '#E95C0B', format: 'hexadecimal' },
400: { value: '#EC7136', format: 'hexadecimal' },
300: { value: '#F0895B', format: 'hexadecimal' },
200: { value: '#F4AA8A', format: 'hexadecimal' },
100: { value: '#F8CBB8', format: 'hexadecimal' },
50: { value: '#F9E8E5', format: 'hexadecimal' },
default: 800,
} as const;
const info = {
900: { value: '#0842B2', format: 'hexadecimal' },
800: { value: '#0062D1', format: 'hexadecimal' },
700: { value: '#0073E3', format: 'hexadecimal' },
600: { value: '#0086F7', format: 'hexadecimal' },
500: { value: '#0094FF', format: 'hexadecimal' },
400: { value: '#2BA4FF', format: 'hexadecimal' },
300: { value: '#58B4FF', format: 'hexadecimal' },
200: { value: '#8BC9FF', format: 'hexadecimal' },
100: { value: '#B9DEFF', format: 'hexadecimal' },
50: { value: '#E2F2FF', format: 'hexadecimal' },
default: 800,
} as const;
const shade = {
950: { value: '#0B0B0B', format: 'hexadecimal' },
900: { value: '#171717', format: 'hexadecimal' },
800: { value: '#2F2F2F', format: 'hexadecimal' },
700: { value: '#474747', format: 'hexadecimal' },
600: { value: '#5F5F5F', format: 'hexadecimal' },
500: { value: '#777777', format: 'hexadecimal' },
400: { value: '#929292', format: 'hexadecimal' },
300: { value: '#ADADAD', format: 'hexadecimal' },
200: { value: '#C8C8C8', format: 'hexadecimal' },
100: { value: '#E3E3E3', format: 'hexadecimal' },
50: { value: '#F1F1F1', format: 'hexadecimal' },
25: { value: '#F8F8F8', format: 'hexadecimal' },
0: { value: '#FFFFFF', format: 'hexadecimal' },
} as const;
const focus = {
500: { value: '#006699', format: 'hexadecimal' },
default: 500,
} as const;
const colors: Readonly<ColorDictionary> = {
primary,
secondary,
success,
error,
warning,
info,
shade,
focus,
};
type PrimaryHex = typeof primary[Exclude<keyof typeof primary, 'default'>]['value'];
type SecondaryHex = typeof secondary[Exclude<keyof typeof secondary, 'default'>]['value'];
type SuccessHex = typeof success[Exclude<keyof typeof success, 'default'>]['value'];
type ErrorHex = typeof error[Exclude<keyof typeof error, 'default'>]['value'];
type WarningHex = typeof warning[Exclude<keyof typeof warning, 'default'>]['value'];
type InfoHex = typeof info[Exclude<keyof typeof info, 'default'>]['value'];
type ShadeHex = typeof shade[Exclude<keyof typeof shade, 'default'>]['value'];
type FocusHex = typeof focus[Exclude<keyof typeof focus, 'default'>]['value'];
/**
* This is all possible colours in hex - to get a colour, we recommend using the getColor() function from @lendi-ui/commons/color
*/
export type ValidHex = PrimaryHex | SecondaryHex | SuccessHex | ErrorHex | WarningHex | InfoHex | ShadeHex | FocusHex;
/*
This creates a type that is limited to only the shades that exist on a given color
This means you can't ask for a shade that doesn't exist
ex: getColor('primary', 25) will error, even though 25 is a valid Shade, primary doesn't define one
*/
type Shades<T> = Exclude<keyof T, 'default'>;
type PossibleShades<T extends Category> = T extends 'primary'
? Shades<typeof primary>
: T extends 'secondary'
? Shades<typeof secondary>
: T extends 'success'
? Shades<typeof success>
: T extends 'error'
? Shades<typeof error>
: T extends 'warning'
? Shades<typeof warning>
: T extends 'info'
? Shades<typeof info>
: T extends 'shade'
? Shades<typeof shade>
: T extends 'focus'
? Shades<typeof focus>
: Shade;
/*
Given a category and a shade, this limits the return type to the appropriate hex code.
If the shade is undefined and the shade has a default, we return the default hex.
If requested shade doesn't exist or we pass undefined and the shade has no default, then we return undefined (will error in runtime).
*/
export type PrimaryReturn<T extends PossibleShades<'primary'>> = typeof primary[T]['value'];
export type SecondaryReturn<T extends PossibleShades<'secondary'>> = typeof secondary[T]['value'];
export type SuccessReturn<T extends PossibleShades<'success'>> = typeof success[T]['value'];
export type ErrorReturn<T extends PossibleShades<'error'>> = typeof error[T]['value'];
export type WarningReturn<T extends PossibleShades<'warning'>> = typeof warning[T]['value'];
export type InfoReturn<T extends PossibleShades<'info'>> = typeof info[T]['value'];
export type ShadeReturn<T extends PossibleShades<'shade'>> = typeof shade[T]['value'];
export type FocusReturn<T extends PossibleShades<'focus'>> = typeof focus[T]['value'];
export type PossibleReturn<T, K> = T extends 'primary'
? K extends PossibleShades<'primary'>
? PrimaryReturn<K>
: K extends undefined
? typeof primary[typeof primary['default']]['value']
: undefined
: T extends 'secondary'
? K extends PossibleShades<'secondary'>
? SecondaryReturn<K>
: K extends undefined
? typeof secondary[typeof secondary['default']]['value']
: undefined
: T extends 'success'
? K extends PossibleShades<'success'>
? SuccessReturn<K>
: K extends undefined
? typeof success[typeof success['default']]['value']
: undefined
: T extends 'error'
? K extends PossibleShades<'error'>
? ErrorReturn<K>
: K extends undefined
? typeof error[typeof error['default']]['value']
: undefined
: T extends 'warning'
? K extends PossibleShades<'warning'>
? WarningReturn<K>
: K extends undefined
? typeof warning[typeof warning['default']]['value']
: undefined
: T extends 'info'
? K extends PossibleShades<'info'>
? InfoReturn<K>
: K extends undefined
? typeof info[typeof info['default']]['value']
: undefined
: T extends 'shade'
? K extends PossibleShades<'shade'>
? ShadeReturn<K>
: undefined
: T extends 'focus'
? K extends PossibleShades<'focus'>
? FocusReturn<K>
: K extends undefined
? FocusHex
: undefined
: ValidHex;
/*
We use an overload here so we can limit the return types more based on the parameters:
- if we pass in a category, we return the valid hexes for the category or undefined
- if we pass in a category and shade, we return the correct hex code if possible
- if we pass in a format and we ask for 'hexadecimal', we also return the correct hex code if possible
- if we pass in any other format, we return a type of string or undefined (i.e. an rgba color - we can't type these)
Note: in the future, we should add a build step that generates the rgba strings so we can type them
*/
function getColor<T extends Category>(category: T): PossibleReturn<T, undefined>;
function getColor<T extends Category, K extends PossibleShades<T>>(category: T, shade?: K): PossibleReturn<T, K>;
function getColor<T extends Category, K extends PossibleShades<T>, U extends Format>(
category: T,
shade: K,
format?: U
): U extends 'hexadecimal' ? PossibleReturn<T, K> : string | undefined;
function getColor(category: Category, shade?: Shade, format?: Format): string | undefined {
const colorShade = shade !== undefined ? shade : colors?.[category]?.default;
const colorStr = colors?.[category]?.[colorShade as Shade]?.value;
// if we recieved a hex string
if (typeof colorStr === 'string' && format === 'rgba') {
return hexToRgba(colorStr);
}
if (colorStr === undefined) {
throw Error('Color does not exist!');
}
return colorStr;
}
export const getColour = getColor; // For the queen!
export default getColor;
// const color1 = getColor('primary'); // "#1B7C9E" (default 500)
// const color2 = getColor('primary', 500); // "#1B7C9E"
// const color3 = getColor('primary', 500, 'rgba'); // string | undefined
// const color4 = getColor('primary', 500, 'hexadecimal'); // "#1B7C9E"
// const color5 = getColor('primary', 25); // TS error (invalid shade)
// const color6 = getColor('shade'); // undefined, but will error because shade has no default
// const color7 = getColor('focus'); // "#006699" (default 500)
// const color8 = getColor('focus', 500); // "#006699"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment