@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.
Files changed (87) hide show
  1. package/CHANGELOG.md +14 -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/Icon.tsx +1 -0
  38. package/src/icons/ast/FileCounter.ts +8 -0
  39. package/src/icons/ast/FileMultiple.ts +8 -0
  40. package/src/icons/ast/Print.ts +8 -0
  41. package/src/icons/ast/ValueEdit.ts +8 -0
  42. package/src/icons/ast/index.tsx +4 -0
  43. package/src/icons/react/Dividers.tsx +1 -1
  44. package/src/icons/react/FileCounter.tsx +13 -0
  45. package/src/icons/react/FileMultiple.tsx +13 -0
  46. package/src/icons/react/Print.tsx +13 -0
  47. package/src/icons/react/ValueEdit.tsx +13 -0
  48. package/src/icons/react/index.tsx +4 -0
  49. package/src/icons/svg/file-counter.svg +3 -0
  50. package/src/icons/svg/file-multiple.svg +3 -0
  51. package/src/icons/svg/print.svg +3 -0
  52. package/src/icons/svg/value-edit.svg +3 -0
  53. package/src/images-gallery/slide-buttons.tsx +4 -11
  54. package/src/lists/actions-menu-row-surface.tsx +5 -5
  55. package/src/mobile-keyboard-aware-popup.tsx +4 -3
  56. package/src/palette-generator.test.ts +566 -0
  57. package/src/palette-generator.ts +166 -0
  58. package/src/palette.ts +71 -55
  59. package/src/platform.ts +0 -3
  60. package/src/popover/index.tsx +20 -28
  61. package/src/progress.tsx +2 -2
  62. package/src/reactions/reaction-button.tsx +12 -6
  63. package/src/root-theme-provider.test.tsx +316 -0
  64. package/src/scale-generator.ts +347 -0
  65. package/src/select/components/menu-list-virtualized.tsx +7 -22
  66. package/src/select/components/menu.tsx +12 -2
  67. package/src/select/select.tsx +2 -1
  68. package/src/select/styles.ts +0 -1
  69. package/src/static-palettes.ts +146 -0
  70. package/src/thematic-color-picker.tsx +266 -0
  71. package/src/thematic-constants.tsx +27 -0
  72. package/src/thematic-controls.tsx +144 -0
  73. package/src/thematic-scales.tsx +122 -0
  74. package/src/thematic-state.ts +333 -0
  75. package/src/thematic.tsx +382 -0
  76. package/src/theme-provider.test.tsx +808 -0
  77. package/src/theme-provider.tsx +132 -69
  78. package/src/theme-settings.ts +1 -1
  79. package/src/theme-styles.ts +12 -5
  80. package/src/toast/toast-action.tsx +1 -1
  81. package/src/toggle-on-off.tsx +2 -2
  82. package/src/toggle.tsx +5 -6
  83. package/src/tooltip.tsx +13 -10
  84. package/src/type-badge.tsx +3 -3
  85. package/src/unit/styles.ts +2 -2
  86. package/src/unit/unit-with-tooltip.tsx +3 -2
  87. package/src/use-long-press.tsx +2 -2
@@ -0,0 +1,382 @@
1
+ import {css} from "@linaria/core";
2
+ import {createPortal} from "react-dom";
3
+ import {border, space, themeVars} from "./design-system";
4
+ import {ThemeProvider} from "./theme-provider";
5
+ import {OKLCH, produceColor} from "./color-utils";
6
+ import type {ColorScale} from "./palette-generator";
7
+ import type {ThematicExport} from "./thematic-state";
8
+ import {useThematicState} from "./thematic-state";
9
+ import {IconButton} from "./button/icon-button";
10
+ import Refresh from "./icons/react/Refresh";
11
+ import Lab from "./icons/react/Lab";
12
+ import Restore from "./icons/react/Restore";
13
+ import ColorCoding from "./icons/react/ColorCoding";
14
+ import Close from "./icons/react/Close";
15
+ import CollapseAll from "./icons/react/CollapseAll";
16
+ import FileDownload from "./icons/react/FileDownload";
17
+ import FileUpload from "./icons/react/FileUpload";
18
+ import {themeIcons} from "./thematic-constants";
19
+ import {ArrayEditor, ArraySlider, controlRowCss, Slider} from "./thematic-controls";
20
+ import {ScalePair, ScaleRow, sectionSeparatorCss} from "./thematic-scales";
21
+
22
+ const panelCss = css`
23
+ position: fixed;
24
+ bottom: 0;
25
+ left: 0;
26
+ background: ${themeVars.colorBgPopup};
27
+ box-shadow: ${themeVars.shadow400};
28
+ padding: ${space.s12}px;
29
+ border-top-right-radius: ${border.radius8}px;
30
+ z-index: 99999;
31
+ font-size: 12px;
32
+ `;
33
+
34
+ const buttonsCss = css`
35
+ display: flex;
36
+ gap: ${space.s8}px;
37
+ margin-top: ${space.s12}px;
38
+ `;
39
+
40
+ function toOklchScale(scale: ColorScale): ColorScale {
41
+ return scale.map((c) => produceColor(c, (x) => x, OKLCH)) as unknown as ColorScale;
42
+ }
43
+
44
+ function toOklchExport(data: ThematicExport): ThematicExport {
45
+ const scales = {...data.scales};
46
+ for (const [k, v] of Object.entries(scales)) {
47
+ (scales as Record<string, ColorScale>)[k] = toOklchScale(v);
48
+ }
49
+ return {...data, scales};
50
+ }
51
+
52
+ export function Thematic() {
53
+ const {
54
+ palette,
55
+ isAltPressed,
56
+ collapsed,
57
+ setCollapsed,
58
+ visible,
59
+ setVisible,
60
+ themePref,
61
+ isModified,
62
+ showLight,
63
+ showDark,
64
+ isAuto,
65
+ prefsPresent,
66
+ staticPalette,
67
+ baseCurves,
68
+ setBaseCurves,
69
+ accentCurves,
70
+ setAccentCurves,
71
+ update,
72
+ randomize,
73
+ cycleStaticPalettes,
74
+ cycleTheme,
75
+ reset,
76
+ updateScaleColor,
77
+ highlightScale,
78
+ clearHighlight,
79
+ exportState,
80
+ importState,
81
+ } = useThematicState();
82
+
83
+ const handleExport = (e: React.MouseEvent) => {
84
+ const data = exportState();
85
+ const output = e.altKey ? toOklchExport(data) : data;
86
+ const blob = new Blob([JSON.stringify(output, null, 2)], {type: "application/json"});
87
+ const url = URL.createObjectURL(blob);
88
+ const a = document.createElement("a");
89
+ a.href = url;
90
+ const hasLight = "base" in data.scales;
91
+ const hasDark = "baseDark" in data.scales;
92
+ const modes = [hasLight && "light", hasDark && "dark"].filter(Boolean).join("&");
93
+ const now = new Date().toISOString().replaceAll(/\D+/g, "");
94
+ a.download = `palette-${modes}${e.altKey ? "-oklch" : ""}-${now}.json`;
95
+ a.click();
96
+ URL.revokeObjectURL(url);
97
+ };
98
+
99
+ const handleImport = () => {
100
+ const input = document.createElement("input");
101
+ input.type = "file";
102
+ input.accept = ".json";
103
+ input.onchange = () => {
104
+ input.files?.[0]?.text().then((json) => {
105
+ try {
106
+ importState(JSON.parse(json));
107
+ } catch {
108
+ /* ignore invalid */
109
+ }
110
+ });
111
+ };
112
+ input.click();
113
+ };
114
+
115
+ if (!visible) {
116
+ return null;
117
+ }
118
+
119
+ const panel = (
120
+ <div className={panelCss}>
121
+ {collapsed ? (
122
+ <IconButton size="small" variant="ghost" color="neutral" onClick={() => setCollapsed(false)} title="Thematic">
123
+ <ColorCoding />
124
+ </IconButton>
125
+ ) : (
126
+ <>
127
+ <IconButton
128
+ size="small"
129
+ variant="ghost"
130
+ color="neutral"
131
+ onClick={() => setVisible(false)}
132
+ title="Close"
133
+ style={{position: "absolute", top: space.s4, right: space.s4}}
134
+ >
135
+ <Close />
136
+ </IconButton>
137
+ <ScalePair
138
+ name="base"
139
+ label="base"
140
+ light={palette.base}
141
+ dark={palette.baseDark}
142
+ showLight={showLight}
143
+ showDark={showDark}
144
+ onLightColorChange={(i, c) => updateScaleColor("base", i, c)}
145
+ onDarkColorChange={(i, c) => updateScaleColor("baseDark", i, c)}
146
+ onLightHighlight={highlightScale("base")}
147
+ onDarkHighlight={highlightScale("baseDark")}
148
+ onHighlightEnd={clearHighlight}
149
+ isAltPressed={isAltPressed}
150
+ />
151
+ {!isAuto && (
152
+ <div className={controlRowCss}>
153
+ <ArrayEditor
154
+ label="L"
155
+ values={baseCurves.lightness}
156
+ onChange={(L) => setBaseCurves({...baseCurves, lightness: L})}
157
+ />
158
+ </div>
159
+ )}
160
+ <ArraySlider
161
+ label="C"
162
+ values={baseCurves.chroma}
163
+ onValuesChange={isAuto ? undefined : (C) => setBaseCurves({...baseCurves, chroma: C})}
164
+ sliderValue={prefsPresent.baseChroma ?? 0}
165
+ onSliderChange={(v) => update({baseChroma: v})}
166
+ min={0}
167
+ max={0.25}
168
+ step={0.001}
169
+ />
170
+ <Slider
171
+ label="H"
172
+ value={prefsPresent.baseHue ?? 0}
173
+ min={0}
174
+ max={360}
175
+ step={1}
176
+ onChange={(v) => update({baseHue: v})}
177
+ />
178
+
179
+ <div className={sectionSeparatorCss} />
180
+
181
+ <ScalePair
182
+ name="accent"
183
+ label="accent"
184
+ light={palette.accent}
185
+ dark={palette.accentDark}
186
+ showLight={showLight}
187
+ showDark={showDark}
188
+ onLightColorChange={(i, c) => updateScaleColor("accent", i, c)}
189
+ onDarkColorChange={(i, c) => updateScaleColor("accentDark", i, c)}
190
+ onLightHighlight={highlightScale("accent")}
191
+ onDarkHighlight={highlightScale("accentDark")}
192
+ onHighlightEnd={clearHighlight}
193
+ isAltPressed={isAltPressed}
194
+ />
195
+ {!isAuto && (
196
+ <div className={controlRowCss}>
197
+ <ArrayEditor
198
+ label="L"
199
+ values={accentCurves.lightness}
200
+ onChange={(L) => setAccentCurves({...accentCurves, lightness: L})}
201
+ />
202
+ </div>
203
+ )}
204
+ <ArraySlider
205
+ label="C"
206
+ values={accentCurves.chroma}
207
+ onValuesChange={isAuto ? undefined : (C) => setAccentCurves({...accentCurves, chroma: C})}
208
+ sliderValue={prefsPresent.accentChroma}
209
+ onSliderChange={(v) => update({accentChroma: v})}
210
+ min={0.05}
211
+ max={0.25}
212
+ step={0.001}
213
+ />
214
+ <Slider
215
+ label="H"
216
+ value={prefsPresent.accentHue}
217
+ min={0}
218
+ max={360}
219
+ step={1}
220
+ onChange={(v) => update({accentHue: v})}
221
+ />
222
+
223
+ <div className={sectionSeparatorCss} />
224
+
225
+ <ScalePair
226
+ name="red"
227
+ light={palette.red}
228
+ dark={palette.redDark}
229
+ showLight={showLight}
230
+ showDark={showDark}
231
+ onLightColorChange={(i, c) => updateScaleColor("red", i, c)}
232
+ onDarkColorChange={(i, c) => updateScaleColor("redDark", i, c)}
233
+ onLightHighlight={highlightScale("red")}
234
+ onDarkHighlight={highlightScale("redDark")}
235
+ onHighlightEnd={clearHighlight}
236
+ isAltPressed={isAltPressed}
237
+ />
238
+ <ScalePair
239
+ name="teal"
240
+ light={palette.teal}
241
+ dark={palette.tealDark}
242
+ showLight={showLight}
243
+ showDark={showDark}
244
+ onLightColorChange={(i, c) => updateScaleColor("teal", i, c)}
245
+ onDarkColorChange={(i, c) => updateScaleColor("tealDark", i, c)}
246
+ onLightHighlight={highlightScale("teal")}
247
+ onDarkHighlight={highlightScale("tealDark")}
248
+ onHighlightEnd={clearHighlight}
249
+ isAltPressed={isAltPressed}
250
+ />
251
+ <ScalePair
252
+ name="yellow"
253
+ light={palette.yellow}
254
+ dark={palette.yellowDark}
255
+ showLight={showLight}
256
+ showDark={showDark}
257
+ onLightColorChange={(i, c) => updateScaleColor("yellow", i, c)}
258
+ onDarkColorChange={(i, c) => updateScaleColor("yellowDark", i, c)}
259
+ onLightHighlight={highlightScale("yellow")}
260
+ onDarkHighlight={highlightScale("yellowDark")}
261
+ onHighlightEnd={clearHighlight}
262
+ isAltPressed={isAltPressed}
263
+ />
264
+
265
+ <Slider
266
+ label="contrast"
267
+ value={prefsPresent.contrast ?? 1}
268
+ min={0.7}
269
+ max={1.3}
270
+ step={0.01}
271
+ onChange={(v) => update({contrast: v})}
272
+ />
273
+
274
+ <div className={sectionSeparatorCss} />
275
+
276
+ <ScaleRow
277
+ name="white"
278
+ scale={palette.white}
279
+ onColorChange={(i, c) => updateScaleColor("white", i, c)}
280
+ onHighlight={highlightScale("white")}
281
+ onHighlightEnd={clearHighlight}
282
+ isAltPressed={isAltPressed}
283
+ />
284
+ <Slider
285
+ label="L"
286
+ value={prefsPresent.whiteLuminosity ?? 1}
287
+ min={0.5}
288
+ max={1.5}
289
+ step={0.01}
290
+ onChange={(v) => update({whiteLuminosity: v})}
291
+ />
292
+ <Slider
293
+ label="C"
294
+ value={prefsPresent.whiteChroma ?? 1}
295
+ min={0}
296
+ max={3}
297
+ step={0.01}
298
+ onChange={(v) => update({whiteChroma: v})}
299
+ />
300
+
301
+ <div className={sectionSeparatorCss} />
302
+
303
+ <ScaleRow
304
+ name="black"
305
+ scale={palette.black}
306
+ onColorChange={(i, c) => updateScaleColor("black", i, c)}
307
+ onHighlight={highlightScale("black")}
308
+ onHighlightEnd={clearHighlight}
309
+ isAltPressed={isAltPressed}
310
+ />
311
+ <Slider
312
+ label="L"
313
+ value={prefsPresent.blackLuminosity ?? 1}
314
+ min={0.5}
315
+ max={1.5}
316
+ step={0.01}
317
+ onChange={(v) => update({blackLuminosity: v})}
318
+ />
319
+ <Slider
320
+ label="C"
321
+ value={prefsPresent.blackChroma ?? 1}
322
+ min={0}
323
+ max={3}
324
+ step={0.01}
325
+ onChange={(v) => update({blackChroma: v})}
326
+ />
327
+
328
+ <div className={buttonsCss} style={{justifyContent: "space-between"}}>
329
+ <div style={{display: "flex", gap: space.s4}}>
330
+ <IconButton
331
+ size="small"
332
+ variant="ghost"
333
+ color="neutral"
334
+ onClick={() => setCollapsed(true)}
335
+ title="Collapse"
336
+ >
337
+ <CollapseAll />
338
+ </IconButton>
339
+ {isAltPressed ? (
340
+ <IconButton
341
+ size="small"
342
+ variant={staticPalette ? "solid" : "soft"}
343
+ color="error"
344
+ onClick={cycleStaticPalettes}
345
+ title="Beware"
346
+ >
347
+ <Lab />
348
+ </IconButton>
349
+ ) : (
350
+ <IconButton size="small" variant="soft" color="accent" onClick={randomize} title="Shuffle">
351
+ <Refresh />
352
+ </IconButton>
353
+ )}
354
+ <IconButton size="small" variant="ghost" color="neutral" onClick={handleExport} title="Export">
355
+ <FileDownload />
356
+ </IconButton>
357
+ <IconButton size="small" variant="ghost" color="neutral" onClick={handleImport} title="Import">
358
+ <FileUpload />
359
+ </IconButton>
360
+ {isModified && (
361
+ <IconButton size="small" variant="ghost" color="neutral" onClick={reset} title="Defaults">
362
+ <Restore />
363
+ </IconButton>
364
+ )}
365
+ </div>
366
+ <IconButton
367
+ size="small"
368
+ variant="ghost"
369
+ color="neutral"
370
+ onClick={cycleTheme}
371
+ title={isAltPressed ? "auto" : themePref}
372
+ >
373
+ {themeIcons[isAltPressed ? "auto" : themePref]}
374
+ </IconButton>
375
+ </div>
376
+ </>
377
+ )}
378
+ </div>
379
+ );
380
+
381
+ return createPortal(<ThemeProvider portal>{panel}</ThemeProvider>, document.body);
382
+ }