@newtonedev/editor 0.1.1

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 (86) hide show
  1. package/dist/Editor.d.ts +3 -0
  2. package/dist/Editor.d.ts.map +1 -0
  3. package/dist/components/CodeBlock.d.ts +7 -0
  4. package/dist/components/CodeBlock.d.ts.map +1 -0
  5. package/dist/components/EditorHeader.d.ts +16 -0
  6. package/dist/components/EditorHeader.d.ts.map +1 -0
  7. package/dist/components/EditorShell.d.ts +10 -0
  8. package/dist/components/EditorShell.d.ts.map +1 -0
  9. package/dist/components/FontPicker.d.ts +11 -0
  10. package/dist/components/FontPicker.d.ts.map +1 -0
  11. package/dist/components/PresetSelector.d.ts +14 -0
  12. package/dist/components/PresetSelector.d.ts.map +1 -0
  13. package/dist/components/PreviewWindow.d.ts +11 -0
  14. package/dist/components/PreviewWindow.d.ts.map +1 -0
  15. package/dist/components/RightSidebar.d.ts +12 -0
  16. package/dist/components/RightSidebar.d.ts.map +1 -0
  17. package/dist/components/Sidebar.d.ts +25 -0
  18. package/dist/components/Sidebar.d.ts.map +1 -0
  19. package/dist/components/TableOfContents.d.ts +9 -0
  20. package/dist/components/TableOfContents.d.ts.map +1 -0
  21. package/dist/components/ThemeBar.d.ts +8 -0
  22. package/dist/components/ThemeBar.d.ts.map +1 -0
  23. package/dist/components/sections/ColorsSection.d.ts +14 -0
  24. package/dist/components/sections/ColorsSection.d.ts.map +1 -0
  25. package/dist/components/sections/DynamicRangeSection.d.ts +9 -0
  26. package/dist/components/sections/DynamicRangeSection.d.ts.map +1 -0
  27. package/dist/components/sections/FontsSection.d.ts +9 -0
  28. package/dist/components/sections/FontsSection.d.ts.map +1 -0
  29. package/dist/components/sections/IconsSection.d.ts +9 -0
  30. package/dist/components/sections/IconsSection.d.ts.map +1 -0
  31. package/dist/components/sections/OthersSection.d.ts +9 -0
  32. package/dist/components/sections/OthersSection.d.ts.map +1 -0
  33. package/dist/components/sections/index.d.ts +6 -0
  34. package/dist/components/sections/index.d.ts.map +1 -0
  35. package/dist/hooks/useEditorState.d.ts +53 -0
  36. package/dist/hooks/useEditorState.d.ts.map +1 -0
  37. package/dist/hooks/useHover.d.ts +8 -0
  38. package/dist/hooks/useHover.d.ts.map +1 -0
  39. package/dist/hooks/usePresets.d.ts +33 -0
  40. package/dist/hooks/usePresets.d.ts.map +1 -0
  41. package/dist/index.cjs +3846 -0
  42. package/dist/index.cjs.map +1 -0
  43. package/dist/index.d.ts +22 -0
  44. package/dist/index.d.ts.map +1 -0
  45. package/dist/index.js +3819 -0
  46. package/dist/index.js.map +1 -0
  47. package/dist/preview/CategoryView.d.ts +7 -0
  48. package/dist/preview/CategoryView.d.ts.map +1 -0
  49. package/dist/preview/ComponentDetailView.d.ts +9 -0
  50. package/dist/preview/ComponentDetailView.d.ts.map +1 -0
  51. package/dist/preview/ComponentRenderer.d.ts +7 -0
  52. package/dist/preview/ComponentRenderer.d.ts.map +1 -0
  53. package/dist/preview/OverviewView.d.ts +7 -0
  54. package/dist/preview/OverviewView.d.ts.map +1 -0
  55. package/dist/types.d.ts +69 -0
  56. package/dist/types.d.ts.map +1 -0
  57. package/dist/utils/presets.d.ts +5 -0
  58. package/dist/utils/presets.d.ts.map +1 -0
  59. package/package.json +51 -0
  60. package/src/Editor.tsx +128 -0
  61. package/src/components/CodeBlock.tsx +58 -0
  62. package/src/components/EditorHeader.tsx +86 -0
  63. package/src/components/EditorShell.tsx +67 -0
  64. package/src/components/FontPicker.tsx +351 -0
  65. package/src/components/PresetSelector.tsx +455 -0
  66. package/src/components/PreviewWindow.tsx +69 -0
  67. package/src/components/RightSidebar.tsx +374 -0
  68. package/src/components/Sidebar.tsx +332 -0
  69. package/src/components/TableOfContents.tsx +152 -0
  70. package/src/components/ThemeBar.tsx +76 -0
  71. package/src/components/sections/ColorsSection.tsx +485 -0
  72. package/src/components/sections/DynamicRangeSection.tsx +399 -0
  73. package/src/components/sections/FontsSection.tsx +132 -0
  74. package/src/components/sections/IconsSection.tsx +66 -0
  75. package/src/components/sections/OthersSection.tsx +70 -0
  76. package/src/components/sections/index.ts +5 -0
  77. package/src/hooks/useEditorState.ts +381 -0
  78. package/src/hooks/useHover.ts +8 -0
  79. package/src/hooks/usePresets.ts +254 -0
  80. package/src/index.ts +52 -0
  81. package/src/preview/CategoryView.tsx +134 -0
  82. package/src/preview/ComponentDetailView.tsx +126 -0
  83. package/src/preview/ComponentRenderer.tsx +107 -0
  84. package/src/preview/OverviewView.tsx +177 -0
  85. package/src/types.ts +77 -0
  86. package/src/utils/presets.ts +24 -0
@@ -0,0 +1,351 @@
1
+ import { useState, useRef, useEffect, useMemo, useCallback } from "react";
2
+ import { useTokens } from "@newtonedev/components";
3
+ import {
4
+ GOOGLE_FONTS,
5
+ SYSTEM_FONTS,
6
+ } from "@newtonedev/components";
7
+ import type { GoogleFontEntry, SystemFontEntry, FontConfig } from "@newtonedev/components";
8
+ import { srgbToHex } from "newtone";
9
+
10
+ type FontSlot = "default" | "display" | "mono";
11
+
12
+ interface FontPickerProps {
13
+ readonly label: string;
14
+ readonly slot: FontSlot;
15
+ readonly currentFont: FontConfig;
16
+ readonly onSelect: (font: FontConfig) => void;
17
+ }
18
+
19
+ /** Preload all curated Google Fonts for preview on first dropdown open. */
20
+ let previewLoaded = false;
21
+ function preloadFontsForPreview() {
22
+ if (previewLoaded || typeof document === "undefined") return;
23
+ previewLoaded = true;
24
+ const families = GOOGLE_FONTS.map(
25
+ (f) => `family=${f.family.replace(/ /g, "+")}:wght@400`,
26
+ ).join("&");
27
+ const url = `https://fonts.googleapis.com/css2?${families}&display=swap`;
28
+ const link = document.createElement("link");
29
+ link.rel = "stylesheet";
30
+ link.href = url;
31
+ document.head.appendChild(link);
32
+ }
33
+
34
+ function googleFontToConfig(entry: GoogleFontEntry): FontConfig {
35
+ return {
36
+ type: "google",
37
+ family: entry.family,
38
+ fallback: entry.fallback,
39
+ };
40
+ }
41
+
42
+ function systemFontToConfig(entry: SystemFontEntry): FontConfig {
43
+ return {
44
+ type: "system",
45
+ family: entry.family,
46
+ fallback: entry.fallback,
47
+ };
48
+ }
49
+
50
+ const CATEGORY_LABELS: Record<string, string> = {
51
+ "sans-serif": "Sans Serif",
52
+ serif: "Serif",
53
+ monospace: "Monospace",
54
+ display: "Display",
55
+ };
56
+
57
+ const CATEGORY_ORDER: readonly string[] = [
58
+ "sans-serif",
59
+ "serif",
60
+ "monospace",
61
+ "display",
62
+ ];
63
+ const MONO_CATEGORY_ORDER: readonly string[] = [
64
+ "monospace",
65
+ "sans-serif",
66
+ "serif",
67
+ "display",
68
+ ];
69
+
70
+ export function FontPicker({
71
+ label,
72
+ slot,
73
+ currentFont,
74
+ onSelect,
75
+ }: FontPickerProps) {
76
+ const tokens = useTokens();
77
+ const [isOpen, setIsOpen] = useState(false);
78
+ const [search, setSearch] = useState("");
79
+ const containerRef = useRef<HTMLDivElement>(null);
80
+ const searchInputRef = useRef<HTMLInputElement>(null);
81
+
82
+ const labelColor = srgbToHex(tokens.textSecondary.srgb);
83
+ const textColor = srgbToHex(tokens.textPrimary.srgb);
84
+ const bgColor = srgbToHex(tokens.backgroundElevated.srgb);
85
+ const borderColor = srgbToHex(tokens.border.srgb);
86
+ const hoverColor = srgbToHex(tokens.backgroundSunken.srgb);
87
+ const interactiveColor = srgbToHex(tokens.interactive.srgb);
88
+
89
+ useEffect(() => {
90
+ if (!isOpen) return;
91
+ function handleMouseDown(e: MouseEvent) {
92
+ if (
93
+ containerRef.current &&
94
+ !containerRef.current.contains(e.target as Node)
95
+ ) {
96
+ setIsOpen(false);
97
+ setSearch("");
98
+ }
99
+ }
100
+ document.addEventListener("mousedown", handleMouseDown);
101
+ return () => document.removeEventListener("mousedown", handleMouseDown);
102
+ }, [isOpen]);
103
+
104
+ useEffect(() => {
105
+ if (isOpen) {
106
+ preloadFontsForPreview();
107
+ requestAnimationFrame(() => searchInputRef.current?.focus());
108
+ }
109
+ }, [isOpen]);
110
+
111
+ const categoryOrder = slot === "mono" ? MONO_CATEGORY_ORDER : CATEGORY_ORDER;
112
+
113
+ const filteredGoogleFonts = useMemo(() => {
114
+ const query = search.toLowerCase().trim();
115
+ const fonts = query
116
+ ? GOOGLE_FONTS.filter((f) => f.family.toLowerCase().includes(query))
117
+ : GOOGLE_FONTS;
118
+
119
+ const grouped: Record<string, GoogleFontEntry[]> = {};
120
+ for (const cat of categoryOrder) {
121
+ const inCategory = fonts.filter((f) => f.category === cat);
122
+ if (inCategory.length > 0) {
123
+ grouped[cat] = inCategory;
124
+ }
125
+ }
126
+ return grouped;
127
+ }, [search, categoryOrder]);
128
+
129
+ const filteredSystemFonts = useMemo(() => {
130
+ const query = search.toLowerCase().trim();
131
+ return query
132
+ ? SYSTEM_FONTS.filter((f) => f.family.toLowerCase().includes(query))
133
+ : [...SYSTEM_FONTS];
134
+ }, [search]);
135
+
136
+ const handleSelect = useCallback(
137
+ (font: FontConfig) => {
138
+ onSelect(font);
139
+ setIsOpen(false);
140
+ setSearch("");
141
+ },
142
+ [onSelect],
143
+ );
144
+
145
+ const fontFamily = currentFont.family.includes(" ")
146
+ ? `"${currentFont.family}"`
147
+ : currentFont.family;
148
+
149
+ return (
150
+ <div ref={containerRef} style={{ position: "relative" }}>
151
+ <button
152
+ type="button"
153
+ onClick={() => setIsOpen(!isOpen)}
154
+ style={{
155
+ width: "100%",
156
+ display: "flex",
157
+ justifyContent: "space-between",
158
+ alignItems: "center",
159
+ padding: "6px 10px",
160
+ borderRadius: 6,
161
+ border: `1px solid ${isOpen ? interactiveColor : borderColor}`,
162
+ background: "transparent",
163
+ cursor: "pointer",
164
+ outline: "none",
165
+ }}
166
+ >
167
+ <span style={{ fontSize: 12, color: labelColor }}>{label}</span>
168
+ <span
169
+ style={{
170
+ fontSize: 12,
171
+ color: textColor,
172
+ fontFamily: `${fontFamily}, ${currentFont.fallback}`,
173
+ maxWidth: 140,
174
+ overflow: "hidden",
175
+ textOverflow: "ellipsis",
176
+ whiteSpace: "nowrap",
177
+ }}
178
+ >
179
+ {currentFont.family}
180
+ </span>
181
+ </button>
182
+
183
+ {isOpen && (
184
+ <div
185
+ style={{
186
+ position: "absolute",
187
+ top: "calc(100% + 4px)",
188
+ left: 0,
189
+ right: 0,
190
+ zIndex: 100,
191
+ background: bgColor,
192
+ border: `1px solid ${borderColor}`,
193
+ borderRadius: 8,
194
+ boxShadow: "0 4px 16px rgba(0,0,0,0.15)",
195
+ maxHeight: 320,
196
+ display: "flex",
197
+ flexDirection: "column",
198
+ overflow: "hidden",
199
+ }}
200
+ >
201
+ <div style={{ padding: "8px 8px 4px" }}>
202
+ <input
203
+ ref={searchInputRef}
204
+ type="text"
205
+ value={search}
206
+ onChange={(e) => setSearch(e.target.value)}
207
+ placeholder="Search fonts..."
208
+ style={{
209
+ width: "100%",
210
+ padding: "6px 8px",
211
+ fontSize: 12,
212
+ borderRadius: 4,
213
+ border: `1px solid ${borderColor}`,
214
+ background: "transparent",
215
+ color: textColor,
216
+ outline: "none",
217
+ boxSizing: "border-box",
218
+ }}
219
+ />
220
+ </div>
221
+
222
+ <div style={{ overflowY: "auto", padding: "4px 0" }}>
223
+ {filteredSystemFonts.length > 0 && (
224
+ <div>
225
+ <div
226
+ style={{
227
+ fontSize: 10,
228
+ fontWeight: 600,
229
+ color: labelColor,
230
+ textTransform: "uppercase",
231
+ letterSpacing: 0.5,
232
+ padding: "6px 12px 2px",
233
+ }}
234
+ >
235
+ System
236
+ </div>
237
+ {filteredSystemFonts.map((f) => (
238
+ <FontOption
239
+ key={f.family}
240
+ family={f.family}
241
+ fallback={f.fallback}
242
+ isSelected={
243
+ currentFont.family === f.family &&
244
+ currentFont.type === "system"
245
+ }
246
+ textColor={textColor}
247
+ hoverColor={hoverColor}
248
+ interactiveColor={interactiveColor}
249
+ onSelect={() => handleSelect(systemFontToConfig(f))}
250
+ />
251
+ ))}
252
+ </div>
253
+ )}
254
+
255
+ {Object.entries(filteredGoogleFonts).map(([category, fonts]) => (
256
+ <div key={category}>
257
+ <div
258
+ style={{
259
+ fontSize: 10,
260
+ fontWeight: 600,
261
+ color: labelColor,
262
+ textTransform: "uppercase",
263
+ letterSpacing: 0.5,
264
+ padding: "8px 12px 2px",
265
+ }}
266
+ >
267
+ {CATEGORY_LABELS[category] ?? category}
268
+ </div>
269
+ {fonts.map((f) => (
270
+ <FontOption
271
+ key={f.family}
272
+ family={f.family}
273
+ fallback={f.fallback}
274
+ isSelected={
275
+ currentFont.family === f.family &&
276
+ currentFont.type === "google"
277
+ }
278
+ textColor={textColor}
279
+ hoverColor={hoverColor}
280
+ interactiveColor={interactiveColor}
281
+ onSelect={() => handleSelect(googleFontToConfig(f))}
282
+ />
283
+ ))}
284
+ </div>
285
+ ))}
286
+
287
+ {filteredSystemFonts.length === 0 &&
288
+ Object.keys(filteredGoogleFonts).length === 0 && (
289
+ <div
290
+ style={{
291
+ padding: "12px",
292
+ fontSize: 12,
293
+ color: labelColor,
294
+ textAlign: "center",
295
+ }}
296
+ >
297
+ No fonts found
298
+ </div>
299
+ )}
300
+ </div>
301
+ </div>
302
+ )}
303
+ </div>
304
+ );
305
+ }
306
+
307
+ function FontOption({
308
+ family,
309
+ fallback,
310
+ isSelected,
311
+ textColor,
312
+ hoverColor,
313
+ interactiveColor,
314
+ onSelect,
315
+ }: {
316
+ readonly family: string;
317
+ readonly fallback: string;
318
+ readonly isSelected: boolean;
319
+ readonly textColor: string;
320
+ readonly hoverColor: string;
321
+ readonly interactiveColor: string;
322
+ readonly onSelect: () => void;
323
+ }) {
324
+ const [hovered, setHovered] = useState(false);
325
+ const fontFamily = family.includes(" ") ? `"${family}"` : family;
326
+
327
+ return (
328
+ <button
329
+ type="button"
330
+ onClick={onSelect}
331
+ onMouseEnter={() => setHovered(true)}
332
+ onMouseLeave={() => setHovered(false)}
333
+ style={{
334
+ display: "block",
335
+ width: "100%",
336
+ padding: "5px 12px",
337
+ fontSize: 13,
338
+ fontFamily: `${fontFamily}, ${fallback}`,
339
+ color: isSelected ? interactiveColor : textColor,
340
+ background: hovered ? hoverColor : "transparent",
341
+ border: "none",
342
+ cursor: "pointer",
343
+ textAlign: "left",
344
+ outline: "none",
345
+ fontWeight: isSelected ? 600 : 400,
346
+ }}
347
+ >
348
+ {family}
349
+ </button>
350
+ );
351
+ }