@fibery/ui-kit 1.40.2 → 1.40.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/package.json +4 -7
- package/src/__snapshots__/design-system.test.ts.snap +7265 -0
- package/src/a11y-color.test.ts +181 -0
- package/src/a11y-color.ts +13 -20
- package/src/actions-menu/actions-menu-item.tsx +7 -7
- package/src/antd/ant-modal.tsx +10 -5
- package/src/antd/styles.ts +6 -6
- package/src/app-icon-with-fallback.tsx +2 -2
- package/src/app-icon.tsx +2 -2
- package/src/button/button.tsx +1 -0
- package/src/button/make-button-colors.ts +5 -13
- package/src/checkbox.tsx +2 -2
- package/src/color-picker/ColorPickerOrLoader.js +2 -2
- package/src/color-utils.test.ts +317 -0
- package/src/color-utils.ts +180 -0
- package/src/comment.tsx +3 -2
- package/src/context-menu/index.tsx +12 -7
- package/src/create-inline-theme.ts +9 -8
- package/src/design-system.colors.ts +760 -0
- package/src/design-system.test.ts +287 -7
- package/src/design-system.ts +146 -940
- 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 +2 -2
- package/src/favorites-icon.tsx +1 -1
- 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 +35 -345
- package/src/file-item.tsx +6 -2
- package/src/hue-shift.test.ts +91 -0
- package/src/icons/Icon.tsx +1 -0
- package/src/icons/ast/FileCounter.ts +8 -0
- package/src/icons/ast/FileMultiple.ts +8 -0
- package/src/icons/ast/Print.ts +8 -0
- package/src/icons/ast/ValueEdit.ts +8 -0
- package/src/icons/ast/index.tsx +4 -0
- package/src/icons/react/Dividers.tsx +1 -1
- package/src/icons/react/FileCounter.tsx +13 -0
- package/src/icons/react/FileMultiple.tsx +13 -0
- package/src/icons/react/Print.tsx +13 -0
- package/src/icons/react/ValueEdit.tsx +13 -0
- package/src/icons/react/index.tsx +4 -0
- package/src/icons/svg/file-counter.svg +3 -0
- package/src/icons/svg/file-multiple.svg +3 -0
- package/src/icons/svg/print.svg +3 -0
- package/src/icons/svg/value-edit.svg +3 -0
- package/src/images-gallery/slide-buttons.tsx +4 -11
- package/src/lists/actions-menu-row-surface.tsx +5 -5
- package/src/mobile-keyboard-aware-popup.tsx +4 -3
- package/src/palette-generator.test.ts +566 -0
- package/src/palette-generator.ts +166 -0
- package/src/palette.ts +71 -55
- package/src/platform.ts +0 -3
- package/src/popover/index.tsx +20 -28
- package/src/progress.tsx +2 -2
- package/src/reactions/reaction-button.tsx +12 -6
- package/src/root-theme-provider.test.tsx +316 -0
- package/src/scale-generator.ts +347 -0
- package/src/select/components/menu-list-virtualized.tsx +7 -22
- package/src/select/components/menu.tsx +12 -2
- package/src/select/select.tsx +2 -1
- package/src/select/styles.ts +0 -1
- package/src/static-palettes.ts +146 -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 +333 -0
- package/src/thematic.tsx +382 -0
- package/src/theme-provider.test.tsx +808 -0
- package/src/theme-provider.tsx +132 -69
- package/src/theme-settings.ts +1 -1
- package/src/theme-styles.ts +12 -5
- package/src/toast/toast-action.tsx +1 -1
- package/src/toggle-on-off.tsx +2 -2
- package/src/toggle.tsx +5 -6
- package/src/tooltip.tsx +13 -10
- package/src/type-badge.tsx +3 -3
- package/src/unit/styles.ts +2 -2
- package/src/unit/unit-with-tooltip.tsx +3 -2
- package/src/use-long-press.tsx +2 -2
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
/* eslint-disable max-lines */
|
|
2
|
+
import {
|
|
3
|
+
fromHSL,
|
|
4
|
+
fromOKLCH,
|
|
5
|
+
getAlpha,
|
|
6
|
+
getColorDistance,
|
|
7
|
+
getContrastRatio,
|
|
8
|
+
getHsl,
|
|
9
|
+
getLuminance,
|
|
10
|
+
isValidColor,
|
|
11
|
+
OKLCH,
|
|
12
|
+
produceColor,
|
|
13
|
+
toCss,
|
|
14
|
+
} from "./color-utils";
|
|
15
|
+
|
|
16
|
+
describe("color-utils", () => {
|
|
17
|
+
describe("css", () => {
|
|
18
|
+
it("normalizes hex to rgb", () => {
|
|
19
|
+
expect(toCss("#ff0000")).toBe("rgb(255,0,0)");
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("normalizes named colors to rgb", () => {
|
|
23
|
+
expect(toCss("red")).toBe("rgb(255,0,0)");
|
|
24
|
+
expect(toCss("blue")).toBe("rgb(0,0,255)");
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("preserves alpha in rgba", () => {
|
|
28
|
+
expect(toCss("rgba(255, 0, 0, 0.5)")).toBe("rgba(255,0,0,0.5)");
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("converts hsl to rgb", () => {
|
|
32
|
+
expect(toCss("hsl(0, 100%, 50%)")).toBe("rgb(255,0,0)");
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("returns fallback for invalid color", () => {
|
|
36
|
+
expect(toCss("invalid")).toBe("rgb(73,120,212)");
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
describe("isValidColor", () => {
|
|
41
|
+
it("returns true for valid hex colors", () => {
|
|
42
|
+
expect(isValidColor("#ff0000")).toBe(true);
|
|
43
|
+
expect(isValidColor("#f00")).toBe(true);
|
|
44
|
+
expect(isValidColor("#FF0000")).toBe(true);
|
|
45
|
+
expect(isValidColor("#ff000080")).toBe(true);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("returns true for valid rgb/rgba colors", () => {
|
|
49
|
+
expect(isValidColor("rgb(255, 0, 0)")).toBe(true);
|
|
50
|
+
expect(isValidColor("rgba(255, 0, 0, 0.5)")).toBe(true);
|
|
51
|
+
expect(isValidColor("rgb(255,0,0)")).toBe(true);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("returns true for valid hsl/hsla colors", () => {
|
|
55
|
+
expect(isValidColor("hsl(0, 100%, 50%)")).toBe(true);
|
|
56
|
+
expect(isValidColor("hsla(0, 100%, 50%, 0.5)")).toBe(true);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("returns true for named colors", () => {
|
|
60
|
+
expect(isValidColor("red")).toBe(true);
|
|
61
|
+
expect(isValidColor("blue")).toBe(true);
|
|
62
|
+
expect(isValidColor("white")).toBe(true);
|
|
63
|
+
expect(isValidColor("black")).toBe(true);
|
|
64
|
+
expect(isValidColor("cornflowerblue")).toBe(true);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("returns true for transparent", () => {
|
|
68
|
+
expect(isValidColor("transparent")).toBe(true);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("returns false for invalid colors", () => {
|
|
72
|
+
expect(isValidColor("invalid")).toBe(false);
|
|
73
|
+
expect(isValidColor("not-a-color")).toBe(false);
|
|
74
|
+
expect(isValidColor("")).toBe(false);
|
|
75
|
+
expect(isValidColor("#gggggg")).toBe(false);
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
describe("getAlpha", () => {
|
|
80
|
+
it("returns 1 for solid colors", () => {
|
|
81
|
+
expect(getAlpha("#ff0000")).toBe(1);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("returns 1 for solid rgb colors", () => {
|
|
85
|
+
expect(getAlpha("rgb(255, 0, 0)")).toBe(1);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("returns correct alpha for rgba", () => {
|
|
89
|
+
expect(getAlpha("rgba(255, 0, 0, 0.5)")).toBeCloseTo(0.5, 2);
|
|
90
|
+
expect(getAlpha("rgba(255, 0, 0, 0)")).toBe(0);
|
|
91
|
+
expect(getAlpha("rgba(255, 0, 0, 1)")).toBe(1);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("returns correct alpha for hsla colors", () => {
|
|
95
|
+
expect(getAlpha("hsla(0, 100%, 50%, 0.7)")).toBeCloseTo(0.7, 2);
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
describe("toCss", () => {
|
|
100
|
+
it("converts to rgb for opaque colors", () => {
|
|
101
|
+
const result = toCss("#ff0000");
|
|
102
|
+
expect(result).toMatch(/rgb\(255,\s*0,\s*0\)/);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it("converts to rgba for colors with alpha", () => {
|
|
106
|
+
const result = toCss("rgba(255, 0, 0, 0.5)");
|
|
107
|
+
expect(result).toMatch(/rgba\(255,\s*0,\s*0,\s*0\.5\)/);
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
describe("getContrastRatio", () => {
|
|
112
|
+
it("returns 21 for black on white", () => {
|
|
113
|
+
expect(getContrastRatio("#000000", "#ffffff")).toBeCloseTo(21, 0);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it("returns 21 for white on black", () => {
|
|
117
|
+
expect(getContrastRatio("#ffffff", "#000000")).toBeCloseTo(21, 0);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it("returns 1 for same colors", () => {
|
|
121
|
+
expect(getContrastRatio("#808080", "#808080")).toBeCloseTo(1, 0);
|
|
122
|
+
expect(getContrastRatio("#ff0000", "#ff0000")).toBeCloseTo(1, 0);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it("returns symmetric results", () => {
|
|
126
|
+
const ratio1 = getContrastRatio("#ff0000", "#00ff00");
|
|
127
|
+
const ratio2 = getContrastRatio("#00ff00", "#ff0000");
|
|
128
|
+
expect(ratio1).toBeCloseTo(ratio2, 5);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it("returns expected values for known pairs", () => {
|
|
132
|
+
// Red on white should have moderate contrast
|
|
133
|
+
const redWhite = getContrastRatio("#ff0000", "#ffffff");
|
|
134
|
+
expect(redWhite).toBeGreaterThan(3);
|
|
135
|
+
expect(redWhite).toBeLessThan(5);
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
describe("getColorDistance", () => {
|
|
140
|
+
it("returns 0 for same colors", () => {
|
|
141
|
+
expect(getColorDistance("#ff0000", "#ff0000")).toBe(0);
|
|
142
|
+
expect(getColorDistance("red", "red")).toBe(0);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it("returns large value for very different colors", () => {
|
|
146
|
+
// Red and green are perceptually very different
|
|
147
|
+
const distance = getColorDistance("#ff0000", "#00ff00");
|
|
148
|
+
expect(distance).toBeGreaterThan(50);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it("returns small value for similar colors", () => {
|
|
152
|
+
const distance = getColorDistance("#ff0000", "#ff1100");
|
|
153
|
+
expect(distance).toBeLessThan(10);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it("is symmetric", () => {
|
|
157
|
+
const d1 = getColorDistance("#ff0000", "#0000ff");
|
|
158
|
+
const d2 = getColorDistance("#0000ff", "#ff0000");
|
|
159
|
+
expect(d1).toBeCloseTo(d2, 5);
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
describe("getLuminance", () => {
|
|
164
|
+
it("returns 1 for white", () => {
|
|
165
|
+
expect(getLuminance("#ffffff")).toBeCloseTo(1, 2);
|
|
166
|
+
expect(getLuminance("white")).toBeCloseTo(1, 2);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it("returns 0 for black", () => {
|
|
170
|
+
expect(getLuminance("#000000")).toBeCloseTo(0, 2);
|
|
171
|
+
expect(getLuminance("black")).toBeCloseTo(0, 2);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it("returns ~0.5 for middle gray", () => {
|
|
175
|
+
// Note: luminance is perceptual, not linear
|
|
176
|
+
// Middle gray (#808080) has luminance ~0.216
|
|
177
|
+
expect(getLuminance("#808080")).toBeGreaterThan(0.1);
|
|
178
|
+
expect(getLuminance("#808080")).toBeLessThan(0.3);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it("returns values between 0 and 1", () => {
|
|
182
|
+
expect(getLuminance("#ff0000")).toBeGreaterThanOrEqual(0);
|
|
183
|
+
expect(getLuminance("#ff0000")).toBeLessThanOrEqual(1);
|
|
184
|
+
expect(getLuminance("#00ff00")).toBeGreaterThanOrEqual(0);
|
|
185
|
+
expect(getLuminance("#00ff00")).toBeLessThanOrEqual(1);
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
describe("hsl", () => {
|
|
190
|
+
it("creates red at hue 0", () => {
|
|
191
|
+
expect(fromHSL(0, 1, 0.5)).toMatchInlineSnapshot(`"rgb(255,0,0)"`);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it("creates green at hue 120", () => {
|
|
195
|
+
const color = fromHSL(120, 1, 0.5);
|
|
196
|
+
expect(color).toMatchInlineSnapshot(`"rgb(0,255,0)"`);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it("creates blue at hue 240", () => {
|
|
200
|
+
const color = fromHSL(240, 1, 0.5);
|
|
201
|
+
expect(color).toMatchInlineSnapshot(`"rgb(0,0,255)"`);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it("creates white at lightness 1", () => {
|
|
205
|
+
const color = fromHSL(0, 1, 1);
|
|
206
|
+
expect(color).toMatchInlineSnapshot(`"rgb(255,255,255)"`);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it("creates black at lightness 0", () => {
|
|
210
|
+
const color = fromHSL(0, 1, 0);
|
|
211
|
+
expect(color).toMatchInlineSnapshot(`"rgb(0,0,0)"`);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it("creates gray at saturation 0", () => {
|
|
215
|
+
const color = fromHSL(0, 0, 0.5);
|
|
216
|
+
expect(color).toMatchInlineSnapshot(`"rgb(128,128,128)"`);
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
describe("fromLCH (OKLCH)", () => {
|
|
221
|
+
it("creates color from OKLCH values", () => {
|
|
222
|
+
expect(fromOKLCH(0.592, 0.16, 264)).toMatchInlineSnapshot(`"rgb(76,120,220)"`);
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it("creates white at L=1 with zero chroma", () => {
|
|
226
|
+
expect(fromOKLCH(1, 0, 0)).toMatchInlineSnapshot(`"rgb(255,255,255)"`);
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
it("creates black at L=0", () => {
|
|
230
|
+
expect(fromOKLCH(0, 0, 0)).toMatchInlineSnapshot(`"rgb(0,0,0)"`);
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it("creates gray at mid-lightness with zero chroma", () => {
|
|
234
|
+
expect(fromOKLCH(0.5, 0, 0)).toMatchInlineSnapshot(`"rgb(99,99,99)"`);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
it("creates saturated red at hue ~29", () => {
|
|
238
|
+
expect(fromOKLCH(0.628, 0.258, 29)).toMatchInlineSnapshot(`"rgb(255,0,3)"`);
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
it("creates saturated blue at hue ~264", () => {
|
|
242
|
+
expect(fromOKLCH(0.452, 0.313, 264)).toMatchInlineSnapshot(`"rgb(0,1,255)"`);
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
it("higher chroma produces more saturated colors", () => {
|
|
246
|
+
expect(fromOKLCH(0.6, 0.05, 264)).toMatchInlineSnapshot(`"rgb(113,128,159)"`);
|
|
247
|
+
expect(fromOKLCH(0.6, 0.2, 264)).toMatchInlineSnapshot(`"rgb(64,118,245)"`);
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
it("gamut maps out-of-range colors", () => {
|
|
251
|
+
expect(fromOKLCH(0.7, 0.4, 150)).toMatchInlineSnapshot(`"rgb(0,194,72)"`);
|
|
252
|
+
expect(fromOKLCH(0.8, 0.4, 150)).toMatchInlineSnapshot(`"rgb(0,231,90)"`);
|
|
253
|
+
expect(fromOKLCH(0.9, 0.4, 150)).toMatchInlineSnapshot(`"rgb(65,255,135)"`);
|
|
254
|
+
expect(fromOKLCH(1, 0.4, 150)).toMatchInlineSnapshot(`"rgb(255,255,255)"`);
|
|
255
|
+
});
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
describe("getHsl", () => {
|
|
259
|
+
it("extracts HSL from red", () => {
|
|
260
|
+
const [h, s, l] = getHsl("#ff0000");
|
|
261
|
+
expect(h).toBeCloseTo(0, 0);
|
|
262
|
+
expect(s).toBeCloseTo(1, 2);
|
|
263
|
+
expect(l).toBeCloseTo(0.5, 2);
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
it("extracts HSL from green", () => {
|
|
267
|
+
const [h, s, l] = getHsl("#00ff00");
|
|
268
|
+
expect(h).toBeCloseTo(120, 0);
|
|
269
|
+
expect(s).toBeCloseTo(1, 2);
|
|
270
|
+
expect(l).toBeCloseTo(0.5, 2);
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
it("extracts HSL from blue", () => {
|
|
274
|
+
const [h, s, l] = getHsl("#0000ff");
|
|
275
|
+
expect(h).toBeCloseTo(240, 0);
|
|
276
|
+
expect(s).toBeCloseTo(1, 2);
|
|
277
|
+
expect(l).toBeCloseTo(0.5, 2);
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
it("extracts HSL from white", () => {
|
|
281
|
+
const [, , l] = getHsl("#ffffff");
|
|
282
|
+
expect(l).toBeCloseTo(1, 2);
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
it("extracts HSL from black", () => {
|
|
286
|
+
const [, l] = getHsl("#000000");
|
|
287
|
+
expect(l).toBeCloseTo(0, 2);
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
it("returns 0 for undefined hue (grayscale)", () => {
|
|
291
|
+
const [h] = getHsl("#808080");
|
|
292
|
+
expect(h).toBe(0); // NaN converted to 0
|
|
293
|
+
});
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
describe("produceColor", () => {
|
|
297
|
+
it("outputs oklch format when OKLCH space is specified", () => {
|
|
298
|
+
expect(produceColor("#ff0000", [], OKLCH)).toMatchInlineSnapshot(`"oklch(62.796% 0.25768 29.234)"`);
|
|
299
|
+
expect(produceColor("rgb(0, 128, 255)", [], OKLCH)).toMatchInlineSnapshot(`"oklch(61.517% 0.21082 256.1)"`);
|
|
300
|
+
expect(produceColor("oklch(61.517% 0.21082 256.1)", [], OKLCH)).toMatchInlineSnapshot(
|
|
301
|
+
`"oklch(61.517% 0.21082 256.1)"`
|
|
302
|
+
);
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
it("parses display-p3 colors correctly (P3 space removed for bundle size)", () => {
|
|
306
|
+
expect(produceColor("color(display-p3 0.992 0.992 0.978)", [], OKLCH)).toMatchInlineSnapshot(
|
|
307
|
+
`"oklch(99.285% 0.00516 106.49)"`
|
|
308
|
+
);
|
|
309
|
+
expect(produceColor("color(display-p3 0.995 0.99 0.922)", [], OKLCH)).toMatchInlineSnapshot(
|
|
310
|
+
`"oklch(98.841% 0.02505 102.92)"`
|
|
311
|
+
);
|
|
312
|
+
expect(produceColor("color(display-p3 0.997 0.982 0.749)", [], OKLCH)).toMatchInlineSnapshot(
|
|
313
|
+
`"oklch(97.402% 0.08449 104.36)"`
|
|
314
|
+
);
|
|
315
|
+
});
|
|
316
|
+
});
|
|
317
|
+
});
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import type {ColorConstructor, Coords, PlainColorObject} from "colorjs.io/fn";
|
|
2
|
+
import {
|
|
3
|
+
ColorSpace,
|
|
4
|
+
contrastWCAG21,
|
|
5
|
+
deltaE76,
|
|
6
|
+
getLuminance as getLuminanceUnsafe,
|
|
7
|
+
HSL,
|
|
8
|
+
Lab,
|
|
9
|
+
LCH,
|
|
10
|
+
mix,
|
|
11
|
+
OKLCH,
|
|
12
|
+
P3,
|
|
13
|
+
parse,
|
|
14
|
+
serialize,
|
|
15
|
+
set,
|
|
16
|
+
sRGB,
|
|
17
|
+
to,
|
|
18
|
+
toGamut,
|
|
19
|
+
} from "colorjs.io/fn";
|
|
20
|
+
|
|
21
|
+
ColorSpace.register(sRGB);
|
|
22
|
+
ColorSpace.register(HSL);
|
|
23
|
+
ColorSpace.register(Lab);
|
|
24
|
+
ColorSpace.register(LCH);
|
|
25
|
+
ColorSpace.register(OKLCH);
|
|
26
|
+
ColorSpace.register(P3);
|
|
27
|
+
|
|
28
|
+
type Color = PlainColorObject | ColorConstructor;
|
|
29
|
+
|
|
30
|
+
function colorSafe(color: string, fallback = "#4978D4"): Color {
|
|
31
|
+
try {
|
|
32
|
+
return parse(color);
|
|
33
|
+
} catch {
|
|
34
|
+
return parse(fallback);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const coords = (a: number, b: number, c: number): Coords => [a, b, c];
|
|
39
|
+
const clamp01 = (v: number | null): number => Math.max(0, Math.min(1, v ?? 0));
|
|
40
|
+
const white: PlainColorObject = {space: sRGB, coords: [1, 1, 1], alpha: 1};
|
|
41
|
+
const black: PlainColorObject = {space: sRGB, coords: [0, 0, 0], alpha: 1};
|
|
42
|
+
|
|
43
|
+
function serializesRGB(color: Color): string {
|
|
44
|
+
const rgb = toGamut(to(color, sRGB), {space: sRGB});
|
|
45
|
+
const r = Math.round(clamp01(rgb.coords[0]) * 255);
|
|
46
|
+
const g = Math.round(clamp01(rgb.coords[1]) * 255);
|
|
47
|
+
const b = Math.round(clamp01(rgb.coords[2]) * 255);
|
|
48
|
+
const a = rgb.alpha ?? 1;
|
|
49
|
+
return a < 1 ? `rgba(${r},${g},${b},${a})` : `rgb(${r},${g},${b})`;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
type ColorTransform = (color: PlainColorObject) => PlainColorObject | void;
|
|
53
|
+
|
|
54
|
+
export function produceColor(color: string, transforms: ColorTransform | ColorTransform[], space?: ColorSpace): string {
|
|
55
|
+
let c = to(colorSafe(color), sRGB);
|
|
56
|
+
for (const transform of Array.isArray(transforms) ? transforms : [transforms]) {
|
|
57
|
+
const result = transform(c);
|
|
58
|
+
if (result !== undefined) {
|
|
59
|
+
c = result;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
// TODO make sRGB default space and apply colorjs.io outputs CSS Level 4 format (rgb(100% 0% 0%))
|
|
63
|
+
// instead of compatible format (rgb(255,0,0))
|
|
64
|
+
if (space === undefined) {
|
|
65
|
+
return serializesRGB(c);
|
|
66
|
+
}
|
|
67
|
+
return serialize(to(c, space));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* @deprecated chroma-js compatibility artifact, try using newer apis
|
|
72
|
+
* Set relative luminance by binary-search interpolation toward white/black in sRGB.
|
|
73
|
+
* This targets relative luminance (Y in XYZ), not perceptual lightness.
|
|
74
|
+
*/
|
|
75
|
+
export function setLuminanceChroma(color: PlainColorObject, target: number): PlainColorObject {
|
|
76
|
+
const curLum = getLuminanceUnsafe(color);
|
|
77
|
+
if (Math.abs(curLum - target) < 1e-7) {
|
|
78
|
+
return color;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const endpoint = target > curLum ? white : black;
|
|
82
|
+
let lo = 0;
|
|
83
|
+
let hi = 1;
|
|
84
|
+
|
|
85
|
+
for (let i = 0; i < 12; i++) {
|
|
86
|
+
const mid = (lo + hi) / 2;
|
|
87
|
+
const lum = getLuminanceUnsafe(mix(color, endpoint, mid, {space: sRGB}));
|
|
88
|
+
if (target > curLum ? lum < target : lum > target) {
|
|
89
|
+
lo = mid;
|
|
90
|
+
} else {
|
|
91
|
+
hi = mid;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const result = mix(color, endpoint, (lo + hi) / 2, {space: sRGB});
|
|
96
|
+
result.alpha = color.alpha ?? 1;
|
|
97
|
+
return result;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* @deprecated chroma-js compatibility artifact, try using newer apis
|
|
102
|
+
* Darken by reducing L* in Lab color space using colorjs.io set().
|
|
103
|
+
* Matches chroma-js: darken(1) reduces Lab L* by 18 units.
|
|
104
|
+
* Negative L* values are handled by toGamut during serialization.
|
|
105
|
+
*/
|
|
106
|
+
export function darkenChroma(color: PlainColorObject, amount: number): PlainColorObject {
|
|
107
|
+
return set(color, "lab.l", (l) => l - amount * 18);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* @deprecated chroma-js compatibility artifact, try using newer apis
|
|
112
|
+
* Desaturate by reducing C (chroma) in LCH color space using colorjs.io set().
|
|
113
|
+
* Matches chroma-js: desaturate(1) reduces LCH C by 18 units.
|
|
114
|
+
* Negative C values are handled by toGamut during serialization.
|
|
115
|
+
*/
|
|
116
|
+
export function desaturateChroma(color: PlainColorObject, amount: number): PlainColorObject {
|
|
117
|
+
return set(color, "lch.c", (c) => c - amount * 18);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function setAlpha(color: PlainColorObject, value: number): void {
|
|
121
|
+
color.alpha = value;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function isValidColor(color: string): boolean {
|
|
125
|
+
if (color === "transparent") {
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
try {
|
|
129
|
+
parse(color);
|
|
130
|
+
return true;
|
|
131
|
+
} catch {
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function getAlpha(color: string): number {
|
|
137
|
+
return colorSafe(color).alpha ?? 1;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export function getContrastRatio(color1: string, color2: string): number {
|
|
141
|
+
return contrastWCAG21(colorSafe(color1), colorSafe(color2));
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function getColorDistance(color1: string, color2: string): number {
|
|
145
|
+
return deltaE76(colorSafe(color1), colorSafe(color2));
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export function getLuminance(color: string): number {
|
|
149
|
+
return getLuminanceUnsafe(colorSafe(color));
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export function fromHSL(h: number, s: number, l: number): string {
|
|
153
|
+
return serializesRGB({space: HSL, coords: coords(h, s * 100, l * 100), alpha: null});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export function fromOKLCH(L: number, C: number, H: number, alpha = 1): string {
|
|
157
|
+
return serializesRGB({space: OKLCH, coords: [L, C, H] as [number, number, number], alpha});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export function toOKLCH(color: string) {
|
|
161
|
+
return produceColor(color, [], OKLCH);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export function getHsl(color: string): [number, number, number] {
|
|
165
|
+
const c = to(colorSafe(color), HSL);
|
|
166
|
+
return [c.coords[0] || 0, (c.coords[1] || 0) / 100, (c.coords[2] || 0) / 100];
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/** @deprecated do not use that, chroma-js compatibility */
|
|
170
|
+
export function clampRgb(color: PlainColorObject): PlainColorObject {
|
|
171
|
+
const rgb = to(color, sRGB);
|
|
172
|
+
rgb.coords = rgb.coords.map(clamp01) as Coords;
|
|
173
|
+
return rgb;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export function toCss(color: string): string {
|
|
177
|
+
return produceColor(color, []);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export {ColorSpace, mix, OKLCH, parse, set, sRGB, to, HSL, Lab, LCH};
|
package/src/comment.tsx
CHANGED
|
@@ -6,7 +6,7 @@ import {border, iconSize, space, textStyles, themeVars, transition} from "./desi
|
|
|
6
6
|
import {formatDateFromNow} from "./format-date-from-now";
|
|
7
7
|
import {LoadingSausage, LoadingSausageList} from "./loading-sausage";
|
|
8
8
|
import {Tooltip} from "./tooltip";
|
|
9
|
-
import {
|
|
9
|
+
import {useIsPhone} from "./use-is-phone";
|
|
10
10
|
|
|
11
11
|
export const commentContentStyle = css`
|
|
12
12
|
${{
|
|
@@ -115,6 +115,7 @@ export const Comment = memo(
|
|
|
115
115
|
|
|
116
116
|
const {formatted, relative} = formatDateFromNow(date, {format: dateTimeFormat});
|
|
117
117
|
const IconComponent = icon ? icon : null;
|
|
118
|
+
const isPhone = useIsPhone();
|
|
118
119
|
return (
|
|
119
120
|
<div
|
|
120
121
|
ref={ref}
|
|
@@ -153,7 +154,7 @@ export const Comment = memo(
|
|
|
153
154
|
<div
|
|
154
155
|
className={cx(
|
|
155
156
|
actionsClassName,
|
|
156
|
-
(actionsShowed ||
|
|
157
|
+
(actionsShowed || isPhone) &&
|
|
157
158
|
css`
|
|
158
159
|
display: flex;
|
|
159
160
|
`
|
|
@@ -4,6 +4,7 @@ import * as ContextMenuPrimitive from "@radix-ui/react-context-menu";
|
|
|
4
4
|
import {forwardRef} from "react";
|
|
5
5
|
import {border, layout, space, textStyles, themeVars} from "../design-system";
|
|
6
6
|
import {mobileRootSelector} from "../mobile-styles";
|
|
7
|
+
import {ThemeProvider} from "../theme-provider";
|
|
7
8
|
|
|
8
9
|
export const Root = (contextMenuProps: ContextMenuPrimitive.ContextMenuProps) => (
|
|
9
10
|
<ContextMenuPrimitive.Root dir="ltr" {...contextMenuProps} />
|
|
@@ -94,12 +95,14 @@ export type ContentProps = ContextMenuPrimitive.ContextMenuContentProps;
|
|
|
94
95
|
export const Content = forwardRef<HTMLDivElement, ContentProps>(function ContextMenuContent({className, ...rest}, ref) {
|
|
95
96
|
return (
|
|
96
97
|
<ContextMenuPrimitive.Portal>
|
|
97
|
-
<
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
98
|
+
<ThemeProvider portal>
|
|
99
|
+
<ContextMenuPrimitive.Content
|
|
100
|
+
ref={ref}
|
|
101
|
+
className={cx(dropdownMenuContentStyles, className)}
|
|
102
|
+
collisionPadding={space.s12 + space.s8}
|
|
103
|
+
{...rest}
|
|
104
|
+
/>
|
|
105
|
+
</ThemeProvider>
|
|
103
106
|
</ContextMenuPrimitive.Portal>
|
|
104
107
|
);
|
|
105
108
|
});
|
|
@@ -132,7 +135,9 @@ export const SubContent = forwardRef<HTMLDivElement, ContextMenuPrimitive.Contex
|
|
|
132
135
|
function DropdownMenuSubContent({className, ...rest}, ref) {
|
|
133
136
|
return (
|
|
134
137
|
<ContextMenuPrimitive.Portal>
|
|
135
|
-
<
|
|
138
|
+
<ThemeProvider portal>
|
|
139
|
+
<ContextMenuPrimitive.SubContent ref={ref} className={cx(dropdownMenuContentStyles, className)} {...rest} />
|
|
140
|
+
</ThemeProvider>
|
|
136
141
|
</ContextMenuPrimitive.Portal>
|
|
137
142
|
);
|
|
138
143
|
}
|
|
@@ -2,11 +2,19 @@
|
|
|
2
2
|
// @ts-ignore
|
|
3
3
|
export const varPrefix = process.env.VAR_THEME_PREFIX || "fibery";
|
|
4
4
|
|
|
5
|
+
export type ThemeStyles = Record<string, string>;
|
|
6
|
+
|
|
5
7
|
export function createInlineTheme<T extends Record<string, unknown>>(
|
|
6
8
|
themes: T,
|
|
7
9
|
rejectKeys: Array<string>
|
|
8
|
-
):
|
|
10
|
+
): ThemeStyles {
|
|
9
11
|
return Object.entries(themes).reduce<Record<string, string>>((inlineTheme, [key, value]) => {
|
|
12
|
+
if (key === "key") {
|
|
13
|
+
return inlineTheme;
|
|
14
|
+
}
|
|
15
|
+
if (key === "fns") {
|
|
16
|
+
return inlineTheme;
|
|
17
|
+
}
|
|
10
18
|
if (rejectKeys.includes(key)) {
|
|
11
19
|
return inlineTheme;
|
|
12
20
|
}
|
|
@@ -18,13 +26,6 @@ export function createInlineTheme<T extends Record<string, unknown>>(
|
|
|
18
26
|
|
|
19
27
|
return inlineTheme;
|
|
20
28
|
}
|
|
21
|
-
if (key === "stateColors") {
|
|
22
|
-
Object.entries(themes.stateColors as Record<string, string>).forEach(([stateColorKey, stateColorValue]) => {
|
|
23
|
-
inlineTheme[`--${varPrefix}-state-colors-${stateColorKey}`] = stateColorValue;
|
|
24
|
-
}, "");
|
|
25
|
-
|
|
26
|
-
return inlineTheme;
|
|
27
|
-
}
|
|
28
29
|
if (key === "brandColors") {
|
|
29
30
|
Object.entries(themes.brandColors as Record<string, string>).forEach(([brandColorKey, brandColorValue]) => {
|
|
30
31
|
inlineTheme[`--${varPrefix}-brand-colors-${brandColorKey}`] = brandColorValue;
|