sonance-brand-mcp 1.3.20 → 1.3.21
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,24 +64,30 @@ You can see screenshots of web pages and understand their visual layout, then mo
|
|
|
64
64
|
CRITICAL RULES
|
|
65
65
|
═══════════════════════════════════════════════════════════════════════════════
|
|
66
66
|
|
|
67
|
+
**FILE RULES (MOST IMPORTANT):**
|
|
68
|
+
1. You may ONLY edit files that are provided in the PAGE CONTEXT section
|
|
69
|
+
2. NEVER create new files - only modify existing ones shown to you
|
|
70
|
+
3. The filePath in your response MUST exactly match one of the provided file paths
|
|
71
|
+
4. If you cannot find the right file to edit, explain this in your response instead of creating a new file
|
|
72
|
+
|
|
67
73
|
**ANALYSIS:**
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
74
|
+
5. CAREFULLY analyze the screenshot to understand the current visual state
|
|
75
|
+
6. Identify elements mentioned in the user's request
|
|
76
|
+
7. Understand the current styling and layout
|
|
77
|
+
8. Consider how changes will affect the overall design
|
|
72
78
|
|
|
73
79
|
**PRESERVATION RULES:**
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
80
|
+
9. NEVER delete or remove existing content, children, or JSX elements
|
|
81
|
+
10. NEVER change component structure unless specifically requested
|
|
82
|
+
11. NEVER modify TypeScript types, imports, or exports unless necessary
|
|
83
|
+
12. NEVER remove data-sonance-* attributes
|
|
78
84
|
|
|
79
85
|
**CHANGE RULES:**
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
86
|
+
13. Make ONLY the changes requested by the user
|
|
87
|
+
14. Modify the minimum amount of code necessary
|
|
88
|
+
15. Use semantic Tailwind classes (bg-primary, text-foreground, etc.)
|
|
89
|
+
16. Maintain dark mode compatibility with CSS variables
|
|
90
|
+
17. Keep the cn() utility for className merging
|
|
85
91
|
|
|
86
92
|
**SONANCE BRAND COLORS:**
|
|
87
93
|
- Charcoal: #333F48, #343D46 (primary)
|
|
@@ -188,8 +194,8 @@ export async function POST(request: Request) {
|
|
|
188
194
|
);
|
|
189
195
|
}
|
|
190
196
|
|
|
191
|
-
// Gather page context
|
|
192
|
-
const pageContext = gatherPageContext(pageRoute, projectRoot);
|
|
197
|
+
// Gather page context (including focused element files via project-wide search)
|
|
198
|
+
const pageContext = gatherPageContext(pageRoute, projectRoot, focusedElements);
|
|
193
199
|
|
|
194
200
|
// Build user message with vision
|
|
195
201
|
const messageContent: Anthropic.MessageCreateParams["messages"][0]["content"] = [];
|
|
@@ -247,13 +253,19 @@ GLOBALS.CSS (relevant theme variables):
|
|
|
247
253
|
${pageContext.globalsCSS.substring(0, 2000)}${pageContext.globalsCSS.length > 2000 ? "\n/* ... (truncated) */" : ""}
|
|
248
254
|
\`\`\`
|
|
249
255
|
|
|
256
|
+
VALID FILES YOU MAY EDIT:
|
|
257
|
+
${pageContext.pageFile ? `- ${pageContext.pageFile}` : ""}
|
|
258
|
+
${pageContext.componentSources.map((c) => `- ${c.path}`).join("\n")}
|
|
259
|
+
|
|
250
260
|
INSTRUCTIONS:
|
|
251
261
|
1. Look at the screenshot and identify elements mentioned in the user's request
|
|
252
|
-
2. Review the code to understand current implementation
|
|
253
|
-
3.
|
|
262
|
+
2. Review the provided code to understand current implementation
|
|
263
|
+
3. Choose which of the VALID FILES above need modifications
|
|
254
264
|
4. Generate complete modified code for each file
|
|
255
265
|
5. Provide previewCSS for immediate visual feedback
|
|
256
|
-
6. Return as JSON in the specified format
|
|
266
|
+
6. Return as JSON in the specified format
|
|
267
|
+
|
|
268
|
+
CRITICAL: Only use file paths from the VALID FILES list above. Do NOT create new files.`;
|
|
257
269
|
|
|
258
270
|
messageContent.push({
|
|
259
271
|
type: "text",
|
|
@@ -327,6 +339,34 @@ INSTRUCTIONS:
|
|
|
327
339
|
);
|
|
328
340
|
}
|
|
329
341
|
|
|
342
|
+
// Build list of valid file paths
|
|
343
|
+
const validPaths = new Set<string>();
|
|
344
|
+
if (pageContext.pageFile) {
|
|
345
|
+
validPaths.add(pageContext.pageFile);
|
|
346
|
+
}
|
|
347
|
+
for (const comp of pageContext.componentSources) {
|
|
348
|
+
validPaths.add(comp.path);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// Validate AI response - reject any file paths not in our valid list
|
|
352
|
+
const invalidMods = (aiResponse.modifications || []).filter(
|
|
353
|
+
(mod) => !validPaths.has(mod.filePath)
|
|
354
|
+
);
|
|
355
|
+
|
|
356
|
+
if (invalidMods.length > 0) {
|
|
357
|
+
console.error(
|
|
358
|
+
"AI attempted to create new files:",
|
|
359
|
+
invalidMods.map((m) => m.filePath)
|
|
360
|
+
);
|
|
361
|
+
return NextResponse.json(
|
|
362
|
+
{
|
|
363
|
+
success: false,
|
|
364
|
+
error: `Cannot create new files. The following paths were not found in the project: ${invalidMods.map((m) => m.filePath).join(", ")}. Please try a more specific request targeting existing components.`,
|
|
365
|
+
} as VisionEditResponse,
|
|
366
|
+
{ status: 400 }
|
|
367
|
+
);
|
|
368
|
+
}
|
|
369
|
+
|
|
330
370
|
// Read original content and generate diffs for each modification
|
|
331
371
|
const modificationsWithOriginals: VisionFileModification[] = [];
|
|
332
372
|
for (const mod of aiResponse.modifications || []) {
|
|
@@ -374,12 +414,77 @@ INSTRUCTIONS:
|
|
|
374
414
|
}
|
|
375
415
|
}
|
|
376
416
|
|
|
417
|
+
/**
|
|
418
|
+
* Search for component files by name across the project
|
|
419
|
+
* Uses similar logic to /api/sonance-find-component
|
|
420
|
+
*/
|
|
421
|
+
function findComponentFileByName(
|
|
422
|
+
componentName: string,
|
|
423
|
+
projectRoot: string
|
|
424
|
+
): string | null {
|
|
425
|
+
const normalizedName = componentName.toLowerCase().replace(/\s+/g, "-");
|
|
426
|
+
const baseName = normalizedName.split("-")[0];
|
|
427
|
+
|
|
428
|
+
const SEARCH_DIRS = ["src/components", "components", "src", "app"];
|
|
429
|
+
const EXTENSIONS = [".tsx", ".jsx", ".ts", ".js"];
|
|
430
|
+
|
|
431
|
+
function searchRecursive(
|
|
432
|
+
dir: string,
|
|
433
|
+
fileName: string,
|
|
434
|
+
depth = 0
|
|
435
|
+
): string | null {
|
|
436
|
+
if (depth > 4) return null;
|
|
437
|
+
try {
|
|
438
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
439
|
+
// Check direct matches
|
|
440
|
+
for (const ext of EXTENSIONS) {
|
|
441
|
+
const match = entries.find(
|
|
442
|
+
(e) => e.isFile() && e.name.toLowerCase() === `${fileName}${ext}`
|
|
443
|
+
);
|
|
444
|
+
if (match) return path.join(dir, match.name);
|
|
445
|
+
}
|
|
446
|
+
// Recurse into subdirs
|
|
447
|
+
for (const entry of entries) {
|
|
448
|
+
if (
|
|
449
|
+
entry.isDirectory() &&
|
|
450
|
+
!entry.name.startsWith(".") &&
|
|
451
|
+
entry.name !== "node_modules"
|
|
452
|
+
) {
|
|
453
|
+
const result = searchRecursive(
|
|
454
|
+
path.join(dir, entry.name),
|
|
455
|
+
fileName,
|
|
456
|
+
depth + 1
|
|
457
|
+
);
|
|
458
|
+
if (result) return result;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
} catch {
|
|
462
|
+
/* skip unreadable */
|
|
463
|
+
}
|
|
464
|
+
return null;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
for (const dir of SEARCH_DIRS) {
|
|
468
|
+
const searchDir = path.join(projectRoot, dir);
|
|
469
|
+
if (fs.existsSync(searchDir)) {
|
|
470
|
+
const result = searchRecursive(searchDir, normalizedName);
|
|
471
|
+
if (result) return path.relative(projectRoot, result);
|
|
472
|
+
if (baseName !== normalizedName) {
|
|
473
|
+
const baseResult = searchRecursive(searchDir, baseName);
|
|
474
|
+
if (baseResult) return path.relative(projectRoot, baseResult);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
return null;
|
|
479
|
+
}
|
|
480
|
+
|
|
377
481
|
/**
|
|
378
482
|
* Gather context about the current page for AI analysis
|
|
379
483
|
*/
|
|
380
484
|
function gatherPageContext(
|
|
381
485
|
pageRoute: string,
|
|
382
|
-
projectRoot: string
|
|
486
|
+
projectRoot: string,
|
|
487
|
+
focusedElements?: VisionFocusedElement[]
|
|
383
488
|
): {
|
|
384
489
|
pageFile: string | null;
|
|
385
490
|
pageContent: string;
|
|
@@ -412,6 +517,25 @@ function gatherPageContext(
|
|
|
412
517
|
}
|
|
413
518
|
}
|
|
414
519
|
|
|
520
|
+
// Also search for focused element components if provided
|
|
521
|
+
if (focusedElements && focusedElements.length > 0) {
|
|
522
|
+
for (const el of focusedElements) {
|
|
523
|
+
// Try to find component file by name
|
|
524
|
+
const foundPath = findComponentFileByName(el.name, projectRoot);
|
|
525
|
+
if (foundPath && !componentSources.some((c) => c.path === foundPath)) {
|
|
526
|
+
try {
|
|
527
|
+
const content = fs.readFileSync(
|
|
528
|
+
path.join(projectRoot, foundPath),
|
|
529
|
+
"utf-8"
|
|
530
|
+
);
|
|
531
|
+
componentSources.push({ path: foundPath, content });
|
|
532
|
+
} catch {
|
|
533
|
+
/* skip if unreadable */
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
|
|
415
539
|
// Read globals.css
|
|
416
540
|
let globalsCSS = "";
|
|
417
541
|
const globalsPath = path.join(projectRoot, "src/app/globals.css");
|
|
@@ -11,6 +11,8 @@ interface CheckboxProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>
|
|
|
11
11
|
description?: string;
|
|
12
12
|
/** Visual state for Storybook/Figma documentation */
|
|
13
13
|
state?: CheckboxState;
|
|
14
|
+
/** Radix UI-style callback for checked state changes */
|
|
15
|
+
onCheckedChange?: (checked: boolean) => void;
|
|
14
16
|
}
|
|
15
17
|
|
|
16
18
|
// State styles for Storybook/Figma visualization
|
|
@@ -28,17 +30,23 @@ const getStateStyles = (state?: CheckboxState) => {
|
|
|
28
30
|
};
|
|
29
31
|
|
|
30
32
|
export const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>(
|
|
31
|
-
({ className, label, description, id, state, disabled, checked, defaultChecked, onChange, style, ...props }, ref) => {
|
|
33
|
+
({ className, label, description, id, state, disabled, checked, defaultChecked, onChange, onCheckedChange, style, ...props }, ref) => {
|
|
32
34
|
const uniqueId = useId();
|
|
33
35
|
const inputId = id || `checkbox-${uniqueId}`;
|
|
34
36
|
const isDisabled = disabled || state === "disabled";
|
|
35
37
|
|
|
36
|
-
// Determine if we're in controlled mode
|
|
37
|
-
const isControlled = checked !== undefined
|
|
38
|
+
// Determine if we're in controlled mode (only when checked is explicitly provided)
|
|
39
|
+
const isControlled = checked !== undefined;
|
|
38
40
|
const isCheckedForState = state === "checked";
|
|
39
41
|
|
|
42
|
+
// Combined change handler that supports both native onChange and Radix-style onCheckedChange
|
|
43
|
+
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
44
|
+
onChange?.(e);
|
|
45
|
+
onCheckedChange?.(e.target.checked);
|
|
46
|
+
};
|
|
47
|
+
|
|
40
48
|
return (
|
|
41
|
-
<
|
|
49
|
+
<label data-sonance-name="checkbox" className="flex items-start gap-3 cursor-pointer">
|
|
42
50
|
<div className="relative flex items-center justify-center">
|
|
43
51
|
<input
|
|
44
52
|
type="checkbox"
|
|
@@ -46,10 +54,12 @@ export const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>(
|
|
|
46
54
|
ref={ref}
|
|
47
55
|
disabled={isDisabled}
|
|
48
56
|
style={style}
|
|
57
|
+
onChange={handleChange}
|
|
49
58
|
{...(isControlled
|
|
50
|
-
? { checked: checked
|
|
51
|
-
: { defaultChecked: defaultChecked
|
|
59
|
+
? { checked: checked ?? isCheckedForState }
|
|
60
|
+
: { defaultChecked: defaultChecked ?? isCheckedForState }
|
|
52
61
|
)}
|
|
62
|
+
readOnly={isControlled && !onChange && !onCheckedChange}
|
|
53
63
|
className={cn(
|
|
54
64
|
"peer h-5 w-5 shrink-0 appearance-none border border-border bg-input",
|
|
55
65
|
"hover:border-border-hover",
|
|
@@ -59,7 +69,7 @@ export const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>(
|
|
|
59
69
|
"transition-colors duration-150",
|
|
60
70
|
getStateStyles(state),
|
|
61
71
|
className
|
|
62
|
-
)}
|
|
72
|
+
)}
|
|
63
73
|
{...props}
|
|
64
74
|
/>
|
|
65
75
|
<Check
|
|
@@ -72,19 +82,18 @@ export const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>(
|
|
|
72
82
|
{(label || description) && (
|
|
73
83
|
<div className="flex flex-col gap-0.5">
|
|
74
84
|
{label && (
|
|
75
|
-
<
|
|
76
|
-
|
|
77
|
-
className="text-sm font-medium text-foreground cursor-pointer select-none"
|
|
85
|
+
<span
|
|
86
|
+
className="text-sm font-medium text-foreground select-none"
|
|
78
87
|
>
|
|
79
88
|
{label}
|
|
80
|
-
</
|
|
89
|
+
</span>
|
|
81
90
|
)}
|
|
82
91
|
{description && (
|
|
83
92
|
<p id="p-description" className="text-xs text-foreground-muted">{description}</p>
|
|
84
93
|
)}
|
|
85
94
|
</div>
|
|
86
95
|
)}
|
|
87
|
-
</
|
|
96
|
+
</label>
|
|
88
97
|
);
|
|
89
98
|
}
|
|
90
99
|
);
|
|
@@ -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 } 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 } from "lucide-react";
|
|
6
6
|
import { useTheme } from "next-themes";
|
|
7
7
|
import { cn } from "../../lib/utils";
|
|
8
8
|
import {
|
|
@@ -118,6 +118,14 @@ export function SonanceDevTools() {
|
|
|
118
118
|
// Track which elements were changed (for highlighting until accept/revert)
|
|
119
119
|
const [changedElements, setChangedElements] = useState<VisionFocusedElement[]>([]);
|
|
120
120
|
|
|
121
|
+
// Drag state for movable panel
|
|
122
|
+
const DEVTOOLS_POSITION_KEY = "sonance-devtools-pos";
|
|
123
|
+
const DEFAULT_POSITION = { x: 0, y: 0 }; // Offset from default bottom-right position
|
|
124
|
+
const [dragPosition, setDragPosition] = useState(DEFAULT_POSITION);
|
|
125
|
+
const [isDragging, setIsDragging] = useState(false);
|
|
126
|
+
const dragOffsetRef = useRef({ x: 0, y: 0 });
|
|
127
|
+
const panelRef = useRef<HTMLDivElement>(null);
|
|
128
|
+
|
|
121
129
|
// Component-specific style overrides (for scalable, project-agnostic styling)
|
|
122
130
|
// Key: component type (e.g., "card", "button-primary", "card:variant123"), Value: style overrides
|
|
123
131
|
const [componentOverrides, setComponentOverrides] = useState<Record<string, ComponentStyle>>({});
|
|
@@ -236,6 +244,93 @@ export function SonanceDevTools() {
|
|
|
236
244
|
setMounted(true);
|
|
237
245
|
}, []);
|
|
238
246
|
|
|
247
|
+
// Load drag position from localStorage on mount
|
|
248
|
+
useEffect(() => {
|
|
249
|
+
if (!mounted) return;
|
|
250
|
+
try {
|
|
251
|
+
const saved = localStorage.getItem(DEVTOOLS_POSITION_KEY);
|
|
252
|
+
if (saved) {
|
|
253
|
+
const parsed = JSON.parse(saved);
|
|
254
|
+
if (typeof parsed.x === "number" && typeof parsed.y === "number") {
|
|
255
|
+
setDragPosition(parsed);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
} catch {
|
|
259
|
+
// Ignore parse errors
|
|
260
|
+
}
|
|
261
|
+
}, [mounted, DEVTOOLS_POSITION_KEY]);
|
|
262
|
+
|
|
263
|
+
// Drag handlers for movable panel
|
|
264
|
+
const headerRef = useRef<HTMLDivElement>(null);
|
|
265
|
+
|
|
266
|
+
const handleDragStart = useCallback((e: React.PointerEvent<HTMLDivElement>) => {
|
|
267
|
+
// Don't start drag if clicking on a button or interactive element
|
|
268
|
+
const target = e.target as HTMLElement;
|
|
269
|
+
if (target.closest('button')) return;
|
|
270
|
+
|
|
271
|
+
if (!panelRef.current || !headerRef.current) return;
|
|
272
|
+
|
|
273
|
+
// Prevent text selection during drag
|
|
274
|
+
e.preventDefault();
|
|
275
|
+
|
|
276
|
+
// Store the initial mouse position and current panel offset
|
|
277
|
+
dragOffsetRef.current = {
|
|
278
|
+
x: e.clientX - dragPosition.x,
|
|
279
|
+
y: e.clientY - dragPosition.y,
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
setIsDragging(true);
|
|
283
|
+
headerRef.current.setPointerCapture(e.pointerId);
|
|
284
|
+
}, [dragPosition]);
|
|
285
|
+
|
|
286
|
+
const handleDragMove = useCallback((e: React.PointerEvent<HTMLDivElement>) => {
|
|
287
|
+
if (!isDragging || !panelRef.current) return;
|
|
288
|
+
|
|
289
|
+
const viewportWidth = window.innerWidth;
|
|
290
|
+
const viewportHeight = window.innerHeight;
|
|
291
|
+
const panelWidth = panelRef.current.offsetWidth;
|
|
292
|
+
const panelHeight = panelRef.current.offsetHeight;
|
|
293
|
+
|
|
294
|
+
// Calculate new position directly from mouse movement
|
|
295
|
+
const newX = e.clientX - dragOffsetRef.current.x;
|
|
296
|
+
const newY = e.clientY - dragOffsetRef.current.y;
|
|
297
|
+
|
|
298
|
+
// Clamp to keep panel within viewport (with 24px padding)
|
|
299
|
+
const padding = 24;
|
|
300
|
+
const maxX = viewportWidth - panelWidth - padding;
|
|
301
|
+
const maxY = viewportHeight - panelHeight - padding;
|
|
302
|
+
const minX = -(viewportWidth - panelWidth - padding);
|
|
303
|
+
const minY = -(viewportHeight - panelHeight - padding);
|
|
304
|
+
|
|
305
|
+
setDragPosition({
|
|
306
|
+
x: Math.max(minX, Math.min(maxX, newX)),
|
|
307
|
+
y: Math.max(minY, Math.min(maxY, newY)),
|
|
308
|
+
});
|
|
309
|
+
}, [isDragging]);
|
|
310
|
+
|
|
311
|
+
const handleDragEnd = useCallback((e: React.PointerEvent<HTMLDivElement>) => {
|
|
312
|
+
if (!isDragging || !headerRef.current) return;
|
|
313
|
+
|
|
314
|
+
setIsDragging(false);
|
|
315
|
+
headerRef.current.releasePointerCapture(e.pointerId);
|
|
316
|
+
|
|
317
|
+
// Save position to localStorage
|
|
318
|
+
try {
|
|
319
|
+
localStorage.setItem(DEVTOOLS_POSITION_KEY, JSON.stringify(dragPosition));
|
|
320
|
+
} catch {
|
|
321
|
+
// Ignore storage errors
|
|
322
|
+
}
|
|
323
|
+
}, [isDragging, dragPosition, DEVTOOLS_POSITION_KEY]);
|
|
324
|
+
|
|
325
|
+
const handleResetPosition = useCallback(() => {
|
|
326
|
+
setDragPosition(DEFAULT_POSITION);
|
|
327
|
+
try {
|
|
328
|
+
localStorage.removeItem(DEVTOOLS_POSITION_KEY);
|
|
329
|
+
} catch {
|
|
330
|
+
// Ignore storage errors
|
|
331
|
+
}
|
|
332
|
+
}, [DEVTOOLS_POSITION_KEY]);
|
|
333
|
+
|
|
239
334
|
// Inject/update component-specific preview styles whenever overrides change
|
|
240
335
|
useEffect(() => {
|
|
241
336
|
if (!mounted) return;
|
|
@@ -2172,8 +2267,9 @@ export function SonanceDevTools() {
|
|
|
2172
2267
|
// Close and reset
|
|
2173
2268
|
const handleClose = useCallback(() => {
|
|
2174
2269
|
setIsOpen(false);
|
|
2175
|
-
// Disable inspector so user can interact with the app
|
|
2270
|
+
// Disable inspector and vision mode so user can interact with the app
|
|
2176
2271
|
setInspectorEnabled(false);
|
|
2272
|
+
setVisionModeActive(false);
|
|
2177
2273
|
resetThemeFromDOM();
|
|
2178
2274
|
// Reset color change tracking for next session
|
|
2179
2275
|
setColorsExplicitlyChanged(false);
|
|
@@ -2201,19 +2297,37 @@ export function SonanceDevTools() {
|
|
|
2201
2297
|
|
|
2202
2298
|
const panel = isOpen && (
|
|
2203
2299
|
<div
|
|
2300
|
+
ref={panelRef}
|
|
2204
2301
|
data-sonance-devtools="true"
|
|
2205
2302
|
className={cn(
|
|
2206
2303
|
"fixed bottom-6 right-6 z-[9999]",
|
|
2207
2304
|
"w-[360px] max-h-[80vh]",
|
|
2208
2305
|
"bg-white rounded-lg shadow-2xl border border-gray-200",
|
|
2209
2306
|
"flex flex-col overflow-hidden",
|
|
2210
|
-
"font-['Montserrat',sans-serif]"
|
|
2307
|
+
"font-['Montserrat',sans-serif]",
|
|
2308
|
+
isDragging && "select-none"
|
|
2211
2309
|
)}
|
|
2212
|
-
style={{
|
|
2310
|
+
style={{
|
|
2311
|
+
colorScheme: "light",
|
|
2312
|
+
transform: `translate(${dragPosition.x}px, ${dragPosition.y}px)`,
|
|
2313
|
+
}}
|
|
2213
2314
|
>
|
|
2214
|
-
{/* Header -
|
|
2215
|
-
<div
|
|
2315
|
+
{/* Header - Draggable */}
|
|
2316
|
+
<div
|
|
2317
|
+
ref={headerRef}
|
|
2318
|
+
className={cn(
|
|
2319
|
+
"flex items-center justify-between px-4 py-3 border-b border-gray-200 bg-[#333F48]",
|
|
2320
|
+
"cursor-move touch-none"
|
|
2321
|
+
)}
|
|
2322
|
+
onPointerDown={handleDragStart}
|
|
2323
|
+
onPointerMove={handleDragMove}
|
|
2324
|
+
onPointerUp={handleDragEnd}
|
|
2325
|
+
onPointerCancel={handleDragEnd}
|
|
2326
|
+
onDoubleClick={handleResetPosition}
|
|
2327
|
+
title="Drag to move • Double-click to reset position"
|
|
2328
|
+
>
|
|
2216
2329
|
<div className="flex items-center gap-2">
|
|
2330
|
+
<GripHorizontal className="h-4 w-4 text-white/50" />
|
|
2217
2331
|
<Palette className="h-5 w-5 text-[#00A3E1]" />
|
|
2218
2332
|
<span id="span-sonance-devtools" className="text-sm font-semibold text-white">
|
|
2219
2333
|
Sonance DevTools
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sonance-brand-mcp",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.21",
|
|
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",
|