Last active
April 21, 2021 10:43
-
-
Save sarahbethfederman/3c0ae82fff88fd2f40a759edd8e12566 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// // 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