@antigenic-oss/paint 0.1.0

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.
Files changed (158) hide show
  1. package/LICENSE +178 -0
  2. package/NOTICE +4 -0
  3. package/README.md +180 -0
  4. package/bin/paint.js +266 -0
  5. package/next-env.d.ts +6 -0
  6. package/next.config.ts +19 -0
  7. package/package.json +81 -0
  8. package/postcss.config.mjs +8 -0
  9. package/public/dev-editor-inspector.js +1872 -0
  10. package/src/app/api/claude/analyze/route.ts +319 -0
  11. package/src/app/api/claude/apply/route.ts +185 -0
  12. package/src/app/api/claude/pick-folder/route.ts +64 -0
  13. package/src/app/api/claude/scan/route.ts +221 -0
  14. package/src/app/api/claude/status/route.ts +55 -0
  15. package/src/app/api/project/scan/route.ts +634 -0
  16. package/src/app/api/project-scan/css-variables/route.ts +238 -0
  17. package/src/app/api/project-scan/route.ts +40 -0
  18. package/src/app/api/project-scan/tailwind-config/route.ts +172 -0
  19. package/src/app/api/proxy/[[...path]]/route.ts +2400 -0
  20. package/src/app/docs/DocsClient.tsx +322 -0
  21. package/src/app/docs/layout.tsx +7 -0
  22. package/src/app/docs/page.tsx +855 -0
  23. package/src/app/globals.css +176 -0
  24. package/src/app/layout.tsx +19 -0
  25. package/src/app/page.tsx +46 -0
  26. package/src/bridge/api-handlers.ts +885 -0
  27. package/src/bridge/proxy-handler.ts +329 -0
  28. package/src/bridge/server.ts +113 -0
  29. package/src/components/BreakpointTabs.tsx +72 -0
  30. package/src/components/ChangeSummaryModal.tsx +267 -0
  31. package/src/components/ConnectModal.tsx +994 -0
  32. package/src/components/Editor.tsx +90 -0
  33. package/src/components/PageSelector.tsx +208 -0
  34. package/src/components/PreviewFrame.tsx +299 -0
  35. package/src/components/ProjectFolderBanner.tsx +91 -0
  36. package/src/components/ResponsiveToolbar.tsx +222 -0
  37. package/src/components/TargetSelector.tsx +243 -0
  38. package/src/components/TopBar.tsx +315 -0
  39. package/src/components/common/CollapsibleSection.tsx +36 -0
  40. package/src/components/common/ColorPicker.tsx +920 -0
  41. package/src/components/common/EditablePre.tsx +136 -0
  42. package/src/components/common/ErrorBoundary.tsx +65 -0
  43. package/src/components/common/ResizablePanel.tsx +83 -0
  44. package/src/components/common/ScanAnimation.tsx +76 -0
  45. package/src/components/common/ToastContainer.tsx +97 -0
  46. package/src/components/common/UnitInput.tsx +77 -0
  47. package/src/components/common/VariableColorPicker.tsx +622 -0
  48. package/src/components/left-panel/AddElementPanel.tsx +237 -0
  49. package/src/components/left-panel/ComponentsPanel.tsx +609 -0
  50. package/src/components/left-panel/IconSidebar.tsx +99 -0
  51. package/src/components/left-panel/LayerNode.tsx +874 -0
  52. package/src/components/left-panel/LayerSearch.tsx +23 -0
  53. package/src/components/left-panel/LayersPanel.tsx +52 -0
  54. package/src/components/left-panel/LeftPanel.tsx +122 -0
  55. package/src/components/left-panel/PagesPanel.tsx +114 -0
  56. package/src/components/left-panel/icons.tsx +162 -0
  57. package/src/components/left-panel/terminal/ScanOverlay.tsx +66 -0
  58. package/src/components/left-panel/terminal/TerminalPanel.tsx +217 -0
  59. package/src/components/right-panel/ElementLogBox.tsx +248 -0
  60. package/src/components/right-panel/PanelTabs.tsx +83 -0
  61. package/src/components/right-panel/RightPanel.tsx +41 -0
  62. package/src/components/right-panel/changes/AiScanResultPanel.tsx +285 -0
  63. package/src/components/right-panel/changes/ChangeEntry.tsx +59 -0
  64. package/src/components/right-panel/changes/ChangelogActions.tsx +105 -0
  65. package/src/components/right-panel/changes/ChangesPanel.tsx +1474 -0
  66. package/src/components/right-panel/claude/ApplyConfirmModal.tsx +376 -0
  67. package/src/components/right-panel/claude/ClaudeErrorState.tsx +125 -0
  68. package/src/components/right-panel/claude/ClaudeIntegrationPanel.tsx +482 -0
  69. package/src/components/right-panel/claude/ClaudeProgressIndicator.tsx +76 -0
  70. package/src/components/right-panel/claude/DiffCard.tsx +130 -0
  71. package/src/components/right-panel/claude/DiffViewer.tsx +54 -0
  72. package/src/components/right-panel/claude/ProjectRootSelector.tsx +275 -0
  73. package/src/components/right-panel/claude/ResultsSummary.tsx +119 -0
  74. package/src/components/right-panel/claude/SetupFlow.tsx +315 -0
  75. package/src/components/right-panel/console/ConsolePanel.tsx +209 -0
  76. package/src/components/right-panel/design/AppearanceSection.tsx +703 -0
  77. package/src/components/right-panel/design/BackgroundSection.tsx +516 -0
  78. package/src/components/right-panel/design/BorderSection.tsx +161 -0
  79. package/src/components/right-panel/design/CSSRawView.tsx +412 -0
  80. package/src/components/right-panel/design/DesignCSSTabToggle.tsx +51 -0
  81. package/src/components/right-panel/design/DesignPanel.tsx +275 -0
  82. package/src/components/right-panel/design/ElementBreadcrumb.tsx +51 -0
  83. package/src/components/right-panel/design/GradientEditor.tsx +726 -0
  84. package/src/components/right-panel/design/LayoutSection.tsx +1948 -0
  85. package/src/components/right-panel/design/PositionSection.tsx +865 -0
  86. package/src/components/right-panel/design/PropertiesSection.tsx +86 -0
  87. package/src/components/right-panel/design/SVGSection.tsx +361 -0
  88. package/src/components/right-panel/design/ShadowBlurSection.tsx +227 -0
  89. package/src/components/right-panel/design/SizeSection.tsx +183 -0
  90. package/src/components/right-panel/design/TextSection.tsx +719 -0
  91. package/src/components/right-panel/design/icons.tsx +948 -0
  92. package/src/components/right-panel/design/inputs/BoxModelPreview.tsx +467 -0
  93. package/src/components/right-panel/design/inputs/ColorInput.tsx +43 -0
  94. package/src/components/right-panel/design/inputs/CompactInput.tsx +333 -0
  95. package/src/components/right-panel/design/inputs/DraggableLabel.tsx +118 -0
  96. package/src/components/right-panel/design/inputs/IconToggleGroup.tsx +54 -0
  97. package/src/components/right-panel/design/inputs/LinkedInputPair.tsx +174 -0
  98. package/src/components/right-panel/design/inputs/SectionHeader.tsx +79 -0
  99. package/src/components/right-panel/variables/VariablesPanel.tsx +388 -0
  100. package/src/hooks/useBridge.ts +95 -0
  101. package/src/hooks/useChangeTracker.ts +563 -0
  102. package/src/hooks/useClaudeAPI.ts +118 -0
  103. package/src/hooks/useDOMTree.ts +25 -0
  104. package/src/hooks/useKeyboardShortcuts.ts +76 -0
  105. package/src/hooks/usePostMessage.ts +589 -0
  106. package/src/hooks/useProjectScan.ts +204 -0
  107. package/src/hooks/useResizable.ts +20 -0
  108. package/src/hooks/useSelectedElement.ts +51 -0
  109. package/src/hooks/useTargetUrl.ts +81 -0
  110. package/src/inspector/DOMTraverser.ts +71 -0
  111. package/src/inspector/ElementSelector.ts +23 -0
  112. package/src/inspector/HoverHighlighter.ts +54 -0
  113. package/src/inspector/SelectionHighlighter.ts +27 -0
  114. package/src/inspector/StyleExtractor.ts +19 -0
  115. package/src/inspector/inspector.ts +17 -0
  116. package/src/inspector/messaging.ts +30 -0
  117. package/src/lib/apiBase.ts +15 -0
  118. package/src/lib/classifyElement.ts +430 -0
  119. package/src/lib/claude-bin.ts +197 -0
  120. package/src/lib/claude-stream.ts +158 -0
  121. package/src/lib/clientProjectScanner.ts +344 -0
  122. package/src/lib/componentMatcher.ts +156 -0
  123. package/src/lib/constants.ts +573 -0
  124. package/src/lib/cssVariableUtils.ts +409 -0
  125. package/src/lib/diffParser.ts +206 -0
  126. package/src/lib/folderPicker.ts +84 -0
  127. package/src/lib/gradientParser.ts +160 -0
  128. package/src/lib/projectScanner.ts +355 -0
  129. package/src/lib/promptBuilder.ts +402 -0
  130. package/src/lib/shadowParser.ts +124 -0
  131. package/src/lib/tailwindClassParser.ts +248 -0
  132. package/src/lib/textShadowUtils.ts +106 -0
  133. package/src/lib/utils.ts +299 -0
  134. package/src/lib/validatePath.ts +40 -0
  135. package/src/proxy.ts +92 -0
  136. package/src/server/terminal-server.ts +104 -0
  137. package/src/store/changeSlice.ts +288 -0
  138. package/src/store/claudeSlice.ts +222 -0
  139. package/src/store/componentSlice.ts +90 -0
  140. package/src/store/consoleSlice.ts +51 -0
  141. package/src/store/cssVariableSlice.ts +94 -0
  142. package/src/store/elementSlice.ts +78 -0
  143. package/src/store/index.ts +35 -0
  144. package/src/store/terminalSlice.ts +30 -0
  145. package/src/store/treeSlice.ts +69 -0
  146. package/src/store/uiSlice.ts +327 -0
  147. package/src/types/changelog.ts +49 -0
  148. package/src/types/claude.ts +131 -0
  149. package/src/types/component.ts +49 -0
  150. package/src/types/cssVariables.ts +18 -0
  151. package/src/types/element.ts +21 -0
  152. package/src/types/file-system-access.d.ts +27 -0
  153. package/src/types/gradient.ts +12 -0
  154. package/src/types/messages.ts +392 -0
  155. package/src/types/shadow.ts +8 -0
  156. package/src/types/tree.ts +9 -0
  157. package/tsconfig.json +42 -0
  158. package/tsconfig.server.json +12 -0
@@ -0,0 +1,412 @@
1
+ 'use client'
2
+
3
+ import { useState, useMemo, useCallback, useRef, useEffect } from 'react'
4
+ import { useEditorStore } from '@/store'
5
+ import { CSS_PROPERTIES } from '@/lib/constants'
6
+ import { camelToKebab } from '@/lib/utils'
7
+
8
+ const GROUP_LABELS: Record<string, string> = {
9
+ size: 'Size',
10
+ spacing: 'Spacing',
11
+ typography: 'Typography',
12
+ border: 'Border',
13
+ background: 'Background',
14
+ layout: 'Layout',
15
+ position: 'Position',
16
+ appearance: 'Appearance',
17
+ shadow: 'Shadow',
18
+ 'flex-item': 'Flex Item',
19
+ transform: 'Transform',
20
+ filter: 'Filter',
21
+ }
22
+
23
+ // ─── Changes Summary Modal ────────────────────────────────────────
24
+
25
+ function ChangesSummaryModal({ onClose }: { onClose: () => void }) {
26
+ const selectorPath = useEditorStore((s) => s.selectorPath)
27
+ const styleChanges = useEditorStore((s) => s.styleChanges)
28
+ const computedStyles = useEditorStore((s) => s.computedStyles)
29
+ const modalRef = useRef<HTMLDivElement>(null)
30
+ const [copied, setCopied] = useState(false)
31
+
32
+ // Derive changed entries from styleChanges but read the current value
33
+ // from computedStyles (the CSS tab source of truth).
34
+ const changes = useMemo(
35
+ () =>
36
+ styleChanges
37
+ .filter((c) => c.elementSelector === selectorPath)
38
+ .map((c) => ({
39
+ ...c,
40
+ displayValue: computedStyles[c.property] ?? c.newValue,
41
+ })),
42
+ [styleChanges, selectorPath, computedStyles],
43
+ )
44
+
45
+ // Close on click outside
46
+ useEffect(() => {
47
+ function handleClick(e: MouseEvent) {
48
+ if (modalRef.current && !modalRef.current.contains(e.target as Node)) {
49
+ onClose()
50
+ }
51
+ }
52
+ document.addEventListener('mousedown', handleClick)
53
+ return () => document.removeEventListener('mousedown', handleClick)
54
+ }, [onClose])
55
+
56
+ // Close on Escape
57
+ useEffect(() => {
58
+ function handleKey(e: KeyboardEvent) {
59
+ if (e.key === 'Escape') onClose()
60
+ }
61
+ document.addEventListener('keydown', handleKey)
62
+ return () => document.removeEventListener('keydown', handleKey)
63
+ }, [onClose])
64
+
65
+ const cssText = useMemo(() => {
66
+ return changes
67
+ .map((c) => `${camelToKebab(c.property)}: ${c.displayValue};`)
68
+ .join('\n')
69
+ }, [changes])
70
+
71
+ const handleCopy = useCallback(() => {
72
+ navigator.clipboard.writeText(cssText)
73
+ setCopied(true)
74
+ setTimeout(() => setCopied(false), 1500)
75
+ }, [cssText])
76
+
77
+ return (
78
+ <div
79
+ className="fixed inset-0 z-50 flex items-center justify-center"
80
+ style={{ background: 'rgba(0,0,0,0.5)' }}
81
+ >
82
+ <div
83
+ ref={modalRef}
84
+ className="w-[320px] max-h-[400px] flex flex-col rounded-lg overflow-hidden"
85
+ style={{
86
+ background: 'var(--bg-secondary, #252526)',
87
+ border: '1px solid var(--border)',
88
+ boxShadow: '0 8px 32px rgba(0,0,0,0.5)',
89
+ }}
90
+ >
91
+ {/* Header */}
92
+ <div
93
+ className="flex items-center justify-between px-3 py-2 shrink-0"
94
+ style={{ borderBottom: '1px solid var(--border)' }}
95
+ >
96
+ <span
97
+ className="text-[12px] font-medium"
98
+ style={{ color: 'var(--text-primary)' }}
99
+ >
100
+ Changed Properties
101
+ <span className="ml-1.5 opacity-50">({changes.length})</span>
102
+ </span>
103
+ <button
104
+ type="button"
105
+ onClick={onClose}
106
+ className="flex items-center justify-center w-5 h-5 rounded hover:opacity-80"
107
+ style={{ color: 'var(--text-muted)' }}
108
+ >
109
+ <svg width={10} height={10} viewBox="0 0 10 10" fill="none">
110
+ <path
111
+ d="M1 1l8 8M9 1l-8 8"
112
+ stroke="currentColor"
113
+ strokeWidth={1.5}
114
+ strokeLinecap="round"
115
+ />
116
+ </svg>
117
+ </button>
118
+ </div>
119
+
120
+ {/* Body */}
121
+ <div className="flex-1 overflow-y-auto px-3 py-2 space-y-1">
122
+ {changes.length === 0 ? (
123
+ <div
124
+ className="py-4 text-center text-[11px]"
125
+ style={{ color: 'var(--text-muted)' }}
126
+ >
127
+ No changes on this element
128
+ </div>
129
+ ) : (
130
+ changes.map((c) => (
131
+ <div key={c.id} className="text-[11px] font-mono leading-relaxed">
132
+ <span style={{ color: 'var(--accent)' }}>
133
+ {camelToKebab(c.property)}
134
+ </span>
135
+ <span style={{ color: 'var(--text-muted)' }}>: </span>
136
+ <span style={{ color: 'var(--text-primary)' }}>
137
+ {c.displayValue}
138
+ </span>
139
+ <span style={{ color: 'var(--text-muted)' }}>;</span>
140
+ {/* Original value hint */}
141
+ <span
142
+ className="ml-2 text-[10px]"
143
+ style={{ color: 'var(--text-muted)', opacity: 0.6 }}
144
+ >
145
+ was {c.originalValue}
146
+ </span>
147
+ </div>
148
+ ))
149
+ )}
150
+ </div>
151
+
152
+ {/* Footer */}
153
+ {changes.length > 0 && (
154
+ <div
155
+ className="flex items-center justify-end px-3 py-2 shrink-0"
156
+ style={{ borderTop: '1px solid var(--border)' }}
157
+ >
158
+ <button
159
+ onClick={handleCopy}
160
+ className="h-6 px-3 rounded text-[11px] hover:opacity-80 transition-opacity"
161
+ style={{
162
+ background: copied
163
+ ? 'rgba(78,201,176,0.15)'
164
+ : 'rgba(74,158,255,0.12)',
165
+ color: copied ? 'var(--success, #4ec9b0)' : 'var(--accent)',
166
+ border: 'none',
167
+ cursor: 'pointer',
168
+ }}
169
+ >
170
+ {copied ? 'Copied!' : 'Copy Changes'}
171
+ </button>
172
+ </div>
173
+ )}
174
+ </div>
175
+ </div>
176
+ )
177
+ }
178
+
179
+ // ─── CSS Raw View ─────────────────────────────────────────────────
180
+
181
+ export function CSSRawView() {
182
+ const computedStyles = useEditorStore((state) => state.computedStyles)
183
+ const selectorPath = useEditorStore((s) => s.selectorPath)
184
+ const styleChanges = useEditorStore((s) => s.styleChanges)
185
+ const [search, setSearch] = useState('')
186
+ const [collapsed, setCollapsed] = useState<Record<string, boolean>>({})
187
+ const [showModal, setShowModal] = useState(false)
188
+ const [copyToast, setCopyToast] = useState(false)
189
+
190
+ // Set of camelCase property names changed on this element
191
+ const changedProps = useMemo(() => {
192
+ const set = new Set<string>()
193
+ for (const c of styleChanges) {
194
+ if (c.elementSelector === selectorPath) set.add(c.property)
195
+ }
196
+ return set
197
+ }, [styleChanges, selectorPath])
198
+
199
+ const groups = useMemo(() => {
200
+ const result: {
201
+ name: string
202
+ label: string
203
+ properties: { name: string; value: string; changed: boolean }[]
204
+ }[] = []
205
+
206
+ for (const [groupName, props] of Object.entries(CSS_PROPERTIES)) {
207
+ const properties: { name: string; value: string; changed: boolean }[] = []
208
+ for (const prop of props) {
209
+ // Convert kebab-case CSS prop to camelCase for lookup
210
+ const camelProp = prop.replace(/-([a-z])/g, (_, c) => c.toUpperCase())
211
+ const value = computedStyles[camelProp]
212
+ if (value !== undefined && value !== '') {
213
+ const kebabName = camelToKebab(camelProp)
214
+ if (!search || kebabName.includes(search.toLowerCase())) {
215
+ properties.push({
216
+ name: kebabName,
217
+ value,
218
+ changed: changedProps.has(camelProp),
219
+ })
220
+ }
221
+ }
222
+ }
223
+ if (properties.length > 0) {
224
+ result.push({
225
+ name: groupName,
226
+ label: GROUP_LABELS[groupName] || groupName,
227
+ properties,
228
+ })
229
+ }
230
+ }
231
+ return result
232
+ }, [computedStyles, search, changedProps])
233
+
234
+ const handleCopyAll = useCallback(() => {
235
+ const lines: string[] = []
236
+ for (const group of groups) {
237
+ lines.push(`/* ${group.label} */`)
238
+ for (const prop of group.properties) {
239
+ lines.push(`${prop.name}: ${prop.value};`)
240
+ }
241
+ lines.push('')
242
+ }
243
+ navigator.clipboard.writeText(lines.join('\n'))
244
+ setCopyToast(true)
245
+ setTimeout(() => setCopyToast(false), 1500)
246
+ }, [groups])
247
+
248
+ const toggleGroup = (name: string) => {
249
+ setCollapsed((prev) => ({ ...prev, [name]: !prev[name] }))
250
+ }
251
+
252
+ return (
253
+ <div style={{ background: 'var(--bg-secondary)' }}>
254
+ {/* Search + Copy */}
255
+ <div
256
+ className="flex items-center gap-1.5 px-3 py-1.5"
257
+ style={{ borderBottom: '1px solid var(--border)' }}
258
+ >
259
+ <input
260
+ type="text"
261
+ value={search}
262
+ onChange={(e) => setSearch(e.target.value)}
263
+ placeholder="Search properties..."
264
+ className="flex-1 h-6 px-2 rounded text-[11px] outline-none"
265
+ style={{
266
+ background: 'var(--bg-tertiary)',
267
+ border: '1px solid var(--border)',
268
+ color: 'var(--text-primary)',
269
+ }}
270
+ />
271
+ {changedProps.size > 0 && (
272
+ <button
273
+ onClick={() => setShowModal(true)}
274
+ className="h-6 px-2 rounded text-[11px] hover:opacity-80 transition-opacity whitespace-nowrap shrink-0"
275
+ style={{
276
+ background: 'rgba(74,158,255,0.12)',
277
+ border: '1px solid rgba(74,158,255,0.25)',
278
+ color: 'var(--accent)',
279
+ }}
280
+ >
281
+ Changes ({changedProps.size})
282
+ </button>
283
+ )}
284
+ <button
285
+ onClick={handleCopyAll}
286
+ className="h-6 px-2 rounded text-[11px] hover:opacity-80 transition-all shrink-0"
287
+ style={{
288
+ background: copyToast
289
+ ? 'rgba(78,201,176,0.15)'
290
+ : 'var(--bg-tertiary)',
291
+ border: copyToast
292
+ ? '1px solid rgba(78,201,176,0.3)'
293
+ : '1px solid var(--border)',
294
+ color: copyToast
295
+ ? 'var(--success, #4ec9b0)'
296
+ : 'var(--text-secondary)',
297
+ }}
298
+ >
299
+ {copyToast ? 'Copied!' : 'Copy'}
300
+ </button>
301
+ </div>
302
+
303
+ {showModal && <ChangesSummaryModal onClose={() => setShowModal(false)} />}
304
+
305
+ {/* Property groups */}
306
+ <div
307
+ className="overflow-y-auto"
308
+ style={{ maxHeight: 'calc(100vh - 300px)' }}
309
+ >
310
+ {groups.map((group) => {
311
+ const groupChangedCount = group.properties.filter(
312
+ (p) => p.changed,
313
+ ).length
314
+ const hasChanges = groupChangedCount > 0
315
+ return (
316
+ <div
317
+ key={group.name}
318
+ style={{ borderBottom: '1px solid var(--border)' }}
319
+ >
320
+ <button
321
+ onClick={() => toggleGroup(group.name)}
322
+ className="flex items-center w-full px-3 py-1.5 text-[10px] font-medium hover:bg-[var(--bg-hover)] transition-colors"
323
+ style={{
324
+ color: hasChanges
325
+ ? 'var(--warning, #dcdcaa)'
326
+ : 'var(--text-muted)',
327
+ }}
328
+ >
329
+ <span
330
+ className="mr-1.5 text-[8px] transition-transform"
331
+ style={{
332
+ transform: collapsed[group.name]
333
+ ? 'rotate(-90deg)'
334
+ : 'rotate(0deg)',
335
+ }}
336
+ >
337
+
338
+ </span>
339
+ {group.label}
340
+ <span className="ml-1 opacity-50">
341
+ ({group.properties.length})
342
+ </span>
343
+ {hasChanges && (
344
+ <span
345
+ className="ml-1.5 px-1 rounded text-[9px]"
346
+ style={{
347
+ background: 'rgba(220,220,170,0.12)',
348
+ color: 'var(--warning, #dcdcaa)',
349
+ }}
350
+ >
351
+ {groupChangedCount}
352
+ </span>
353
+ )}
354
+ </button>
355
+ {!collapsed[group.name] && (
356
+ <div className="px-3 pb-2 space-y-0.5">
357
+ {group.properties.map((prop) => (
358
+ <div
359
+ key={prop.name}
360
+ className="flex text-[11px] font-mono leading-tight"
361
+ style={
362
+ prop.changed
363
+ ? {
364
+ background: 'rgba(74,158,255,0.06)',
365
+ borderRadius: 2,
366
+ padding: '1px 3px',
367
+ margin: '0 -3px',
368
+ }
369
+ : undefined
370
+ }
371
+ >
372
+ <span
373
+ style={{
374
+ color: prop.changed
375
+ ? 'var(--warning, #dcdcaa)'
376
+ : 'var(--accent)',
377
+ }}
378
+ >
379
+ {prop.name}
380
+ </span>
381
+ <span style={{ color: 'var(--text-muted)' }}>
382
+ :&nbsp;
383
+ </span>
384
+ <span
385
+ style={{
386
+ color: prop.changed
387
+ ? 'var(--success, #4ec9b0)'
388
+ : 'var(--text-primary)',
389
+ }}
390
+ >
391
+ {prop.value}
392
+ </span>
393
+ <span style={{ color: 'var(--text-muted)' }}>;</span>
394
+ </div>
395
+ ))}
396
+ </div>
397
+ )}
398
+ </div>
399
+ )
400
+ })}
401
+ {groups.length === 0 && (
402
+ <div
403
+ className="px-3 py-4 text-xs text-center"
404
+ style={{ color: 'var(--text-muted)' }}
405
+ >
406
+ {search ? 'No matching properties' : 'No computed styles available'}
407
+ </div>
408
+ )}
409
+ </div>
410
+ </div>
411
+ )
412
+ }
@@ -0,0 +1,51 @@
1
+ 'use client'
2
+
3
+ interface DesignCSSTabToggleProps {
4
+ activeTab: 'design' | 'css'
5
+ onTabChange: (tab: 'design' | 'css') => void
6
+ }
7
+
8
+ export function DesignCSSTabToggle({
9
+ activeTab,
10
+ onTabChange,
11
+ }: DesignCSSTabToggleProps) {
12
+ return (
13
+ <div
14
+ className="flex items-center justify-center px-3 py-1.5"
15
+ style={{ borderBottom: '1px solid var(--border)' }}
16
+ >
17
+ <div
18
+ className="flex items-center gap-0.5 rounded p-0.5"
19
+ style={{ background: 'var(--bg-tertiary)' }}
20
+ >
21
+ <button
22
+ onClick={() => onTabChange('design')}
23
+ className="px-3 py-0.5 text-[11px] rounded transition-colors"
24
+ style={{
25
+ background:
26
+ activeTab === 'design'
27
+ ? 'var(--accent-bg, rgba(74,158,255,0.15))'
28
+ : 'transparent',
29
+ color:
30
+ activeTab === 'design' ? 'var(--accent)' : 'var(--text-muted)',
31
+ }}
32
+ >
33
+ Design
34
+ </button>
35
+ <button
36
+ onClick={() => onTabChange('css')}
37
+ className="px-3 py-0.5 text-[11px] rounded transition-colors"
38
+ style={{
39
+ background:
40
+ activeTab === 'css'
41
+ ? 'var(--accent-bg, rgba(74,158,255,0.15))'
42
+ : 'transparent',
43
+ color: activeTab === 'css' ? 'var(--accent)' : 'var(--text-muted)',
44
+ }}
45
+ >
46
+ CSS
47
+ </button>
48
+ </div>
49
+ </div>
50
+ )
51
+ }