@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.
@@ -1,9 +1,10 @@
1
1
  interface ComponentDetailViewProps {
2
2
  readonly componentId: string;
3
3
  readonly selectedVariantId: string | null;
4
- readonly propOverrides?: Record<string, unknown>;
5
4
  readonly onSelectVariant: (variantId: string) => void;
5
+ readonly propOverrides?: Record<string, unknown>;
6
+ readonly onPropOverride?: (name: string, value: unknown) => void;
6
7
  }
7
- export declare function ComponentDetailView({ componentId, selectedVariantId, propOverrides, onSelectVariant, }: ComponentDetailViewProps): import("react/jsx-runtime").JSX.Element | null;
8
+ export declare function ComponentDetailView({ componentId, selectedVariantId, onSelectVariant, propOverrides, onPropOverride, }: ComponentDetailViewProps): import("react/jsx-runtime").JSX.Element | null;
8
9
  export {};
9
10
  //# sourceMappingURL=ComponentDetailView.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ComponentDetailView.d.ts","sourceRoot":"","sources":["../../src/preview/ComponentDetailView.tsx"],"names":[],"mappings":"AAMA,UAAU,wBAAwB;IAChC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1C,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjD,QAAQ,CAAC,eAAe,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;CACvD;AAED,wBAAgB,mBAAmB,CAAC,EAClC,WAAW,EACX,iBAAiB,EACjB,aAAa,EACb,eAAe,GAChB,EAAE,wBAAwB,kDA2G1B"}
1
+ {"version":3,"file":"ComponentDetailView.d.ts","sourceRoot":"","sources":["../../src/preview/ComponentDetailView.tsx"],"names":[],"mappings":"AAOA,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;CAClE;AAED,wBAAgB,mBAAmB,CAAC,EAClC,WAAW,EACX,iBAAiB,EACjB,eAAe,EACf,aAAa,EACb,cAAc,GACf,EAAE,wBAAwB,kDA+I1B"}
@@ -0,0 +1,7 @@
1
+ interface IconBrowserViewProps {
2
+ readonly selectedIconName: string;
3
+ readonly onIconSelect: (name: string) => void;
4
+ }
5
+ export declare function IconBrowserView({ selectedIconName, onIconSelect, }: IconBrowserViewProps): import("react/jsx-runtime").JSX.Element;
6
+ export {};
7
+ //# sourceMappingURL=IconBrowserView.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"IconBrowserView.d.ts","sourceRoot":"","sources":["../../src/preview/IconBrowserView.tsx"],"names":[],"mappings":"AAIA,UAAU,oBAAoB;IAC5B,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,YAAY,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAC/C;AAED,wBAAgB,eAAe,CAAC,EAC9B,gBAAgB,EAChB,YAAY,GACb,EAAE,oBAAoB,2CA8KtB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@newtonedev/editor",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "description": "Shared color system editor for Newtone applications",
5
5
  "license": "MIT",
6
6
  "repository": {
package/src/Editor.tsx CHANGED
@@ -110,9 +110,10 @@ export function Editor({
110
110
  <PreviewWindow
111
111
  view={editor.previewView}
112
112
  selectedVariantId={editor.selectedVariantId}
113
- propOverrides={editor.propOverrides}
114
113
  onNavigate={editor.handlePreviewNavigate}
115
114
  onSelectVariant={editor.handleSelectVariant}
115
+ propOverrides={editor.propOverrides}
116
+ onPropOverride={editor.handlePropOverride}
116
117
  />
117
118
  </div>
118
119
  </NewtoneProvider>
@@ -127,6 +128,8 @@ export function Editor({
127
128
  onResetOverrides={editor.handleResetOverrides}
128
129
  onClose={editor.handleCloseSidebar}
129
130
  onScopeToComponent={editor.handleScopeToComponent}
131
+ previewConfig={previewConfig}
132
+ colorMode={editor.colorMode}
130
133
  />
131
134
  }
132
135
  />
@@ -1,8 +1,9 @@
1
1
  import { useState, useCallback } from "react";
2
- import { useTokens, Button } from "@newtonedev/components";
2
+ import { useTokens, Icon } from "@newtonedev/components";
3
3
  import { srgbToHex } from "newtone";
4
4
 
5
5
  export function CopyButton({ text }: { readonly text: string }) {
6
+ const tokens = useTokens();
6
7
  const [copied, setCopied] = useState(false);
7
8
 
8
9
  const handleCopy = useCallback(async () => {
@@ -12,9 +13,31 @@ export function CopyButton({ text }: { readonly text: string }) {
12
13
  }, [text]);
13
14
 
14
15
  return (
15
- <Button variant="tertiary" semantic="neutral" size="sm" icon={copied ? "check" : "content_copy"} onPress={handleCopy}>
16
- {copied ? "Copied!" : "Copy"}
17
- </Button>
16
+ <button
17
+ onClick={handleCopy}
18
+ aria-label={copied ? "Copied" : "Copy code"}
19
+ style={{
20
+ background: "none",
21
+ border: "none",
22
+ cursor: "pointer",
23
+ padding: 4,
24
+ display: "flex",
25
+ alignItems: "center",
26
+ justifyContent: "center",
27
+ color: srgbToHex(
28
+ copied ? tokens.accent.fill.srgb : tokens.textTertiary.srgb,
29
+ ),
30
+ transition: "color 150ms ease",
31
+ }}
32
+ >
33
+ <Icon
34
+ name={copied ? "check" : "content_copy"}
35
+ size={16}
36
+ color={srgbToHex(
37
+ copied ? tokens.accent.fill.srgb : tokens.textTertiary.srgb,
38
+ )}
39
+ />
40
+ </button>
18
41
  );
19
42
  }
20
43
 
@@ -26,24 +49,29 @@ export function CodeBlock({
26
49
  const tokens = useTokens();
27
50
 
28
51
  return (
29
- <div style={{ position: "relative" }}>
52
+ <div
53
+ style={{
54
+ backgroundColor: srgbToHex(tokens.backgroundSunken.srgb),
55
+ border: `1px solid ${srgbToHex(tokens.border.srgb)}`,
56
+ borderRadius: 8,
57
+ overflow: "hidden",
58
+ }}
59
+ >
30
60
  <div
31
61
  style={{
32
- position: "absolute",
33
- top: 8,
34
- right: 8,
62
+ display: "flex",
63
+ justifyContent: "flex-end",
64
+ padding: "4px 8px",
65
+ borderBottom: `1px solid ${srgbToHex(tokens.border.srgb)}`,
35
66
  }}
36
67
  >
37
68
  <CopyButton text={code} />
38
69
  </div>
39
70
  <pre
40
71
  style={{
41
- backgroundColor: srgbToHex(tokens.backgroundSunken.srgb),
42
- border: `1px solid ${srgbToHex(tokens.border.srgb)}`,
43
- borderRadius: 8,
44
- padding: 16,
45
- paddingRight: 80,
46
- overflow: "auto",
72
+ padding: "12px 16px",
73
+ whiteSpace: "pre-wrap",
74
+ wordBreak: "break-word",
47
75
  fontSize: 13,
48
76
  lineHeight: 1.5,
49
77
  fontFamily: "'SF Mono', 'Fira Code', 'Fira Mono', Menlo, monospace",
@@ -1,5 +1,5 @@
1
1
  import { useState, useRef, useEffect, useCallback } from "react";
2
- import { useTokens } from "@newtonedev/components";
2
+ import { useTokens, Icon } from "@newtonedev/components";
3
3
  import { srgbToHex } from "newtone";
4
4
  import type { Preset } from "../types";
5
5
  import { presetHasUnpublishedChanges } from "../utils/presets";
@@ -145,21 +145,15 @@ export function PresetSelector({
145
145
  >
146
146
  {activePreset?.name ?? "Default"}
147
147
  </span>
148
- <svg
149
- width={10}
150
- height={10}
151
- viewBox="0 0 24 24"
152
- fill="none"
153
- stroke="currentColor"
154
- strokeWidth={2}
148
+ <Icon
149
+ name="expand_more"
150
+ size={14}
155
151
  style={{
156
152
  transform: isOpen ? "rotate(180deg)" : "none",
157
153
  transition: "transform 150ms ease",
158
154
  flexShrink: 0,
159
- }}
160
- >
161
- <polyline points="6 9 12 15 18 9" />
162
- </svg>
155
+ } as any}
156
+ />
163
157
  </button>
164
158
 
165
159
  {isOpen && (
@@ -303,16 +297,7 @@ export function PresetSelector({
303
297
  flexShrink: 0,
304
298
  }}
305
299
  >
306
- <svg
307
- width={14}
308
- height={14}
309
- viewBox="0 0 24 24"
310
- fill="currentColor"
311
- >
312
- <circle cx={12} cy={5} r={2} />
313
- <circle cx={12} cy={12} r={2} />
314
- <circle cx={12} cy={19} r={2} />
315
- </svg>
300
+ <Icon name="more_vert" size={14} color={textSecondary} />
316
301
  </button>
317
302
  )}
318
303
  </>
@@ -435,17 +420,7 @@ export function PresetSelector({
435
420
  cursor: "pointer",
436
421
  }}
437
422
  >
438
- <svg
439
- width={14}
440
- height={14}
441
- viewBox="0 0 24 24"
442
- fill="none"
443
- stroke="currentColor"
444
- strokeWidth={2}
445
- >
446
- <line x1={12} y1={5} x2={12} y2={19} />
447
- <line x1={5} y1={12} x2={19} y2={12} />
448
- </svg>
423
+ <Icon name="add" size={14} color={textSecondary} />
449
424
  New preset
450
425
  </button>
451
426
  </div>
@@ -9,17 +9,19 @@ import type { PreviewView } from "../types";
9
9
  interface PreviewWindowProps {
10
10
  readonly view: PreviewView;
11
11
  readonly selectedVariantId: string | null;
12
- readonly propOverrides?: Record<string, unknown>;
13
12
  readonly onNavigate: (view: PreviewView) => void;
14
13
  readonly onSelectVariant: (variantId: string) => void;
14
+ readonly propOverrides?: Record<string, unknown>;
15
+ readonly onPropOverride?: (name: string, value: unknown) => void;
15
16
  }
16
17
 
17
18
  export function PreviewWindow({
18
19
  view,
19
20
  selectedVariantId,
20
- propOverrides,
21
21
  onNavigate,
22
22
  onSelectVariant,
23
+ propOverrides,
24
+ onPropOverride,
23
25
  }: PreviewWindowProps) {
24
26
  const tokens = useTokens();
25
27
 
@@ -59,8 +61,9 @@ export function PreviewWindow({
59
61
  <ComponentDetailView
60
62
  componentId={view.componentId}
61
63
  selectedVariantId={selectedVariantId}
62
- propOverrides={propOverrides}
63
64
  onSelectVariant={onSelectVariant}
65
+ propOverrides={propOverrides}
66
+ onPropOverride={onPropOverride}
64
67
  />
65
68
  )}
66
69
  </div>
@@ -1,8 +1,9 @@
1
1
  import { useCallback, type KeyboardEvent } from "react";
2
- import { useTokens, getComponent, generateComponentCode, Select } from "@newtonedev/components";
3
- import type { EditableProp } from "@newtonedev/components";
2
+ import { useTokens, getComponent, generateComponentCode, Select, NewtoneProvider, Icon } from "@newtonedev/components";
3
+ import type { EditableProp, NewtoneThemeConfig, ColorMode } from "@newtonedev/components";
4
4
  import { srgbToHex } from "newtone";
5
5
  import { CodeBlock } from "./CodeBlock";
6
+ import { ComponentRenderer } from "../preview/ComponentRenderer";
6
7
  import type { SidebarSelection } from "../types";
7
8
 
8
9
  interface RightSidebarProps {
@@ -12,6 +13,8 @@ interface RightSidebarProps {
12
13
  readonly onResetOverrides: () => void;
13
14
  readonly onClose: () => void;
14
15
  readonly onScopeToComponent: () => void;
16
+ readonly previewConfig: NewtoneThemeConfig;
17
+ readonly colorMode: ColorMode;
15
18
  }
16
19
 
17
20
  export function RightSidebar({
@@ -21,6 +24,8 @@ export function RightSidebar({
21
24
  onResetOverrides,
22
25
  onClose,
23
26
  onScopeToComponent,
27
+ previewConfig,
28
+ colorMode,
24
29
  }: RightSidebarProps) {
25
30
  const tokens = useTokens();
26
31
  const visible = selection !== null;
@@ -79,19 +84,7 @@ export function RightSidebar({
79
84
  alignItems: "center",
80
85
  }}
81
86
  >
82
- <svg
83
- width={16}
84
- height={16}
85
- viewBox="0 0 24 24"
86
- fill="none"
87
- stroke="currentColor"
88
- strokeWidth={2}
89
- strokeLinecap="round"
90
- strokeLinejoin="round"
91
- >
92
- <line x1="19" y1="12" x2="5" y2="12" />
93
- <polyline points="12 19 5 12 12 5" />
94
- </svg>
87
+ <Icon name="arrow_back" size={16} color={srgbToHex(tokens.textSecondary.srgb)} />
95
88
  </button>
96
89
  {selection.scope === "variant" && variant ? (
97
90
  <>
@@ -154,6 +147,27 @@ export function RightSidebar({
154
147
  padding: 16,
155
148
  }}
156
149
  >
150
+ {/* Live Preview */}
151
+ <div
152
+ style={{
153
+ marginBottom: 20,
154
+ borderRadius: 8,
155
+ border: `1px solid ${srgbToHex(tokens.border.srgb)}`,
156
+ overflow: "hidden",
157
+ }}
158
+ >
159
+ <NewtoneProvider
160
+ config={previewConfig}
161
+ initialMode={colorMode}
162
+ key={colorMode}
163
+ >
164
+ <PreviewSurface
165
+ componentId={selection.componentId}
166
+ propOverrides={propOverrides}
167
+ />
168
+ </NewtoneProvider>
169
+ </div>
170
+
157
171
  <h3
158
172
  style={{
159
173
  fontSize: 13,
@@ -222,6 +236,31 @@ export function RightSidebar({
222
236
  );
223
237
  }
224
238
 
239
+ function PreviewSurface({
240
+ componentId,
241
+ propOverrides,
242
+ }: {
243
+ readonly componentId: string;
244
+ readonly propOverrides: Record<string, unknown>;
245
+ }) {
246
+ const previewTokens = useTokens();
247
+
248
+ return (
249
+ <div
250
+ style={{
251
+ display: "flex",
252
+ alignItems: "center",
253
+ justifyContent: "center",
254
+ padding: 24,
255
+ height: 120,
256
+ backgroundColor: srgbToHex(previewTokens.backgroundElevated.srgb),
257
+ }}
258
+ >
259
+ <ComponentRenderer componentId={componentId} props={propOverrides} />
260
+ </div>
261
+ );
262
+ }
263
+
225
264
  function PropControl({
226
265
  prop,
227
266
  value,
@@ -257,14 +296,7 @@ function PropControl({
257
296
 
258
297
  return (
259
298
  <div>
260
- <div
261
- style={{
262
- display: "flex",
263
- alignItems: "center",
264
- justifyContent: "space-between",
265
- marginBottom: 4,
266
- }}
267
- >
299
+ <div style={{ marginBottom: 4 }}>
268
300
  <span
269
301
  style={{
270
302
  fontSize: 12,
@@ -274,15 +306,6 @@ function PropControl({
274
306
  >
275
307
  {prop.label}
276
308
  </span>
277
- <span
278
- style={{
279
- fontSize: 11,
280
- color: srgbToHex(tokens.textSecondary.srgb),
281
- fontFamily: "'SF Mono', 'Fira Code', Menlo, monospace",
282
- }}
283
- >
284
- {prop.control}
285
- </span>
286
309
  </div>
287
310
 
288
311
  {prop.control === "select" && prop.options && (
@@ -318,6 +341,54 @@ function PropControl({
318
341
  />
319
342
  )}
320
343
 
344
+ {prop.control === "discrete-slider" && prop.options && (() => {
345
+ const options = prop.options!;
346
+ const currentIndex = options.findIndex((o) => o.value === value);
347
+ const idx = currentIndex >= 0 ? currentIndex : 0;
348
+
349
+ return (
350
+ <div>
351
+ <input
352
+ type="range"
353
+ min={0}
354
+ max={options.length - 1}
355
+ step={1}
356
+ value={idx}
357
+ onChange={(e) => onChange(options[Number(e.target.value)].value)}
358
+ aria-label={prop.label}
359
+ style={{
360
+ width: "100%",
361
+ accentColor: srgbToHex(tokens.accent.fill.srgb),
362
+ cursor: "pointer",
363
+ }}
364
+ />
365
+ <div
366
+ style={{
367
+ display: "flex",
368
+ justifyContent: "space-between",
369
+ marginTop: 2,
370
+ }}
371
+ >
372
+ {options.map((o) => (
373
+ <span
374
+ key={String(o.value)}
375
+ style={{
376
+ fontSize: 11,
377
+ fontFamily: "'SF Mono', 'Fira Code', Menlo, monospace",
378
+ color: o.value === value
379
+ ? srgbToHex(tokens.textPrimary.srgb)
380
+ : srgbToHex(tokens.textTertiary.srgb),
381
+ fontWeight: o.value === value ? 600 : 400,
382
+ }}
383
+ >
384
+ {o.label}
385
+ </span>
386
+ ))}
387
+ </div>
388
+ </div>
389
+ );
390
+ })()}
391
+
321
392
  {prop.control === "toggle" && (
322
393
  <div
323
394
  role="switch"
@@ -359,14 +430,6 @@ function PropControl({
359
430
  }}
360
431
  />
361
432
  </div>
362
- <span
363
- style={{
364
- fontSize: 12,
365
- color: srgbToHex(tokens.textSecondary.srgb),
366
- }}
367
- >
368
- {value ? "true" : "false"}
369
- </span>
370
433
  </div>
371
434
  )}
372
435
  </div>
@@ -1,5 +1,5 @@
1
1
  import { useState } from "react";
2
- import { useTokens } from "@newtonedev/components";
2
+ import { useTokens, Icon } from "@newtonedev/components";
3
3
  import type { ColorMode } from "@newtonedev/components";
4
4
  import { srgbToHex } from "newtone";
5
5
  import type { ColorResult } from "newtone";
@@ -18,87 +18,13 @@ import type { Preset } from "../types";
18
18
  const SIDEBAR_WIDTH = 360;
19
19
 
20
20
  const ACCORDION_SECTIONS = [
21
- { id: "dynamic-range", label: "Dynamic Range" },
22
- { id: "colors", label: "Colors" },
23
- { id: "fonts", label: "Fonts" },
24
- { id: "icons", label: "Icons" },
25
- { id: "others", label: "Others" },
21
+ { id: "dynamic-range", label: "Dynamic Range", icon: "contrast" },
22
+ { id: "colors", label: "Colors", icon: "palette" },
23
+ { id: "fonts", label: "Fonts", icon: "text_fields" },
24
+ { id: "icons", label: "Icons", icon: "grid_view" },
25
+ { id: "others", label: "Others", icon: "tune" },
26
26
  ] as const;
27
27
 
28
- function SectionIcon({ id }: { readonly id: string }) {
29
- const props = {
30
- width: 16,
31
- height: 16,
32
- viewBox: "0 0 24 24",
33
- fill: "none",
34
- stroke: "currentColor",
35
- strokeWidth: 2,
36
- strokeLinecap: "round" as const,
37
- strokeLinejoin: "round" as const,
38
- };
39
-
40
- switch (id) {
41
- case "dynamic-range":
42
- // Sun/contrast icon
43
- return (
44
- <svg {...props}>
45
- <circle cx="12" cy="12" r="5" />
46
- <line x1="12" y1="1" x2="12" y2="3" />
47
- <line x1="12" y1="21" x2="12" y2="23" />
48
- <line x1="4.22" y1="4.22" x2="5.64" y2="5.64" />
49
- <line x1="18.36" y1="18.36" x2="19.78" y2="19.78" />
50
- <line x1="1" y1="12" x2="3" y2="12" />
51
- <line x1="21" y1="12" x2="23" y2="12" />
52
- <line x1="4.22" y1="19.78" x2="5.64" y2="18.36" />
53
- <line x1="18.36" y1="5.64" x2="19.78" y2="4.22" />
54
- </svg>
55
- );
56
- case "colors":
57
- // Palette/droplet icon
58
- return (
59
- <svg {...props}>
60
- <path d="M12 2.69l5.66 5.66a8 8 0 1 1-11.31 0z" />
61
- </svg>
62
- );
63
- case "fonts":
64
- // Type/text icon
65
- return (
66
- <svg {...props}>
67
- <polyline points="4 7 4 4 20 4 20 7" />
68
- <line x1="9" y1="20" x2="15" y2="20" />
69
- <line x1="12" y1="4" x2="12" y2="20" />
70
- </svg>
71
- );
72
- case "icons":
73
- // Grid icon
74
- return (
75
- <svg {...props}>
76
- <rect x="3" y="3" width="7" height="7" />
77
- <rect x="14" y="3" width="7" height="7" />
78
- <rect x="3" y="14" width="7" height="7" />
79
- <rect x="14" y="14" width="7" height="7" />
80
- </svg>
81
- );
82
- case "others":
83
- // Sliders icon
84
- return (
85
- <svg {...props}>
86
- <line x1="4" y1="21" x2="4" y2="14" />
87
- <line x1="4" y1="10" x2="4" y2="3" />
88
- <line x1="12" y1="21" x2="12" y2="12" />
89
- <line x1="12" y1="8" x2="12" y2="3" />
90
- <line x1="20" y1="21" x2="20" y2="16" />
91
- <line x1="20" y1="12" x2="20" y2="3" />
92
- <line x1="1" y1="14" x2="7" y2="14" />
93
- <line x1="9" y1="8" x2="15" y2="8" />
94
- <line x1="17" y1="16" x2="23" y2="16" />
95
- </svg>
96
- );
97
- default:
98
- return null;
99
- }
100
- }
101
-
102
28
  interface SidebarProps {
103
29
  readonly state: ConfiguratorState;
104
30
  readonly dispatch: (action: ConfiguratorAction) => void;
@@ -262,23 +188,17 @@ export function Sidebar({
262
188
  }}
263
189
  >
264
190
  <span style={{ display: "flex", alignItems: "center", gap: 8 }}>
265
- <SectionIcon id={section.id} />
191
+ <Icon name={section.icon} size={16} />
266
192
  {section.label}
267
193
  </span>
268
- <svg
269
- width={12}
270
- height={12}
271
- viewBox="0 0 24 24"
272
- fill="none"
273
- stroke="currentColor"
274
- strokeWidth={2}
194
+ <Icon
195
+ name="expand_more"
196
+ size={16}
275
197
  style={{
276
198
  transform: isOpen ? "rotate(180deg)" : "none",
277
199
  transition: "transform 150ms ease",
278
- }}
279
- >
280
- <polyline points="6 9 12 15 18 9" />
281
- </svg>
200
+ } as any}
201
+ />
282
202
  </button>
283
203
  {isOpen && (
284
204
  <div
@@ -201,9 +201,20 @@ export function useEditorState({
201
201
  }
202
202
  }, [sidebarSelection, initOverridesFromVariant]);
203
203
 
204
- const handlePropOverride = useCallback((propName: string, value: unknown) => {
205
- setPropOverrides((prev) => ({ ...prev, [propName]: value }));
206
- }, []);
204
+ const handlePropOverride = useCallback(
205
+ (propName: string, value: unknown) => {
206
+ setPropOverrides((prev) => ({ ...prev, [propName]: value }));
207
+ // Re-open sidebar if closed while viewing a component (e.g. icon browser click)
208
+ setSidebarSelection((prev) => {
209
+ if (prev !== null) return prev;
210
+ if (previewView.kind === "component") {
211
+ return { scope: "component", componentId: previewView.componentId };
212
+ }
213
+ return prev;
214
+ });
215
+ },
216
+ [previewView],
217
+ );
207
218
 
208
219
  const handleResetOverrides = useCallback(() => {
209
220
  if (sidebarSelection?.scope === "variant") {