@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.
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 +2464 -2046
  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 +2466 -2049
  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 +54 -6
  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") {
@@ -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";