sonance-brand-mcp 1.3.14 → 1.3.16

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.
@@ -0,0 +1,623 @@
1
+ "use client";
2
+
3
+ import React, { useState, useMemo, useCallback, useEffect } from "react";
4
+ import { Box, X, RotateCcw } from "lucide-react";
5
+ import { cn } from "../../../lib/utils";
6
+ import { DetectedElement, AnalysisResult, ComponentStyle, PendingEdit, AIEditResult, ComponentStyleOverride, VisionFocusedElement, VisionPendingEdit, ApplyFirstSession, ApplyFirstStatus } from "../types";
7
+ import { COMPONENT_CONFIG_MAP, getVisibleSections } from "../constants";
8
+ import { componentSnippets, ThemeConfig } from "../../../lib/brand-system";
9
+ import { ChatInterface } from "../components/ChatInterface";
10
+ import { DiffPreview } from "../components/DiffPreview";
11
+ import { VisionDiffPreview } from "../components/VisionDiffPreview";
12
+ import { ApplyFirstPreview } from "../components/ApplyFirstPreview";
13
+
14
+ type ComponentsViewMode = "visual" | "inspector";
15
+
16
+ export interface ComponentsPanelProps {
17
+ copiedId: string | null;
18
+ onCopy: (text: string, id: string) => void;
19
+ installedComponents: string[];
20
+ inspectorEnabled: boolean;
21
+ onToggleInspector: (force?: boolean) => void;
22
+ config: ThemeConfig;
23
+ updateConfig: (updates: Partial<ThemeConfig>) => void;
24
+ onReset: () => void;
25
+ taggedElements: DetectedElement[];
26
+ selectedComponentType: string;
27
+ onSelectComponentType: (type: string) => void;
28
+ componentScope: "all" | "variant" | "page" | "selected";
29
+ onScopeChange: (scope: "all" | "variant" | "page" | "selected") => void;
30
+ selectedComponentId: string | null;
31
+ onSelectComponent: (id: string | null) => void;
32
+ selectedVariantId: string | null;
33
+ onSelectVariant: (id: string | null) => void;
34
+ analysisResult: AnalysisResult | null;
35
+ // Component-specific style overrides
36
+ componentOverrides: Record<string, ComponentStyle>;
37
+ onUpdateComponentOverride: (componentType: string, override: Partial<ComponentStyle>) => void;
38
+ onResetComponentOverrides: () => void;
39
+ // New props for view mode
40
+ viewMode: ComponentsViewMode;
41
+ onViewModeChange: (mode: ComponentsViewMode) => void;
42
+ // Preview mode - lifted to parent for overlay coordination
43
+ isPreviewActive: boolean;
44
+ onPreviewActiveChange: (active: boolean) => void;
45
+ // Vision mode props
46
+ visionMode?: boolean;
47
+ visionFocusedElements?: VisionFocusedElement[];
48
+ onVisionEditComplete?: (result: VisionPendingEdit) => void;
49
+ // Vision pending edit (for VisionDiffPreview)
50
+ visionPendingEdit?: VisionPendingEdit | null;
51
+ onSaveVisionEdit?: () => void;
52
+ onClearVisionPendingEdit?: () => void;
53
+ // Apply-First Mode props
54
+ applyFirstSession?: ApplyFirstSession | null;
55
+ applyFirstStatus?: ApplyFirstStatus;
56
+ onApplyFirstComplete?: (session: ApplyFirstSession) => void;
57
+ onApplyFirstAccept?: () => void;
58
+ onApplyFirstRevert?: () => void;
59
+ }
60
+
61
+ export function ComponentsPanel({
62
+ copiedId,
63
+ onCopy,
64
+ installedComponents,
65
+ inspectorEnabled,
66
+ onToggleInspector,
67
+ config,
68
+ updateConfig,
69
+ onReset,
70
+ taggedElements,
71
+ selectedComponentType,
72
+ onSelectComponentType,
73
+ componentScope,
74
+ onScopeChange,
75
+ selectedComponentId,
76
+ onSelectComponent,
77
+ selectedVariantId,
78
+ onSelectVariant,
79
+ analysisResult,
80
+ componentOverrides,
81
+ onUpdateComponentOverride,
82
+ onResetComponentOverrides,
83
+ viewMode,
84
+ onViewModeChange,
85
+ isPreviewActive,
86
+ onPreviewActiveChange,
87
+ visionMode = false,
88
+ visionFocusedElements = [],
89
+ onVisionEditComplete,
90
+ visionPendingEdit,
91
+ onSaveVisionEdit,
92
+ onClearVisionPendingEdit,
93
+ applyFirstSession,
94
+ applyFirstStatus = "idle",
95
+ onApplyFirstComplete,
96
+ onApplyFirstAccept,
97
+ onApplyFirstRevert,
98
+ }: ComponentsPanelProps) {
99
+ // Auto-activate inspector when entering this tab
100
+ useEffect(() => {
101
+ if (!inspectorEnabled && selectedComponentType === "all") {
102
+ onToggleInspector(true);
103
+ }
104
+ }, []);
105
+
106
+ // AI Chat state
107
+ const [pendingEdit, setPendingEdit] = useState<PendingEdit | null>(null);
108
+ const [aiSaveStatus, setAiSaveStatus] = useState<"idle" | "saving" | "success" | "error">("idle");
109
+ const [aiSaveMessage, setAiSaveMessage] = useState("");
110
+
111
+ // Edit scope: "component" affects all instances, "variant" affects only selected variant
112
+ const [editScope, setEditScope] = useState<"component" | "variant">("component");
113
+
114
+ // Color architecture state
115
+ const [colorArchitecture, setColorArchitecture] = useState<{
116
+ primary: string;
117
+ accent: string;
118
+ sources: { filePath: string; type: string; variables: { name: string; value: string; lineNumber: number }[] }[];
119
+ recommendation: string;
120
+ } | null>(null);
121
+ const [colorSaveStatus, setColorSaveStatus] = useState<"idle" | "saving" | "success" | "error">("idle");
122
+ const [colorSaveMessage, setColorSaveMessage] = useState("");
123
+
124
+ // Get count of detected components on this page
125
+ const pageCount = useMemo(() => {
126
+ if (selectedComponentType === "all") {
127
+ return taggedElements.filter(el => el.type === "component").length;
128
+ }
129
+
130
+ return taggedElements.filter((el) => {
131
+ if (el.type !== "component") return false;
132
+ const name = el.name.toLowerCase();
133
+
134
+ // Match specific component or generic fallback
135
+ if (name === selectedComponentType || name.includes(selectedComponentType)) {
136
+ return true;
137
+ }
138
+ if (selectedComponentType === "button-primary" && name === "button") {
139
+ return true;
140
+ }
141
+ if (selectedComponentType === "input" && name === "input") return true;
142
+
143
+ return false;
144
+ }).length;
145
+ }, [taggedElements, selectedComponentType]);
146
+
147
+ // Get app-wide count from analysis
148
+ const appCount = useMemo(() => {
149
+ if (!analysisResult?.summary?.totalElements) return 0;
150
+
151
+ // For now, use total elements. Could be refined per component type.
152
+ if (selectedComponentType === "all") {
153
+ return analysisResult.summary.totalElements;
154
+ }
155
+
156
+ // Map selected type to analysis categories
157
+ if (selectedComponentType.includes("button")) {
158
+ return analysisResult.summary.byCategory?.interactive?.total || 0;
159
+ }
160
+ if (selectedComponentType === "input" || selectedComponentType === "select" || selectedComponentType === "textarea") {
161
+ return analysisResult.summary.byCategory?.input?.total || 0;
162
+ }
163
+
164
+ return 0;
165
+ }, [analysisResult, selectedComponentType]);
166
+
167
+ // Get the selected component's display name
168
+ const getComponentDisplayName = () => {
169
+ if (selectedComponentType === "all") return "Component";
170
+
171
+ // Check if it's a specific component
172
+ const snippet = componentSnippets.find(s => s.id === selectedComponentType);
173
+ if (snippet) return snippet.name;
174
+
175
+ // Check if it's a category
176
+ const categoryKey = selectedComponentType.toLowerCase();
177
+ if (COMPONENT_CONFIG_MAP[categoryKey]) {
178
+ return categoryKey.charAt(0).toUpperCase() + categoryKey.slice(1);
179
+ }
180
+
181
+ // Capitalize generic names
182
+ return selectedComponentType
183
+ .split('-')
184
+ .map(word => word.charAt(0).toUpperCase() + word.slice(1))
185
+ .join(' ');
186
+ };
187
+
188
+ const selectedComponentName = useMemo(() => {
189
+ if (selectedComponentType === "all") return null;
190
+ return getComponentDisplayName();
191
+ }, [selectedComponentType]);
192
+
193
+ // Get visible config sections for the selected component
194
+ const visibleSections = useMemo(() => {
195
+ return getVisibleSections(selectedComponentType);
196
+ }, [selectedComponentType]);
197
+
198
+ // Get current component's style overrides (or defaults)
199
+ const currentOverrides = useMemo(() => {
200
+ return componentOverrides[selectedComponentType] || {};
201
+ }, [componentOverrides, selectedComponentType]);
202
+
203
+ // Get computed styles of the selected variant for AI context
204
+ const variantStyles = useMemo(() => {
205
+ if (!selectedVariantId) return null;
206
+
207
+ // Find an element with this variant ID
208
+ const element = document.querySelector(`[data-sonance-variant="${selectedVariantId}"]`);
209
+ if (!element) return null;
210
+
211
+ const computed = window.getComputedStyle(element);
212
+ return {
213
+ backgroundColor: computed.backgroundColor,
214
+ color: computed.color,
215
+ borderColor: computed.borderColor,
216
+ borderRadius: computed.borderRadius,
217
+ borderWidth: computed.borderWidth,
218
+ padding: computed.padding,
219
+ fontSize: computed.fontSize,
220
+ fontWeight: computed.fontWeight,
221
+ boxShadow: computed.boxShadow,
222
+ };
223
+ }, [selectedVariantId]);
224
+
225
+ // Reset edit scope when variant selection changes
226
+ useEffect(() => {
227
+ if (selectedVariantId) {
228
+ setEditScope("variant"); // Default to variant when one is selected
229
+ } else {
230
+ setEditScope("component");
231
+ }
232
+ }, [selectedVariantId]);
233
+
234
+ // Helper to update a style property for the selected component
235
+ const updateComponentStyle = useCallback((updates: Partial<ComponentStyleOverride>) => {
236
+ if (selectedComponentType !== "all") {
237
+ onUpdateComponentOverride(selectedComponentType, updates);
238
+ }
239
+ }, [selectedComponentType, onUpdateComponentOverride]);
240
+
241
+ // Get list of page instances matching the selected type (for navigation)
242
+ const pageInstances = useMemo<DetectedElement[]>(() => {
243
+ if (selectedComponentType === "all") {
244
+ return taggedElements.filter((el) => el.type === "component");
245
+ }
246
+
247
+ return taggedElements.filter((el) => {
248
+ if (el.type !== "component") return false;
249
+ const name = el.name.toLowerCase();
250
+
251
+ // Check for specific component match
252
+ if (name === selectedComponentType || name.includes(selectedComponentType)) {
253
+ return true;
254
+ }
255
+
256
+ // Handle generic "button" -> "button-primary" mapping
257
+ if (selectedComponentType === "button-primary" && name === "button") {
258
+ return true;
259
+ }
260
+
261
+ // Handle generic mappings for other types
262
+ if (selectedComponentType === "input" && name === "input") return true;
263
+ if (selectedComponentType === "select" && name === "select") return true;
264
+ if (selectedComponentType === "textarea" && name === "textarea") return true;
265
+
266
+ // Fallback to category matching
267
+ const categoryMappings: Record<string, string[]> = {
268
+ "buttons": ["button"],
269
+ "forms": ["input", "textarea", "select", "checkbox", "radio", "switch"],
270
+ };
271
+
272
+ const keywords = categoryMappings[selectedComponentType] || [];
273
+ return keywords.some((keyword) => name.includes(keyword));
274
+ });
275
+ }, [taggedElements, selectedComponentType]);
276
+
277
+ // Check color architecture on mount
278
+ useEffect(() => {
279
+ async function fetchAnalysis() {
280
+ try {
281
+ const response = await fetch("/api/sonance-analyze");
282
+ if (response.ok) {
283
+ const data = await response.json();
284
+
285
+ // Update color architecture
286
+ if (data.colorArchitecture) {
287
+ setColorArchitecture(data.colorArchitecture);
288
+ }
289
+ }
290
+ } catch {
291
+ // API might not exist yet
292
+ }
293
+ }
294
+ fetchAnalysis();
295
+ }, []);
296
+
297
+ // Handle AI edit completion
298
+ const handleAIEditComplete = useCallback((result: AIEditResult) => {
299
+ // Could do additional processing here if needed
300
+ console.log("AI edit complete:", result);
301
+ }, []);
302
+
303
+ // Handle saving AI edits to file
304
+ const handleSaveAIEdit = useCallback(async () => {
305
+ if (!pendingEdit) return;
306
+
307
+ setAiSaveStatus("saving");
308
+ setAiSaveMessage("");
309
+
310
+ try {
311
+ const response = await fetch("/api/sonance-ai-edit", {
312
+ method: "POST",
313
+ headers: { "Content-Type": "application/json" },
314
+ body: JSON.stringify({
315
+ action: "save",
316
+ componentType: selectedComponentType,
317
+ filePath: pendingEdit.filePath,
318
+ modifiedCode: pendingEdit.modifiedCode,
319
+ }),
320
+ });
321
+
322
+ const data = await response.json();
323
+
324
+ if (!response.ok) {
325
+ throw new Error(data.error || "Failed to save changes");
326
+ }
327
+
328
+ setAiSaveStatus("success");
329
+ setAiSaveMessage(data.message || "Changes saved successfully!");
330
+ onPreviewActiveChange(false); // Clear preview on save
331
+
332
+ // Keep showing success for a bit, then auto-clear
333
+ setTimeout(() => {
334
+ setAiSaveStatus("idle");
335
+ setAiSaveMessage("");
336
+ }, 10000);
337
+ } catch (error) {
338
+ setAiSaveStatus("error");
339
+ setAiSaveMessage(error instanceof Error ? error.message : "Failed to save changes");
340
+
341
+ setTimeout(() => {
342
+ setAiSaveStatus("idle");
343
+ setAiSaveMessage("");
344
+ }, 5000);
345
+ }
346
+ }, [pendingEdit, selectedComponentType]);
347
+
348
+ // Clear pending edit and preview
349
+ const handleClearPendingEdit = useCallback(() => {
350
+ setPendingEdit(null);
351
+ setAiSaveStatus("idle");
352
+ setAiSaveMessage("");
353
+ onPreviewActiveChange(false);
354
+ }, [onPreviewActiveChange]);
355
+
356
+ // Handle preview toggle from DiffPreview
357
+ const handlePreviewToggle = useCallback((active: boolean) => {
358
+ onPreviewActiveChange(active);
359
+ }, [onPreviewActiveChange]);
360
+
361
+ // Handle saving color changes
362
+ const handleSaveColors = async () => {
363
+ if (!colorArchitecture) return;
364
+
365
+ setColorSaveStatus("saving");
366
+ setColorSaveMessage("");
367
+
368
+ try {
369
+ const response = await fetch("/api/sonance-save-colors", {
370
+ method: "POST",
371
+ headers: { "Content-Type": "application/json" },
372
+ body: JSON.stringify({
373
+ primaryColor: config.baseColor,
374
+ accentColor: config.accentColor,
375
+ colorArchitecture,
376
+ }),
377
+ });
378
+
379
+ const data = await response.json();
380
+
381
+ if (!response.ok) {
382
+ throw new Error(data.error || "Failed to save colors");
383
+ }
384
+
385
+ setColorSaveStatus("success");
386
+ setColorSaveMessage(data.message || "Colors saved successfully!");
387
+
388
+ setTimeout(() => {
389
+ setColorSaveStatus("idle");
390
+ setColorSaveMessage("");
391
+ }, 5000);
392
+ } catch (error) {
393
+ setColorSaveStatus("error");
394
+ setColorSaveMessage(error instanceof Error ? error.message : "Failed to save colors");
395
+
396
+ setTimeout(() => {
397
+ setColorSaveStatus("idle");
398
+ setColorSaveMessage("");
399
+ }, 5000);
400
+ }
401
+ };
402
+
403
+ return (
404
+ <div className="space-y-4">
405
+ {/* Selected Component State */}
406
+ {selectedComponentType !== "all" && (
407
+ <>
408
+ {/* Selected Component Header */}
409
+ <div className="p-3 rounded border border-[#00A3E1] bg-[#00A3E1]/5">
410
+ <div className="flex items-center justify-between">
411
+ <div className="flex items-center gap-2">
412
+ <Box className="h-4 w-4 text-[#00A3E1]" />
413
+ <div className="flex flex-col">
414
+ <span id="brand-panel-span-selectedcomponentnam" className="text-sm font-semibold text-gray-900">{selectedComponentName}</span>
415
+ {selectedVariantId && (
416
+ <span id="brand-panel-span-variant-selectedvari" className="text-[10px] text-gray-500 font-mono">
417
+ Variant: {selectedVariantId.substring(0, 6)}
418
+ </span>
419
+ )}
420
+ </div>
421
+ </div>
422
+ <button
423
+ onClick={() => onSelectComponentType("all")}
424
+ className="text-xs text-gray-400 hover:text-gray-600"
425
+ >
426
+ <X className="h-4 w-4" />
427
+ </button>
428
+ </div>
429
+
430
+ {/* Stats */}
431
+ <div className="flex items-center gap-4 mt-2 text-xs text-gray-500">
432
+ <span id="brand-panel-span"><strong className="text-gray-700">{pageCount}</strong> on this page</span>
433
+ {appCount > 0 && (
434
+ <span id="brand-panel-span"><strong className="text-gray-700">{appCount}</strong> in entire app</span>
435
+ )}
436
+ </div>
437
+ </div>
438
+
439
+ {/* Variants List - show all unique variants */}
440
+ {(() => {
441
+ // Get unique variants for this component type
442
+ const variantMap = new Map<string, { count: number; variantId: string }>();
443
+ pageInstances.forEach((el) => {
444
+ if (el.variantId) {
445
+ const existing = variantMap.get(el.variantId);
446
+ if (existing) {
447
+ existing.count++;
448
+ } else {
449
+ variantMap.set(el.variantId, { count: 1, variantId: el.variantId });
450
+ }
451
+ }
452
+ });
453
+
454
+ const variants = Array.from(variantMap.values());
455
+
456
+ if (variants.length > 1) {
457
+ return (
458
+ <div className="space-y-2">
459
+ <div className="flex items-center justify-between">
460
+ <span className="text-xs font-medium text-gray-600">Variants ({variants.length})</span>
461
+ {selectedVariantId && (
462
+ <button
463
+ onClick={() => onSelectVariant(null)}
464
+ className="text-xs text-[#00A3E1] hover:underline"
465
+ >
466
+ Clear selection
467
+ </button>
468
+ )}
469
+ </div>
470
+ <div className="grid grid-cols-2 gap-2">
471
+ {variants.map(({ variantId, count }) => (
472
+ <button
473
+ key={variantId}
474
+ onClick={() => onSelectVariant(selectedVariantId === variantId ? null : variantId)}
475
+ className={cn(
476
+ "p-2 rounded border text-left transition-colors",
477
+ selectedVariantId === variantId
478
+ ? "border-[#00A3E1] bg-[#00A3E1]/10"
479
+ : "border-gray-200 hover:border-gray-300 bg-white"
480
+ )}
481
+ >
482
+ <span className="text-xs font-mono text-gray-700">#{variantId.substring(0, 6)}</span>
483
+ <span className="text-[10px] text-gray-400 ml-1">({count})</span>
484
+ </button>
485
+ ))}
486
+ </div>
487
+ </div>
488
+ );
489
+ }
490
+
491
+ return null;
492
+ })()}
493
+
494
+ {/* No instances on page */}
495
+ {pageInstances.length === 0 && (
496
+ <div className="p-2 rounded border border-amber-200 bg-amber-50 text-center">
497
+ <span id="brand-panel-span-no-instances-on-this" className="text-xs text-amber-700">
498
+ No instances on this page. Navigate to a page with this component.
499
+ </span>
500
+ </div>
501
+ )}
502
+
503
+ {/* Edit Scope Toggle - only show when a variant is selected */}
504
+ {selectedVariantId && (
505
+ <div className="flex items-center gap-2 p-2 bg-gray-50 rounded border border-gray-200">
506
+ <span className="text-xs text-gray-500">Apply changes to:</span>
507
+ <div className="flex gap-1 flex-1">
508
+ <button
509
+ onClick={() => setEditScope("component")}
510
+ className={cn(
511
+ "flex-1 px-2 py-1.5 text-xs font-medium rounded transition-colors",
512
+ editScope === "component"
513
+ ? "bg-[#00A3E1] text-white"
514
+ : "bg-white text-gray-600 border border-gray-200 hover:bg-gray-100"
515
+ )}
516
+ >
517
+ All {selectedComponentName}s
518
+ </button>
519
+ <button
520
+ onClick={() => setEditScope("variant")}
521
+ className={cn(
522
+ "flex-1 px-2 py-1.5 text-xs font-medium rounded transition-colors",
523
+ editScope === "variant"
524
+ ? "bg-[#00A3E1] text-white"
525
+ : "bg-white text-gray-600 border border-gray-200 hover:bg-gray-100"
526
+ )}
527
+ >
528
+ This Variant
529
+ </button>
530
+ </div>
531
+ </div>
532
+ )}
533
+
534
+ </>
535
+ )}
536
+
537
+ {/* How it works - only when no component selected */}
538
+ {selectedComponentType === "all" && (
539
+ <div className="bg-blue-50/50 rounded-lg p-3 border border-blue-100">
540
+ <h4 id="h4-how-it-works" className="text-xs font-semibold text-blue-900 mb-2 uppercase tracking-wide">How it works</h4>
541
+ <ol className="text-xs text-blue-800 space-y-1 list-decimal list-inside">
542
+ <li>Hover & click any element on the page to select it</li>
543
+ <li>Describe how you want to change it below</li>
544
+ </ol>
545
+ </div>
546
+ )}
547
+
548
+ {/* AI Component Editor */}
549
+ <div className="space-y-4 pt-2">
550
+ {/* Pending Edit Preview (single-file component edit) */}
551
+ {pendingEdit && (
552
+ <DiffPreview
553
+ pendingEdit={pendingEdit}
554
+ componentType={selectedComponentType}
555
+ onSave={handleSaveAIEdit}
556
+ onCancel={handleClearPendingEdit}
557
+ onPreviewToggle={handlePreviewToggle}
558
+ isPreviewActive={isPreviewActive}
559
+ saveStatus={aiSaveStatus}
560
+ saveMessage={aiSaveMessage}
561
+ variantId={selectedVariantId}
562
+ />
563
+ )}
564
+
565
+ {/* Vision Mode Pending Edit Preview (multi-file) - OLD: CSS preview */}
566
+ {visionPendingEdit && onSaveVisionEdit && onClearVisionPendingEdit && !applyFirstSession && (
567
+ <VisionDiffPreview
568
+ pendingEdit={visionPendingEdit}
569
+ onSave={onSaveVisionEdit}
570
+ onCancel={onClearVisionPendingEdit}
571
+ />
572
+ )}
573
+
574
+ {/* Apply-First Mode Preview - NEW: Changes already applied */}
575
+ {applyFirstSession && onApplyFirstAccept && onApplyFirstRevert && (
576
+ <ApplyFirstPreview
577
+ session={applyFirstSession}
578
+ status={applyFirstStatus}
579
+ onAccept={onApplyFirstAccept}
580
+ onRevert={onApplyFirstRevert}
581
+ />
582
+ )}
583
+
584
+ {/* AI Chat Interface - hide when any pending edit is present */}
585
+ {!pendingEdit && !visionPendingEdit && !applyFirstSession && (
586
+ <ChatInterface
587
+ componentType={selectedComponentType}
588
+ componentName={selectedComponentName || selectedComponentType}
589
+ onEditComplete={handleAIEditComplete}
590
+ onSaveRequest={setPendingEdit}
591
+ pendingEdit={pendingEdit}
592
+ onClearPending={handleClearPendingEdit}
593
+ editScope={editScope}
594
+ variantId={selectedVariantId}
595
+ variantStyles={variantStyles}
596
+ visionMode={visionMode}
597
+ visionFocusedElements={visionFocusedElements}
598
+ onVisionEditComplete={onVisionEditComplete}
599
+ onApplyFirstComplete={onApplyFirstComplete}
600
+ />
601
+ )}
602
+
603
+ {/* Reset Button */}
604
+ <button
605
+ onClick={() => {
606
+ onReset();
607
+ onResetComponentOverrides();
608
+ handleClearPendingEdit();
609
+ }}
610
+ className={cn(
611
+ "w-full flex items-center justify-center gap-2 py-2.5",
612
+ "text-xs font-medium text-gray-500 hover:text-gray-700",
613
+ "border border-gray-200 rounded hover:bg-gray-50 transition-colors"
614
+ )}
615
+ >
616
+ <RotateCcw className="h-3.5 w-3.5" />
617
+ Reset
618
+ </button>
619
+ </div>
620
+ </div>
621
+ );
622
+ }
623
+