@newtonedev/editor 0.1.4 → 0.1.6
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/PresetSelector.d.ts.map +1 -1
- package/dist/components/PreviewWindow.d.ts +3 -2
- package/dist/components/PreviewWindow.d.ts.map +1 -1
- package/dist/components/RightSidebar.d.ts +4 -1
- package/dist/components/RightSidebar.d.ts.map +1 -1
- package/dist/components/Sidebar.d.ts.map +1 -1
- package/dist/components/sections/DynamicRangeSection.d.ts.map +1 -1
- package/dist/hooks/useEditorState.d.ts +1 -3
- package/dist/hooks/useEditorState.d.ts.map +1 -1
- package/dist/index.cjs +686 -346
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +689 -348
- package/dist/index.js.map +1 -1
- package/dist/preview/ComponentDetailView.d.ts +3 -2
- package/dist/preview/ComponentDetailView.d.ts.map +1 -1
- package/dist/preview/ComponentRenderer.d.ts.map +1 -1
- package/dist/preview/IconBrowserView.d.ts +7 -0
- package/dist/preview/IconBrowserView.d.ts.map +1 -0
- package/dist/types.d.ts +0 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/Editor.tsx +19 -9
- package/src/components/CodeBlock.tsx +42 -14
- package/src/components/EditorHeader.tsx +3 -3
- package/src/components/EditorShell.tsx +2 -2
- package/src/components/FontPicker.tsx +1 -1
- package/src/components/PresetSelector.tsx +11 -36
- package/src/components/PreviewWindow.tsx +6 -3
- package/src/components/RightSidebar.tsx +105 -42
- package/src/components/Sidebar.tsx +12 -92
- package/src/components/TableOfContents.tsx +1 -1
- package/src/components/sections/ColorsSection.tsx +2 -2
- package/src/components/sections/DynamicRangeSection.tsx +226 -3
- package/src/hooks/useEditorState.ts +14 -19
- package/src/index.ts +0 -2
- package/src/preview/CategoryView.tsx +1 -1
- package/src/preview/ComponentDetailView.tsx +47 -8
- package/src/preview/ComponentRenderer.tsx +51 -0
- package/src/preview/IconBrowserView.tsx +187 -0
- package/src/preview/OverviewView.tsx +1 -1
- package/src/types.ts +0 -2
- package/dist/components/ThemeBar.d.ts +0 -8
- package/dist/components/ThemeBar.d.ts.map +0 -1
- package/src/components/ThemeBar.tsx +0 -76
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useState } from "react";
|
|
2
|
-
import { useTokens } from "@newtonedev/components";
|
|
2
|
+
import { useTokens, Icon } from "@newtonedev/components";
|
|
3
3
|
import type { ColorMode } from "@newtonedev/components";
|
|
4
4
|
import { srgbToHex } from "newtone";
|
|
5
5
|
import type { ColorResult } from "newtone";
|
|
@@ -18,87 +18,13 @@ import type { Preset } from "../types";
|
|
|
18
18
|
const SIDEBAR_WIDTH = 360;
|
|
19
19
|
|
|
20
20
|
const ACCORDION_SECTIONS = [
|
|
21
|
-
{ id: "dynamic-range", label: "Dynamic Range" },
|
|
22
|
-
{ id: "colors", label: "Colors" },
|
|
23
|
-
{ id: "fonts", label: "Fonts" },
|
|
24
|
-
{ id: "icons", label: "Icons" },
|
|
25
|
-
{ id: "others", label: "Others" },
|
|
21
|
+
{ id: "dynamic-range", label: "Dynamic Range", icon: "contrast" },
|
|
22
|
+
{ id: "colors", label: "Colors", icon: "palette" },
|
|
23
|
+
{ id: "fonts", label: "Fonts", icon: "text_fields" },
|
|
24
|
+
{ id: "icons", label: "Icons", icon: "grid_view" },
|
|
25
|
+
{ id: "others", label: "Others", icon: "tune" },
|
|
26
26
|
] as const;
|
|
27
27
|
|
|
28
|
-
function SectionIcon({ id }: { readonly id: string }) {
|
|
29
|
-
const props = {
|
|
30
|
-
width: 16,
|
|
31
|
-
height: 16,
|
|
32
|
-
viewBox: "0 0 24 24",
|
|
33
|
-
fill: "none",
|
|
34
|
-
stroke: "currentColor",
|
|
35
|
-
strokeWidth: 2,
|
|
36
|
-
strokeLinecap: "round" as const,
|
|
37
|
-
strokeLinejoin: "round" as const,
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
switch (id) {
|
|
41
|
-
case "dynamic-range":
|
|
42
|
-
// Sun/contrast icon
|
|
43
|
-
return (
|
|
44
|
-
<svg {...props}>
|
|
45
|
-
<circle cx="12" cy="12" r="5" />
|
|
46
|
-
<line x1="12" y1="1" x2="12" y2="3" />
|
|
47
|
-
<line x1="12" y1="21" x2="12" y2="23" />
|
|
48
|
-
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64" />
|
|
49
|
-
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78" />
|
|
50
|
-
<line x1="1" y1="12" x2="3" y2="12" />
|
|
51
|
-
<line x1="21" y1="12" x2="23" y2="12" />
|
|
52
|
-
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36" />
|
|
53
|
-
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22" />
|
|
54
|
-
</svg>
|
|
55
|
-
);
|
|
56
|
-
case "colors":
|
|
57
|
-
// Palette/droplet icon
|
|
58
|
-
return (
|
|
59
|
-
<svg {...props}>
|
|
60
|
-
<path d="M12 2.69l5.66 5.66a8 8 0 1 1-11.31 0z" />
|
|
61
|
-
</svg>
|
|
62
|
-
);
|
|
63
|
-
case "fonts":
|
|
64
|
-
// Type/text icon
|
|
65
|
-
return (
|
|
66
|
-
<svg {...props}>
|
|
67
|
-
<polyline points="4 7 4 4 20 4 20 7" />
|
|
68
|
-
<line x1="9" y1="20" x2="15" y2="20" />
|
|
69
|
-
<line x1="12" y1="4" x2="12" y2="20" />
|
|
70
|
-
</svg>
|
|
71
|
-
);
|
|
72
|
-
case "icons":
|
|
73
|
-
// Grid icon
|
|
74
|
-
return (
|
|
75
|
-
<svg {...props}>
|
|
76
|
-
<rect x="3" y="3" width="7" height="7" />
|
|
77
|
-
<rect x="14" y="3" width="7" height="7" />
|
|
78
|
-
<rect x="3" y="14" width="7" height="7" />
|
|
79
|
-
<rect x="14" y="14" width="7" height="7" />
|
|
80
|
-
</svg>
|
|
81
|
-
);
|
|
82
|
-
case "others":
|
|
83
|
-
// Sliders icon
|
|
84
|
-
return (
|
|
85
|
-
<svg {...props}>
|
|
86
|
-
<line x1="4" y1="21" x2="4" y2="14" />
|
|
87
|
-
<line x1="4" y1="10" x2="4" y2="3" />
|
|
88
|
-
<line x1="12" y1="21" x2="12" y2="12" />
|
|
89
|
-
<line x1="12" y1="8" x2="12" y2="3" />
|
|
90
|
-
<line x1="20" y1="21" x2="20" y2="16" />
|
|
91
|
-
<line x1="20" y1="12" x2="20" y2="3" />
|
|
92
|
-
<line x1="1" y1="14" x2="7" y2="14" />
|
|
93
|
-
<line x1="9" y1="8" x2="15" y2="8" />
|
|
94
|
-
<line x1="17" y1="16" x2="23" y2="16" />
|
|
95
|
-
</svg>
|
|
96
|
-
);
|
|
97
|
-
default:
|
|
98
|
-
return null;
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
28
|
interface SidebarProps {
|
|
103
29
|
readonly state: ConfiguratorState;
|
|
104
30
|
readonly dispatch: (action: ConfiguratorAction) => void;
|
|
@@ -262,23 +188,17 @@ export function Sidebar({
|
|
|
262
188
|
}}
|
|
263
189
|
>
|
|
264
190
|
<span style={{ display: "flex", alignItems: "center", gap: 8 }}>
|
|
265
|
-
<
|
|
191
|
+
<Icon name={section.icon} size={16} />
|
|
266
192
|
{section.label}
|
|
267
193
|
</span>
|
|
268
|
-
<
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
viewBox="0 0 24 24"
|
|
272
|
-
fill="none"
|
|
273
|
-
stroke="currentColor"
|
|
274
|
-
strokeWidth={2}
|
|
194
|
+
<Icon
|
|
195
|
+
name="expand_more"
|
|
196
|
+
size={16}
|
|
275
197
|
style={{
|
|
276
198
|
transform: isOpen ? "rotate(180deg)" : "none",
|
|
277
199
|
transition: "transform 150ms ease",
|
|
278
|
-
}}
|
|
279
|
-
|
|
280
|
-
<polyline points="6 9 12 15 18 9" />
|
|
281
|
-
</svg>
|
|
200
|
+
} as any}
|
|
201
|
+
/>
|
|
282
202
|
</button>
|
|
283
203
|
{isOpen && (
|
|
284
204
|
<div
|
|
@@ -24,7 +24,7 @@ export function TableOfContents({
|
|
|
24
24
|
const [hoveredId, setHoveredId] = useState<string | null>(null);
|
|
25
25
|
|
|
26
26
|
const borderColor = srgbToHex(tokens.border.srgb);
|
|
27
|
-
const activeColor = srgbToHex(tokens.
|
|
27
|
+
const activeColor = srgbToHex(tokens.accent.fill.srgb);
|
|
28
28
|
const textPrimary = srgbToHex(tokens.textPrimary.srgb);
|
|
29
29
|
const textSecondary = srgbToHex(tokens.textSecondary.srgb);
|
|
30
30
|
const hoverBg = `${borderColor}20`;
|
|
@@ -61,7 +61,7 @@ export function ColorsSection({
|
|
|
61
61
|
const hueRange = SEMANTIC_HUE_RANGES[activePaletteIndex];
|
|
62
62
|
const isNeutral = activePaletteIndex === 0;
|
|
63
63
|
|
|
64
|
-
const activeColor = srgbToHex(tokens.
|
|
64
|
+
const activeColor = srgbToHex(tokens.accent.fill.srgb);
|
|
65
65
|
const borderColor = srgbToHex(tokens.border.srgb);
|
|
66
66
|
|
|
67
67
|
// Resolve effective key color for current mode
|
|
@@ -350,7 +350,7 @@ export function ColorsSection({
|
|
|
350
350
|
style={{
|
|
351
351
|
fontSize: 12,
|
|
352
352
|
fontWeight: 500,
|
|
353
|
-
color: srgbToHex(tokens.error.srgb),
|
|
353
|
+
color: srgbToHex(tokens.error.fill.srgb),
|
|
354
354
|
}}
|
|
355
355
|
>
|
|
356
356
|
{hexError}
|
|
@@ -1,9 +1,20 @@
|
|
|
1
|
-
import { useState, useRef, useCallback } from "react";
|
|
1
|
+
import { useState, useRef, useCallback, useMemo, useEffect } from "react";
|
|
2
2
|
import { HueSlider, Select, useTokens } from "@newtonedev/components";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
srgbToHex,
|
|
5
|
+
resolveLightness,
|
|
6
|
+
findMaxChromaInGamut,
|
|
7
|
+
oklchToSrgb,
|
|
8
|
+
clampSrgb,
|
|
9
|
+
HUE_GRADING_STRENGTH_LOW,
|
|
10
|
+
HUE_GRADING_STRENGTH_MEDIUM,
|
|
11
|
+
HUE_GRADING_STRENGTH_HARD,
|
|
12
|
+
HUE_GRADING_EASING_POWER,
|
|
13
|
+
} from "newtone";
|
|
4
14
|
import type { HueGradingStrength } from "newtone";
|
|
5
15
|
import type { ConfiguratorState } from "@newtonedev/configurator";
|
|
6
16
|
import type { ConfiguratorAction } from "@newtonedev/configurator";
|
|
17
|
+
import { traditionalHueToOklch } from "@newtonedev/configurator";
|
|
7
18
|
|
|
8
19
|
const STRENGTH_OPTIONS = [
|
|
9
20
|
{ label: "None", value: "none" },
|
|
@@ -76,7 +87,7 @@ function DualRangeSlider({
|
|
|
76
87
|
"whites" | "blacks" | null
|
|
77
88
|
>(null);
|
|
78
89
|
|
|
79
|
-
const interactiveColor = srgbToHex(tokens.
|
|
90
|
+
const interactiveColor = srgbToHex(tokens.accent.fill.srgb);
|
|
80
91
|
const borderColor = srgbToHex(tokens.border.srgb);
|
|
81
92
|
|
|
82
93
|
const wDisplay = internalToDisplay(whitesValue);
|
|
@@ -264,6 +275,215 @@ function RangeInput({ display, onCommit, toInternal }: RangeInputProps) {
|
|
|
264
275
|
);
|
|
265
276
|
}
|
|
266
277
|
|
|
278
|
+
// --- Dynamic Range Graph ---
|
|
279
|
+
|
|
280
|
+
const GRAPH_HEIGHT = 80;
|
|
281
|
+
const GRAPH_COLS = 256;
|
|
282
|
+
const GRAPH_ROWS = 64;
|
|
283
|
+
|
|
284
|
+
function strengthToFactor(strength: HueGradingStrength): number {
|
|
285
|
+
switch (strength) {
|
|
286
|
+
case "none":
|
|
287
|
+
return 0;
|
|
288
|
+
case "low":
|
|
289
|
+
return HUE_GRADING_STRENGTH_LOW;
|
|
290
|
+
case "medium":
|
|
291
|
+
return HUE_GRADING_STRENGTH_MEDIUM;
|
|
292
|
+
case "hard":
|
|
293
|
+
return HUE_GRADING_STRENGTH_HARD;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function blendHues(
|
|
298
|
+
lightHue: number,
|
|
299
|
+
darkHue: number,
|
|
300
|
+
wLight: number,
|
|
301
|
+
wDark: number,
|
|
302
|
+
): number {
|
|
303
|
+
const totalW = wLight + wDark;
|
|
304
|
+
if (totalW === 0) return 0;
|
|
305
|
+
const delta = (((darkHue - lightHue + 180) % 360) + 360) % 360 - 180;
|
|
306
|
+
const t = wDark / totalW;
|
|
307
|
+
const result = lightHue + delta * t;
|
|
308
|
+
return ((result % 360) + 360) % 360;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
interface GraphData {
|
|
312
|
+
readonly buffer: Uint8ClampedArray;
|
|
313
|
+
readonly curvePoints: readonly { readonly x: number; readonly y: number }[];
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function computeGraphData(state: ConfiguratorState): GraphData {
|
|
317
|
+
const { dynamicRange, globalHueGrading } = state;
|
|
318
|
+
|
|
319
|
+
const lightActive = globalHueGrading.light.strength !== "none";
|
|
320
|
+
const darkActive = globalHueGrading.dark.strength !== "none";
|
|
321
|
+
const lightOklchHue = traditionalHueToOklch(globalHueGrading.light.hue);
|
|
322
|
+
const darkOklchHue = traditionalHueToOklch(globalHueGrading.dark.hue);
|
|
323
|
+
const lightFactor = strengthToFactor(globalHueGrading.light.strength);
|
|
324
|
+
const darkFactor = strengthToFactor(globalHueGrading.dark.strength);
|
|
325
|
+
|
|
326
|
+
const buffer = new Uint8ClampedArray(GRAPH_COLS * GRAPH_ROWS * 4);
|
|
327
|
+
|
|
328
|
+
for (let col = 0; col < GRAPH_COLS; col++) {
|
|
329
|
+
const nv = 1 - col / (GRAPH_COLS - 1);
|
|
330
|
+
const L = resolveLightness(dynamicRange, nv);
|
|
331
|
+
|
|
332
|
+
// Easing weights for hue blend at top row (assumes hard strength)
|
|
333
|
+
const wLight = lightActive ? Math.pow(nv, HUE_GRADING_EASING_POWER) : 0;
|
|
334
|
+
const wDark = darkActive
|
|
335
|
+
? Math.pow(1 - nv, HUE_GRADING_EASING_POWER)
|
|
336
|
+
: 0;
|
|
337
|
+
const totalW = wLight + wDark;
|
|
338
|
+
|
|
339
|
+
let topHue: number;
|
|
340
|
+
let topChroma: number;
|
|
341
|
+
|
|
342
|
+
if (totalW === 0) {
|
|
343
|
+
topHue = 0;
|
|
344
|
+
topChroma = 0;
|
|
345
|
+
} else {
|
|
346
|
+
if (!lightActive) {
|
|
347
|
+
topHue = darkOklchHue;
|
|
348
|
+
} else if (!darkActive) {
|
|
349
|
+
topHue = lightOklchHue;
|
|
350
|
+
} else {
|
|
351
|
+
topHue = blendHues(lightOklchHue, darkOklchHue, wLight, wDark);
|
|
352
|
+
}
|
|
353
|
+
topChroma =
|
|
354
|
+
findMaxChromaInGamut(L, topHue) * Math.min(totalW, 1);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
for (let row = 0; row < GRAPH_ROWS; row++) {
|
|
358
|
+
const gradingIntensity = row / (GRAPH_ROWS - 1);
|
|
359
|
+
const C = topChroma * gradingIntensity;
|
|
360
|
+
const srgb = clampSrgb(oklchToSrgb({ L, C, h: topHue }));
|
|
361
|
+
|
|
362
|
+
// Canvas Y=0 is top; row=0 is bottom of our graph
|
|
363
|
+
const canvasY = GRAPH_ROWS - 1 - row;
|
|
364
|
+
const idx = (canvasY * GRAPH_COLS + col) * 4;
|
|
365
|
+
buffer[idx] = Math.round(srgb.r * 255);
|
|
366
|
+
buffer[idx + 1] = Math.round(srgb.g * 255);
|
|
367
|
+
buffer[idx + 2] = Math.round(srgb.b * 255);
|
|
368
|
+
buffer[idx + 3] = 255;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// 26-step curve points
|
|
373
|
+
const curvePoints: { x: number; y: number }[] = [];
|
|
374
|
+
for (let i = 0; i < 26; i++) {
|
|
375
|
+
const nv = 1 - i / 25;
|
|
376
|
+
const x = (i / 25) * (GRAPH_COLS - 1);
|
|
377
|
+
|
|
378
|
+
const lightContrib =
|
|
379
|
+
Math.pow(nv, HUE_GRADING_EASING_POWER) *
|
|
380
|
+
(lightFactor / HUE_GRADING_STRENGTH_HARD);
|
|
381
|
+
const darkContrib =
|
|
382
|
+
Math.pow(1 - nv, HUE_GRADING_EASING_POWER) *
|
|
383
|
+
(darkFactor / HUE_GRADING_STRENGTH_HARD);
|
|
384
|
+
const y = clamp(lightContrib + darkContrib, 0, 1);
|
|
385
|
+
|
|
386
|
+
curvePoints.push({ x, y });
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
return { buffer, curvePoints };
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
interface DynamicRangeGraphProps {
|
|
393
|
+
readonly state: ConfiguratorState;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
function DynamicRangeGraph({ state }: DynamicRangeGraphProps) {
|
|
397
|
+
const tokens = useTokens();
|
|
398
|
+
const canvasRef = useRef<HTMLCanvasElement>(null);
|
|
399
|
+
|
|
400
|
+
const graphData = useMemo(
|
|
401
|
+
() => computeGraphData(state),
|
|
402
|
+
[
|
|
403
|
+
state.dynamicRange.lightest,
|
|
404
|
+
state.dynamicRange.darkest,
|
|
405
|
+
state.globalHueGrading.light.strength,
|
|
406
|
+
state.globalHueGrading.light.hue,
|
|
407
|
+
state.globalHueGrading.dark.strength,
|
|
408
|
+
state.globalHueGrading.dark.hue,
|
|
409
|
+
],
|
|
410
|
+
);
|
|
411
|
+
|
|
412
|
+
useEffect(() => {
|
|
413
|
+
const canvas = canvasRef.current;
|
|
414
|
+
if (!canvas) return;
|
|
415
|
+
|
|
416
|
+
canvas.width = GRAPH_COLS;
|
|
417
|
+
canvas.height = GRAPH_ROWS;
|
|
418
|
+
|
|
419
|
+
const ctx = canvas.getContext("2d");
|
|
420
|
+
if (!ctx) return;
|
|
421
|
+
|
|
422
|
+
// Draw gradient from pre-computed buffer
|
|
423
|
+
const imageData = ctx.createImageData(GRAPH_COLS, GRAPH_ROWS);
|
|
424
|
+
imageData.data.set(graphData.buffer);
|
|
425
|
+
ctx.putImageData(imageData, 0, 0);
|
|
426
|
+
|
|
427
|
+
// Draw 26-step curve overlay
|
|
428
|
+
const curveColor = srgbToHex(tokens.accent.fill.srgb);
|
|
429
|
+
const { curvePoints } = graphData;
|
|
430
|
+
|
|
431
|
+
if (curvePoints.length < 2) return;
|
|
432
|
+
|
|
433
|
+
const mapped = curvePoints.map((p) => ({
|
|
434
|
+
cx: p.x,
|
|
435
|
+
cy: (1 - p.y) * (GRAPH_ROWS - 1),
|
|
436
|
+
}));
|
|
437
|
+
|
|
438
|
+
// Smooth Catmull-Rom spline
|
|
439
|
+
ctx.beginPath();
|
|
440
|
+
ctx.strokeStyle = curveColor;
|
|
441
|
+
ctx.lineWidth = 1.5;
|
|
442
|
+
ctx.lineJoin = "round";
|
|
443
|
+
ctx.lineCap = "round";
|
|
444
|
+
|
|
445
|
+
ctx.moveTo(mapped[0].cx, mapped[0].cy);
|
|
446
|
+
for (let i = 0; i < mapped.length - 1; i++) {
|
|
447
|
+
const p0 = mapped[Math.max(0, i - 1)];
|
|
448
|
+
const p1 = mapped[i];
|
|
449
|
+
const p2 = mapped[i + 1];
|
|
450
|
+
const p3 = mapped[Math.min(mapped.length - 1, i + 2)];
|
|
451
|
+
|
|
452
|
+
const cp1x = p1.cx + (p2.cx - p0.cx) / 6;
|
|
453
|
+
const cp1y = p1.cy + (p2.cy - p0.cy) / 6;
|
|
454
|
+
const cp2x = p2.cx - (p3.cx - p1.cx) / 6;
|
|
455
|
+
const cp2y = p2.cy - (p3.cy - p1.cy) / 6;
|
|
456
|
+
|
|
457
|
+
ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, p2.cx, p2.cy);
|
|
458
|
+
}
|
|
459
|
+
ctx.stroke();
|
|
460
|
+
|
|
461
|
+
// Draw dots at each of the 26 steps
|
|
462
|
+
ctx.fillStyle = curveColor;
|
|
463
|
+
for (const p of mapped) {
|
|
464
|
+
ctx.beginPath();
|
|
465
|
+
ctx.arc(p.cx, p.cy, 2, 0, Math.PI * 2);
|
|
466
|
+
ctx.fill();
|
|
467
|
+
}
|
|
468
|
+
}, [graphData, tokens]);
|
|
469
|
+
|
|
470
|
+
const borderColor = srgbToHex(tokens.border.srgb);
|
|
471
|
+
|
|
472
|
+
return (
|
|
473
|
+
<canvas
|
|
474
|
+
ref={canvasRef}
|
|
475
|
+
style={{
|
|
476
|
+
width: "100%",
|
|
477
|
+
height: GRAPH_HEIGHT,
|
|
478
|
+
borderRadius: 6,
|
|
479
|
+
border: `1px solid ${borderColor}`,
|
|
480
|
+
display: "block",
|
|
481
|
+
overflow: "hidden",
|
|
482
|
+
}}
|
|
483
|
+
/>
|
|
484
|
+
);
|
|
485
|
+
}
|
|
486
|
+
|
|
267
487
|
// --- Section ---
|
|
268
488
|
|
|
269
489
|
interface DynamicRangeSectionProps {
|
|
@@ -291,6 +511,9 @@ export function DynamicRangeSection({
|
|
|
291
511
|
|
|
292
512
|
return (
|
|
293
513
|
<div style={{ display: "flex", flexDirection: "column", gap: 12 }}>
|
|
514
|
+
{/* Dynamic range graph */}
|
|
515
|
+
<DynamicRangeGraph state={state} />
|
|
516
|
+
|
|
294
517
|
{/* Labels above slider */}
|
|
295
518
|
<div
|
|
296
519
|
style={{
|
|
@@ -7,7 +7,6 @@ import { usePresets } from "./usePresets";
|
|
|
7
7
|
import type {
|
|
8
8
|
Preset,
|
|
9
9
|
SaveStatus,
|
|
10
|
-
ThemeName,
|
|
11
10
|
PreviewView,
|
|
12
11
|
SidebarSelection,
|
|
13
12
|
EditorPersistence,
|
|
@@ -50,9 +49,6 @@ export function useEditorState({
|
|
|
50
49
|
const [colorMode, setColorMode] = useState<ColorMode>(
|
|
51
50
|
initialState.preview.mode,
|
|
52
51
|
);
|
|
53
|
-
const [activeTheme, setActiveTheme] = useState<ThemeName>(
|
|
54
|
-
(initialState.preview.theme as ThemeName) || "neutral",
|
|
55
|
-
);
|
|
56
52
|
const [previewView, setPreviewView] = useState<PreviewView>(
|
|
57
53
|
initialPreviewView ?? { kind: "overview" },
|
|
58
54
|
);
|
|
@@ -205,9 +201,20 @@ export function useEditorState({
|
|
|
205
201
|
}
|
|
206
202
|
}, [sidebarSelection, initOverridesFromVariant]);
|
|
207
203
|
|
|
208
|
-
const handlePropOverride = useCallback(
|
|
209
|
-
|
|
210
|
-
|
|
204
|
+
const handlePropOverride = useCallback(
|
|
205
|
+
(propName: string, value: unknown) => {
|
|
206
|
+
setPropOverrides((prev) => ({ ...prev, [propName]: value }));
|
|
207
|
+
// Re-open sidebar if closed while viewing a component (e.g. icon browser click)
|
|
208
|
+
setSidebarSelection((prev) => {
|
|
209
|
+
if (prev !== null) return prev;
|
|
210
|
+
if (previewView.kind === "component") {
|
|
211
|
+
return { scope: "component", componentId: previewView.componentId };
|
|
212
|
+
}
|
|
213
|
+
return prev;
|
|
214
|
+
});
|
|
215
|
+
},
|
|
216
|
+
[previewView],
|
|
217
|
+
);
|
|
211
218
|
|
|
212
219
|
const handleResetOverrides = useCallback(() => {
|
|
213
220
|
if (sidebarSelection?.scope === "variant") {
|
|
@@ -253,16 +260,6 @@ export function useEditorState({
|
|
|
253
260
|
scheduleSave();
|
|
254
261
|
}, [configuratorState, scheduleSave]);
|
|
255
262
|
|
|
256
|
-
// --- ThemeBar dispatch handlers ---
|
|
257
|
-
|
|
258
|
-
const handleThemeChange = useCallback(
|
|
259
|
-
(theme: ThemeName) => {
|
|
260
|
-
setActiveTheme(theme);
|
|
261
|
-
dispatch({ type: "SET_PREVIEW_THEME", theme });
|
|
262
|
-
},
|
|
263
|
-
[dispatch],
|
|
264
|
-
);
|
|
265
|
-
|
|
266
263
|
const handleColorModeChange = useCallback(
|
|
267
264
|
(mode: ColorMode) => {
|
|
268
265
|
setColorMode(mode);
|
|
@@ -345,10 +342,8 @@ export function useEditorState({
|
|
|
345
342
|
// Preview
|
|
346
343
|
previewView,
|
|
347
344
|
colorMode,
|
|
348
|
-
activeTheme,
|
|
349
345
|
handlePreviewNavigate,
|
|
350
346
|
handleSelectVariant,
|
|
351
|
-
handleThemeChange,
|
|
352
347
|
handleColorModeChange,
|
|
353
348
|
|
|
354
349
|
// Sidebar
|
package/src/index.ts
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
export type {
|
|
3
3
|
Preset,
|
|
4
4
|
SaveStatus,
|
|
5
|
-
ThemeName,
|
|
6
5
|
PreviewView,
|
|
7
6
|
SidebarSelection,
|
|
8
7
|
EditorPersistence,
|
|
@@ -35,7 +34,6 @@ export { PreviewWindow } from "./components/PreviewWindow";
|
|
|
35
34
|
export { RightSidebar } from "./components/RightSidebar";
|
|
36
35
|
export { Sidebar } from "./components/Sidebar";
|
|
37
36
|
export { TableOfContents } from "./components/TableOfContents";
|
|
38
|
-
export { ThemeBar } from "./components/ThemeBar";
|
|
39
37
|
|
|
40
38
|
// Sections
|
|
41
39
|
export {
|
|
@@ -66,7 +66,7 @@ export function CategoryView({
|
|
|
66
66
|
padding: 24,
|
|
67
67
|
borderRadius: 12,
|
|
68
68
|
border: `1px solid ${srgbToHex(
|
|
69
|
-
isHovered ? tokens.
|
|
69
|
+
isHovered ? tokens.accent.fill.srgb : tokens.border.srgb,
|
|
70
70
|
)}`,
|
|
71
71
|
backgroundColor: srgbToHex(tokens.backgroundElevated.srgb),
|
|
72
72
|
cursor: "pointer",
|
|
@@ -3,19 +3,22 @@ import { useTokens } from "@newtonedev/components";
|
|
|
3
3
|
import { srgbToHex } from "newtone";
|
|
4
4
|
import { getComponent } from "@newtonedev/components";
|
|
5
5
|
import { ComponentRenderer } from "./ComponentRenderer";
|
|
6
|
+
import { IconBrowserView } from "./IconBrowserView";
|
|
6
7
|
|
|
7
8
|
interface ComponentDetailViewProps {
|
|
8
9
|
readonly componentId: string;
|
|
9
10
|
readonly selectedVariantId: string | null;
|
|
10
|
-
readonly propOverrides?: Record<string, unknown>;
|
|
11
11
|
readonly onSelectVariant: (variantId: string) => void;
|
|
12
|
+
readonly propOverrides?: Record<string, unknown>;
|
|
13
|
+
readonly onPropOverride?: (name: string, value: unknown) => void;
|
|
12
14
|
}
|
|
13
15
|
|
|
14
16
|
export function ComponentDetailView({
|
|
15
17
|
componentId,
|
|
16
18
|
selectedVariantId,
|
|
17
|
-
propOverrides,
|
|
18
19
|
onSelectVariant,
|
|
20
|
+
propOverrides,
|
|
21
|
+
onPropOverride,
|
|
19
22
|
}: ComponentDetailViewProps) {
|
|
20
23
|
const tokens = useTokens();
|
|
21
24
|
const component = getComponent(componentId);
|
|
@@ -23,7 +26,47 @@ export function ComponentDetailView({
|
|
|
23
26
|
|
|
24
27
|
if (!component) return null;
|
|
25
28
|
|
|
26
|
-
|
|
29
|
+
if (componentId === "icon" && propOverrides && onPropOverride) {
|
|
30
|
+
return (
|
|
31
|
+
<div
|
|
32
|
+
style={{
|
|
33
|
+
padding: "32px 0 0",
|
|
34
|
+
height: "100%",
|
|
35
|
+
display: "flex",
|
|
36
|
+
flexDirection: "column",
|
|
37
|
+
}}
|
|
38
|
+
>
|
|
39
|
+
<div style={{ padding: "0 32px", marginBottom: 24 }}>
|
|
40
|
+
<h2
|
|
41
|
+
style={{
|
|
42
|
+
fontSize: 22,
|
|
43
|
+
fontWeight: 700,
|
|
44
|
+
color: srgbToHex(tokens.textPrimary.srgb),
|
|
45
|
+
margin: 0,
|
|
46
|
+
marginBottom: 4,
|
|
47
|
+
}}
|
|
48
|
+
>
|
|
49
|
+
{component.name}
|
|
50
|
+
</h2>
|
|
51
|
+
<p
|
|
52
|
+
style={{
|
|
53
|
+
fontSize: 14,
|
|
54
|
+
color: srgbToHex(tokens.textSecondary.srgb),
|
|
55
|
+
margin: 0,
|
|
56
|
+
}}
|
|
57
|
+
>
|
|
58
|
+
{component.description}
|
|
59
|
+
</p>
|
|
60
|
+
</div>
|
|
61
|
+
<IconBrowserView
|
|
62
|
+
selectedIconName={(propOverrides.name as string) ?? "add"}
|
|
63
|
+
onIconSelect={(name) => onPropOverride("name", name)}
|
|
64
|
+
/>
|
|
65
|
+
</div>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const interactiveColor = srgbToHex(tokens.accent.fill.srgb);
|
|
27
70
|
|
|
28
71
|
return (
|
|
29
72
|
<div style={{ padding: 32 }}>
|
|
@@ -99,11 +142,7 @@ export function ComponentDetailView({
|
|
|
99
142
|
>
|
|
100
143
|
<ComponentRenderer
|
|
101
144
|
componentId={componentId}
|
|
102
|
-
props={
|
|
103
|
-
isSelected && propOverrides
|
|
104
|
-
? { ...variant.props, ...propOverrides }
|
|
105
|
-
: variant.props
|
|
106
|
-
}
|
|
145
|
+
props={variant.props}
|
|
107
146
|
/>
|
|
108
147
|
</div>
|
|
109
148
|
<span
|
|
@@ -7,6 +7,10 @@ import {
|
|
|
7
7
|
Toggle,
|
|
8
8
|
Slider,
|
|
9
9
|
HueSlider,
|
|
10
|
+
Frame,
|
|
11
|
+
Text,
|
|
12
|
+
Icon,
|
|
13
|
+
Wrapper,
|
|
10
14
|
useTokens,
|
|
11
15
|
} from "@newtonedev/components";
|
|
12
16
|
import { srgbToHex } from "newtone";
|
|
@@ -71,6 +75,24 @@ function CardPreview(props: AnyProps) {
|
|
|
71
75
|
);
|
|
72
76
|
}
|
|
73
77
|
|
|
78
|
+
function FramePreview(props: AnyProps) {
|
|
79
|
+
return (
|
|
80
|
+
<Frame {...props} style={{ minWidth: 200, minHeight: 60 }}>
|
|
81
|
+
<Text size="sm">Frame content</Text>
|
|
82
|
+
</Frame>
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function WrapperPreview(props: AnyProps) {
|
|
87
|
+
return (
|
|
88
|
+
<Wrapper {...props} style={{ minWidth: 200 }}>
|
|
89
|
+
<Text size="sm">Item 1</Text>
|
|
90
|
+
<Text size="sm">Item 2</Text>
|
|
91
|
+
<Text size="sm">Item 3</Text>
|
|
92
|
+
</Wrapper>
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
74
96
|
export function ComponentRenderer({ componentId, props }: ComponentRendererProps) {
|
|
75
97
|
const noop = useCallback(() => {}, []);
|
|
76
98
|
|
|
@@ -80,9 +102,11 @@ export function ComponentRenderer({ componentId, props }: ComponentRendererProps
|
|
|
80
102
|
return (
|
|
81
103
|
<Button
|
|
82
104
|
variant={props.variant as AnyProps}
|
|
105
|
+
semantic={props.semantic as AnyProps}
|
|
83
106
|
size={props.size as AnyProps}
|
|
84
107
|
icon={icon}
|
|
85
108
|
iconPosition={props.iconPosition as AnyProps}
|
|
109
|
+
disabled={props.disabled as AnyProps}
|
|
86
110
|
onPress={noop}
|
|
87
111
|
>
|
|
88
112
|
Button
|
|
@@ -101,6 +125,33 @@ export function ComponentRenderer({ componentId, props }: ComponentRendererProps
|
|
|
101
125
|
return <StatefulHueSlider {...props} />;
|
|
102
126
|
case "card":
|
|
103
127
|
return <CardPreview {...props} />;
|
|
128
|
+
case "text":
|
|
129
|
+
return (
|
|
130
|
+
<Text
|
|
131
|
+
size={props.size as AnyProps}
|
|
132
|
+
weight={props.weight as AnyProps}
|
|
133
|
+
color={props.color as AnyProps}
|
|
134
|
+
font={props.font as AnyProps}
|
|
135
|
+
>
|
|
136
|
+
The quick brown fox
|
|
137
|
+
</Text>
|
|
138
|
+
);
|
|
139
|
+
case "icon":
|
|
140
|
+
return (
|
|
141
|
+
<Icon
|
|
142
|
+
name={(props.name as string) ?? "home"}
|
|
143
|
+
size={props.size as AnyProps}
|
|
144
|
+
fill={props.fill as AnyProps}
|
|
145
|
+
/>
|
|
146
|
+
);
|
|
147
|
+
case "frame":
|
|
148
|
+
return (
|
|
149
|
+
<FramePreview {...props} />
|
|
150
|
+
);
|
|
151
|
+
case "wrapper":
|
|
152
|
+
return (
|
|
153
|
+
<WrapperPreview {...props} />
|
|
154
|
+
);
|
|
104
155
|
default:
|
|
105
156
|
return null;
|
|
106
157
|
}
|