Skip to content

Commit aeac2e2

Browse files
committed
Feat. a function that resolves color hex code to DSFR palette decision
1 parent 6c02519 commit aeac2e2

File tree

7 files changed

+149
-5
lines changed

7 files changed

+149
-5
lines changed

src/fr/colorResolver.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { colorOptions, colorDecisions } from "./generatedFromCss/colorResolution";
2+
import { exclude } from "tsafe/exclude";
3+
4+
type ColorDecision = {
5+
cssVarName: `--${string}`;
6+
decisionObjectPath: readonly string[];
7+
option: {
8+
color: `#${string}` | { light: `#${string}`; dark: `#${string}` };
9+
optionObjectPath: readonly string[];
10+
cssVarName: `--${string}`;
11+
};
12+
};
13+
14+
export function resolveColorHexCodeToDecision(params: {
15+
hexColorCode: `#${string}`;
16+
}): ColorDecision[] {
17+
const { hexColorCode } = params;
18+
19+
const options = colorOptions
20+
.filter(({ color }) =>
21+
typeof color === "string"
22+
? color === hexColorCode
23+
: color.dark === hexColorCode || color.light === hexColorCode
24+
)
25+
.map((colorOption): ColorDecision["option"] => ({
26+
"color": colorOption.color,
27+
"optionObjectPath": colorOption.themePath,
28+
"cssVarName": colorOption.colorOptionName
29+
}));
30+
31+
return colorDecisions
32+
.map(colorDecision => {
33+
const option = options.find(
34+
({ optionObjectPath }) =>
35+
optionObjectPath.join(".") === colorDecision.optionThemePath.join(".")
36+
);
37+
return option === undefined ? undefined : ([colorDecision, option] as const);
38+
})
39+
.filter(exclude(undefined))
40+
.map(
41+
([{ colorDecisionName, themePath }, option]): ColorDecision => ({
42+
"cssVarName": colorDecisionName,
43+
"decisionObjectPath": themePath,
44+
option
45+
})
46+
);
47+
}

src/scripts/build/cssToTs/colorDecisions.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { is } from "tsafe/is";
99
import { parseColorOptionName, getThemePath as getColorOptionThemePath } from "./colorOptions";
1010
import * as crypto from "crypto";
1111
import { multiReplace } from "../../tools/multiReplace";
12+
import memoize from "memoizee";
1213

1314
const contexts = ["background", "text", "border", "artwork"] as const;
1415

@@ -159,11 +160,12 @@ export function getThemePath(parsedColorDecisionName: ParsedColorDecisionName) {
159160
}
160161

161162
export type ColorDecision = {
163+
colorDecisionName: `--${string}`;
162164
themePath: string[];
163165
optionThemePath: string[];
164166
};
165167

166-
export function parseColorDecision(rawCssCode: string): ColorDecision[] {
168+
export const parseColorDecision = memoize((rawCssCode: string): ColorDecision[] => {
167169
const { parseColorDecisionName } = createParseColorDecisionName(rawCssCode);
168170

169171
const parsedCss = parseCss(rawCssCode);
@@ -179,13 +181,13 @@ export function parseColorDecision(rawCssCode: string): ColorDecision[] {
179181

180182
assert(node !== undefined);
181183

182-
const { declarations } = node as any;
184+
const { declarations } = node as { declarations: { property: string; value: string }[] };
183185

184186
return { declarations };
185187
})();
186188

187189
return declarations
188-
.map(({ property, value }: { property: string; value: string }) => {
190+
.map(({ property, value }) => {
189191
const mathArray = value.match(/^var\((--[^)]+)\)$/);
190192

191193
if (mathArray === null) {
@@ -203,12 +205,13 @@ export function parseColorDecision(rawCssCode: string): ColorDecision[] {
203205
assert(is<`--${string}`>(property));
204206

205207
return {
208+
"colorDecisionName": property,
206209
"themePath": getThemePath(parseColorDecisionName(property)),
207210
"optionThemePath": getColorOptionThemePath(parseColorOptionName(colorOptionName))
208211
};
209212
})
210213
.filter(exclude(undefined));
211-
}
214+
});
212215

213216
export function generateGetColorDecisionsTsCode(rawCssCode: string): string {
214217
const obj: any = {};

src/scripts/build/cssToTs/colorOptions.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,7 @@ export type ColorOption = {
321321
colorOptionName: `--${string}`;
322322
themePath: string[];
323323
color:
324-
| string
324+
| `#${string}`
325325
| {
326326
light: `#${string}`;
327327
dark: `#${string}`;
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { parseColorOptions } from "./colorOptions";
2+
import { parseColorDecision } from "./colorDecisions";
3+
4+
export function generateColorResolutionTsCode(rawCssCode: string): string {
5+
return [
6+
`export const colorOptions= ${JSON.stringify(
7+
parseColorOptions(rawCssCode),
8+
null,
9+
4
10+
)} as const;`,
11+
``,
12+
`export const colorDecisions= ${JSON.stringify(
13+
parseColorDecision(rawCssCode),
14+
null,
15+
4
16+
)} as const;`,
17+
``
18+
].join("\n");
19+
}

src/scripts/build/cssToTs/cssToTs.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { getProjectRoot } from "../../../bin/tools/getProjectRoot";
55
import { generateTypographyTsCode } from "./typography";
66
import { generateSpacingTsCode } from "./spacing";
77
import { generateClassNamesTsCode } from "./classNames";
8+
import { generateColorResolutionTsCode } from "./colorResolution";
89
import * as fs from "fs";
910
import { join as pathJoin, basename as pathBasename, relative as pathRelative } from "path";
1011
import type { Icon } from "../../../bin/only-include-used-icons";
@@ -111,4 +112,12 @@ export function cssToTs(params: {
111112
"utf8"
112113
)
113114
);
115+
116+
fs.writeFileSync(
117+
pathJoin(generatedDirPath, "colorResolution.ts"),
118+
Buffer.from(
119+
[warningMessage, ``, generateColorResolutionTsCode(rawDsfrCssCode), ``].join("\n"),
120+
"utf8"
121+
)
122+
);
114123
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { it, expect } from "vitest";
2+
import { resolveColorHexCodeToDecision } from "../../../src/fr/colorResolver";
3+
4+
it("Resolve to the correct color decision", () => {
5+
const got = resolveColorHexCodeToDecision({
6+
"hexColorCode": "#474747"
7+
});
8+
9+
const expected = [
10+
{
11+
"cssVarName": "--background-default-grey-active",
12+
"decisionObjectPath": ["background", "default", "grey", "active"],
13+
"option": {
14+
"color": {
15+
"light": "#ededed",
16+
"dark": "#474747"
17+
},
18+
"optionObjectPath": ["grey", "_1000_50", "active"],
19+
"cssVarName": "--grey-1000-50-active"
20+
}
21+
},
22+
{
23+
"cssVarName": "--background-contrast-grey-hover",
24+
"decisionObjectPath": ["background", "contrast", "grey", "hover"],
25+
"option": {
26+
"color": {
27+
"light": "#d2d2d2",
28+
"dark": "#474747"
29+
},
30+
"optionObjectPath": ["grey", "_950_100", "hover"],
31+
"cssVarName": "--grey-950-100-hover"
32+
}
33+
},
34+
{
35+
"cssVarName": "--background-overlap-grey-hover",
36+
"decisionObjectPath": ["background", "overlap", "grey", "hover"],
37+
"option": {
38+
"color": {
39+
"light": "#f6f6f6",
40+
"dark": "#474747"
41+
},
42+
"optionObjectPath": ["grey", "_1000_100", "hover"],
43+
"cssVarName": "--grey-1000-100-hover"
44+
}
45+
},
46+
{
47+
"cssVarName": "--background-alt-raised-grey-hover",
48+
"decisionObjectPath": ["background", "altRaised", "grey", "hover"],
49+
"option": {
50+
"color": {
51+
"light": "#dfdfdf",
52+
"dark": "#474747"
53+
},
54+
"optionObjectPath": ["grey", "_975_100", "hover"],
55+
"cssVarName": "--grey-975-100-hover"
56+
}
57+
}
58+
];
59+
60+
expect(got).toStrictEqual(expected);
61+
});

test/runtime/scripts/colorDecisions/parseColorDecision.test.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,22 +39,27 @@ it("Color decisions to be successfully parsed", () => {
3939

4040
const expected: ColorDecision[] = [
4141
{
42+
"colorDecisionName": "--background-default-grey-hover",
4243
"themePath": ["background", "default", "grey", "hover"],
4344
"optionThemePath": ["grey", "_1000_50", "hover"]
4445
},
4546
{
47+
"colorDecisionName": "--background-default-grey",
4648
"themePath": ["background", "default", "grey", "default"],
4749
"optionThemePath": ["grey", "_1000_50", "default"]
4850
},
4951
{
52+
"colorDecisionName": "--border-action-low-orange-terre-battue",
5053
"themePath": ["border", "actionLow", "orangeTerreBattue", "default"],
5154
"optionThemePath": ["orangeTerreBattue", "_850_200", "default"]
5255
},
5356
{
57+
"colorDecisionName": "--background-alt-raised-grey-hover",
5458
"themePath": ["background", "altRaised", "grey", "hover"],
5559
"optionThemePath": ["grey", "_975_100", "hover"]
5660
},
5761
{
62+
"colorDecisionName": "--background-contrast-overlap-grey",
5863
"themePath": ["background", "contrastOverlap", "grey", "default"],
5964
"optionThemePath": ["grey", "_950_150", "default"]
6065
}

0 commit comments

Comments
 (0)