@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.
@@ -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,CA8B7C"}
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,CAoBjC"}
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@newtonedev/editor",
3
- "version": "0.1.8",
3
+ "version": "0.1.10",
4
4
  "description": "Shared color system editor for Newtone applications",
5
5
  "license": "MIT",
6
6
  "repository": {
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
@@ -23,7 +23,7 @@ interface ConfiguratorPanelProps {
23
23
  readonly fontCatalog?: readonly GoogleFontEntry[];
24
24
  }
25
25
 
26
- const PANEL_WIDTH = 280;
26
+ const PANEL_WIDTH = 360;
27
27
 
28
28
  export function ConfiguratorPanel({
29
29
  activeSectionId,
@@ -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) */}
@@ -13,7 +13,7 @@ interface TableOfContentsProps {
13
13
  readonly onNavigate: (view: PreviewView) => void;
14
14
  }
15
15
 
16
- const TOC_WIDTH = 220;
16
+ const TOC_WIDTH = 360;
17
17
 
18
18
  export function TableOfContents({
19
19
  activeSectionId,
@@ -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: "overview" },
58
+ initialPreviewView ?? { kind: "category", categoryId: defaultCategoryId },
58
59
  );
59
60
  const [activeSectionId, setActiveSectionId] = useState<string>(
60
- CATEGORIES[0]?.id ?? "colors",
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
- const view: PreviewView = { kind: "component", componentId: comp.id };
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
- 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),
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
- const { error } = await persistence.onPublish({
326
- state: currentState,
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.regular,
35
+ slot.weights?.regular ?? 400,
35
36
  fallback,
36
37
  );
37
38
  calibrations[family] = ratio;