@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.
Files changed (86) hide show
  1. package/dist/Editor.d.ts +3 -0
  2. package/dist/Editor.d.ts.map +1 -0
  3. package/dist/components/CodeBlock.d.ts +7 -0
  4. package/dist/components/CodeBlock.d.ts.map +1 -0
  5. package/dist/components/EditorHeader.d.ts +16 -0
  6. package/dist/components/EditorHeader.d.ts.map +1 -0
  7. package/dist/components/EditorShell.d.ts +10 -0
  8. package/dist/components/EditorShell.d.ts.map +1 -0
  9. package/dist/components/FontPicker.d.ts +11 -0
  10. package/dist/components/FontPicker.d.ts.map +1 -0
  11. package/dist/components/PresetSelector.d.ts +14 -0
  12. package/dist/components/PresetSelector.d.ts.map +1 -0
  13. package/dist/components/PreviewWindow.d.ts +11 -0
  14. package/dist/components/PreviewWindow.d.ts.map +1 -0
  15. package/dist/components/RightSidebar.d.ts +12 -0
  16. package/dist/components/RightSidebar.d.ts.map +1 -0
  17. package/dist/components/Sidebar.d.ts +25 -0
  18. package/dist/components/Sidebar.d.ts.map +1 -0
  19. package/dist/components/TableOfContents.d.ts +9 -0
  20. package/dist/components/TableOfContents.d.ts.map +1 -0
  21. package/dist/components/ThemeBar.d.ts +8 -0
  22. package/dist/components/ThemeBar.d.ts.map +1 -0
  23. package/dist/components/sections/ColorsSection.d.ts +14 -0
  24. package/dist/components/sections/ColorsSection.d.ts.map +1 -0
  25. package/dist/components/sections/DynamicRangeSection.d.ts +9 -0
  26. package/dist/components/sections/DynamicRangeSection.d.ts.map +1 -0
  27. package/dist/components/sections/FontsSection.d.ts +9 -0
  28. package/dist/components/sections/FontsSection.d.ts.map +1 -0
  29. package/dist/components/sections/IconsSection.d.ts +9 -0
  30. package/dist/components/sections/IconsSection.d.ts.map +1 -0
  31. package/dist/components/sections/OthersSection.d.ts +9 -0
  32. package/dist/components/sections/OthersSection.d.ts.map +1 -0
  33. package/dist/components/sections/index.d.ts +6 -0
  34. package/dist/components/sections/index.d.ts.map +1 -0
  35. package/dist/hooks/useEditorState.d.ts +53 -0
  36. package/dist/hooks/useEditorState.d.ts.map +1 -0
  37. package/dist/hooks/useHover.d.ts +8 -0
  38. package/dist/hooks/useHover.d.ts.map +1 -0
  39. package/dist/hooks/usePresets.d.ts +33 -0
  40. package/dist/hooks/usePresets.d.ts.map +1 -0
  41. package/dist/index.cjs +3846 -0
  42. package/dist/index.cjs.map +1 -0
  43. package/dist/index.d.ts +22 -0
  44. package/dist/index.d.ts.map +1 -0
  45. package/dist/index.js +3819 -0
  46. package/dist/index.js.map +1 -0
  47. package/dist/preview/CategoryView.d.ts +7 -0
  48. package/dist/preview/CategoryView.d.ts.map +1 -0
  49. package/dist/preview/ComponentDetailView.d.ts +9 -0
  50. package/dist/preview/ComponentDetailView.d.ts.map +1 -0
  51. package/dist/preview/ComponentRenderer.d.ts +7 -0
  52. package/dist/preview/ComponentRenderer.d.ts.map +1 -0
  53. package/dist/preview/OverviewView.d.ts +7 -0
  54. package/dist/preview/OverviewView.d.ts.map +1 -0
  55. package/dist/types.d.ts +69 -0
  56. package/dist/types.d.ts.map +1 -0
  57. package/dist/utils/presets.d.ts +5 -0
  58. package/dist/utils/presets.d.ts.map +1 -0
  59. package/package.json +51 -0
  60. package/src/Editor.tsx +128 -0
  61. package/src/components/CodeBlock.tsx +58 -0
  62. package/src/components/EditorHeader.tsx +86 -0
  63. package/src/components/EditorShell.tsx +67 -0
  64. package/src/components/FontPicker.tsx +351 -0
  65. package/src/components/PresetSelector.tsx +455 -0
  66. package/src/components/PreviewWindow.tsx +69 -0
  67. package/src/components/RightSidebar.tsx +374 -0
  68. package/src/components/Sidebar.tsx +332 -0
  69. package/src/components/TableOfContents.tsx +152 -0
  70. package/src/components/ThemeBar.tsx +76 -0
  71. package/src/components/sections/ColorsSection.tsx +485 -0
  72. package/src/components/sections/DynamicRangeSection.tsx +399 -0
  73. package/src/components/sections/FontsSection.tsx +132 -0
  74. package/src/components/sections/IconsSection.tsx +66 -0
  75. package/src/components/sections/OthersSection.tsx +70 -0
  76. package/src/components/sections/index.ts +5 -0
  77. package/src/hooks/useEditorState.ts +381 -0
  78. package/src/hooks/useHover.ts +8 -0
  79. package/src/hooks/usePresets.ts +254 -0
  80. package/src/index.ts +52 -0
  81. package/src/preview/CategoryView.tsx +134 -0
  82. package/src/preview/ComponentDetailView.tsx +126 -0
  83. package/src/preview/ComponentRenderer.tsx +107 -0
  84. package/src/preview/OverviewView.tsx +177 -0
  85. package/src/types.ts +77 -0
  86. 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
+ }
@@ -0,0 +1,5 @@
1
+ export { ColorsSection } from "./ColorsSection";
2
+ export { DynamicRangeSection } from "./DynamicRangeSection";
3
+ export { IconsSection } from "./IconsSection";
4
+ export { FontsSection } from "./FontsSection";
5
+ export { OthersSection } from "./OthersSection";