@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.
Files changed (52) hide show
  1. package/dist/Editor.d.ts +1 -1
  2. package/dist/Editor.d.ts.map +1 -1
  3. package/dist/components/ConfiguratorPanel.d.ts +17 -0
  4. package/dist/components/ConfiguratorPanel.d.ts.map +1 -0
  5. package/dist/components/FontPicker.d.ts +4 -2
  6. package/dist/components/FontPicker.d.ts.map +1 -1
  7. package/dist/components/PreviewWindow.d.ts +7 -2
  8. package/dist/components/PreviewWindow.d.ts.map +1 -1
  9. package/dist/components/PrimaryNav.d.ts +7 -0
  10. package/dist/components/PrimaryNav.d.ts.map +1 -0
  11. package/dist/components/Sidebar.d.ts +1 -10
  12. package/dist/components/Sidebar.d.ts.map +1 -1
  13. package/dist/components/TableOfContents.d.ts +2 -1
  14. package/dist/components/TableOfContents.d.ts.map +1 -1
  15. package/dist/components/sections/DynamicRangeSection.d.ts.map +1 -1
  16. package/dist/components/sections/FontsSection.d.ts +3 -1
  17. package/dist/components/sections/FontsSection.d.ts.map +1 -1
  18. package/dist/hooks/useEditorState.d.ts +4 -1
  19. package/dist/hooks/useEditorState.d.ts.map +1 -1
  20. package/dist/index.cjs +2484 -2052
  21. package/dist/index.cjs.map +1 -1
  22. package/dist/index.d.ts +2 -1
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +2486 -2055
  25. package/dist/index.js.map +1 -1
  26. package/dist/preview/ComponentDetailView.d.ts +7 -1
  27. package/dist/preview/ComponentDetailView.d.ts.map +1 -1
  28. package/dist/preview/ComponentRenderer.d.ts +2 -1
  29. package/dist/preview/ComponentRenderer.d.ts.map +1 -1
  30. package/dist/types.d.ts +17 -0
  31. package/dist/types.d.ts.map +1 -1
  32. package/dist/utils/lookupFontMetrics.d.ts +19 -0
  33. package/dist/utils/lookupFontMetrics.d.ts.map +1 -0
  34. package/dist/utils/measureFonts.d.ts +18 -0
  35. package/dist/utils/measureFonts.d.ts.map +1 -0
  36. package/package.json +1 -1
  37. package/src/Editor.tsx +53 -10
  38. package/src/components/ConfiguratorPanel.tsx +77 -0
  39. package/src/components/FontPicker.tsx +38 -29
  40. package/src/components/PreviewWindow.tsx +14 -1
  41. package/src/components/PrimaryNav.tsx +76 -0
  42. package/src/components/Sidebar.tsx +5 -132
  43. package/src/components/TableOfContents.tsx +41 -78
  44. package/src/components/sections/DynamicRangeSection.tsx +2 -225
  45. package/src/components/sections/FontsSection.tsx +61 -93
  46. package/src/hooks/useEditorState.ts +68 -17
  47. package/src/index.ts +2 -0
  48. package/src/preview/ComponentDetailView.tsx +531 -67
  49. package/src/preview/ComponentRenderer.tsx +6 -4
  50. package/src/types.ts +15 -0
  51. package/src/utils/lookupFontMetrics.ts +52 -0
  52. package/src/utils/measureFonts.ts +41 -0
@@ -1,18 +1,13 @@
1
- import { Slider, useTokens } from "@newtonedev/components";
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 DEFAULT_FONT_DEFAULT: FontConfig = {
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
- export function FontsSection({ state, dispatch }: FontsSectionProps) {
34
- const tokens = useTokens();
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
- const baseSize = state.typography?.scale.baseSize ?? 16;
37
- const ratio = state.typography?.scale.ratio ?? 1.25;
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
- slot: "default" | "display" | "mono",
43
- font: FontConfig,
44
- ) => {
45
- const actionType = {
46
- default: "SET_FONT_DEFAULT",
47
- display: "SET_FONT_DISPLAY",
48
- mono: "SET_FONT_MONO",
49
- }[slot] as "SET_FONT_DEFAULT" | "SET_FONT_DISPLAY" | "SET_FONT_MONO";
50
- dispatch({ type: actionType, font });
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
- <Slider
70
- value={baseSize}
71
- onValueChange={(v) =>
72
- dispatch({ type: "SET_TYPOGRAPHY_BASE_SIZE", baseSize: v })
73
- }
74
- min={12}
75
- max={24}
76
- step={1}
77
- label="Base Size"
78
- showValue
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
- style={{
97
- fontSize: 11,
98
- fontWeight: 600,
99
- color: labelColor,
100
- textTransform: "uppercase",
101
- letterSpacing: 0.5,
102
- marginBottom: 8,
103
- }}
104
- >
105
- Fonts
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
- setSidebarSelection({
163
- scope: "component",
164
- componentId: view.componentId,
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
- const currentState = latestStateRef.current;
278
- const updatedPresets = publishActivePreset(currentState);
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
- const { error } = await persistence.onPublish({
281
- state: currentState,
282
- presets: updatedPresets,
283
- activePresetId,
284
- });
325
+ const { error } = await persistence.onPublish({
326
+ state: currentState,
327
+ presets: updatedPresets,
328
+ activePresetId,
329
+ calibrations,
330
+ fontMetrics,
331
+ });
285
332
 
286
- if (!error) {
287
- setSaveStatus("saved");
288
- setIsPublished(true);
333
+ if (!error) {
334
+ setSaveStatus("saved");
335
+ setIsPublished(true);
336
+ }
337
+ } finally {
338
+ setPublishing(false);
289
339
  }
290
- setPublishing(false);
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";