@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.
- 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 +2464 -2046
- 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 +2466 -2049
- 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 +54 -6
- 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,10 +1,16 @@
|
|
|
1
|
+
import type { TextRole } from "@newtonedev/fonts";
|
|
2
|
+
import type { EditorFontEntry } from "../types";
|
|
1
3
|
interface ComponentDetailViewProps {
|
|
2
4
|
readonly componentId: string;
|
|
3
5
|
readonly selectedVariantId: string | null;
|
|
4
6
|
readonly onSelectVariant: (variantId: string) => void;
|
|
5
7
|
readonly propOverrides?: Record<string, unknown>;
|
|
6
8
|
readonly onPropOverride?: (name: string, value: unknown) => void;
|
|
9
|
+
readonly roleWeights?: Partial<Record<TextRole, number>>;
|
|
10
|
+
readonly onRoleWeightChange?: (role: TextRole, weight: number) => void;
|
|
11
|
+
readonly fontCatalog?: readonly EditorFontEntry[];
|
|
12
|
+
readonly scopeFontMap?: Record<string, string>;
|
|
7
13
|
}
|
|
8
|
-
export declare function ComponentDetailView({ componentId, selectedVariantId, onSelectVariant, propOverrides, onPropOverride, }: ComponentDetailViewProps): import("react/jsx-runtime").JSX.Element | null;
|
|
14
|
+
export declare function ComponentDetailView({ componentId, selectedVariantId, onSelectVariant, propOverrides, onPropOverride, roleWeights, onRoleWeightChange, fontCatalog, scopeFontMap, }: ComponentDetailViewProps): import("react/jsx-runtime").JSX.Element | null;
|
|
9
15
|
export {};
|
|
10
16
|
//# sourceMappingURL=ComponentDetailView.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ComponentDetailView.d.ts","sourceRoot":"","sources":["../../src/preview/ComponentDetailView.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ComponentDetailView.d.ts","sourceRoot":"","sources":["../../src/preview/ComponentDetailView.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,QAAQ,EAAuC,MAAM,mBAAmB,CAAC;AAIvF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAahD,UAAU,wBAAwB;IAChC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1C,QAAQ,CAAC,eAAe,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IACtD,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjD,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IACjE,QAAQ,CAAC,WAAW,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC;IACzD,QAAQ,CAAC,kBAAkB,CAAC,EAAE,CAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACvE,QAAQ,CAAC,WAAW,CAAC,EAAE,SAAS,eAAe,EAAE,CAAC;IAClD,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAChD;AA+PD,wBAAgB,mBAAmB,CAAC,EAClC,WAAW,EACX,iBAAiB,EACjB,eAAe,EACf,aAAa,EACb,cAAc,EACd,WAAW,EACX,kBAAkB,EAClB,WAAW,EACX,YAAY,GACb,EAAE,wBAAwB,kDA4U1B"}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
interface ComponentRendererProps {
|
|
2
2
|
readonly componentId: string;
|
|
3
3
|
readonly props: Record<string, unknown>;
|
|
4
|
+
readonly previewText?: string;
|
|
4
5
|
}
|
|
5
|
-
export declare function ComponentRenderer({ componentId, props }: ComponentRendererProps): import("react/jsx-runtime").JSX.Element | null;
|
|
6
|
+
export declare function ComponentRenderer({ componentId, props, previewText }: ComponentRendererProps): import("react/jsx-runtime").JSX.Element | null;
|
|
6
7
|
export {};
|
|
7
8
|
//# sourceMappingURL=ComponentRenderer.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ComponentRenderer.d.ts","sourceRoot":"","sources":["../../src/preview/ComponentRenderer.tsx"],"names":[],"mappings":"AAiBA,UAAU,sBAAsB;IAC9B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"ComponentRenderer.d.ts","sourceRoot":"","sources":["../../src/preview/ComponentRenderer.tsx"],"names":[],"mappings":"AAiBA,UAAU,sBAAsB;IAC9B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACxC,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC/B;AA2ED,wBAAgB,iBAAiB,CAAC,EAAE,WAAW,EAAE,KAAK,EAAE,WAAW,EAAE,EAAE,sBAAsB,kDA+D5F"}
|
package/dist/types.d.ts
CHANGED
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
import type { ConfiguratorState } from "@newtonedev/configurator";
|
|
2
2
|
import type { NewtoneThemeConfig } from "@newtonedev/components";
|
|
3
|
+
import type { FontRuntimeMetrics, GoogleFontEntry } from "@newtonedev/fonts";
|
|
3
4
|
import type { ReactNode } from "react";
|
|
5
|
+
/** Font catalog entry enriched with weight metadata for the editor. */
|
|
6
|
+
export interface EditorFontEntry extends GoogleFontEntry {
|
|
7
|
+
readonly isVariable?: boolean;
|
|
8
|
+
readonly availableWeights?: readonly number[];
|
|
9
|
+
/** Weight axis range for variable fonts (from Google Fonts API wght axis). */
|
|
10
|
+
readonly weightAxisRange?: {
|
|
11
|
+
readonly min: number;
|
|
12
|
+
readonly max: number;
|
|
13
|
+
};
|
|
14
|
+
}
|
|
4
15
|
export interface Preset {
|
|
5
16
|
readonly id: string;
|
|
6
17
|
readonly name: string;
|
|
@@ -38,6 +49,8 @@ export interface EditorPersistence {
|
|
|
38
49
|
readonly state: ConfiguratorState;
|
|
39
50
|
readonly presets: readonly Preset[];
|
|
40
51
|
readonly activePresetId: string;
|
|
52
|
+
readonly calibrations?: Record<string, number>;
|
|
53
|
+
readonly fontMetrics?: Record<string, FontRuntimeMetrics>;
|
|
41
54
|
}) => Promise<{
|
|
42
55
|
error?: unknown;
|
|
43
56
|
}>;
|
|
@@ -64,5 +77,9 @@ export interface EditorProps {
|
|
|
64
77
|
readonly headerSlots?: EditorHeaderSlots;
|
|
65
78
|
readonly onNavigate?: (view: PreviewView) => void;
|
|
66
79
|
readonly initialPreviewView?: PreviewView;
|
|
80
|
+
/** URL of the font manifest for metrics lookup at publish time. */
|
|
81
|
+
readonly manifestUrl?: string;
|
|
82
|
+
/** Curated fonts available in the font picker, enriched with weight metadata. */
|
|
83
|
+
readonly fontCatalog?: readonly EditorFontEntry[];
|
|
67
84
|
}
|
|
68
85
|
//# sourceMappingURL=types.d.ts.map
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AACjE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { FontScope } from '@newtonedev/fonts';
|
|
2
|
+
import type { FontSlot } from '@newtonedev/components';
|
|
3
|
+
import type { FontRuntimeMetrics } from '@newtonedev/fonts';
|
|
4
|
+
/**
|
|
5
|
+
* Look up FontRuntimeMetrics for all font scopes from the font manifest.
|
|
6
|
+
*
|
|
7
|
+
* Called at publish time alongside measureFontCalibrations. Fetches the
|
|
8
|
+
* manifest JSON from the given URL and extracts metrics for each unique
|
|
9
|
+
* font family in the current typography configuration.
|
|
10
|
+
*
|
|
11
|
+
* Deduplicates by family name so a font used in multiple scopes is
|
|
12
|
+
* looked up only once. Returns empty object if manifest is unavailable.
|
|
13
|
+
*
|
|
14
|
+
* @param fonts - The typography.fonts record from ConfiguratorState.
|
|
15
|
+
* @param manifestUrl - URL of the font manifest (e.g., Supabase Storage public URL).
|
|
16
|
+
* @returns Map of fontFamily → FontRuntimeMetrics.
|
|
17
|
+
*/
|
|
18
|
+
export declare function lookupFontMetrics(fonts: Record<FontScope, FontSlot> | undefined, manifestUrl: string | undefined): Promise<Record<string, FontRuntimeMetrics>>;
|
|
19
|
+
//# sourceMappingURL=lookupFontMetrics.d.ts.map
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { FontScope } from '@newtonedev/fonts';
|
|
2
|
+
import type { FontSlot } from '@newtonedev/components';
|
|
3
|
+
/**
|
|
4
|
+
* Measure avgCharWidth ratios for all font scopes at publish time.
|
|
5
|
+
*
|
|
6
|
+
* Deduplicates by font family name so a font used in multiple scopes is
|
|
7
|
+
* measured only once. Waits for fonts to load via `document.fonts.ready`
|
|
8
|
+
* before measuring, since the editor always preloads fonts for preview.
|
|
9
|
+
*
|
|
10
|
+
* Called in `handlePublish` before writing to persistence so that calibration
|
|
11
|
+
* data is included in the published config served to consumer sites.
|
|
12
|
+
*
|
|
13
|
+
* @param fonts - The typography.fonts record from ConfiguratorState.
|
|
14
|
+
* @returns Map of fontFamily → avgCharWidthRatio (e.g. `{ "Inter": 0.52 }`).
|
|
15
|
+
* Returns empty object if called outside a browser context.
|
|
16
|
+
*/
|
|
17
|
+
export declare function measureFontCalibrations(fonts: Record<FontScope, FontSlot> | undefined): Promise<Record<string, number>>;
|
|
18
|
+
//# sourceMappingURL=measureFonts.d.ts.map
|
|
@@ -0,0 +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"}
|
package/package.json
CHANGED
package/src/Editor.tsx
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
|
-
import { useMemo } from "react";
|
|
1
|
+
import { useMemo, useCallback } from "react";
|
|
2
2
|
import { NewtoneProvider } from "@newtonedev/components";
|
|
3
3
|
import type { NewtoneThemeConfig } from "@newtonedev/components";
|
|
4
|
+
import type { TextRole } from "@newtonedev/fonts";
|
|
4
5
|
import { useEditorState } from "./hooks/useEditorState";
|
|
5
6
|
import { EditorShell } from "./components/EditorShell";
|
|
6
7
|
import { Sidebar } from "./components/Sidebar";
|
|
7
8
|
import { EditorHeader } from "./components/EditorHeader";
|
|
9
|
+
import { PrimaryNav } from "./components/PrimaryNav";
|
|
10
|
+
import { ConfiguratorPanel } from "./components/ConfiguratorPanel";
|
|
8
11
|
import { TableOfContents } from "./components/TableOfContents";
|
|
9
12
|
import { PreviewWindow } from "./components/PreviewWindow";
|
|
10
13
|
import { RightSidebar } from "./components/RightSidebar";
|
|
@@ -22,6 +25,8 @@ export function Editor({
|
|
|
22
25
|
headerSlots,
|
|
23
26
|
onNavigate,
|
|
24
27
|
initialPreviewView,
|
|
28
|
+
manifestUrl,
|
|
29
|
+
fontCatalog,
|
|
25
30
|
}: EditorProps) {
|
|
26
31
|
const editor = useEditorState({
|
|
27
32
|
initialState,
|
|
@@ -33,8 +38,30 @@ export function Editor({
|
|
|
33
38
|
persistence,
|
|
34
39
|
onNavigate,
|
|
35
40
|
initialPreviewView,
|
|
41
|
+
manifestUrl,
|
|
36
42
|
});
|
|
37
43
|
|
|
44
|
+
// Per-role weight state and handler
|
|
45
|
+
const roleWeights = editor.configuratorState.typography?.roleWeights;
|
|
46
|
+
const handleRoleWeightChange = useCallback(
|
|
47
|
+
(role: TextRole, weight: number) => {
|
|
48
|
+
editor.dispatch({ type: "SET_ROLE_WEIGHT", role, weight });
|
|
49
|
+
},
|
|
50
|
+
[editor.dispatch],
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
// Map font scopes to family names for weight metadata lookup
|
|
54
|
+
const scopeFontMap = useMemo((): Record<string, string> => {
|
|
55
|
+
const fonts = editor.configuratorState.typography?.fonts;
|
|
56
|
+
if (!fonts) return {};
|
|
57
|
+
const map: Record<string, string> = {};
|
|
58
|
+
if (fonts.main?.config?.family) map.main = fonts.main.config.family;
|
|
59
|
+
if (fonts.display?.config?.family) map.display = fonts.display.config.family;
|
|
60
|
+
if (fonts.mono?.config?.family) map.mono = fonts.mono.config.family;
|
|
61
|
+
if (fonts.currency?.config?.family) map.currency = fonts.currency.config.family;
|
|
62
|
+
return map;
|
|
63
|
+
}, [editor.configuratorState.typography?.fonts]);
|
|
64
|
+
|
|
38
65
|
// Merge token overrides from chrome config into the preview config.
|
|
39
66
|
// Token overrides (from Token Tuner) are stored separately from configurator
|
|
40
67
|
// state, but should apply to the preview so components render accurately.
|
|
@@ -51,9 +78,6 @@ export function Editor({
|
|
|
51
78
|
<EditorShell
|
|
52
79
|
sidebar={
|
|
53
80
|
<Sidebar
|
|
54
|
-
state={editor.configuratorState}
|
|
55
|
-
dispatch={editor.dispatch}
|
|
56
|
-
previewColors={editor.previewColors}
|
|
57
81
|
isDirty={editor.isDirty}
|
|
58
82
|
onRevert={editor.handleRevert}
|
|
59
83
|
presets={editor.presets}
|
|
@@ -64,8 +88,6 @@ export function Editor({
|
|
|
64
88
|
onRenamePreset={editor.renamePreset}
|
|
65
89
|
onDeletePreset={editor.deletePreset}
|
|
66
90
|
onDuplicatePreset={editor.duplicatePreset}
|
|
67
|
-
colorMode={editor.colorMode}
|
|
68
|
-
onColorModeChange={editor.handleColorModeChange}
|
|
69
91
|
/>
|
|
70
92
|
}
|
|
71
93
|
navbar={
|
|
@@ -87,11 +109,28 @@ export function Editor({
|
|
|
87
109
|
minWidth: 0,
|
|
88
110
|
}}
|
|
89
111
|
>
|
|
90
|
-
<
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
onNavigate={editor.handlePreviewNavigate}
|
|
112
|
+
<PrimaryNav
|
|
113
|
+
activeSectionId={editor.activeSectionId}
|
|
114
|
+
onSelectSection={editor.handleSectionChange}
|
|
94
115
|
/>
|
|
116
|
+
{editor.activeSectionId === "components" ? (
|
|
117
|
+
<TableOfContents
|
|
118
|
+
activeSectionId={editor.activeSectionId}
|
|
119
|
+
activeView={editor.previewView}
|
|
120
|
+
selectedComponentId={editor.selectedComponentId}
|
|
121
|
+
onNavigate={editor.handlePreviewNavigate}
|
|
122
|
+
/>
|
|
123
|
+
) : (
|
|
124
|
+
<ConfiguratorPanel
|
|
125
|
+
activeSectionId={editor.activeSectionId}
|
|
126
|
+
state={editor.configuratorState}
|
|
127
|
+
dispatch={editor.dispatch}
|
|
128
|
+
previewColors={editor.previewColors}
|
|
129
|
+
colorMode={editor.colorMode}
|
|
130
|
+
onColorModeChange={editor.handleColorModeChange}
|
|
131
|
+
fontCatalog={fontCatalog}
|
|
132
|
+
/>
|
|
133
|
+
)}
|
|
95
134
|
<div
|
|
96
135
|
style={{
|
|
97
136
|
flex: 1,
|
|
@@ -114,6 +153,10 @@ export function Editor({
|
|
|
114
153
|
onSelectVariant={editor.handleSelectVariant}
|
|
115
154
|
propOverrides={editor.propOverrides}
|
|
116
155
|
onPropOverride={editor.handlePropOverride}
|
|
156
|
+
roleWeights={roleWeights}
|
|
157
|
+
onRoleWeightChange={handleRoleWeightChange}
|
|
158
|
+
fontCatalog={fontCatalog}
|
|
159
|
+
scopeFontMap={scopeFontMap}
|
|
117
160
|
/>
|
|
118
161
|
</div>
|
|
119
162
|
</NewtoneProvider>
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { useTokens } from "@newtonedev/components";
|
|
2
|
+
import type { ColorMode } from "@newtonedev/components";
|
|
3
|
+
import { srgbToHex } from "newtone";
|
|
4
|
+
import type { ColorResult } from "newtone";
|
|
5
|
+
import type { ConfiguratorState } from "@newtonedev/configurator";
|
|
6
|
+
import type { ConfiguratorAction } from "@newtonedev/configurator";
|
|
7
|
+
import type { GoogleFontEntry } from "@newtonedev/fonts";
|
|
8
|
+
import {
|
|
9
|
+
ColorsSection,
|
|
10
|
+
DynamicRangeSection,
|
|
11
|
+
IconsSection,
|
|
12
|
+
FontsSection,
|
|
13
|
+
OthersSection,
|
|
14
|
+
} from "./sections";
|
|
15
|
+
|
|
16
|
+
interface ConfiguratorPanelProps {
|
|
17
|
+
readonly activeSectionId: string;
|
|
18
|
+
readonly state: ConfiguratorState;
|
|
19
|
+
readonly dispatch: (action: ConfiguratorAction) => void;
|
|
20
|
+
readonly previewColors: readonly (readonly ColorResult[])[];
|
|
21
|
+
readonly colorMode: ColorMode;
|
|
22
|
+
readonly onColorModeChange: (mode: ColorMode) => void;
|
|
23
|
+
readonly fontCatalog?: readonly GoogleFontEntry[];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const PANEL_WIDTH = 280;
|
|
27
|
+
|
|
28
|
+
export function ConfiguratorPanel({
|
|
29
|
+
activeSectionId,
|
|
30
|
+
state,
|
|
31
|
+
dispatch,
|
|
32
|
+
previewColors,
|
|
33
|
+
colorMode,
|
|
34
|
+
onColorModeChange,
|
|
35
|
+
fontCatalog,
|
|
36
|
+
}: ConfiguratorPanelProps) {
|
|
37
|
+
const tokens = useTokens();
|
|
38
|
+
const borderColor = srgbToHex(tokens.border.srgb);
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<div
|
|
42
|
+
style={{
|
|
43
|
+
width: PANEL_WIDTH,
|
|
44
|
+
flexShrink: 0,
|
|
45
|
+
overflowY: "auto",
|
|
46
|
+
borderRight: `1px solid ${borderColor}`,
|
|
47
|
+
padding: 20,
|
|
48
|
+
backgroundColor: srgbToHex(tokens.background.srgb),
|
|
49
|
+
display: "flex",
|
|
50
|
+
flexDirection: "column",
|
|
51
|
+
gap: 24,
|
|
52
|
+
}}
|
|
53
|
+
>
|
|
54
|
+
{activeSectionId === "colors" && (
|
|
55
|
+
<>
|
|
56
|
+
<DynamicRangeSection state={state} dispatch={dispatch} />
|
|
57
|
+
<ColorsSection
|
|
58
|
+
state={state}
|
|
59
|
+
dispatch={dispatch}
|
|
60
|
+
previewColors={previewColors}
|
|
61
|
+
colorMode={colorMode}
|
|
62
|
+
onColorModeChange={onColorModeChange}
|
|
63
|
+
/>
|
|
64
|
+
</>
|
|
65
|
+
)}
|
|
66
|
+
{activeSectionId === "typography" && (
|
|
67
|
+
<FontsSection state={state} dispatch={dispatch} fontCatalog={fontCatalog} />
|
|
68
|
+
)}
|
|
69
|
+
{activeSectionId === "symbols" && (
|
|
70
|
+
<IconsSection state={state} dispatch={dispatch} />
|
|
71
|
+
)}
|
|
72
|
+
{activeSectionId === "layout" && (
|
|
73
|
+
<OthersSection state={state} dispatch={dispatch} />
|
|
74
|
+
)}
|
|
75
|
+
</div>
|
|
76
|
+
);
|
|
77
|
+
}
|
|
@@ -1,27 +1,29 @@
|
|
|
1
1
|
import { useState, useRef, useEffect, useMemo, useCallback } from "react";
|
|
2
2
|
import { useTokens } from "@newtonedev/components";
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
} from "@newtonedev/components";
|
|
7
|
-
import type { GoogleFontEntry, SystemFontEntry, FontConfig } from "@newtonedev/components";
|
|
3
|
+
import type { FontConfig } from "@newtonedev/components";
|
|
4
|
+
import { SYSTEM_FONTS } from "@newtonedev/fonts";
|
|
5
|
+
import type { GoogleFontEntry, SystemFontEntry } from "@newtonedev/fonts";
|
|
8
6
|
import { srgbToHex } from "newtone";
|
|
9
7
|
|
|
10
|
-
type FontSlot = "default" | "display" | "mono";
|
|
8
|
+
type FontSlot = "default" | "display" | "mono" | "currency";
|
|
11
9
|
|
|
12
10
|
interface FontPickerProps {
|
|
13
11
|
readonly label: string;
|
|
14
12
|
readonly slot: FontSlot;
|
|
15
13
|
readonly currentFont: FontConfig;
|
|
16
14
|
readonly onSelect: (font: FontConfig) => void;
|
|
15
|
+
readonly fontCatalog?: readonly GoogleFontEntry[];
|
|
17
16
|
}
|
|
18
17
|
|
|
19
|
-
/** Preload
|
|
20
|
-
let
|
|
21
|
-
function preloadFontsForPreview() {
|
|
22
|
-
if (
|
|
23
|
-
|
|
24
|
-
const
|
|
18
|
+
/** Preload curated Google Fonts for preview on first dropdown open. */
|
|
19
|
+
let previewLoadedKey = "";
|
|
20
|
+
function preloadFontsForPreview(catalog: readonly GoogleFontEntry[]) {
|
|
21
|
+
if (catalog.length === 0 || typeof document === "undefined") return;
|
|
22
|
+
// Re-preload if the catalog changes (e.g. new fonts curated)
|
|
23
|
+
const key = catalog.map((f) => f.family).join(",");
|
|
24
|
+
if (key === previewLoadedKey) return;
|
|
25
|
+
previewLoadedKey = key;
|
|
26
|
+
const families = catalog.map(
|
|
25
27
|
(f) => `family=${f.family.replace(/ /g, "+")}:wght@400`,
|
|
26
28
|
).join("&");
|
|
27
29
|
const url = `https://fonts.googleapis.com/css2?${families}&display=swap`;
|
|
@@ -60,18 +62,13 @@ const CATEGORY_ORDER: readonly string[] = [
|
|
|
60
62
|
"monospace",
|
|
61
63
|
"display",
|
|
62
64
|
];
|
|
63
|
-
const MONO_CATEGORY_ORDER: readonly string[] = [
|
|
64
|
-
"monospace",
|
|
65
|
-
"sans-serif",
|
|
66
|
-
"serif",
|
|
67
|
-
"display",
|
|
68
|
-
];
|
|
69
65
|
|
|
70
66
|
export function FontPicker({
|
|
71
67
|
label,
|
|
72
68
|
slot,
|
|
73
69
|
currentFont,
|
|
74
70
|
onSelect,
|
|
71
|
+
fontCatalog = [],
|
|
75
72
|
}: FontPickerProps) {
|
|
76
73
|
const tokens = useTokens();
|
|
77
74
|
const [isOpen, setIsOpen] = useState(false);
|
|
@@ -103,35 +100,47 @@ export function FontPicker({
|
|
|
103
100
|
|
|
104
101
|
useEffect(() => {
|
|
105
102
|
if (isOpen) {
|
|
106
|
-
preloadFontsForPreview();
|
|
103
|
+
preloadFontsForPreview(fontCatalog);
|
|
107
104
|
requestAnimationFrame(() => searchInputRef.current?.focus());
|
|
108
105
|
}
|
|
109
|
-
}, [isOpen]);
|
|
110
|
-
|
|
111
|
-
const categoryOrder = slot === "mono" ? MONO_CATEGORY_ORDER : CATEGORY_ORDER;
|
|
106
|
+
}, [isOpen, fontCatalog]);
|
|
112
107
|
|
|
113
108
|
const filteredGoogleFonts = useMemo(() => {
|
|
114
109
|
const query = search.toLowerCase().trim();
|
|
115
|
-
|
|
116
|
-
?
|
|
117
|
-
:
|
|
110
|
+
let fonts: readonly GoogleFontEntry[] = query
|
|
111
|
+
? fontCatalog.filter((f) => f.family.toLowerCase().includes(query))
|
|
112
|
+
: fontCatalog;
|
|
113
|
+
|
|
114
|
+
if (slot === "mono") {
|
|
115
|
+
fonts = fonts.filter((f) => f.category === "monospace");
|
|
116
|
+
} else if (slot === "currency") {
|
|
117
|
+
fonts = fonts.filter((f) => f.category === "monospace" || f.category === "sans-serif");
|
|
118
|
+
}
|
|
118
119
|
|
|
119
120
|
const grouped: Record<string, GoogleFontEntry[]> = {};
|
|
120
|
-
for (const cat of
|
|
121
|
+
for (const cat of CATEGORY_ORDER) {
|
|
121
122
|
const inCategory = fonts.filter((f) => f.category === cat);
|
|
122
123
|
if (inCategory.length > 0) {
|
|
123
124
|
grouped[cat] = inCategory;
|
|
124
125
|
}
|
|
125
126
|
}
|
|
126
127
|
return grouped;
|
|
127
|
-
}, [search,
|
|
128
|
+
}, [search, slot, fontCatalog]);
|
|
128
129
|
|
|
129
130
|
const filteredSystemFonts = useMemo(() => {
|
|
130
131
|
const query = search.toLowerCase().trim();
|
|
131
|
-
|
|
132
|
+
let fonts = query
|
|
132
133
|
? SYSTEM_FONTS.filter((f) => f.family.toLowerCase().includes(query))
|
|
133
134
|
: [...SYSTEM_FONTS];
|
|
134
|
-
|
|
135
|
+
|
|
136
|
+
if (slot === "mono") {
|
|
137
|
+
fonts = fonts.filter((f) => f.category === "monospace");
|
|
138
|
+
} else if (slot === "currency") {
|
|
139
|
+
fonts = fonts.filter((f) => f.category !== "serif");
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return fonts;
|
|
143
|
+
}, [search, slot]);
|
|
135
144
|
|
|
136
145
|
const handleSelect = useCallback(
|
|
137
146
|
(font: FontConfig) => {
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { useCallback } from "react";
|
|
2
2
|
import { useTokens } from "@newtonedev/components";
|
|
3
3
|
import { srgbToHex } from "newtone";
|
|
4
|
+
import type { TextRole } from "@newtonedev/fonts";
|
|
4
5
|
import { OverviewView } from "../preview/OverviewView";
|
|
5
6
|
import { CategoryView } from "../preview/CategoryView";
|
|
6
7
|
import { ComponentDetailView } from "../preview/ComponentDetailView";
|
|
7
|
-
import type { PreviewView } from "../types";
|
|
8
|
+
import type { PreviewView, EditorFontEntry } from "../types";
|
|
8
9
|
|
|
9
10
|
interface PreviewWindowProps {
|
|
10
11
|
readonly view: PreviewView;
|
|
@@ -13,6 +14,10 @@ interface PreviewWindowProps {
|
|
|
13
14
|
readonly onSelectVariant: (variantId: string) => void;
|
|
14
15
|
readonly propOverrides?: Record<string, unknown>;
|
|
15
16
|
readonly onPropOverride?: (name: string, value: unknown) => void;
|
|
17
|
+
readonly roleWeights?: Partial<Record<TextRole, number>>;
|
|
18
|
+
readonly onRoleWeightChange?: (role: TextRole, weight: number) => void;
|
|
19
|
+
readonly fontCatalog?: readonly EditorFontEntry[];
|
|
20
|
+
readonly scopeFontMap?: Record<string, string>;
|
|
16
21
|
}
|
|
17
22
|
|
|
18
23
|
export function PreviewWindow({
|
|
@@ -22,6 +27,10 @@ export function PreviewWindow({
|
|
|
22
27
|
onSelectVariant,
|
|
23
28
|
propOverrides,
|
|
24
29
|
onPropOverride,
|
|
30
|
+
roleWeights,
|
|
31
|
+
onRoleWeightChange,
|
|
32
|
+
fontCatalog,
|
|
33
|
+
scopeFontMap,
|
|
25
34
|
}: PreviewWindowProps) {
|
|
26
35
|
const tokens = useTokens();
|
|
27
36
|
|
|
@@ -64,6 +73,10 @@ export function PreviewWindow({
|
|
|
64
73
|
onSelectVariant={onSelectVariant}
|
|
65
74
|
propOverrides={propOverrides}
|
|
66
75
|
onPropOverride={onPropOverride}
|
|
76
|
+
roleWeights={roleWeights}
|
|
77
|
+
onRoleWeightChange={onRoleWeightChange}
|
|
78
|
+
fontCatalog={fontCatalog}
|
|
79
|
+
scopeFontMap={scopeFontMap}
|
|
67
80
|
/>
|
|
68
81
|
)}
|
|
69
82
|
</div>
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import { useTokens, Icon, CATEGORIES } from "@newtonedev/components";
|
|
3
|
+
import { srgbToHex } from "newtone";
|
|
4
|
+
|
|
5
|
+
interface PrimaryNavProps {
|
|
6
|
+
readonly activeSectionId: string | null;
|
|
7
|
+
readonly onSelectSection: (sectionId: string) => void;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const NAV_WIDTH = 60;
|
|
11
|
+
|
|
12
|
+
export function PrimaryNav({ activeSectionId, onSelectSection }: PrimaryNavProps) {
|
|
13
|
+
const tokens = useTokens();
|
|
14
|
+
const [hoveredId, setHoveredId] = useState<string | null>(null);
|
|
15
|
+
|
|
16
|
+
const bg = srgbToHex(tokens.background.srgb);
|
|
17
|
+
const borderColor = srgbToHex(tokens.border.srgb);
|
|
18
|
+
const activeBg = srgbToHex(tokens.backgroundInteractive.srgb);
|
|
19
|
+
const iconColor = srgbToHex(tokens.textSecondary.srgb);
|
|
20
|
+
const activeIconColor = srgbToHex(tokens.textPrimary.srgb);
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<nav
|
|
24
|
+
aria-label="Section navigation"
|
|
25
|
+
style={{
|
|
26
|
+
width: NAV_WIDTH,
|
|
27
|
+
flexShrink: 0,
|
|
28
|
+
display: "flex",
|
|
29
|
+
flexDirection: "column",
|
|
30
|
+
alignItems: "center",
|
|
31
|
+
paddingTop: 12,
|
|
32
|
+
gap: 4,
|
|
33
|
+
backgroundColor: bg,
|
|
34
|
+
borderRight: `1px solid ${borderColor}`,
|
|
35
|
+
}}
|
|
36
|
+
>
|
|
37
|
+
{CATEGORIES.map((category) => {
|
|
38
|
+
const isActive = activeSectionId === category.id;
|
|
39
|
+
const isHovered = hoveredId === category.id;
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<button
|
|
43
|
+
key={category.id}
|
|
44
|
+
onClick={() => onSelectSection(category.id)}
|
|
45
|
+
onMouseEnter={() => setHoveredId(category.id)}
|
|
46
|
+
onMouseLeave={() => setHoveredId(null)}
|
|
47
|
+
title={category.name}
|
|
48
|
+
aria-current={isActive ? "page" : undefined}
|
|
49
|
+
style={{
|
|
50
|
+
display: "flex",
|
|
51
|
+
alignItems: "center",
|
|
52
|
+
justifyContent: "center",
|
|
53
|
+
width: 44,
|
|
54
|
+
height: 44,
|
|
55
|
+
borderRadius: 12,
|
|
56
|
+
border: "none",
|
|
57
|
+
backgroundColor: isActive
|
|
58
|
+
? activeBg
|
|
59
|
+
: isHovered
|
|
60
|
+
? `${borderColor}20`
|
|
61
|
+
: "transparent",
|
|
62
|
+
cursor: "pointer",
|
|
63
|
+
transition: "background-color 150ms",
|
|
64
|
+
}}
|
|
65
|
+
>
|
|
66
|
+
<Icon
|
|
67
|
+
name={category.icon ?? "circle"}
|
|
68
|
+
size={24}
|
|
69
|
+
color={isActive ? activeIconColor : iconColor}
|
|
70
|
+
/>
|
|
71
|
+
</button>
|
|
72
|
+
);
|
|
73
|
+
})}
|
|
74
|
+
</nav>
|
|
75
|
+
);
|
|
76
|
+
}
|