Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to create standalone type for a HEX color string?

I'm trying to create a standalone type in TypeScript that can be used to represent a single valid HEX color code as a fully type-safe string.

My attempt is below, which falls short due to not actually being a standalone type, which is what I would hope to achieve.

type HexDigit<T extends '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | 'a' | 'b' | 'c' | 'd' | 'e'| 'f' | 'A' | 'B' | 'C' | 'D' | 'E'| 'F'> = T;
type HexColor<T extends string> =
    T extends `#${HexDigit<infer D1>}${HexDigit<infer D2>}${HexDigit<infer D3>}${HexDigit<infer D4>}${HexDigit<infer D5>}${HexDigit<infer D5>}`
        ? T
        : (
            T extends `#${HexDigit<infer D1>}${HexDigit<infer D2>}${HexDigit<infer D3>}`
            ? T
            : never
        );

function hex<T extends string>(s: HexColor<T>): T {
    return s;
}

// All valid
hex('#ffffff');
hex('#fff');
hex('#000');
hex('#123456');
hex('#abcdef');
hex('#012def');
hex('#ABCDEF');

TypeScript playground link

Trying to use a type with a generic as a standalone type was bound to fail, so I got stuck at this point.

// Ideal use case - does not compute
const color: HexColor = '#aaa';
const theme: Record<string, HexColor> = {
    backgroundColor: '#ff0000',
    color: '#0f0',
};

Is this even possible to achieve with TypeScript, and if so, how?

like image 567
SeinopSys Avatar asked Aug 13 '21 04:08

SeinopSys


People also ask

How do I create a hexadecimal color code?

Hex color codes start with a pound sign or hashtag (#) and are followed by six letters and/or numbers. The first two letters/numbers refer to red, the next two refer to green, and the last two refer to blue. The color values are defined in values between 00 and FF (instead of from 0 to 255 in RGB).

What format do hex Colours use?

A hex color code is a 6-symbol code made of up to three 2-symbol elements. Each of the 2-symbol elements expresses a color value from 0 to 255. The code is written using a formula that turns each value into a unique 2-digit alphanumeric code. For example, the RGB code (224, 105, 16) is E06910 in hexadecimal code.

How to get hex code of selected color using hex color generator?

Hex color generator gives the hex code of selected color. To select a color, we will use <input type=”color”> which creates a color picker. Get the value returned by color picker.

What is Hex and how do I use it?

For the visualizations where you are specifying a list of colors (i.e. multiple colors at once), this is where HEX comes into play. As the visualizations and charts universally can use HEX for custom coloring, the goal is to get a list of HEX codes in a palette.

Can displayr use hex codes for custom coloring?

As the visualizations and charts universally can use HEX for custom coloring, the goal is to get a list of HEX codes in a palette. The purpose of this post is two-fold: How you can set up a color palette for use in Displayr (with both RBG and HEX codes)

What is the difference between hex codes and RGB?

The visualizations in Displayr require you to use HEX codes when you want to customize the color palette. HEX codes are a six digit string of letters/characters that encode a specific color. RGB can encode colors too - they use a combination of three numbers separated by a comma (red,green,blue).


1 Answers

You probably already tried to do it the naive way, where you make a union of all the possibilites, and it does actually work for a three-digit hex color, but not for a longer one (see code below). I suspect, to answer your question directly, that it's impossible to make a 'standalone' type that works for all six-digit hex colors, because this would have to be a union of millions of elements.

type Digit = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | 'a' | 'b' | 'c' | 'd' | 'e'| 'f' | 'A' | 'B' | 'C' | 'D' | 'E' | 'F';
type ShortColor = `#${Digit}${Digit}${Digit}`;
type LongColor = `#${Digit}${Digit}${Digit}${Digit}${Digit}${Digit}`;  // Error
type Color = ShortColor | LongColor;

const someColor: ShortColor = '#fc2';
const badColor: ShortColor = '#cg2';  // Error

That said, I think your solution is a fine one itself. I did notice your code doesn't work on newer versions of TypeScript, so I made a slight modification to it so that it does work with the latest version, 4.4.0-beta. The code is a bit simpler and avoids generating too large of a union by doing the conditional check in two steps:

type HexDigit = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | 'a' | 'b' | 'c' | 'd' | 'e'| 'f' | 'A' | 'B' | 'C' | 'D' | 'E'| 'F';
type HexColor<T extends string> =
    T extends `#${HexDigit}${HexDigit}${HexDigit}${infer Rest1}`
        ? (Rest1 extends `` 
            ? T // three-digit hex color
            : (
                Rest1 extends `${HexDigit}${HexDigit}${HexDigit}`
                    ? T  // six-digit hex color
                    : never
            )
        )
        : never;

function hex<T extends string>(s: HexColor<T>): T {
    return s;
}

// All valid
hex('#ffffff');
hex('#fff');
hex('#000');
hex('#123456');
hex('#abcdef');
hex('#012def');
hex('#ABCDEF');

Finally, I might suggest that you use type brands to create a type which signifies that a string is a valid hex color, and only use your hex function to create strings of that type? In the example below, you could simply use Color throughout your codebase and only need to rely on the hex helper function when specifically making a Color from a string literal for the first time. I don't know if this'll meet your exact ideal usecase, because you do still need to use your hex helper wherever a literal is declared, but it gets you pretty close:

type Color = string & { __type: "HexColor" };
function hex<T extends string>(s: HexColor<T>): Color {
    return s;
}

const color: Color = hex('#aaa');
const theme: Record<string, Color> = {
    backgroundColor: hex('#ff0000'),
    color: hex('#0f0'),
};
like image 185
JKillian Avatar answered Oct 19 '22 04:10

JKillian