@fibery/ui-kit 1.40.3 → 1.41.0
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/CHANGELOG.md +14 -0
- package/eslint.config.mjs +16 -0
- package/package.json +11 -13
- package/src/a11y-color.test.ts +162 -0
- package/src/a11y-color.ts +13 -20
- package/src/actions-menu/actions-menu-item.tsx +9 -12
- package/src/actions-menu/context-actions-menu.tsx +8 -3
- package/src/ai/model.tsx +0 -1
- package/src/ai/temperature.tsx +0 -1
- package/src/animated-height-container.tsx +3 -3
- package/src/antd/ant-modal.tsx +10 -5
- package/src/antd/index.tsx +3 -0
- package/src/antd/input.tsx +0 -1
- package/src/antd/styles.ts +15 -15
- package/src/app-icon-with-fallback.tsx +6 -6
- package/src/app-icon-wrapper.tsx +2 -3
- package/src/app-icon.tsx +4 -4
- package/src/avatar.tsx +3 -3
- package/src/button/base-button.tsx +0 -1
- package/src/button/button.tsx +1 -0
- package/src/button/make-button-colors.ts +5 -13
- package/src/checkbox.tsx +2 -2
- package/src/collapsible-section.tsx +11 -1
- package/src/color-picker/ColorPickerOrLoader.js +2 -2
- package/src/color-utils.test.ts +307 -0
- package/src/color-utils.ts +215 -0
- package/src/command-menu/index.tsx +1 -0
- package/src/comment.tsx +3 -2
- package/src/context-menu/index.tsx +12 -7
- package/src/date-picker/contexts.ts +2 -2
- package/src/date-picker/date-range-picker.tsx +3 -4
- package/src/date-picker/single-date-picker.tsx +147 -119
- package/src/date-picker/types.ts +4 -3
- package/src/day-select/iso-week-day-select.tsx +2 -2
- package/src/day-select/week-day-select.tsx +2 -2
- package/src/delayed.tsx +0 -1
- package/src/design-system/alpha.test.ts +59 -0
- package/src/design-system/alpha.ts +41 -0
- package/src/design-system/animation.ts +10 -0
- package/src/design-system/colors-css.test.ts +39 -0
- package/src/design-system/colors-css.ts +25 -0
- package/src/design-system/colors-js.test.ts +232 -0
- package/src/design-system/colors-js.ts +107 -0
- package/src/design-system/colors.test.ts +74 -0
- package/src/design-system/colors.ts +759 -0
- package/src/design-system/date.ts +3 -0
- package/src/design-system/layout.ts +106 -0
- package/src/design-system/theme.test.ts +94 -0
- package/src/design-system/theme.ts +93 -0
- package/src/design-system/typography.ts +179 -0
- package/src/design-system/vars.test.ts +1679 -0
- package/src/design-system/vars.ts +52 -0
- package/src/design-system.test.ts +45 -22
- package/src/design-system.ts +25 -1339
- package/src/dropdown-menu/index.tsx +21 -16
- package/src/emoji-picker/app-icon-picker.tsx +5 -5
- package/src/emoji-picker/emoji-picker-content-with-color.tsx +4 -4
- package/src/emoji-picker/icon-emoji-picker.tsx +3 -3
- package/src/favorites-icon.tsx +1 -1
- package/src/fibermoji-placeholder.tsx +3 -3
- package/src/field-container.tsx +11 -2
- package/src/file-item/file-icon.tsx +169 -0
- package/src/file-item/file-menu-items.tsx +68 -0
- package/src/file-item/file-preview-actions.tsx +38 -0
- package/src/file-item/file-title.tsx +48 -0
- package/src/file-item/types.ts +27 -0
- package/src/file-item/use-register-in-image-gallery.tsx +70 -0
- package/src/file-item-2.tsx +32 -348
- package/src/file-item.tsx +6 -3
- package/src/hue-shift.test.ts +91 -0
- package/src/icons/ast/ChatBubble.ts +8 -0
- package/src/icons/ast/ChatFloat.ts +8 -0
- package/src/icons/ast/ChatSidebar.ts +8 -0
- package/src/icons/ast/FileCounter.ts +8 -0
- package/src/icons/ast/FileMultiple.ts +8 -0
- package/src/icons/ast/FileOther.ts +8 -0
- package/src/icons/ast/NoBorder.ts +8 -0
- package/src/icons/ast/NoFill.ts +8 -0
- package/src/icons/ast/ValueEdit.ts +8 -0
- package/src/icons/ast/index.tsx +9 -0
- package/src/icons/react/ChatBubble.tsx +13 -0
- package/src/icons/react/ChatFloat.tsx +13 -0
- package/src/icons/react/ChatSidebar.tsx +13 -0
- package/src/icons/react/FileCounter.tsx +13 -0
- package/src/icons/react/FileMultiple.tsx +13 -0
- package/src/icons/react/FileOther.tsx +13 -0
- package/src/icons/react/NoBorder.tsx +13 -0
- package/src/icons/react/NoFill.tsx +13 -0
- package/src/icons/react/ValueEdit.tsx +13 -0
- package/src/icons/react/index.tsx +9 -0
- package/src/icons/svg/chat-bubble.svg +4 -0
- package/src/icons/svg/chat-float.svg +4 -0
- package/src/icons/svg/chat-sidebar.svg +4 -0
- package/src/icons/svg/file-counter.svg +3 -0
- package/src/icons/svg/file-multiple.svg +3 -0
- package/src/icons/svg/file-other.svg +3 -0
- package/src/icons/svg/no-border.svg +17 -0
- package/src/icons/svg/no-fill.svg +4 -0
- package/src/icons/svg/value-edit.svg +3 -0
- package/src/images-gallery/images-gallery.tsx +8 -6
- package/src/images-gallery/slide-buttons.tsx +4 -11
- package/src/is-iOS.ts +0 -1
- package/src/is-in-popup.ts +2 -1
- package/src/lists/actions-menu-row-surface.tsx +7 -7
- package/src/loading-sausage.tsx +2 -2
- package/src/media-query-utils.ts +1 -2
- package/src/mobile-keyboard-aware-popup.tsx +6 -5
- package/src/modal-menu/modal-menu-content.tsx +2 -2
- package/src/number-input/decimal.js +9 -7
- package/src/number-input/{number-inline-input-with-autosize.tsx → number-input-inline-with-autosize.tsx} +20 -41
- package/src/number-input/number-input-inline.tsx +118 -0
- package/src/number-input/{index.test.js → number-input.test.tsx} +38 -43
- package/src/number-input/number-input.tsx +63 -121
- package/src/number-input/types.ts +19 -0
- package/src/number-input/utils.ts +61 -0
- package/src/online-users.tsx +2 -2
- package/src/palette-generator.test.ts +309 -0
- package/src/palette-generator.ts +160 -0
- package/src/palettes/_.ts +72 -0
- package/src/palettes/slate-arch.colors-snapshot.test.ts +1689 -0
- package/src/palettes/slate-arch.ts +188 -0
- package/src/palettes/slate-user.colors-snapshot.test.ts +1689 -0
- package/src/palettes/slate-user.ts +187 -0
- package/src/palettes/warm-arch.colors-snapshot.test.ts +1689 -0
- package/src/palettes/warm-arch.ts +222 -0
- package/src/palettes/warm-user.colors-snapshot.test.ts +1689 -0
- package/src/palettes/warm-user.ts +222 -0
- package/src/platform.ts +0 -3
- package/src/popover/get-element-ref.ts +28 -0
- package/src/popover/index.tsx +236 -339
- package/src/popover/{mobile-popup-context.tsx → mobile-popover-context.tsx} +7 -7
- package/src/popover/mobile-popover.tsx +169 -0
- package/src/popover/modifiers.tsx +2 -2
- package/src/popover/popup-stack-context.tsx +8 -9
- package/src/progress.tsx +2 -2
- package/src/reactions/reaction-button.tsx +12 -6
- package/src/root-theme-provider.test.tsx +411 -0
- package/src/scale-generator.ts +356 -0
- package/src/select/components/menu-list-virtualized.tsx +12 -25
- package/src/select/components/menu.tsx +12 -2
- package/src/select/index.tsx +6 -5
- package/src/select/select.tsx +38 -39
- package/src/select/styles.ts +0 -1
- package/src/select/util.ts +1 -1
- package/src/static-palettes.ts +356 -0
- package/src/thematic-color-picker.tsx +266 -0
- package/src/thematic-constants.tsx +27 -0
- package/src/thematic-controls.tsx +144 -0
- package/src/thematic-scales.tsx +122 -0
- package/src/thematic-state.ts +370 -0
- package/src/thematic.tsx +386 -0
- package/src/theme-provider.test.tsx +820 -0
- package/src/theme-provider.tsx +158 -92
- package/src/theme-settings.ts +67 -12
- package/src/theme-styles.ts +58 -6
- package/src/toast/toast-action.tsx +1 -1
- package/src/toast/toast.tsx +1 -2
- package/src/toggle-on-off.tsx +2 -2
- package/src/toggle.tsx +7 -8
- package/src/tooltip.tsx +14 -10
- package/src/type-badge.tsx +7 -14
- package/src/unit/styles.ts +2 -25
- package/src/unit/unit-with-tooltip.tsx +3 -2
- package/src/use-is-phone.tsx +7 -2
- package/src/use-long-press.tsx +2 -2
- package/src/use-on-screen-keyboard-data.tsx +2 -2
- package/src/with-data.tsx +4 -3
- package/src/workflow-progress-icon.tsx +2 -2
- package/.eslintignore +0 -3
- package/.eslintrc +0 -14
- package/src/__mocks__/createInlineTheme.js +0 -3
- package/src/create-inline-theme.ts +0 -66
- package/src/number-input/index.js +0 -191
- package/src/palette.ts +0 -237
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
import {getAlpha, OKLCH, parse, to} from "./color-utils";
|
|
2
|
+
import {type ColorScale, makeDefaultPalette, makeThematicPalette} from "./palette-generator";
|
|
3
|
+
import {zeroBasedFromOneBased} from "./scale-generator";
|
|
4
|
+
|
|
5
|
+
function roundToThreeDigits(number: number) {
|
|
6
|
+
return Math.round(number * 1000) / 1000;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function getOklch(color: string): {l: number; c: number; h: number} {
|
|
10
|
+
const oklch = to(parse(color), OKLCH);
|
|
11
|
+
return {
|
|
12
|
+
l: roundToThreeDigits(100 * (oklch.coords[0] ?? 0)),
|
|
13
|
+
c: oklch.coords[1] ?? 0,
|
|
14
|
+
h: oklch.coords[2] ?? 0,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function extractLightness(scale: ColorScale): number[] {
|
|
19
|
+
return zeroBasedFromOneBased(scale).map((color) => getOklch(color).l);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function extractChromaNormalized(scale: ColorScale): number[] {
|
|
23
|
+
const chromas = zeroBasedFromOneBased(scale).map((color) => getOklch(color).c);
|
|
24
|
+
const maxChroma = Math.max(...chromas);
|
|
25
|
+
return chromas.map((c) => roundToThreeDigits(c / maxChroma));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
describe("palette-generator", () => {
|
|
29
|
+
describe("extract curves from default palette", () => {
|
|
30
|
+
it("extracts YELLOW curves", () => {
|
|
31
|
+
const palette = makeDefaultPalette();
|
|
32
|
+
expect(extractLightness(palette.yellow)).toMatchInlineSnapshot(`
|
|
33
|
+
[
|
|
34
|
+
99.15,
|
|
35
|
+
98.666,
|
|
36
|
+
98.103,
|
|
37
|
+
97.119,
|
|
38
|
+
95.34,
|
|
39
|
+
92.061,
|
|
40
|
+
86.841,
|
|
41
|
+
81.273,
|
|
42
|
+
88.224,
|
|
43
|
+
85.516,
|
|
44
|
+
54.771,
|
|
45
|
+
28.162,
|
|
46
|
+
]
|
|
47
|
+
`);
|
|
48
|
+
expect(extractChromaNormalized(palette.yellow)).toMatchInlineSnapshot(`
|
|
49
|
+
[
|
|
50
|
+
0.04,
|
|
51
|
+
0.16,
|
|
52
|
+
0.299,
|
|
53
|
+
0.419,
|
|
54
|
+
0.539,
|
|
55
|
+
0.621,
|
|
56
|
+
0.692,
|
|
57
|
+
0.911,
|
|
58
|
+
1,
|
|
59
|
+
0.962,
|
|
60
|
+
0.624,
|
|
61
|
+
0.231,
|
|
62
|
+
]
|
|
63
|
+
`);
|
|
64
|
+
expect(extractLightness(palette.yellowDark)).toMatchInlineSnapshot(`
|
|
65
|
+
[
|
|
66
|
+
18.975,
|
|
67
|
+
22.715,
|
|
68
|
+
25.994,
|
|
69
|
+
27.672,
|
|
70
|
+
31.467,
|
|
71
|
+
35.323,
|
|
72
|
+
40.577,
|
|
73
|
+
48.421,
|
|
74
|
+
88.224,
|
|
75
|
+
93.814,
|
|
76
|
+
82.593,
|
|
77
|
+
97.801,
|
|
78
|
+
]
|
|
79
|
+
`);
|
|
80
|
+
expect(extractChromaNormalized(palette.yellowDark)).toMatchInlineSnapshot(`
|
|
81
|
+
[
|
|
82
|
+
0.213,
|
|
83
|
+
0.255,
|
|
84
|
+
0.291,
|
|
85
|
+
0.31,
|
|
86
|
+
0.353,
|
|
87
|
+
0.398,
|
|
88
|
+
0.444,
|
|
89
|
+
0.546,
|
|
90
|
+
1,
|
|
91
|
+
0.898,
|
|
92
|
+
0.926,
|
|
93
|
+
0.289,
|
|
94
|
+
]
|
|
95
|
+
`);
|
|
96
|
+
// Extract hue from step 9 (primary yellow)
|
|
97
|
+
expect(getOklch(palette.yellow[9]).h).toMatchInlineSnapshot(`99.99954352835232`);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it("extracts SLATE curves", () => {
|
|
101
|
+
const palette = makeDefaultPalette();
|
|
102
|
+
expect(extractLightness(palette.base)).toMatchInlineSnapshot(`
|
|
103
|
+
[
|
|
104
|
+
99.241,
|
|
105
|
+
97.719,
|
|
106
|
+
96.955,
|
|
107
|
+
94.656,
|
|
108
|
+
93.115,
|
|
109
|
+
91.568,
|
|
110
|
+
89.235,
|
|
111
|
+
82.937,
|
|
112
|
+
69.959,
|
|
113
|
+
57.217,
|
|
114
|
+
48.318,
|
|
115
|
+
20.441,
|
|
116
|
+
]
|
|
117
|
+
`);
|
|
118
|
+
expect(extractLightness(palette.baseDark)).toMatchInlineSnapshot(`
|
|
119
|
+
[
|
|
120
|
+
20.441,
|
|
121
|
+
22.667,
|
|
122
|
+
25.908,
|
|
123
|
+
28.012,
|
|
124
|
+
30.078,
|
|
125
|
+
32.109,
|
|
126
|
+
36.079,
|
|
127
|
+
43.714,
|
|
128
|
+
54.586,
|
|
129
|
+
59.818,
|
|
130
|
+
70.787,
|
|
131
|
+
94.656,
|
|
132
|
+
]
|
|
133
|
+
`);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it("extracts INDIGO curves", () => {
|
|
137
|
+
const palette = makeDefaultPalette();
|
|
138
|
+
expect(extractLightness(palette.accent)).toMatchInlineSnapshot(`
|
|
139
|
+
[
|
|
140
|
+
99.069,
|
|
141
|
+
98.983,
|
|
142
|
+
96.966,
|
|
143
|
+
95.012,
|
|
144
|
+
92.145,
|
|
145
|
+
88.367,
|
|
146
|
+
82.757,
|
|
147
|
+
76.14,
|
|
148
|
+
58.48,
|
|
149
|
+
54.173,
|
|
150
|
+
49.642,
|
|
151
|
+
26.071,
|
|
152
|
+
]
|
|
153
|
+
`);
|
|
154
|
+
expect(extractChromaNormalized(palette.accent)).toMatchInlineSnapshot(`
|
|
155
|
+
[
|
|
156
|
+
0.018,
|
|
157
|
+
0.027,
|
|
158
|
+
0.08,
|
|
159
|
+
0.126,
|
|
160
|
+
0.19,
|
|
161
|
+
0.27,
|
|
162
|
+
0.384,
|
|
163
|
+
0.529,
|
|
164
|
+
0.934,
|
|
165
|
+
1,
|
|
166
|
+
0.878,
|
|
167
|
+
0.44,
|
|
168
|
+
]
|
|
169
|
+
`);
|
|
170
|
+
expect(extractLightness(palette.accentDark)).toMatchInlineSnapshot(`
|
|
171
|
+
[
|
|
172
|
+
20.512,
|
|
173
|
+
22.974,
|
|
174
|
+
27.284,
|
|
175
|
+
29.742,
|
|
176
|
+
32.452,
|
|
177
|
+
35.981,
|
|
178
|
+
41.256,
|
|
179
|
+
47.972,
|
|
180
|
+
57.658,
|
|
181
|
+
62.928,
|
|
182
|
+
76.225,
|
|
183
|
+
95.899,
|
|
184
|
+
]
|
|
185
|
+
`);
|
|
186
|
+
expect(extractChromaNormalized(palette.accentDark)).toMatchInlineSnapshot(`
|
|
187
|
+
[
|
|
188
|
+
0.119,
|
|
189
|
+
0.226,
|
|
190
|
+
0.357,
|
|
191
|
+
0.444,
|
|
192
|
+
0.475,
|
|
193
|
+
0.517,
|
|
194
|
+
0.72,
|
|
195
|
+
0.952,
|
|
196
|
+
1,
|
|
197
|
+
0.921,
|
|
198
|
+
0.601,
|
|
199
|
+
0.111,
|
|
200
|
+
]
|
|
201
|
+
`);
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
describe("white/black scales", () => {
|
|
206
|
+
it("generated neutral scales match default palette", () => {
|
|
207
|
+
const defaultPalette = makeDefaultPalette();
|
|
208
|
+
const generated = makeThematicPalette({
|
|
209
|
+
baseHue: 0,
|
|
210
|
+
baseChroma: 0,
|
|
211
|
+
accentHue: 264,
|
|
212
|
+
accentChroma: 0.16,
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
expect(zeroBasedFromOneBased(generated.whiteA)).toMatchInlineSnapshot(`
|
|
216
|
+
[
|
|
217
|
+
"rgba(255,255,255,0)",
|
|
218
|
+
"rgba(255,255,255,0.01)",
|
|
219
|
+
"rgba(255,255,255,0.03)",
|
|
220
|
+
"rgba(255,255,255,0.06)",
|
|
221
|
+
"rgba(255,255,255,0.09)",
|
|
222
|
+
"rgba(255,255,255,0.12)",
|
|
223
|
+
"rgba(255,255,255,0.18)",
|
|
224
|
+
"rgba(255,255,255,0.25)",
|
|
225
|
+
"rgba(255,255,255,0.39)",
|
|
226
|
+
"rgba(255,255,255,0.45)",
|
|
227
|
+
"rgba(255,255,255,0.59)",
|
|
228
|
+
"rgba(255,255,255,0.92)",
|
|
229
|
+
]
|
|
230
|
+
`);
|
|
231
|
+
expect(zeroBasedFromOneBased(generated.blackA)).toMatchInlineSnapshot(`
|
|
232
|
+
[
|
|
233
|
+
"rgba(0,0,0,0.01)",
|
|
234
|
+
"rgba(0,0,0,0.03)",
|
|
235
|
+
"rgba(0,0,0,0.05)",
|
|
236
|
+
"rgba(0,0,0,0.07)",
|
|
237
|
+
"rgba(0,0,0,0.09)",
|
|
238
|
+
"rgba(0,0,0,0.11)",
|
|
239
|
+
"rgba(0,0,0,0.14)",
|
|
240
|
+
"rgba(0,0,0,0.22)",
|
|
241
|
+
"rgba(0,0,0,0.44)",
|
|
242
|
+
"rgba(0,0,0,0.48)",
|
|
243
|
+
"rgba(0,0,0,0.57)",
|
|
244
|
+
"rgba(0,0,0,0.91)",
|
|
245
|
+
]
|
|
246
|
+
`);
|
|
247
|
+
|
|
248
|
+
const whiteADefault = zeroBasedFromOneBased(defaultPalette.whiteA);
|
|
249
|
+
const blackADefault = zeroBasedFromOneBased(defaultPalette.blackA);
|
|
250
|
+
const whiteAGenerated = zeroBasedFromOneBased(generated.whiteA);
|
|
251
|
+
const blackAGenerated = zeroBasedFromOneBased(generated.blackA);
|
|
252
|
+
// default palette uses hsla strings, generated uses rgba — compare parsed alpha values
|
|
253
|
+
for (let i = 0; i < 12; i++) {
|
|
254
|
+
const defWhiteAlpha = getAlpha(whiteADefault[i]);
|
|
255
|
+
const genWhiteAlpha = getAlpha(whiteAGenerated[i]);
|
|
256
|
+
expect(genWhiteAlpha).toBeCloseTo(defWhiteAlpha, 2);
|
|
257
|
+
|
|
258
|
+
const defBlackAlpha = getAlpha(blackADefault[i]);
|
|
259
|
+
const genBlackAlpha = getAlpha(blackAGenerated[i]);
|
|
260
|
+
expect(genBlackAlpha).toBeCloseTo(defBlackAlpha, 2);
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
describe("white/black tinting across hues", () => {
|
|
266
|
+
it("white/black should be tinted for all hues when chroma > 0", () => {
|
|
267
|
+
const hues = Array.from({length: 36}, (_, i) => i * 10);
|
|
268
|
+
|
|
269
|
+
for (const hue of hues) {
|
|
270
|
+
const palette = makeThematicPalette({baseHue: hue, baseChroma: 0.175, accentHue: hue, accentChroma: 0.16});
|
|
271
|
+
// white should not be pure white
|
|
272
|
+
expect(palette.white).not.toBe("rgb(255,255,255)");
|
|
273
|
+
expect(palette.white).not.toBe("#FFFFFF");
|
|
274
|
+
// black should not be pure black
|
|
275
|
+
expect(palette.black).not.toBe("rgb(0,0,0)");
|
|
276
|
+
expect(palette.black).not.toBe("#000000");
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
describe("createPalette with default config", () => {
|
|
282
|
+
it("should closely match default palette lightness", () => {
|
|
283
|
+
const defaultPalette = makeDefaultPalette();
|
|
284
|
+
const generatedPalette = makeThematicPalette({
|
|
285
|
+
baseHue: 0,
|
|
286
|
+
baseChroma: 0,
|
|
287
|
+
accentHue: 264,
|
|
288
|
+
accentChroma: 0.16,
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
const defaultBase = extractLightness(defaultPalette.base);
|
|
292
|
+
const generatedBase = extractLightness(generatedPalette.base);
|
|
293
|
+
const defaultBaseDark = extractLightness(defaultPalette.baseDark);
|
|
294
|
+
const generatedBaseDark = extractLightness(generatedPalette.baseDark);
|
|
295
|
+
const defaultAccent = extractLightness(defaultPalette.accent);
|
|
296
|
+
const generatedAccent = extractLightness(generatedPalette.accent);
|
|
297
|
+
const defaultAccentDark = extractLightness(defaultPalette.accentDark);
|
|
298
|
+
const generatedAccentDark = extractLightness(generatedPalette.accentDark);
|
|
299
|
+
|
|
300
|
+
// Allow 0.5% lightness tolerance due to color space conversion
|
|
301
|
+
for (let i = 0; i < 12; i++) {
|
|
302
|
+
expect(Math.abs(generatedBase[i] - defaultBase[i])).toBeLessThan(0.5);
|
|
303
|
+
expect(Math.abs(generatedBaseDark[i] - defaultBaseDark[i])).toBeLessThan(0.5);
|
|
304
|
+
expect(Math.abs(generatedAccent[i] - defaultAccent[i])).toBeLessThan(0.5);
|
|
305
|
+
expect(Math.abs(generatedAccentDark[i] - defaultAccentDark[i])).toBeLessThan(0.5);
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
});
|
|
309
|
+
});
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import {slateUserPalette} from "./palettes/slate-user";
|
|
2
|
+
import {slateArchPalette} from "./palettes/slate-arch";
|
|
3
|
+
import {warmUserPalette} from "./palettes/warm-user";
|
|
4
|
+
import {warmArchPalette} from "./palettes/warm-arch";
|
|
5
|
+
import {
|
|
6
|
+
DEFAULT_DARK_CURVES,
|
|
7
|
+
DEFAULT_LIGHT_CURVES,
|
|
8
|
+
generateBlacks,
|
|
9
|
+
generateColorScale,
|
|
10
|
+
generateGrayScale,
|
|
11
|
+
generateInterpolatedScale,
|
|
12
|
+
generatePresetScale,
|
|
13
|
+
generateWhites,
|
|
14
|
+
type InterpolatedScaleConfig,
|
|
15
|
+
OneBasedScale,
|
|
16
|
+
paletteFromHue,
|
|
17
|
+
paletteFromHueBoth,
|
|
18
|
+
SCALE_PRESETS,
|
|
19
|
+
type ScaleConfig,
|
|
20
|
+
type ScaleCurves,
|
|
21
|
+
type ZeroBasedScale,
|
|
22
|
+
} from "./scale-generator";
|
|
23
|
+
|
|
24
|
+
export type ColorScale = OneBasedScale<string>;
|
|
25
|
+
|
|
26
|
+
export interface ThemePalette {
|
|
27
|
+
key: string;
|
|
28
|
+
white: string;
|
|
29
|
+
black: string;
|
|
30
|
+
whiteA: ColorScale;
|
|
31
|
+
blackA: ColorScale;
|
|
32
|
+
base: ColorScale;
|
|
33
|
+
baseDark: ColorScale;
|
|
34
|
+
accent: ColorScale;
|
|
35
|
+
accentDark: ColorScale;
|
|
36
|
+
red: ColorScale;
|
|
37
|
+
redDark: ColorScale;
|
|
38
|
+
teal: ColorScale;
|
|
39
|
+
tealDark: ColorScale;
|
|
40
|
+
yellow: ColorScale;
|
|
41
|
+
yellowDark: ColorScale;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export const defaultPaletteKey = slateUserPalette.key;
|
|
45
|
+
|
|
46
|
+
export function makeDefaultPalette(): ThemePalette {
|
|
47
|
+
return slateUserPalette;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function selectPalette({
|
|
51
|
+
warmLook = false,
|
|
52
|
+
isArchitect = false,
|
|
53
|
+
}: {
|
|
54
|
+
warmLook?: boolean;
|
|
55
|
+
isArchitect?: boolean;
|
|
56
|
+
} = {}): ThemePalette {
|
|
57
|
+
if (warmLook) {
|
|
58
|
+
return isArchitect ? warmArchPalette : warmUserPalette;
|
|
59
|
+
} else {
|
|
60
|
+
return isArchitect ? slateArchPalette : slateUserPalette;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export type ThematicPrefs = {
|
|
65
|
+
baseHue?: number;
|
|
66
|
+
baseChroma?: number;
|
|
67
|
+
accentHue: number;
|
|
68
|
+
accentChroma: number;
|
|
69
|
+
contrast?: number;
|
|
70
|
+
whiteLuminosity?: number;
|
|
71
|
+
whiteChroma?: number;
|
|
72
|
+
blackLuminosity?: number;
|
|
73
|
+
blackChroma?: number;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
function shiftHueTowards(hue: number, targetHue: number, amount: number): number {
|
|
77
|
+
// Find shortest path on hue circle
|
|
78
|
+
let diff = targetHue - hue;
|
|
79
|
+
if (diff > 180) {
|
|
80
|
+
diff -= 360;
|
|
81
|
+
} else if (diff < -180) {
|
|
82
|
+
diff += 360;
|
|
83
|
+
}
|
|
84
|
+
return (hue + diff * amount + 360) % 360;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function makeThematicPalette(config: ThematicPrefs): ThemePalette {
|
|
88
|
+
const {baseHue = 0, baseChroma = 0, accentHue, accentChroma, contrast} = config;
|
|
89
|
+
const {white, whiteA} = generateWhites(baseHue, baseChroma, config.whiteLuminosity, config.whiteChroma);
|
|
90
|
+
const {black, blackA} = generateBlacks(baseHue, baseChroma, config.blackLuminosity, config.blackChroma);
|
|
91
|
+
|
|
92
|
+
// Generate base/gray scales with tinted base colors
|
|
93
|
+
const base = generateGrayScale({hue: baseHue, chroma: baseChroma, mode: "light", contrast});
|
|
94
|
+
const baseDark = generateGrayScale({hue: baseHue, chroma: baseChroma, mode: "dark", contrast});
|
|
95
|
+
|
|
96
|
+
// Generate accent scales
|
|
97
|
+
const accent = generateColorScale({hue: accentHue, chroma: accentChroma, mode: "light", contrast});
|
|
98
|
+
const accentDark = generateColorScale({hue: accentHue, chroma: accentChroma, mode: "dark", contrast});
|
|
99
|
+
|
|
100
|
+
// Shift semantic hues towards base hue when baseChroma is high
|
|
101
|
+
const hueShift = Math.min(baseChroma * 2, 0.3); // max 30% shift at chroma 0.15+
|
|
102
|
+
const redHue = shiftHueTowards(SCALE_PRESETS.red.hue, baseHue, hueShift);
|
|
103
|
+
const tealHue = shiftHueTowards(SCALE_PRESETS.teal.hue, baseHue, hueShift);
|
|
104
|
+
const yellowHue = shiftHueTowards(SCALE_PRESETS.yellow.hue, baseHue, hueShift);
|
|
105
|
+
|
|
106
|
+
const red = generateColorScale({hue: redHue, chroma: SCALE_PRESETS.red.chroma, mode: "light", contrast});
|
|
107
|
+
const redDark = generateColorScale({hue: redHue, chroma: SCALE_PRESETS.red.chroma, mode: "dark", contrast});
|
|
108
|
+
const teal = generateColorScale({hue: tealHue, chroma: SCALE_PRESETS.teal.chroma, mode: "light", contrast});
|
|
109
|
+
const tealDark = generateColorScale({hue: tealHue, chroma: SCALE_PRESETS.teal.chroma, mode: "dark", contrast});
|
|
110
|
+
const yellow = generateColorScale({
|
|
111
|
+
hue: yellowHue,
|
|
112
|
+
chroma: SCALE_PRESETS.yellow.chroma,
|
|
113
|
+
mode: "light",
|
|
114
|
+
contrast,
|
|
115
|
+
curves: SCALE_PRESETS.yellow.lightCurves,
|
|
116
|
+
});
|
|
117
|
+
const yellowDark = generateColorScale({
|
|
118
|
+
hue: yellowHue,
|
|
119
|
+
chroma: SCALE_PRESETS.yellow.chroma,
|
|
120
|
+
mode: "dark",
|
|
121
|
+
contrast,
|
|
122
|
+
curves: SCALE_PRESETS.yellow.darkCurves,
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
key: JSON.stringify(config),
|
|
127
|
+
white,
|
|
128
|
+
black,
|
|
129
|
+
whiteA,
|
|
130
|
+
blackA,
|
|
131
|
+
base,
|
|
132
|
+
baseDark,
|
|
133
|
+
accent,
|
|
134
|
+
accentDark,
|
|
135
|
+
red,
|
|
136
|
+
redDark,
|
|
137
|
+
teal,
|
|
138
|
+
tealDark,
|
|
139
|
+
yellow,
|
|
140
|
+
yellowDark,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export {
|
|
145
|
+
generateBlacks,
|
|
146
|
+
generateColorScale,
|
|
147
|
+
generateGrayScale,
|
|
148
|
+
generateInterpolatedScale,
|
|
149
|
+
generatePresetScale,
|
|
150
|
+
generateWhites,
|
|
151
|
+
paletteFromHue,
|
|
152
|
+
paletteFromHueBoth,
|
|
153
|
+
SCALE_PRESETS,
|
|
154
|
+
DEFAULT_LIGHT_CURVES,
|
|
155
|
+
DEFAULT_DARK_CURVES,
|
|
156
|
+
type ZeroBasedScale,
|
|
157
|
+
type ScaleConfig,
|
|
158
|
+
type ScaleCurves,
|
|
159
|
+
type InterpolatedScaleConfig,
|
|
160
|
+
};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import {makeThemeDefs} from "../design-system/colors";
|
|
2
|
+
import {HSL, isValidColor, OKLCH, produceColor, sRGB} from "../color-utils";
|
|
3
|
+
import {ThemePalette} from "../palette-generator";
|
|
4
|
+
|
|
5
|
+
function verboseColor(color: string) {
|
|
6
|
+
return `${produceColor(color, [], sRGB)} / ${produceColor(color, [], OKLCH)} / ${produceColor(color, [], HSL)}`;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const COLOR_PREFIXES = ["rgba(", "rgb(", "hsla(", "hsl("] as const;
|
|
10
|
+
const HEX_CHARS = "0123456789abcdefABCDEF";
|
|
11
|
+
|
|
12
|
+
function replaceColorsInString(str: string) {
|
|
13
|
+
let result = "";
|
|
14
|
+
let i = 0;
|
|
15
|
+
const seen = new Set<string>();
|
|
16
|
+
while (i < str.length) {
|
|
17
|
+
const position = i;
|
|
18
|
+
const prefix = COLOR_PREFIXES.find((p) => str.startsWith(p, position));
|
|
19
|
+
if (prefix) {
|
|
20
|
+
const closeIdx = str.indexOf(")", i + prefix.length);
|
|
21
|
+
if (closeIdx !== -1) {
|
|
22
|
+
const candidate = str.slice(i, closeIdx + 1);
|
|
23
|
+
if (isValidColor(candidate)) {
|
|
24
|
+
const verbose = verboseColor(candidate);
|
|
25
|
+
result += seen.has(verbose) ? "[SAME]" : verbose;
|
|
26
|
+
seen.add(verbose);
|
|
27
|
+
i = closeIdx + 1;
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
if (str[i] === "#") {
|
|
33
|
+
let end = i + 1;
|
|
34
|
+
while (end < str.length && HEX_CHARS.includes(str[end])) {
|
|
35
|
+
end++;
|
|
36
|
+
}
|
|
37
|
+
const candidate = str.slice(i, end);
|
|
38
|
+
if (candidate.length >= 4 && isValidColor(candidate)) {
|
|
39
|
+
const verbose = verboseColor(candidate);
|
|
40
|
+
result += seen.has(verbose) ? "[SAME]" : verbose;
|
|
41
|
+
seen.add(verbose);
|
|
42
|
+
i = end;
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
result += str[i];
|
|
47
|
+
i++;
|
|
48
|
+
}
|
|
49
|
+
return result;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function deepMapColors(value: unknown): unknown {
|
|
53
|
+
if (typeof value === "string") {
|
|
54
|
+
if (isValidColor(value)) {
|
|
55
|
+
return verboseColor(value);
|
|
56
|
+
}
|
|
57
|
+
return replaceColorsInString(value);
|
|
58
|
+
}
|
|
59
|
+
if (Array.isArray(value)) {
|
|
60
|
+
return value.map(deepMapColors);
|
|
61
|
+
}
|
|
62
|
+
if (typeof value === "object" && value !== null) {
|
|
63
|
+
return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, deepMapColors(v)]));
|
|
64
|
+
}
|
|
65
|
+
return value;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function colors(palette: ThemePalette) {
|
|
69
|
+
const {colors} = makeThemeDefs(palette);
|
|
70
|
+
|
|
71
|
+
return deepMapColors(colors);
|
|
72
|
+
}
|