@newtonedev/editor 0.1.4 → 0.1.6

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 (48) hide show
  1. package/dist/Editor.d.ts.map +1 -1
  2. package/dist/components/CodeBlock.d.ts.map +1 -1
  3. package/dist/components/PresetSelector.d.ts.map +1 -1
  4. package/dist/components/PreviewWindow.d.ts +3 -2
  5. package/dist/components/PreviewWindow.d.ts.map +1 -1
  6. package/dist/components/RightSidebar.d.ts +4 -1
  7. package/dist/components/RightSidebar.d.ts.map +1 -1
  8. package/dist/components/Sidebar.d.ts.map +1 -1
  9. package/dist/components/sections/DynamicRangeSection.d.ts.map +1 -1
  10. package/dist/hooks/useEditorState.d.ts +1 -3
  11. package/dist/hooks/useEditorState.d.ts.map +1 -1
  12. package/dist/index.cjs +686 -346
  13. package/dist/index.cjs.map +1 -1
  14. package/dist/index.d.ts +1 -2
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +689 -348
  17. package/dist/index.js.map +1 -1
  18. package/dist/preview/ComponentDetailView.d.ts +3 -2
  19. package/dist/preview/ComponentDetailView.d.ts.map +1 -1
  20. package/dist/preview/ComponentRenderer.d.ts.map +1 -1
  21. package/dist/preview/IconBrowserView.d.ts +7 -0
  22. package/dist/preview/IconBrowserView.d.ts.map +1 -0
  23. package/dist/types.d.ts +0 -1
  24. package/dist/types.d.ts.map +1 -1
  25. package/package.json +1 -1
  26. package/src/Editor.tsx +19 -9
  27. package/src/components/CodeBlock.tsx +42 -14
  28. package/src/components/EditorHeader.tsx +3 -3
  29. package/src/components/EditorShell.tsx +2 -2
  30. package/src/components/FontPicker.tsx +1 -1
  31. package/src/components/PresetSelector.tsx +11 -36
  32. package/src/components/PreviewWindow.tsx +6 -3
  33. package/src/components/RightSidebar.tsx +105 -42
  34. package/src/components/Sidebar.tsx +12 -92
  35. package/src/components/TableOfContents.tsx +1 -1
  36. package/src/components/sections/ColorsSection.tsx +2 -2
  37. package/src/components/sections/DynamicRangeSection.tsx +226 -3
  38. package/src/hooks/useEditorState.ts +14 -19
  39. package/src/index.ts +0 -2
  40. package/src/preview/CategoryView.tsx +1 -1
  41. package/src/preview/ComponentDetailView.tsx +47 -8
  42. package/src/preview/ComponentRenderer.tsx +51 -0
  43. package/src/preview/IconBrowserView.tsx +187 -0
  44. package/src/preview/OverviewView.tsx +1 -1
  45. package/src/types.ts +0 -2
  46. package/dist/components/ThemeBar.d.ts +0 -8
  47. package/dist/components/ThemeBar.d.ts.map +0 -1
  48. package/src/components/ThemeBar.tsx +0 -76
@@ -0,0 +1,187 @@
1
+ import { useState, useRef, useEffect, useMemo } from "react";
2
+ import { Icon, useTokens, ICON_CATALOG } from "@newtonedev/components";
3
+ import { srgbToHex } from "newtone";
4
+
5
+ interface IconBrowserViewProps {
6
+ readonly selectedIconName: string;
7
+ readonly onIconSelect: (name: string) => void;
8
+ }
9
+
10
+ export function IconBrowserView({
11
+ selectedIconName,
12
+ onIconSelect,
13
+ }: IconBrowserViewProps) {
14
+ const tokens = useTokens();
15
+ const [search, setSearch] = useState("");
16
+ const [hoveredIcon, setHoveredIcon] = useState<string | null>(null);
17
+ const scrollRef = useRef<HTMLDivElement>(null);
18
+
19
+ const filteredCategories = useMemo(() => {
20
+ const q = search.toLowerCase().trim();
21
+ if (!q) return ICON_CATALOG;
22
+ return ICON_CATALOG
23
+ .map((cat) => ({
24
+ ...cat,
25
+ icons: cat.icons.filter((name) => name.includes(q)),
26
+ }))
27
+ .filter((cat) => cat.icons.length > 0);
28
+ }, [search]);
29
+
30
+ // Scroll to selected icon when it changes externally (user types in sidebar)
31
+ useEffect(() => {
32
+ if (!selectedIconName || !scrollRef.current) return;
33
+ const el = scrollRef.current.querySelector(
34
+ `[data-icon="${selectedIconName}"]`,
35
+ );
36
+ if (el) {
37
+ el.scrollIntoView({ behavior: "smooth", block: "nearest" });
38
+ }
39
+ }, [selectedIconName]);
40
+
41
+ const accentColor = srgbToHex(tokens.accent.fill.srgb);
42
+
43
+ return (
44
+ <div
45
+ style={{
46
+ display: "flex",
47
+ flexDirection: "column",
48
+ height: "100%",
49
+ minHeight: 0,
50
+ }}
51
+ >
52
+ {/* Search */}
53
+ <div style={{ padding: "0 32px", flexShrink: 0 }}>
54
+ <div style={{ position: "relative" }}>
55
+ <Icon
56
+ name="search"
57
+ size={18}
58
+ color={srgbToHex(tokens.textTertiary.srgb)}
59
+ style={{
60
+ position: "absolute",
61
+ left: 10,
62
+ top: 9,
63
+ pointerEvents: "none",
64
+ }}
65
+ />
66
+ <input
67
+ type="text"
68
+ placeholder="Search icons..."
69
+ value={search}
70
+ onChange={(e) => setSearch(e.target.value)}
71
+ style={{
72
+ width: "100%",
73
+ padding: "8px 12px 8px 34px",
74
+ borderRadius: 8,
75
+ border: `1px solid ${srgbToHex(tokens.border.srgb)}`,
76
+ backgroundColor: srgbToHex(tokens.backgroundSunken.srgb),
77
+ color: srgbToHex(tokens.textPrimary.srgb),
78
+ fontSize: 13,
79
+ boxSizing: "border-box",
80
+ outline: "none",
81
+ }}
82
+ />
83
+ </div>
84
+ </div>
85
+
86
+ {/* Icon grid */}
87
+ <div
88
+ ref={scrollRef}
89
+ style={{
90
+ flex: 1,
91
+ overflowY: "auto",
92
+ padding: "16px 32px 32px",
93
+ }}
94
+ >
95
+ {filteredCategories.length === 0 && (
96
+ <p
97
+ style={{
98
+ fontSize: 13,
99
+ color: srgbToHex(tokens.textTertiary.srgb),
100
+ textAlign: "center",
101
+ marginTop: 32,
102
+ }}
103
+ >
104
+ No icons found
105
+ </p>
106
+ )}
107
+
108
+ {filteredCategories.map((category) => (
109
+ <div key={category.id} style={{ marginBottom: 24 }}>
110
+ <h3
111
+ style={{
112
+ fontSize: 12,
113
+ fontWeight: 600,
114
+ color: srgbToHex(tokens.textSecondary.srgb),
115
+ textTransform: "uppercase",
116
+ letterSpacing: 0.5,
117
+ margin: "0 0 8px",
118
+ }}
119
+ >
120
+ {category.label}
121
+ </h3>
122
+ <div
123
+ style={{
124
+ display: "grid",
125
+ gridTemplateColumns: "repeat(auto-fill, minmax(80px, 1fr))",
126
+ gap: 6,
127
+ }}
128
+ >
129
+ {category.icons.map((name) => {
130
+ const isSelected = selectedIconName === name;
131
+ const isHovered = hoveredIcon === name;
132
+
133
+ const borderColor = isSelected
134
+ ? accentColor
135
+ : isHovered
136
+ ? `${accentColor}66`
137
+ : "transparent";
138
+
139
+ return (
140
+ <button
141
+ key={name}
142
+ data-icon={name}
143
+ onClick={() => onIconSelect(name)}
144
+ onMouseEnter={() => setHoveredIcon(name)}
145
+ onMouseLeave={() => setHoveredIcon(null)}
146
+ style={{
147
+ display: "flex",
148
+ flexDirection: "column",
149
+ alignItems: "center",
150
+ justifyContent: "center",
151
+ gap: 4,
152
+ padding: "8px 4px 6px",
153
+ borderRadius: 8,
154
+ border: `2px solid ${borderColor}`,
155
+ backgroundColor: isSelected
156
+ ? srgbToHex(tokens.backgroundElevated.srgb)
157
+ : "transparent",
158
+ cursor: "pointer",
159
+ transition: "border-color 150ms ease",
160
+ }}
161
+ >
162
+ <Icon name={name} size={40} />
163
+ <span
164
+ style={{
165
+ fontSize: 10,
166
+ color: isSelected
167
+ ? accentColor
168
+ : srgbToHex(tokens.textTertiary.srgb),
169
+ fontWeight: isSelected ? 600 : 400,
170
+ maxWidth: "100%",
171
+ overflow: "hidden",
172
+ textOverflow: "ellipsis",
173
+ whiteSpace: "nowrap",
174
+ }}
175
+ >
176
+ {name}
177
+ </span>
178
+ </button>
179
+ );
180
+ })}
181
+ </div>
182
+ </div>
183
+ ))}
184
+ </div>
185
+ </div>
186
+ );
187
+ }
@@ -128,7 +128,7 @@ function ComponentCard({
128
128
  padding: 20,
129
129
  borderRadius: 12,
130
130
  border: `1px solid ${srgbToHex(
131
- isHovered ? tokens.interactive.srgb : tokens.border.srgb,
131
+ isHovered ? tokens.accent.fill.srgb : tokens.border.srgb,
132
132
  )}`,
133
133
  backgroundColor: srgbToHex(tokens.backgroundElevated.srgb),
134
134
  cursor: "pointer",
package/src/types.ts CHANGED
@@ -15,8 +15,6 @@ export interface Preset {
15
15
 
16
16
  export type SaveStatus = "saved" | "saving" | "unsaved" | "error";
17
17
 
18
- export type ThemeName = "neutral" | "primary" | "secondary" | "strong";
19
-
20
18
  export type PreviewView =
21
19
  | { readonly kind: "overview" }
22
20
  | { readonly kind: "category"; readonly categoryId: string }
@@ -1,8 +0,0 @@
1
- import type { ThemeName } from "../types";
2
- interface ThemeBarProps {
3
- readonly activeTheme: ThemeName;
4
- readonly onThemeChange: (theme: ThemeName) => void;
5
- }
6
- export declare function ThemeBar({ activeTheme, onThemeChange }: ThemeBarProps): import("react/jsx-runtime").JSX.Element;
7
- export {};
8
- //# sourceMappingURL=ThemeBar.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"ThemeBar.d.ts","sourceRoot":"","sources":["../../src/components/ThemeBar.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAE1C,UAAU,aAAa;IACrB,QAAQ,CAAC,WAAW,EAAE,SAAS,CAAC;IAChC,QAAQ,CAAC,aAAa,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,IAAI,CAAC;CACpD;AASD,wBAAgB,QAAQ,CAAC,EAAE,WAAW,EAAE,aAAa,EAAE,EAAE,aAAa,2CA0DrE"}
@@ -1,76 +0,0 @@
1
- import { useState } from "react";
2
- import { useTokens } from "@newtonedev/components";
3
- import { srgbToHex } from "newtone";
4
- import type { ThemeName } from "../types";
5
-
6
- interface ThemeBarProps {
7
- readonly activeTheme: ThemeName;
8
- readonly onThemeChange: (theme: ThemeName) => void;
9
- }
10
-
11
- const THEME_CHIPS: readonly { id: ThemeName; label: string }[] = [
12
- { id: "neutral", label: "Neutral" },
13
- { id: "primary", label: "Primary" },
14
- { id: "secondary", label: "Secondary" },
15
- { id: "strong", label: "Strong" },
16
- ];
17
-
18
- export function ThemeBar({ activeTheme, onThemeChange }: ThemeBarProps) {
19
- const tokens = useTokens();
20
- const [hoveredChipId, setHoveredChipId] = useState<string | null>(null);
21
-
22
- const borderColor = srgbToHex(tokens.border.srgb);
23
- const interactiveColor = srgbToHex(tokens.interactive.srgb);
24
-
25
- return (
26
- <div
27
- style={{
28
- display: "flex",
29
- alignItems: "center",
30
- padding: "8px 24px",
31
- borderBottom: `1px solid ${borderColor}`,
32
- backgroundColor: srgbToHex(tokens.background.srgb),
33
- flexShrink: 0,
34
- }}
35
- >
36
- {/* Theme Chips */}
37
- <div style={{ display: "flex", gap: 8 }} role="group" aria-label="Theme">
38
- {THEME_CHIPS.map((chip) => {
39
- const isActive = chip.id === activeTheme;
40
- const isHovered = hoveredChipId === chip.id;
41
-
42
- return (
43
- <button
44
- key={chip.id}
45
- onClick={() => onThemeChange(chip.id)}
46
- onMouseEnter={() => setHoveredChipId(chip.id)}
47
- onMouseLeave={() => setHoveredChipId(null)}
48
- aria-pressed={isActive}
49
- style={{
50
- padding: "4px 12px",
51
- borderRadius: 16,
52
- border: `1px solid ${srgbToHex(
53
- isActive ? tokens.interactive.srgb : tokens.border.srgb,
54
- )}`,
55
- backgroundColor: isActive
56
- ? interactiveColor
57
- : isHovered
58
- ? `${interactiveColor}10`
59
- : "transparent",
60
- color: isActive
61
- ? "#fff"
62
- : srgbToHex(tokens.textPrimary.srgb),
63
- fontSize: 12,
64
- fontWeight: 500,
65
- cursor: "pointer",
66
- transition: "background-color 150ms ease",
67
- }}
68
- >
69
- {chip.label}
70
- </button>
71
- );
72
- })}
73
- </div>
74
- </div>
75
- );
76
- }