@fibery/ui-kit 2.1.0 → 2.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/package.json +6 -3
  3. package/src/@types/react-color.d.ts +36 -0
  4. package/src/antd/{Tabs.js → Tabs.tsx} +5 -2
  5. package/src/color-filter.test.ts +24 -65
  6. package/src/color-filter.ts +62 -93
  7. package/src/color-picker/{ColorPickerOrLoader.js → ColorPickerOrLoader.tsx} +1 -1
  8. package/src/color-picker/{index.js → index.tsx} +32 -6
  9. package/src/color-utils.ts +3 -9
  10. package/src/design-system/colors-css.ts +16 -0
  11. package/src/design-system/colors.ts +23 -45
  12. package/src/design-system/def.ts +17 -0
  13. package/src/design-system/theme.ts +6 -2
  14. package/src/design-system/types.ts +102 -0
  15. package/src/design-system/vars.test.ts +9 -2
  16. package/src/design-system/vars.ts +4 -2
  17. package/src/design-system.ts +9 -1
  18. package/src/emoji-picker/emoji-picker-content-with-color.tsx +3 -3
  19. package/src/heat.ts +8 -0
  20. package/src/highlight-colors.test.ts +68 -0
  21. package/src/highlight-colors.ts +91 -0
  22. package/src/link-input/components/{AntTextAreaWithCustomReadState.js → AntTextAreaWithCustomReadState.tsx} +19 -8
  23. package/src/link-input/index.tsx +94 -0
  24. package/src/link-input/{utils.js → utils.ts} +1 -6
  25. package/src/number-input/decimal.ts +50 -0
  26. package/src/number-input/utils.ts +2 -2
  27. package/src/palette-generator.test.ts +2 -1
  28. package/src/palette-generator.ts +8 -26
  29. package/src/palettes/_.ts +2 -2
  30. package/src/palettes/common.ts +31 -0
  31. package/src/palettes/diff-colors.ts +29 -12
  32. package/src/palettes/inspect.defs.colors.neutral-arch.test.ts +72 -36
  33. package/src/palettes/inspect.defs.colors.neutral-user.test.ts +72 -36
  34. package/src/palettes/inspect.defs.colors.warm-arch.test.ts +226 -190
  35. package/src/palettes/inspect.defs.colors.warm-user.test.ts +226 -190
  36. package/src/palettes/neutral-arch.ts +3 -3
  37. package/src/palettes/neutral-user.ts +3 -3
  38. package/src/palettes/neutral.ts +4 -2
  39. package/src/palettes/warm-arch.ts +3 -3
  40. package/src/palettes/warm-user.ts +3 -3
  41. package/src/palettes/warm.ts +4 -3
  42. package/src/pretty-size.ts +5 -2
  43. package/src/root-theme-provider.test.tsx +1 -1
  44. package/src/scale-generator.ts +1 -32
  45. package/src/select/components/menu-list-virtua.tsx +127 -0
  46. package/src/select/index.tsx +1 -1
  47. package/src/select/reflection.ts +22 -0
  48. package/src/select/select.tsx +1 -0
  49. package/src/static-palettes.ts +2 -1
  50. package/src/thematic-color-picker.tsx +8 -1
  51. package/src/thematic-constants.tsx +2 -0
  52. package/src/thematic-controls.tsx +9 -1
  53. package/src/thematic-cvd.tsx +73 -0
  54. package/src/thematic-highlights.tsx +49 -0
  55. package/src/thematic-scales.tsx +8 -6
  56. package/src/thematic-state.ts +37 -16
  57. package/src/thematic.tsx +298 -305
  58. package/src/theme-provider.tsx +9 -3
  59. package/src/theme-styles.ts +2 -1
  60. package/src/link-input/index.js +0 -89
  61. package/src/number-input/decimal.js +0 -61
package/src/thematic.tsx CHANGED
@@ -2,14 +2,14 @@
2
2
  import {css} from "@linaria/core";
3
3
  import {useState} from "react";
4
4
  import {createPortal} from "react-dom";
5
- import {border, space, themeVars} from "./design-system";
5
+ import {border, type ColorScale, space, themeVars} from "./design-system";
6
6
  import {ThemeProvider, useTheme} from "./theme-provider";
7
7
  import {useWarmPreference} from "./theme-settings";
8
8
  import {inspectColor} from "./color-utils";
9
9
  import {cardTypeColors} from "./design-system/colors";
10
10
  import {backgrounds, sections, stickers} from "./canvas-colors";
11
- import {type ColorScale, type ThemePalette} from "./palette-generator";
12
- import {useThematicState} from "./thematic-state";
11
+ import {bgKeyFrom, fgOnBgKeyFrom, highlightHueNames} from "./highlight-colors";
12
+ import {type PaletteScaleKey, useThematicState} from "./thematic-state";
13
13
  import {IconButton} from "./button/icon-button";
14
14
  import Refresh from "./icons/react/Refresh";
15
15
  import Lab from "./icons/react/Lab";
@@ -31,14 +31,17 @@ import {
31
31
  Slider,
32
32
  } from "./thematic-controls";
33
33
  import type {InspectChannel} from "./thematic-color-picker";
34
- import {ScaleGrid, sectionLabelCss, sectionSeparatorCss, SPACER} from "./thematic-scales";
35
-
36
- type PaletteScaleKey = Exclude<keyof ThemePalette, "key" | "deneutralize">;
34
+ import {type ScaleEntry, ScaleGrid, sectionLabelCss, sectionSeparatorCss, SPACER} from "./thematic-scales";
35
+ import {HighlightSection} from "./thematic-highlights";
36
+ import {CvdSection} from "./thematic-cvd";
37
37
 
38
38
  const panelCss = css`
39
39
  position: fixed;
40
40
  bottom: 0;
41
41
  left: 0;
42
+ max-height: 100vh;
43
+ display: flex;
44
+ flex-direction: column;
42
45
  background: ${themeVars.colorBgPopup};
43
46
  box-shadow: ${themeVars.shadow400};
44
47
  padding: ${space.s12}px;
@@ -47,17 +50,42 @@ const panelCss = css`
47
50
  font-size: 12px;
48
51
  `;
49
52
 
53
+ const slidersCss = css`
54
+ overflow-y: auto;
55
+ min-height: 0;
56
+ `;
57
+
50
58
  const buttonsCss = css`
51
59
  display: flex;
52
60
  gap: ${space.s8}px;
53
61
  margin-top: ${space.s12}px;
54
62
  `;
55
63
 
64
+ const highlightRowCss = css`
65
+ display: flex;
66
+ gap: 2px;
67
+ margin: ${space.s4}px 0;
68
+ `;
69
+
70
+ const highlightSwatchCss = css`
71
+ flex: 1;
72
+ height: 22px;
73
+ display: flex;
74
+ align-items: center;
75
+ justify-content: center;
76
+ font-size: 12px;
77
+ font-weight: 700;
78
+ line-height: 1;
79
+ border-radius: 2px;
80
+ `;
81
+
56
82
  export function Thematic() {
57
83
  const theme = useTheme();
58
84
  const [warm, setWarm] = useWarmPreference();
59
85
  const state = useThematicState();
60
86
 
87
+ const deneutralizeSpec = state.deneutralizeSpec;
88
+
61
89
  const [hovered, setHovered] = useState(false);
62
90
  const expanded = state.pinned || hovered;
63
91
 
@@ -107,116 +135,121 @@ export function Thematic() {
107
135
  return null;
108
136
  }
109
137
 
138
+ const makeScales = (): ScaleEntry[] => {
139
+ const alt = (name: PaletteScaleKey) => {
140
+ return state.isAltPressed ? [...state.providerPalette[name]] : undefined;
141
+ };
142
+ const s = (name: string, scale: ColorScale, scaleName: PaletteScaleKey) => ({
143
+ name,
144
+ scale,
145
+ onColorChange: (i: number, c: string) => state.updateScaleColor(scaleName as PaletteScaleKey, i, c),
146
+ onHover: state.onSwatchHover,
147
+ onHoverEnd: state.onSwatchHoverEnd,
148
+ altColors: alt(scaleName),
149
+ });
150
+
151
+ const dn = state.palette.deneutralize ? theme.fns.deneutralize : (c: string) => c;
152
+ const isDark = state.showDark;
153
+ const canvasCol = (
154
+ defs: readonly (readonly [string, string])[],
155
+ label: string,
156
+ padBefore?: Record<number, number>
157
+ ) => {
158
+ const scale: (string | null)[] = [];
159
+ defs.forEach(([light, dark], i) => {
160
+ const pad = padBefore?.[i] ?? 0;
161
+ for (let p = 0; p < pad; p++) {
162
+ scale.push(SPACER);
163
+ }
164
+ scale.push(dn(isDark ? dark : light));
165
+ });
166
+ return {
167
+ name: label,
168
+ scale,
169
+ onHover: state.onSwatchHover,
170
+ onHoverEnd: state.onSwatchHoverEnd,
171
+ tiny: true,
172
+ };
173
+ };
174
+
175
+ return [
176
+ {
177
+ name: "whiteA",
178
+ scale: state.palette.whiteA,
179
+ onColorChange: (i: number, c: string) => state.updateScaleColor("whiteA", i, c),
180
+ onHover: state.onSwatchHover,
181
+ onHoverEnd: state.onSwatchHoverEnd,
182
+ extra: {
183
+ color: state.palette.white,
184
+ name: "white",
185
+ onChange: (c: string) => state.updateScaleColor("white", 0, c),
186
+ },
187
+ splitBg: true,
188
+ altColors: alt("whiteA"),
189
+ },
190
+ {
191
+ name: "blackA",
192
+ scale: state.palette.blackA,
193
+ onColorChange: (i: number, c: string) => state.updateScaleColor("blackA", i, c),
194
+ onHover: state.onSwatchHover,
195
+ onHoverEnd: state.onSwatchHoverEnd,
196
+ extra: {
197
+ color: state.palette.black,
198
+ name: "black",
199
+ onChange: (c: string) => state.updateScaleColor("black", 0, c),
200
+ },
201
+ splitBg: true,
202
+ altColors: alt("blackA"),
203
+ },
204
+ ...(state.showLight
205
+ ? [
206
+ s("base", state.palette.base, "base"),
207
+ s("accent", state.palette.accent, "accent"),
208
+ s("red", state.palette.red, "red"),
209
+ s("teal", state.palette.teal, "teal"),
210
+ s("yellow", state.palette.yellow, "yellow"),
211
+ ]
212
+ : []),
213
+ ...(state.showDark
214
+ ? [
215
+ s("baseDark", state.palette.baseDark, "baseDark"),
216
+ s("accentDark", state.palette.accentDark, "accentDark"),
217
+ s("redDark", state.palette.redDark, "redDark"),
218
+ s("tealDark", state.palette.tealDark, "tealDark"),
219
+ s("yellowDark", state.palette.yellowDark, "yellowDark"),
220
+ ]
221
+ : []),
222
+ {
223
+ name: "cardType",
224
+ scale: cardTypeColors.map((c) => dn(c)) as unknown as ColorScale,
225
+ onHover: state.onSwatchHover,
226
+ onHoverEnd: state.onSwatchHoverEnd,
227
+ altColors: state.isAltPressed ? [...cardTypeColors] : undefined,
228
+ mini: true,
229
+ },
230
+ canvasCol(backgrounds, "background"),
231
+ canvasCol(stickers, "sticker", {0: 1, 8: 2}),
232
+ canvasCol(sections, "section"),
233
+ ];
234
+ };
110
235
  const panel = (
111
236
  <div className={panelCss} onMouseEnter={() => setHovered(true)} onMouseLeave={() => setHovered(false)}>
112
237
  {!expanded ? (
113
238
  <ColorCoding />
114
239
  ) : (
115
240
  <>
116
- <ScaleGrid
117
- isAltPressed={state.isAltPressed}
118
- inspectChannel={inspectChannel}
119
- scales={(() => {
120
- const alt = (name: PaletteScaleKey) =>
121
- state.isAltPressed ? ([...state.providerPalette[name]] as (string | null)[]) : undefined;
122
- const s = (name: string, scale: ColorScale, scaleName: PaletteScaleKey) => ({
123
- name,
124
- scale,
125
- onColorChange: (i: number, c: string) => state.updateScaleColor(scaleName as PaletteScaleKey, i, c),
126
- onHover: state.onSwatchHover,
127
- onHoverEnd: state.onSwatchHoverEnd,
128
- altColors: alt(scaleName),
129
- });
130
-
131
- const dn = state.palette.deneutralize ? theme.fns.deneutralize : (c: string) => c;
132
- const isDark = state.showDark;
133
- const canvasCol = (
134
- defs: readonly (readonly [string, string])[],
135
- label: string,
136
- padBefore?: Record<number, number>
137
- ) => {
138
- const scale: (string | null)[] = [];
139
- const altColors: (string | null)[] = [];
140
- defs.forEach(([light, dark], i) => {
141
- const pad = padBefore?.[i] ?? 0;
142
- for (let p = 0; p < pad; p++) {
143
- scale.push(SPACER);
144
- altColors.push(SPACER);
145
- }
146
- scale.push(dn(isDark ? dark : light));
147
- altColors.push(isDark ? light : dark);
148
- });
149
- return {
150
- name: label,
151
- scale: scale as unknown as ColorScale,
152
- onHover: state.onSwatchHover,
153
- onHoverEnd: state.onSwatchHoverEnd,
154
- altColors: state.isAltPressed ? altColors : undefined,
155
- mini: true,
156
- };
157
- };
158
-
159
- return [
160
- {
161
- name: "whiteA",
162
- scale: state.palette.whiteA,
163
- onColorChange: (i: number, c: string) => state.updateScaleColor("whiteA", i, c),
164
- onHover: state.onSwatchHover,
165
- onHoverEnd: state.onSwatchHoverEnd,
166
- extra: {
167
- color: state.palette.white,
168
- name: "white",
169
- onChange: (c: string) => state.updateScaleColor("white", 0, c),
170
- },
171
- splitBg: true,
172
- altColors: alt("whiteA"),
173
- },
174
- {
175
- name: "blackA",
176
- scale: state.palette.blackA,
177
- onColorChange: (i: number, c: string) => state.updateScaleColor("blackA", i, c),
178
- onHover: state.onSwatchHover,
179
- onHoverEnd: state.onSwatchHoverEnd,
180
- extra: {
181
- color: state.palette.black,
182
- name: "black",
183
- onChange: (c: string) => state.updateScaleColor("black", 0, c),
184
- },
185
- splitBg: true,
186
- altColors: alt("blackA"),
187
- },
188
- ...(state.showLight
189
- ? [
190
- s("base", state.palette.base, "base"),
191
- s("accent", state.palette.accent, "accent"),
192
- s("red", state.palette.red, "red"),
193
- s("teal", state.palette.teal, "teal"),
194
- s("yellow", state.palette.yellow, "yellow"),
195
- ]
196
- : []),
197
- ...(state.showDark
198
- ? [
199
- s("baseDark", state.palette.baseDark, "baseDark"),
200
- s("accentDark", state.palette.accentDark, "accentDark"),
201
- s("redDark", state.palette.redDark, "redDark"),
202
- s("tealDark", state.palette.tealDark, "tealDark"),
203
- s("yellowDark", state.palette.yellowDark, "yellowDark"),
204
- ]
205
- : []),
206
- {
207
- name: "cardType",
208
- scale: cardTypeColors.map((c) => dn(c)) as unknown as ColorScale,
209
- onHover: state.onSwatchHover,
210
- onHoverEnd: state.onSwatchHoverEnd,
211
- altColors: state.isAltPressed ? [...cardTypeColors] : undefined,
212
- mini: true,
213
- },
214
- canvasCol(backgrounds, "background"),
215
- canvasCol(stickers, "sticker", {0: 1, 8: 2}),
216
- canvasCol(sections, "section"),
217
- ];
218
- })()}
219
- />
241
+ <ScaleGrid isAltPressed={state.isAltPressed} inspectChannel={inspectChannel} scales={makeScales()} />
242
+ <div className={highlightRowCss}>
243
+ {highlightHueNames.map((hue) => {
244
+ const bg = theme.colors[bgKeyFrom(hue)];
245
+ const fg = theme.colors[fgOnBgKeyFrom(hue)];
246
+ return (
247
+ <div key={hue} className={highlightSwatchCss} style={{background: bg, color: fg}} title={hue}>
248
+ Aa
249
+ </div>
250
+ );
251
+ })}
252
+ </div>
220
253
 
221
254
  <div style={{display: "flex", alignItems: "center", gap: space.s8, minHeight: 28}}>
222
255
  <div style={{display: "flex", gap: space.s2, flexShrink: 0}}>
@@ -242,219 +275,179 @@ export function Thematic() {
242
275
  color: themeVars.colorTextSecondary,
243
276
  }}
244
277
  >
245
- <span>{state.hoveredInfo.name}</span>
278
+ {/* TODO: index [N] is broken for tiny swatches, fix later */}
279
+ {/* <span>{state.hoveredInfo.name}</span> */}
246
280
  <span>{hsl}</span>
247
281
  <span>{hex}</span>
248
- <span>{oklch}</span>
282
+ <span>{oklch?.replace("oklch", "lch")}</span>
249
283
  </div>
250
284
  );
251
285
  })()}
252
286
  </div>
253
287
 
254
- <div className={sectionSeparatorCss} />
288
+ <div className={slidersCss}>
289
+ <div className={sectionSeparatorCss} />
255
290
 
256
- <div className={sectionLabelCss} style={{fontWeight: 700}}>
257
- palette
258
- </div>
291
+ <div className={sectionLabelCss} style={{fontWeight: 700}}>
292
+ palette
293
+ </div>
259
294
 
260
- <div className={sectionLabelCss}>base{state.prefs ? " *" : ""}</div>
261
- <div className={controlRowCss}>
262
- <ArrayEditor
263
- label="L"
264
- values={state.baseCurves.lightness}
265
- onChange={(L) => state.setBaseCurves({...state.baseCurves, lightness: L})}
266
- onLabelHover={inspL}
295
+ <div className={sectionLabelCss}>base{state.prefs ? " *" : ""}</div>
296
+ <div className={controlRowCss}>
297
+ <ArrayEditor
298
+ label="L"
299
+ values={state.baseCurves.lightness}
300
+ onChange={(L) => state.setBaseCurves({...state.baseCurves, lightness: L})}
301
+ onLabelHover={inspL}
302
+ onLabelLeave={inspOff}
303
+ />
304
+ </div>
305
+ <ArraySlider
306
+ label={state.prefs?.baseChroma !== undefined ? "C *" : "C"}
307
+ values={state.baseCurves.chroma}
308
+ onValuesChange={(C) => state.setBaseCurves({...state.baseCurves, chroma: C})}
309
+ sliderValue={state.prefsPresent.baseChroma ?? 0}
310
+ onSliderChange={(v) => state.update({baseChroma: v})}
311
+ min={0}
312
+ max={0.25}
313
+ step={0.001}
314
+ gradient={chromaGradient(state.prefsPresent.baseHue ?? 0, 0.25)}
315
+ color={oklchColor(state.prefsPresent.baseHue ?? 0, state.prefsPresent.baseChroma ?? 0)}
316
+ onLabelHover={inspC}
317
+ onLabelLeave={inspOff}
318
+ />
319
+ <Slider
320
+ label={state.prefs?.baseHue !== undefined ? "H *" : "H"}
321
+ value={state.prefsPresent.baseHue ?? 0}
322
+ min={0}
323
+ max={360}
324
+ step={1}
325
+ onChange={(v) => state.update({baseHue: v})}
326
+ gradient={hueGradient(state.prefsPresent.baseChroma ?? 0)}
327
+ color={oklchColor(state.prefsPresent.baseHue ?? 0, state.prefsPresent.baseChroma ?? 0)}
328
+ onLabelHover={inspH}
267
329
  onLabelLeave={inspOff}
268
330
  />
269
- </div>
270
- <ArraySlider
271
- label={state.prefs?.baseChroma !== undefined ? "C *" : "C"}
272
- values={state.baseCurves.chroma}
273
- onValuesChange={(C) => state.setBaseCurves({...state.baseCurves, chroma: C})}
274
- sliderValue={state.prefsPresent.baseChroma ?? 0}
275
- onSliderChange={(v) => state.update({baseChroma: v})}
276
- min={0}
277
- max={0.25}
278
- step={0.001}
279
- gradient={chromaGradient(state.prefsPresent.baseHue ?? 0, 0.25)}
280
- color={oklchColor(state.prefsPresent.baseHue ?? 0, state.prefsPresent.baseChroma ?? 0)}
281
- onLabelHover={inspC}
282
- onLabelLeave={inspOff}
283
- />
284
- <Slider
285
- label={state.prefs?.baseHue !== undefined ? "H *" : "H"}
286
- value={state.prefsPresent.baseHue ?? 0}
287
- min={0}
288
- max={360}
289
- step={1}
290
- onChange={(v) => state.update({baseHue: v})}
291
- gradient={hueGradient(state.prefsPresent.baseChroma ?? 0)}
292
- color={oklchColor(state.prefsPresent.baseHue ?? 0, state.prefsPresent.baseChroma ?? 0)}
293
- onLabelHover={inspH}
294
- onLabelLeave={inspOff}
295
- />
296
331
 
297
- <div className={sectionLabelCss}>accent{state.prefs ? " *" : ""}</div>
298
- <div className={controlRowCss}>
299
- <ArrayEditor
300
- label="L"
301
- values={state.accentCurves.lightness}
302
- onChange={(L) => state.setAccentCurves({...state.accentCurves, lightness: L})}
303
- onLabelHover={inspL}
332
+ <div className={sectionLabelCss}>accent{state.prefs ? " *" : ""}</div>
333
+ <div className={controlRowCss}>
334
+ <ArrayEditor
335
+ label="L"
336
+ values={state.accentCurves.lightness}
337
+ onChange={(L) => state.setAccentCurves({...state.accentCurves, lightness: L})}
338
+ onLabelHover={inspL}
339
+ onLabelLeave={inspOff}
340
+ />
341
+ </div>
342
+ <ArraySlider
343
+ label={state.prefs?.accentChroma !== undefined ? "C *" : "C"}
344
+ values={state.accentCurves.chroma}
345
+ onValuesChange={(C) => state.setAccentCurves({...state.accentCurves, chroma: C})}
346
+ sliderValue={state.prefsPresent.accentChroma}
347
+ onSliderChange={(v) => state.update({accentChroma: v})}
348
+ min={0.05}
349
+ max={0.25}
350
+ step={0.001}
351
+ gradient={chromaGradient(state.prefsPresent.accentHue, 0.25)}
352
+ color={oklchColor(state.prefsPresent.accentHue, state.prefsPresent.accentChroma)}
353
+ onLabelHover={inspC}
354
+ onLabelLeave={inspOff}
355
+ />
356
+ <Slider
357
+ label={state.prefs?.accentHue !== undefined ? "H *" : "H"}
358
+ value={state.prefsPresent.accentHue}
359
+ min={0}
360
+ max={360}
361
+ step={1}
362
+ onChange={(v) => state.update({accentHue: v})}
363
+ gradient={hueGradient(state.prefsPresent.accentChroma)}
364
+ color={oklchColor(state.prefsPresent.accentHue, state.prefsPresent.accentChroma)}
365
+ onLabelHover={inspH}
304
366
  onLabelLeave={inspOff}
305
367
  />
306
- </div>
307
- <ArraySlider
308
- label={state.prefs?.accentChroma !== undefined ? "C *" : "C"}
309
- values={state.accentCurves.chroma}
310
- onValuesChange={(C) => state.setAccentCurves({...state.accentCurves, chroma: C})}
311
- sliderValue={state.prefsPresent.accentChroma}
312
- onSliderChange={(v) => state.update({accentChroma: v})}
313
- min={0.05}
314
- max={0.25}
315
- step={0.001}
316
- gradient={chromaGradient(state.prefsPresent.accentHue, 0.25)}
317
- color={oklchColor(state.prefsPresent.accentHue, state.prefsPresent.accentChroma)}
318
- onLabelHover={inspC}
319
- onLabelLeave={inspOff}
320
- />
321
- <Slider
322
- label={state.prefs?.accentHue !== undefined ? "H *" : "H"}
323
- value={state.prefsPresent.accentHue}
324
- min={0}
325
- max={360}
326
- step={1}
327
- onChange={(v) => state.update({accentHue: v})}
328
- gradient={hueGradient(state.prefsPresent.accentChroma)}
329
- color={oklchColor(state.prefsPresent.accentHue, state.prefsPresent.accentChroma)}
330
- onLabelHover={inspH}
331
- onLabelLeave={inspOff}
332
- />
333
368
 
334
- <div className={sectionLabelCss}>
335
- hue shift
336
- {state.prefs?.hueShiftTargetHue !== undefined || state.prefs?.hueShiftAmount !== undefined ? " *" : ""}
337
- </div>
338
- <Slider
339
- label={state.prefs?.hueShiftTargetHue !== undefined ? "target H *" : "target H"}
340
- value={state.prefsPresent.hueShiftTargetHue ?? state.prefsPresent.baseHue ?? 0}
341
- min={0}
342
- max={360}
343
- step={1}
344
- onChange={(v) => state.updateHueShift({hueShiftTargetHue: v})}
345
- gradient={hueGradient(0.15)}
346
- color={oklchColor(state.prefsPresent.hueShiftTargetHue ?? state.prefsPresent.baseHue ?? 0, 0.15)}
347
- />
348
- <Slider
349
- label={state.prefs?.hueShiftAmount !== undefined ? "amount *" : "amount"}
350
- value={state.prefsPresent.hueShiftAmount ?? Math.min((state.prefsPresent.baseChroma ?? 0) * 2, 0.3)}
351
- min={0}
352
- max={1}
353
- step={0.01}
354
- onChange={(v) => state.updateHueShift({hueShiftAmount: v})}
355
- />
369
+ <div className={sectionLabelCss}>
370
+ hue shift
371
+ {state.prefs?.hueShiftTargetHue !== undefined || state.prefs?.hueShiftAmount !== undefined ? " *" : ""}
372
+ </div>
373
+ <Slider
374
+ label={state.prefs?.hueShiftTargetHue !== undefined ? "target H *" : "target H"}
375
+ value={state.prefsPresent.hueShiftTargetHue ?? state.prefsPresent.baseHue ?? 0}
376
+ min={0}
377
+ max={360}
378
+ step={1}
379
+ onChange={(v) => state.updateHueShift({hueShiftTargetHue: v})}
380
+ gradient={hueGradient(0.15)}
381
+ color={oklchColor(state.prefsPresent.hueShiftTargetHue ?? state.prefsPresent.baseHue ?? 0, 0.15)}
382
+ />
383
+ <Slider
384
+ label={state.prefs?.hueShiftAmount !== undefined ? "amount *" : "amount"}
385
+ value={state.prefsPresent.hueShiftAmount ?? Math.min((state.prefsPresent.baseChroma ?? 0) * 2, 0.3)}
386
+ min={0}
387
+ max={1}
388
+ step={0.01}
389
+ onChange={(v) => state.updateHueShift({hueShiftAmount: v})}
390
+ />
356
391
 
357
- <div className={sectionSeparatorCss} />
392
+ <div className={sectionSeparatorCss} />
393
+
394
+ <div className={sectionLabelCss} style={{fontWeight: 700}}>
395
+ deneutralize ({state.showDark ? "dark" : "light"})
396
+ {state.prefs?.filter || state.customPalette?.deneutralize ? " *" : ""}
397
+ </div>
398
+ <Slider
399
+ label="hue shift H"
400
+ value={deneutralizeSpec?.hueShift?.targetHue ?? 0}
401
+ min={0}
402
+ max={360}
403
+ step={1}
404
+ onChange={(v) =>
405
+ state.updateFilter({hueShift: {targetHue: v, amount: deneutralizeSpec?.hueShift?.amount ?? 0}})
406
+ }
407
+ gradient={hueGradient(0.15)}
408
+ color={oklchColor(deneutralizeSpec?.hueShift?.targetHue ?? 0, 0.15)}
409
+ />
410
+ <Slider
411
+ label="hue shift amt"
412
+ value={deneutralizeSpec?.hueShift?.amount ?? 0}
413
+ min={0}
414
+ max={1}
415
+ step={0.01}
416
+ onChange={(v) =>
417
+ state.updateFilter({
418
+ hueShift: {targetHue: deneutralizeSpec?.hueShift?.targetHue ?? 0, amount: v},
419
+ })
420
+ }
421
+ />
422
+ <Slider
423
+ label="oklch L"
424
+ value={deneutralizeSpec?.oklchL ?? 1}
425
+ min={0}
426
+ max={2}
427
+ step={0.01}
428
+ onChange={(v) => state.updateFilter({oklchL: v})}
429
+ />
430
+ <Slider
431
+ label="oklch C"
432
+ value={deneutralizeSpec?.oklchC ?? 1}
433
+ min={0}
434
+ max={3}
435
+ step={0.01}
436
+ onChange={(v) => state.updateFilter({oklchC: v})}
437
+ />
438
+ <Slider
439
+ label="heat"
440
+ value={deneutralizeSpec?.heat ?? 0}
441
+ min={-1}
442
+ max={1}
443
+ step={0.01}
444
+ onChange={(v) => state.updateFilter({heat: v})}
445
+ />
446
+
447
+ <CvdSection state={state} />
358
448
 
359
- <div className={sectionLabelCss} style={{fontWeight: 700}}>
360
- deneutralize{state.prefs?.filter || state.customPalette?.deneutralize ? " *" : ""}
449
+ <HighlightSection state={state} />
361
450
  </div>
362
- <Slider
363
- label="hue shift H"
364
- value={state.palette.deneutralize?.hueShift?.targetHue ?? 0}
365
- min={0}
366
- max={360}
367
- step={1}
368
- onChange={(v) =>
369
- state.updateFilter({hueShift: {targetHue: v, amount: state.palette.deneutralize?.hueShift?.amount ?? 0}})
370
- }
371
- gradient={hueGradient(0.15)}
372
- color={oklchColor(state.palette.deneutralize?.hueShift?.targetHue ?? 0, 0.15)}
373
- />
374
- <Slider
375
- label="hue shift amt"
376
- value={state.palette.deneutralize?.hueShift?.amount ?? 0}
377
- min={0}
378
- max={1}
379
- step={0.01}
380
- onChange={(v) =>
381
- state.updateFilter({
382
- hueShift: {targetHue: state.palette.deneutralize?.hueShift?.targetHue ?? 0, amount: v},
383
- })
384
- }
385
- />
386
- <Slider
387
- label="oklch L"
388
- value={state.palette.deneutralize?.oklchL ?? 1}
389
- min={0}
390
- max={2}
391
- step={0.01}
392
- onChange={(v) => state.updateFilter({oklchL: v})}
393
- />
394
- <Slider
395
- label="oklch C"
396
- value={state.palette.deneutralize?.oklchC ?? 1}
397
- min={0}
398
- max={3}
399
- step={0.01}
400
- onChange={(v) => state.updateFilter({oklchC: v})}
401
- />
402
- <Slider
403
- label="brightness"
404
- value={state.palette.deneutralize?.brightness ?? 1}
405
- min={0}
406
- max={3}
407
- step={0.01}
408
- onChange={(v) => state.updateFilter({brightness: v})}
409
- />
410
- <Slider
411
- label="contrast"
412
- value={state.palette.deneutralize?.contrast ?? 1}
413
- min={0}
414
- max={3}
415
- step={0.01}
416
- onChange={(v) => state.updateFilter({contrast: v})}
417
- />
418
- <Slider
419
- label="saturate"
420
- value={state.palette.deneutralize?.saturate ?? 1}
421
- min={0}
422
- max={3}
423
- step={0.01}
424
- onChange={(v) => state.updateFilter({saturate: v})}
425
- />
426
- <Slider
427
- label="hue rotate"
428
- value={state.palette.deneutralize?.hueRotate ?? 0}
429
- min={-180}
430
- max={180}
431
- step={1}
432
- onChange={(v) => state.updateFilter({hueRotate: v})}
433
- />
434
- <Slider
435
- label="sepia"
436
- value={state.palette.deneutralize?.sepia ?? 0}
437
- min={0}
438
- max={1}
439
- step={0.01}
440
- onChange={(v) => state.updateFilter({sepia: v})}
441
- />
442
- <Slider
443
- label="grayscale"
444
- value={state.palette.deneutralize?.grayscale ?? 0}
445
- min={0}
446
- max={1}
447
- step={0.01}
448
- onChange={(v) => state.updateFilter({grayscale: v})}
449
- />
450
- <Slider
451
- label="invert"
452
- value={state.palette.deneutralize?.invert ?? 0}
453
- min={0}
454
- max={1}
455
- step={0.01}
456
- onChange={(v) => state.updateFilter({invert: v})}
457
- />
458
451
 
459
452
  <div className={sectionSeparatorCss} />
460
453