sonance-brand-mcp 1.3.77 → 1.3.79
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.
|
@@ -64,6 +64,7 @@ import {
|
|
|
64
64
|
removePreviewStyles,
|
|
65
65
|
injectPreviewStyles,
|
|
66
66
|
extractPreviewCSSFromCode,
|
|
67
|
+
getActiveModalContent,
|
|
67
68
|
} from "./utils";
|
|
68
69
|
import { Section, ColorSwatch, SelectField } from "./components/common";
|
|
69
70
|
import { InspectorOverlay } from "./components/InspectorOverlay";
|
|
@@ -126,6 +127,34 @@ export function SonanceDevTools() {
|
|
|
126
127
|
const dragOffsetRef = useRef({ x: 0, y: 0 });
|
|
127
128
|
const panelRef = useRef<HTMLDivElement>(null);
|
|
128
129
|
|
|
130
|
+
// Portal container state - ensures DevTool is always on top of modals
|
|
131
|
+
const [portalContainer, setPortalContainer] = useState<HTMLElement | null>(null);
|
|
132
|
+
|
|
133
|
+
// Create and manage dedicated portal container that stays on top
|
|
134
|
+
useEffect(() => {
|
|
135
|
+
// Create container if it doesn't exist
|
|
136
|
+
let container = document.getElementById('sonance-devtools-root');
|
|
137
|
+
if (!container) {
|
|
138
|
+
container = document.createElement('div');
|
|
139
|
+
container.id = 'sonance-devtools-root';
|
|
140
|
+
container.style.cssText = 'position: fixed; inset: 0; pointer-events: none; z-index: 2147483647; isolation: isolate;';
|
|
141
|
+
document.body.appendChild(container);
|
|
142
|
+
}
|
|
143
|
+
setPortalContainer(container);
|
|
144
|
+
|
|
145
|
+
// MutationObserver to keep container as last child of body
|
|
146
|
+
// This ensures DevTool stays on top even when modals are dynamically added
|
|
147
|
+
const observer = new MutationObserver(() => {
|
|
148
|
+
if (container && container.nextSibling) {
|
|
149
|
+
document.body.appendChild(container);
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
observer.observe(document.body, { childList: true });
|
|
154
|
+
|
|
155
|
+
return () => observer.disconnect();
|
|
156
|
+
}, []);
|
|
157
|
+
|
|
129
158
|
// Component-specific style overrides (for scalable, project-agnostic styling)
|
|
130
159
|
// Key: component type (e.g., "card", "button-primary", "card:variant123"), Value: style overrides
|
|
131
160
|
const [componentOverrides, setComponentOverrides] = useState<Record<string, ComponentStyle>>({});
|
|
@@ -767,14 +796,28 @@ export function SonanceDevTools() {
|
|
|
767
796
|
return rect; // No scroll parent, use original rect
|
|
768
797
|
};
|
|
769
798
|
|
|
799
|
+
// Helper: Detect active modal and filter elements to only those in the topmost layer
|
|
800
|
+
const activeModalContent = getActiveModalContent();
|
|
801
|
+
const isInActiveLayer = (el: Element): boolean => {
|
|
802
|
+
// Always exclude DevTools panel
|
|
803
|
+
if (el.closest("[data-sonance-devtools]")) return false;
|
|
804
|
+
|
|
805
|
+
// If a modal is active, only include elements inside the modal content
|
|
806
|
+
if (activeModalContent) {
|
|
807
|
+
return activeModalContent.contains(el) || el === activeModalContent;
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
return true;
|
|
811
|
+
};
|
|
812
|
+
|
|
770
813
|
// Scan for tagged components
|
|
771
814
|
if (inspectorEnabled || activeTab === "components") {
|
|
772
815
|
// 1. Scan for explicitly tagged components
|
|
773
816
|
const taggedComponents = document.querySelectorAll("[data-sonance-name]");
|
|
774
817
|
taggedComponents.forEach((el) => {
|
|
775
|
-
// Skip DevTools
|
|
776
|
-
if (el
|
|
777
|
-
|
|
818
|
+
// Skip elements outside the active layer (DevTools, behind modals)
|
|
819
|
+
if (!isInActiveLayer(el)) return;
|
|
820
|
+
|
|
778
821
|
const name = el.getAttribute("data-sonance-name");
|
|
779
822
|
if (name) {
|
|
780
823
|
const rawRect = el.getBoundingClientRect();
|
|
@@ -820,9 +863,9 @@ export function SonanceDevTools() {
|
|
|
820
863
|
Object.entries(genericSelectors).forEach(([genericName, selector]) => {
|
|
821
864
|
const elements = document.querySelectorAll(selector);
|
|
822
865
|
elements.forEach((el) => {
|
|
823
|
-
// Skip DevTools
|
|
824
|
-
if (el
|
|
825
|
-
|
|
866
|
+
// Skip elements outside the active layer (DevTools, behind modals)
|
|
867
|
+
if (!isInActiveLayer(el)) return;
|
|
868
|
+
|
|
826
869
|
const rawRect = el.getBoundingClientRect();
|
|
827
870
|
const rect = getVisibleRect(el, rawRect);
|
|
828
871
|
if (rect && rect.width > 0 && rect.height > 0) {
|
|
@@ -862,6 +905,9 @@ export function SonanceDevTools() {
|
|
|
862
905
|
if (activeTab === "logos") {
|
|
863
906
|
const images = document.querySelectorAll("img");
|
|
864
907
|
images.forEach((img) => {
|
|
908
|
+
// Skip elements outside the active layer (DevTools, behind modals)
|
|
909
|
+
if (!isInActiveLayer(img)) return;
|
|
910
|
+
|
|
865
911
|
const src = img.src || img.getAttribute("src") || "";
|
|
866
912
|
const alt = img.alt || "";
|
|
867
913
|
|
|
@@ -916,9 +962,9 @@ export function SonanceDevTools() {
|
|
|
916
962
|
const addedElements = new Set<Element>();
|
|
917
963
|
|
|
918
964
|
textElements.forEach((el) => {
|
|
919
|
-
// Skip elements
|
|
920
|
-
if (el
|
|
921
|
-
|
|
965
|
+
// Skip elements outside the active layer (DevTools, behind modals)
|
|
966
|
+
if (!isInActiveLayer(el)) return;
|
|
967
|
+
|
|
922
968
|
// Skip if this element is inside another text element we're already tracking
|
|
923
969
|
// This prevents duplicate labels for nested structures
|
|
924
970
|
const parentTextEl = el.parentElement?.closest("h1, h2, h3, h4, h5, h6, p, a, label, blockquote, figcaption, li");
|
|
@@ -2384,7 +2430,7 @@ export function SonanceDevTools() {
|
|
|
2384
2430
|
<button
|
|
2385
2431
|
onClick={() => setIsOpen(true)}
|
|
2386
2432
|
className={cn(
|
|
2387
|
-
"fixed bottom-6 right-6 z-[2147483646]",
|
|
2433
|
+
"fixed bottom-6 right-6 z-[2147483646] pointer-events-auto",
|
|
2388
2434
|
"flex h-14 w-14 items-center justify-center",
|
|
2389
2435
|
"rounded-full bg-[#333F48] text-white shadow-lg",
|
|
2390
2436
|
"hover:scale-105 hover:shadow-xl",
|
|
@@ -2403,7 +2449,7 @@ export function SonanceDevTools() {
|
|
|
2403
2449
|
ref={panelRef}
|
|
2404
2450
|
data-sonance-devtools="true"
|
|
2405
2451
|
className={cn(
|
|
2406
|
-
"fixed bottom-6 right-6 z-[2147483647]",
|
|
2452
|
+
"fixed bottom-6 right-6 z-[2147483647] pointer-events-auto",
|
|
2407
2453
|
"w-[360px] max-h-[80vh]",
|
|
2408
2454
|
"bg-white rounded-lg shadow-2xl border border-gray-200",
|
|
2409
2455
|
"flex flex-col overflow-hidden",
|
|
@@ -2637,6 +2683,9 @@ export function SonanceDevTools() {
|
|
|
2637
2683
|
</div>
|
|
2638
2684
|
);
|
|
2639
2685
|
|
|
2686
|
+
// Wait for portal container to be ready
|
|
2687
|
+
if (!portalContainer) return null;
|
|
2688
|
+
|
|
2640
2689
|
return createPortal(
|
|
2641
2690
|
<>
|
|
2642
2691
|
{trigger}
|
|
@@ -2677,6 +2726,6 @@ export function SonanceDevTools() {
|
|
|
2677
2726
|
/>
|
|
2678
2727
|
)}
|
|
2679
2728
|
</>,
|
|
2680
|
-
|
|
2729
|
+
portalContainer
|
|
2681
2730
|
);
|
|
2682
2731
|
}
|
|
@@ -329,6 +329,55 @@ export function extractPreviewCSSFromCode(
|
|
|
329
329
|
return rules.join("\n");
|
|
330
330
|
}
|
|
331
331
|
|
|
332
|
+
/**
|
|
333
|
+
* Finds the topmost modal/dialog content container if one is open.
|
|
334
|
+
* Returns the content element (not the backdrop) or null if no modal is active.
|
|
335
|
+
*
|
|
336
|
+
* Detection strategy:
|
|
337
|
+
* 1. Look for Radix UI dialogs with [data-state="open"]
|
|
338
|
+
* 2. Look for role="dialog" elements
|
|
339
|
+
* 3. Look for native <dialog open> elements
|
|
340
|
+
* 4. Look for custom Dialog with z-50 fixed positioning
|
|
341
|
+
* 5. Look for Drawer/Sheet overlays
|
|
342
|
+
*/
|
|
343
|
+
export function getActiveModalContent(): Element | null {
|
|
344
|
+
if (typeof document === "undefined") return null;
|
|
345
|
+
|
|
346
|
+
// Priority-ordered list of modal content selectors
|
|
347
|
+
const contentSelectors = [
|
|
348
|
+
// Radix UI Dialog content
|
|
349
|
+
'[data-state="open"][role="dialog"]',
|
|
350
|
+
// Custom Dialog content (has data-sonance-name="dialog")
|
|
351
|
+
'.fixed.inset-0.z-50 [data-sonance-name="dialog"]',
|
|
352
|
+
'.fixed.inset-0.z-50 > .relative.z-10',
|
|
353
|
+
// Native dialog
|
|
354
|
+
'dialog[open]',
|
|
355
|
+
// aria-modal content
|
|
356
|
+
'[aria-modal="true"]',
|
|
357
|
+
// Sheet/Drawer content
|
|
358
|
+
'[data-state="open"][data-sheet-content]',
|
|
359
|
+
'[data-state="open"][role="complementary"]',
|
|
360
|
+
];
|
|
361
|
+
|
|
362
|
+
for (const selector of contentSelectors) {
|
|
363
|
+
const modal = document.querySelector(selector);
|
|
364
|
+
if (modal) {
|
|
365
|
+
// Return innermost dialog content, not the wrapper
|
|
366
|
+
return modal;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// Fallback: check for z-50 fixed full-screen overlays
|
|
371
|
+
const fixedOverlays = document.querySelectorAll('.fixed.inset-0.z-50');
|
|
372
|
+
for (const overlay of fixedOverlays) {
|
|
373
|
+
// Find the content within (skip if it's just a backdrop)
|
|
374
|
+
const content = overlay.querySelector('.relative.z-10, [data-sonance-name]');
|
|
375
|
+
if (content) return content;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
return null;
|
|
379
|
+
}
|
|
380
|
+
|
|
332
381
|
// Helper to check if a category should show scope options
|
|
333
382
|
export function shouldShowScopeOptions(category: string): boolean {
|
|
334
383
|
if (category === "all") return true; // Show for "all" selection
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sonance-brand-mcp",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.79",
|
|
4
4
|
"description": "MCP Server for Sonance Brand Guidelines and Component Library - gives Claude instant access to brand colors, typography, and UI components.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|