How to Type a Color Prop

How to type a color prop?

This one is pretty hard to encode in TypeScript's type system. I believe a full fledged parser can do a better job in both speed and accuracy.


Anyway, if you really want to get some typecheking for your color values from typescript then let's start with w3c color property description:

Values: <color value> | <color keyword> | currentColor | transparent | inherit

playground link for those who don't need explanations and what to look right into the code.


Well, color keyword, currentColor, transparent and inherit are pretty straightforward:

type Color = ColorValue | ColorKeyword | 'currentColor' | 'transparent' | 'inherit'

type ColorKeyword =
| "black"
| "silver"
| "gray"
...
| "rebeccapurple"

The tricky part is <color value>:

The color can be specified as

* a hexadecimal RGB value: #faf or #ffaaff
* a RGB value: rgb(255, 160, 255) or rgb(100%, 62.5%, 100%)
Each value is from 0 to 255, or from 0% to 100%.
* a RGBA value: rgba(255, 160, 255, 1) or rgba(100%, 62.5%, 100%, 1)
This variant includes an “alpha” component to allow
specification of the opacity of a color. Values are
in the range 0.0 (fully transparent) to 1.0 (fully opaque).
* a HSL value: hsl(0, 100%, 50%)
A triple (hue, saturation, lightness). hue is an
angle in degrees. saturation and lightness are
percentages (0-100%).
* a HSLA value: hsla(0, 100%, 50%, 1)
This variant includes an “alpha” component to allow
specification of the opacity of a color. Values are
in the range 0.0 (fully transparent) to 1.0 (fully opaque).

hexadecimal RGB value is still ok-ish:

type HexDigit = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | 'a' | 'b' | 'c' | 'd' | 'e' | 'f'

type Hex3 = `${HexDigit}${HexDigit}${HexDigit}`

type RGBColor<T extends string> =
Lowercase<T> extends `#${Hex3}`
? T
: Lowercase<T> extends `#${Hex3}${infer Rest}`
? Rest extends Hex3
? T
: never
: never

We have to introduce type variable T. Otherwise 'flat' union type:

type RGBColor = `#${Hex3}` | `#${Hex3}${Hex3}`

is going to consist of 16^3 + 16^6 constituents that's far beyound 100000 typescript limit for union types.

Let's introduce some helper types to work with numbers. We have to check the percents are not greater than 100% and end with % character.

type DecDigit = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'
type Digits0to4 = '0' | '1' | '2' | '3' | '4'

type OnlyDecDigits<T extends string> =
T extends `${DecDigit}${infer Rest}`
? Rest extends ''
? 1
: OnlyDecDigits<Rest>
: never

type IsDecNumber<T extends string> =
T extends `${infer Integer}.${infer Fractional}`
? Integer extends ''
? OnlyDecDigits<Fractional>
: Fractional extends ''
? OnlyDecDigits<Integer>
: OnlyDecDigits<Integer> & OnlyDecDigits<Fractional>
: OnlyDecDigits<T>

type IntegerPart<T extends string> =
T extends `${infer I}.${infer F}`
? I
: T

type IsInteger<T extends string> =
1 extends IsDecNumber<T>
? T extends IntegerPart<T>
? 1
: never
: never

type Less100<T extends string> =
IsDecNumber<T> extends 1
? IntegerPart<T> extends `${DecDigit}` | `${DecDigit}${DecDigit}` | '100'
? 1
: never
: never

type IsPercent<T extends string> =
'0' extends T
? 1
: T extends `${infer P}%`
? Less100<P>
: never

Also color values must be integers and not greater than 255:

type Color255<T extends string> =
1 extends IsInteger<T>
? T extends `${DecDigit}`
| `${DecDigit}${DecDigit}`
| `1${DecDigit}${DecDigit}`
| `2${Digits0to4}${DecDigit}`
| `25${Digits0to4 | '5'}`
? 1
: never
: never

so, any color value can be encoded as an integer number in [0..255] range or a percent:

type IsColorValue<T extends string> = IsPercent<T> | Color255<T>

Adding utility Trim type to trim extra spaces on both ends:

type WhiteSpace = ' '
type Trim<T> = T extends `${WhiteSpace}${infer U}`
? Trim<U>
: T extends `${infer U}${WhiteSpace}`
? Trim<U>
: T;

That's enough for rgb:

type RGB<T extends string> = 
T extends `rgb(${infer R},${infer G},${infer B})`
? '111' extends `${IsColorValue<Trim<R>>}${IsColorValue<Trim<G>>}${IsColorValue<Trim<B>>}`
? T
: never
: never

For rgba/hsla we'll need opacity. Here we just ask for any valid number or a percent:

type Opacity<T extends string> = IsDecNumber<T> | IsPercent<T>

Now we can check rgba values:

type RGBA<T extends string> =
T extends `rgba(${infer R},${infer G},${infer B},${infer O})`
? '1111' extends `${IsColorValue<Trim<R>>}${IsColorValue<Trim<G>>}${IsColorValue<Trim<B>>}${Opacity<Trim<O>>}`
? T
: never
: never

Adding degree checker for hsl/hsla:

type Degree<T extends string> =
1 extends IsInteger<T>
? T extends `${DecDigit}`
| `${DecDigit}${DecDigit}`
| `${'1' | '2'}${DecDigit}${DecDigit}`
| `3${Digits0to4 | '5'}${DecDigit}`
| '360'
? 1
: never
: never

and finally we can cover the last cases:

type HSL<T extends string> =
T extends `hsl(${infer H},${infer S},${infer L})`
? `111` extends `${Degree<Trim<H>>}${IsPercent<Trim<S>>}${IsPercent<Trim<L>>}`
? T
: never
:never

type HSLA<T extends string> =
T extends `hsla(${infer H},${infer S},${infer L},${infer O})`
? `1111` extends `${Degree<Trim<H>>}${IsPercent<Trim<S>>}${IsPercent<Trim<L>>}${Opacity<Trim<O>>}`
? T
: never
:never

So our final type will look like that:

type ColorValue<T extends string> = HexColor<T> | RGB<T> | RGBA<T> | HSL<T> | HSLA<T>

type Color<T extends string> = ColorValue<T> | ColorKeyword | 'currentColor' | 'transparent' | 'inherit'

playground link

How to pass props to react component to change text color

It will work if you change your h3 className to template string:

<h3 className={`${styles.h3} ${props.lightText ? styles.lightText : styles.darkText}`}>{props.text}</h3>

or at least last part of it:

<h3 className={styles.h3 + " " + `${props.lightText ? styles.lightText : styles.darkText}`}>{props.text}</h3>

Here you can find a lot of great examples how to add multiple classnames: How to add multiple classes to a ReactJS Component?

React props for passing color

The issue is here:

<div className="colors" style={{backgroundColor: {props.color}}}></div>

You need to do like this(remove extra currly braces):

<div className="colors" style={{backgroundColor: props.color}}></div>

react I want to change the color with the value passed by props

Assuming that the Box component takes a colour as its colorScheme prop, simply define a mapping from the backgroundColor to the colour you want to apply.

const COLORS: Record<Props['backgroundColor'], string> = {
primary: "blue",
secondary: "green",
brand: "yellow"
};

export const Button: FunctionComponent<Props> = ({ backgroundColor, size, children }) => {
return (
<Box colorScheme={COLORS[backgroundColor]} size={size}>
{children}
</Box>
);
}
};

How to have custom colors as props with mui v5 react typescript?

Add appcolor as a possilbe type in your ButtonPropsType.ts

type ButtonPropsType = {
color?:
| "inherit"
| "primary"
| "secondary"
| "success"
| "error"
| "info"
| "warning"
| "appcolor"; //<-- Here
disabled?: boolean;
variant?: "text" | "outlined" | "contained";
};

Alternatively if you want to dynamically add options as needed, mui has an interface for that.

ButtonPropsTypes.ts

import { ButtonProps } from "@mui/material";

type ButtonPropsType = {
color?: ButtonProps["color"];
disabled?: boolean;
variant?: "text" | "outlined" | "contained";
};

export default ButtonPropsType;

createPalette.d.ts

import * as createPalette from "@mui/material/styles/createPalette";

declare module "@mui/material/styles/createPalette" {
interface PaletteOptions {
appcolor?: PaletteColorOptions;
}
interface Palette {
appcolor: PaletteColor;
}
}

declare module "@mui/material" {
interface ButtonPropsColorOverrides {
appcolor;
}
}


Related Topics



Leave a reply



Submit