@newtonedev/editor 0.1.12 → 0.2.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.
- package/dist/Editor.d.ts.map +1 -1
- package/dist/components/CodeBlock.d.ts.map +1 -1
- package/dist/components/ConfiguratorPanel.d.ts +6 -3
- package/dist/components/ConfiguratorPanel.d.ts.map +1 -1
- package/dist/components/EditorHeader.d.ts +3 -2
- package/dist/components/EditorHeader.d.ts.map +1 -1
- package/dist/components/EditorShell.d.ts.map +1 -1
- package/dist/components/PresetSelector.d.ts +3 -2
- package/dist/components/PresetSelector.d.ts.map +1 -1
- package/dist/components/PreviewWindow.d.ts.map +1 -1
- package/dist/components/RightSidebar.d.ts.map +1 -1
- package/dist/components/Sidebar.d.ts +8 -1
- package/dist/components/Sidebar.d.ts.map +1 -1
- package/dist/components/TableOfContents.d.ts.map +1 -1
- package/dist/components/sections/ColorsSection.d.ts +6 -3
- package/dist/components/sections/ColorsSection.d.ts.map +1 -1
- package/dist/components/sections/DynamicRangeSection.d.ts +2 -2
- package/dist/components/sections/DynamicRangeSection.d.ts.map +1 -1
- package/dist/components/sections/FontsSection.d.ts +2 -2
- package/dist/components/sections/FontsSection.d.ts.map +1 -1
- package/dist/components/sections/IconsSection.d.ts +2 -2
- package/dist/components/sections/IconsSection.d.ts.map +1 -1
- package/dist/components/sections/OthersSection.d.ts +2 -2
- package/dist/components/sections/OthersSection.d.ts.map +1 -1
- package/dist/components/sections/ScalePlots.d.ts +11 -0
- package/dist/components/sections/ScalePlots.d.ts.map +1 -0
- package/dist/components/sections/index.d.ts +1 -0
- package/dist/components/sections/index.d.ts.map +1 -1
- package/dist/configurator/bridge/toCSS.d.ts +7 -0
- package/dist/configurator/bridge/toCSS.d.ts.map +1 -0
- package/dist/configurator/bridge/toJSON.d.ts +15 -0
- package/dist/configurator/bridge/toJSON.d.ts.map +1 -0
- package/dist/configurator/bridge/toThemeConfig.d.ts +8 -0
- package/dist/configurator/bridge/toThemeConfig.d.ts.map +1 -0
- package/dist/configurator/constants.d.ts +13 -0
- package/dist/configurator/constants.d.ts.map +1 -0
- package/dist/configurator/hex-conversion.d.ts +21 -0
- package/dist/configurator/hex-conversion.d.ts.map +1 -0
- package/dist/configurator/hooks/useConfigurator.d.ts +11 -0
- package/dist/configurator/hooks/useConfigurator.d.ts.map +1 -0
- package/dist/configurator/hooks/usePreviewColors.d.ts +8 -0
- package/dist/configurator/hooks/usePreviewColors.d.ts.map +1 -0
- package/dist/configurator/hooks/useWcagValidation.d.ts +20 -0
- package/dist/configurator/hooks/useWcagValidation.d.ts.map +1 -0
- package/dist/configurator/hue-conversion.d.ts +10 -0
- package/dist/configurator/hue-conversion.d.ts.map +1 -0
- package/dist/configurator/state/actions.d.ts +107 -0
- package/dist/configurator/state/actions.d.ts.map +1 -0
- package/dist/configurator/state/defaults.d.ts +7 -0
- package/dist/configurator/state/defaults.d.ts.map +1 -0
- package/dist/configurator/state/reducer.d.ts +19 -0
- package/dist/configurator/state/reducer.d.ts.map +1 -0
- package/dist/configurator/types.d.ts +60 -0
- package/dist/configurator/types.d.ts.map +1 -0
- package/dist/hooks/useEditorState.d.ts +8 -6
- package/dist/hooks/useEditorState.d.ts.map +1 -1
- package/dist/hooks/usePresets.d.ts +7 -6
- package/dist/hooks/usePresets.d.ts.map +1 -1
- package/dist/index.cjs +30372 -808
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +30351 -799
- package/dist/index.js.map +1 -1
- package/dist/preview/CategoryView.d.ts.map +1 -1
- package/dist/preview/ComponentDetailView.d.ts.map +1 -1
- package/dist/preview/ComponentRenderer.d.ts.map +1 -1
- package/dist/preview/IconBrowserView.d.ts.map +1 -1
- package/dist/preview/OverviewView.d.ts.map +1 -1
- package/dist/preview/PaletteScaleView.d.ts +11 -0
- package/dist/preview/PaletteScaleView.d.ts.map +1 -0
- package/dist/types.d.ts +4 -3
- package/dist/types.d.ts.map +1 -1
- package/package.json +7 -4
- package/src/Editor.tsx +43 -19
- package/src/components/CodeBlock.tsx +7 -11
- package/src/components/ConfiguratorPanel.tsx +25 -18
- package/src/components/EditorHeader.tsx +29 -39
- package/src/components/EditorShell.tsx +17 -29
- package/src/components/FontPicker.tsx +7 -7
- package/src/components/PresetSelector.tsx +211 -129
- package/src/components/PreviewWindow.tsx +5 -12
- package/src/components/PrimaryNav.tsx +6 -6
- package/src/components/RightSidebar.tsx +24 -25
- package/src/components/Sidebar.tsx +54 -60
- package/src/components/TableOfContents.tsx +4 -5
- package/src/components/sections/ColorsSection.tsx +109 -121
- package/src/components/sections/DynamicRangeSection.tsx +61 -75
- package/src/components/sections/FontsSection.tsx +17 -28
- package/src/components/sections/IconsSection.tsx +2 -2
- package/src/components/sections/OthersSection.tsx +4 -5
- package/src/components/sections/ScalePlots.tsx +221 -0
- package/src/components/sections/index.ts +1 -0
- package/src/configurator/bridge/toCSS.ts +44 -0
- package/src/configurator/bridge/toJSON.ts +24 -0
- package/src/configurator/bridge/toThemeConfig.ts +114 -0
- package/src/configurator/constants.ts +13 -0
- package/src/configurator/hex-conversion.ts +67 -0
- package/src/configurator/hooks/useConfigurator.ts +33 -0
- package/src/configurator/hooks/usePreviewColors.ts +47 -0
- package/src/configurator/hooks/useWcagValidation.ts +133 -0
- package/src/configurator/hue-conversion.ts +25 -0
- package/src/configurator/state/actions.ts +43 -0
- package/src/configurator/state/defaults.ts +107 -0
- package/src/configurator/state/reducer.ts +399 -0
- package/src/configurator/types.ts +65 -0
- package/src/hooks/useEditorState.ts +25 -11
- package/src/hooks/usePresets.ts +54 -33
- package/src/index.ts +33 -0
- package/src/preview/CategoryView.tsx +8 -11
- package/src/preview/ComponentDetailView.tsx +24 -54
- package/src/preview/ComponentRenderer.tsx +2 -4
- package/src/preview/IconBrowserView.tsx +9 -10
- package/src/preview/OverviewView.tsx +9 -12
- package/src/preview/PaletteScaleView.tsx +122 -0
- package/src/types.ts +4 -3
|
@@ -2,7 +2,6 @@ import { useState, useMemo, useCallback, useEffect } from "react";
|
|
|
2
2
|
import {
|
|
3
3
|
HueSlider,
|
|
4
4
|
Slider,
|
|
5
|
-
Select,
|
|
6
5
|
Toggle,
|
|
7
6
|
ColorScaleSlider,
|
|
8
7
|
TextInput,
|
|
@@ -10,40 +9,42 @@ import {
|
|
|
10
9
|
} from "@newtonedev/components";
|
|
11
10
|
import type { ColorMode } from "@newtonedev/components";
|
|
12
11
|
import { srgbToHex } from "newtone";
|
|
13
|
-
import type { ColorResult
|
|
14
|
-
import
|
|
15
|
-
import type { ConfiguratorState } from "
|
|
16
|
-
import type { ConfiguratorAction } from "
|
|
17
|
-
import {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
hexToPaletteParams,
|
|
21
|
-
traditionalHueToOklch,
|
|
22
|
-
} from "@newtonedev/configurator";
|
|
23
|
-
|
|
24
|
-
const STRENGTH_OPTIONS = [
|
|
25
|
-
{ label: "None", value: "none" },
|
|
26
|
-
{ label: "Low", value: "low" },
|
|
27
|
-
{ label: "Medium", value: "medium" },
|
|
28
|
-
{ label: "Hard", value: "hard" },
|
|
29
|
-
];
|
|
30
|
-
|
|
12
|
+
import type { ColorResult } from "newtone";
|
|
13
|
+
import { oklchToP3 } from "@newtonedev/colors";
|
|
14
|
+
import type { ConfiguratorState } from "../../configurator/types";
|
|
15
|
+
import type { ConfiguratorAction } from "../../configurator/state/actions";
|
|
16
|
+
import { SEMANTIC_HUE_RANGES } from "../../configurator/constants";
|
|
17
|
+
import { useWcagValidation } from "../../configurator/hooks/useWcagValidation";
|
|
18
|
+
import { hexToPaletteParams } from "../../configurator/hex-conversion";
|
|
31
19
|
interface ColorsSectionProps {
|
|
32
20
|
readonly state: ConfiguratorState;
|
|
33
21
|
readonly dispatch: (action: ConfiguratorAction) => void;
|
|
34
22
|
readonly previewColors: readonly (readonly ColorResult[])[];
|
|
35
23
|
readonly colorMode: ColorMode;
|
|
36
24
|
readonly onColorModeChange: (mode: ColorMode) => void;
|
|
25
|
+
readonly activePaletteIndex: number;
|
|
26
|
+
readonly onActivePaletteChange: (index: number) => void;
|
|
27
|
+
readonly useP3: boolean;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Format a ColorResult as a CSS color string in the appropriate gamut. */
|
|
31
|
+
function colorToCss(color: ColorResult, useP3: boolean): string {
|
|
32
|
+
if (!useP3) return srgbToHex(color.srgb);
|
|
33
|
+
const p3 = oklchToP3(color.oklch);
|
|
34
|
+
const r = Math.max(0, Math.min(1, p3.r));
|
|
35
|
+
const g = Math.max(0, Math.min(1, p3.g));
|
|
36
|
+
const b = Math.max(0, Math.min(1, p3.b));
|
|
37
|
+
return `color(display-p3 ${r.toFixed(5)} ${g.toFixed(5)} ${b.toFixed(5)})`;
|
|
37
38
|
}
|
|
38
39
|
|
|
39
|
-
/**
|
|
40
|
-
function
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
return
|
|
40
|
+
/** Convert step index [0=lightest, 25=darkest] to NV [0=darkest, 1=lightest] for ColorScaleSlider */
|
|
41
|
+
function stepToNv(step: number): number {
|
|
42
|
+
return 1 - Math.max(0, Math.min(25, step)) / 25;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Convert NV [0=darkest, 1=lightest] to step index [0=lightest, 25=darkest] */
|
|
46
|
+
function nvToStep(nv: number): number {
|
|
47
|
+
return Math.round((1 - nv) * 25);
|
|
47
48
|
}
|
|
48
49
|
|
|
49
50
|
export function ColorsSection({
|
|
@@ -52,31 +53,33 @@ export function ColorsSection({
|
|
|
52
53
|
previewColors,
|
|
53
54
|
colorMode,
|
|
54
55
|
onColorModeChange,
|
|
56
|
+
activePaletteIndex,
|
|
57
|
+
onActivePaletteChange,
|
|
58
|
+
useP3,
|
|
55
59
|
}: ColorsSectionProps) {
|
|
56
60
|
const tokens = useTokens();
|
|
57
|
-
const [activePaletteIndex, setActivePaletteIndex] = useState(0);
|
|
58
61
|
const [modeToggleHovered, setModeToggleHovered] = useState(false);
|
|
59
62
|
|
|
60
63
|
const palette = state.palettes[activePaletteIndex];
|
|
61
64
|
const hueRange = SEMANTIC_HUE_RANGES[activePaletteIndex];
|
|
62
|
-
const
|
|
65
|
+
const isPrimary = activePaletteIndex === 0;
|
|
63
66
|
|
|
64
|
-
const activeColor =
|
|
65
|
-
const borderColor =
|
|
67
|
+
const activeColor = tokens.colors.secondary.emphasis.fontPrimary;
|
|
68
|
+
const borderColor = tokens.colors.primary.main.fontDisabled;
|
|
66
69
|
|
|
67
|
-
// Resolve effective key color for current mode
|
|
68
|
-
const
|
|
69
|
-
colorMode === "dark" ? palette.
|
|
70
|
+
// Resolve effective key color step for current mode
|
|
71
|
+
const effectiveKeyStep =
|
|
72
|
+
colorMode === "dark" ? palette.keyColorStepDark : palette.keyColorStep;
|
|
70
73
|
|
|
71
74
|
// Mode-aware action types
|
|
72
75
|
const setKeyColorAction =
|
|
73
76
|
colorMode === "dark"
|
|
74
|
-
? ("
|
|
75
|
-
: ("
|
|
77
|
+
? ("SET_PALETTE_KEY_COLOR_STEP_DARK" as const)
|
|
78
|
+
: ("SET_PALETTE_KEY_COLOR_STEP" as const);
|
|
76
79
|
const clearKeyColorAction =
|
|
77
80
|
colorMode === "dark"
|
|
78
|
-
? ("
|
|
79
|
-
: ("
|
|
81
|
+
? ("CLEAR_PALETTE_KEY_COLOR_STEP_DARK" as const)
|
|
82
|
+
: ("CLEAR_PALETTE_KEY_COLOR_STEP" as const);
|
|
80
83
|
const hexAction =
|
|
81
84
|
colorMode === "dark"
|
|
82
85
|
? ("SET_PALETTE_FROM_HEX_DARK" as const)
|
|
@@ -99,13 +102,15 @@ export function ColorsSection({
|
|
|
99
102
|
setIsHexUserSet(false);
|
|
100
103
|
}, [colorMode]);
|
|
101
104
|
|
|
102
|
-
// Compute displayed hex from current key color
|
|
105
|
+
// Compute displayed hex from current key color step (always sRGB for text input)
|
|
103
106
|
const currentPreview = previewColors[activePaletteIndex];
|
|
104
107
|
const displayedHex = useMemo(() => {
|
|
105
108
|
if (!currentPreview || currentPreview.length === 0) return "";
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
|
|
109
|
+
const step = effectiveKeyStep ?? wcag.autoStep;
|
|
110
|
+
// step 0=lightest (array start), step 25=darkest (array end)
|
|
111
|
+
const idx = Math.max(0, Math.min(currentPreview.length - 1, step));
|
|
112
|
+
return colorToCss(currentPreview[idx], false);
|
|
113
|
+
}, [currentPreview, effectiveKeyStep, wcag.autoStep]);
|
|
109
114
|
|
|
110
115
|
// Sync hex text when not actively editing and not user-submitted
|
|
111
116
|
useEffect(() => {
|
|
@@ -114,30 +119,6 @@ export function ColorsSection({
|
|
|
114
119
|
}
|
|
115
120
|
}, [displayedHex, isEditingHex, isHexUserSet]);
|
|
116
121
|
|
|
117
|
-
// Build dynamic range for hex conversion
|
|
118
|
-
const dynamicRange = useMemo((): DynamicRange => {
|
|
119
|
-
const light =
|
|
120
|
-
state.globalHueGrading.light.strength !== "none"
|
|
121
|
-
? {
|
|
122
|
-
hue: traditionalHueToOklch(state.globalHueGrading.light.hue),
|
|
123
|
-
strength: state.globalHueGrading.light.strength,
|
|
124
|
-
}
|
|
125
|
-
: undefined;
|
|
126
|
-
const dark =
|
|
127
|
-
state.globalHueGrading.dark.strength !== "none"
|
|
128
|
-
? {
|
|
129
|
-
hue: traditionalHueToOklch(state.globalHueGrading.dark.hue),
|
|
130
|
-
strength: state.globalHueGrading.dark.strength,
|
|
131
|
-
}
|
|
132
|
-
: undefined;
|
|
133
|
-
const hueGrading = light || dark ? { light, dark } : undefined;
|
|
134
|
-
return {
|
|
135
|
-
lightest: state.dynamicRange.lightest,
|
|
136
|
-
darkest: state.dynamicRange.darkest,
|
|
137
|
-
...(hueGrading ? { hueGrading } : {}),
|
|
138
|
-
};
|
|
139
|
-
}, [state.dynamicRange, state.globalHueGrading]);
|
|
140
|
-
|
|
141
122
|
const handleHexSubmit = useCallback(() => {
|
|
142
123
|
setIsEditingHex(false);
|
|
143
124
|
const trimmed = hexText.trim();
|
|
@@ -147,7 +128,7 @@ export function ColorsSection({
|
|
|
147
128
|
}
|
|
148
129
|
|
|
149
130
|
const hex = trimmed.startsWith("#") ? trimmed : `#${trimmed}`;
|
|
150
|
-
const params = hexToPaletteParams(hex, dynamicRange);
|
|
131
|
+
const params = hexToPaletteParams(hex, state.dynamicRange);
|
|
151
132
|
|
|
152
133
|
if (!params) {
|
|
153
134
|
setHexError("Invalid hex color");
|
|
@@ -160,10 +141,10 @@ export function ColorsSection({
|
|
|
160
141
|
type: hexAction,
|
|
161
142
|
index: activePaletteIndex,
|
|
162
143
|
hue: params.hue,
|
|
163
|
-
|
|
164
|
-
|
|
144
|
+
chromaRatio: params.chromaRatio,
|
|
145
|
+
keyColorStep: params.stepIndex,
|
|
165
146
|
});
|
|
166
|
-
}, [hexText, dynamicRange, dispatch, activePaletteIndex, hexAction]);
|
|
147
|
+
}, [hexText, state.dynamicRange, dispatch, activePaletteIndex, hexAction]);
|
|
167
148
|
|
|
168
149
|
const handleClearKeyColor = useCallback(() => {
|
|
169
150
|
dispatch({ type: clearKeyColorAction, index: activePaletteIndex });
|
|
@@ -173,7 +154,7 @@ export function ColorsSection({
|
|
|
173
154
|
|
|
174
155
|
// Build WCAG warning message
|
|
175
156
|
const wcagWarning = useMemo(() => {
|
|
176
|
-
if (
|
|
157
|
+
if (effectiveKeyStep === undefined || wcag.keyColorContrast === null)
|
|
177
158
|
return undefined;
|
|
178
159
|
if (wcag.passesAA) return undefined;
|
|
179
160
|
const ratio = wcag.keyColorContrast.toFixed(1);
|
|
@@ -181,7 +162,9 @@ export function ColorsSection({
|
|
|
181
162
|
return `Contrast ${ratio}:1 — passes large text (AA) but fails normal text (requires 4.5:1)`;
|
|
182
163
|
}
|
|
183
164
|
return `Contrast ${ratio}:1 — fails WCAG AA (requires 4.5:1 for normal text, 3:1 for large text)`;
|
|
184
|
-
}, [
|
|
165
|
+
}, [effectiveKeyStep, wcag]);
|
|
166
|
+
|
|
167
|
+
const localIntensity = palette.localHueGrade?.intensity ?? 0;
|
|
185
168
|
|
|
186
169
|
return (
|
|
187
170
|
<div style={{ display: "flex", flexDirection: "column", gap: 16 }}>
|
|
@@ -190,27 +173,28 @@ export function ColorsSection({
|
|
|
190
173
|
{state.palettes.map((_p, index) => {
|
|
191
174
|
const isActive = index === activePaletteIndex;
|
|
192
175
|
const colors = previewColors[index];
|
|
193
|
-
const
|
|
194
|
-
|
|
195
|
-
// Per-mode key color for this palette's circle
|
|
196
|
-
const
|
|
197
|
-
colorMode === "dark" ? _p.
|
|
176
|
+
const isPrimaryCircle = index === 0;
|
|
177
|
+
|
|
178
|
+
// Per-mode key color step for this palette's circle
|
|
179
|
+
const paletteKeyStep =
|
|
180
|
+
colorMode === "dark" ? _p.keyColorStepDark : _p.keyColorStep;
|
|
181
|
+
const circleStep = paletteKeyStep ?? wcag.autoStep;
|
|
182
|
+
const circleIdx = !isPrimaryCircle && colors
|
|
183
|
+
? Math.max(0, Math.min(colors.length - 1, circleStep))
|
|
184
|
+
: -1;
|
|
198
185
|
const circleColor =
|
|
199
|
-
!
|
|
200
|
-
?
|
|
201
|
-
colors,
|
|
202
|
-
paletteKeyColor ?? wcag.autoNormalizedValue,
|
|
203
|
-
)
|
|
186
|
+
!isPrimaryCircle && colors && circleIdx >= 0
|
|
187
|
+
? colorToCss(colors[circleIdx], useP3)
|
|
204
188
|
: undefined;
|
|
205
189
|
|
|
206
190
|
const ringStyle = isActive
|
|
207
|
-
? `0 0 0 2px ${
|
|
191
|
+
? `0 0 0 2px ${tokens.colors.primary.main.background}, 0 0 0 4px ${activeColor}`
|
|
208
192
|
: "none";
|
|
209
193
|
|
|
210
194
|
return (
|
|
211
195
|
<button
|
|
212
196
|
key={index}
|
|
213
|
-
onClick={() =>
|
|
197
|
+
onClick={() => onActivePaletteChange(index)}
|
|
214
198
|
aria-label={_p.name}
|
|
215
199
|
aria-pressed={isActive}
|
|
216
200
|
style={{
|
|
@@ -224,10 +208,10 @@ export function ColorsSection({
|
|
|
224
208
|
transition: "box-shadow 150ms ease",
|
|
225
209
|
padding: 0,
|
|
226
210
|
overflow: "hidden",
|
|
227
|
-
...(
|
|
211
|
+
...(isPrimaryCircle
|
|
228
212
|
? {
|
|
229
213
|
background: colors
|
|
230
|
-
? `linear-gradient(to right, ${
|
|
214
|
+
? `linear-gradient(to right, ${colorToCss(colors[0], useP3)} 50%, ${colorToCss(colors[colors.length - 1], useP3)} 50%)`
|
|
231
215
|
: `linear-gradient(to right, #ffffff 50%, #000000 50%)`,
|
|
232
216
|
}
|
|
233
217
|
: { backgroundColor: circleColor ?? borderColor }),
|
|
@@ -261,18 +245,19 @@ export function ColorsSection({
|
|
|
261
245
|
background: modeToggleHovered ? `${borderColor}20` : "none",
|
|
262
246
|
cursor: "pointer",
|
|
263
247
|
fontSize: 12,
|
|
264
|
-
color:
|
|
248
|
+
color: tokens.colors.primary.main.fontPrimary,
|
|
265
249
|
transition: "background-color 150ms ease",
|
|
266
250
|
}}
|
|
267
251
|
>
|
|
268
252
|
{colorMode === "light" ? "\u2600" : "\u263E"}
|
|
269
253
|
<span>{colorMode === "light" ? "Light" : "Dark"}</span>
|
|
270
254
|
</button>
|
|
255
|
+
|
|
271
256
|
</div>
|
|
272
257
|
|
|
273
258
|
{/* ─── Key Color (mode-specific) ─── */}
|
|
274
259
|
{currentPreview &&
|
|
275
|
-
(
|
|
260
|
+
(isPrimary ? (
|
|
276
261
|
<div style={{ display: "flex", gap: 1 }}>
|
|
277
262
|
{currentPreview.map((color, i) => (
|
|
278
263
|
<div
|
|
@@ -281,7 +266,7 @@ export function ColorsSection({
|
|
|
281
266
|
flex: 1,
|
|
282
267
|
height: 64,
|
|
283
268
|
borderRadius: 2,
|
|
284
|
-
backgroundColor:
|
|
269
|
+
backgroundColor: colorToCss(color, useP3),
|
|
285
270
|
}}
|
|
286
271
|
/>
|
|
287
272
|
))}
|
|
@@ -292,13 +277,13 @@ export function ColorsSection({
|
|
|
292
277
|
>
|
|
293
278
|
<ColorScaleSlider
|
|
294
279
|
colors={currentPreview}
|
|
295
|
-
value={
|
|
280
|
+
value={stepToNv(effectiveKeyStep ?? wcag.autoStep)}
|
|
296
281
|
onValueChange={(nv) => {
|
|
297
282
|
setIsHexUserSet(false);
|
|
298
283
|
dispatch({
|
|
299
284
|
type: setKeyColorAction,
|
|
300
285
|
index: activePaletteIndex,
|
|
301
|
-
|
|
286
|
+
step: nvToStep(nv),
|
|
302
287
|
});
|
|
303
288
|
}}
|
|
304
289
|
trimEnds
|
|
@@ -328,7 +313,7 @@ export function ColorsSection({
|
|
|
328
313
|
placeholder="#000000"
|
|
329
314
|
/>
|
|
330
315
|
</div>
|
|
331
|
-
{
|
|
316
|
+
{effectiveKeyStep !== undefined && (
|
|
332
317
|
<button
|
|
333
318
|
onClick={handleClearKeyColor}
|
|
334
319
|
style={{
|
|
@@ -350,7 +335,7 @@ export function ColorsSection({
|
|
|
350
335
|
style={{
|
|
351
336
|
fontSize: 12,
|
|
352
337
|
fontWeight: 500,
|
|
353
|
-
color:
|
|
338
|
+
color: tokens.colors.error.emphasis.fontPrimary,
|
|
354
339
|
}}
|
|
355
340
|
>
|
|
356
341
|
{hexError}
|
|
@@ -385,84 +370,87 @@ export function ColorsSection({
|
|
|
385
370
|
/>
|
|
386
371
|
|
|
387
372
|
<Slider
|
|
388
|
-
value={palette.
|
|
389
|
-
onValueChange={(
|
|
373
|
+
value={Math.round(palette.chromaRatio * 100)}
|
|
374
|
+
onValueChange={(v) =>
|
|
390
375
|
dispatch({
|
|
391
|
-
type: "
|
|
376
|
+
type: "SET_PALETTE_CHROMA_RATIO",
|
|
392
377
|
index: activePaletteIndex,
|
|
393
|
-
|
|
378
|
+
chromaRatio: v / 100,
|
|
394
379
|
})
|
|
395
380
|
}
|
|
396
381
|
min={0}
|
|
397
382
|
max={100}
|
|
398
|
-
label="
|
|
383
|
+
label="Chroma"
|
|
399
384
|
editableValue
|
|
400
385
|
/>
|
|
401
386
|
|
|
402
|
-
{/* Desaturation */}
|
|
403
387
|
<Slider
|
|
404
|
-
value={Math.round(
|
|
388
|
+
value={Math.round(palette.chromaPeak * 100)}
|
|
405
389
|
onValueChange={(v) =>
|
|
406
390
|
dispatch({
|
|
407
|
-
type: "
|
|
391
|
+
type: "SET_PALETTE_CHROMA_PEAK",
|
|
408
392
|
index: activePaletteIndex,
|
|
409
|
-
|
|
393
|
+
chromaPeak: v / 100,
|
|
410
394
|
})
|
|
411
395
|
}
|
|
412
|
-
min={
|
|
396
|
+
min={0}
|
|
413
397
|
max={100}
|
|
414
|
-
label="
|
|
398
|
+
label="Balance"
|
|
415
399
|
editableValue
|
|
400
|
+
disabled={palette.chromaRatio <= 0 || palette.chromaRatio >= 1}
|
|
416
401
|
/>
|
|
417
402
|
|
|
418
|
-
{/*
|
|
403
|
+
{/* Per-palette hue grading */}
|
|
419
404
|
<div style={{ display: "flex", gap: 12, alignItems: "flex-end" }}>
|
|
420
405
|
<div style={{ flex: 1 }}>
|
|
421
|
-
<
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
onValueChange={(strength) =>
|
|
406
|
+
<Slider
|
|
407
|
+
value={Math.round(localIntensity * 100)}
|
|
408
|
+
onValueChange={(v) =>
|
|
425
409
|
dispatch({
|
|
426
|
-
type: "
|
|
410
|
+
type: "SET_PALETTE_LOCAL_HUE_GRADE_INTENSITY",
|
|
427
411
|
index: activePaletteIndex,
|
|
428
|
-
|
|
412
|
+
intensity: v / 100,
|
|
429
413
|
})
|
|
430
414
|
}
|
|
431
|
-
|
|
415
|
+
min={0}
|
|
416
|
+
max={50}
|
|
417
|
+
label="Shift"
|
|
418
|
+
editableValue
|
|
432
419
|
/>
|
|
433
420
|
</div>
|
|
434
|
-
{
|
|
421
|
+
{localIntensity > 0 && (
|
|
435
422
|
<div style={{ paddingBottom: 2 }}>
|
|
436
423
|
<Toggle
|
|
437
|
-
value={palette.
|
|
424
|
+
value={palette.localHueGrade?.side === "dark"}
|
|
438
425
|
onValueChange={(v) =>
|
|
439
426
|
dispatch({
|
|
440
|
-
type: "
|
|
427
|
+
type: "SET_PALETTE_LOCAL_HUE_GRADE_SIDE",
|
|
441
428
|
index: activePaletteIndex,
|
|
442
|
-
|
|
429
|
+
side: v ? "dark" : "light",
|
|
443
430
|
})
|
|
444
431
|
}
|
|
445
|
-
label="
|
|
432
|
+
label="Dark"
|
|
446
433
|
/>
|
|
447
434
|
</div>
|
|
448
435
|
)}
|
|
449
436
|
</div>
|
|
450
437
|
|
|
451
|
-
{
|
|
438
|
+
{localIntensity > 0 && (
|
|
452
439
|
<HueSlider
|
|
453
|
-
value={palette.
|
|
440
|
+
value={palette.localHueGrade?.hue ?? palette.hue}
|
|
454
441
|
onValueChange={(hue) =>
|
|
455
442
|
dispatch({
|
|
456
|
-
type: "
|
|
443
|
+
type: "SET_PALETTE_LOCAL_HUE_GRADE_HUE",
|
|
457
444
|
index: activePaletteIndex,
|
|
458
445
|
hue,
|
|
459
446
|
})
|
|
460
447
|
}
|
|
461
|
-
label="
|
|
448
|
+
label="Shift Hue"
|
|
462
449
|
editableValue
|
|
463
450
|
/>
|
|
464
451
|
)}
|
|
465
452
|
</div>
|
|
453
|
+
|
|
466
454
|
</div>
|
|
467
455
|
);
|
|
468
456
|
}
|
|
@@ -1,16 +1,7 @@
|
|
|
1
1
|
import { useState, useRef, useCallback } from "react";
|
|
2
|
-
import { HueSlider,
|
|
3
|
-
import {
|
|
4
|
-
import type {
|
|
5
|
-
import type { ConfiguratorState } from "@newtonedev/configurator";
|
|
6
|
-
import type { ConfiguratorAction } from "@newtonedev/configurator";
|
|
7
|
-
|
|
8
|
-
const STRENGTH_OPTIONS = [
|
|
9
|
-
{ label: "None", value: "none" },
|
|
10
|
-
{ label: "Low", value: "low" },
|
|
11
|
-
{ label: "Medium", value: "medium" },
|
|
12
|
-
{ label: "Hard", value: "hard" },
|
|
13
|
-
];
|
|
2
|
+
import { HueSlider, Slider, useTokens } from "@newtonedev/components";
|
|
3
|
+
import type { ConfiguratorState } from "../../configurator/types";
|
|
4
|
+
import type { ConfiguratorAction } from "../../configurator/state/actions";
|
|
14
5
|
|
|
15
6
|
// --- Dual Range Slider ---
|
|
16
7
|
|
|
@@ -76,8 +67,8 @@ function DualRangeSlider({
|
|
|
76
67
|
"whites" | "blacks" | null
|
|
77
68
|
>(null);
|
|
78
69
|
|
|
79
|
-
const interactiveColor =
|
|
80
|
-
const borderColor =
|
|
70
|
+
const interactiveColor = tokens.colors.secondary.emphasis.fontPrimary;
|
|
71
|
+
const borderColor = tokens.colors.primary.main.fontDisabled;
|
|
81
72
|
|
|
82
73
|
const wDisplay = internalToDisplay(whitesValue);
|
|
83
74
|
const bDisplay = internalToDisplay(blacksValue);
|
|
@@ -250,10 +241,10 @@ function RangeInput({ display, onCommit, toInternal }: RangeInputProps) {
|
|
|
250
241
|
style={{
|
|
251
242
|
width: 40,
|
|
252
243
|
padding: "2px 6px",
|
|
253
|
-
border: `1px solid ${
|
|
244
|
+
border: `1px solid ${tokens.colors.primary.main.fontDisabled}`,
|
|
254
245
|
borderRadius: 4,
|
|
255
246
|
backgroundColor: "transparent",
|
|
256
|
-
color:
|
|
247
|
+
color: tokens.colors.primary.main.fontPrimary,
|
|
257
248
|
fontFamily: "inherit",
|
|
258
249
|
fontSize: 12,
|
|
259
250
|
fontWeight: 500,
|
|
@@ -276,7 +267,7 @@ export function DynamicRangeSection({
|
|
|
276
267
|
dispatch,
|
|
277
268
|
}: DynamicRangeSectionProps) {
|
|
278
269
|
const tokens = useTokens();
|
|
279
|
-
const labelColor =
|
|
270
|
+
const labelColor = tokens.colors.primary.main.fontTertiary;
|
|
280
271
|
|
|
281
272
|
const labelStyle = {
|
|
282
273
|
fontSize: 11,
|
|
@@ -289,6 +280,9 @@ export function DynamicRangeSection({
|
|
|
289
280
|
const wDisplay = internalToDisplay(state.dynamicRange.lightest);
|
|
290
281
|
const bDisplay = internalToDisplay(state.dynamicRange.darkest);
|
|
291
282
|
|
|
283
|
+
const lightIntensity = state.globalHueGrading.lightIntensity;
|
|
284
|
+
const darkIntensity = state.globalHueGrading.darkIntensity;
|
|
285
|
+
|
|
292
286
|
return (
|
|
293
287
|
<div style={{ display: "flex", flexDirection: "column", gap: 12 }}>
|
|
294
288
|
{/* Labels above slider */}
|
|
@@ -331,69 +325,61 @@ export function DynamicRangeSection({
|
|
|
331
325
|
/>
|
|
332
326
|
</div>
|
|
333
327
|
|
|
334
|
-
{/* Global
|
|
328
|
+
{/* Global Grading — Light End */}
|
|
335
329
|
<div style={{ ...labelStyle, marginTop: 4 }}>
|
|
336
|
-
|
|
337
|
-
</div>
|
|
338
|
-
<div style={{ display: "flex", gap: 12 }}>
|
|
339
|
-
<div style={{ flex: 1 }}>
|
|
340
|
-
<Select
|
|
341
|
-
options={STRENGTH_OPTIONS}
|
|
342
|
-
value={state.globalHueGrading.light.strength}
|
|
343
|
-
onValueChange={(s) =>
|
|
344
|
-
dispatch({
|
|
345
|
-
type: "SET_GLOBAL_GRADE_LIGHT_STRENGTH",
|
|
346
|
-
strength: s as HueGradingStrength,
|
|
347
|
-
})
|
|
348
|
-
}
|
|
349
|
-
label="Strength"
|
|
350
|
-
/>
|
|
351
|
-
</div>
|
|
352
|
-
{state.globalHueGrading.light.strength !== "none" && (
|
|
353
|
-
<div style={{ flex: 1 }}>
|
|
354
|
-
<HueSlider
|
|
355
|
-
value={state.globalHueGrading.light.hue}
|
|
356
|
-
onValueChange={(hue) =>
|
|
357
|
-
dispatch({ type: "SET_GLOBAL_GRADE_LIGHT_HUE", hue })
|
|
358
|
-
}
|
|
359
|
-
label="Target Hue"
|
|
360
|
-
showValue
|
|
361
|
-
/>
|
|
362
|
-
</div>
|
|
363
|
-
)}
|
|
330
|
+
Grading — Light
|
|
364
331
|
</div>
|
|
332
|
+
<Slider
|
|
333
|
+
value={Math.round(lightIntensity * 100)}
|
|
334
|
+
onValueChange={(v) =>
|
|
335
|
+
dispatch({
|
|
336
|
+
type: "SET_GLOBAL_GRADE_LIGHT_INTENSITY",
|
|
337
|
+
intensity: v / 100,
|
|
338
|
+
})
|
|
339
|
+
}
|
|
340
|
+
min={0}
|
|
341
|
+
max={25}
|
|
342
|
+
label="Intensity"
|
|
343
|
+
editableValue
|
|
344
|
+
/>
|
|
345
|
+
{lightIntensity > 0 && (
|
|
346
|
+
<HueSlider
|
|
347
|
+
value={state.globalHueGrading.lightHue}
|
|
348
|
+
onValueChange={(hue) =>
|
|
349
|
+
dispatch({ type: "SET_GLOBAL_GRADE_LIGHT_HUE", hue })
|
|
350
|
+
}
|
|
351
|
+
label="Target Hue"
|
|
352
|
+
showValue
|
|
353
|
+
/>
|
|
354
|
+
)}
|
|
365
355
|
|
|
366
|
-
{/* Global
|
|
356
|
+
{/* Global Grading — Dark End */}
|
|
367
357
|
<div style={{ ...labelStyle, marginTop: 4 }}>
|
|
368
|
-
|
|
369
|
-
</div>
|
|
370
|
-
<div style={{ display: "flex", gap: 12 }}>
|
|
371
|
-
<div style={{ flex: 1 }}>
|
|
372
|
-
<Select
|
|
373
|
-
options={STRENGTH_OPTIONS}
|
|
374
|
-
value={state.globalHueGrading.dark.strength}
|
|
375
|
-
onValueChange={(s) =>
|
|
376
|
-
dispatch({
|
|
377
|
-
type: "SET_GLOBAL_GRADE_DARK_STRENGTH",
|
|
378
|
-
strength: s as HueGradingStrength,
|
|
379
|
-
})
|
|
380
|
-
}
|
|
381
|
-
label="Strength"
|
|
382
|
-
/>
|
|
383
|
-
</div>
|
|
384
|
-
{state.globalHueGrading.dark.strength !== "none" && (
|
|
385
|
-
<div style={{ flex: 1 }}>
|
|
386
|
-
<HueSlider
|
|
387
|
-
value={state.globalHueGrading.dark.hue}
|
|
388
|
-
onValueChange={(hue) =>
|
|
389
|
-
dispatch({ type: "SET_GLOBAL_GRADE_DARK_HUE", hue })
|
|
390
|
-
}
|
|
391
|
-
label="Target Hue"
|
|
392
|
-
showValue
|
|
393
|
-
/>
|
|
394
|
-
</div>
|
|
395
|
-
)}
|
|
358
|
+
Grading — Dark
|
|
396
359
|
</div>
|
|
360
|
+
<Slider
|
|
361
|
+
value={Math.round(darkIntensity * 100)}
|
|
362
|
+
onValueChange={(v) =>
|
|
363
|
+
dispatch({
|
|
364
|
+
type: "SET_GLOBAL_GRADE_DARK_INTENSITY",
|
|
365
|
+
intensity: v / 100,
|
|
366
|
+
})
|
|
367
|
+
}
|
|
368
|
+
min={0}
|
|
369
|
+
max={25}
|
|
370
|
+
label="Intensity"
|
|
371
|
+
editableValue
|
|
372
|
+
/>
|
|
373
|
+
{darkIntensity > 0 && (
|
|
374
|
+
<HueSlider
|
|
375
|
+
value={state.globalHueGrading.darkHue}
|
|
376
|
+
onValueChange={(hue) =>
|
|
377
|
+
dispatch({ type: "SET_GLOBAL_GRADE_DARK_HUE", hue })
|
|
378
|
+
}
|
|
379
|
+
label="Target Hue"
|
|
380
|
+
showValue
|
|
381
|
+
/>
|
|
382
|
+
)}
|
|
397
383
|
</div>
|
|
398
384
|
);
|
|
399
385
|
}
|