@newtonedev/editor 0.1.5 → 0.1.7
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 +1 -1
- package/dist/Editor.d.ts.map +1 -1
- package/dist/components/CodeBlock.d.ts.map +1 -1
- package/dist/components/ConfiguratorPanel.d.ts +17 -0
- package/dist/components/ConfiguratorPanel.d.ts.map +1 -0
- package/dist/components/FontPicker.d.ts +4 -2
- package/dist/components/FontPicker.d.ts.map +1 -1
- package/dist/components/PresetSelector.d.ts.map +1 -1
- package/dist/components/PreviewWindow.d.ts +9 -3
- package/dist/components/PreviewWindow.d.ts.map +1 -1
- package/dist/components/PrimaryNav.d.ts +7 -0
- package/dist/components/PrimaryNav.d.ts.map +1 -0
- package/dist/components/RightSidebar.d.ts +4 -1
- package/dist/components/RightSidebar.d.ts.map +1 -1
- package/dist/components/Sidebar.d.ts +1 -10
- package/dist/components/Sidebar.d.ts.map +1 -1
- package/dist/components/TableOfContents.d.ts +2 -1
- package/dist/components/TableOfContents.d.ts.map +1 -1
- package/dist/components/sections/DynamicRangeSection.d.ts.map +1 -1
- package/dist/components/sections/FontsSection.d.ts +3 -1
- package/dist/components/sections/FontsSection.d.ts.map +1 -1
- package/dist/hooks/useEditorState.d.ts +4 -1
- package/dist/hooks/useEditorState.d.ts.map +1 -1
- package/dist/index.cjs +2893 -2248
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2895 -2251
- package/dist/index.js.map +1 -1
- package/dist/preview/ComponentDetailView.d.ts +9 -2
- package/dist/preview/ComponentDetailView.d.ts.map +1 -1
- package/dist/preview/ComponentRenderer.d.ts +2 -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 +17 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/lookupFontMetrics.d.ts +19 -0
- package/dist/utils/lookupFontMetrics.d.ts.map +1 -0
- package/dist/utils/measureFonts.d.ts +18 -0
- package/dist/utils/measureFonts.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/Editor.tsx +57 -11
- package/src/components/CodeBlock.tsx +42 -14
- package/src/components/ConfiguratorPanel.tsx +77 -0
- package/src/components/FontPicker.tsx +38 -29
- package/src/components/PresetSelector.tsx +8 -33
- package/src/components/PreviewWindow.tsx +20 -4
- package/src/components/PrimaryNav.tsx +76 -0
- package/src/components/RightSidebar.tsx +103 -40
- package/src/components/Sidebar.tsx +4 -211
- package/src/components/TableOfContents.tsx +41 -78
- package/src/components/sections/DynamicRangeSection.tsx +2 -225
- package/src/components/sections/FontsSection.tsx +61 -93
- package/src/hooks/useEditorState.ts +68 -9
- package/src/index.ts +2 -0
- package/src/preview/ComponentDetailView.tsx +576 -73
- package/src/preview/ComponentRenderer.tsx +6 -4
- package/src/preview/IconBrowserView.tsx +187 -0
- package/src/types.ts +15 -0
- package/src/utils/lookupFontMetrics.ts +52 -0
- package/src/utils/measureFonts.ts +41 -0
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { useState } from "react";
|
|
2
2
|
import {
|
|
3
3
|
useTokens,
|
|
4
|
-
CATEGORIES,
|
|
5
4
|
getComponentsByCategory,
|
|
6
5
|
} from "@newtonedev/components";
|
|
7
6
|
import { srgbToHex } from "newtone";
|
|
8
7
|
import type { PreviewView } from "../types";
|
|
9
8
|
|
|
10
9
|
interface TableOfContentsProps {
|
|
10
|
+
readonly activeSectionId: string;
|
|
11
11
|
readonly activeView: PreviewView;
|
|
12
12
|
readonly selectedComponentId: string | null;
|
|
13
13
|
readonly onNavigate: (view: PreviewView) => void;
|
|
@@ -16,6 +16,7 @@ interface TableOfContentsProps {
|
|
|
16
16
|
const TOC_WIDTH = 220;
|
|
17
17
|
|
|
18
18
|
export function TableOfContents({
|
|
19
|
+
activeSectionId,
|
|
19
20
|
activeView,
|
|
20
21
|
selectedComponentId,
|
|
21
22
|
onNavigate,
|
|
@@ -26,10 +27,13 @@ export function TableOfContents({
|
|
|
26
27
|
const borderColor = srgbToHex(tokens.border.srgb);
|
|
27
28
|
const activeColor = srgbToHex(tokens.accent.fill.srgb);
|
|
28
29
|
const textPrimary = srgbToHex(tokens.textPrimary.srgb);
|
|
29
|
-
const textSecondary = srgbToHex(tokens.textSecondary.srgb);
|
|
30
30
|
const hoverBg = `${borderColor}20`;
|
|
31
31
|
|
|
32
|
-
const
|
|
32
|
+
const components = getComponentsByCategory(activeSectionId);
|
|
33
|
+
|
|
34
|
+
const isOverviewActive =
|
|
35
|
+
activeView.kind === "overview" ||
|
|
36
|
+
(activeView.kind === "category" && activeView.categoryId === activeSectionId);
|
|
33
37
|
|
|
34
38
|
return (
|
|
35
39
|
<nav
|
|
@@ -44,7 +48,7 @@ export function TableOfContents({
|
|
|
44
48
|
}}
|
|
45
49
|
>
|
|
46
50
|
<button
|
|
47
|
-
onClick={() => onNavigate({ kind: "
|
|
51
|
+
onClick={() => onNavigate({ kind: "category", categoryId: activeSectionId })}
|
|
48
52
|
onMouseEnter={() => setHoveredId("overview")}
|
|
49
53
|
onMouseLeave={() => setHoveredId(null)}
|
|
50
54
|
aria-current={isOverviewActive ? "page" : undefined}
|
|
@@ -69,82 +73,41 @@ export function TableOfContents({
|
|
|
69
73
|
Overview
|
|
70
74
|
</button>
|
|
71
75
|
|
|
72
|
-
{
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
76
|
+
{components.map((comp) => {
|
|
77
|
+
const isComponentActive =
|
|
78
|
+
(activeView.kind === "component" &&
|
|
79
|
+
activeView.componentId === comp.id) ||
|
|
80
|
+
selectedComponentId === comp.id;
|
|
77
81
|
|
|
78
82
|
return (
|
|
79
|
-
<
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
{category.name}
|
|
108
|
-
</button>
|
|
109
|
-
|
|
110
|
-
{components.map((comp) => {
|
|
111
|
-
const isComponentActive =
|
|
112
|
-
(activeView.kind === "component" &&
|
|
113
|
-
activeView.componentId === comp.id) ||
|
|
114
|
-
selectedComponentId === comp.id;
|
|
115
|
-
|
|
116
|
-
return (
|
|
117
|
-
<button
|
|
118
|
-
key={comp.id}
|
|
119
|
-
onClick={() =>
|
|
120
|
-
onNavigate({ kind: "component", componentId: comp.id })
|
|
121
|
-
}
|
|
122
|
-
onMouseEnter={() => setHoveredId(comp.id)}
|
|
123
|
-
onMouseLeave={() => setHoveredId(null)}
|
|
124
|
-
aria-current={isComponentActive ? "page" : undefined}
|
|
125
|
-
style={{
|
|
126
|
-
display: "block",
|
|
127
|
-
width: "100%",
|
|
128
|
-
padding: "4px 20px 4px 32px",
|
|
129
|
-
border: "none",
|
|
130
|
-
background: isComponentActive
|
|
131
|
-
? `${activeColor}14`
|
|
132
|
-
: hoveredId === comp.id
|
|
133
|
-
? hoverBg
|
|
134
|
-
: "none",
|
|
135
|
-
cursor: "pointer",
|
|
136
|
-
textAlign: "left",
|
|
137
|
-
fontSize: 13,
|
|
138
|
-
fontWeight: isComponentActive ? 600 : 400,
|
|
139
|
-
color: isComponentActive ? activeColor : textPrimary,
|
|
140
|
-
transition: "background-color 100ms ease",
|
|
141
|
-
}}
|
|
142
|
-
>
|
|
143
|
-
{comp.name}
|
|
144
|
-
</button>
|
|
145
|
-
);
|
|
146
|
-
})}
|
|
147
|
-
</div>
|
|
83
|
+
<button
|
|
84
|
+
key={comp.id}
|
|
85
|
+
onClick={() =>
|
|
86
|
+
onNavigate({ kind: "component", componentId: comp.id })
|
|
87
|
+
}
|
|
88
|
+
onMouseEnter={() => setHoveredId(comp.id)}
|
|
89
|
+
onMouseLeave={() => setHoveredId(null)}
|
|
90
|
+
aria-current={isComponentActive ? "page" : undefined}
|
|
91
|
+
style={{
|
|
92
|
+
display: "block",
|
|
93
|
+
width: "100%",
|
|
94
|
+
padding: "4px 20px",
|
|
95
|
+
border: "none",
|
|
96
|
+
background: isComponentActive
|
|
97
|
+
? `${activeColor}14`
|
|
98
|
+
: hoveredId === comp.id
|
|
99
|
+
? hoverBg
|
|
100
|
+
: "none",
|
|
101
|
+
cursor: "pointer",
|
|
102
|
+
textAlign: "left",
|
|
103
|
+
fontSize: 13,
|
|
104
|
+
fontWeight: isComponentActive ? 600 : 400,
|
|
105
|
+
color: isComponentActive ? activeColor : textPrimary,
|
|
106
|
+
transition: "background-color 100ms ease",
|
|
107
|
+
}}
|
|
108
|
+
>
|
|
109
|
+
{comp.name}
|
|
110
|
+
</button>
|
|
148
111
|
);
|
|
149
112
|
})}
|
|
150
113
|
</nav>
|
|
@@ -1,20 +1,9 @@
|
|
|
1
|
-
import { useState, useRef, useCallback
|
|
1
|
+
import { useState, useRef, useCallback } from "react";
|
|
2
2
|
import { HueSlider, Select, useTokens } from "@newtonedev/components";
|
|
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";
|
|
3
|
+
import { srgbToHex } from "newtone";
|
|
14
4
|
import type { HueGradingStrength } from "newtone";
|
|
15
5
|
import type { ConfiguratorState } from "@newtonedev/configurator";
|
|
16
6
|
import type { ConfiguratorAction } from "@newtonedev/configurator";
|
|
17
|
-
import { traditionalHueToOklch } from "@newtonedev/configurator";
|
|
18
7
|
|
|
19
8
|
const STRENGTH_OPTIONS = [
|
|
20
9
|
{ label: "None", value: "none" },
|
|
@@ -275,215 +264,6 @@ function RangeInput({ display, onCommit, toInternal }: RangeInputProps) {
|
|
|
275
264
|
);
|
|
276
265
|
}
|
|
277
266
|
|
|
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
|
-
|
|
487
267
|
// --- Section ---
|
|
488
268
|
|
|
489
269
|
interface DynamicRangeSectionProps {
|
|
@@ -511,9 +291,6 @@ export function DynamicRangeSection({
|
|
|
511
291
|
|
|
512
292
|
return (
|
|
513
293
|
<div style={{ display: "flex", flexDirection: "column", gap: 12 }}>
|
|
514
|
-
{/* Dynamic range graph */}
|
|
515
|
-
<DynamicRangeGraph state={state} />
|
|
516
|
-
|
|
517
294
|
{/* Labels above slider */}
|
|
518
295
|
<div
|
|
519
296
|
style={{
|
|
@@ -1,18 +1,13 @@
|
|
|
1
|
-
import { Slider
|
|
1
|
+
import { Slider } from "@newtonedev/components";
|
|
2
|
+
import { useTokens } from "@newtonedev/components";
|
|
2
3
|
import type { FontConfig } from "@newtonedev/components";
|
|
3
4
|
import { srgbToHex } from "newtone";
|
|
4
|
-
import type { ConfiguratorState } from "@newtonedev/configurator";
|
|
5
|
+
import type { ConfiguratorState, FontScope, FontSlotConfig } from "@newtonedev/configurator";
|
|
5
6
|
import type { ConfiguratorAction } from "@newtonedev/configurator";
|
|
7
|
+
import type { GoogleFontEntry } from "@newtonedev/fonts";
|
|
6
8
|
import { FontPicker } from "../FontPicker";
|
|
7
9
|
|
|
8
|
-
const
|
|
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 = {
|
|
10
|
+
const DEFAULT_FONT_SYSTEM: FontConfig = {
|
|
16
11
|
type: "system",
|
|
17
12
|
family: "system-ui",
|
|
18
13
|
fallback:
|
|
@@ -25,107 +20,80 @@ const DEFAULT_FONT_MONO: FontConfig = {
|
|
|
25
20
|
fallback: "SFMono-Regular, Menlo, Monaco, Consolas, monospace",
|
|
26
21
|
};
|
|
27
22
|
|
|
23
|
+
function getDefaultFontConfig(scope: FontScope): FontConfig {
|
|
24
|
+
return scope === "mono" ? DEFAULT_FONT_MONO : DEFAULT_FONT_SYSTEM;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function getCurrentFontConfig(
|
|
28
|
+
state: ConfiguratorState,
|
|
29
|
+
scope: FontScope,
|
|
30
|
+
): FontConfig {
|
|
31
|
+
return state.typography?.fonts[scope]?.config ?? getDefaultFontConfig(scope);
|
|
32
|
+
}
|
|
33
|
+
|
|
28
34
|
interface FontsSectionProps {
|
|
29
35
|
readonly state: ConfiguratorState;
|
|
30
36
|
readonly dispatch: (action: ConfiguratorAction) => void;
|
|
37
|
+
readonly fontCatalog?: readonly GoogleFontEntry[];
|
|
31
38
|
}
|
|
32
39
|
|
|
33
|
-
|
|
34
|
-
|
|
40
|
+
const FONT_SCOPES: readonly { scope: FontScope; label: string; slot: "default" | "display" | "mono" | "currency" }[] = [
|
|
41
|
+
{ scope: "main", label: "Main", slot: "default" },
|
|
42
|
+
{ scope: "display", label: "Display", slot: "display" },
|
|
43
|
+
{ scope: "mono", label: "Mono", slot: "mono" },
|
|
44
|
+
{ scope: "currency", label: "Currency", slot: "currency" },
|
|
45
|
+
];
|
|
35
46
|
|
|
36
|
-
|
|
37
|
-
const
|
|
47
|
+
export function FontsSection({ state, dispatch, fontCatalog }: FontsSectionProps) {
|
|
48
|
+
const tokens = useTokens();
|
|
38
49
|
|
|
39
50
|
const labelColor = srgbToHex(tokens.textSecondary.srgb);
|
|
40
51
|
|
|
41
|
-
const handleFontChange = (
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
52
|
+
const handleFontChange = (scope: FontScope, font: FontConfig) => {
|
|
53
|
+
// Preserve existing weight slots when switching font family
|
|
54
|
+
const weights = state.typography?.fonts[scope]?.weights ?? { regular: 400, medium: 500, bold: 700 };
|
|
55
|
+
const slotConfig: FontSlotConfig = { config: font, weights };
|
|
56
|
+
dispatch({ type: "SET_FONT", scope, font: slotConfig });
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const sectionLabelStyle = {
|
|
60
|
+
fontSize: 11,
|
|
61
|
+
fontWeight: 600 as const,
|
|
62
|
+
color: labelColor,
|
|
63
|
+
textTransform: "uppercase" as const,
|
|
64
|
+
letterSpacing: 0.5,
|
|
65
|
+
marginBottom: 8,
|
|
51
66
|
};
|
|
52
67
|
|
|
53
68
|
return (
|
|
54
69
|
<div style={{ display: "flex", flexDirection: "column", gap: 16 }}>
|
|
55
70
|
<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>
|
|
71
|
+
<div style={sectionLabelStyle}>Fonts</div>
|
|
68
72
|
<div style={{ display: "flex", flexDirection: "column", gap: 12 }}>
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
/>
|
|
73
|
+
{FONT_SCOPES.map(({ scope, label, slot }) => (
|
|
74
|
+
<FontPicker
|
|
75
|
+
key={scope}
|
|
76
|
+
label={label}
|
|
77
|
+
slot={slot}
|
|
78
|
+
currentFont={getCurrentFontConfig(state, scope)}
|
|
79
|
+
onSelect={(font) => handleFontChange(scope, font)}
|
|
80
|
+
fontCatalog={fontCatalog}
|
|
81
|
+
/>
|
|
82
|
+
))}
|
|
91
83
|
</div>
|
|
92
84
|
</div>
|
|
93
|
-
|
|
94
85
|
<div>
|
|
95
|
-
<div
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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>
|
|
86
|
+
<div style={sectionLabelStyle}>Type Scale</div>
|
|
87
|
+
<Slider
|
|
88
|
+
value={Math.round((state.typography?.typeScaleOffset ?? 0.5) * 100)}
|
|
89
|
+
onValueChange={(v) =>
|
|
90
|
+
dispatch({ type: "SET_TYPE_SCALE_OFFSET", offset: v / 100 })
|
|
91
|
+
}
|
|
92
|
+
min={0}
|
|
93
|
+
max={100}
|
|
94
|
+
label="Scale"
|
|
95
|
+
showValue
|
|
96
|
+
/>
|
|
129
97
|
</div>
|
|
130
98
|
</div>
|
|
131
99
|
);
|