@newtonedev/editor 0.1.8 → 0.1.10
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/hooks/useEditorState.d.ts.map +1 -1
- package/dist/index.cjs +29 -17
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +29 -17
- package/dist/index.js.map +1 -1
- package/dist/utils/lookupFontMetrics.d.ts.map +1 -1
- package/dist/utils/measureFonts.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/Editor.tsx +10 -8
- package/src/components/ConfiguratorPanel.tsx +1 -1
- package/src/components/EditorHeader.tsx +28 -1
- package/src/components/PrimaryNav.tsx +8 -1
- package/src/components/Sidebar.tsx +0 -32
- package/src/components/TableOfContents.tsx +1 -1
- package/src/hooks/useEditorState.ts +38 -24
- package/src/preview/ComponentDetailView.tsx +2 -2
- package/src/types.ts +2 -0
- package/src/utils/lookupFontMetrics.ts +1 -0
- package/src/utils/measureFonts.ts +2 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lookupFontMetrics.d.ts","sourceRoot":"","sources":["../../src/utils/lookupFontMetrics.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAE5D;;;;;;;;;;;;;GAaG;AACH,wBAAsB,iBAAiB,CACrC,KAAK,EAAE,MAAM,CAAC,SAAS,EAAE,QAAQ,CAAC,GAAG,SAAS,EAC9C,WAAW,EAAE,MAAM,GAAG,SAAS,GAC9B,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC,
|
|
1
|
+
{"version":3,"file":"lookupFontMetrics.d.ts","sourceRoot":"","sources":["../../src/utils/lookupFontMetrics.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAE5D;;;;;;;;;;;;;GAaG;AACH,wBAAsB,iBAAiB,CACrC,KAAK,EAAE,MAAM,CAAC,SAAS,EAAE,QAAQ,CAAC,GAAG,SAAS,EAC9C,WAAW,EAAE,MAAM,GAAG,SAAS,GAC9B,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC,CA+B7C"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"measureFonts.d.ts","sourceRoot":"","sources":["../../src/utils/measureFonts.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAGvD;;;;;;;;;;;;;GAaG;AACH,wBAAsB,uBAAuB,CAC3C,KAAK,EAAE,MAAM,CAAC,SAAS,EAAE,QAAQ,CAAC,GAAG,SAAS,GAC7C,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,
|
|
1
|
+
{"version":3,"file":"measureFonts.d.ts","sourceRoot":"","sources":["../../src/utils/measureFonts.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAGvD;;;;;;;;;;;;;GAaG;AACH,wBAAsB,uBAAuB,CAC3C,KAAK,EAAE,MAAM,CAAC,SAAS,EAAE,QAAQ,CAAC,GAAG,SAAS,GAC7C,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAqBjC"}
|
package/package.json
CHANGED
package/src/Editor.tsx
CHANGED
|
@@ -23,6 +23,7 @@ export function Editor({
|
|
|
23
23
|
chromeThemeConfig,
|
|
24
24
|
persistence,
|
|
25
25
|
headerSlots,
|
|
26
|
+
navFooter,
|
|
26
27
|
onNavigate,
|
|
27
28
|
initialPreviewView,
|
|
28
29
|
manifestUrl,
|
|
@@ -80,14 +81,6 @@ export function Editor({
|
|
|
80
81
|
<Sidebar
|
|
81
82
|
isDirty={editor.isDirty}
|
|
82
83
|
onRevert={editor.handleRevert}
|
|
83
|
-
presets={editor.presets}
|
|
84
|
-
activePresetId={editor.activePresetId}
|
|
85
|
-
publishedPresetId={editor.publishedPresetId}
|
|
86
|
-
onSwitchPreset={editor.switchPreset}
|
|
87
|
-
onCreatePreset={editor.createPreset}
|
|
88
|
-
onRenamePreset={editor.renamePreset}
|
|
89
|
-
onDeletePreset={editor.deletePreset}
|
|
90
|
-
onDuplicatePreset={editor.duplicatePreset}
|
|
91
84
|
/>
|
|
92
85
|
}
|
|
93
86
|
navbar={
|
|
@@ -98,6 +91,14 @@ export function Editor({
|
|
|
98
91
|
onPublish={editor.handlePublish}
|
|
99
92
|
onRetry={() => editor.saveDraft(editor.latestStateRef.current)}
|
|
100
93
|
headerSlots={headerSlots}
|
|
94
|
+
presets={editor.presets}
|
|
95
|
+
activePresetId={editor.activePresetId}
|
|
96
|
+
publishedPresetId={editor.publishedPresetId}
|
|
97
|
+
onSwitchPreset={editor.switchPreset}
|
|
98
|
+
onCreatePreset={editor.createPreset}
|
|
99
|
+
onRenamePreset={editor.renamePreset}
|
|
100
|
+
onDeletePreset={editor.deletePreset}
|
|
101
|
+
onDuplicatePreset={editor.duplicatePreset}
|
|
101
102
|
/>
|
|
102
103
|
}
|
|
103
104
|
content={
|
|
@@ -112,6 +113,7 @@ export function Editor({
|
|
|
112
113
|
<PrimaryNav
|
|
113
114
|
activeSectionId={editor.activeSectionId}
|
|
114
115
|
onSelectSection={editor.handleSectionChange}
|
|
116
|
+
footer={navFooter}
|
|
115
117
|
/>
|
|
116
118
|
{editor.activeSectionId === "components" ? (
|
|
117
119
|
<TableOfContents
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
2
|
import { useTokens, Button } from "@newtonedev/components";
|
|
3
3
|
import { srgbToHex } from "newtone";
|
|
4
|
-
import type { SaveStatus } from "../types";
|
|
4
|
+
import type { SaveStatus, Preset } from "../types";
|
|
5
|
+
import { PresetSelector } from "./PresetSelector";
|
|
5
6
|
|
|
6
7
|
interface EditorHeaderProps {
|
|
7
8
|
readonly saveStatus: SaveStatus;
|
|
@@ -13,6 +14,14 @@ interface EditorHeaderProps {
|
|
|
13
14
|
readonly left?: ReactNode;
|
|
14
15
|
readonly right?: ReactNode;
|
|
15
16
|
};
|
|
17
|
+
readonly presets: readonly Preset[];
|
|
18
|
+
readonly activePresetId: string;
|
|
19
|
+
readonly publishedPresetId: string | null;
|
|
20
|
+
readonly onSwitchPreset: (presetId: string) => void;
|
|
21
|
+
readonly onCreatePreset: (name: string) => Promise<string>;
|
|
22
|
+
readonly onRenamePreset: (presetId: string, name: string) => void;
|
|
23
|
+
readonly onDeletePreset: (presetId: string) => Promise<void>;
|
|
24
|
+
readonly onDuplicatePreset: (presetId: string, name: string) => Promise<string>;
|
|
16
25
|
}
|
|
17
26
|
|
|
18
27
|
const STATUS_LABEL: Record<SaveStatus, string> = {
|
|
@@ -29,6 +38,14 @@ export function EditorHeader({
|
|
|
29
38
|
onPublish,
|
|
30
39
|
onRetry,
|
|
31
40
|
headerSlots,
|
|
41
|
+
presets,
|
|
42
|
+
activePresetId,
|
|
43
|
+
publishedPresetId,
|
|
44
|
+
onSwitchPreset,
|
|
45
|
+
onCreatePreset,
|
|
46
|
+
onRenamePreset,
|
|
47
|
+
onDeletePreset,
|
|
48
|
+
onDuplicatePreset,
|
|
32
49
|
}: EditorHeaderProps) {
|
|
33
50
|
const tokens = useTokens();
|
|
34
51
|
const borderColor = srgbToHex(tokens.border.srgb);
|
|
@@ -54,6 +71,16 @@ export function EditorHeader({
|
|
|
54
71
|
>
|
|
55
72
|
<div style={{ display: "flex", alignItems: "center", gap: 16 }}>
|
|
56
73
|
{headerSlots?.left}
|
|
74
|
+
<PresetSelector
|
|
75
|
+
presets={presets}
|
|
76
|
+
activePresetId={activePresetId}
|
|
77
|
+
publishedPresetId={publishedPresetId}
|
|
78
|
+
onSwitchPreset={onSwitchPreset}
|
|
79
|
+
onCreatePreset={onCreatePreset}
|
|
80
|
+
onRenamePreset={onRenamePreset}
|
|
81
|
+
onDeletePreset={onDeletePreset}
|
|
82
|
+
onDuplicatePreset={onDuplicatePreset}
|
|
83
|
+
/>
|
|
57
84
|
</div>
|
|
58
85
|
<div style={{ display: "flex", alignItems: "center", gap: 12 }}>
|
|
59
86
|
<span
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
1
2
|
import { useState } from "react";
|
|
2
3
|
import { useTokens, Icon, CATEGORIES } from "@newtonedev/components";
|
|
3
4
|
import { srgbToHex } from "newtone";
|
|
@@ -5,11 +6,12 @@ import { srgbToHex } from "newtone";
|
|
|
5
6
|
interface PrimaryNavProps {
|
|
6
7
|
readonly activeSectionId: string | null;
|
|
7
8
|
readonly onSelectSection: (sectionId: string) => void;
|
|
9
|
+
readonly footer?: ReactNode;
|
|
8
10
|
}
|
|
9
11
|
|
|
10
12
|
const NAV_WIDTH = 60;
|
|
11
13
|
|
|
12
|
-
export function PrimaryNav({ activeSectionId, onSelectSection }: PrimaryNavProps) {
|
|
14
|
+
export function PrimaryNav({ activeSectionId, onSelectSection, footer }: PrimaryNavProps) {
|
|
13
15
|
const tokens = useTokens();
|
|
14
16
|
const [hoveredId, setHoveredId] = useState<string | null>(null);
|
|
15
17
|
|
|
@@ -71,6 +73,11 @@ export function PrimaryNav({ activeSectionId, onSelectSection }: PrimaryNavProps
|
|
|
71
73
|
</button>
|
|
72
74
|
);
|
|
73
75
|
})}
|
|
76
|
+
{footer && (
|
|
77
|
+
<div style={{ marginTop: "auto", paddingBottom: 12 }}>
|
|
78
|
+
{footer}
|
|
79
|
+
</div>
|
|
80
|
+
)}
|
|
74
81
|
</nav>
|
|
75
82
|
);
|
|
76
83
|
}
|
|
@@ -1,37 +1,16 @@
|
|
|
1
1
|
import { useTokens } from "@newtonedev/components";
|
|
2
2
|
import { srgbToHex } from "newtone";
|
|
3
|
-
import { PresetSelector } from "./PresetSelector";
|
|
4
|
-
import type { Preset } from "../types";
|
|
5
3
|
|
|
6
4
|
const SIDEBAR_WIDTH = 360;
|
|
7
5
|
|
|
8
6
|
interface SidebarProps {
|
|
9
7
|
readonly isDirty: boolean;
|
|
10
8
|
readonly onRevert: () => void;
|
|
11
|
-
readonly presets: readonly Preset[];
|
|
12
|
-
readonly activePresetId: string;
|
|
13
|
-
readonly publishedPresetId: string | null;
|
|
14
|
-
readonly onSwitchPreset: (presetId: string) => void;
|
|
15
|
-
readonly onCreatePreset: (name: string) => Promise<string>;
|
|
16
|
-
readonly onRenamePreset: (presetId: string, name: string) => void;
|
|
17
|
-
readonly onDeletePreset: (presetId: string) => Promise<void>;
|
|
18
|
-
readonly onDuplicatePreset: (
|
|
19
|
-
presetId: string,
|
|
20
|
-
name: string,
|
|
21
|
-
) => Promise<string>;
|
|
22
9
|
}
|
|
23
10
|
|
|
24
11
|
export function Sidebar({
|
|
25
12
|
isDirty,
|
|
26
13
|
onRevert,
|
|
27
|
-
presets,
|
|
28
|
-
activePresetId,
|
|
29
|
-
publishedPresetId,
|
|
30
|
-
onSwitchPreset,
|
|
31
|
-
onCreatePreset,
|
|
32
|
-
onRenamePreset,
|
|
33
|
-
onDeletePreset,
|
|
34
|
-
onDuplicatePreset,
|
|
35
14
|
}: SidebarProps) {
|
|
36
15
|
const tokens = useTokens();
|
|
37
16
|
|
|
@@ -58,7 +37,6 @@ export function Sidebar({
|
|
|
58
37
|
borderBottom: `1px solid ${borderColor}`,
|
|
59
38
|
display: "flex",
|
|
60
39
|
alignItems: "center",
|
|
61
|
-
justifyContent: "space-between",
|
|
62
40
|
}}
|
|
63
41
|
>
|
|
64
42
|
<span
|
|
@@ -70,16 +48,6 @@ export function Sidebar({
|
|
|
70
48
|
>
|
|
71
49
|
newtone
|
|
72
50
|
</span>
|
|
73
|
-
<PresetSelector
|
|
74
|
-
presets={presets}
|
|
75
|
-
activePresetId={activePresetId}
|
|
76
|
-
publishedPresetId={publishedPresetId}
|
|
77
|
-
onSwitchPreset={onSwitchPreset}
|
|
78
|
-
onCreatePreset={onCreatePreset}
|
|
79
|
-
onRenamePreset={onRenamePreset}
|
|
80
|
-
onDeletePreset={onDeletePreset}
|
|
81
|
-
onDuplicatePreset={onDuplicatePreset}
|
|
82
|
-
/>
|
|
83
51
|
</div>
|
|
84
52
|
|
|
85
53
|
{/* Content area (empty for now) */}
|
|
@@ -53,11 +53,13 @@ export function useEditorState({
|
|
|
53
53
|
const [colorMode, setColorMode] = useState<ColorMode>(
|
|
54
54
|
initialState.preview.mode,
|
|
55
55
|
);
|
|
56
|
+
const defaultCategoryId = CATEGORIES[0]?.id ?? "colors";
|
|
56
57
|
const [previewView, setPreviewView] = useState<PreviewView>(
|
|
57
|
-
initialPreviewView ?? { kind: "
|
|
58
|
+
initialPreviewView ?? { kind: "category", categoryId: defaultCategoryId },
|
|
58
59
|
);
|
|
59
60
|
const [activeSectionId, setActiveSectionId] = useState<string>(
|
|
60
|
-
|
|
61
|
+
(initialPreviewView?.kind === "category" ? initialPreviewView.categoryId : undefined)
|
|
62
|
+
?? defaultCategoryId,
|
|
61
63
|
);
|
|
62
64
|
const [sidebarSelection, setSidebarSelection] =
|
|
63
65
|
useState<SidebarSelection>(null);
|
|
@@ -187,13 +189,14 @@ export function useEditorState({
|
|
|
187
189
|
setActiveSectionId(sectionId);
|
|
188
190
|
|
|
189
191
|
const sectionComponents = getComponentsByCategory(sectionId);
|
|
192
|
+
// Always notify with the category view so the URL reflects the section.
|
|
193
|
+
onNavigate?.({ kind: "category", categoryId: sectionId });
|
|
194
|
+
|
|
190
195
|
if (sectionComponents.length === 1) {
|
|
191
196
|
// Single-component sections (e.g. Typography, Symbols) skip
|
|
192
197
|
// the category overview and navigate directly to the component.
|
|
193
198
|
const comp = sectionComponents[0];
|
|
194
|
-
|
|
195
|
-
setPreviewView(view);
|
|
196
|
-
onNavigate?.(view);
|
|
199
|
+
setPreviewView({ kind: "component", componentId: comp.id });
|
|
197
200
|
const firstVariantId = comp.variants[0]?.id;
|
|
198
201
|
setSidebarSelection(
|
|
199
202
|
firstVariantId
|
|
@@ -204,7 +207,6 @@ export function useEditorState({
|
|
|
204
207
|
} else {
|
|
205
208
|
const view: PreviewView = { kind: "category", categoryId: sectionId };
|
|
206
209
|
setPreviewView(view);
|
|
207
|
-
onNavigate?.(view);
|
|
208
210
|
setSidebarSelection(null);
|
|
209
211
|
setPropOverrides({});
|
|
210
212
|
}
|
|
@@ -314,26 +316,38 @@ export function useEditorState({
|
|
|
314
316
|
if (debounceRef.current) clearTimeout(debounceRef.current);
|
|
315
317
|
setPublishing(true);
|
|
316
318
|
|
|
319
|
+
const timeout = new Promise<never>((_, reject) =>
|
|
320
|
+
setTimeout(() => reject(new Error('Publish timed out after 15 s')), 15_000),
|
|
321
|
+
);
|
|
322
|
+
|
|
317
323
|
try {
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
324
|
+
await Promise.race([
|
|
325
|
+
(async () => {
|
|
326
|
+
const currentState = latestStateRef.current;
|
|
327
|
+
const updatedPresets = publishActivePreset(currentState);
|
|
328
|
+
const [calibrations, fontMetrics] = await Promise.all([
|
|
329
|
+
measureFontCalibrations(currentState.typography?.fonts),
|
|
330
|
+
lookupFontMetrics(currentState.typography?.fonts, manifestUrl),
|
|
331
|
+
]);
|
|
332
|
+
|
|
333
|
+
const { error } = await persistence.onPublish({
|
|
334
|
+
state: currentState,
|
|
335
|
+
presets: updatedPresets,
|
|
336
|
+
activePresetId,
|
|
337
|
+
calibrations,
|
|
338
|
+
fontMetrics,
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
if (!error) {
|
|
342
|
+
setSaveStatus("saved");
|
|
343
|
+
setIsPublished(true);
|
|
344
|
+
}
|
|
345
|
+
})(),
|
|
346
|
+
timeout,
|
|
323
347
|
]);
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
presets: updatedPresets,
|
|
328
|
-
activePresetId,
|
|
329
|
-
calibrations,
|
|
330
|
-
fontMetrics,
|
|
331
|
-
});
|
|
332
|
-
|
|
333
|
-
if (!error) {
|
|
334
|
-
setSaveStatus("saved");
|
|
335
|
-
setIsPublished(true);
|
|
336
|
-
}
|
|
348
|
+
} catch (err) {
|
|
349
|
+
// Log publish failures (timeout, network, RLS) for debugging
|
|
350
|
+
console.error('[Editor] Publish failed:', err);
|
|
337
351
|
} finally {
|
|
338
352
|
setPublishing(false);
|
|
339
353
|
}
|
|
@@ -296,7 +296,7 @@ export function ComponentDetailView({
|
|
|
296
296
|
scopeFontMap,
|
|
297
297
|
}: ComponentDetailViewProps) {
|
|
298
298
|
const tokens = useTokens();
|
|
299
|
-
const { config } = useNewtoneTheme();
|
|
299
|
+
const { config, mode } = useNewtoneTheme();
|
|
300
300
|
const component = getComponent(componentId);
|
|
301
301
|
const [hoveredId, setHoveredId] = useState<string | null>(null);
|
|
302
302
|
const [previewBreakpoint, setPreviewBreakpoint] = useState<BreakpointKey>("lg");
|
|
@@ -436,7 +436,7 @@ export function ComponentDetailView({
|
|
|
436
436
|
)}
|
|
437
437
|
|
|
438
438
|
{component.previewLayout === "list" ? (
|
|
439
|
-
<NewtoneProvider config={scaledConfig}>
|
|
439
|
+
<NewtoneProvider config={scaledConfig} initialMode={mode} key={mode}>
|
|
440
440
|
<div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
|
|
441
441
|
{component.variants.map((variant) => {
|
|
442
442
|
const isSelected = selectedVariantId === variant.id;
|
package/src/types.ts
CHANGED
|
@@ -81,6 +81,8 @@ export interface EditorProps {
|
|
|
81
81
|
readonly chromeThemeConfig: NewtoneThemeConfig;
|
|
82
82
|
readonly persistence: EditorPersistence;
|
|
83
83
|
readonly headerSlots?: EditorHeaderSlots;
|
|
84
|
+
/** Content rendered at the bottom of the left navigation rail. */
|
|
85
|
+
readonly navFooter?: ReactNode;
|
|
84
86
|
readonly onNavigate?: (view: PreviewView) => void;
|
|
85
87
|
readonly initialPreviewView?: PreviewView;
|
|
86
88
|
/** URL of the font manifest for metrics lookup at publish time. */
|
|
@@ -31,6 +31,7 @@ export async function lookupFontMetrics(
|
|
|
31
31
|
const seen = new Set<string>();
|
|
32
32
|
|
|
33
33
|
for (const slot of Object.values(fonts) as FontSlot[]) {
|
|
34
|
+
if (!slot?.config) continue;
|
|
34
35
|
const family = slot.config.family;
|
|
35
36
|
if (seen.has(family)) continue;
|
|
36
37
|
seen.add(family);
|
|
@@ -25,13 +25,14 @@ export async function measureFontCalibrations(
|
|
|
25
25
|
const seen = new Set<string>();
|
|
26
26
|
|
|
27
27
|
for (const slot of Object.values(fonts) as FontSlot[]) {
|
|
28
|
+
if (!slot?.config) continue;
|
|
28
29
|
const { family, fallback } = slot.config;
|
|
29
30
|
if (seen.has(family)) continue;
|
|
30
31
|
seen.add(family);
|
|
31
32
|
|
|
32
33
|
const ratio = await measureAvgCharWidth(
|
|
33
34
|
family,
|
|
34
|
-
slot.weights
|
|
35
|
+
slot.weights?.regular ?? 400,
|
|
35
36
|
fallback,
|
|
36
37
|
);
|
|
37
38
|
calibrations[family] = ratio;
|