Skip to content

Commit ffe9925

Browse files
committed
Publish draft of color resolver
1 parent a5cc891 commit ffe9925

File tree

8 files changed

+183
-19
lines changed

8 files changed

+183
-19
lines changed

src/Input.tsx

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,20 +17,13 @@ export type InputProps = {
1717
classes?: Partial<
1818
Record<"root" | "label" | "description" | "nativeInputOrTextArea" | "message", string>
1919
>;
20-
} & (InputProps.WithSpecialState | InputProps.WithoutSpecialState) &
21-
(InputProps.WithoutTextArea | InputProps.WithTextArea);
20+
/** Default: "default" */
21+
state?: "success" | "error" | "default";
22+
/** The message won't be displayed if state is "default" */
23+
stateRelatedMessage?: ReactNode;
24+
} & (InputProps.WithoutTextArea | InputProps.WithTextArea);
2225

2326
export namespace InputProps {
24-
export type WithSpecialState = {
25-
state: "success" | "error";
26-
stateRelatedMessage: ReactNode;
27-
};
28-
29-
export type WithoutSpecialState = {
30-
state?: "default";
31-
stateRelatedMessage?: never;
32-
};
33-
3427
export type WithoutTextArea = {
3528
/** Default: false */
3629
textArea?: false;

src/fr/colorResolver.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { colorOptions, colorDecisions } from "./generatedFromCss/colorResolution";
22
import { exclude } from "tsafe/exclude";
3+
import { threeDigitColorHexToSixDigitsColorHex } from "../tools/threeDigitColorHexToSixDigitsColorHex";
34

4-
type ColorDecision = {
5+
export type ColorDecision = {
56
cssVarName: `--${string}`;
67
decisionObjectPath: readonly string[];
78
option: {
@@ -12,9 +13,16 @@ type ColorDecision = {
1213
};
1314

1415
export function resolveColorHexCodeToDecision(params: {
15-
hexColorCode: `#${string}`;
16+
/** Expects: #xxxxxx or #xxx */
17+
hexColorCode: string;
1618
}): ColorDecision[] {
17-
const { hexColorCode } = params;
19+
let { hexColorCode } = params;
20+
21+
hexColorCode = hexColorCode.toLowerCase();
22+
23+
if (hexColorCode.length === 4) {
24+
hexColorCode = threeDigitColorHexToSixDigitsColorHex(hexColorCode);
25+
}
1826

1927
const options = colorOptions
2028
.filter(({ color }) =>

src/scripts/build/cssToTs/colorOptions.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { createGetCssVariable } from "./cssVariable";
88
import memoize from "memoizee";
99
import * as crypto from "crypto";
1010
import { cssColorRegexp } from "../../tools/cssColorRegexp";
11+
import { threeDigitColorHexToSixDigitsColorHex } from "../../tools/threeDigitColorHexToSixDigitsColorHex";
1112

1213
export type ColorScheme = "light" | "dark";
1314

@@ -351,9 +352,9 @@ export const parseColorOptions = memoize((rawCssCode: string): ColorOption[] =>
351352
.map(({ property }: any) => {
352353
const cssVariableValue = getCssVariable(property);
353354

354-
const colorLight = cssVariableValue.root.light;
355+
const colorLight = toConsistentColor(cssVariableValue.root.light);
355356

356-
const colorDark = cssVariableValue.root.dark;
357+
const colorDark = toConsistentColor(cssVariableValue.root.dark);
357358

358359
assert(typeof colorDark === "string");
359360
assert(cssColorRegexp.test(colorDark), `${colorDark} doesn't seem to be a color`);
@@ -445,3 +446,17 @@ export function generateGetColorOptionsTsCode(rawCssCode: string) {
445446
`}`
446447
].join("\n");
447448
}
449+
450+
function toConsistentColor(color: string) {
451+
if (!color.startsWith("#")) {
452+
return color;
453+
}
454+
455+
color = color.toLowerCase();
456+
457+
if (color.length === 4) {
458+
threeDigitColorHexToSixDigitsColorHex(color);
459+
}
460+
461+
return color;
462+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
export function threeDigitColorHexToSixDigitsColorHex(threeDigitColorHex: string) {
2+
let v, w;
3+
v = parseInt(threeDigitColorHex, 16); // in rrggbb
4+
if (threeDigitColorHex.length == 3) {
5+
// nybble colors - fix to hex colors
6+
// 0x00000rgb -> 0x000r0g0b
7+
// 0x000r0g0b | 0x00r0g0b0 -> 0x00rrggbb
8+
w = ((v & 0xf00) << 8) | ((v & 0x0f0) << 4) | (v & 0x00f);
9+
v = w | (w << 4);
10+
}
11+
return v.toString(16).toUpperCase();
12+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
export function threeDigitColorHexToSixDigitsColorHex(threeDigitColorHex: string) {
2+
let v, w;
3+
v = parseInt(threeDigitColorHex, 16); // in rrggbb
4+
if (threeDigitColorHex.length == 3) {
5+
// nybble colors - fix to hex colors
6+
// 0x00000rgb -> 0x000r0g0b
7+
// 0x000r0g0b | 0x00r0g0b0 -> 0x00rrggbb
8+
w = ((v & 0xf00) << 8) | ((v & 0x0f0) << 4) | (v & 0x00f);
9+
v = w | (w << 4);
10+
}
11+
return v.toString(16).toUpperCase();
12+
}

stories/ColorResolver.tsx

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import React, { useState } from "react";
2+
import { Input } from "../dist/Input";
3+
import { resolveColorHexCodeToDecision } from "../dist/fr/colorResolver";
4+
import type { ColorDecision } from "../dist/fr/colorResolver";
5+
import { useColors } from "../dist/useColors";
6+
7+
export function ColorResolver() {
8+
const [hexColorCode, setHexColorCode] = useState("#161616");
9+
10+
const onChange = (event: React.ChangeEvent<HTMLInputElement>) =>
11+
setHexColorCode(event.target.value);
12+
13+
const state =
14+
hexColorCode === ""
15+
? undefined
16+
: /^#[0-9A-F]+/.test(hexColorCode.toUpperCase())
17+
? undefined
18+
: "error";
19+
20+
return (
21+
<div>
22+
<Input
23+
label="Hex color code"
24+
hintText="Color code, starting by #"
25+
nativeInputProps={{
26+
"value": hexColorCode,
27+
onChange
28+
}}
29+
state={state}
30+
stateRelatedMessage={state === "error" ? "This isn't a valid hex color" : undefined}
31+
/>
32+
{state === undefined &&
33+
resolveColorHexCodeToDecision({ hexColorCode }).map((colorDecision, i) => (
34+
<ColorDecisionShowcase {...colorDecision} key={i} />
35+
))}
36+
</div>
37+
);
38+
}
39+
40+
function ColorDecisionShowcase(props: ColorDecision) {
41+
const { cssVarName, decisionObjectPath, option } = props;
42+
43+
return (
44+
<div>
45+
<p>
46+
<span>CSS variable: </span>: {cssVarName}
47+
</p>
48+
<p>
49+
<span>Decision path: </span>{" "}
50+
<code>theme.decisions.{decisionObjectPath.join(".")}</code>
51+
</p>
52+
<p>Corresponding color option:</p>
53+
<p>
54+
<span>CSS variable: </span>: {option.cssVarName}
55+
</p>
56+
<p>
57+
<span>Option path: </span>{" "}
58+
<code>theme.options.{option.optionObjectPath.join(".")}</code>
59+
</p>
60+
61+
{typeof option.color === "string" ? (
62+
<p>
63+
<span>Colors: </span>: <ColoredSquare hexColorCode={option.color} />
64+
</p>
65+
) : (
66+
<p>
67+
<span>Colors: </span>:<span>Light: </span>
68+
<ColoredSquare hexColorCode={option.color.light} />
69+
<span>Dark: </span>
70+
<ColoredSquare hexColorCode={option.color.dark} />
71+
</p>
72+
)}
73+
</div>
74+
);
75+
}
76+
77+
function ColoredSquare(props: { hexColorCode: string }) {
78+
const { hexColorCode } = props;
79+
80+
const theme = useColors();
81+
82+
return (
83+
<>
84+
<span>{hexColorCode}</span>:
85+
<div
86+
style={{
87+
"borderWidth": "2",
88+
"borderStyle": "solid",
89+
"borderColor": theme.decisions.border.default.grey.default,
90+
"width": 30,
91+
"height": 30,
92+
"backgroundColor": hexColorCode
93+
}}
94+
/>
95+
</>
96+
);
97+
}

stories/Input.stories.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,16 @@ const { meta, getStory } = getStoryFactory({
3737
})(),
3838
"control": { "type": "radio" }
3939
},
40+
"stateRelatedMessage": {
41+
"description": `This message is only displayed when \`state\` is \`success\` or \`error\`.
42+
If state is \`success\` or \`error\` this message is mandatory.`
43+
},
4044
"textArea": {
4145
"control": { "type": "boolean" }
4246
},
4347
"nativeInputProps": {
4448
"description": `An object that is forwarded as props to te underlying native \`<input />\` element.
45-
This is where you pass the \`name\` prop or \`onKeyDown\` for example.`,
49+
This is where you pass the \`name\` prop or \`onChange\` for example.`,
4650
"control": { "type": null }
4751
},
4852
"nativeTextAreaProps": {
@@ -59,7 +63,7 @@ export const Default = getStory({
5963
"label": "Label champ de saisie",
6064
"hintText": "Texte de description",
6165
"state": "default",
62-
"stateRelatedMessage": "Text de validation / d'explication de l'erreur" as unknown as undefined,
66+
"stateRelatedMessage": "Text de validation / d'explication de l'erreur",
6367
"textArea": false
6468
});
6569

stories/colorHelper.stories.mdx

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { Meta } from "@storybook/addon-docs";
2+
import { useDarkMode } from "storybook-dark-mode";
3+
import { ColorResolver } from "./ColorResolver";
4+
5+
<Meta
6+
title="🎨 Color resolver"
7+
parameters={{
8+
"viewMode": "docs",
9+
"previewTabs": {
10+
"canvas": { "hidden": true },
11+
"zoom": { "hidden": true },
12+
"storybook/background": { "hidden": true },
13+
"storybook/viewport": { "hidden": true },
14+
},
15+
}}
16+
17+
18+
/>
19+
20+
<div style={{ "margin": "0 auto", "maxWidth": "600px" }}>
21+
<p>NOTE: This is a work in progress</p>
22+
<ColorResolver />
23+
</div>

0 commit comments

Comments
 (0)