@newtonedev/editor 0.1.6 → 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/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/PreviewWindow.d.ts +7 -2
- 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/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 +2464 -2046
- 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 +2466 -2049
- package/dist/index.js.map +1 -1
- package/dist/preview/ComponentDetailView.d.ts +7 -1
- 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/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 +53 -10
- package/src/components/ConfiguratorPanel.tsx +77 -0
- package/src/components/FontPicker.tsx +38 -29
- package/src/components/PreviewWindow.tsx +14 -1
- package/src/components/PrimaryNav.tsx +76 -0
- package/src/components/Sidebar.tsx +5 -132
- 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 +54 -6
- package/src/index.ts +2 -0
- package/src/preview/ComponentDetailView.tsx +531 -67
- package/src/preview/ComponentRenderer.tsx +6 -4
- package/src/types.ts +15 -0
- package/src/utils/lookupFontMetrics.ts +52 -0
- package/src/utils/measureFonts.ts +41 -0
|
@@ -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
|
);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useState, useCallback, useRef, useEffect, useMemo } from "react";
|
|
2
|
-
import { getComponent } from "@newtonedev/components";
|
|
2
|
+
import { getComponent, CATEGORIES, getComponentsByCategory } from "@newtonedev/components";
|
|
3
3
|
import type { ColorMode } from "@newtonedev/components";
|
|
4
4
|
import type { ConfiguratorState } from "@newtonedev/configurator";
|
|
5
5
|
import { useConfigurator, usePreviewColors } from "@newtonedev/configurator";
|
|
@@ -11,6 +11,8 @@ import type {
|
|
|
11
11
|
SidebarSelection,
|
|
12
12
|
EditorPersistence,
|
|
13
13
|
} from "../types";
|
|
14
|
+
import { measureFontCalibrations } from "../utils/measureFonts";
|
|
15
|
+
import { lookupFontMetrics } from "../utils/lookupFontMetrics";
|
|
14
16
|
|
|
15
17
|
interface UseEditorStateOptions {
|
|
16
18
|
readonly initialState: ConfiguratorState;
|
|
@@ -22,6 +24,7 @@ interface UseEditorStateOptions {
|
|
|
22
24
|
readonly persistence: EditorPersistence;
|
|
23
25
|
readonly onNavigate?: (view: PreviewView) => void;
|
|
24
26
|
readonly initialPreviewView?: PreviewView;
|
|
27
|
+
readonly manifestUrl?: string;
|
|
25
28
|
}
|
|
26
29
|
|
|
27
30
|
export function useEditorState({
|
|
@@ -34,6 +37,7 @@ export function useEditorState({
|
|
|
34
37
|
persistence,
|
|
35
38
|
onNavigate,
|
|
36
39
|
initialPreviewView,
|
|
40
|
+
manifestUrl,
|
|
37
41
|
}: UseEditorStateOptions) {
|
|
38
42
|
// --- Configurator state management ---
|
|
39
43
|
const {
|
|
@@ -52,6 +56,9 @@ export function useEditorState({
|
|
|
52
56
|
const [previewView, setPreviewView] = useState<PreviewView>(
|
|
53
57
|
initialPreviewView ?? { kind: "overview" },
|
|
54
58
|
);
|
|
59
|
+
const [activeSectionId, setActiveSectionId] = useState<string>(
|
|
60
|
+
CATEGORIES[0]?.id ?? "colors",
|
|
61
|
+
);
|
|
55
62
|
const [sidebarSelection, setSidebarSelection] =
|
|
56
63
|
useState<SidebarSelection>(null);
|
|
57
64
|
const [propOverrides, setPropOverrides] = useState<Record<string, unknown>>(
|
|
@@ -159,10 +166,13 @@ export function useEditorState({
|
|
|
159
166
|
onNavigate?.(view);
|
|
160
167
|
|
|
161
168
|
if (view.kind === "component") {
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
169
|
+
const comp = getComponent(view.componentId);
|
|
170
|
+
const firstVariantId = comp?.variants[0]?.id;
|
|
171
|
+
setSidebarSelection(
|
|
172
|
+
firstVariantId
|
|
173
|
+
? { scope: "variant", componentId: view.componentId, variantId: firstVariantId }
|
|
174
|
+
: { scope: "component", componentId: view.componentId },
|
|
175
|
+
);
|
|
166
176
|
initOverridesFromVariant(view.componentId);
|
|
167
177
|
} else {
|
|
168
178
|
setSidebarSelection(null);
|
|
@@ -172,6 +182,36 @@ export function useEditorState({
|
|
|
172
182
|
[onNavigate, initOverridesFromVariant],
|
|
173
183
|
);
|
|
174
184
|
|
|
185
|
+
const handleSectionChange = useCallback(
|
|
186
|
+
(sectionId: string) => {
|
|
187
|
+
setActiveSectionId(sectionId);
|
|
188
|
+
|
|
189
|
+
const sectionComponents = getComponentsByCategory(sectionId);
|
|
190
|
+
if (sectionComponents.length === 1) {
|
|
191
|
+
// Single-component sections (e.g. Typography, Symbols) skip
|
|
192
|
+
// the category overview and navigate directly to the component.
|
|
193
|
+
const comp = sectionComponents[0];
|
|
194
|
+
const view: PreviewView = { kind: "component", componentId: comp.id };
|
|
195
|
+
setPreviewView(view);
|
|
196
|
+
onNavigate?.(view);
|
|
197
|
+
const firstVariantId = comp.variants[0]?.id;
|
|
198
|
+
setSidebarSelection(
|
|
199
|
+
firstVariantId
|
|
200
|
+
? { scope: "variant", componentId: comp.id, variantId: firstVariantId }
|
|
201
|
+
: { scope: "component", componentId: comp.id },
|
|
202
|
+
);
|
|
203
|
+
initOverridesFromVariant(comp.id);
|
|
204
|
+
} else {
|
|
205
|
+
const view: PreviewView = { kind: "category", categoryId: sectionId };
|
|
206
|
+
setPreviewView(view);
|
|
207
|
+
onNavigate?.(view);
|
|
208
|
+
setSidebarSelection(null);
|
|
209
|
+
setPropOverrides({});
|
|
210
|
+
}
|
|
211
|
+
},
|
|
212
|
+
[onNavigate, initOverridesFromVariant],
|
|
213
|
+
);
|
|
214
|
+
|
|
175
215
|
const handleSelectVariant = useCallback(
|
|
176
216
|
(variantId: string) => {
|
|
177
217
|
if (previewView.kind === "component") {
|
|
@@ -276,11 +316,17 @@ export function useEditorState({
|
|
|
276
316
|
|
|
277
317
|
const currentState = latestStateRef.current;
|
|
278
318
|
const updatedPresets = publishActivePreset(currentState);
|
|
319
|
+
const [calibrations, fontMetrics] = await Promise.all([
|
|
320
|
+
measureFontCalibrations(currentState.typography?.fonts),
|
|
321
|
+
lookupFontMetrics(currentState.typography?.fonts, manifestUrl),
|
|
322
|
+
]);
|
|
279
323
|
|
|
280
324
|
const { error } = await persistence.onPublish({
|
|
281
325
|
state: currentState,
|
|
282
326
|
presets: updatedPresets,
|
|
283
327
|
activePresetId,
|
|
328
|
+
calibrations,
|
|
329
|
+
fontMetrics,
|
|
284
330
|
});
|
|
285
331
|
|
|
286
332
|
if (!error) {
|
|
@@ -288,7 +334,7 @@ export function useEditorState({
|
|
|
288
334
|
setIsPublished(true);
|
|
289
335
|
}
|
|
290
336
|
setPublishing(false);
|
|
291
|
-
}, [activePresetId, publishActivePreset, persistence]);
|
|
337
|
+
}, [activePresetId, publishActivePreset, persistence, manifestUrl]);
|
|
292
338
|
|
|
293
339
|
// --- beforeunload warning ---
|
|
294
340
|
useEffect(() => {
|
|
@@ -342,7 +388,9 @@ export function useEditorState({
|
|
|
342
388
|
// Preview
|
|
343
389
|
previewView,
|
|
344
390
|
colorMode,
|
|
391
|
+
activeSectionId,
|
|
345
392
|
handlePreviewNavigate,
|
|
393
|
+
handleSectionChange,
|
|
346
394
|
handleSelectVariant,
|
|
347
395
|
handleColorModeChange,
|
|
348
396
|
|
package/src/index.ts
CHANGED
|
@@ -7,6 +7,7 @@ export type {
|
|
|
7
7
|
EditorPersistence,
|
|
8
8
|
EditorHeaderSlots,
|
|
9
9
|
EditorProps,
|
|
10
|
+
EditorFontEntry,
|
|
10
11
|
} from "./types";
|
|
11
12
|
|
|
12
13
|
// Utilities
|
|
@@ -30,6 +31,7 @@ export { EditorHeader } from "./components/EditorHeader";
|
|
|
30
31
|
export { EditorShell } from "./components/EditorShell";
|
|
31
32
|
export { FontPicker } from "./components/FontPicker";
|
|
32
33
|
export { PresetSelector } from "./components/PresetSelector";
|
|
34
|
+
export { PrimaryNav } from "./components/PrimaryNav";
|
|
33
35
|
export { PreviewWindow } from "./components/PreviewWindow";
|
|
34
36
|
export { RightSidebar } from "./components/RightSidebar";
|
|
35
37
|
export { Sidebar } from "./components/Sidebar";
|