@newtonedev/editor 0.1.5 → 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.
- package/dist/Editor.d.ts.map +1 -1
- package/dist/components/CodeBlock.d.ts.map +1 -1
- package/dist/components/PresetSelector.d.ts.map +1 -1
- package/dist/components/PreviewWindow.d.ts +3 -2
- package/dist/components/PreviewWindow.d.ts.map +1 -1
- package/dist/components/RightSidebar.d.ts +4 -1
- package/dist/components/RightSidebar.d.ts.map +1 -1
- package/dist/components/Sidebar.d.ts.map +1 -1
- package/dist/hooks/useEditorState.d.ts.map +1 -1
- package/dist/index.cjs +469 -242
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +470 -243
- package/dist/index.js.map +1 -1
- package/dist/preview/ComponentDetailView.d.ts +3 -2
- package/dist/preview/ComponentDetailView.d.ts.map +1 -1
- package/dist/preview/IconBrowserView.d.ts +7 -0
- package/dist/preview/IconBrowserView.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/Editor.tsx +4 -1
- package/src/components/CodeBlock.tsx +42 -14
- package/src/components/PresetSelector.tsx +8 -33
- package/src/components/PreviewWindow.tsx +6 -3
- package/src/components/RightSidebar.tsx +103 -40
- package/src/components/Sidebar.tsx +12 -92
- package/src/hooks/useEditorState.ts +14 -3
- package/src/preview/ComponentDetailView.tsx +46 -7
- package/src/preview/IconBrowserView.tsx +187 -0
|
@@ -3,19 +3,22 @@ import { useTokens } from "@newtonedev/components";
|
|
|
3
3
|
import { srgbToHex } from "newtone";
|
|
4
4
|
import { getComponent } from "@newtonedev/components";
|
|
5
5
|
import { ComponentRenderer } from "./ComponentRenderer";
|
|
6
|
+
import { IconBrowserView } from "./IconBrowserView";
|
|
6
7
|
|
|
7
8
|
interface ComponentDetailViewProps {
|
|
8
9
|
readonly componentId: string;
|
|
9
10
|
readonly selectedVariantId: string | null;
|
|
10
|
-
readonly propOverrides?: Record<string, unknown>;
|
|
11
11
|
readonly onSelectVariant: (variantId: string) => void;
|
|
12
|
+
readonly propOverrides?: Record<string, unknown>;
|
|
13
|
+
readonly onPropOverride?: (name: string, value: unknown) => void;
|
|
12
14
|
}
|
|
13
15
|
|
|
14
16
|
export function ComponentDetailView({
|
|
15
17
|
componentId,
|
|
16
18
|
selectedVariantId,
|
|
17
|
-
propOverrides,
|
|
18
19
|
onSelectVariant,
|
|
20
|
+
propOverrides,
|
|
21
|
+
onPropOverride,
|
|
19
22
|
}: ComponentDetailViewProps) {
|
|
20
23
|
const tokens = useTokens();
|
|
21
24
|
const component = getComponent(componentId);
|
|
@@ -23,6 +26,46 @@ export function ComponentDetailView({
|
|
|
23
26
|
|
|
24
27
|
if (!component) return null;
|
|
25
28
|
|
|
29
|
+
if (componentId === "icon" && propOverrides && onPropOverride) {
|
|
30
|
+
return (
|
|
31
|
+
<div
|
|
32
|
+
style={{
|
|
33
|
+
padding: "32px 0 0",
|
|
34
|
+
height: "100%",
|
|
35
|
+
display: "flex",
|
|
36
|
+
flexDirection: "column",
|
|
37
|
+
}}
|
|
38
|
+
>
|
|
39
|
+
<div style={{ padding: "0 32px", marginBottom: 24 }}>
|
|
40
|
+
<h2
|
|
41
|
+
style={{
|
|
42
|
+
fontSize: 22,
|
|
43
|
+
fontWeight: 700,
|
|
44
|
+
color: srgbToHex(tokens.textPrimary.srgb),
|
|
45
|
+
margin: 0,
|
|
46
|
+
marginBottom: 4,
|
|
47
|
+
}}
|
|
48
|
+
>
|
|
49
|
+
{component.name}
|
|
50
|
+
</h2>
|
|
51
|
+
<p
|
|
52
|
+
style={{
|
|
53
|
+
fontSize: 14,
|
|
54
|
+
color: srgbToHex(tokens.textSecondary.srgb),
|
|
55
|
+
margin: 0,
|
|
56
|
+
}}
|
|
57
|
+
>
|
|
58
|
+
{component.description}
|
|
59
|
+
</p>
|
|
60
|
+
</div>
|
|
61
|
+
<IconBrowserView
|
|
62
|
+
selectedIconName={(propOverrides.name as string) ?? "add"}
|
|
63
|
+
onIconSelect={(name) => onPropOverride("name", name)}
|
|
64
|
+
/>
|
|
65
|
+
</div>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
26
69
|
const interactiveColor = srgbToHex(tokens.accent.fill.srgb);
|
|
27
70
|
|
|
28
71
|
return (
|
|
@@ -99,11 +142,7 @@ export function ComponentDetailView({
|
|
|
99
142
|
>
|
|
100
143
|
<ComponentRenderer
|
|
101
144
|
componentId={componentId}
|
|
102
|
-
props={
|
|
103
|
-
isSelected && propOverrides
|
|
104
|
-
? { ...variant.props, ...propOverrides }
|
|
105
|
-
: variant.props
|
|
106
|
-
}
|
|
145
|
+
props={variant.props}
|
|
107
146
|
/>
|
|
108
147
|
</div>
|
|
109
148
|
<span
|
|
@@ -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
|
+
}
|