@newtonedev/editor 0.1.11 → 0.2.0
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 +30380 -828
- 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 +30359 -819
- 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 +118 -147
- 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)})`;
|
|
38
|
+
}
|
|
39
|
+
|
|
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;
|
|
37
43
|
}
|
|
38
44
|
|
|
39
|
-
/**
|
|
40
|
-
function
|
|
41
|
-
|
|
42
|
-
nv: number,
|
|
43
|
-
): string {
|
|
44
|
-
const idx = Math.round((1 - nv) * (previewColors.length - 1));
|
|
45
|
-
const clamped = Math.max(0, Math.min(previewColors.length - 1, idx));
|
|
46
|
-
return srgbToHex(previewColors[clamped].srgb);
|
|
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,101 +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
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
</div>
|
|
418
|
-
{palette.desaturationStrength !== "none" && (
|
|
419
|
-
<div style={{ paddingBottom: 2 }}>
|
|
420
|
-
<Toggle
|
|
421
|
-
value={palette.desaturationDirection === "dark"}
|
|
422
|
-
onValueChange={(v) =>
|
|
423
|
-
dispatch({
|
|
424
|
-
type: "SET_PALETTE_DESAT_DIRECTION",
|
|
425
|
-
index: activePaletteIndex,
|
|
426
|
-
direction: v ? "dark" : "light",
|
|
427
|
-
})
|
|
428
|
-
}
|
|
429
|
-
label="Invert"
|
|
430
|
-
/>
|
|
431
|
-
</div>
|
|
432
|
-
)}
|
|
433
|
-
</div>
|
|
387
|
+
<Slider
|
|
388
|
+
value={Math.round(palette.chromaPeak * 100)}
|
|
389
|
+
onValueChange={(v) =>
|
|
390
|
+
dispatch({
|
|
391
|
+
type: "SET_PALETTE_CHROMA_PEAK",
|
|
392
|
+
index: activePaletteIndex,
|
|
393
|
+
chromaPeak: v / 100,
|
|
394
|
+
})
|
|
395
|
+
}
|
|
396
|
+
min={0}
|
|
397
|
+
max={100}
|
|
398
|
+
label="Balance"
|
|
399
|
+
editableValue
|
|
400
|
+
disabled={palette.chromaRatio <= 0 || palette.chromaRatio >= 1}
|
|
401
|
+
/>
|
|
434
402
|
|
|
435
|
-
{/*
|
|
403
|
+
{/* Per-palette hue grading */}
|
|
436
404
|
<div style={{ display: "flex", gap: 12, alignItems: "flex-end" }}>
|
|
437
405
|
<div style={{ flex: 1 }}>
|
|
438
|
-
<
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
onValueChange={(strength) =>
|
|
406
|
+
<Slider
|
|
407
|
+
value={Math.round(localIntensity * 100)}
|
|
408
|
+
onValueChange={(v) =>
|
|
442
409
|
dispatch({
|
|
443
|
-
type: "
|
|
410
|
+
type: "SET_PALETTE_LOCAL_HUE_GRADE_INTENSITY",
|
|
444
411
|
index: activePaletteIndex,
|
|
445
|
-
|
|
412
|
+
intensity: v / 100,
|
|
446
413
|
})
|
|
447
414
|
}
|
|
448
|
-
|
|
415
|
+
min={0}
|
|
416
|
+
max={50}
|
|
417
|
+
label="Shift"
|
|
418
|
+
editableValue
|
|
449
419
|
/>
|
|
450
420
|
</div>
|
|
451
|
-
{
|
|
421
|
+
{localIntensity > 0 && (
|
|
452
422
|
<div style={{ paddingBottom: 2 }}>
|
|
453
423
|
<Toggle
|
|
454
|
-
value={palette.
|
|
424
|
+
value={palette.localHueGrade?.side === "dark"}
|
|
455
425
|
onValueChange={(v) =>
|
|
456
426
|
dispatch({
|
|
457
|
-
type: "
|
|
427
|
+
type: "SET_PALETTE_LOCAL_HUE_GRADE_SIDE",
|
|
458
428
|
index: activePaletteIndex,
|
|
459
|
-
|
|
429
|
+
side: v ? "dark" : "light",
|
|
460
430
|
})
|
|
461
431
|
}
|
|
462
|
-
label="
|
|
432
|
+
label="Dark"
|
|
463
433
|
/>
|
|
464
434
|
</div>
|
|
465
435
|
)}
|
|
466
436
|
</div>
|
|
467
437
|
|
|
468
|
-
{
|
|
438
|
+
{localIntensity > 0 && (
|
|
469
439
|
<HueSlider
|
|
470
|
-
value={palette.
|
|
440
|
+
value={palette.localHueGrade?.hue ?? palette.hue}
|
|
471
441
|
onValueChange={(hue) =>
|
|
472
442
|
dispatch({
|
|
473
|
-
type: "
|
|
443
|
+
type: "SET_PALETTE_LOCAL_HUE_GRADE_HUE",
|
|
474
444
|
index: activePaletteIndex,
|
|
475
445
|
hue,
|
|
476
446
|
})
|
|
477
447
|
}
|
|
478
|
-
label="
|
|
448
|
+
label="Shift Hue"
|
|
479
449
|
editableValue
|
|
480
450
|
/>
|
|
481
451
|
)}
|
|
482
452
|
</div>
|
|
453
|
+
|
|
483
454
|
</div>
|
|
484
455
|
);
|
|
485
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
|
}
|