@skbkontur/colors 2.0.0 → 2.0.1
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/.gitignore +10 -0
- package/.npmignore +10 -0
- package/CHANGELOG.md +117 -0
- package/__docs__/Colors.docs.stories.tsx +1578 -0
- package/__docs__/Colors.mdx +228 -0
- package/__docs__/ColorsAPI.docs.stories.tsx +954 -0
- package/__docs__/ColorsAPI.mdx +133 -0
- package/__stories__/colors.stories.tsx +452 -0
- package/__tests__/convert-color.test.ts +23 -0
- package/__tests__/create-tokens-from-figma.test.ts +162 -0
- package/__tests__/format-variable.test.ts +16 -0
- package/__tests__/get-colors-base.test.ts +55 -0
- package/__tests__/get-colors.test.ts +75 -0
- package/__tests__/get-interactions.test.ts +37 -0
- package/__tests__/get-logo.test.ts +24 -0
- package/__tests__/get-palette.test.ts +43 -0
- package/__tests__/get-promo.test.ts +32 -0
- package/colors-default-dark.d.ts +319 -0
- package/colors-default-dark.js +319 -0
- package/colors-default-dark.ts +332 -0
- package/colors-default-light.d.ts +319 -0
- package/colors-default-light.js +319 -0
- package/colors-default-light.ts +336 -0
- package/package.json +25 -28
- package/scripts/create-tokens-files.ts +424 -0
- package/scripts/create-tokens-from-figma.ts +376 -0
- package/scripts/figma-tokens-base.json +3499 -0
- package/scripts/figma-tokens.json +710 -0
- package/tsconfig.json +8 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import * as ColorsCustomAPI from './ColorsAPI.docs.stories';
|
|
2
|
+
import { Stories } from '@storybook/blocks';
|
|
3
|
+
import { Meta } from '../../../.storybook-docs/Meta';
|
|
4
|
+
|
|
5
|
+
<Meta of={ColorsCustomAPI} />
|
|
6
|
+
|
|
7
|
+
# Kontur Colors API
|
|
8
|
+
|
|
9
|
+
<style>{`
|
|
10
|
+
.docs-link {
|
|
11
|
+
position: relative;
|
|
12
|
+
display: inline-flex;
|
|
13
|
+
gap: 6px;
|
|
14
|
+
padding: 4px 8px;
|
|
15
|
+
align-items: center;
|
|
16
|
+
transition: 0.15s;
|
|
17
|
+
border-radius: 8px;
|
|
18
|
+
font-size: 16px;
|
|
19
|
+
text-decoration: none !important;
|
|
20
|
+
margin-top: 12px;
|
|
21
|
+
margin-bottom: 12px;
|
|
22
|
+
margin-left: -8px;
|
|
23
|
+
margin-right: 8px;
|
|
24
|
+
}
|
|
25
|
+
.docs-link img {
|
|
26
|
+
width: 16px;
|
|
27
|
+
height: 16px;
|
|
28
|
+
}
|
|
29
|
+
.docs-link:hover {
|
|
30
|
+
background: rgba(0, 0, 0, 0.06);
|
|
31
|
+
}
|
|
32
|
+
.docs-link:active {
|
|
33
|
+
background: rgba(0, 0, 0, 0.1);
|
|
34
|
+
}
|
|
35
|
+
.ep__pageLayout {
|
|
36
|
+
min-height: 720px;
|
|
37
|
+
}
|
|
38
|
+
`}</style>
|
|
39
|
+
|
|
40
|
+
<a
|
|
41
|
+
className="docs-link"
|
|
42
|
+
href="https://guides.kontur.ru/principles/base/color/"
|
|
43
|
+
target="_blank"
|
|
44
|
+
children={
|
|
45
|
+
<>
|
|
46
|
+
<img src="https://tech.skbkontur.ru/kontur-ui/favicon.svg" alt="" /> Гайд
|
|
47
|
+
</>
|
|
48
|
+
}
|
|
49
|
+
/>
|
|
50
|
+
<a
|
|
51
|
+
className="docs-link"
|
|
52
|
+
href="https://www.figma.com/design/XVgPCAAFhEbIiQDDznkdat/%F0%9F%9A%A7-Kontur-Colors?m=auto&t=cg9j7F8DpdELh2px-6"
|
|
53
|
+
target="_blank"
|
|
54
|
+
children={
|
|
55
|
+
<>
|
|
56
|
+
<img src="https://static.figma.com/app/icon/1/favicon.svg" alt="" /> Figma
|
|
57
|
+
</>
|
|
58
|
+
}
|
|
59
|
+
/>
|
|
60
|
+
<a
|
|
61
|
+
className="docs-link"
|
|
62
|
+
target="_blank"
|
|
63
|
+
href="https://git.skbkontur.ru/ui/ui-parking-2/-/tree/master/packages/colors"
|
|
64
|
+
children={
|
|
65
|
+
<>
|
|
66
|
+
<img
|
|
67
|
+
src="https://git.skbkontur.ru/assets/favicon-72a2cad5025aa931d6ea56c3201d1f18e68a8cd39788c7c80d5b2b82aa5143ef.png"
|
|
68
|
+
alt=""
|
|
69
|
+
/>{' '}
|
|
70
|
+
GitLab
|
|
71
|
+
</>
|
|
72
|
+
}
|
|
73
|
+
/>
|
|
74
|
+
|
|
75
|
+
Библиотека Colors содержит JS API для продвинутых сценариев:
|
|
76
|
+
|
|
77
|
+
- Генерация палитр для произвольных HEX-цветов, а не только брендовых
|
|
78
|
+
- Создание/расширение цветовых палитр с собственными семантическими токенами
|
|
79
|
+
- Генерация цветов в форматах для разных платформ (oklch, hex, rgba и др.)
|
|
80
|
+
- Перекраска интерфейсов в любой цвет на лету
|
|
81
|
+
|
|
82
|
+
<br />
|
|
83
|
+
|
|
84
|
+
## Генерация палитр
|
|
85
|
+
|
|
86
|
+
С помощью функции `getColors` можно переопределять настройки генератора цветов:
|
|
87
|
+
|
|
88
|
+
- **brand** — брендовый цвет `red | orange | green | mint | blue | blueDeep | violet | purple | #custom-hex`
|
|
89
|
+
- **accent** — акцентный цвет `gray | brand | #custom-hex` (по умолчанию gray)
|
|
90
|
+
- **theme** — тема `light | dark` (по умолчанию light)
|
|
91
|
+
- **system** — настройка образцов цветов состояний warning/error/success
|
|
92
|
+
- **overrides** — переопределение/добавление токенов со ссылкой на базовые палитры
|
|
93
|
+
- **format** — формат цвета `'hex/rgba' | 'oklch' | 'hex-aarrggbb'` (по умолчанию hex/rgba)
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
getColors({
|
|
97
|
+
brand: 'red' | 'orange' | 'green' | 'mint' | 'blue' | 'blueDeep' | 'violet' | 'purple' | string,
|
|
98
|
+
accent: 'gray' | 'brand' | string
|
|
99
|
+
theme: 'light' | 'dark',
|
|
100
|
+
system?: {
|
|
101
|
+
warning?: string,
|
|
102
|
+
error?: string,
|
|
103
|
+
success?: string,
|
|
104
|
+
},
|
|
105
|
+
overrides?: (base, defaults, params) => {
|
|
106
|
+
light: {
|
|
107
|
+
...defaults.light,
|
|
108
|
+
// Кастомные токены светлой темы
|
|
109
|
+
};
|
|
110
|
+
dark: {
|
|
111
|
+
...defaults.dark,
|
|
112
|
+
// Кастомные токены тёмной темы
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
format?: 'hex/rgba' | 'oklch' | 'hex-aarrggbb'
|
|
116
|
+
});
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Функция `getColors` возвращает JS-объект с токенами `colors`:
|
|
120
|
+
|
|
121
|
+
```js
|
|
122
|
+
getColors({ brand: '#FE4C4C', accent: 'brand', theme: 'dark', format: 'oklch' });
|
|
123
|
+
|
|
124
|
+
// ->
|
|
125
|
+
{
|
|
126
|
+
textNeutralHeavy: 'oklch(100% 0 0 / 0.96)',
|
|
127
|
+
shapePaleBrand: 'oklch(36% 0.079 25)',
|
|
128
|
+
shapePaleBrandHover: 'oklch(40% 0.091 25)',
|
|
129
|
+
...
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
<Stories title="" includePrimary={true} />
|
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { brand as brandSwatch } from '../lib/consts/default-swatch';
|
|
3
|
+
import { getColors } from '../lib/get-colors';
|
|
4
|
+
import { getColorsBase } from '../lib/get-colors-base';
|
|
5
|
+
import { DropdownMenu, MenuHeader, MenuItem, Toast } from '@skbkontur/react-ui';
|
|
6
|
+
|
|
7
|
+
type ColorValue = string;
|
|
8
|
+
type ThemeValues = { light: ColorValue; dark: ColorValue };
|
|
9
|
+
|
|
10
|
+
interface TokenPair {
|
|
11
|
+
key: string;
|
|
12
|
+
value: ThemeValues;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface ColorSection {
|
|
16
|
+
[key: string]: ColorValue | ColorSection;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface BaseTokenSection {
|
|
20
|
+
[key: string]: ColorValue | BaseTokenSection;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface BaseTokensObject {
|
|
24
|
+
[key: string]: BaseTokenSection | { [key: string]: BaseTokenSection };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export default {
|
|
28
|
+
title: 'Colors',
|
|
29
|
+
parameters: {
|
|
30
|
+
creevey: {
|
|
31
|
+
skip: true,
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export const SemanticTokensStory = () => {
|
|
37
|
+
const allConfigs = Object.keys(brandSwatch).flatMap((color) =>
|
|
38
|
+
['brand', 'gray'].map((accent) => ({ color, accent }))
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
const cssStyles = `
|
|
42
|
+
.configTitle {
|
|
43
|
+
position: sticky;
|
|
44
|
+
top: 0;
|
|
45
|
+
margin: 0;
|
|
46
|
+
padding: 4px 16px;
|
|
47
|
+
font-size: 16px;
|
|
48
|
+
font-weight: 700;
|
|
49
|
+
color: #1f2937;
|
|
50
|
+
background: #fff;
|
|
51
|
+
border-bottom: 1px solid #d1d5db;
|
|
52
|
+
z-index: 11;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.groupContainer {
|
|
56
|
+
margin-bottom: 48px;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.groupTitle {
|
|
60
|
+
position: sticky;
|
|
61
|
+
top: 28px;
|
|
62
|
+
z-index: 10;
|
|
63
|
+
padding: 4px 0;
|
|
64
|
+
background: white;
|
|
65
|
+
border-bottom: 1px solid #d1d5db;
|
|
66
|
+
margin: 0 0 10px 0;
|
|
67
|
+
padding: 4px 16px;
|
|
68
|
+
font-size: 14px;
|
|
69
|
+
font-weight: 600;
|
|
70
|
+
color: #4b5563;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.grid {
|
|
74
|
+
display: grid;
|
|
75
|
+
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
|
76
|
+
gap: 12px;
|
|
77
|
+
padding: 8px 16px;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.tokenCard {
|
|
81
|
+
box-sizing: border-box;
|
|
82
|
+
display: flex;
|
|
83
|
+
flex-direction: column;
|
|
84
|
+
align-items: flex-start;
|
|
85
|
+
width: 100%;
|
|
86
|
+
padding: 8px;
|
|
87
|
+
border: 1px solid #d1d5db;
|
|
88
|
+
background: #ffffff;
|
|
89
|
+
text-align: left;
|
|
90
|
+
transition: all 0.2s ease-in-out;
|
|
91
|
+
margin: 0;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.tokenName {
|
|
95
|
+
font-weight: 600;
|
|
96
|
+
line-height: 1.4;
|
|
97
|
+
margin-bottom: 8px;
|
|
98
|
+
word-break: break-word;
|
|
99
|
+
font-size: 13px;
|
|
100
|
+
color: #1f2937;
|
|
101
|
+
width: 100%;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.colorTilesContainer {
|
|
105
|
+
display: flex;
|
|
106
|
+
width: 100%;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.colorThemeWrapper {
|
|
110
|
+
display: flex;
|
|
111
|
+
flex-direction: column;
|
|
112
|
+
align-items: flex-start;
|
|
113
|
+
flex-grow: 1;
|
|
114
|
+
width: 50%;
|
|
115
|
+
padding: 8px;
|
|
116
|
+
border: 1px solid rgba(0,0,0,.1);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.colorThemeWrapper.light {
|
|
120
|
+
background: transparent;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.colorThemeWrapper.dark {
|
|
124
|
+
background: #1f2937;
|
|
125
|
+
color: #d1d5db;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.colorSquare {
|
|
129
|
+
width: 100%;
|
|
130
|
+
height: 32px;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.colorSquare.light {
|
|
134
|
+
box-shadow: 0 0 0 1px rgba(0 0 0 / 0.1);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.colorSquare.dark {
|
|
138
|
+
box-shadow: 0 0 0 1px rgba(255 255 255 / 0.2);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.colorValue {
|
|
142
|
+
font-size: 11px;
|
|
143
|
+
color: #6b7280;
|
|
144
|
+
margin-top: 6px;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.colorValue.dark {
|
|
148
|
+
color: #d1d5db;
|
|
149
|
+
}
|
|
150
|
+
`;
|
|
151
|
+
|
|
152
|
+
const transformTokens = (themes: any) => {
|
|
153
|
+
const result = {} as any;
|
|
154
|
+
|
|
155
|
+
for (const [themeName, tokens] of Object.entries(themes)) {
|
|
156
|
+
for (const [tokenName, value] of Object.entries(tokens as any)) {
|
|
157
|
+
if (!result[tokenName]) {
|
|
158
|
+
result[tokenName] = {};
|
|
159
|
+
}
|
|
160
|
+
result[tokenName][themeName] = value;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return result;
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
return (
|
|
168
|
+
<>
|
|
169
|
+
<style>{cssStyles}</style>
|
|
170
|
+
{allConfigs.map(({ color, accent }) => {
|
|
171
|
+
const grouped = transformTokens({
|
|
172
|
+
light: getColors({
|
|
173
|
+
brand: color,
|
|
174
|
+
accent,
|
|
175
|
+
theme: 'light',
|
|
176
|
+
}),
|
|
177
|
+
dark: getColors({
|
|
178
|
+
brand: color,
|
|
179
|
+
accent,
|
|
180
|
+
theme: 'dark',
|
|
181
|
+
}),
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
return (
|
|
185
|
+
<div key={`${color}-${accent}`}>
|
|
186
|
+
<h2 className="configTitle">
|
|
187
|
+
Brand: {color} · Accent: {accent}
|
|
188
|
+
</h2>
|
|
189
|
+
|
|
190
|
+
<div className="grid">
|
|
191
|
+
{Object.entries(grouped).map(([key, value]) => (
|
|
192
|
+
<div className="tokenCard" key={key}>
|
|
193
|
+
<div className="tokenName">{key}</div>
|
|
194
|
+
|
|
195
|
+
<div className="colorTilesContainer">
|
|
196
|
+
<div className="colorThemeWrapper light">
|
|
197
|
+
<div
|
|
198
|
+
title={`Light: ${value.light}`}
|
|
199
|
+
className="colorSquare light"
|
|
200
|
+
style={{ backgroundColor: value.light }}
|
|
201
|
+
/>
|
|
202
|
+
<small className="colorValue">{value.light}</small>
|
|
203
|
+
</div>
|
|
204
|
+
|
|
205
|
+
<div className="colorThemeWrapper dark">
|
|
206
|
+
<div
|
|
207
|
+
title={`Dark: ${value.dark}`}
|
|
208
|
+
className="colorSquare dark"
|
|
209
|
+
style={{ backgroundColor: value.dark }}
|
|
210
|
+
/>
|
|
211
|
+
<small className="colorValue dark">{value.dark}</small>
|
|
212
|
+
</div>
|
|
213
|
+
</div>
|
|
214
|
+
</div>
|
|
215
|
+
))}
|
|
216
|
+
</div>
|
|
217
|
+
</div>
|
|
218
|
+
);
|
|
219
|
+
})}
|
|
220
|
+
</>
|
|
221
|
+
);
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
SemanticTokensStory.storyName = 'Semantic tokens';
|
|
225
|
+
|
|
226
|
+
export const BaseTokensStory = () => {
|
|
227
|
+
const isObject = (val: unknown): val is Record<string, unknown> =>
|
|
228
|
+
typeof val === 'object' && val !== null && !Array.isArray(val);
|
|
229
|
+
|
|
230
|
+
const generateBaseTokenList = (tokens: BaseTokensObject): TokenPair[] => {
|
|
231
|
+
const tokenList: TokenPair[] = [];
|
|
232
|
+
|
|
233
|
+
const traverseTokens = (section: BaseTokenSection, pathPrefix: string) => {
|
|
234
|
+
for (const key in section) {
|
|
235
|
+
if (!Object.prototype.hasOwnProperty.call(section, key)) {
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const currentPath = pathPrefix ? `${pathPrefix}.${key}` : key;
|
|
240
|
+
const value = section[key];
|
|
241
|
+
|
|
242
|
+
const isNode = isObject(value);
|
|
243
|
+
const isLeaf = typeof value === 'string';
|
|
244
|
+
|
|
245
|
+
if (isNode) {
|
|
246
|
+
traverseTokens(value, currentPath);
|
|
247
|
+
} else if (isLeaf) {
|
|
248
|
+
tokenList.push({
|
|
249
|
+
key: currentPath,
|
|
250
|
+
value: {
|
|
251
|
+
light: value,
|
|
252
|
+
dark: value,
|
|
253
|
+
},
|
|
254
|
+
} as TokenPair);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
for (const rootKey in tokens) {
|
|
260
|
+
if (rootKey !== 'customizable' && Object.prototype.hasOwnProperty.call(tokens, rootKey)) {
|
|
261
|
+
traverseTokens(tokens[rootKey] as BaseTokenSection, rootKey);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (tokens.customizable) {
|
|
266
|
+
for (const colorKey in tokens.customizable) {
|
|
267
|
+
if (Object.prototype.hasOwnProperty.call(tokens.customizable, colorKey)) {
|
|
268
|
+
traverseTokens(tokens.customizable[colorKey] as BaseTokenSection, `customizable.${colorKey}`);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return tokenList;
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
const groupTokensByRoot = (tokens: TokenPair[]): Record<string, TokenPair[]> =>
|
|
277
|
+
tokens.reduce((acc, t) => {
|
|
278
|
+
const parts = t.key.split('.');
|
|
279
|
+
const rootKey = parts[0] === 'customizable' ? `${parts[0]}.${parts[1]}` : parts[0];
|
|
280
|
+
|
|
281
|
+
acc[rootKey] = [...(acc[rootKey] || []), t];
|
|
282
|
+
return acc;
|
|
283
|
+
}, {} as Record<string, TokenPair[]>);
|
|
284
|
+
|
|
285
|
+
const copyColor = (v: string) => {
|
|
286
|
+
window.navigator.clipboard.writeText(v);
|
|
287
|
+
Toast.push('Скопировано', null, 1000);
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
const allConfigs = Object.keys(brandSwatch).flatMap((color) =>
|
|
291
|
+
['brand', 'gray'].map((accent) => ({ color, accent }))
|
|
292
|
+
);
|
|
293
|
+
|
|
294
|
+
const cssStyles = `
|
|
295
|
+
.configTitle {
|
|
296
|
+
position: sticky;
|
|
297
|
+
top: 0;
|
|
298
|
+
margin: 0;
|
|
299
|
+
padding: 4px 16px;
|
|
300
|
+
font-size: 16px;
|
|
301
|
+
font-weight: 700;
|
|
302
|
+
color: #1f2937;
|
|
303
|
+
background: #fff;
|
|
304
|
+
border-bottom: 1px solid #d1d5db;
|
|
305
|
+
z-index: 11;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
.groupContainer {
|
|
309
|
+
margin-bottom: 48px;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
.groupTitle {
|
|
313
|
+
position: sticky;
|
|
314
|
+
top: 28px;
|
|
315
|
+
z-index: 10;
|
|
316
|
+
padding: 4px 0;
|
|
317
|
+
background: white;
|
|
318
|
+
border-bottom: 1px solid #d1d5db;
|
|
319
|
+
margin: 0 0 10px 0;
|
|
320
|
+
padding: 4px 16px;
|
|
321
|
+
font-size: 14px;
|
|
322
|
+
font-weight: 600;
|
|
323
|
+
color: #4b5563;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
.grid {
|
|
327
|
+
display: grid;
|
|
328
|
+
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
|
|
329
|
+
gap: 12px;
|
|
330
|
+
padding: 8px 16px;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
.tokenCard {
|
|
334
|
+
display: flex;
|
|
335
|
+
flex-direction: column;
|
|
336
|
+
align-items: flex-start;
|
|
337
|
+
padding: 8px;
|
|
338
|
+
border: 1px solid #d1d5db;
|
|
339
|
+
background: #ffffff;
|
|
340
|
+
text-align: left;
|
|
341
|
+
transition: all 0.2s ease-in-out;
|
|
342
|
+
margin: 0;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
.tokenName {
|
|
346
|
+
font-weight: 600;
|
|
347
|
+
line-height: 1.4;
|
|
348
|
+
margin-bottom: 8px;
|
|
349
|
+
word-break: break-word;
|
|
350
|
+
font-size: 13px;
|
|
351
|
+
color: #1f2937;
|
|
352
|
+
width: 100%;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
.colorTilesContainer {
|
|
356
|
+
display: flex;
|
|
357
|
+
width: 100%;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
.colorThemeWrapper {
|
|
361
|
+
display: flex;
|
|
362
|
+
flex-direction: column;
|
|
363
|
+
align-items: flex-start;
|
|
364
|
+
flex-grow: 1;
|
|
365
|
+
width: 50%;
|
|
366
|
+
padding: 8px;
|
|
367
|
+
border: 1px solid rgba(0,0,0,.1);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
.colorThemeWrapper.light {
|
|
371
|
+
background: transparent;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
.colorThemeWrapper.dark {
|
|
375
|
+
background: #1f2937;
|
|
376
|
+
color: #d1d5db;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
.colorSquare {
|
|
380
|
+
width: 100%;
|
|
381
|
+
height: 32px;
|
|
382
|
+
box-shadow: 0 0 0 1px rgba(0 0 0 / 0.1);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
.colorValue {
|
|
386
|
+
font-size: 11px;
|
|
387
|
+
color: #6b7280;
|
|
388
|
+
margin-top: 6px;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
.singleColorWrapper {
|
|
392
|
+
flex-grow: 1;
|
|
393
|
+
width: 100%;
|
|
394
|
+
padding: 8px;
|
|
395
|
+
border: 1px solid rgba(0,0,0,.1);
|
|
396
|
+
background: transparent;
|
|
397
|
+
color: #1f2937;
|
|
398
|
+
}
|
|
399
|
+
.singleColorWrapper .colorValue {
|
|
400
|
+
color: #1f2937;
|
|
401
|
+
}
|
|
402
|
+
`;
|
|
403
|
+
|
|
404
|
+
return (
|
|
405
|
+
<>
|
|
406
|
+
<style>{cssStyles}</style>
|
|
407
|
+
{allConfigs.map(({ color, accent }) => {
|
|
408
|
+
const baseTokens = getColorsBase({ brand: color, accent }) as unknown as BaseTokensObject;
|
|
409
|
+
const grouped = groupTokensByRoot(generateBaseTokenList(baseTokens));
|
|
410
|
+
|
|
411
|
+
return (
|
|
412
|
+
<div key={`${color}-${accent}`}>
|
|
413
|
+
<h2 className="configTitle">
|
|
414
|
+
Brand: {color} · Accent: {accent}
|
|
415
|
+
</h2>
|
|
416
|
+
|
|
417
|
+
{Object.entries(grouped).map(([rK, tokens]) => (
|
|
418
|
+
<div key={rK} className="groupContainer">
|
|
419
|
+
<h3 className="groupTitle">{rK}</h3>
|
|
420
|
+
|
|
421
|
+
<div className="grid">
|
|
422
|
+
{tokens.map(({ key, value }) => {
|
|
423
|
+
return (
|
|
424
|
+
<div key={key}>
|
|
425
|
+
<div className="tokenCard">
|
|
426
|
+
<div className="tokenName">{key}</div>
|
|
427
|
+
|
|
428
|
+
<div className="colorTilesContainer">
|
|
429
|
+
<div className="singleColorWrapper">
|
|
430
|
+
<div
|
|
431
|
+
title={`${value.light}`}
|
|
432
|
+
className="colorSquare light"
|
|
433
|
+
style={{ backgroundColor: value.light }}
|
|
434
|
+
/>
|
|
435
|
+
<small className="colorValue">{value.light}</small>
|
|
436
|
+
</div>
|
|
437
|
+
</div>
|
|
438
|
+
</div>
|
|
439
|
+
</div>
|
|
440
|
+
);
|
|
441
|
+
})}
|
|
442
|
+
</div>
|
|
443
|
+
</div>
|
|
444
|
+
))}
|
|
445
|
+
</div>
|
|
446
|
+
);
|
|
447
|
+
})}
|
|
448
|
+
</>
|
|
449
|
+
);
|
|
450
|
+
};
|
|
451
|
+
|
|
452
|
+
BaseTokensStory.storyName = 'Base tokens';
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { test, expect } from 'vitest';
|
|
2
|
+
import { convertColorFormat } from '../lib/utils/convert-color.js';
|
|
3
|
+
|
|
4
|
+
const mockTokens = {
|
|
5
|
+
primary: 'oklch(0.6 0.2 250)',
|
|
6
|
+
accent: 'oklch(0.6 0.2 250 / 0.5)',
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
test('should convert oklch to hex/rgba by default', () => {
|
|
10
|
+
const result = convertColorFormat(mockTokens) as any;
|
|
11
|
+
expect(result.primary).toMatch(/^#[0-9a-f]{6}$/i);
|
|
12
|
+
expect(result.accent).toContain('rgba(');
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
test('should return oklch as is when format is oklch', () => {
|
|
16
|
+
const result = convertColorFormat(mockTokens, 'oklch');
|
|
17
|
+
expect(result).toEqual(mockTokens);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test('should convert to hex-aarrggbb format', () => {
|
|
21
|
+
const result = convertColorFormat(mockTokens, 'hex-aarrggbb') as any;
|
|
22
|
+
expect(result.primary).toMatch(/^#[0-9A-F]{6}$/);
|
|
23
|
+
});
|