@fibery/ui-kit 1.40.3 → 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.
Files changed (82) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/package.json +4 -7
  3. package/src/__snapshots__/design-system.test.ts.snap +7265 -0
  4. package/src/a11y-color.test.ts +181 -0
  5. package/src/a11y-color.ts +13 -20
  6. package/src/actions-menu/actions-menu-item.tsx +7 -7
  7. package/src/antd/ant-modal.tsx +10 -5
  8. package/src/antd/styles.ts +6 -6
  9. package/src/app-icon-with-fallback.tsx +2 -2
  10. package/src/app-icon.tsx +2 -2
  11. package/src/button/button.tsx +1 -0
  12. package/src/button/make-button-colors.ts +5 -13
  13. package/src/checkbox.tsx +2 -2
  14. package/src/color-picker/ColorPickerOrLoader.js +2 -2
  15. package/src/color-utils.test.ts +317 -0
  16. package/src/color-utils.ts +180 -0
  17. package/src/comment.tsx +3 -2
  18. package/src/context-menu/index.tsx +12 -7
  19. package/src/create-inline-theme.ts +9 -8
  20. package/src/design-system.colors.ts +760 -0
  21. package/src/design-system.test.ts +287 -7
  22. package/src/design-system.ts +146 -940
  23. package/src/dropdown-menu/index.tsx +21 -16
  24. package/src/emoji-picker/app-icon-picker.tsx +5 -5
  25. package/src/emoji-picker/emoji-picker-content-with-color.tsx +4 -4
  26. package/src/emoji-picker/icon-emoji-picker.tsx +2 -2
  27. package/src/favorites-icon.tsx +1 -1
  28. package/src/file-item/file-icon.tsx +169 -0
  29. package/src/file-item/file-menu-items.tsx +68 -0
  30. package/src/file-item/file-preview-actions.tsx +38 -0
  31. package/src/file-item/file-title.tsx +48 -0
  32. package/src/file-item/types.ts +27 -0
  33. package/src/file-item/use-register-in-image-gallery.tsx +70 -0
  34. package/src/file-item-2.tsx +35 -345
  35. package/src/file-item.tsx +6 -2
  36. package/src/hue-shift.test.ts +91 -0
  37. package/src/icons/ast/FileCounter.ts +8 -0
  38. package/src/icons/ast/FileMultiple.ts +8 -0
  39. package/src/icons/ast/ValueEdit.ts +8 -0
  40. package/src/icons/ast/index.tsx +3 -0
  41. package/src/icons/react/FileCounter.tsx +13 -0
  42. package/src/icons/react/FileMultiple.tsx +13 -0
  43. package/src/icons/react/ValueEdit.tsx +13 -0
  44. package/src/icons/react/index.tsx +3 -0
  45. package/src/icons/svg/file-counter.svg +3 -0
  46. package/src/icons/svg/file-multiple.svg +3 -0
  47. package/src/icons/svg/value-edit.svg +3 -0
  48. package/src/images-gallery/slide-buttons.tsx +4 -11
  49. package/src/lists/actions-menu-row-surface.tsx +5 -5
  50. package/src/mobile-keyboard-aware-popup.tsx +4 -3
  51. package/src/palette-generator.test.ts +566 -0
  52. package/src/palette-generator.ts +166 -0
  53. package/src/palette.ts +71 -55
  54. package/src/platform.ts +0 -3
  55. package/src/popover/index.tsx +13 -15
  56. package/src/progress.tsx +2 -2
  57. package/src/reactions/reaction-button.tsx +12 -6
  58. package/src/root-theme-provider.test.tsx +316 -0
  59. package/src/scale-generator.ts +347 -0
  60. package/src/select/components/menu-list-virtualized.tsx +7 -22
  61. package/src/select/components/menu.tsx +12 -2
  62. package/src/select/select.tsx +2 -1
  63. package/src/select/styles.ts +0 -1
  64. package/src/static-palettes.ts +146 -0
  65. package/src/thematic-color-picker.tsx +266 -0
  66. package/src/thematic-constants.tsx +27 -0
  67. package/src/thematic-controls.tsx +144 -0
  68. package/src/thematic-scales.tsx +122 -0
  69. package/src/thematic-state.ts +333 -0
  70. package/src/thematic.tsx +382 -0
  71. package/src/theme-provider.test.tsx +808 -0
  72. package/src/theme-provider.tsx +132 -69
  73. package/src/theme-settings.ts +1 -1
  74. package/src/theme-styles.ts +12 -5
  75. package/src/toast/toast-action.tsx +1 -1
  76. package/src/toggle-on-off.tsx +2 -2
  77. package/src/toggle.tsx +5 -6
  78. package/src/tooltip.tsx +13 -10
  79. package/src/type-badge.tsx +3 -3
  80. package/src/unit/styles.ts +2 -2
  81. package/src/unit/unit-with-tooltip.tsx +3 -2
  82. 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 {isMobile} from "./platform";
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 || isMobile) &&
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
- <ContextMenuPrimitive.Content
98
- ref={ref}
99
- className={cx(dropdownMenuContentStyles, className)}
100
- collisionPadding={space.s12 + space.s8}
101
- {...rest}
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
- <ContextMenuPrimitive.SubContent ref={ref} className={cx(dropdownMenuContentStyles, className)} {...rest} />
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
- ): Record<string, string> {
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;