I understand that it might be toooo much, but you can try this:
type ComputeRange<
N extends number,
Result extends Array<unknown> = [],
> =
(Result['length'] extends N
? Result[number]
: ComputeRange<N, [...Result, Result['length']]>
)
type HexNumber = `${ComputeRange<10>}`
type HexString = 'A' | 'B' | 'C' | 'D' | 'E' | 'F' | 'a' | 'b' | 'c' | 'd' | 'e' | 'f'
type Hex = `${HexNumber}` | HexString;
type StringLength<
Str extends string,
Acc extends string[] = []
> =
(Str extends `${infer S}${infer Rest}`
? StringLength<Rest, [...Acc, S]>
: Acc['length'])
type ValidateLength<
Str extends string,
Length extends number
> =
(StringLength<Str> extends Length
? Str
: never)
type ValidateHex<
Color extends string,
Cache extends string = '',
> =
Color extends `${infer A}${infer Rest}`
? (A extends ''
? Cache
: (A extends Hex
? ValidateHex<Rest, `${Cache}${A}`>
: never)
) : Cache
type ExtractHash<T extends string> = T extends `#${infer Rest}` ? Rest : never
type ValidateMap<T extends Record<string, string>> = {
[Prop in keyof T]: T[Prop] & ValidateHex<T[Prop]> & ValidateLength<T[Prop], 6 | 3>
}
type AddHash<T extends Record<string, string>> = { [Prop in keyof T]: `#${T[Prop]}` }
const color = <
Key extends string,
Value extends string,
ColorMap extends Record<Key, Value>
>(dictionary: AddHash<ValidateMap<ColorMap>>) => dictionary
const colorTheme = color({
white: '#ffffff',
gray: '#e2e2e2',
green: '#ffx' // expected error
})
colorTheme.gray // '#e2e2e2'
Playground
Pros:
TS will validate your hex value
Cons:
You need to create dummy runtime function for inference validation.
If you are interested in this solution I will provide some explanation.
Meanwhile, you can check my article and related answer