@newtonedev/editor 0.1.6 → 0.1.8
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 +2484 -2052
- 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 +2486 -2055
- 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 +68 -17
- 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") {
|
|
@@ -274,21 +314,30 @@ export function useEditorState({
|
|
|
274
314
|
if (debounceRef.current) clearTimeout(debounceRef.current);
|
|
275
315
|
setPublishing(true);
|
|
276
316
|
|
|
277
|
-
|
|
278
|
-
|
|
317
|
+
try {
|
|
318
|
+
const currentState = latestStateRef.current;
|
|
319
|
+
const updatedPresets = publishActivePreset(currentState);
|
|
320
|
+
const [calibrations, fontMetrics] = await Promise.all([
|
|
321
|
+
measureFontCalibrations(currentState.typography?.fonts),
|
|
322
|
+
lookupFontMetrics(currentState.typography?.fonts, manifestUrl),
|
|
323
|
+
]);
|
|
279
324
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
325
|
+
const { error } = await persistence.onPublish({
|
|
326
|
+
state: currentState,
|
|
327
|
+
presets: updatedPresets,
|
|
328
|
+
activePresetId,
|
|
329
|
+
calibrations,
|
|
330
|
+
fontMetrics,
|
|
331
|
+
});
|
|
285
332
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
333
|
+
if (!error) {
|
|
334
|
+
setSaveStatus("saved");
|
|
335
|
+
setIsPublished(true);
|
|
336
|
+
}
|
|
337
|
+
} finally {
|
|
338
|
+
setPublishing(false);
|
|
289
339
|
}
|
|
290
|
-
|
|
291
|
-
}, [activePresetId, publishActivePreset, persistence]);
|
|
340
|
+
}, [activePresetId, publishActivePreset, persistence, manifestUrl]);
|
|
292
341
|
|
|
293
342
|
// --- beforeunload warning ---
|
|
294
343
|
useEffect(() => {
|
|
@@ -342,7 +391,9 @@ export function useEditorState({
|
|
|
342
391
|
// Preview
|
|
343
392
|
previewView,
|
|
344
393
|
colorMode,
|
|
394
|
+
activeSectionId,
|
|
345
395
|
handlePreviewNavigate,
|
|
396
|
+
handleSectionChange,
|
|
346
397
|
handleSelectVariant,
|
|
347
398
|
handleColorModeChange,
|
|
348
399
|
|
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";
|