Reputation: 24700
I have the following example code snippet:
type Color = string;
interface Props {
color: Color;
text: string;
}
function Badge(props: Props) {
return `<div style="color:${props.color}">${props.text}</div>`;
}
var badge = Badge({
color: '#F00',
text: 'Danger'
});
console.log(badge);
I'm trying to get a build error if the color is invalid, like so:
var badge = Badge({
color: 'rgba(100, 100, 100)',
text: 'Danger'
});
Is there a way to define Color
so that it allows only strings matching one of the following patterns?
#FFF
#FFFFFF
rgb(5, 5, 5)
rgba(5, 5, 5, 1)
hsa(5, 5, 5)
I realize that there are colors like red
and white
but that might make this harder to answer if Color
can accept those.
Upvotes: 58
Views: 80990
Reputation: 90277
You can use CSS module's internal API: CSS.supports('color', yourColor)
In more detail, write a validation function and define what you want to happen when the color fails validation:
const isValidCssColor = (s: string) => {
if (CSS.supports('color', s)) return true
const message = `${s} is not a valid CSS color`
throw new Error(message)
/* or, if you don't want to throw an error:
console.warn(message)
return false
// */
}
// tests
;[
'#f00',
'#F00F',
'#FF0000',
'#FF0000FF',
'rgb(255,0,0)',
'rgb(255 0 0)',
'rgba(255,0,0,1)',
'rgba(255, 0, 0, 1)',
'hsl(0 0 0)',
'hsl(0deg 100% 50%)',
'hwb(0 0 0)',
'hwb(0deg 0% 0%)',
'lch(54 106.85 40.86)',
'oklch(0.63 0.26 29.23)',
'lab(54 80.82 69.9)',
'oklab(0.63 0.22 0.13)',
'red',
'#i-am-not-a-css-color'
].forEach(isValidCssColor)
Usage:
if (isValidCssColor(myColor)) {
// this code only executes if `myColor` is a valid CSS color
}
See it working in TS playground.
Upvotes: 2
Reputation: 1
You can parse that the string contains only valid chars, like
type hexSymbol =
| "0"
| "1"
| "2"
| "3"
| "4"
| "5"
| "6"
| "7"
| "8"
| "9"
| "a"
| "b"
| "c"
| "d"
| "e"
| "f";
type SkipHashSymbol<T> = T extends `#${infer Rest}` ? Rest : T;
Then go recursively through your string
type isHexSymbolsString<T> = T extends `${hexSymbol}${infer Rest}`
? Rest extends ""
? true
: isHexSymbolsString<Rest>
: false;
type isHex<T extends string> = isHexSymbolsString<
SkipHashSymbol<T>
> extends true
? true
: false;
type valid = isHex<"#123abc">;
type invalid = isHex<"#qwe">;
Upvotes: 0
Reputation: 2352
Install color-types
can help you.
npm i -D color-types
Then add library to file tsconfig.json
{
"compilerOptions": {
"types": ["color-types"]
}
}
const color: ColorName = 'red';
const color: HEX = '#ff0000';
const color: RGB = 'rgb(255, 0, 0)';
const color: RGBA = 'rgb(255 0 0 / 100%)' | 'rgba(255, 0, 0, 1)';
const color: HSL = 'hsl(0deg 100% 50%)';
const color: HSLA = 'hsla(0rad, 100%, 50%, 1)';
const color: HWB = 'hwb(0turn 0% 0%)';
const color: RGBObject = { r: 255, g: 0, b: 0 } || { red: 255, green: 0, blue: 0 };
const color: RGBAObject = { r: 255, g: 0, b: 0, a: 1 } || { red: 255, green: 0, blue: 0, alpha: 1 };
const color: HSLObject = { h: 0, s: 100, l: 50 } || { hue: 0, saturation: 100, lightness: 50 };
const color: HSLAObject = { h: 0, s: 100, l: 50, a: 1 } || { hue: 0, saturation: 100, lightness: 50, alpha: 1 };
const color: HWBObject = { h: 0, w: 0, b: 0 } || { hue: 0, whiteness: 0, blackness: 0 } || { hue: 0, whiteness: 0, blackness: 0, aplha: 1 };
// All of type color
const color: Color = 'red' || '#ff0000' || 'rgb(255, 0, 0)';
Upvotes: 1
Reputation: 1387
My 2 cents:
type RGB = `rgb(${string})`
type RGBA = `rgba(${string})`
type HEX = `#${string}`
type HSL = `hsl(${string})`
type HSLA = `hsla(${string})`
type VAR = `var(${string})`
type CssGlobals = 'inherit' | 'initial' | 'revert' | 'unset'
export type CssColor =
| 'currentColor'
| 'transparent'
| RGB
| RGBA
| HEX
| HSL
| HSLA
| VAR
| CssGlobals
Upvotes: 8
Reputation: 2284
It's not an answer (see above for that) but here's something you might find useful:
type CssColorNames =
| 'AliceBlue'
| 'AntiqueWhite'
| 'Aqua'
| 'Aquamarine'
| 'Azure'
| 'Beige'
| 'Bisque'
| 'Black'
| 'BlanchedAlmond'
| 'Blue'
| 'BlueViolet'
| 'Brown'
| 'BurlyWood'
| 'CadetBlue'
| 'Chartreuse'
| 'Chocolate'
| 'Coral'
| 'CornflowerBlue'
| 'Cornsilk'
| 'Crimson'
| 'Cyan'
| 'DarkBlue'
| 'DarkCyan'
| 'DarkGoldenRod'
| 'DarkGray'
| 'DarkGrey'
| 'DarkGreen'
| 'DarkKhaki'
| 'DarkMagenta'
| 'DarkOliveGreen'
| 'DarkOrange'
| 'DarkOrchid'
| 'DarkRed'
| 'DarkSalmon'
| 'DarkSeaGreen'
| 'DarkSlateBlue'
| 'DarkSlateGray'
| 'DarkSlateGrey'
| 'DarkTurquoise'
| 'DarkViolet'
| 'DeepPink'
| 'DeepSkyBlue'
| 'DimGray'
| 'DimGrey'
| 'DodgerBlue'
| 'FireBrick'
| 'FloralWhite'
| 'ForestGreen'
| 'Fuchsia'
| 'Gainsboro'
| 'GhostWhite'
| 'Gold'
| 'GoldenRod'
| 'Gray'
| 'Grey'
| 'Green'
| 'GreenYellow'
| 'HoneyDew'
| 'HotPink'
| 'IndianRed'
| 'Indigo'
| 'Ivory'
| 'Khaki'
| 'Lavender'
| 'LavenderBlush'
| 'LawnGreen'
| 'LemonChiffon'
| 'LightBlue'
| 'LightCoral'
| 'LightCyan'
| 'LightGoldenRodYellow'
| 'LightGray'
| 'LightGrey'
| 'LightGreen'
| 'LightPink'
| 'LightSalmon'
| 'LightSeaGreen'
| 'LightSkyBlue'
| 'LightSlateGray'
| 'LightSlateGrey'
| 'LightSteelBlue'
| 'LightYellow'
| 'Lime'
| 'LimeGreen'
| 'Linen'
| 'Magenta'
| 'Maroon'
| 'MediumAquaMarine'
| 'MediumBlue'
| 'MediumOrchid'
| 'MediumPurple'
| 'MediumSeaGreen'
| 'MediumSlateBlue'
| 'MediumSpringGreen'
| 'MediumTurquoise'
| 'MediumVioletRed'
| 'MidnightBlue'
| 'MintCream'
| 'MistyRose'
| 'Moccasin'
| 'NavajoWhite'
| 'Navy'
| 'OldLace'
| 'Olive'
| 'OliveDrab'
| 'Orange'
| 'OrangeRed'
| 'Orchid'
| 'PaleGoldenRod'
| 'PaleGreen'
| 'PaleTurquoise'
| 'PaleVioletRed'
| 'PapayaWhip'
| 'PeachPuff'
| 'Peru'
| 'Pink'
| 'Plum'
| 'PowderBlue'
| 'Purple'
| 'RebeccaPurple'
| 'Red'
| 'RosyBrown'
| 'RoyalBlue'
| 'SaddleBrown'
| 'Salmon'
| 'SandyBrown'
| 'SeaGreen'
| 'SeaShell'
| 'Sienna'
| 'Silver'
| 'SkyBlue'
| 'SlateBlue'
| 'SlateGray'
| 'SlateGrey'
| 'Snow'
| 'SpringGreen'
| 'SteelBlue'
| 'Tan'
| 'Teal'
| 'Thistle'
| 'Tomato'
| 'Turquoise'
| 'Violet'
| 'Wheat'
| 'White'
| 'WhiteSmoke'
| 'Yellow'
| 'YellowGreen';
Upvotes: 3
Reputation: 123
A runtime check for color validity if (process.env.NODE_ENV === 'development')
would be the best suitable solution for now, since TypeScript (as of 4.8.4) cannot handle such complex types.
I tried making a HEX color type, which seems like it should work if you just read the code as a human, but actually it fails with anything more overloaded than a short format CSS HEX color (like #fff):
type HexLetter = 'a' | 'b' | 'c' | 'd' | 'e' | 'f';
type HexDigit = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9;
type HexPart = HexLetter | HexDigit;
type HexEncodedNumber = `${HexPart}${HexPart}`;
const hexEncodedNumberTestForSuccess: HexEncodedNumber = '0f';
const hexEncodedNumberTestForError: HexEncodedNumber = 'r3'; //* Type '"r3"' is not assignable to type 'HexEncodedNumber'.
// A single HexEncodedNumber works!
// This means we can have type safety defining a number from 0 to 255 in HEX format.
/** Length: 3 */
type HexCodeShort = `${HexPart}${HexPart}${HexPart}`;
/** Length: 6 */
type HexCodeClassic = `${HexEncodedNumber}${HexEncodedNumber}${HexEncodedNumber}`; //! Expression produces a union type that is too complex to represent. ts(2590)
// HexCodeClassic evaluates to 'any' type.
/** Length: 8 */
type HexCodeWithAlpha = `${HexCodeClassic}${HexEncodedNumber}`; // So this is `${any}aa` | `${any}ab` etc.
/** Length: 3 (short) | 6 (classic) | 8 (alpha) */
type HexCodeModern = HexCodeShort | HexCodeClassic | HexCodeWithAlpha; // Just 'any'.
type HexCodeCss = `#${HexCodeModern}`;
const hexCodeCssTestForEmergedAny: HexCodeCss = '#ICanSetAnythingHereWithNoTrouble'; // `#${any}`.
// Full CSS HEX code does not work.
So, you can only ensure full type safety for 3 characters of a CSS color, not even considering uppercase letters.
As a workaround, you can construct a function that accepts r, g, b, a
all of type HexEncodedNumber, and TypeScript will type-check the parameters, but the ReturnType<>
of that function will be just string
, or any
if you try to add as const
or even 'as `#${string}`' to the return statement.
The RGB situation should be around the same, as it's the same possible number of combinations.
Upvotes: 4
Reputation: 334
It looks like you're using React in your example. A simple option may be to import CSSProperties
and access the color
property.
import { CSSProperties } from "react";
const Entry = ({color}: {color: CSSProperties["color"]}) => {
return (
<div style={{color: color}}>
This text will be colored.
</div>
);
};
The benefit of this is it can easily be provided any form of color: named colors like yellow
, aliceblue
, or powderblue
, hex values, RGB, HSL, or any string that is valid CSS.
This is functionally the same as using string
but with a more expressive type usage. If you need to use type guarding, go with the first and accepted answer here.
Upvotes: 9
Reputation: 183
It is theoretically possible with union types, however if you JUST do hex codes, you have well over 16,000,000 possible combinations, and typescript (4.6.3 at least) does not like that idea.
It has been on the backburner for MS for over a year now https://github.com/microsoft/TypeScript/issues/42790
Upvotes: 4
Reputation: 1000
type RGB = `rgb(${number}, ${number}, ${number})`;
type RGBA = `rgba(${number}, ${number}, ${number}, ${number})`;
type HEX = `#${string}`;
type Color = RGB | RGBA | HEX;
`${...}` notation is available on new (^4.1) ts versions.
https://www.typescriptlang.org/docs/handbook/2/template-literal-types.html
Upvotes: 88
Reputation: 52193
You can't do this yet in a general sense, but you can use constants and string literal types if you have a well defined set of colors:
type Color = "#FFFFFF" | "#FF0000" | "#0000FF";
const WHITE: Color = "#FFFFFF";
const RED: Color = "#FF0000";
const BLUE: Color = "#0000FF";
Obviously, this won't be practical if you want to allow any color, but in reality you probably do want to have re-usable color variables anyway.
In my project I use a script to generate a similar file from my colors.css
file which defines a bunch of CSS properties:
:root {
--primary-red: #ff0000;
--secondary-red: #993333;
/* etc */
}
Which gets converted to:
export const primaryRed: Color = "#ff0000";
export const secondaryRed: Color = "#993333";
// etc
export type Color = "#ff0000" | "#993333" // | etc...
And I'd use it like:
import {primaryRed} from "./Colors.ts";
interface BadgeProps {
color: Color;
text: string;
}
var badge = Badge({
color: primaryRed,
text: 'Danger'
});
Upvotes: 9
Reputation: 175088
There was a proposal for a type of string which matches a pattern (regex or something else), but that proposal haven't come to fruition yet.
As a result, what you ask for is unfortunately impossible as of TypeScript 2.2.
Upvotes: 15