@skbkontur/colors 2.0.1 → 2.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.
- package/colors.less +319 -319
- package/colors.scss +319 -319
- package/lib/get-colors.js +19 -1
- package/lib/get-colors.ts +9 -1
- package/package.json +2 -2
- package/tokens-mobile/brand-blue-deep_accent-brand.json +117 -117
- package/tokens-mobile/brand-blue-deep_accent-gray.json +135 -135
- package/tokens-mobile/brand-blue_accent-brand.json +117 -117
- package/tokens-mobile/brand-blue_accent-gray.json +135 -135
- package/tokens-mobile/brand-green_accent-brand.json +117 -117
- package/tokens-mobile/brand-green_accent-gray.json +135 -135
- package/tokens-mobile/brand-mint_accent-brand.json +117 -117
- package/tokens-mobile/brand-mint_accent-gray.json +135 -135
- package/tokens-mobile/brand-orange_accent-gray.json +135 -135
- package/tokens-mobile/brand-purple_accent-brand.json +117 -117
- package/tokens-mobile/brand-purple_accent-gray.json +135 -135
- package/tokens-mobile/brand-red_accent-gray.json +135 -135
- package/tokens-mobile/brand-violet_accent-brand.json +117 -117
- package/tokens-mobile/brand-violet_accent-gray.json +135 -135
- package/.gitignore +0 -10
- package/.npmignore +0 -10
- package/CHANGELOG.md +0 -117
- package/__docs__/Colors.docs.stories.tsx +0 -1578
- package/__docs__/Colors.mdx +0 -228
- package/__docs__/ColorsAPI.docs.stories.tsx +0 -954
- package/__docs__/ColorsAPI.mdx +0 -133
- package/__stories__/colors.stories.tsx +0 -452
- package/__tests__/convert-color.test.ts +0 -23
- package/__tests__/create-tokens-from-figma.test.ts +0 -162
- package/__tests__/format-variable.test.ts +0 -16
- package/__tests__/get-colors-base.test.ts +0 -55
- package/__tests__/get-colors.test.ts +0 -75
- package/__tests__/get-interactions.test.ts +0 -37
- package/__tests__/get-logo.test.ts +0 -24
- package/__tests__/get-palette.test.ts +0 -43
- package/__tests__/get-promo.test.ts +0 -32
- package/colors-default-dark.ts +0 -332
- package/colors-default-light.ts +0 -336
- package/colors.ts +0 -319
- package/scripts/create-tokens-files.ts +0 -424
- package/scripts/create-tokens-from-figma.ts +0 -376
- package/scripts/figma-tokens-base.json +0 -3499
- package/scripts/figma-tokens.json +0 -710
- package/tsconfig.json +0 -8
|
@@ -1,376 +0,0 @@
|
|
|
1
|
-
import * as fs from 'fs';
|
|
2
|
-
import * as path from 'path';
|
|
3
|
-
|
|
4
|
-
import { customizable } from '../lib/consts/default-swatch';
|
|
5
|
-
|
|
6
|
-
interface Value {
|
|
7
|
-
light: string;
|
|
8
|
-
dark: string;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
type Input = Record<string, string>;
|
|
12
|
-
type Groups = Record<string, Partial<Value>>;
|
|
13
|
-
type Output = Record<string, Value>;
|
|
14
|
-
type Entries = Array<[string, Value]>;
|
|
15
|
-
|
|
16
|
-
interface Rule {
|
|
17
|
-
rule: string;
|
|
18
|
-
fn: (data: any) => any;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const INPUT_FIGMA_BASE_PATH = path.join(import.meta.dirname, 'figma-tokens-base.json');
|
|
22
|
-
const INPUT_FIGMA_SEMANTIC_PATH = path.join(import.meta.dirname, 'figma-tokens.json');
|
|
23
|
-
const OUTPUT_TOKENS_PATH = './lib/get-colors-default-tokens.ts';
|
|
24
|
-
const TOKENS_BASE_MAP: Record<string, string> = JSON.parse(fs.readFileSync(INPUT_FIGMA_BASE_PATH, 'utf-8'));
|
|
25
|
-
|
|
26
|
-
export const transformations: Rule[] = [
|
|
27
|
-
{
|
|
28
|
-
rule: 'Filter',
|
|
29
|
-
fn: (inputMap: Input): Input => {
|
|
30
|
-
const MANUAL_PATTERNS: RegExp[] = [];
|
|
31
|
-
const filtered: Input = {};
|
|
32
|
-
for (const [key, value] of Object.entries(inputMap)) {
|
|
33
|
-
if (key.startsWith('Effect/')) {
|
|
34
|
-
continue;
|
|
35
|
-
}
|
|
36
|
-
if (MANUAL_PATTERNS.some((pattern) => pattern.test(value))) {
|
|
37
|
-
continue;
|
|
38
|
-
}
|
|
39
|
-
if (value.includes('rgba(') || value.includes('#')) {
|
|
40
|
-
continue;
|
|
41
|
-
}
|
|
42
|
-
filtered[key] = value;
|
|
43
|
-
}
|
|
44
|
-
return filtered;
|
|
45
|
-
},
|
|
46
|
-
},
|
|
47
|
-
{
|
|
48
|
-
rule: 'Group by Theme',
|
|
49
|
-
fn: (inputMap: Input): Groups => {
|
|
50
|
-
const groups: Groups = {};
|
|
51
|
-
for (const [key, value] of Object.entries(inputMap)) {
|
|
52
|
-
const parts = key.split(' / ');
|
|
53
|
-
const theme = parts.pop()?.toLowerCase() as 'light' | 'dark';
|
|
54
|
-
const baseName = parts.join(' / ');
|
|
55
|
-
|
|
56
|
-
if (!theme || !['light', 'dark'].includes(theme)) {
|
|
57
|
-
continue;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
if (!groups[baseName]) {
|
|
61
|
-
groups[baseName] = {};
|
|
62
|
-
}
|
|
63
|
-
groups[baseName][theme] = value;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
for (const baseName in groups) {
|
|
67
|
-
if (!groups[baseName].light || !groups[baseName].dark) {
|
|
68
|
-
delete groups[baseName];
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
return groups;
|
|
72
|
-
},
|
|
73
|
-
},
|
|
74
|
-
{
|
|
75
|
-
rule: 'Reorder States',
|
|
76
|
-
fn: (groups: Groups): Groups => {
|
|
77
|
-
const STATE_SUFFIXES = ['Hover', 'Pressed', 'Disabled'];
|
|
78
|
-
const reordered: Groups = {};
|
|
79
|
-
|
|
80
|
-
for (const [baseName, values] of Object.entries(groups)) {
|
|
81
|
-
const parts: string[] = baseName.split('/');
|
|
82
|
-
let newParts = [...parts];
|
|
83
|
-
|
|
84
|
-
const state = parts.find((p: string) => STATE_SUFFIXES.includes(p));
|
|
85
|
-
|
|
86
|
-
if (state) {
|
|
87
|
-
newParts = parts.filter((p: string) => p !== state);
|
|
88
|
-
newParts.push(state);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
reordered[newParts.join('/')] = values;
|
|
92
|
-
}
|
|
93
|
-
return reordered;
|
|
94
|
-
},
|
|
95
|
-
},
|
|
96
|
-
{
|
|
97
|
-
rule: 'Apply Naming',
|
|
98
|
-
fn: (groups: Groups): Output => {
|
|
99
|
-
const output: Output = {};
|
|
100
|
-
for (const [baseName, values] of Object.entries(groups)) {
|
|
101
|
-
const tokenPath = baseName;
|
|
102
|
-
output[slashToCamelCase(tokenPath)] = values as Value;
|
|
103
|
-
}
|
|
104
|
-
return output;
|
|
105
|
-
},
|
|
106
|
-
},
|
|
107
|
-
{
|
|
108
|
-
rule: 'Map to BaseTokens',
|
|
109
|
-
fn: (tokens: Output): Output => {
|
|
110
|
-
const CUSTOMIZABLE_KEYS = Object.keys(customizable);
|
|
111
|
-
const DIRECT_SCALE_BASE_NAMES = ['onbrand', 'gray', 'whitealpha', 'blackalpha'];
|
|
112
|
-
|
|
113
|
-
const transformToken = (rawValue: string): string => {
|
|
114
|
-
const parts = rawValue.split('/');
|
|
115
|
-
if (parts.length >= 2) {
|
|
116
|
-
const last = parts[parts.length - 1];
|
|
117
|
-
const path = parts.slice(0, -1);
|
|
118
|
-
const mainPart = path[0];
|
|
119
|
-
const camelMainPart = slashToCamelCase(mainPart);
|
|
120
|
-
let expression: string = CUSTOMIZABLE_KEYS.includes(camelMainPart) ? 'base.customizable' : 'base';
|
|
121
|
-
|
|
122
|
-
for (const part of path) {
|
|
123
|
-
const camelPart = slashToCamelCase(part);
|
|
124
|
-
|
|
125
|
-
if (part === mainPart && expression === 'base') {
|
|
126
|
-
if (camelPart === 'brand') {
|
|
127
|
-
expression += '.' + camelPart + '.palette?';
|
|
128
|
-
continue;
|
|
129
|
-
} else if (camelPart === 'accent') {
|
|
130
|
-
expression += '.' + camelPart + '?.palette?';
|
|
131
|
-
continue;
|
|
132
|
-
} else if (camelPart === 'onAccent') {
|
|
133
|
-
expression += '.onAccent';
|
|
134
|
-
continue;
|
|
135
|
-
} else if (DIRECT_SCALE_BASE_NAMES.includes(camelPart)) {
|
|
136
|
-
expression += '.' + camelPart;
|
|
137
|
-
continue;
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
expression += '.' + camelPart;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
const lastIsNumber = last.match(/^\d+$/);
|
|
145
|
-
if (lastIsNumber) {
|
|
146
|
-
const isIndexAfterOptionalBase = camelMainPart === 'onAccent' && path.length === 1;
|
|
147
|
-
|
|
148
|
-
if (isIndexAfterOptionalBase) {
|
|
149
|
-
expression += `?.[${last}]`;
|
|
150
|
-
} else {
|
|
151
|
-
expression += `[${last}]`;
|
|
152
|
-
}
|
|
153
|
-
} else {
|
|
154
|
-
expression += '.' + last.toLowerCase();
|
|
155
|
-
}
|
|
156
|
-
return expression;
|
|
157
|
-
}
|
|
158
|
-
return rawValue;
|
|
159
|
-
};
|
|
160
|
-
|
|
161
|
-
const valueToExpression = (value: string): string => {
|
|
162
|
-
let rawValue = value;
|
|
163
|
-
const camelValue = slashToCamelCase(rawValue);
|
|
164
|
-
|
|
165
|
-
if (/^Light\//.test(rawValue) || /^Dark\//.test(rawValue)) {
|
|
166
|
-
const brandValue = TOKENS_BASE_MAP[`${rawValue.replace(/'/g, '')} / Brand`];
|
|
167
|
-
const grayValue = TOKENS_BASE_MAP[`${rawValue.replace(/'/g, '')} / Gray`];
|
|
168
|
-
|
|
169
|
-
rawValue = `${brandValue.replace('Brand', 'Accent')} || ${grayValue}`;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
return rawValue
|
|
173
|
-
.split(' || ')
|
|
174
|
-
.map((item: string) => {
|
|
175
|
-
if (CUSTOMIZABLE_KEYS.includes(camelValue)) {
|
|
176
|
-
return `base.customizable.${camelValue}`;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
if (item === 'Brand/Normal/0') {
|
|
180
|
-
return 'base.brand.promo';
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
if (item === 'Brand/Normal/100') {
|
|
184
|
-
return 'base.brand.original';
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
if (item.includes('Semantic Tokens/Light') && item.includes('Logo')) {
|
|
188
|
-
return 'base.brand.logo.light';
|
|
189
|
-
}
|
|
190
|
-
if (item.includes('Semantic Tokens/Dark') && item.includes('Logo')) {
|
|
191
|
-
return 'base.brand.logo.dark';
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
if (item.includes('Brand')) {
|
|
195
|
-
if (item.includes('Semantic Tokens/Light') && item.includes('Default')) {
|
|
196
|
-
return 'base.brand.original';
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
if (item.includes('Semantic Tokens/Dark') && item.includes('Default')) {
|
|
200
|
-
return 'base.brand.original';
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
if (item.includes('Semantic Tokens/Light') && item.includes('Hover')) {
|
|
204
|
-
return 'base.brand.interactions.hover.light';
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
if (item.includes('Semantic Tokens/Dark') && item.includes('Hover')) {
|
|
208
|
-
return 'base.brand.interactions.hover.dark';
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
if (item.includes('Semantic Tokens/Light') && item.includes('Pressed')) {
|
|
212
|
-
return 'base.brand.interactions.pressed.light';
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
if (item.includes('Semantic Tokens/Dark') && item.includes('Pressed')) {
|
|
216
|
-
return 'base.brand.interactions.pressed.dark';
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
if (item.includes('Accent')) {
|
|
221
|
-
if (item.includes('Semantic Tokens/Light') && item.includes('Default')) {
|
|
222
|
-
return 'base.accent?.original.light';
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
if (item.includes('Semantic Tokens/Dark') && item.includes('Default')) {
|
|
226
|
-
return 'base.accent?.original.dark';
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
if (item.includes('Semantic Tokens/Light') && item.includes('Hover')) {
|
|
230
|
-
return 'base.accent?.interactions.hover.light';
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
if (item.includes('Semantic Tokens/Dark') && item.includes('Hover')) {
|
|
234
|
-
return 'base.accent?.interactions.hover.dark';
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
if (item.includes('Semantic Tokens/Light') && item.includes('Pressed')) {
|
|
238
|
-
return 'base.accent?.interactions.pressed.light';
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
if (item.includes('Semantic Tokens/Dark') && item.includes('Pressed')) {
|
|
242
|
-
return 'base.accent?.interactions.pressed.dark';
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
return transformToken(item);
|
|
247
|
-
})
|
|
248
|
-
.join(' || ');
|
|
249
|
-
};
|
|
250
|
-
|
|
251
|
-
const result: Output = {};
|
|
252
|
-
for (const [key, values] of Object.entries(tokens)) {
|
|
253
|
-
result[key] = {
|
|
254
|
-
light: valueToExpression(values.light),
|
|
255
|
-
dark: valueToExpression(values.dark),
|
|
256
|
-
};
|
|
257
|
-
}
|
|
258
|
-
return result;
|
|
259
|
-
},
|
|
260
|
-
},
|
|
261
|
-
{
|
|
262
|
-
rule: 'Sort Keys',
|
|
263
|
-
fn: (tokens: Output): Entries => {
|
|
264
|
-
const SORT_ORDER = [
|
|
265
|
-
'text',
|
|
266
|
-
'texInverted',
|
|
267
|
-
'textConst',
|
|
268
|
-
'textOnAccent',
|
|
269
|
-
'textOnBrand',
|
|
270
|
-
'shape',
|
|
271
|
-
'shapeInverted',
|
|
272
|
-
'shapeConst',
|
|
273
|
-
'line',
|
|
274
|
-
'lineInverted',
|
|
275
|
-
'lineConst',
|
|
276
|
-
'surface',
|
|
277
|
-
'illustration',
|
|
278
|
-
'customizable',
|
|
279
|
-
];
|
|
280
|
-
|
|
281
|
-
const getSortIndex = (key: string): number => {
|
|
282
|
-
let bestIndex = SORT_ORDER.length;
|
|
283
|
-
let longestMatchLength = 0;
|
|
284
|
-
|
|
285
|
-
for (let i = 0; i < SORT_ORDER.length; i++) {
|
|
286
|
-
const prefix = SORT_ORDER[i];
|
|
287
|
-
const index = i;
|
|
288
|
-
|
|
289
|
-
if (key.startsWith(prefix)) {
|
|
290
|
-
if (key === prefix) {
|
|
291
|
-
return index;
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
if (prefix.length > longestMatchLength) {
|
|
295
|
-
longestMatchLength = prefix.length;
|
|
296
|
-
bestIndex = index;
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
return bestIndex;
|
|
301
|
-
};
|
|
302
|
-
|
|
303
|
-
return Object.entries(tokens).sort(([keyA], [keyB]) => {
|
|
304
|
-
const indexA = getSortIndex(keyA);
|
|
305
|
-
const indexB = getSortIndex(keyB);
|
|
306
|
-
|
|
307
|
-
if (indexA !== indexB) {
|
|
308
|
-
return indexA - indexB;
|
|
309
|
-
}
|
|
310
|
-
return keyA.localeCompare(keyB);
|
|
311
|
-
}) as Entries;
|
|
312
|
-
},
|
|
313
|
-
},
|
|
314
|
-
{
|
|
315
|
-
rule: 'Generate Code',
|
|
316
|
-
fn: (sorted: Entries): string => {
|
|
317
|
-
const generateBody = (theme: 'light' | 'dark') =>
|
|
318
|
-
sorted
|
|
319
|
-
.map(([key, values], i) => ` ${key}: ${values[theme]}${i === sorted.length - 1 ? '' : ','}`)
|
|
320
|
-
.join('\n');
|
|
321
|
-
|
|
322
|
-
const lightBody = generateBody('light');
|
|
323
|
-
const darkBody = generateBody('dark');
|
|
324
|
-
|
|
325
|
-
return `import type { TokensBase } from './types/tokens-base.js';
|
|
326
|
-
|
|
327
|
-
export const getColorsDefaultTokens = (base: TokensBase) => ({
|
|
328
|
-
light: {
|
|
329
|
-
${lightBody}
|
|
330
|
-
},
|
|
331
|
-
dark: {
|
|
332
|
-
${darkBody}
|
|
333
|
-
}
|
|
334
|
-
});
|
|
335
|
-
`;
|
|
336
|
-
},
|
|
337
|
-
},
|
|
338
|
-
];
|
|
339
|
-
|
|
340
|
-
try {
|
|
341
|
-
const inputData = JSON.parse(fs.readFileSync(INPUT_FIGMA_SEMANTIC_PATH, 'utf-8')) as Input;
|
|
342
|
-
fs.writeFileSync(OUTPUT_TOKENS_PATH, extractTokensFromFigma(inputData));
|
|
343
|
-
} catch (error) {
|
|
344
|
-
console.error('An error occurred during token transformation:', error);
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
export function extractTokensFromFigma(figmaJson: Input): string {
|
|
348
|
-
let currentData: any = figmaJson;
|
|
349
|
-
for (const rule of transformations) {
|
|
350
|
-
currentData = rule.fn(currentData);
|
|
351
|
-
}
|
|
352
|
-
return currentData as string;
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
export function slashToCamelCase(str: string): string {
|
|
356
|
-
let processedStr = str.replace(/onbrand/gi, 'On Brand');
|
|
357
|
-
processedStr = processedStr.replace(/[/-]/g, ' ');
|
|
358
|
-
processedStr = processedStr.replace(/([a-z])([A-Z])/g, '$1 $2');
|
|
359
|
-
const parts = processedStr.split(/\s+/).filter(Boolean);
|
|
360
|
-
|
|
361
|
-
if (parts.length === 0) {
|
|
362
|
-
return '';
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
return parts
|
|
366
|
-
.map((part, index) => {
|
|
367
|
-
const lowerPart = part.toLowerCase();
|
|
368
|
-
|
|
369
|
-
if (index === 0) {
|
|
370
|
-
return lowerPart;
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
return lowerPart.charAt(0).toUpperCase() + lowerPart.slice(1);
|
|
374
|
-
})
|
|
375
|
-
.join('');
|
|
376
|
-
}
|