sonance-brand-mcp 1.3.110 → 1.3.111
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/assets/api/sonance-ai-edit/route.ts +30 -7
- package/dist/assets/api/sonance-vision-apply/route.ts +33 -8
- package/dist/assets/api/sonance-vision-edit/route.ts +33 -8
- package/dist/assets/dev-tools/SonanceDevTools.tsx +441 -365
- package/dist/assets/dev-tools/components/ChatHistory.tsx +141 -0
- package/dist/assets/dev-tools/components/ChatInterface.tsx +402 -294
- package/dist/assets/dev-tools/components/ChatTabBar.tsx +82 -0
- package/dist/assets/dev-tools/components/InlineDiffPreview.tsx +204 -0
- package/dist/assets/dev-tools/components/InspectorOverlay.tsx +12 -9
- package/dist/assets/dev-tools/components/PropertiesPanel.tsx +695 -0
- package/dist/assets/dev-tools/components/VisionModeBorder.tsx +16 -7
- package/dist/assets/dev-tools/constants.ts +38 -6
- package/dist/assets/dev-tools/hooks/useComputedStyles.ts +365 -0
- package/dist/assets/dev-tools/index.ts +3 -0
- package/dist/assets/dev-tools/panels/AnalysisPanel.tsx +32 -32
- package/dist/assets/dev-tools/panels/ComponentsPanel.tsx +277 -127
- package/dist/assets/dev-tools/types.ts +51 -2
- package/dist/index.js +22 -3
- package/package.json +2 -1
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import React, { useState, useEffect, useCallback, useRef, useMemo } from "react";
|
|
4
4
|
import { createPortal } from "react-dom";
|
|
5
|
-
import { Palette, X, Copy, Check, RotateCcw, ChevronDown, Save, Loader2, AlertCircle, CheckCircle, Sun, Moon, Eye, EyeOff, Zap, Image as ImageIcon, Wand2, Scan, FileCode, Tag, Type, MousePointer, FormInput, Box, Search, Send, Sparkles, RefreshCw, GripHorizontal } from "lucide-react";
|
|
5
|
+
import { Palette, X, Copy, Check, RotateCcw, ChevronDown, Save, Loader2, AlertCircle, CheckCircle, Sun, Moon, Eye, EyeOff, Zap, Image as ImageIcon, Wand2, Scan, FileCode, Tag, Type, MousePointer, FormInput, Box, Search, Send, Sparkles, RefreshCw, GripHorizontal, PanelRightClose, PanelRightOpen, ChevronUp, ChevronDown as ChevronDownIcon } from "lucide-react";
|
|
6
6
|
import { useTheme } from "next-themes";
|
|
7
7
|
import { cn } from "../../lib/utils";
|
|
8
8
|
import {
|
|
@@ -49,9 +49,10 @@ import {
|
|
|
49
49
|
TextOverride,
|
|
50
50
|
OriginalTextState,
|
|
51
51
|
ParentSectionInfo,
|
|
52
|
+
ElementFilters,
|
|
53
|
+
SelectedElementType,
|
|
52
54
|
} from "./types";
|
|
53
55
|
import {
|
|
54
|
-
tabs,
|
|
55
56
|
QUICK_ACTIONS,
|
|
56
57
|
SONANCE_PREVIEW_STYLE_ID,
|
|
57
58
|
TAILWIND_TO_CSS,
|
|
@@ -59,6 +60,8 @@ import {
|
|
|
59
60
|
COMPONENT_CONFIG_MAP,
|
|
60
61
|
getVisibleSections,
|
|
61
62
|
shouldShowScopeOptions,
|
|
63
|
+
DEFAULT_ELEMENT_FILTERS,
|
|
64
|
+
ELEMENT_TYPE_COLORS,
|
|
62
65
|
} from "./constants";
|
|
63
66
|
import {
|
|
64
67
|
generateComponentCSS,
|
|
@@ -180,9 +183,16 @@ function findParentSection(element: Element): ParentSectionInfo | undefined {
|
|
|
180
183
|
|
|
181
184
|
// ---- Main Component ----
|
|
182
185
|
|
|
186
|
+
// Frame layout dimensions
|
|
187
|
+
const BANNER_HEIGHT = 44;
|
|
188
|
+
const SIDEBAR_WIDTH = 260;
|
|
189
|
+
const SIDEBAR_COLLAPSED_WIDTH = 48;
|
|
190
|
+
|
|
183
191
|
export function SonanceDevTools() {
|
|
184
|
-
|
|
185
|
-
const [
|
|
192
|
+
// Frame is always visible in dev mode - start open by default
|
|
193
|
+
const [isOpen, setIsOpen] = useState(true);
|
|
194
|
+
const [isSidebarCollapsed, setIsSidebarCollapsed] = useState(false);
|
|
195
|
+
const [activeTab, setActiveTab] = useState<TabId>("elements");
|
|
186
196
|
const [config, setConfig] = useState<ThemeConfig>(defaultThemeConfig);
|
|
187
197
|
const [mounted, setMounted] = useState(false);
|
|
188
198
|
const [copiedId, setCopiedId] = useState<string | null>(null);
|
|
@@ -190,6 +200,11 @@ export function SonanceDevTools() {
|
|
|
190
200
|
const [saveMessage, setSaveMessage] = useState<string>("");
|
|
191
201
|
const [installedComponents, setInstalledComponents] = useState<string[]>([]);
|
|
192
202
|
|
|
203
|
+
// Unified element filters - show all element types with color coding
|
|
204
|
+
const [elementFilters, setElementFilters] = useState<ElementFilters>(DEFAULT_ELEMENT_FILTERS);
|
|
205
|
+
// Selected element determines what the sidebar shows
|
|
206
|
+
const [selectedElementType, setSelectedElementType] = useState<SelectedElementType>(null);
|
|
207
|
+
|
|
193
208
|
// Visual Inspector state - single toggle, behavior depends on active tab
|
|
194
209
|
const [inspectorEnabled, setInspectorEnabled] = useState(false);
|
|
195
210
|
const [selectedComponentType, setSelectedComponentType] = useState<string>("all");
|
|
@@ -215,12 +230,7 @@ export function SonanceDevTools() {
|
|
|
215
230
|
// Track which elements were changed (for highlighting until accept/revert)
|
|
216
231
|
const [changedElements, setChangedElements] = useState<VisionFocusedElement[]>([]);
|
|
217
232
|
|
|
218
|
-
//
|
|
219
|
-
const DEVTOOLS_POSITION_KEY = "sonance-devtools-pos";
|
|
220
|
-
const DEFAULT_POSITION = { x: 0, y: 0 }; // Offset from default bottom-right position
|
|
221
|
-
const [dragPosition, setDragPosition] = useState(DEFAULT_POSITION);
|
|
222
|
-
const [isDragging, setIsDragging] = useState(false);
|
|
223
|
-
const dragOffsetRef = useRef({ x: 0, y: 0 });
|
|
233
|
+
// Refs for panel
|
|
224
234
|
const panelRef = useRef<HTMLDivElement>(null);
|
|
225
235
|
|
|
226
236
|
// Portal container state - ensures DevTool is always on top of modals
|
|
@@ -369,21 +379,74 @@ export function SonanceDevTools() {
|
|
|
369
379
|
setMounted(true);
|
|
370
380
|
}, []);
|
|
371
381
|
|
|
372
|
-
//
|
|
382
|
+
// Body layout injection for "squeeze" effect - app content respects dev tools frame
|
|
373
383
|
useEffect(() => {
|
|
374
384
|
if (!mounted) return;
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
385
|
+
|
|
386
|
+
const root = document.documentElement;
|
|
387
|
+
const body = document.body;
|
|
388
|
+
const styleId = 'sonance-devtools-layout-styles';
|
|
389
|
+
|
|
390
|
+
if (isOpen) {
|
|
391
|
+
const sidebarWidth = isSidebarCollapsed ? SIDEBAR_COLLAPSED_WIDTH : SIDEBAR_WIDTH;
|
|
392
|
+
|
|
393
|
+
// Set CSS variables for components that need to reference the offset
|
|
394
|
+
root.style.setProperty('--devtools-banner-height', `${BANNER_HEIGHT}px`);
|
|
395
|
+
root.style.setProperty('--devtools-sidebar-width', `${sidebarWidth}px`);
|
|
396
|
+
|
|
397
|
+
// Inject styles that adjust fixed elements and body layout
|
|
398
|
+
let styleEl = document.getElementById(styleId) as HTMLStyleElement;
|
|
399
|
+
if (!styleEl) {
|
|
400
|
+
styleEl = document.createElement('style');
|
|
401
|
+
styleEl.id = styleId;
|
|
402
|
+
document.head.appendChild(styleEl);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// CSS that creates space for the DevTools
|
|
406
|
+
// Components that read --devtools-banner-height and --devtools-sidebar-width will adjust automatically
|
|
407
|
+
styleEl.textContent = `
|
|
408
|
+
/* Ensure body/html don't overflow */
|
|
409
|
+
html, body {
|
|
410
|
+
margin: 0 !important;
|
|
411
|
+
padding: 0 !important;
|
|
412
|
+
overflow-x: hidden;
|
|
381
413
|
}
|
|
414
|
+
|
|
415
|
+
/* Push the main content wrapper down */
|
|
416
|
+
body > div:first-child:not([data-sonance-devtools="true"]) {
|
|
417
|
+
padding-top: ${BANNER_HEIGHT}px !important;
|
|
418
|
+
margin-right: ${sidebarWidth}px !important;
|
|
419
|
+
min-height: 100vh;
|
|
420
|
+
box-sizing: border-box;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/* Ensure main content areas respect the sidebar width */
|
|
424
|
+
main:not([data-sonance-devtools] *) {
|
|
425
|
+
max-width: calc(100vw - 256px - ${sidebarWidth}px) !important;
|
|
426
|
+
}
|
|
427
|
+
`;
|
|
428
|
+
} else {
|
|
429
|
+
// Reset everything when closed
|
|
430
|
+
root.style.removeProperty('--devtools-banner-height');
|
|
431
|
+
root.style.removeProperty('--devtools-sidebar-width');
|
|
432
|
+
|
|
433
|
+
const styleEl = document.getElementById(styleId);
|
|
434
|
+
if (styleEl) {
|
|
435
|
+
styleEl.remove();
|
|
382
436
|
}
|
|
383
|
-
} catch {
|
|
384
|
-
// Ignore parse errors
|
|
385
437
|
}
|
|
386
|
-
|
|
438
|
+
|
|
439
|
+
return () => {
|
|
440
|
+
// Cleanup on unmount
|
|
441
|
+
root.style.removeProperty('--devtools-banner-height');
|
|
442
|
+
root.style.removeProperty('--devtools-sidebar-width');
|
|
443
|
+
|
|
444
|
+
const styleEl = document.getElementById(styleId);
|
|
445
|
+
if (styleEl) {
|
|
446
|
+
styleEl.remove();
|
|
447
|
+
}
|
|
448
|
+
};
|
|
449
|
+
}, [mounted, isOpen, isSidebarCollapsed]);
|
|
387
450
|
|
|
388
451
|
// Restore apply-first session from localStorage on mount
|
|
389
452
|
// This allows the Accept/Revert UI to survive page refreshes
|
|
@@ -420,77 +483,6 @@ export function SonanceDevTools() {
|
|
|
420
483
|
}
|
|
421
484
|
}, [mounted]);
|
|
422
485
|
|
|
423
|
-
// Drag handlers for movable panel
|
|
424
|
-
const headerRef = useRef<HTMLDivElement>(null);
|
|
425
|
-
|
|
426
|
-
const handleDragStart = useCallback((e: React.PointerEvent<HTMLDivElement>) => {
|
|
427
|
-
// Don't start drag if clicking on a button or interactive element
|
|
428
|
-
const target = e.target as HTMLElement;
|
|
429
|
-
if (target.closest('button')) return;
|
|
430
|
-
|
|
431
|
-
if (!panelRef.current || !headerRef.current) return;
|
|
432
|
-
|
|
433
|
-
// Prevent text selection during drag
|
|
434
|
-
e.preventDefault();
|
|
435
|
-
|
|
436
|
-
// Store the initial mouse position and current panel offset
|
|
437
|
-
dragOffsetRef.current = {
|
|
438
|
-
x: e.clientX - dragPosition.x,
|
|
439
|
-
y: e.clientY - dragPosition.y,
|
|
440
|
-
};
|
|
441
|
-
|
|
442
|
-
setIsDragging(true);
|
|
443
|
-
headerRef.current.setPointerCapture(e.pointerId);
|
|
444
|
-
}, [dragPosition]);
|
|
445
|
-
|
|
446
|
-
const handleDragMove = useCallback((e: React.PointerEvent<HTMLDivElement>) => {
|
|
447
|
-
if (!isDragging || !panelRef.current) return;
|
|
448
|
-
|
|
449
|
-
const viewportWidth = window.innerWidth;
|
|
450
|
-
const viewportHeight = window.innerHeight;
|
|
451
|
-
const panelWidth = panelRef.current.offsetWidth;
|
|
452
|
-
const panelHeight = panelRef.current.offsetHeight;
|
|
453
|
-
|
|
454
|
-
// Calculate new position directly from mouse movement
|
|
455
|
-
const newX = e.clientX - dragOffsetRef.current.x;
|
|
456
|
-
const newY = e.clientY - dragOffsetRef.current.y;
|
|
457
|
-
|
|
458
|
-
// Clamp to keep panel within viewport (with 24px padding)
|
|
459
|
-
const padding = 24;
|
|
460
|
-
const maxX = viewportWidth - panelWidth - padding;
|
|
461
|
-
const maxY = viewportHeight - panelHeight - padding;
|
|
462
|
-
const minX = -(viewportWidth - panelWidth - padding);
|
|
463
|
-
const minY = -(viewportHeight - panelHeight - padding);
|
|
464
|
-
|
|
465
|
-
setDragPosition({
|
|
466
|
-
x: Math.max(minX, Math.min(maxX, newX)),
|
|
467
|
-
y: Math.max(minY, Math.min(maxY, newY)),
|
|
468
|
-
});
|
|
469
|
-
}, [isDragging]);
|
|
470
|
-
|
|
471
|
-
const handleDragEnd = useCallback((e: React.PointerEvent<HTMLDivElement>) => {
|
|
472
|
-
if (!isDragging || !headerRef.current) return;
|
|
473
|
-
|
|
474
|
-
setIsDragging(false);
|
|
475
|
-
headerRef.current.releasePointerCapture(e.pointerId);
|
|
476
|
-
|
|
477
|
-
// Save position to localStorage
|
|
478
|
-
try {
|
|
479
|
-
localStorage.setItem(DEVTOOLS_POSITION_KEY, JSON.stringify(dragPosition));
|
|
480
|
-
} catch {
|
|
481
|
-
// Ignore storage errors
|
|
482
|
-
}
|
|
483
|
-
}, [isDragging, dragPosition, DEVTOOLS_POSITION_KEY]);
|
|
484
|
-
|
|
485
|
-
const handleResetPosition = useCallback(() => {
|
|
486
|
-
setDragPosition(DEFAULT_POSITION);
|
|
487
|
-
try {
|
|
488
|
-
localStorage.removeItem(DEVTOOLS_POSITION_KEY);
|
|
489
|
-
} catch {
|
|
490
|
-
// Ignore storage errors
|
|
491
|
-
}
|
|
492
|
-
}, [DEVTOOLS_POSITION_KEY]);
|
|
493
|
-
|
|
494
486
|
// Inject/update component-specific preview styles whenever overrides change
|
|
495
487
|
useEffect(() => {
|
|
496
488
|
if (!mounted) return;
|
|
@@ -659,10 +651,10 @@ export function SonanceDevTools() {
|
|
|
659
651
|
}
|
|
660
652
|
}
|
|
661
653
|
|
|
662
|
-
if (mounted && (
|
|
654
|
+
if (mounted && (elementFilters.images || inspectorEnabled) && logoAssets.length === 0) {
|
|
663
655
|
fetchLogoAssets();
|
|
664
656
|
}
|
|
665
|
-
}, [mounted,
|
|
657
|
+
}, [mounted, elementFilters.images, inspectorEnabled, logoAssets.length]);
|
|
666
658
|
|
|
667
659
|
// Helper to extract logo filename from src (handles next/image optimized URLs)
|
|
668
660
|
const extractLogoName = useCallback((src: string): string | null => {
|
|
@@ -759,21 +751,15 @@ export function SonanceDevTools() {
|
|
|
759
751
|
// Filter elements for InspectorOverlay based on active tab and selected component type
|
|
760
752
|
// Now supports both specific component IDs (e.g., "button-primary") and categories (e.g., "buttons")
|
|
761
753
|
const filteredOverlayElements = useMemo(() => {
|
|
762
|
-
// Filter based on
|
|
763
|
-
let filtered = taggedElements
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
}
|
|
769
|
-
// On text tab, only show text elements
|
|
770
|
-
filtered = taggedElements.filter(el => el.type === "text");
|
|
771
|
-
} else if (activeTab === "components") {
|
|
772
|
-
// On components tab, only show components
|
|
773
|
-
filtered = taggedElements.filter(el => el.type === "component");
|
|
774
|
-
}
|
|
754
|
+
// Filter based on element type filters (unified color-coded view)
|
|
755
|
+
let filtered = taggedElements.filter(el => {
|
|
756
|
+
if (el.type === "component" && !elementFilters.components) return false;
|
|
757
|
+
if (el.type === "logo" && !elementFilters.images) return false;
|
|
758
|
+
if (el.type === "text" && !elementFilters.text) return false;
|
|
759
|
+
return true;
|
|
760
|
+
});
|
|
775
761
|
|
|
776
|
-
// When inspector is enabled, show
|
|
762
|
+
// When inspector is enabled, show all filtered elements
|
|
777
763
|
if (inspectorEnabled) {
|
|
778
764
|
return filtered;
|
|
779
765
|
}
|
|
@@ -781,19 +767,19 @@ export function SonanceDevTools() {
|
|
|
781
767
|
// When viewing inspector mode for a selected component, show ALL elements
|
|
782
768
|
// (the InspectorOverlay will handle highlighting/dimming based on type match)
|
|
783
769
|
if (viewMode === "inspector" && selectedComponentType !== "all") {
|
|
784
|
-
return
|
|
770
|
+
return filtered;
|
|
785
771
|
}
|
|
786
772
|
|
|
787
773
|
if (selectedComponentType === "all") {
|
|
788
|
-
return
|
|
774
|
+
return filtered;
|
|
789
775
|
}
|
|
790
776
|
|
|
791
777
|
// Check if this is a specific component ID (exists in componentSnippets)
|
|
792
778
|
const isSpecificComponent = componentSnippets.some(s => s.id === selectedComponentType);
|
|
793
779
|
|
|
794
780
|
// Filter components based on selected type
|
|
795
|
-
return
|
|
796
|
-
// Always include logos and text
|
|
781
|
+
return filtered.filter((el) => {
|
|
782
|
+
// Always include logos and text (they're already filtered by elementFilters)
|
|
797
783
|
if (el.type === "logo" || el.type === "text") return true;
|
|
798
784
|
|
|
799
785
|
// For component type filtering
|
|
@@ -832,7 +818,7 @@ export function SonanceDevTools() {
|
|
|
832
818
|
|
|
833
819
|
return true;
|
|
834
820
|
});
|
|
835
|
-
}, [taggedElements, selectedComponentType, inspectorEnabled, viewMode]);
|
|
821
|
+
}, [taggedElements, selectedComponentType, inspectorEnabled, viewMode, elementFilters]);
|
|
836
822
|
|
|
837
823
|
// Simple string hash function for variant detection
|
|
838
824
|
const generateHash = (str: string): string => {
|
|
@@ -848,9 +834,8 @@ export function SonanceDevTools() {
|
|
|
848
834
|
|
|
849
835
|
// Visual Inspector: Scan for tagged elements, logos, and text, update positions
|
|
850
836
|
useEffect(() => {
|
|
851
|
-
// Scan
|
|
852
|
-
|
|
853
|
-
const shouldScan = inspectorEnabled || activeTab === "components" || activeTab === "logos" || activeTab === "text";
|
|
837
|
+
// Scan when inspector is enabled or on elements tab - now unified with filters
|
|
838
|
+
const shouldScan = inspectorEnabled || activeTab === "elements";
|
|
854
839
|
|
|
855
840
|
// console.log("Scan effect running. shouldScan:", shouldScan, "activeTab:", activeTab);
|
|
856
841
|
|
|
@@ -906,8 +891,8 @@ export function SonanceDevTools() {
|
|
|
906
891
|
return true;
|
|
907
892
|
};
|
|
908
893
|
|
|
909
|
-
// Scan for tagged components
|
|
910
|
-
if (inspectorEnabled || activeTab === "
|
|
894
|
+
// Scan for tagged components (always scan when filters allow)
|
|
895
|
+
if ((inspectorEnabled || activeTab === "elements") && elementFilters.components) {
|
|
911
896
|
// 1. Scan for explicitly tagged components
|
|
912
897
|
const taggedComponents = document.querySelectorAll("[data-sonance-name]");
|
|
913
898
|
taggedComponents.forEach((el) => {
|
|
@@ -997,8 +982,8 @@ export function SonanceDevTools() {
|
|
|
997
982
|
});
|
|
998
983
|
}
|
|
999
984
|
|
|
1000
|
-
// Scan for logo
|
|
1001
|
-
if (
|
|
985
|
+
// Scan for logo/image elements (always scan when filters allow)
|
|
986
|
+
if (elementFilters.images) {
|
|
1002
987
|
const images = document.querySelectorAll("img");
|
|
1003
988
|
images.forEach((img) => {
|
|
1004
989
|
// Skip elements outside the active layer (DevTools, behind modals)
|
|
@@ -1047,9 +1032,8 @@ export function SonanceDevTools() {
|
|
|
1047
1032
|
});
|
|
1048
1033
|
}
|
|
1049
1034
|
|
|
1050
|
-
// Scan for text elements
|
|
1051
|
-
|
|
1052
|
-
if (activeTab === "text" && inspectorEnabled) {
|
|
1035
|
+
// Scan for text elements (always scan when filters allow and inspector enabled)
|
|
1036
|
+
if (elementFilters.text && inspectorEnabled) {
|
|
1053
1037
|
// Only select meaningful text containers - exclude spans as they're usually nested
|
|
1054
1038
|
const textSelectors = "h1, h2, h3, h4, h5, h6, p, a, label, blockquote, figcaption, li";
|
|
1055
1039
|
const textElements = document.querySelectorAll(textSelectors);
|
|
@@ -1148,7 +1132,7 @@ export function SonanceDevTools() {
|
|
|
1148
1132
|
inspectorRef.current = null;
|
|
1149
1133
|
}
|
|
1150
1134
|
};
|
|
1151
|
-
}, [inspectorEnabled, mounted, extractLogoName, originalLogoStates, activeTab]);
|
|
1135
|
+
}, [inspectorEnabled, mounted, extractLogoName, originalLogoStates, activeTab, elementFilters]);
|
|
1152
1136
|
|
|
1153
1137
|
// Toggle Visual Inspector
|
|
1154
1138
|
const toggleInspector = useCallback((force?: boolean | unknown) => {
|
|
@@ -1156,20 +1140,27 @@ export function SonanceDevTools() {
|
|
|
1156
1140
|
}, []);
|
|
1157
1141
|
|
|
1158
1142
|
// Toggle Vision Mode - AI-powered page-wide editing
|
|
1143
|
+
// Inspector functionality is now integrated into Vision Mode
|
|
1144
|
+
// Vision mode and Analysis tab are mutually exclusive
|
|
1159
1145
|
const toggleVisionMode = useCallback(() => {
|
|
1160
1146
|
setVisionModeActive((prev) => {
|
|
1161
1147
|
const newValue = !prev;
|
|
1162
1148
|
if (newValue) {
|
|
1163
|
-
// When enabling vision mode,
|
|
1149
|
+
// When enabling vision mode, auto-enable inspector highlighting
|
|
1164
1150
|
setInspectorEnabled(true);
|
|
1151
|
+
// If on Analysis tab, switch to Elements tab (mutually exclusive)
|
|
1152
|
+
if (activeTab === "analysis") {
|
|
1153
|
+
setActiveTab("elements");
|
|
1154
|
+
}
|
|
1165
1155
|
} else {
|
|
1166
|
-
// When disabling, clear all vision state
|
|
1156
|
+
// When disabling, clear all vision state AND disable inspector
|
|
1157
|
+
setInspectorEnabled(false);
|
|
1167
1158
|
setVisionFocusedElements([]);
|
|
1168
1159
|
setVisionScreenshot(null);
|
|
1169
1160
|
}
|
|
1170
1161
|
return newValue;
|
|
1171
1162
|
});
|
|
1172
|
-
}, []);
|
|
1163
|
+
}, [activeTab]);
|
|
1173
1164
|
|
|
1174
1165
|
// Handle vision mode element click - toggle focus on/off
|
|
1175
1166
|
const handleVisionElementClick = useCallback((element: DetectedElement) => {
|
|
@@ -1219,13 +1210,11 @@ export function SonanceDevTools() {
|
|
|
1219
1210
|
);
|
|
1220
1211
|
|
|
1221
1212
|
if (exists) {
|
|
1222
|
-
//
|
|
1223
|
-
return
|
|
1224
|
-
(el) => !(el.name === element.name && el.variantId === element.variantId)
|
|
1225
|
-
);
|
|
1213
|
+
// Click on same element - deselect it
|
|
1214
|
+
return [];
|
|
1226
1215
|
} else {
|
|
1227
|
-
//
|
|
1228
|
-
return [
|
|
1216
|
+
// Click on different element - select only this one (single selection)
|
|
1217
|
+
return [focusedElement];
|
|
1229
1218
|
}
|
|
1230
1219
|
});
|
|
1231
1220
|
}, [visionModeActive]);
|
|
@@ -1454,14 +1443,18 @@ export function SonanceDevTools() {
|
|
|
1454
1443
|
}, [applyFirstSession]);
|
|
1455
1444
|
|
|
1456
1445
|
// Handle tab change - clear selections when switching tabs
|
|
1446
|
+
// Now simplified: just "analysis" and "elements" modes
|
|
1457
1447
|
const handleTabChange = useCallback((newTab: TabId) => {
|
|
1458
1448
|
if (newTab !== activeTab) {
|
|
1459
|
-
// Clear
|
|
1460
|
-
if (activeTab === "
|
|
1449
|
+
// Clear selections when leaving elements tab
|
|
1450
|
+
if (activeTab === "elements") {
|
|
1461
1451
|
setSelectedComponentType("all");
|
|
1462
1452
|
setSelectedComponentId(null);
|
|
1463
1453
|
setSelectedVariantId(null);
|
|
1464
|
-
|
|
1454
|
+
setSelectedLogoId(null);
|
|
1455
|
+
setSelectedTextId(null);
|
|
1456
|
+
setSelectedElementType(null);
|
|
1457
|
+
// Clear vision mode when leaving elements
|
|
1465
1458
|
if (visionModeActive) {
|
|
1466
1459
|
clearVisionMode();
|
|
1467
1460
|
}
|
|
@@ -1470,13 +1463,13 @@ export function SonanceDevTools() {
|
|
|
1470
1463
|
handleApplyFirstRevert();
|
|
1471
1464
|
}
|
|
1472
1465
|
}
|
|
1473
|
-
//
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1466
|
+
// Analysis and Vision mode are mutually exclusive
|
|
1467
|
+
// When switching to Analysis, disable Vision mode
|
|
1468
|
+
if (newTab === "analysis" && visionModeActive) {
|
|
1469
|
+
setVisionModeActive(false);
|
|
1470
|
+
setInspectorEnabled(false);
|
|
1471
|
+
setVisionFocusedElements([]);
|
|
1472
|
+
setVisionScreenshot(null);
|
|
1480
1473
|
}
|
|
1481
1474
|
setActiveTab(newTab);
|
|
1482
1475
|
}
|
|
@@ -2543,118 +2536,98 @@ export function SonanceDevTools() {
|
|
|
2543
2536
|
|
|
2544
2537
|
if (!mounted) return null;
|
|
2545
2538
|
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
"fixed bottom-6 right-6 z-[2147483646] pointer-events-auto",
|
|
2551
|
-
"flex h-14 w-14 items-center justify-center",
|
|
2552
|
-
"rounded-full bg-[#333F48] text-white shadow-lg",
|
|
2553
|
-
"hover:scale-105 hover:shadow-xl",
|
|
2554
|
-
"transition-all duration-200",
|
|
2555
|
-
"focus:outline-none focus:ring-2 focus:ring-[#00A3E1] focus:ring-offset-2",
|
|
2556
|
-
isOpen && "hidden"
|
|
2557
|
-
)}
|
|
2558
|
-
aria-label="Open Sonance DevTools"
|
|
2559
|
-
>
|
|
2560
|
-
<Palette className="h-6 w-6" />
|
|
2561
|
-
</button>
|
|
2562
|
-
);
|
|
2539
|
+
// Glassmorphism base classes
|
|
2540
|
+
const glassBase = "backdrop-blur-xl bg-black/75 border-white/10";
|
|
2541
|
+
|
|
2542
|
+
const sidebarWidth = isSidebarCollapsed ? SIDEBAR_COLLAPSED_WIDTH : SIDEBAR_WIDTH;
|
|
2563
2543
|
|
|
2564
|
-
|
|
2544
|
+
// ======== TOP BANNER ========
|
|
2545
|
+
const topBanner = isOpen && (
|
|
2565
2546
|
<div
|
|
2566
|
-
|
|
2567
|
-
data-sonance-devtools="true"
|
|
2547
|
+
data-sonance-devtools="banner"
|
|
2568
2548
|
className={cn(
|
|
2569
|
-
"fixed
|
|
2570
|
-
"
|
|
2571
|
-
"
|
|
2572
|
-
|
|
2573
|
-
"
|
|
2574
|
-
isDragging && "select-none"
|
|
2549
|
+
"fixed top-0 left-0 right-0 z-[2147483647] pointer-events-auto",
|
|
2550
|
+
"flex items-center justify-between px-4",
|
|
2551
|
+
"border-b font-['Montserrat',sans-serif]",
|
|
2552
|
+
glassBase,
|
|
2553
|
+
"border-b-white/10"
|
|
2575
2554
|
)}
|
|
2576
2555
|
style={{
|
|
2577
|
-
|
|
2578
|
-
|
|
2556
|
+
height: `${BANNER_HEIGHT}px`,
|
|
2557
|
+
colorScheme: "dark",
|
|
2579
2558
|
}}
|
|
2580
|
-
// Event isolation - prevent clicks from closing popups in the main app
|
|
2581
|
-
// onPointerDown fires before onMouseDown in modern browsers - must stop both
|
|
2582
|
-
onPointerDown={(e) => e.stopPropagation()}
|
|
2583
|
-
onTouchStart={(e) => e.stopPropagation()}
|
|
2584
|
-
onMouseDown={(e) => e.stopPropagation()}
|
|
2585
|
-
onClick={(e) => e.stopPropagation()}
|
|
2586
|
-
onFocus={(e) => e.stopPropagation()}
|
|
2587
|
-
onBlur={(e) => e.stopPropagation()}
|
|
2588
2559
|
>
|
|
2589
|
-
{/*
|
|
2590
|
-
<div
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
onPointerMove={handleDragMove}
|
|
2598
|
-
onPointerUp={handleDragEnd}
|
|
2599
|
-
onPointerCancel={handleDragEnd}
|
|
2600
|
-
onDoubleClick={handleResetPosition}
|
|
2601
|
-
title="Drag to move • Double-click to reset position"
|
|
2602
|
-
>
|
|
2603
|
-
<div className="flex items-center gap-1.5">
|
|
2604
|
-
<GripHorizontal className="h-3.5 w-3.5 text-white/50" />
|
|
2605
|
-
<Palette className="h-4 w-4 text-[#00A3E1]" />
|
|
2606
|
-
<span id="span-sonance-devtools" className="text-xs font-semibold text-white whitespace-nowrap">
|
|
2560
|
+
{/* Left: Branding */}
|
|
2561
|
+
<div className="flex items-center gap-3">
|
|
2562
|
+
<div className="flex items-center gap-2">
|
|
2563
|
+
<div className="flex items-center justify-center w-8 h-8 rounded-lg bg-gradient-to-br from-[#00D3C8] to-[#00A3E1]">
|
|
2564
|
+
<Palette className="h-4 w-4 text-white" />
|
|
2565
|
+
</div>
|
|
2566
|
+
<div className="flex flex-col">
|
|
2567
|
+
<span className="text-sm font-semibold text-white tracking-tight">
|
|
2607
2568
|
Sonance DevTools
|
|
2608
2569
|
</span>
|
|
2570
|
+
<span className="text-[10px] text-white/50 uppercase tracking-widest">
|
|
2571
|
+
Design System
|
|
2572
|
+
</span>
|
|
2573
|
+
</div>
|
|
2609
2574
|
</div>
|
|
2610
|
-
|
|
2611
|
-
|
|
2575
|
+
</div>
|
|
2576
|
+
|
|
2577
|
+
{/* Center: Mode Toggles */}
|
|
2578
|
+
<div className="flex items-center gap-1 bg-white/5 rounded-full px-2 py-1">
|
|
2579
|
+
{/* Analysis Toggle */}
|
|
2612
2580
|
<button
|
|
2613
2581
|
onClick={() => handleTabChange(activeTab === "analysis" ? "components" : "analysis")}
|
|
2614
2582
|
className={cn(
|
|
2615
|
-
|
|
2583
|
+
"flex items-center gap-1.5 px-3 py-1.5 rounded-full text-xs font-medium transition-all",
|
|
2616
2584
|
activeTab === "analysis"
|
|
2617
|
-
|
|
2618
|
-
|
|
2585
|
+
? "bg-[#00D3C8] text-white shadow-lg shadow-[#00D3C8]/25"
|
|
2586
|
+
: "text-white/70 hover:text-white hover:bg-white/10"
|
|
2619
2587
|
)}
|
|
2620
|
-
|
|
2621
|
-
title={activeTab === "analysis" ? "Close analysis" : "Open analysis"}
|
|
2588
|
+
title="Page Analysis"
|
|
2622
2589
|
>
|
|
2623
|
-
|
|
2590
|
+
<Scan className="h-3.5 w-3.5" />
|
|
2591
|
+
<span className="hidden sm:inline">Analysis</span>
|
|
2624
2592
|
</button>
|
|
2625
|
-
|
|
2593
|
+
{/* Vision Mode Toggle */}
|
|
2626
2594
|
<button
|
|
2627
|
-
|
|
2595
|
+
onClick={toggleVisionMode}
|
|
2628
2596
|
className={cn(
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2597
|
+
"flex items-center gap-1.5 px-3 py-1.5 rounded-full text-xs font-medium transition-all",
|
|
2598
|
+
visionModeActive
|
|
2599
|
+
? "bg-[#8B5CF6] text-white shadow-lg shadow-[#8B5CF6]/25"
|
|
2600
|
+
: "text-white/70 hover:text-white hover:bg-white/10"
|
|
2601
|
+
)}
|
|
2602
|
+
title="AI Vision Mode"
|
|
2603
|
+
>
|
|
2604
|
+
<Eye className={cn("h-3.5 w-3.5", visionModeActive && "animate-pulse")} />
|
|
2605
|
+
<span className="hidden sm:inline">Vision</span>
|
|
2638
2606
|
</button>
|
|
2639
|
-
|
|
2607
|
+
{/* Element Highlight Toggle - only visible when Vision Mode is active */}
|
|
2608
|
+
{visionModeActive && (
|
|
2640
2609
|
<button
|
|
2641
|
-
onClick={
|
|
2610
|
+
onClick={toggleInspector}
|
|
2642
2611
|
className={cn(
|
|
2643
|
-
"
|
|
2644
|
-
|
|
2645
|
-
? "bg-[#
|
|
2646
|
-
: "
|
|
2612
|
+
"flex items-center gap-1.5 px-3 py-1.5 rounded-full text-xs font-medium transition-all",
|
|
2613
|
+
inspectorEnabled
|
|
2614
|
+
? "bg-[#00A3E1] text-white shadow-lg shadow-[#00A3E1]/25"
|
|
2615
|
+
: "text-white/70 hover:text-white hover:bg-white/10"
|
|
2647
2616
|
)}
|
|
2648
|
-
|
|
2649
|
-
title="Vision Mode: AI-powered page editing with screenshot analysis"
|
|
2617
|
+
title={inspectorEnabled ? "Disable element highlighting" : "Enable element highlighting"}
|
|
2650
2618
|
>
|
|
2651
|
-
<
|
|
2619
|
+
<MousePointer className={cn("h-3.5 w-3.5", inspectorEnabled && "animate-pulse")} />
|
|
2620
|
+
<span className="hidden sm:inline">{inspectorEnabled ? "Highlighting" : "Highlight"}</span>
|
|
2652
2621
|
</button>
|
|
2653
|
-
|
|
2622
|
+
)}
|
|
2623
|
+
</div>
|
|
2624
|
+
|
|
2625
|
+
{/* Right: Actions */}
|
|
2626
|
+
<div className="flex items-center gap-2">
|
|
2627
|
+
{/* Theme Toggle */}
|
|
2654
2628
|
<button
|
|
2655
2629
|
onClick={toggleThemeMode}
|
|
2656
|
-
|
|
2657
|
-
aria-label={resolvedTheme === "dark" ? "Switch to light mode" : "Switch to dark mode"}
|
|
2630
|
+
className="flex items-center justify-center w-8 h-8 rounded-lg bg-white/5 hover:bg-white/10 transition-colors"
|
|
2658
2631
|
title={resolvedTheme === "dark" ? "Switch to light mode" : "Switch to dark mode"}
|
|
2659
2632
|
>
|
|
2660
2633
|
{resolvedTheme === "dark" ? (
|
|
@@ -2666,182 +2639,285 @@ export function SonanceDevTools() {
|
|
|
2666
2639
|
{/* Close Button */}
|
|
2667
2640
|
<button
|
|
2668
2641
|
onClick={handleClose}
|
|
2669
|
-
|
|
2670
|
-
|
|
2642
|
+
className="flex items-center justify-center w-8 h-8 rounded-lg bg-white/5 hover:bg-red-500/20 hover:text-red-400 transition-colors text-white/70"
|
|
2643
|
+
title="Close DevTools"
|
|
2671
2644
|
>
|
|
2672
|
-
|
|
2645
|
+
<X className="h-4 w-4" />
|
|
2673
2646
|
</button>
|
|
2674
2647
|
</div>
|
|
2675
2648
|
</div>
|
|
2649
|
+
);
|
|
2676
2650
|
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2651
|
+
// ======== RIGHT SIDEBAR ========
|
|
2652
|
+
const sidebar = isOpen && (
|
|
2653
|
+
<div
|
|
2654
|
+
ref={panelRef}
|
|
2655
|
+
data-sonance-devtools="sidebar"
|
|
2656
|
+
className={cn(
|
|
2657
|
+
"fixed top-0 right-0 z-[2147483646] pointer-events-auto",
|
|
2658
|
+
"flex flex-col font-['Montserrat',sans-serif]",
|
|
2659
|
+
"border-l",
|
|
2660
|
+
glassBase,
|
|
2661
|
+
"border-l-white/10",
|
|
2662
|
+
"transition-all duration-200 ease-out"
|
|
2663
|
+
)}
|
|
2664
|
+
style={{
|
|
2665
|
+
width: `${sidebarWidth}px`,
|
|
2666
|
+
height: "100vh",
|
|
2667
|
+
paddingTop: `${BANNER_HEIGHT}px`,
|
|
2668
|
+
colorScheme: "dark",
|
|
2669
|
+
}}
|
|
2670
|
+
onPointerDown={(e) => e.stopPropagation()}
|
|
2671
|
+
onTouchStart={(e) => e.stopPropagation()}
|
|
2672
|
+
onMouseDown={(e) => e.stopPropagation()}
|
|
2673
|
+
onClick={(e) => e.stopPropagation()}
|
|
2674
|
+
>
|
|
2675
|
+
{/* Collapse/Expand Toggle */}
|
|
2683
2676
|
<button
|
|
2684
|
-
|
|
2685
|
-
onClick={() => handleTabChange(tab.id)}
|
|
2677
|
+
onClick={() => setIsSidebarCollapsed(!isSidebarCollapsed)}
|
|
2686
2678
|
className={cn(
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
aria-label={tab.label}
|
|
2693
|
-
title={tab.label}
|
|
2694
|
-
>
|
|
2695
|
-
<IconComponent className="h-4 w-4" />
|
|
2696
|
-
</button>
|
|
2697
|
-
);
|
|
2698
|
-
})}
|
|
2699
|
-
</div>
|
|
2700
|
-
|
|
2701
|
-
{/* Content */}
|
|
2702
|
-
<div
|
|
2703
|
-
className="flex-1 overflow-y-auto p-4 text-[#333F48]"
|
|
2704
|
-
style={{
|
|
2705
|
-
overscrollBehavior: "contain", // Prevent scroll chaining to parent (modal)
|
|
2706
|
-
WebkitOverflowScrolling: "touch", // Smooth scrolling on iOS
|
|
2707
|
-
}}
|
|
2708
|
-
onWheel={(e) => e.stopPropagation()} // Prevent modal from intercepting wheel events
|
|
2709
|
-
onTouchMove={(e) => e.stopPropagation()} // Prevent modal from intercepting touch scroll
|
|
2710
|
-
>
|
|
2711
|
-
{/* Analysis Tab */}
|
|
2712
|
-
{activeTab === "analysis" && (
|
|
2713
|
-
<AnalysisPanel
|
|
2714
|
-
analysisStatus={analysisStatus}
|
|
2715
|
-
analysisResult={analysisResult}
|
|
2716
|
-
analysisError={analysisError}
|
|
2717
|
-
bulkTagStatus={bulkTagStatus}
|
|
2718
|
-
bulkTagMessage={bulkTagMessage}
|
|
2719
|
-
onRunAnalysis={handleRunAnalysis}
|
|
2720
|
-
onBulkTag={handleBulkTagAll}
|
|
2721
|
-
/>
|
|
2679
|
+
"absolute -left-3 top-1/2 -translate-y-1/2 z-10",
|
|
2680
|
+
"flex items-center justify-center w-6 h-12 rounded-l-lg",
|
|
2681
|
+
"bg-black/80 border border-white/10 border-r-0",
|
|
2682
|
+
"text-white/60 hover:text-white hover:bg-black/90",
|
|
2683
|
+
"transition-all"
|
|
2722
2684
|
)}
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
{
|
|
2726
|
-
<
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
installedComponents={installedComponents}
|
|
2730
|
-
inspectorEnabled={inspectorEnabled}
|
|
2731
|
-
onToggleInspector={toggleInspector}
|
|
2732
|
-
config={config}
|
|
2733
|
-
updateConfig={updateConfig}
|
|
2734
|
-
onReset={handleReset}
|
|
2735
|
-
taggedElements={taggedElements}
|
|
2736
|
-
selectedComponentType={selectedComponentType}
|
|
2737
|
-
onSelectComponentType={handleSelectComponentType}
|
|
2738
|
-
componentScope={componentScope}
|
|
2739
|
-
onScopeChange={setComponentScope}
|
|
2740
|
-
selectedComponentId={selectedComponentId}
|
|
2741
|
-
onSelectComponent={setSelectedComponentId}
|
|
2742
|
-
selectedVariantId={selectedVariantId}
|
|
2743
|
-
onSelectVariant={setSelectedVariantId}
|
|
2744
|
-
analysisResult={analysisResult}
|
|
2745
|
-
componentOverrides={componentOverrides}
|
|
2746
|
-
onUpdateComponentOverride={updateComponentOverride}
|
|
2747
|
-
onResetComponentOverrides={resetComponentOverrides}
|
|
2748
|
-
viewMode={viewMode}
|
|
2749
|
-
onViewModeChange={setViewMode}
|
|
2750
|
-
isPreviewActive={isPreviewActive}
|
|
2751
|
-
onPreviewActiveChange={setIsPreviewActive}
|
|
2752
|
-
visionMode={visionModeActive}
|
|
2753
|
-
visionFocusedElements={visionFocusedElements}
|
|
2754
|
-
onVisionEditComplete={handleVisionEditComplete}
|
|
2755
|
-
visionPendingEdit={visionPendingEdit}
|
|
2756
|
-
onSaveVisionEdit={handleSaveVisionEdit}
|
|
2757
|
-
onClearVisionPendingEdit={handleClearVisionPendingEdit}
|
|
2758
|
-
// Apply-First Mode props
|
|
2759
|
-
applyFirstSession={applyFirstSession}
|
|
2760
|
-
applyFirstStatus={applyFirstStatus}
|
|
2761
|
-
onApplyFirstComplete={handleApplyFirstComplete}
|
|
2762
|
-
onApplyFirstAccept={handleApplyFirstAccept}
|
|
2763
|
-
onApplyFirstRevert={handleApplyFirstRevert}
|
|
2764
|
-
onApplyFirstForceClear={handleApplyFirstForceClear}
|
|
2765
|
-
/>
|
|
2685
|
+
title={isSidebarCollapsed ? "Expand sidebar" : "Collapse sidebar"}
|
|
2686
|
+
>
|
|
2687
|
+
{isSidebarCollapsed ? (
|
|
2688
|
+
<PanelRightClose className="h-3.5 w-3.5" />
|
|
2689
|
+
) : (
|
|
2690
|
+
<PanelRightOpen className="h-3.5 w-3.5" />
|
|
2766
2691
|
)}
|
|
2692
|
+
</button>
|
|
2767
2693
|
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2694
|
+
{/* Element Type Filter Toggles - Color coded */}
|
|
2695
|
+
{!isSidebarCollapsed && activeTab === "elements" && (
|
|
2696
|
+
<div className="flex items-center gap-1 px-2 py-2 border-b border-white/10 bg-white/5">
|
|
2697
|
+
{/* Components Filter */}
|
|
2698
|
+
<button
|
|
2699
|
+
onClick={() => setElementFilters(prev => ({ ...prev, components: !prev.components }))}
|
|
2700
|
+
className={cn(
|
|
2701
|
+
"flex items-center gap-1 px-2 py-1.5 rounded text-[10px] font-medium transition-all",
|
|
2702
|
+
elementFilters.components
|
|
2703
|
+
? "bg-purple-500/20 text-purple-300 border border-purple-400/30"
|
|
2704
|
+
: "text-white/40 hover:text-white/60 border border-transparent"
|
|
2705
|
+
)}
|
|
2706
|
+
title="Toggle Components"
|
|
2707
|
+
>
|
|
2708
|
+
<Box className="h-3 w-3" />
|
|
2709
|
+
<span>Components</span>
|
|
2710
|
+
</button>
|
|
2711
|
+
{/* Images Filter */}
|
|
2712
|
+
<button
|
|
2713
|
+
onClick={() => setElementFilters(prev => ({ ...prev, images: !prev.images }))}
|
|
2714
|
+
className={cn(
|
|
2715
|
+
"flex items-center gap-1 px-2 py-1.5 rounded text-[10px] font-medium transition-all",
|
|
2716
|
+
elementFilters.images
|
|
2717
|
+
? "bg-blue-500/20 text-blue-300 border border-blue-400/30"
|
|
2718
|
+
: "text-white/40 hover:text-white/60 border border-transparent"
|
|
2719
|
+
)}
|
|
2720
|
+
title="Toggle Images"
|
|
2721
|
+
>
|
|
2722
|
+
<ImageIcon className="h-3 w-3" />
|
|
2723
|
+
<span>Images</span>
|
|
2724
|
+
</button>
|
|
2725
|
+
{/* Text Filter */}
|
|
2726
|
+
<button
|
|
2727
|
+
onClick={() => setElementFilters(prev => ({ ...prev, text: !prev.text }))}
|
|
2728
|
+
className={cn(
|
|
2729
|
+
"flex items-center gap-1 px-2 py-1.5 rounded text-[10px] font-medium transition-all",
|
|
2730
|
+
elementFilters.text
|
|
2731
|
+
? "bg-amber-500/20 text-amber-300 border border-amber-400/30"
|
|
2732
|
+
: "text-white/40 hover:text-white/60 border border-transparent"
|
|
2733
|
+
)}
|
|
2734
|
+
title="Toggle Text"
|
|
2735
|
+
>
|
|
2736
|
+
<Type className="h-3 w-3" />
|
|
2737
|
+
<span>Text</span>
|
|
2738
|
+
</button>
|
|
2810
2739
|
</div>
|
|
2740
|
+
)}
|
|
2741
|
+
{/* Collapsed sidebar - just show active filters as icons */}
|
|
2742
|
+
{isSidebarCollapsed && activeTab === "elements" && (
|
|
2743
|
+
<div className="flex flex-col items-center gap-0.5 py-1.5 border-b border-white/10 bg-white/5">
|
|
2744
|
+
<button
|
|
2745
|
+
onClick={() => setElementFilters(prev => ({ ...prev, components: !prev.components }))}
|
|
2746
|
+
className={cn(
|
|
2747
|
+
"p-1.5 rounded transition-all",
|
|
2748
|
+
elementFilters.components ? "text-purple-400" : "text-white/30"
|
|
2749
|
+
)}
|
|
2750
|
+
title="Components"
|
|
2751
|
+
>
|
|
2752
|
+
<Box className="h-3.5 w-3.5" />
|
|
2753
|
+
</button>
|
|
2754
|
+
<button
|
|
2755
|
+
onClick={() => setElementFilters(prev => ({ ...prev, images: !prev.images }))}
|
|
2756
|
+
className={cn(
|
|
2757
|
+
"p-1.5 rounded transition-all",
|
|
2758
|
+
elementFilters.images ? "text-blue-400" : "text-white/30"
|
|
2759
|
+
)}
|
|
2760
|
+
title="Images"
|
|
2761
|
+
>
|
|
2762
|
+
<ImageIcon className="h-3.5 w-3.5" />
|
|
2763
|
+
</button>
|
|
2764
|
+
<button
|
|
2765
|
+
onClick={() => setElementFilters(prev => ({ ...prev, text: !prev.text }))}
|
|
2766
|
+
className={cn(
|
|
2767
|
+
"p-1.5 rounded transition-all",
|
|
2768
|
+
elementFilters.text ? "text-amber-400" : "text-white/30"
|
|
2769
|
+
)}
|
|
2770
|
+
title="Text"
|
|
2771
|
+
>
|
|
2772
|
+
<Type className="h-3.5 w-3.5" />
|
|
2773
|
+
</button>
|
|
2774
|
+
</div>
|
|
2775
|
+
)}
|
|
2776
|
+
|
|
2777
|
+
{/* Panel Content - Light themed for contrast with dark frame */}
|
|
2778
|
+
{!isSidebarCollapsed && (
|
|
2779
|
+
<div
|
|
2780
|
+
className={cn(
|
|
2781
|
+
"flex-1 m-1.5 rounded-md bg-white/95 backdrop-blur-sm shadow-inner",
|
|
2782
|
+
// Elements tab needs flex layout for pinned chat
|
|
2783
|
+
activeTab === "elements" ? "flex flex-col overflow-hidden" : "overflow-y-auto"
|
|
2784
|
+
)}
|
|
2785
|
+
style={{
|
|
2786
|
+
overscrollBehavior: "contain",
|
|
2787
|
+
WebkitOverflowScrolling: "touch",
|
|
2788
|
+
colorScheme: "light",
|
|
2789
|
+
}}
|
|
2790
|
+
onWheel={(e) => e.stopPropagation()}
|
|
2791
|
+
onTouchMove={(e) => e.stopPropagation()}
|
|
2792
|
+
>
|
|
2793
|
+
{/* Analysis Tab - with padding wrapper */}
|
|
2794
|
+
{activeTab === "analysis" && (
|
|
2795
|
+
<div className="p-3 text-[#333F48]">
|
|
2796
|
+
<AnalysisPanel
|
|
2797
|
+
analysisStatus={analysisStatus}
|
|
2798
|
+
analysisResult={analysisResult}
|
|
2799
|
+
analysisError={analysisError}
|
|
2800
|
+
bulkTagStatus={bulkTagStatus}
|
|
2801
|
+
bulkTagMessage={bulkTagMessage}
|
|
2802
|
+
onRunAnalysis={handleRunAnalysis}
|
|
2803
|
+
onBulkTag={handleBulkTagAll}
|
|
2804
|
+
/>
|
|
2805
|
+
</div>
|
|
2806
|
+
)}
|
|
2807
|
+
|
|
2808
|
+
{/* Unified Elements Tab - full height flex layout */}
|
|
2809
|
+
{activeTab === "elements" && (
|
|
2810
|
+
<div className="flex-1 flex flex-col min-h-0 text-[#333F48]">
|
|
2811
|
+
{/* Dynamic content based on selected element type or default to ComponentsPanel */}
|
|
2812
|
+
<ComponentsPanel
|
|
2813
|
+
copiedId={copiedId}
|
|
2814
|
+
onCopy={handleCopy}
|
|
2815
|
+
installedComponents={installedComponents}
|
|
2816
|
+
inspectorEnabled={inspectorEnabled}
|
|
2817
|
+
onToggleInspector={toggleInspector}
|
|
2818
|
+
config={config}
|
|
2819
|
+
updateConfig={updateConfig}
|
|
2820
|
+
onReset={handleReset}
|
|
2821
|
+
taggedElements={taggedElements}
|
|
2822
|
+
selectedComponentType={selectedComponentType}
|
|
2823
|
+
onSelectComponentType={handleSelectComponentType}
|
|
2824
|
+
componentScope={componentScope}
|
|
2825
|
+
onScopeChange={setComponentScope}
|
|
2826
|
+
selectedComponentId={selectedComponentId}
|
|
2827
|
+
onSelectComponent={setSelectedComponentId}
|
|
2828
|
+
selectedVariantId={selectedVariantId}
|
|
2829
|
+
onSelectVariant={setSelectedVariantId}
|
|
2830
|
+
analysisResult={analysisResult}
|
|
2831
|
+
componentOverrides={componentOverrides}
|
|
2832
|
+
onUpdateComponentOverride={updateComponentOverride}
|
|
2833
|
+
onResetComponentOverrides={resetComponentOverrides}
|
|
2834
|
+
viewMode={viewMode}
|
|
2835
|
+
onViewModeChange={setViewMode}
|
|
2836
|
+
isPreviewActive={isPreviewActive}
|
|
2837
|
+
onPreviewActiveChange={setIsPreviewActive}
|
|
2838
|
+
visionMode={visionModeActive}
|
|
2839
|
+
visionFocusedElements={visionFocusedElements}
|
|
2840
|
+
onVisionEditComplete={handleVisionEditComplete}
|
|
2841
|
+
visionPendingEdit={visionPendingEdit}
|
|
2842
|
+
onSaveVisionEdit={handleSaveVisionEdit}
|
|
2843
|
+
onClearVisionPendingEdit={handleClearVisionPendingEdit}
|
|
2844
|
+
// Apply-First Mode props
|
|
2845
|
+
applyFirstSession={applyFirstSession}
|
|
2846
|
+
applyFirstStatus={applyFirstStatus}
|
|
2847
|
+
onApplyFirstComplete={handleApplyFirstComplete}
|
|
2848
|
+
onApplyFirstAccept={handleApplyFirstAccept}
|
|
2849
|
+
onApplyFirstRevert={handleApplyFirstRevert}
|
|
2850
|
+
onApplyFirstForceClear={handleApplyFirstForceClear}
|
|
2851
|
+
// Element filter context - for showing filter-aware content
|
|
2852
|
+
elementFilters={elementFilters}
|
|
2853
|
+
selectedLogoId={selectedLogoId}
|
|
2854
|
+
onSelectLogo={handleSelectLogo}
|
|
2855
|
+
selectedTextId={selectedTextId}
|
|
2856
|
+
onSelectText={handleSelectText}
|
|
2857
|
+
/>
|
|
2858
|
+
</div>
|
|
2859
|
+
)}
|
|
2860
|
+
</div>
|
|
2861
|
+
)}
|
|
2811
2862
|
</div>
|
|
2812
2863
|
);
|
|
2864
|
+
|
|
2865
|
+
// Floating trigger when closed (for re-opening)
|
|
2866
|
+
const closedTrigger = !isOpen && (
|
|
2867
|
+
<button
|
|
2868
|
+
onClick={() => setIsOpen(true)}
|
|
2869
|
+
className={cn(
|
|
2870
|
+
"fixed bottom-6 right-6 z-[2147483646] pointer-events-auto",
|
|
2871
|
+
"flex items-center gap-2 px-4 py-2.5",
|
|
2872
|
+
"rounded-full shadow-2xl",
|
|
2873
|
+
"backdrop-blur-xl bg-black/80 border border-white/10",
|
|
2874
|
+
"text-white font-medium text-sm",
|
|
2875
|
+
"hover:scale-105 hover:shadow-[0_0_30px_rgba(0,211,200,0.3)]",
|
|
2876
|
+
"transition-all duration-200",
|
|
2877
|
+
"focus:outline-none focus:ring-2 focus:ring-[#00D3C8] focus:ring-offset-2 focus:ring-offset-black"
|
|
2878
|
+
)}
|
|
2879
|
+
aria-label="Open Sonance DevTools"
|
|
2880
|
+
>
|
|
2881
|
+
<Palette className="h-4 w-4 text-[#00D3C8]" />
|
|
2882
|
+
<span>DevTools</span>
|
|
2883
|
+
</button>
|
|
2884
|
+
);
|
|
2813
2885
|
|
|
2814
2886
|
// Wait for portal container to be ready
|
|
2815
2887
|
if (!portalContainer) return null;
|
|
2816
2888
|
|
|
2817
2889
|
return createPortal(
|
|
2818
2890
|
<>
|
|
2819
|
-
{trigger}
|
|
2820
|
-
{
|
|
2891
|
+
{/* Closed state trigger */}
|
|
2892
|
+
{closedTrigger}
|
|
2893
|
+
{/* Frame layout: Top Banner + Right Sidebar */}
|
|
2894
|
+
{topBanner}
|
|
2895
|
+
{sidebar}
|
|
2821
2896
|
{/* Vision Mode Border Overlay */}
|
|
2822
2897
|
<VisionModeBorder
|
|
2823
2898
|
active={visionModeActive}
|
|
2824
2899
|
focusedCount={visionFocusedElements.length}
|
|
2900
|
+
highlightEnabled={inspectorEnabled}
|
|
2825
2901
|
/>
|
|
2826
2902
|
{/* Section Highlight - shows detected parent section when element is clicked */}
|
|
2827
2903
|
<SectionHighlight
|
|
2828
2904
|
active={visionModeActive}
|
|
2829
2905
|
focusedElements={visionFocusedElements}
|
|
2830
2906
|
/>
|
|
2831
|
-
{/* Visual Inspector Overlay -
|
|
2832
|
-
{(inspectorEnabled || isPreviewActive ||
|
|
2907
|
+
{/* Visual Inspector Overlay - shows when highlighting is enabled in Vision Mode or for previews */}
|
|
2908
|
+
{((visionModeActive && inspectorEnabled) || isPreviewActive || changedElements.length > 0 || (viewMode === "inspector" && selectedComponentType !== "all")) && filteredOverlayElements.length > 0 && (
|
|
2833
2909
|
<InspectorOverlay
|
|
2834
2910
|
elements={filteredOverlayElements}
|
|
2835
2911
|
selectedLogoId={selectedLogoId}
|
|
2836
2912
|
onLogoClick={handleSelectLogo}
|
|
2837
2913
|
selectedTextId={selectedTextId}
|
|
2838
2914
|
onTextClick={handleSelectText}
|
|
2839
|
-
interactive={activeTab === "
|
|
2915
|
+
interactive={activeTab === "elements" && (elementFilters.images || elementFilters.text)}
|
|
2840
2916
|
selectedComponentId={selectedComponentId}
|
|
2841
2917
|
onComponentClick={setSelectedComponentId}
|
|
2842
2918
|
componentSelectionMode={componentScope === "selected" && inspectorEnabled}
|
|
2843
|
-
//
|
|
2844
|
-
inspectorClickMode={
|
|
2919
|
+
// Vision Mode with highlighting: clicking a component selects its type
|
|
2920
|
+
inspectorClickMode={visionModeActive && inspectorEnabled && !isPreviewActive}
|
|
2845
2921
|
onSelectComponentAndClose={handleInspectorSelectAndClose}
|
|
2846
2922
|
// Pass selected type to highlight only matching components
|
|
2847
2923
|
selectedComponentType={selectedComponentType}
|