@sb1/ffe-core 33.2.0 → 100.0.3

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.
Files changed (45) hide show
  1. package/README.md +8 -8
  2. package/css/colors-semantic-tailwind-4.css +6 -0
  3. package/css/colors-semantic.css +136 -100
  4. package/css/ffe.css +138 -101
  5. package/less/colors-semantic-chevron.less +1 -1
  6. package/less/colors-semantic.less +136 -100
  7. package/less/dimensions.less +1 -0
  8. package/less/variables.less +0 -1
  9. package/lib/semanticColors.js +7 -1
  10. package/lib/semanticColorsTailwind.js +7 -1
  11. package/package.json +6 -4
  12. package/scripts/build-colors/build-colors.cjs +115 -0
  13. package/scripts/build-colors/json/json.cjs +12 -0
  14. package/scripts/build-colors/mobile/android.cjs +99 -0
  15. package/scripts/build-colors/mobile/android.test.js +98 -0
  16. package/scripts/build-colors/mobile/ios.cjs +205 -0
  17. package/scripts/build-colors/web/chevron.cjs +44 -0
  18. package/scripts/build-colors/web/color-names.cjs +24 -0
  19. package/scripts/build-colors/web/css.cjs +78 -0
  20. package/scripts/build-colors/web/js.cjs +44 -0
  21. package/scripts/build-colors/web/tailwind.cjs +36 -0
  22. package/scripts/build-colors/web/tailwind4.cjs +24 -0
  23. package/scripts/build-colors/web/web.cjs +18 -0
  24. package/scripts/build-custom-mq.cjs +45 -0
  25. package/scripts/build.cjs +38 -0
  26. package/scripts/figma-api/color.spec.tsx +126 -0
  27. package/scripts/figma-api/color.ts +52 -0
  28. package/scripts/figma-api/figma_api.ts +41 -0
  29. package/scripts/figma-api/sync_figma_to_tokens.ts +57 -0
  30. package/scripts/figma-api/token_export.spec.tsx +359 -0
  31. package/scripts/figma-api/token_export.ts +99 -0
  32. package/scripts/figma-api/token_import.spec.tsx +1097 -0
  33. package/scripts/figma-api/token_import.ts +430 -0
  34. package/scripts/figma-api/token_types.ts +55 -0
  35. package/scripts/figma-api/utils.ts +11 -0
  36. package/scripts/lib/extractCustomProps.cjs +24 -0
  37. package/scripts/lib/genTypeScript.cjs +7 -0
  38. package/scripts/lib/renderLessVarsToCSSProps.cjs +23 -0
  39. package/scripts/lib/writeToFile.cjs +9 -0
  40. package/scripts/utils.cjs +27 -0
  41. package/tokens/01 Primitive.value.json +4726 -0
  42. package/tokens/02 Semantic.Dark.json +3942 -0
  43. package/tokens/02 Semantic.Light.json +3942 -0
  44. package/tokens/03 Context.Accent.json +2273 -0
  45. package/tokens/03 Context.Default.json +2273 -0
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
 
4
- exports['background-default'] = exports['background-subtle'] = exports['surface-primary-default'] = exports['surface-primary-default-hover'] = exports['surface-primary-default-pressed'] = exports['surface-secondary-default'] = exports['surface-secondary-default-hover'] = exports['surface-secondary-default-pressed'] = exports['surface-tertiary-default'] = exports['surface-tertiary-default-hover'] = exports['surface-tertiary-default-pressed'] = exports['surface-neutral-default'] = exports['surface-neutral-default-hover'] = exports['surface-neutral-default-pressed'] = exports['surface-feedback-info'] = exports['surface-feedback-success'] = exports['surface-feedback-warning'] = exports['surface-feedback-critical'] = exports['surface-feedback-tip'] = exports['fill-primary-default'] = exports['fill-primary-default-hover'] = exports['fill-primary-default-pressed'] = exports['fill-primary-selected-default'] = exports['fill-primary-selected-hover'] = exports['fill-primary-selected-pressed'] = exports['fill-primary-subtle'] = exports['fill-primary-subtle-hover'] = exports['fill-primary-subtle-pressed'] = exports['fill-primary-invisible'] = exports['fill-secondary-default'] = exports['fill-secondary-default-hover'] = exports['fill-secondary-default-pressed'] = exports['fill-secondary-subtle'] = exports['fill-secondary-subtle-hover'] = exports['fill-secondary-subtle-pressed'] = exports['fill-tertiary-default'] = exports['fill-tertiary-default-hover'] = exports['fill-tertiary-default-pressed'] = exports['fill-tertiary-subtle'] = exports['fill-tertiary-subtle-hover'] = exports['fill-tertiary-subtle-pressed'] = exports['fill-neutral-default'] = exports['fill-neutral-default-hover'] = exports['fill-neutral-default-pressed'] = exports['fill-neutral-subtle'] = exports['fill-neutral-subtle-hover'] = exports['fill-neutral-subtle-pressed'] = exports['fill-neutral-extra-subtle'] = exports['fill-neutral-extra-subtle-hover'] = exports['fill-neutral-extra-subtle-pressed'] = exports['fill-feedback-info'] = exports['fill-feedback-info-subtle'] = exports['fill-feedback-success'] = exports['fill-feedback-success-subtle'] = exports['fill-feedback-warning'] = exports['fill-feedback-warning-subtle'] = exports['fill-feedback-critical'] = exports['fill-feedback-critical-subtle'] = exports['fill-feedback-tip'] = exports['fill-feedback-tip-subtle'] = exports['foreground-default'] = exports['foreground-subtle'] = exports['foreground-emphasis'] = exports['foreground-inverse'] = exports['foreground-interactive-link'] = exports['foreground-interactive-link-hover'] = exports['foreground-interactive-link-pressed'] = exports['foreground-interactive-link-active'] = exports['foreground-feedback-info'] = exports['foreground-feedback-success'] = exports['foreground-feedback-warning'] = exports['foreground-feedback-critical'] = exports['foreground-feedback-tip'] = exports['component-form-input-foreground-default'] = exports['component-form-input-foreground-subtle'] = exports['component-form-input-foreground-placeholder'] = exports['component-form-input-foreground-disabled'] = exports['component-form-input-foreground-read-only'] = exports['component-form-input-foreground-error'] = exports['component-form-input-border-active'] = exports['component-form-input-fill-default'] = exports['component-form-input-fill-default-hover'] = exports['component-form-input-fill-default-pressed'] = exports['component-form-input-fill-disabled'] = exports['component-form-input-fill-read-only'] = exports['component-form-input-fill-error'] = exports['component-form-toggleswitch-handle-default'] = exports['component-form-toggleswitch-fill-default'] = exports['component-form-toggleswitch-fill-default-hover'] = exports['component-form-toggleswitch-fill-default-pressed'] = exports['component-form-toggleswitch-fill-selected'] = exports['component-form-toggleswitch-fill-selected-hover'] = exports['component-form-toggleswitch-fill-selected-pressed'] = exports['component-form-toggleswitch-fill-critical'] = exports['component-button-action-border-default'] = exports['component-button-action-border-hover'] = exports['component-button-action-border-pressed'] = exports['component-button-action-fill-default'] = exports['component-button-action-fill-hover'] = exports['component-button-action-fill-pressed'] = exports['component-button-action-foreground-default'] = exports['component-button-action-foreground-hover'] = exports['component-button-primary-border-default'] = exports['component-button-primary-border-hover'] = exports['component-button-primary-border-pressed'] = exports['component-button-primary-fill-default'] = exports['component-button-primary-fill-hover'] = exports['component-button-primary-fill-pressed'] = exports['component-button-primary-foreground-default'] = exports['component-button-primary-foreground-hover'] = exports['component-button-secondary-border-default'] = exports['component-button-secondary-border-hover'] = exports['component-button-secondary-border-pressed'] = exports['component-button-secondary-fill-default'] = exports['component-button-secondary-fill-hover'] = exports['component-button-secondary-fill-pressed'] = exports['component-button-secondary-foreground-default'] = exports['component-button-secondary-foreground-hover'] = exports['component-button-tertiary-fill-default'] = exports['component-button-tertiary-fill-hover'] = exports['component-button-tertiary-fill-pressed'] = exports['component-button-tertiary-foreground-default'] = exports['component-button-tertiary-foreground-hover'] = exports['component-logo-name'] = exports['component-logo-number1'] = exports['component-logo-circle-dark-red'] = exports['component-logo-circle-light-red'] = exports['border-primary-default'] = exports['border-primary-default-hover'] = exports['border-primary-default-pressed'] = exports['border-primary-subtle'] = exports['border-primary-subtle-hover'] = exports['border-primary-subtle-pressed'] = exports['border-primary-emphasis'] = exports['border-feedback-info'] = exports['border-feedback-success'] = exports['border-feedback-warning'] = exports['border-feedback-critical'] = exports['border-feedback-tip'] = exports['border-interactive-focus'] = exports['border-interactive-selected'] = void 0
4
+ exports['background-default'] = exports['background-subtle'] = exports['surface-primary-default'] = exports['surface-primary-default-hover'] = exports['surface-primary-default-pressed'] = exports['surface-secondary-default'] = exports['surface-secondary-default-hover'] = exports['surface-secondary-default-pressed'] = exports['surface-tertiary-default'] = exports['surface-tertiary-default-hover'] = exports['surface-tertiary-default-pressed'] = exports['surface-neutral-default'] = exports['surface-neutral-default-hover'] = exports['surface-neutral-default-pressed'] = exports['surface-feedback-info'] = exports['surface-feedback-success'] = exports['surface-feedback-warning'] = exports['surface-feedback-critical'] = exports['surface-feedback-tip'] = exports['fill-primary-default'] = exports['fill-primary-default-hover'] = exports['fill-primary-default-pressed'] = exports['fill-primary-selected-default'] = exports['fill-primary-selected-hover'] = exports['fill-primary-selected-pressed'] = exports['fill-primary-subtle'] = exports['fill-primary-subtle-hover'] = exports['fill-primary-subtle-pressed'] = exports['fill-primary-invisible'] = exports['fill-secondary-default'] = exports['fill-secondary-default-hover'] = exports['fill-secondary-default-pressed'] = exports['fill-secondary-subtle'] = exports['fill-secondary-subtle-hover'] = exports['fill-secondary-subtle-pressed'] = exports['fill-tertiary-default'] = exports['fill-tertiary-default-hover'] = exports['fill-tertiary-default-pressed'] = exports['fill-tertiary-subtle'] = exports['fill-tertiary-subtle-hover'] = exports['fill-tertiary-subtle-pressed'] = exports['fill-neutral-default'] = exports['fill-neutral-default-hover'] = exports['fill-neutral-default-pressed'] = exports['fill-neutral-subtle'] = exports['fill-neutral-subtle-hover'] = exports['fill-neutral-subtle-pressed'] = exports['fill-neutral-extra-subtle'] = exports['fill-neutral-extra-subtle-hover'] = exports['fill-neutral-extra-subtle-pressed'] = exports['fill-feedback-info'] = exports['fill-feedback-info-subtle'] = exports['fill-feedback-success'] = exports['fill-feedback-success-subtle'] = exports['fill-feedback-warning'] = exports['fill-feedback-warning-subtle'] = exports['fill-feedback-critical'] = exports['fill-feedback-critical-subtle'] = exports['fill-feedback-tip'] = exports['fill-feedback-tip-subtle'] = exports['foreground-default'] = exports['foreground-subtle'] = exports['foreground-emphasis'] = exports['foreground-inverse'] = exports['foreground-interactive-link'] = exports['foreground-interactive-link-hover'] = exports['foreground-interactive-link-pressed'] = exports['foreground-interactive-link-active'] = exports['foreground-interactive-disabled'] = exports['foreground-feedback-info'] = exports['foreground-feedback-success'] = exports['foreground-feedback-warning'] = exports['foreground-feedback-critical'] = exports['foreground-feedback-tip'] = exports['component-form-input-foreground-default'] = exports['component-form-input-foreground-subtle'] = exports['component-form-input-foreground-placeholder'] = exports['component-form-input-foreground-disabled'] = exports['component-form-input-foreground-read-only'] = exports['component-form-input-foreground-error'] = exports['component-form-input-border-active'] = exports['component-form-input-fill-default'] = exports['component-form-input-fill-default-hover'] = exports['component-form-input-fill-default-pressed'] = exports['component-form-input-fill-disabled'] = exports['component-form-input-fill-read-only'] = exports['component-form-input-fill-error'] = exports['component-form-toggleswitch-handle-default'] = exports['component-form-toggleswitch-handle-disabled'] = exports['component-form-toggleswitch-fill-default'] = exports['component-form-toggleswitch-fill-default-hover'] = exports['component-form-toggleswitch-fill-default-pressed'] = exports['component-form-toggleswitch-fill-selected'] = exports['component-form-toggleswitch-fill-selected-hover'] = exports['component-form-toggleswitch-fill-selected-pressed'] = exports['component-form-toggleswitch-fill-selected-disabled'] = exports['component-form-toggleswitch-fill-selected-read-only'] = exports['component-form-toggleswitch-fill-critical'] = exports['component-form-toggleswitch-fill-disabled'] = exports['component-form-toggleswitch-fill-read-only'] = exports['component-button-action-border-default'] = exports['component-button-action-border-hover'] = exports['component-button-action-border-pressed'] = exports['component-button-action-fill-default'] = exports['component-button-action-fill-hover'] = exports['component-button-action-fill-pressed'] = exports['component-button-action-foreground-default'] = exports['component-button-action-foreground-hover'] = exports['component-button-primary-border-default'] = exports['component-button-primary-border-hover'] = exports['component-button-primary-border-pressed'] = exports['component-button-primary-fill-default'] = exports['component-button-primary-fill-hover'] = exports['component-button-primary-fill-pressed'] = exports['component-button-primary-foreground-default'] = exports['component-button-primary-foreground-hover'] = exports['component-button-secondary-border-default'] = exports['component-button-secondary-border-hover'] = exports['component-button-secondary-border-pressed'] = exports['component-button-secondary-fill-default'] = exports['component-button-secondary-fill-hover'] = exports['component-button-secondary-fill-pressed'] = exports['component-button-secondary-foreground-default'] = exports['component-button-secondary-foreground-hover'] = exports['component-button-tertiary-fill-default'] = exports['component-button-tertiary-fill-hover'] = exports['component-button-tertiary-fill-pressed'] = exports['component-button-tertiary-foreground-default'] = exports['component-button-tertiary-foreground-hover'] = exports['component-logo-name'] = exports['component-logo-number1'] = exports['component-logo-circle-dark-red'] = exports['component-logo-circle-light-red'] = exports['border-primary-default'] = exports['border-primary-default-hover'] = exports['border-primary-default-pressed'] = exports['border-primary-subtle'] = exports['border-primary-subtle-hover'] = exports['border-primary-subtle-pressed'] = exports['border-primary-emphasis'] = exports['border-feedback-info'] = exports['border-feedback-success'] = exports['border-feedback-warning'] = exports['border-feedback-critical'] = exports['border-feedback-tip'] = exports['border-interactive-focus'] = exports['border-interactive-selected'] = void 0
5
5
 
6
6
  exports['background-default'] = 'var(--ffe-color-background-default)';
7
7
  exports['background-subtle'] = 'var(--ffe-color-background-subtle)';
@@ -71,6 +71,7 @@ exports['foreground-interactive-link'] = 'var(--ffe-color-foreground-interactive
71
71
  exports['foreground-interactive-link-hover'] = 'var(--ffe-color-foreground-interactive-link-hover)';
72
72
  exports['foreground-interactive-link-pressed'] = 'var(--ffe-color-foreground-interactive-link-pressed)';
73
73
  exports['foreground-interactive-link-active'] = 'var(--ffe-color-foreground-interactive-link-active)';
74
+ exports['foreground-interactive-disabled'] = 'var(--ffe-color-foreground-interactive-disabled)';
74
75
  exports['foreground-feedback-info'] = 'var(--ffe-color-foreground-feedback-info)';
75
76
  exports['foreground-feedback-success'] = 'var(--ffe-color-foreground-feedback-success)';
76
77
  exports['foreground-feedback-warning'] = 'var(--ffe-color-foreground-feedback-warning)';
@@ -90,13 +91,18 @@ exports['component-form-input-fill-disabled'] = 'var(--ffe-color-component-form-
90
91
  exports['component-form-input-fill-read-only'] = 'var(--ffe-color-component-form-input-fill-read-only)';
91
92
  exports['component-form-input-fill-error'] = 'var(--ffe-color-component-form-input-fill-error)';
92
93
  exports['component-form-toggleswitch-handle-default'] = 'var(--ffe-color-component-form-toggleswitch-handle-default)';
94
+ exports['component-form-toggleswitch-handle-disabled'] = 'var(--ffe-color-component-form-toggleswitch-handle-disabled)';
93
95
  exports['component-form-toggleswitch-fill-default'] = 'var(--ffe-color-component-form-toggleswitch-fill-default)';
94
96
  exports['component-form-toggleswitch-fill-default-hover'] = 'var(--ffe-color-component-form-toggleswitch-fill-default-hover)';
95
97
  exports['component-form-toggleswitch-fill-default-pressed'] = 'var(--ffe-color-component-form-toggleswitch-fill-default-pressed)';
96
98
  exports['component-form-toggleswitch-fill-selected'] = 'var(--ffe-color-component-form-toggleswitch-fill-selected)';
97
99
  exports['component-form-toggleswitch-fill-selected-hover'] = 'var(--ffe-color-component-form-toggleswitch-fill-selected-hover)';
98
100
  exports['component-form-toggleswitch-fill-selected-pressed'] = 'var(--ffe-color-component-form-toggleswitch-fill-selected-pressed)';
101
+ exports['component-form-toggleswitch-fill-selected-disabled'] = 'var(--ffe-color-component-form-toggleswitch-fill-selected-disabled)';
102
+ exports['component-form-toggleswitch-fill-selected-read-only'] = 'var(--ffe-color-component-form-toggleswitch-fill-selected-read-only)';
99
103
  exports['component-form-toggleswitch-fill-critical'] = 'var(--ffe-color-component-form-toggleswitch-fill-critical)';
104
+ exports['component-form-toggleswitch-fill-disabled'] = 'var(--ffe-color-component-form-toggleswitch-fill-disabled)';
105
+ exports['component-form-toggleswitch-fill-read-only'] = 'var(--ffe-color-component-form-toggleswitch-fill-read-only)';
100
106
  exports['component-button-action-border-default'] = 'var(--ffe-color-component-button-action-border-default)';
101
107
  exports['component-button-action-border-hover'] = 'var(--ffe-color-component-button-action-border-hover)';
102
108
  exports['component-button-action-border-pressed'] = 'var(--ffe-color-component-button-action-border-pressed)';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sb1/ffe-core",
3
- "version": "33.2.0",
3
+ "version": "100.0.3",
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": "^0.10.2",
40
+ "@sb1/ffe-buildtool": "^100.0.3",
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": "cc5bdf057ae2acf93315e4eb4d8e026320cf03be"
48
+ "gitHead": "8b55768332b15f6ac19ddde8e17edbbcfff2be8c"
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 };