@newtonedev/editor 0.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.
- package/dist/Editor.d.ts +3 -0
- package/dist/Editor.d.ts.map +1 -0
- package/dist/components/CodeBlock.d.ts +7 -0
- package/dist/components/CodeBlock.d.ts.map +1 -0
- package/dist/components/EditorHeader.d.ts +16 -0
- package/dist/components/EditorHeader.d.ts.map +1 -0
- package/dist/components/EditorShell.d.ts +10 -0
- package/dist/components/EditorShell.d.ts.map +1 -0
- package/dist/components/FontPicker.d.ts +11 -0
- package/dist/components/FontPicker.d.ts.map +1 -0
- package/dist/components/PresetSelector.d.ts +14 -0
- package/dist/components/PresetSelector.d.ts.map +1 -0
- package/dist/components/PreviewWindow.d.ts +11 -0
- package/dist/components/PreviewWindow.d.ts.map +1 -0
- package/dist/components/RightSidebar.d.ts +12 -0
- package/dist/components/RightSidebar.d.ts.map +1 -0
- package/dist/components/Sidebar.d.ts +25 -0
- package/dist/components/Sidebar.d.ts.map +1 -0
- package/dist/components/TableOfContents.d.ts +9 -0
- package/dist/components/TableOfContents.d.ts.map +1 -0
- package/dist/components/ThemeBar.d.ts +8 -0
- package/dist/components/ThemeBar.d.ts.map +1 -0
- package/dist/components/sections/ColorsSection.d.ts +14 -0
- package/dist/components/sections/ColorsSection.d.ts.map +1 -0
- package/dist/components/sections/DynamicRangeSection.d.ts +9 -0
- package/dist/components/sections/DynamicRangeSection.d.ts.map +1 -0
- package/dist/components/sections/FontsSection.d.ts +9 -0
- package/dist/components/sections/FontsSection.d.ts.map +1 -0
- package/dist/components/sections/IconsSection.d.ts +9 -0
- package/dist/components/sections/IconsSection.d.ts.map +1 -0
- package/dist/components/sections/OthersSection.d.ts +9 -0
- package/dist/components/sections/OthersSection.d.ts.map +1 -0
- package/dist/components/sections/index.d.ts +6 -0
- package/dist/components/sections/index.d.ts.map +1 -0
- package/dist/hooks/useEditorState.d.ts +53 -0
- package/dist/hooks/useEditorState.d.ts.map +1 -0
- package/dist/hooks/useHover.d.ts +8 -0
- package/dist/hooks/useHover.d.ts.map +1 -0
- package/dist/hooks/usePresets.d.ts +33 -0
- package/dist/hooks/usePresets.d.ts.map +1 -0
- package/dist/index.cjs +3846 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3819 -0
- package/dist/index.js.map +1 -0
- package/dist/preview/CategoryView.d.ts +7 -0
- package/dist/preview/CategoryView.d.ts.map +1 -0
- package/dist/preview/ComponentDetailView.d.ts +9 -0
- package/dist/preview/ComponentDetailView.d.ts.map +1 -0
- package/dist/preview/ComponentRenderer.d.ts +7 -0
- package/dist/preview/ComponentRenderer.d.ts.map +1 -0
- package/dist/preview/OverviewView.d.ts +7 -0
- package/dist/preview/OverviewView.d.ts.map +1 -0
- package/dist/types.d.ts +69 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/utils/presets.d.ts +5 -0
- package/dist/utils/presets.d.ts.map +1 -0
- package/package.json +51 -0
- package/src/Editor.tsx +128 -0
- package/src/components/CodeBlock.tsx +58 -0
- package/src/components/EditorHeader.tsx +86 -0
- package/src/components/EditorShell.tsx +67 -0
- package/src/components/FontPicker.tsx +351 -0
- package/src/components/PresetSelector.tsx +455 -0
- package/src/components/PreviewWindow.tsx +69 -0
- package/src/components/RightSidebar.tsx +374 -0
- package/src/components/Sidebar.tsx +332 -0
- package/src/components/TableOfContents.tsx +152 -0
- package/src/components/ThemeBar.tsx +76 -0
- package/src/components/sections/ColorsSection.tsx +485 -0
- package/src/components/sections/DynamicRangeSection.tsx +399 -0
- package/src/components/sections/FontsSection.tsx +132 -0
- package/src/components/sections/IconsSection.tsx +66 -0
- package/src/components/sections/OthersSection.tsx +70 -0
- package/src/components/sections/index.ts +5 -0
- package/src/hooks/useEditorState.ts +381 -0
- package/src/hooks/useHover.ts +8 -0
- package/src/hooks/usePresets.ts +254 -0
- package/src/index.ts +52 -0
- package/src/preview/CategoryView.tsx +134 -0
- package/src/preview/ComponentDetailView.tsx +126 -0
- package/src/preview/ComponentRenderer.tsx +107 -0
- package/src/preview/OverviewView.tsx +177 -0
- package/src/types.ts +77 -0
- package/src/utils/presets.ts +24 -0
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
import { useState, useRef, useCallback } from "react";
|
|
2
|
+
import { HueSlider, Select, useTokens } from "@newtonedev/components";
|
|
3
|
+
import { srgbToHex } from "newtone";
|
|
4
|
+
import type { HueGradingStrength } from "newtone";
|
|
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
|
+
];
|
|
14
|
+
|
|
15
|
+
// --- Dual Range Slider ---
|
|
16
|
+
|
|
17
|
+
const TRACK_HEIGHT = 8;
|
|
18
|
+
const THUMB_SIZE = 18;
|
|
19
|
+
const ZONE_FRAC = 1 / 3;
|
|
20
|
+
|
|
21
|
+
function clamp(v: number, min: number, max: number): number {
|
|
22
|
+
return Math.min(max, Math.max(min, v));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// --- Display ↔ Internal conversions ---
|
|
26
|
+
// Both lightest and darkest are 0–1 slider positions.
|
|
27
|
+
// The engine maps these to OKLCH L via resolveLightest/resolveDarkest.
|
|
28
|
+
// Display: 0–10 integer scale, linearly mapped to 0.0–1.0.
|
|
29
|
+
|
|
30
|
+
function internalToDisplay(internal: number): number {
|
|
31
|
+
return clamp(Math.round(internal * 10), 0, 10);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function displayToInternal(display: number): number {
|
|
35
|
+
return clamp(display, 0, 10) / 10;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// --- Position ↔ Display conversions ---
|
|
39
|
+
// Left zone: pos 0 = display 10, pos ZONE_FRAC = display 0
|
|
40
|
+
// Right zone: pos (1-ZONE_FRAC) = display 0, pos 1 = display 10
|
|
41
|
+
|
|
42
|
+
function posToWhitesDisplay(pos: number): number {
|
|
43
|
+
const ratio = clamp(pos / ZONE_FRAC, 0, 1);
|
|
44
|
+
return Math.round(10 * (1 - ratio));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function posToBlacksDisplay(pos: number): number {
|
|
48
|
+
const ratio = clamp((pos - (1 - ZONE_FRAC)) / ZONE_FRAC, 0, 1);
|
|
49
|
+
return Math.round(ratio * 10);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function whitesDisplayToPos(display: number): number {
|
|
53
|
+
return ((10 - display) / 10) * ZONE_FRAC;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function blacksDisplayToPos(display: number): number {
|
|
57
|
+
return 1 - ZONE_FRAC + (display / 10) * ZONE_FRAC;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
interface DualRangeSliderProps {
|
|
61
|
+
readonly whitesValue: number; // internal 0–1
|
|
62
|
+
readonly blacksValue: number; // internal 0–1
|
|
63
|
+
readonly onWhitesChange: (v: number) => void;
|
|
64
|
+
readonly onBlacksChange: (v: number) => void;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function DualRangeSlider({
|
|
68
|
+
whitesValue,
|
|
69
|
+
blacksValue,
|
|
70
|
+
onWhitesChange,
|
|
71
|
+
onBlacksChange,
|
|
72
|
+
}: DualRangeSliderProps) {
|
|
73
|
+
const tokens = useTokens();
|
|
74
|
+
const trackRef = useRef<HTMLDivElement>(null);
|
|
75
|
+
const [activeThumb, setActiveThumb] = useState<
|
|
76
|
+
"whites" | "blacks" | null
|
|
77
|
+
>(null);
|
|
78
|
+
|
|
79
|
+
const interactiveColor = srgbToHex(tokens.interactive.srgb);
|
|
80
|
+
const borderColor = srgbToHex(tokens.border.srgb);
|
|
81
|
+
|
|
82
|
+
const wDisplay = internalToDisplay(whitesValue);
|
|
83
|
+
const bDisplay = internalToDisplay(blacksValue);
|
|
84
|
+
const wPos = whitesDisplayToPos(wDisplay);
|
|
85
|
+
const bPos = blacksDisplayToPos(bDisplay);
|
|
86
|
+
|
|
87
|
+
const getPosRatio = useCallback((clientX: number): number => {
|
|
88
|
+
if (!trackRef.current) return 0;
|
|
89
|
+
const rect = trackRef.current.getBoundingClientRect();
|
|
90
|
+
return clamp((clientX - rect.left) / rect.width, 0, 1);
|
|
91
|
+
}, []);
|
|
92
|
+
|
|
93
|
+
const handlePointerDown = useCallback(
|
|
94
|
+
(e: React.PointerEvent<HTMLDivElement>) => {
|
|
95
|
+
e.preventDefault();
|
|
96
|
+
const pos = getPosRatio(e.clientX);
|
|
97
|
+
|
|
98
|
+
if (pos <= ZONE_FRAC) {
|
|
99
|
+
setActiveThumb("whites");
|
|
100
|
+
onWhitesChange(displayToInternal(posToWhitesDisplay(pos)));
|
|
101
|
+
} else if (pos >= 1 - ZONE_FRAC) {
|
|
102
|
+
setActiveThumb("blacks");
|
|
103
|
+
onBlacksChange(displayToInternal(posToBlacksDisplay(pos)));
|
|
104
|
+
} else {
|
|
105
|
+
return; // dead zone
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
(e.currentTarget as HTMLElement).setPointerCapture(e.pointerId);
|
|
109
|
+
},
|
|
110
|
+
[getPosRatio, onWhitesChange, onBlacksChange],
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
const handlePointerMove = useCallback(
|
|
114
|
+
(e: React.PointerEvent) => {
|
|
115
|
+
if (!activeThumb) return;
|
|
116
|
+
const pos = getPosRatio(e.clientX);
|
|
117
|
+
|
|
118
|
+
if (activeThumb === "whites") {
|
|
119
|
+
onWhitesChange(displayToInternal(posToWhitesDisplay(pos)));
|
|
120
|
+
} else {
|
|
121
|
+
onBlacksChange(displayToInternal(posToBlacksDisplay(pos)));
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
[activeThumb, getPosRatio, onWhitesChange, onBlacksChange],
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
const handlePointerUp = useCallback(() => {
|
|
128
|
+
setActiveThumb(null);
|
|
129
|
+
}, []);
|
|
130
|
+
|
|
131
|
+
const trackTop = (THUMB_SIZE - TRACK_HEIGHT) / 2;
|
|
132
|
+
|
|
133
|
+
return (
|
|
134
|
+
<div style={{ padding: `0 ${THUMB_SIZE / 2}px` }}>
|
|
135
|
+
<div
|
|
136
|
+
ref={trackRef}
|
|
137
|
+
onPointerDown={handlePointerDown}
|
|
138
|
+
onPointerMove={handlePointerMove}
|
|
139
|
+
onPointerUp={handlePointerUp}
|
|
140
|
+
onPointerCancel={handlePointerUp}
|
|
141
|
+
style={{
|
|
142
|
+
position: "relative",
|
|
143
|
+
height: THUMB_SIZE,
|
|
144
|
+
cursor: activeThumb ? "grabbing" : "pointer",
|
|
145
|
+
touchAction: "none",
|
|
146
|
+
userSelect: "none",
|
|
147
|
+
}}
|
|
148
|
+
>
|
|
149
|
+
{/* Gradient track */}
|
|
150
|
+
<div
|
|
151
|
+
style={{
|
|
152
|
+
position: "absolute",
|
|
153
|
+
left: 0,
|
|
154
|
+
right: 0,
|
|
155
|
+
top: trackTop,
|
|
156
|
+
height: TRACK_HEIGHT,
|
|
157
|
+
borderRadius: TRACK_HEIGHT / 2,
|
|
158
|
+
background: "linear-gradient(to right, white, black)",
|
|
159
|
+
border: `1px solid ${borderColor}`,
|
|
160
|
+
boxSizing: "border-box",
|
|
161
|
+
}}
|
|
162
|
+
/>
|
|
163
|
+
|
|
164
|
+
{/* Accent fill between thumbs */}
|
|
165
|
+
<div
|
|
166
|
+
style={{
|
|
167
|
+
position: "absolute",
|
|
168
|
+
left: `${wPos * 100}%`,
|
|
169
|
+
width: `${(bPos - wPos) * 100}%`,
|
|
170
|
+
top: trackTop,
|
|
171
|
+
height: TRACK_HEIGHT,
|
|
172
|
+
backgroundColor: interactiveColor,
|
|
173
|
+
}}
|
|
174
|
+
/>
|
|
175
|
+
|
|
176
|
+
{/* Whites thumb (left) */}
|
|
177
|
+
<div
|
|
178
|
+
style={{
|
|
179
|
+
position: "absolute",
|
|
180
|
+
left: `calc(${wPos * 100}% - ${THUMB_SIZE / 2}px)`,
|
|
181
|
+
top: 0,
|
|
182
|
+
width: THUMB_SIZE,
|
|
183
|
+
height: THUMB_SIZE,
|
|
184
|
+
borderRadius: THUMB_SIZE / 2,
|
|
185
|
+
backgroundColor: interactiveColor,
|
|
186
|
+
pointerEvents: "none",
|
|
187
|
+
zIndex: activeThumb === "whites" ? 2 : 1,
|
|
188
|
+
}}
|
|
189
|
+
/>
|
|
190
|
+
|
|
191
|
+
{/* Blacks thumb (right) */}
|
|
192
|
+
<div
|
|
193
|
+
style={{
|
|
194
|
+
position: "absolute",
|
|
195
|
+
left: `calc(${bPos * 100}% - ${THUMB_SIZE / 2}px)`,
|
|
196
|
+
top: 0,
|
|
197
|
+
width: THUMB_SIZE,
|
|
198
|
+
height: THUMB_SIZE,
|
|
199
|
+
borderRadius: THUMB_SIZE / 2,
|
|
200
|
+
backgroundColor: interactiveColor,
|
|
201
|
+
pointerEvents: "none",
|
|
202
|
+
zIndex: activeThumb === "blacks" ? 2 : 1,
|
|
203
|
+
}}
|
|
204
|
+
/>
|
|
205
|
+
</div>
|
|
206
|
+
</div>
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// --- Numeric Input ---
|
|
211
|
+
|
|
212
|
+
interface RangeInputProps {
|
|
213
|
+
readonly display: number; // 0–10 integer
|
|
214
|
+
readonly onCommit: (internal: number) => void;
|
|
215
|
+
readonly toInternal: (display: number) => number;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function RangeInput({ display, onCommit, toInternal }: RangeInputProps) {
|
|
219
|
+
const tokens = useTokens();
|
|
220
|
+
const [text, setText] = useState(String(display));
|
|
221
|
+
const [isEditing, setIsEditing] = useState(false);
|
|
222
|
+
|
|
223
|
+
const displayText = isEditing ? text : String(display);
|
|
224
|
+
|
|
225
|
+
const commit = () => {
|
|
226
|
+
setIsEditing(false);
|
|
227
|
+
const parsed = parseInt(text, 10);
|
|
228
|
+
if (isNaN(parsed)) {
|
|
229
|
+
setText(String(display));
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
const clamped = clamp(Math.round(parsed), 0, 10);
|
|
233
|
+
onCommit(toInternal(clamped));
|
|
234
|
+
setText(String(clamped));
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
return (
|
|
238
|
+
<input
|
|
239
|
+
type="text"
|
|
240
|
+
inputMode="numeric"
|
|
241
|
+
value={displayText}
|
|
242
|
+
onChange={(e) => {
|
|
243
|
+
setIsEditing(true);
|
|
244
|
+
setText(e.target.value);
|
|
245
|
+
}}
|
|
246
|
+
onBlur={commit}
|
|
247
|
+
onKeyDown={(e) => {
|
|
248
|
+
if (e.key === "Enter") commit();
|
|
249
|
+
}}
|
|
250
|
+
style={{
|
|
251
|
+
width: 40,
|
|
252
|
+
padding: "2px 6px",
|
|
253
|
+
border: `1px solid ${srgbToHex(tokens.border.srgb)}`,
|
|
254
|
+
borderRadius: 4,
|
|
255
|
+
backgroundColor: "transparent",
|
|
256
|
+
color: srgbToHex(tokens.textPrimary.srgb),
|
|
257
|
+
fontFamily: "inherit",
|
|
258
|
+
fontSize: 12,
|
|
259
|
+
fontWeight: 500,
|
|
260
|
+
textAlign: "center",
|
|
261
|
+
outline: "none",
|
|
262
|
+
}}
|
|
263
|
+
/>
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// --- Section ---
|
|
268
|
+
|
|
269
|
+
interface DynamicRangeSectionProps {
|
|
270
|
+
readonly state: ConfiguratorState;
|
|
271
|
+
readonly dispatch: (action: ConfiguratorAction) => void;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
export function DynamicRangeSection({
|
|
275
|
+
state,
|
|
276
|
+
dispatch,
|
|
277
|
+
}: DynamicRangeSectionProps) {
|
|
278
|
+
const tokens = useTokens();
|
|
279
|
+
const labelColor = srgbToHex(tokens.textSecondary.srgb);
|
|
280
|
+
|
|
281
|
+
const labelStyle = {
|
|
282
|
+
fontSize: 11,
|
|
283
|
+
fontWeight: 600 as const,
|
|
284
|
+
color: labelColor,
|
|
285
|
+
textTransform: "uppercase" as const,
|
|
286
|
+
letterSpacing: 0.5,
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
const wDisplay = internalToDisplay(state.dynamicRange.lightest);
|
|
290
|
+
const bDisplay = internalToDisplay(state.dynamicRange.darkest);
|
|
291
|
+
|
|
292
|
+
return (
|
|
293
|
+
<div style={{ display: "flex", flexDirection: "column", gap: 12 }}>
|
|
294
|
+
{/* Labels above slider */}
|
|
295
|
+
<div
|
|
296
|
+
style={{
|
|
297
|
+
display: "flex",
|
|
298
|
+
justifyContent: "space-between",
|
|
299
|
+
alignItems: "center",
|
|
300
|
+
}}
|
|
301
|
+
>
|
|
302
|
+
<span style={labelStyle}>Whites</span>
|
|
303
|
+
<span style={labelStyle}>Blacks</span>
|
|
304
|
+
</div>
|
|
305
|
+
|
|
306
|
+
{/* Dual range slider */}
|
|
307
|
+
<DualRangeSlider
|
|
308
|
+
whitesValue={state.dynamicRange.lightest}
|
|
309
|
+
blacksValue={state.dynamicRange.darkest}
|
|
310
|
+
onWhitesChange={(v) => dispatch({ type: "SET_LIGHTEST", value: v })}
|
|
311
|
+
onBlacksChange={(v) => dispatch({ type: "SET_DARKEST", value: v })}
|
|
312
|
+
/>
|
|
313
|
+
|
|
314
|
+
{/* Numeric inputs below slider */}
|
|
315
|
+
<div
|
|
316
|
+
style={{
|
|
317
|
+
display: "flex",
|
|
318
|
+
justifyContent: "space-between",
|
|
319
|
+
alignItems: "center",
|
|
320
|
+
}}
|
|
321
|
+
>
|
|
322
|
+
<RangeInput
|
|
323
|
+
display={wDisplay}
|
|
324
|
+
onCommit={(v) => dispatch({ type: "SET_LIGHTEST", value: v })}
|
|
325
|
+
toInternal={displayToInternal}
|
|
326
|
+
/>
|
|
327
|
+
<RangeInput
|
|
328
|
+
display={bDisplay}
|
|
329
|
+
onCommit={(v) => dispatch({ type: "SET_DARKEST", value: v })}
|
|
330
|
+
toInternal={displayToInternal}
|
|
331
|
+
/>
|
|
332
|
+
</div>
|
|
333
|
+
|
|
334
|
+
{/* Global Hue Grading — Light End */}
|
|
335
|
+
<div style={{ ...labelStyle, marginTop: 4 }}>
|
|
336
|
+
Global Hue Grading — Light
|
|
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
|
+
)}
|
|
364
|
+
</div>
|
|
365
|
+
|
|
366
|
+
{/* Global Hue Grading — Dark End */}
|
|
367
|
+
<div style={{ ...labelStyle, marginTop: 4 }}>
|
|
368
|
+
Global Hue Grading — Dark
|
|
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
|
+
)}
|
|
396
|
+
</div>
|
|
397
|
+
</div>
|
|
398
|
+
);
|
|
399
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { Slider, useTokens } from "@newtonedev/components";
|
|
2
|
+
import type { FontConfig } from "@newtonedev/components";
|
|
3
|
+
import { srgbToHex } from "newtone";
|
|
4
|
+
import type { ConfiguratorState } from "@newtonedev/configurator";
|
|
5
|
+
import type { ConfiguratorAction } from "@newtonedev/configurator";
|
|
6
|
+
import { FontPicker } from "../FontPicker";
|
|
7
|
+
|
|
8
|
+
const DEFAULT_FONT_DEFAULT: FontConfig = {
|
|
9
|
+
type: "system",
|
|
10
|
+
family: "system-ui",
|
|
11
|
+
fallback:
|
|
12
|
+
'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const DEFAULT_FONT_DISPLAY: FontConfig = {
|
|
16
|
+
type: "system",
|
|
17
|
+
family: "system-ui",
|
|
18
|
+
fallback:
|
|
19
|
+
'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const DEFAULT_FONT_MONO: FontConfig = {
|
|
23
|
+
type: "system",
|
|
24
|
+
family: "ui-monospace",
|
|
25
|
+
fallback: "SFMono-Regular, Menlo, Monaco, Consolas, monospace",
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
interface FontsSectionProps {
|
|
29
|
+
readonly state: ConfiguratorState;
|
|
30
|
+
readonly dispatch: (action: ConfiguratorAction) => void;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function FontsSection({ state, dispatch }: FontsSectionProps) {
|
|
34
|
+
const tokens = useTokens();
|
|
35
|
+
|
|
36
|
+
const baseSize = state.typography?.scale.baseSize ?? 16;
|
|
37
|
+
const ratio = state.typography?.scale.ratio ?? 1.25;
|
|
38
|
+
|
|
39
|
+
const labelColor = srgbToHex(tokens.textSecondary.srgb);
|
|
40
|
+
|
|
41
|
+
const handleFontChange = (
|
|
42
|
+
slot: "default" | "display" | "mono",
|
|
43
|
+
font: FontConfig,
|
|
44
|
+
) => {
|
|
45
|
+
const actionType = {
|
|
46
|
+
default: "SET_FONT_DEFAULT",
|
|
47
|
+
display: "SET_FONT_DISPLAY",
|
|
48
|
+
mono: "SET_FONT_MONO",
|
|
49
|
+
}[slot] as "SET_FONT_DEFAULT" | "SET_FONT_DISPLAY" | "SET_FONT_MONO";
|
|
50
|
+
dispatch({ type: actionType, font });
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<div style={{ display: "flex", flexDirection: "column", gap: 16 }}>
|
|
55
|
+
<div>
|
|
56
|
+
<div
|
|
57
|
+
style={{
|
|
58
|
+
fontSize: 11,
|
|
59
|
+
fontWeight: 600,
|
|
60
|
+
color: labelColor,
|
|
61
|
+
textTransform: "uppercase",
|
|
62
|
+
letterSpacing: 0.5,
|
|
63
|
+
marginBottom: 8,
|
|
64
|
+
}}
|
|
65
|
+
>
|
|
66
|
+
Scale
|
|
67
|
+
</div>
|
|
68
|
+
<div style={{ display: "flex", flexDirection: "column", gap: 12 }}>
|
|
69
|
+
<Slider
|
|
70
|
+
value={baseSize}
|
|
71
|
+
onValueChange={(v) =>
|
|
72
|
+
dispatch({ type: "SET_TYPOGRAPHY_BASE_SIZE", baseSize: v })
|
|
73
|
+
}
|
|
74
|
+
min={12}
|
|
75
|
+
max={24}
|
|
76
|
+
step={1}
|
|
77
|
+
label="Base Size"
|
|
78
|
+
showValue
|
|
79
|
+
/>
|
|
80
|
+
<Slider
|
|
81
|
+
value={Math.round(ratio * 100)}
|
|
82
|
+
onValueChange={(v) =>
|
|
83
|
+
dispatch({ type: "SET_TYPOGRAPHY_RATIO", ratio: v / 100 })
|
|
84
|
+
}
|
|
85
|
+
min={110}
|
|
86
|
+
max={150}
|
|
87
|
+
step={5}
|
|
88
|
+
label="Scale Ratio"
|
|
89
|
+
showValue
|
|
90
|
+
/>
|
|
91
|
+
</div>
|
|
92
|
+
</div>
|
|
93
|
+
|
|
94
|
+
<div>
|
|
95
|
+
<div
|
|
96
|
+
style={{
|
|
97
|
+
fontSize: 11,
|
|
98
|
+
fontWeight: 600,
|
|
99
|
+
color: labelColor,
|
|
100
|
+
textTransform: "uppercase",
|
|
101
|
+
letterSpacing: 0.5,
|
|
102
|
+
marginBottom: 8,
|
|
103
|
+
}}
|
|
104
|
+
>
|
|
105
|
+
Fonts
|
|
106
|
+
</div>
|
|
107
|
+
<div style={{ display: "flex", flexDirection: "column", gap: 6 }}>
|
|
108
|
+
<FontPicker
|
|
109
|
+
label="Default"
|
|
110
|
+
slot="default"
|
|
111
|
+
currentFont={state.typography?.fonts.default ?? DEFAULT_FONT_DEFAULT}
|
|
112
|
+
onSelect={(font) => handleFontChange("default", font)}
|
|
113
|
+
/>
|
|
114
|
+
<FontPicker
|
|
115
|
+
label="Display"
|
|
116
|
+
slot="display"
|
|
117
|
+
currentFont={
|
|
118
|
+
state.typography?.fonts.display ?? DEFAULT_FONT_DISPLAY
|
|
119
|
+
}
|
|
120
|
+
onSelect={(font) => handleFontChange("display", font)}
|
|
121
|
+
/>
|
|
122
|
+
<FontPicker
|
|
123
|
+
label="Mono"
|
|
124
|
+
slot="mono"
|
|
125
|
+
currentFont={state.typography?.fonts.mono ?? DEFAULT_FONT_MONO}
|
|
126
|
+
onSelect={(font) => handleFontChange("mono", font)}
|
|
127
|
+
/>
|
|
128
|
+
</div>
|
|
129
|
+
</div>
|
|
130
|
+
</div>
|
|
131
|
+
);
|
|
132
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { Select } from "@newtonedev/components";
|
|
2
|
+
import type { ConfiguratorState } from "@newtonedev/configurator";
|
|
3
|
+
import type { ConfiguratorAction } from "@newtonedev/configurator";
|
|
4
|
+
|
|
5
|
+
const ICON_VARIANT_OPTIONS = [
|
|
6
|
+
{ label: "Outlined", value: "outlined" },
|
|
7
|
+
{ label: "Rounded", value: "rounded" },
|
|
8
|
+
{ label: "Sharp", value: "sharp" },
|
|
9
|
+
];
|
|
10
|
+
|
|
11
|
+
const ICON_WEIGHT_OPTIONS = [
|
|
12
|
+
{ label: "100", value: "100" },
|
|
13
|
+
{ label: "200", value: "200" },
|
|
14
|
+
{ label: "300", value: "300" },
|
|
15
|
+
{ label: "400", value: "400" },
|
|
16
|
+
{ label: "500", value: "500" },
|
|
17
|
+
{ label: "600", value: "600" },
|
|
18
|
+
{ label: "700", value: "700" },
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
interface IconsSectionProps {
|
|
22
|
+
readonly state: ConfiguratorState;
|
|
23
|
+
readonly dispatch: (action: ConfiguratorAction) => void;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function IconsSection({ state, dispatch }: IconsSectionProps) {
|
|
27
|
+
const variant = state.icons?.variant ?? "rounded";
|
|
28
|
+
const weight = state.icons?.weight ?? 400;
|
|
29
|
+
return (
|
|
30
|
+
<div style={{ display: "flex", gap: 12 }}>
|
|
31
|
+
<div style={{ flex: 1 }}>
|
|
32
|
+
<Select
|
|
33
|
+
options={ICON_VARIANT_OPTIONS}
|
|
34
|
+
value={variant}
|
|
35
|
+
onValueChange={(v) =>
|
|
36
|
+
dispatch({
|
|
37
|
+
type: "SET_ICON_VARIANT",
|
|
38
|
+
variant: v as "outlined" | "rounded" | "sharp",
|
|
39
|
+
})
|
|
40
|
+
}
|
|
41
|
+
label="Variant"
|
|
42
|
+
/>
|
|
43
|
+
</div>
|
|
44
|
+
<div style={{ flex: 1 }}>
|
|
45
|
+
<Select
|
|
46
|
+
options={ICON_WEIGHT_OPTIONS}
|
|
47
|
+
value={weight.toString()}
|
|
48
|
+
onValueChange={(v) =>
|
|
49
|
+
dispatch({
|
|
50
|
+
type: "SET_ICON_WEIGHT",
|
|
51
|
+
weight: parseInt(v) as
|
|
52
|
+
| 100
|
|
53
|
+
| 200
|
|
54
|
+
| 300
|
|
55
|
+
| 400
|
|
56
|
+
| 500
|
|
57
|
+
| 600
|
|
58
|
+
| 700,
|
|
59
|
+
})
|
|
60
|
+
}
|
|
61
|
+
label="Weight"
|
|
62
|
+
/>
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { Slider, useTokens } from "@newtonedev/components";
|
|
2
|
+
import { srgbToHex } from "newtone";
|
|
3
|
+
import type { ConfiguratorState } from "@newtonedev/configurator";
|
|
4
|
+
import type { ConfiguratorAction } from "@newtonedev/configurator";
|
|
5
|
+
|
|
6
|
+
interface OthersSectionProps {
|
|
7
|
+
readonly state: ConfiguratorState;
|
|
8
|
+
readonly dispatch: (action: ConfiguratorAction) => void;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function OthersSection({ state, dispatch }: OthersSectionProps) {
|
|
12
|
+
const tokens = useTokens();
|
|
13
|
+
|
|
14
|
+
const density = state.spacing?.density ?? 0.5;
|
|
15
|
+
const intensity = state.roundness?.intensity ?? 0.5;
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<div style={{ display: "flex", flexDirection: "column", gap: 16 }}>
|
|
19
|
+
<div>
|
|
20
|
+
<div
|
|
21
|
+
style={{
|
|
22
|
+
fontSize: 11,
|
|
23
|
+
fontWeight: 600,
|
|
24
|
+
color: srgbToHex(tokens.textSecondary.srgb),
|
|
25
|
+
textTransform: "uppercase",
|
|
26
|
+
letterSpacing: 0.5,
|
|
27
|
+
marginBottom: 8,
|
|
28
|
+
}}
|
|
29
|
+
>
|
|
30
|
+
Spacing
|
|
31
|
+
</div>
|
|
32
|
+
<Slider
|
|
33
|
+
value={Math.round(density * 100)}
|
|
34
|
+
onValueChange={(v) =>
|
|
35
|
+
dispatch({ type: "SET_SPACING_DENSITY", density: v / 100 })
|
|
36
|
+
}
|
|
37
|
+
min={0}
|
|
38
|
+
max={100}
|
|
39
|
+
label="Density"
|
|
40
|
+
showValue
|
|
41
|
+
/>
|
|
42
|
+
</div>
|
|
43
|
+
|
|
44
|
+
<div>
|
|
45
|
+
<div
|
|
46
|
+
style={{
|
|
47
|
+
fontSize: 11,
|
|
48
|
+
fontWeight: 600,
|
|
49
|
+
color: srgbToHex(tokens.textSecondary.srgb),
|
|
50
|
+
textTransform: "uppercase",
|
|
51
|
+
letterSpacing: 0.5,
|
|
52
|
+
marginBottom: 8,
|
|
53
|
+
}}
|
|
54
|
+
>
|
|
55
|
+
Roundness
|
|
56
|
+
</div>
|
|
57
|
+
<Slider
|
|
58
|
+
value={Math.round(intensity * 100)}
|
|
59
|
+
onValueChange={(v) =>
|
|
60
|
+
dispatch({ type: "SET_ROUNDNESS_INTENSITY", intensity: v / 100 })
|
|
61
|
+
}
|
|
62
|
+
min={0}
|
|
63
|
+
max={100}
|
|
64
|
+
label="Intensity"
|
|
65
|
+
showValue
|
|
66
|
+
/>
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
69
|
+
);
|
|
70
|
+
}
|