@newtonedev/editor 0.1.4 → 0.1.5
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/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 +220 -107
- 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 +223 -109
- package/dist/index.js.map +1 -1
- package/dist/preview/ComponentRenderer.d.ts.map +1 -1
- package/dist/types.d.ts +0 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/Editor.tsx +15 -8
- 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 +3 -3
- package/src/components/RightSidebar.tsx +2 -2
- 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 +0 -16
- package/src/index.ts +0 -2
- package/src/preview/CategoryView.tsx +1 -1
- package/src/preview/ComponentDetailView.tsx +1 -1
- package/src/preview/ComponentRenderer.tsx +51 -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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ComponentRenderer.d.ts","sourceRoot":"","sources":["../../src/preview/ComponentRenderer.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ComponentRenderer.d.ts","sourceRoot":"","sources":["../../src/preview/ComponentRenderer.tsx"],"names":[],"mappings":"AAiBA,UAAU,sBAAsB;IAC9B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACzC;AA2ED,wBAAgB,iBAAiB,CAAC,EAAE,WAAW,EAAE,KAAK,EAAE,EAAE,sBAAsB,kDA8D/E"}
|
package/dist/types.d.ts
CHANGED
|
@@ -8,7 +8,6 @@ export interface Preset {
|
|
|
8
8
|
readonly published_state: ConfiguratorState | null;
|
|
9
9
|
}
|
|
10
10
|
export type SaveStatus = "saved" | "saving" | "unsaved" | "error";
|
|
11
|
-
export type ThemeName = "neutral" | "primary" | "secondary" | "strong";
|
|
12
11
|
export type PreviewView = {
|
|
13
12
|
readonly kind: "overview";
|
|
14
13
|
} | {
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AACjE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAIvC,MAAM,WAAW,MAAM;IACrB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,EAAE,iBAAiB,CAAC;IACxC,QAAQ,CAAC,eAAe,EAAE,iBAAiB,GAAG,IAAI,CAAC;CACpD;AAID,MAAM,MAAM,UAAU,GAAG,OAAO,GAAG,QAAQ,GAAG,SAAS,GAAG,OAAO,CAAC;AAElE,MAAM,MAAM,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AACjE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAIvC,MAAM,WAAW,MAAM;IACrB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,EAAE,iBAAiB,CAAC;IACxC,QAAQ,CAAC,eAAe,EAAE,iBAAiB,GAAG,IAAI,CAAC;CACpD;AAID,MAAM,MAAM,UAAU,GAAG,OAAO,GAAG,QAAQ,GAAG,SAAS,GAAG,OAAO,CAAC;AAElE,MAAM,MAAM,WAAW,GACnB;IAAE,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAA;CAAE,GAC7B;IAAE,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAAC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GAC1D;IAAE,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAAC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;CAAE,CAAC;AAEjE,MAAM,MAAM,gBAAgB,GACxB,IAAI,GACJ;IAAE,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC;IAAC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;CAAE,GAC7D;IACE,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC;IAC1B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B,CAAC;AAIN,MAAM,WAAW,iBAAiB;IAChC,+CAA+C;IAC/C,QAAQ,CAAC,WAAW,EAAE,CAAC,MAAM,EAAE;QAC7B,QAAQ,CAAC,KAAK,EAAE,iBAAiB,CAAC;QAClC,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;KACrC,KAAK,OAAO,CAAC;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IAEnC,iCAAiC;IACjC,QAAQ,CAAC,SAAS,EAAE,CAAC,MAAM,EAAE;QAC3B,QAAQ,CAAC,KAAK,EAAE,iBAAiB,CAAC;QAClC,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;QACpC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;KACjC,KAAK,OAAO,CAAC;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IAEnC,gEAAgE;IAChE,QAAQ,CAAC,cAAc,EAAE,CAAC,MAAM,EAAE;QAChC,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;QACpC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;QAChC,QAAQ,CAAC,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;KAC3C,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACrB;AAID,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,CAAC;IAC1B,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,CAAC;CAC5B;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,YAAY,EAAE,iBAAiB,CAAC;IACzC,QAAQ,CAAC,kBAAkB,EAAE,OAAO,CAAC;IACrC,QAAQ,CAAC,cAAc,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3C,QAAQ,CAAC,qBAAqB,EAAE,MAAM,CAAC;IACvC,QAAQ,CAAC,wBAAwB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjD,QAAQ,CAAC,YAAY,EAAE,iBAAiB,CAAC;IACzC,QAAQ,CAAC,iBAAiB,EAAE,kBAAkB,CAAC;IAC/C,QAAQ,CAAC,WAAW,EAAE,iBAAiB,CAAC;IACxC,QAAQ,CAAC,WAAW,CAAC,EAAE,iBAAiB,CAAC;IACzC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,WAAW,KAAK,IAAI,CAAC;IAClD,QAAQ,CAAC,kBAAkB,CAAC,EAAE,WAAW,CAAC;CAC3C"}
|
package/package.json
CHANGED
package/src/Editor.tsx
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import { useMemo } from "react";
|
|
1
2
|
import { NewtoneProvider } from "@newtonedev/components";
|
|
3
|
+
import type { NewtoneThemeConfig } from "@newtonedev/components";
|
|
2
4
|
import { useEditorState } from "./hooks/useEditorState";
|
|
3
5
|
import { EditorShell } from "./components/EditorShell";
|
|
4
6
|
import { Sidebar } from "./components/Sidebar";
|
|
5
7
|
import { EditorHeader } from "./components/EditorHeader";
|
|
6
|
-
import { ThemeBar } from "./components/ThemeBar";
|
|
7
8
|
import { TableOfContents } from "./components/TableOfContents";
|
|
8
9
|
import { PreviewWindow } from "./components/PreviewWindow";
|
|
9
10
|
import { RightSidebar } from "./components/RightSidebar";
|
|
@@ -34,6 +35,17 @@ export function Editor({
|
|
|
34
35
|
initialPreviewView,
|
|
35
36
|
});
|
|
36
37
|
|
|
38
|
+
// Merge token overrides from chrome config into the preview config.
|
|
39
|
+
// Token overrides (from Token Tuner) are stored separately from configurator
|
|
40
|
+
// state, but should apply to the preview so components render accurately.
|
|
41
|
+
const previewConfig: NewtoneThemeConfig = useMemo(
|
|
42
|
+
() =>
|
|
43
|
+
chromeThemeConfig.tokenOverrides
|
|
44
|
+
? { ...editor.themeConfig, tokenOverrides: chromeThemeConfig.tokenOverrides }
|
|
45
|
+
: editor.themeConfig,
|
|
46
|
+
[editor.themeConfig, chromeThemeConfig.tokenOverrides],
|
|
47
|
+
);
|
|
48
|
+
|
|
37
49
|
return (
|
|
38
50
|
<NewtoneProvider config={chromeThemeConfig}>
|
|
39
51
|
<EditorShell
|
|
@@ -90,15 +102,10 @@ export function Editor({
|
|
|
90
102
|
}}
|
|
91
103
|
>
|
|
92
104
|
<NewtoneProvider
|
|
93
|
-
config={
|
|
105
|
+
config={previewConfig}
|
|
94
106
|
initialMode={editor.colorMode}
|
|
95
|
-
|
|
96
|
-
key={`${editor.colorMode}-${editor.activeTheme}`}
|
|
107
|
+
key={editor.colorMode}
|
|
97
108
|
>
|
|
98
|
-
<ThemeBar
|
|
99
|
-
activeTheme={editor.activeTheme}
|
|
100
|
-
onThemeChange={editor.handleThemeChange}
|
|
101
|
-
/>
|
|
102
109
|
<div style={{ flex: 1, overflowY: "auto", minWidth: 0 }}>
|
|
103
110
|
<PreviewWindow
|
|
104
111
|
view={editor.previewView}
|
|
@@ -34,10 +34,10 @@ export function EditorHeader({
|
|
|
34
34
|
const borderColor = srgbToHex(tokens.border.srgb);
|
|
35
35
|
|
|
36
36
|
const statusColor: Record<SaveStatus, string> = {
|
|
37
|
-
saved: srgbToHex(tokens.success.srgb),
|
|
38
|
-
saving: srgbToHex(tokens.warning.srgb),
|
|
37
|
+
saved: srgbToHex(tokens.success.fill.srgb),
|
|
38
|
+
saving: srgbToHex(tokens.warning.fill.srgb),
|
|
39
39
|
unsaved: srgbToHex(tokens.textSecondary.srgb),
|
|
40
|
-
error: srgbToHex(tokens.error.srgb),
|
|
40
|
+
error: srgbToHex(tokens.error.fill.srgb),
|
|
41
41
|
};
|
|
42
42
|
|
|
43
43
|
return (
|
|
@@ -23,7 +23,7 @@ export function EditorShell({
|
|
|
23
23
|
<div
|
|
24
24
|
style={{
|
|
25
25
|
display: "flex",
|
|
26
|
-
height: "
|
|
26
|
+
height: "100%",
|
|
27
27
|
overflow: "hidden",
|
|
28
28
|
backgroundColor: srgbToHex(tokens.background.srgb),
|
|
29
29
|
}}
|
|
@@ -33,7 +33,7 @@ export function EditorShell({
|
|
|
33
33
|
flex: 1,
|
|
34
34
|
display: "flex",
|
|
35
35
|
flexDirection: "column",
|
|
36
|
-
height: "
|
|
36
|
+
height: "100%",
|
|
37
37
|
overflow: "hidden",
|
|
38
38
|
minWidth: 0,
|
|
39
39
|
}}
|
|
@@ -84,7 +84,7 @@ export function FontPicker({
|
|
|
84
84
|
const bgColor = srgbToHex(tokens.backgroundElevated.srgb);
|
|
85
85
|
const borderColor = srgbToHex(tokens.border.srgb);
|
|
86
86
|
const hoverColor = srgbToHex(tokens.backgroundSunken.srgb);
|
|
87
|
-
const interactiveColor = srgbToHex(tokens.
|
|
87
|
+
const interactiveColor = srgbToHex(tokens.accent.fill.srgb);
|
|
88
88
|
|
|
89
89
|
useEffect(() => {
|
|
90
90
|
if (!isOpen) return;
|
|
@@ -44,9 +44,9 @@ export function PresetSelector({
|
|
|
44
44
|
const bgColor = srgbToHex(tokens.background.srgb);
|
|
45
45
|
const textPrimary = srgbToHex(tokens.textPrimary.srgb);
|
|
46
46
|
const textSecondary = srgbToHex(tokens.textSecondary.srgb);
|
|
47
|
-
const interactiveColor = srgbToHex(tokens.
|
|
48
|
-
const warningColor = srgbToHex(tokens.warning.srgb);
|
|
49
|
-
const errorColor = srgbToHex(tokens.error.srgb);
|
|
47
|
+
const interactiveColor = srgbToHex(tokens.accent.fill.srgb);
|
|
48
|
+
const warningColor = srgbToHex(tokens.warning.fill.srgb);
|
|
49
|
+
const errorColor = srgbToHex(tokens.error.fill.srgb);
|
|
50
50
|
const hoverBg = `${borderColor}18`;
|
|
51
51
|
const activeBg = `${interactiveColor}14`;
|
|
52
52
|
|
|
@@ -105,7 +105,7 @@ export function RightSidebar({
|
|
|
105
105
|
padding: 0,
|
|
106
106
|
fontSize: 14,
|
|
107
107
|
fontWeight: 500,
|
|
108
|
-
color: srgbToHex(tokens.
|
|
108
|
+
color: srgbToHex(tokens.accent.fill.srgb),
|
|
109
109
|
whiteSpace: "nowrap",
|
|
110
110
|
}}
|
|
111
111
|
>
|
|
@@ -339,7 +339,7 @@ function PropControl({
|
|
|
339
339
|
height: 20,
|
|
340
340
|
borderRadius: 10,
|
|
341
341
|
backgroundColor: value
|
|
342
|
-
? srgbToHex(tokens.
|
|
342
|
+
? srgbToHex(tokens.accent.fill.srgb)
|
|
343
343
|
: srgbToHex(tokens.border.srgb),
|
|
344
344
|
position: "relative",
|
|
345
345
|
transition: "background-color 150ms ease",
|
|
@@ -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
|
);
|
|
@@ -253,16 +249,6 @@ export function useEditorState({
|
|
|
253
249
|
scheduleSave();
|
|
254
250
|
}, [configuratorState, scheduleSave]);
|
|
255
251
|
|
|
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
252
|
const handleColorModeChange = useCallback(
|
|
267
253
|
(mode: ColorMode) => {
|
|
268
254
|
setColorMode(mode);
|
|
@@ -345,10 +331,8 @@ export function useEditorState({
|
|
|
345
331
|
// Preview
|
|
346
332
|
previewView,
|
|
347
333
|
colorMode,
|
|
348
|
-
activeTheme,
|
|
349
334
|
handlePreviewNavigate,
|
|
350
335
|
handleSelectVariant,
|
|
351
|
-
handleThemeChange,
|
|
352
336
|
handleColorModeChange,
|
|
353
337
|
|
|
354
338
|
// 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",
|
|
@@ -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
|
}
|
|
@@ -128,7 +128,7 @@ function ComponentCard({
|
|
|
128
128
|
padding: 20,
|
|
129
129
|
borderRadius: 12,
|
|
130
130
|
border: `1px solid ${srgbToHex(
|
|
131
|
-
isHovered ? tokens.
|
|
131
|
+
isHovered ? tokens.accent.fill.srgb : tokens.border.srgb,
|
|
132
132
|
)}`,
|
|
133
133
|
backgroundColor: srgbToHex(tokens.backgroundElevated.srgb),
|
|
134
134
|
cursor: "pointer",
|
package/src/types.ts
CHANGED
|
@@ -15,8 +15,6 @@ export interface Preset {
|
|
|
15
15
|
|
|
16
16
|
export type SaveStatus = "saved" | "saving" | "unsaved" | "error";
|
|
17
17
|
|
|
18
|
-
export type ThemeName = "neutral" | "primary" | "secondary" | "strong";
|
|
19
|
-
|
|
20
18
|
export type PreviewView =
|
|
21
19
|
| { readonly kind: "overview" }
|
|
22
20
|
| { readonly kind: "category"; readonly categoryId: string }
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import type { ThemeName } from "../types";
|
|
2
|
-
interface ThemeBarProps {
|
|
3
|
-
readonly activeTheme: ThemeName;
|
|
4
|
-
readonly onThemeChange: (theme: ThemeName) => void;
|
|
5
|
-
}
|
|
6
|
-
export declare function ThemeBar({ activeTheme, onThemeChange }: ThemeBarProps): import("react/jsx-runtime").JSX.Element;
|
|
7
|
-
export {};
|
|
8
|
-
//# sourceMappingURL=ThemeBar.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ThemeBar.d.ts","sourceRoot":"","sources":["../../src/components/ThemeBar.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAE1C,UAAU,aAAa;IACrB,QAAQ,CAAC,WAAW,EAAE,SAAS,CAAC;IAChC,QAAQ,CAAC,aAAa,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,IAAI,CAAC;CACpD;AASD,wBAAgB,QAAQ,CAAC,EAAE,WAAW,EAAE,aAAa,EAAE,EAAE,aAAa,2CA0DrE"}
|