@sb1/ffe-core 100.0.0 → 100.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -8
- package/css/ffe.css +0 -1
- package/less/dimensions.less +1 -0
- package/less/variables.less +0 -1
- package/package.json +6 -4
- package/scripts/build-colors/build-colors.cjs +115 -0
- package/scripts/build-colors/json/json.cjs +12 -0
- package/scripts/build-colors/mobile/android.cjs +99 -0
- package/scripts/build-colors/mobile/android.test.js +98 -0
- package/scripts/build-colors/mobile/ios.cjs +205 -0
- package/scripts/build-colors/web/chevron.cjs +44 -0
- package/scripts/build-colors/web/color-names.cjs +24 -0
- package/scripts/build-colors/web/css.cjs +78 -0
- package/scripts/build-colors/web/js.cjs +44 -0
- package/scripts/build-colors/web/tailwind.cjs +36 -0
- package/scripts/build-colors/web/tailwind4.cjs +24 -0
- package/scripts/build-colors/web/web.cjs +18 -0
- package/scripts/build-custom-mq.cjs +45 -0
- package/scripts/build.cjs +38 -0
- package/scripts/figma-api/color.spec.tsx +126 -0
- package/scripts/figma-api/color.ts +52 -0
- package/scripts/figma-api/figma_api.ts +41 -0
- package/scripts/figma-api/sync_figma_to_tokens.ts +57 -0
- package/scripts/figma-api/token_export.spec.tsx +359 -0
- package/scripts/figma-api/token_export.ts +99 -0
- package/scripts/figma-api/token_import.spec.tsx +1097 -0
- package/scripts/figma-api/token_import.ts +430 -0
- package/scripts/figma-api/token_types.ts +55 -0
- package/scripts/figma-api/utils.ts +11 -0
- package/scripts/lib/extractCustomProps.cjs +24 -0
- package/scripts/lib/genTypeScript.cjs +7 -0
- package/scripts/lib/renderLessVarsToCSSProps.cjs +23 -0
- package/scripts/lib/writeToFile.cjs +9 -0
- package/scripts/utils.cjs +27 -0
- package/tokens/01 Primitive.value.json +4726 -0
- package/tokens/02 Semantic.Dark.json +3942 -0
- package/tokens/02 Semantic.Light.json +3942 -0
- package/tokens/03 Context.Accent.json +2273 -0
- package/tokens/03 Context.Default.json +2273 -0
package/README.md
CHANGED
|
@@ -14,9 +14,9 @@ npm install --save-dev less less-plugin-npm-import # less-plugin is optional,
|
|
|
14
14
|
|
|
15
15
|
Full documentation on usage is available at:
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
- https://design.sparebank1.no/komponenter/typografi/
|
|
18
|
+
- https://design.sparebank1.no/profil/farger/
|
|
19
|
+
- https://design.sparebank1.no/profil/spacing/
|
|
20
20
|
|
|
21
21
|
```less
|
|
22
22
|
/* Your project's main .less import file */
|
|
@@ -66,11 +66,11 @@ If your project uses the core styling, you're probably good to go. If not, make
|
|
|
66
66
|
|
|
67
67
|
This package provides a set of less files containing variables for colors and other reusable constants. These files are:
|
|
68
68
|
|
|
69
|
-
-
|
|
70
|
-
-
|
|
71
|
-
-
|
|
72
|
-
-
|
|
73
|
-
-
|
|
69
|
+
- less/breakpoints.less
|
|
70
|
+
- less/colors.less
|
|
71
|
+
- less/dimensions.less
|
|
72
|
+
- less/motion.less
|
|
73
|
+
- less/spacing.less
|
|
74
74
|
|
|
75
75
|
The less variables specified in this package are also available as named JavaScript imports. This can be useful for consumers that use CSS-in-JS, or that need access to these values in their JavaScript for other reasons. They are named the same as their LESS siblings, but are camelCased, and do not have the `@ffe-` prefix.
|
|
76
76
|
|
package/css/ffe.css
CHANGED
package/less/dimensions.less
CHANGED
package/less/variables.less
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sb1/ffe-core",
|
|
3
|
-
"version": "100.0.
|
|
3
|
+
"version": "100.0.4",
|
|
4
4
|
"description": "Rammeverk for Felles Front End",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "SpareBank 1",
|
|
@@ -9,7 +9,9 @@
|
|
|
9
9
|
"lib",
|
|
10
10
|
"es",
|
|
11
11
|
"less",
|
|
12
|
-
"css"
|
|
12
|
+
"css",
|
|
13
|
+
"scripts",
|
|
14
|
+
"tokens"
|
|
13
15
|
],
|
|
14
16
|
"main": "lib/index.js",
|
|
15
17
|
"module": "es/index.js",
|
|
@@ -35,7 +37,7 @@
|
|
|
35
37
|
"build-colors": "./scripts/build-colors.cjs"
|
|
36
38
|
},
|
|
37
39
|
"devDependencies": {
|
|
38
|
-
"@sb1/ffe-buildtool": "^100.0.
|
|
40
|
+
"@sb1/ffe-buildtool": "^100.0.4",
|
|
39
41
|
"case": "^1.5.5",
|
|
40
42
|
"less": "^4.1.2",
|
|
41
43
|
"postcss": "^8.3.6"
|
|
@@ -43,5 +45,5 @@
|
|
|
43
45
|
"publishConfig": {
|
|
44
46
|
"access": "public"
|
|
45
47
|
},
|
|
46
|
-
"gitHead": "
|
|
48
|
+
"gitHead": "3671afc1060e06a9f224892510b38fb923a33e06"
|
|
47
49
|
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { buildAndroidColors } = require('./mobile/android.cjs');
|
|
4
|
+
const { buildIosColors } = require('./mobile/ios.cjs');
|
|
5
|
+
const { buildWebColors } = require('./web/web.cjs');
|
|
6
|
+
const { buildJSONColors } = require('./json/json.cjs');
|
|
7
|
+
|
|
8
|
+
const tokensPath = '../../tokens';
|
|
9
|
+
const files = {
|
|
10
|
+
primitive: '01 Primitive.value.json',
|
|
11
|
+
semanticLight: '02 Semantic.Light.json',
|
|
12
|
+
semanticDark: '02 Semantic.Dark.json',
|
|
13
|
+
context: '03 Context.Default.json',
|
|
14
|
+
contextAccent: '03 Context.Accent.json',
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
function filePath(filename) {
|
|
18
|
+
return path.resolve(__dirname, tokensPath, filename);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function loadColorsFromTokens(fileName, referenceDict) {
|
|
22
|
+
const colorDict = {};
|
|
23
|
+
const jsonContent = JSON.parse(
|
|
24
|
+
fs
|
|
25
|
+
.readFileSync(filePath(fileName), 'utf8')
|
|
26
|
+
.replaceAll('-', '.')
|
|
27
|
+
.toLowerCase(),
|
|
28
|
+
);
|
|
29
|
+
const processColorTokens = (obj, prefix = '') => {
|
|
30
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
31
|
+
if (value.$type === 'color') {
|
|
32
|
+
const cssVarName = `${prefix}${key}`;
|
|
33
|
+
if (!referenceDict) {
|
|
34
|
+
colorDict[cssVarName] = value.$value;
|
|
35
|
+
} else {
|
|
36
|
+
const colorValue = referenceDict[value.$value.slice(1, -1)];
|
|
37
|
+
if (!colorValue) {
|
|
38
|
+
throw new Error(
|
|
39
|
+
`In file ${fileName} .Color value (${colorValue}) for ${cssVarName} is missing in the reference dictionary.`,
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
colorDict[cssVarName] = colorValue;
|
|
43
|
+
}
|
|
44
|
+
} else if (typeof value === 'object') {
|
|
45
|
+
processColorTokens(value, `${prefix}${key}.`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
processColorTokens(jsonContent);
|
|
50
|
+
return colorDict;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function buildColorDict() {
|
|
54
|
+
const primitiveColors = loadColorsFromTokens(files.primitive);
|
|
55
|
+
const semanticLightColors = loadColorsFromTokens(
|
|
56
|
+
files.semanticLight,
|
|
57
|
+
primitiveColors,
|
|
58
|
+
);
|
|
59
|
+
const semanticDarkColors = loadColorsFromTokens(
|
|
60
|
+
files.semanticDark,
|
|
61
|
+
primitiveColors,
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
light: loadColorsFromTokens(files.context, semanticLightColors),
|
|
66
|
+
dark: loadColorsFromTokens(files.context, semanticDarkColors),
|
|
67
|
+
lightAccent: loadColorsFromTokens(
|
|
68
|
+
files.contextAccent,
|
|
69
|
+
semanticLightColors,
|
|
70
|
+
),
|
|
71
|
+
darkAccent: loadColorsFromTokens(
|
|
72
|
+
files.contextAccent,
|
|
73
|
+
semanticDarkColors,
|
|
74
|
+
),
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function buildColors(platforms, iosPaths, androidPaths, jsonPaths, webPaths) {
|
|
79
|
+
console.log(platforms, iosPaths, androidPaths);
|
|
80
|
+
if (!platforms || (!androidPaths && !iosPaths && !jsonPaths && !webPaths)) {
|
|
81
|
+
console.error('Platform and path arguments are required.');
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
if (
|
|
85
|
+
!platforms.includes('android') &&
|
|
86
|
+
!platforms.includes('ios') &&
|
|
87
|
+
!platforms.includes('json') &&
|
|
88
|
+
!platforms.includes('web')
|
|
89
|
+
) {
|
|
90
|
+
console.error(
|
|
91
|
+
"Platform must include 'android', 'ios', 'json, or 'web'.",
|
|
92
|
+
);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const colorDict = buildColorDict();
|
|
97
|
+
if (platforms.includes('android')) {
|
|
98
|
+
buildAndroidColors(androidPaths, colorDict);
|
|
99
|
+
}
|
|
100
|
+
if (platforms.includes('ios')) {
|
|
101
|
+
buildIosColors(iosPaths, colorDict);
|
|
102
|
+
}
|
|
103
|
+
if (platforms.includes('web')) {
|
|
104
|
+
buildWebColors(webPaths, colorDict);
|
|
105
|
+
}
|
|
106
|
+
if (platforms.includes('json')) {
|
|
107
|
+
buildJSONColors(jsonPaths, colorDict);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
module.exports = {
|
|
112
|
+
loadColorsFromTokens,
|
|
113
|
+
buildColorDict,
|
|
114
|
+
buildColors,
|
|
115
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const { writeToFile } = require('../../utils.cjs');
|
|
3
|
+
|
|
4
|
+
function buildJSONColors(paths, colors) {
|
|
5
|
+
paths.forEach(path => {
|
|
6
|
+
writeToFile(path + '/colors-semantic.json')(
|
|
7
|
+
JSON.stringify(colors, null, 2),
|
|
8
|
+
);
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
module.exports = { buildJSONColors };
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const { writeToFile } = require('../../utils.cjs');
|
|
3
|
+
|
|
4
|
+
const removeHash = value => (value.startsWith('#') ? value.slice(1) : value);
|
|
5
|
+
const cssHexToAndroidHex = str => str.slice(-2) + str.slice(0, -2);
|
|
6
|
+
|
|
7
|
+
function transformToAndroidHex(value) {
|
|
8
|
+
value = removeHash(value).toUpperCase();
|
|
9
|
+
if (value.length === 6) {
|
|
10
|
+
return `0xFF${value}`;
|
|
11
|
+
} else if (value.length === 8) {
|
|
12
|
+
return `0x${cssHexToAndroidHex(value)}`;
|
|
13
|
+
} else {
|
|
14
|
+
throw new Error(
|
|
15
|
+
`Invalid color value: ${value}. Expected 6 or 8 characters (with or without #).`,
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function transformToAndroidColorName(name) {
|
|
21
|
+
return name
|
|
22
|
+
.split('.')
|
|
23
|
+
.map((part, idx) =>
|
|
24
|
+
idx === 0 ? part : part.charAt(0).toUpperCase() + part.slice(1),
|
|
25
|
+
)
|
|
26
|
+
.join('');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function generateAndroidColorFileContent(colors) {
|
|
30
|
+
return `
|
|
31
|
+
/* Generated from Figma tokens */
|
|
32
|
+
/* Dokumentasjon på: https://sparebank1.github.io/designsystem/?path=/docs/design-farger-native--docs */
|
|
33
|
+
/* Fargene er bygget fra FFE Core versjon: ${require('../../../package.json').version} */
|
|
34
|
+
|
|
35
|
+
object Colors {
|
|
36
|
+
|
|
37
|
+
object LightDefault {
|
|
38
|
+
${Object.entries(colors.light)
|
|
39
|
+
.map(
|
|
40
|
+
([name, value]) =>
|
|
41
|
+
`val ${transformToAndroidColorName(
|
|
42
|
+
name,
|
|
43
|
+
)} = Color(${transformToAndroidHex(value)})`,
|
|
44
|
+
)
|
|
45
|
+
.join('\n')}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
object DarkDefault {
|
|
49
|
+
${Object.entries(colors.dark)
|
|
50
|
+
.map(
|
|
51
|
+
([name, value]) =>
|
|
52
|
+
`val ${transformToAndroidColorName(
|
|
53
|
+
name,
|
|
54
|
+
)} = Color(${transformToAndroidHex(value)})`,
|
|
55
|
+
)
|
|
56
|
+
.join('\n')}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
object LightAccent {
|
|
60
|
+
${Object.entries(colors.lightAccent)
|
|
61
|
+
.map(
|
|
62
|
+
([name, value]) =>
|
|
63
|
+
`val ${transformToAndroidColorName(
|
|
64
|
+
name,
|
|
65
|
+
)} = Color(${transformToAndroidHex(value)})`,
|
|
66
|
+
)
|
|
67
|
+
.join('\n')}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
object DarkAccent {
|
|
71
|
+
${Object.entries(colors.darkAccent)
|
|
72
|
+
.map(
|
|
73
|
+
([name, value]) =>
|
|
74
|
+
`\t\tval ${transformToAndroidColorName(
|
|
75
|
+
name,
|
|
76
|
+
)} = Color(${transformToAndroidHex(value)})`,
|
|
77
|
+
)
|
|
78
|
+
.join('\n')}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
`;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function buildAndroidColors(paths, colors) {
|
|
87
|
+
paths.forEach(path => {
|
|
88
|
+
writeToFile(path + '/Colors.kt')(
|
|
89
|
+
generateAndroidColorFileContent(colors),
|
|
90
|
+
);
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
module.exports = {
|
|
95
|
+
buildAndroidColors,
|
|
96
|
+
generateAndroidColorFileContent,
|
|
97
|
+
transformToAndroidHex,
|
|
98
|
+
transformToAndroidColorName,
|
|
99
|
+
};
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
const mockWrite = jest.fn();
|
|
2
|
+
const mockWriteToFile = jest.fn(() => mockWrite);
|
|
3
|
+
|
|
4
|
+
jest.mock("../../utils.cjs", () => ({
|
|
5
|
+
writeToFile: mockWriteToFile,
|
|
6
|
+
}));
|
|
7
|
+
|
|
8
|
+
const android = require("./android.cjs");
|
|
9
|
+
|
|
10
|
+
describe("android.cjs", () => {
|
|
11
|
+
describe("transformToAndroidHex", () => {
|
|
12
|
+
it("should convert #hex to C0xFF... (uppercase)", () => {
|
|
13
|
+
expect(android.transformToAndroidHex("#123abc")).toBe("C0xFF123ABC");
|
|
14
|
+
expect(android.transformToAndroidHex("#ABCDEF")).toBe("C0xFFABCDEF");
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("should convert 0x hex to uppercase", () => {
|
|
18
|
+
expect(android.transformToAndroidHex("0xFFAABBCC")).toBe("0XFFAABBCC");
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("should throw on invalid format", () => {
|
|
22
|
+
expect(() => android.transformToAndroidHex("red")).toThrow(
|
|
23
|
+
"Invalid color format"
|
|
24
|
+
);
|
|
25
|
+
expect(() => android.transformToAndroidHex("123456")).toThrow(
|
|
26
|
+
"Invalid color format"
|
|
27
|
+
);
|
|
28
|
+
expect(() => android.transformToAndroidHex("")).toThrow(
|
|
29
|
+
"Invalid color format"
|
|
30
|
+
);
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
describe("transformToAndroidColorName", () => {
|
|
35
|
+
it("should convert dot notation to camelCase", () => {
|
|
36
|
+
expect(android.transformToAndroidColorName("primary.color")).toBe(
|
|
37
|
+
"primaryColor"
|
|
38
|
+
);
|
|
39
|
+
expect(android.transformToAndroidColorName("a.b.c")).toBe("aBC");
|
|
40
|
+
expect(android.transformToAndroidColorName("single")).toBe("single");
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("should handle empty string", () => {
|
|
44
|
+
expect(android.transformToAndroidColorName("")).toBe("");
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
describe("generateAndroidColorFileContent", () => {
|
|
49
|
+
it("should generate Kotlin color file content", () => {
|
|
50
|
+
const colors = {
|
|
51
|
+
"primary.color": "#123456",
|
|
52
|
+
"secondary.color": "#abcdef",
|
|
53
|
+
};
|
|
54
|
+
const content = android.generateAndroidColorFileContent(colors);
|
|
55
|
+
expect(content).toContain("val primaryColor = Color(C0xFF123456)");
|
|
56
|
+
expect(content).toContain("val secondaryColor = Color(C0xFFABCDEF)");
|
|
57
|
+
expect(content).toContain(
|
|
58
|
+
"package com.<domain_name>.<app_name>.ui.theme"
|
|
59
|
+
);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("should throw if color value is invalid", () => {
|
|
63
|
+
const colors = { "bad.color": "notacolor" };
|
|
64
|
+
expect(() => android.generateAndroidColorFileContent(colors)).toThrow(
|
|
65
|
+
"Invalid color format"
|
|
66
|
+
);
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
describe("buildAndroidColors", () => {
|
|
71
|
+
it("should call writeToFile for each color variant", () => {
|
|
72
|
+
const paths = ["/tmp/test"];
|
|
73
|
+
const colors = {
|
|
74
|
+
light: { "a.b": "#111111" },
|
|
75
|
+
dark: { "a.b": "#222222" },
|
|
76
|
+
lightAccent: { "a.b": "#333333" },
|
|
77
|
+
darkAccent: { "a.b": "#444444" },
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
android.buildAndroidColors(paths, colors);
|
|
81
|
+
|
|
82
|
+
expect(mockWriteToFile).toHaveBeenCalledTimes(4);
|
|
83
|
+
expect(mockWriteToFile).toHaveBeenCalledWith(
|
|
84
|
+
"/tmp/test/ColorsDefaultLight.kt"
|
|
85
|
+
);
|
|
86
|
+
expect(mockWriteToFile).toHaveBeenCalledWith(
|
|
87
|
+
"/tmp/test/ColorsDefaultDark.kt"
|
|
88
|
+
);
|
|
89
|
+
expect(mockWriteToFile).toHaveBeenCalledWith(
|
|
90
|
+
"/tmp/test/ColorsAccentLight.kt"
|
|
91
|
+
);
|
|
92
|
+
expect(mockWriteToFile).toHaveBeenCalledWith(
|
|
93
|
+
"/tmp/test/ColorsAccentDark.kt"
|
|
94
|
+
);
|
|
95
|
+
expect(mockWrite).toHaveBeenCalledTimes(4);
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
});
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const { writeToFile } = require('../../utils.cjs');
|
|
3
|
+
|
|
4
|
+
function buildContents(paths) {
|
|
5
|
+
paths.forEach(path => {
|
|
6
|
+
writeToFile(path + '/SemanticColors.xcassets/Contents.json')(
|
|
7
|
+
`{
|
|
8
|
+
"info" : {
|
|
9
|
+
"author" : "xcode",
|
|
10
|
+
"version" : 1
|
|
11
|
+
}
|
|
12
|
+
}`,
|
|
13
|
+
);
|
|
14
|
+
writeToFile(path + '/SemanticColors.xcassets/README.md')(
|
|
15
|
+
`# Semantic Colors
|
|
16
|
+
|
|
17
|
+
Fargene er bygget fra FFE Core versjon: ${require('../../../package.json').version}
|
|
18
|
+
|
|
19
|
+
Dokumentasjon på: https://sparebank1.github.io/designsystem/?path=/docs/design-farger-native--docs
|
|
20
|
+
`,
|
|
21
|
+
);
|
|
22
|
+
writeToFile(path + '/SemanticColors.xcassets/default/Contents.json')(
|
|
23
|
+
`{
|
|
24
|
+
"info" : {
|
|
25
|
+
"author" : "xcode",
|
|
26
|
+
"version" : 1
|
|
27
|
+
}
|
|
28
|
+
}`,
|
|
29
|
+
);
|
|
30
|
+
writeToFile(path + '/SemanticColors.xcassets/accent/Contents.json')(
|
|
31
|
+
`{
|
|
32
|
+
"info" : {
|
|
33
|
+
"author" : "xcode",
|
|
34
|
+
"version" : 1
|
|
35
|
+
}
|
|
36
|
+
}`,
|
|
37
|
+
);
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function buildSRGBComponentsFromHex(hex) {
|
|
42
|
+
// Remove # if present and convert to lowercase
|
|
43
|
+
const cleanHex = hex.replace('#', '').toLowerCase();
|
|
44
|
+
|
|
45
|
+
let r,
|
|
46
|
+
g,
|
|
47
|
+
b,
|
|
48
|
+
a = 255; // Default alpha to 255 (fully opaque)
|
|
49
|
+
|
|
50
|
+
if (cleanHex.length === 3) {
|
|
51
|
+
// RGB format: aaa -> aaaaaa
|
|
52
|
+
r = parseInt(cleanHex[0] + cleanHex[0], 16);
|
|
53
|
+
g = parseInt(cleanHex[1] + cleanHex[1], 16);
|
|
54
|
+
b = parseInt(cleanHex[2] + cleanHex[2], 16);
|
|
55
|
+
} else if (cleanHex.length === 4) {
|
|
56
|
+
// RGBA format: aaaf -> aaaaafff
|
|
57
|
+
r = parseInt(cleanHex[0] + cleanHex[0], 16);
|
|
58
|
+
g = parseInt(cleanHex[1] + cleanHex[1], 16);
|
|
59
|
+
b = parseInt(cleanHex[2] + cleanHex[2], 16);
|
|
60
|
+
a = parseInt(cleanHex[3] + cleanHex[3], 16);
|
|
61
|
+
} else if (cleanHex.length === 6) {
|
|
62
|
+
// RRGGBB format: aaaaaa
|
|
63
|
+
r = parseInt(cleanHex.slice(0, 2), 16);
|
|
64
|
+
g = parseInt(cleanHex.slice(2, 4), 16);
|
|
65
|
+
b = parseInt(cleanHex.slice(4, 6), 16);
|
|
66
|
+
} else if (cleanHex.length === 8) {
|
|
67
|
+
// RRGGBBAA format: aaaaaaff
|
|
68
|
+
r = parseInt(cleanHex.slice(0, 2), 16);
|
|
69
|
+
g = parseInt(cleanHex.slice(2, 4), 16);
|
|
70
|
+
b = parseInt(cleanHex.slice(4, 6), 16);
|
|
71
|
+
a = parseInt(cleanHex.slice(6, 8), 16);
|
|
72
|
+
} else {
|
|
73
|
+
throw new Error(`Invalid hex color format: ${hex}`);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Convert to 0-1 range and format to 3 decimal places
|
|
77
|
+
return `{
|
|
78
|
+
"alpha": "${(a / 255).toFixed(3)}",
|
|
79
|
+
"blue": "${(b / 255).toFixed(3)}",
|
|
80
|
+
"green": "${(g / 255).toFixed(3)}",
|
|
81
|
+
"red": "${(r / 255).toFixed(3)}"
|
|
82
|
+
}`;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function colorNameToFolderName(name, prefix = '') {
|
|
86
|
+
const str = prefix ? prefix + '.' : '';
|
|
87
|
+
const parts = (str + name).split('.').filter(part => part !== 'color'); // Remove 'color' prefix if present
|
|
88
|
+
|
|
89
|
+
if (parts.length === 0) return '.colorset';
|
|
90
|
+
|
|
91
|
+
// First part: lowercase first letter, rest unchanged
|
|
92
|
+
const first = parts[0].charAt(0).toLowerCase() + parts[0].slice(1);
|
|
93
|
+
|
|
94
|
+
// Remaining parts: uppercase first letter, rest unchanged
|
|
95
|
+
const rest = parts
|
|
96
|
+
.slice(1)
|
|
97
|
+
.map(part => part.charAt(0).toUpperCase() + part.slice(1));
|
|
98
|
+
|
|
99
|
+
return [first, ...rest].join('') + '.colorset';
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function buildColorFileContents(
|
|
103
|
+
path,
|
|
104
|
+
name,
|
|
105
|
+
light,
|
|
106
|
+
lightAccent,
|
|
107
|
+
dark,
|
|
108
|
+
darkAccent,
|
|
109
|
+
) {
|
|
110
|
+
writeToFile(
|
|
111
|
+
path +
|
|
112
|
+
'/SemanticColors.xcassets/default/' +
|
|
113
|
+
colorNameToFolderName(name) +
|
|
114
|
+
'/Contents.json',
|
|
115
|
+
)(
|
|
116
|
+
`{
|
|
117
|
+
"colors" : [
|
|
118
|
+
{
|
|
119
|
+
"color" : {
|
|
120
|
+
"color-space" : "srgb",
|
|
121
|
+
"components" : ${buildSRGBComponentsFromHex(light)}
|
|
122
|
+
},
|
|
123
|
+
"idiom" : "universal"
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
"appearances" : [
|
|
127
|
+
{
|
|
128
|
+
"appearance" : "luminosity",
|
|
129
|
+
"value" : "dark"
|
|
130
|
+
}
|
|
131
|
+
],
|
|
132
|
+
"color" : {
|
|
133
|
+
"color-space" : "srgb",
|
|
134
|
+
"components" : ${buildSRGBComponentsFromHex(dark)}
|
|
135
|
+
},
|
|
136
|
+
"idiom" : "universal"
|
|
137
|
+
}
|
|
138
|
+
],
|
|
139
|
+
"info" : {
|
|
140
|
+
"author" : "xcode",
|
|
141
|
+
"version" : 1
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
`,
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
writeToFile(
|
|
148
|
+
path +
|
|
149
|
+
'/SemanticColors.xcassets/accent/' +
|
|
150
|
+
colorNameToFolderName(name, 'accent') +
|
|
151
|
+
'/Contents.json',
|
|
152
|
+
)(
|
|
153
|
+
`{
|
|
154
|
+
"colors" : [
|
|
155
|
+
{
|
|
156
|
+
"color" : {
|
|
157
|
+
"color-space" : "srgb",
|
|
158
|
+
"components" : ${buildSRGBComponentsFromHex(lightAccent)}
|
|
159
|
+
},
|
|
160
|
+
"idiom" : "universal"
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
"appearances" : [
|
|
164
|
+
{
|
|
165
|
+
"appearance" : "luminosity",
|
|
166
|
+
"value" : "dark"
|
|
167
|
+
}
|
|
168
|
+
],
|
|
169
|
+
"color" : {
|
|
170
|
+
"color-space" : "srgb",
|
|
171
|
+
"components" : ${buildSRGBComponentsFromHex(darkAccent)}
|
|
172
|
+
},
|
|
173
|
+
"idiom" : "universal"
|
|
174
|
+
}
|
|
175
|
+
],
|
|
176
|
+
"info" : {
|
|
177
|
+
"author" : "xcode",
|
|
178
|
+
"version" : 1
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
`,
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function buildColorFiles(paths, colors) {
|
|
186
|
+
Object.keys(colors.light).forEach(name => {
|
|
187
|
+
paths.forEach(path => {
|
|
188
|
+
buildColorFileContents(
|
|
189
|
+
path,
|
|
190
|
+
name,
|
|
191
|
+
colors.light[name],
|
|
192
|
+
colors.lightAccent[name],
|
|
193
|
+
colors.dark[name],
|
|
194
|
+
colors.darkAccent[name],
|
|
195
|
+
);
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function buildIosColors(paths, colors) {
|
|
201
|
+
buildContents(paths);
|
|
202
|
+
buildColorFiles(paths, colors);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
module.exports = { buildIosColors };
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const { writeToFile } = require('../../utils.cjs');
|
|
3
|
+
|
|
4
|
+
function transformToCssProperty(name, value) {
|
|
5
|
+
return `--ffe-${name.replaceAll('.', '-')}: ${value};`;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function generateChevronCssColorFileContent(colors) {
|
|
9
|
+
return `
|
|
10
|
+
/* Generated from Figma tokens */
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
// Chevron colors to make it work with semantic colors
|
|
15
|
+
@ffe-color-chevron-light-default: ${Object.entries(colors.light)
|
|
16
|
+
.filter(([name]) => name === 'color.foreground.default')
|
|
17
|
+
.map(([_, value]) => value)[0]
|
|
18
|
+
.substring(1)};
|
|
19
|
+
@ffe-color-chevron-dark-default: ${Object.entries(colors.lightAccent)
|
|
20
|
+
.filter(([name]) => name === 'color.foreground.default')
|
|
21
|
+
.map(([_, value]) => value)[0]
|
|
22
|
+
.substring(1)};
|
|
23
|
+
@ffe-color-chevron-light-accent: ${Object.entries(colors.dark)
|
|
24
|
+
.filter(([name]) => name === 'color.foreground.default')
|
|
25
|
+
.map(([_, value]) => value)[0]
|
|
26
|
+
.substring(1)};
|
|
27
|
+
@ffe-color-chevron-dark-accent: ${Object.entries(colors.darkAccent)
|
|
28
|
+
.filter(([name]) => name === 'color.foreground.default')
|
|
29
|
+
.map(([_, value]) => value)[0]
|
|
30
|
+
.substring(1)};
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function buildChevronCssColors(paths, colors) {
|
|
38
|
+
paths.forEach(path => {
|
|
39
|
+
const content = generateChevronCssColorFileContent(colors);
|
|
40
|
+
writeToFile(path + 'less/colors-semantic-chevron.less')(content);
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
module.exports = { buildChevronCssColors };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const { writeToFile } = require('../../utils.cjs');
|
|
3
|
+
|
|
4
|
+
function transformToCssProperty(name) {
|
|
5
|
+
return `--ffe-${name.replaceAll('.', '-')}`;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function generateColorNamesFileContent(colors) {
|
|
9
|
+
const colorNames = {
|
|
10
|
+
colors: Object.entries(colors.light).map(([name]) =>
|
|
11
|
+
transformToCssProperty(name),
|
|
12
|
+
),
|
|
13
|
+
};
|
|
14
|
+
return JSON.stringify(colorNames, null, 2);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function buildColorsNames(paths, colors) {
|
|
18
|
+
paths.forEach(path => {
|
|
19
|
+
const content = generateColorNamesFileContent(colors);
|
|
20
|
+
writeToFile(path + 'gen-src/semantic-color-names.json')(content);
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
module.exports = { buildColorsNames };
|