@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.
- package/LICENSE +178 -0
- package/NOTICE +4 -0
- package/README.md +180 -0
- package/bin/paint.js +266 -0
- package/next-env.d.ts +6 -0
- package/next.config.ts +19 -0
- package/package.json +81 -0
- package/postcss.config.mjs +8 -0
- package/public/dev-editor-inspector.js +1872 -0
- package/src/app/api/claude/analyze/route.ts +319 -0
- package/src/app/api/claude/apply/route.ts +185 -0
- package/src/app/api/claude/pick-folder/route.ts +64 -0
- package/src/app/api/claude/scan/route.ts +221 -0
- package/src/app/api/claude/status/route.ts +55 -0
- package/src/app/api/project/scan/route.ts +634 -0
- package/src/app/api/project-scan/css-variables/route.ts +238 -0
- package/src/app/api/project-scan/route.ts +40 -0
- package/src/app/api/project-scan/tailwind-config/route.ts +172 -0
- package/src/app/api/proxy/[[...path]]/route.ts +2400 -0
- package/src/app/docs/DocsClient.tsx +322 -0
- package/src/app/docs/layout.tsx +7 -0
- package/src/app/docs/page.tsx +855 -0
- package/src/app/globals.css +176 -0
- package/src/app/layout.tsx +19 -0
- package/src/app/page.tsx +46 -0
- package/src/bridge/api-handlers.ts +885 -0
- package/src/bridge/proxy-handler.ts +329 -0
- package/src/bridge/server.ts +113 -0
- package/src/components/BreakpointTabs.tsx +72 -0
- package/src/components/ChangeSummaryModal.tsx +267 -0
- package/src/components/ConnectModal.tsx +994 -0
- package/src/components/Editor.tsx +90 -0
- package/src/components/PageSelector.tsx +208 -0
- package/src/components/PreviewFrame.tsx +299 -0
- package/src/components/ProjectFolderBanner.tsx +91 -0
- package/src/components/ResponsiveToolbar.tsx +222 -0
- package/src/components/TargetSelector.tsx +243 -0
- package/src/components/TopBar.tsx +315 -0
- package/src/components/common/CollapsibleSection.tsx +36 -0
- package/src/components/common/ColorPicker.tsx +920 -0
- package/src/components/common/EditablePre.tsx +136 -0
- package/src/components/common/ErrorBoundary.tsx +65 -0
- package/src/components/common/ResizablePanel.tsx +83 -0
- package/src/components/common/ScanAnimation.tsx +76 -0
- package/src/components/common/ToastContainer.tsx +97 -0
- package/src/components/common/UnitInput.tsx +77 -0
- package/src/components/common/VariableColorPicker.tsx +622 -0
- package/src/components/left-panel/AddElementPanel.tsx +237 -0
- package/src/components/left-panel/ComponentsPanel.tsx +609 -0
- package/src/components/left-panel/IconSidebar.tsx +99 -0
- package/src/components/left-panel/LayerNode.tsx +874 -0
- package/src/components/left-panel/LayerSearch.tsx +23 -0
- package/src/components/left-panel/LayersPanel.tsx +52 -0
- package/src/components/left-panel/LeftPanel.tsx +122 -0
- package/src/components/left-panel/PagesPanel.tsx +114 -0
- package/src/components/left-panel/icons.tsx +162 -0
- package/src/components/left-panel/terminal/ScanOverlay.tsx +66 -0
- package/src/components/left-panel/terminal/TerminalPanel.tsx +217 -0
- package/src/components/right-panel/ElementLogBox.tsx +248 -0
- package/src/components/right-panel/PanelTabs.tsx +83 -0
- package/src/components/right-panel/RightPanel.tsx +41 -0
- package/src/components/right-panel/changes/AiScanResultPanel.tsx +285 -0
- package/src/components/right-panel/changes/ChangeEntry.tsx +59 -0
- package/src/components/right-panel/changes/ChangelogActions.tsx +105 -0
- package/src/components/right-panel/changes/ChangesPanel.tsx +1474 -0
- package/src/components/right-panel/claude/ApplyConfirmModal.tsx +376 -0
- package/src/components/right-panel/claude/ClaudeErrorState.tsx +125 -0
- package/src/components/right-panel/claude/ClaudeIntegrationPanel.tsx +482 -0
- package/src/components/right-panel/claude/ClaudeProgressIndicator.tsx +76 -0
- package/src/components/right-panel/claude/DiffCard.tsx +130 -0
- package/src/components/right-panel/claude/DiffViewer.tsx +54 -0
- package/src/components/right-panel/claude/ProjectRootSelector.tsx +275 -0
- package/src/components/right-panel/claude/ResultsSummary.tsx +119 -0
- package/src/components/right-panel/claude/SetupFlow.tsx +315 -0
- package/src/components/right-panel/console/ConsolePanel.tsx +209 -0
- package/src/components/right-panel/design/AppearanceSection.tsx +703 -0
- package/src/components/right-panel/design/BackgroundSection.tsx +516 -0
- package/src/components/right-panel/design/BorderSection.tsx +161 -0
- package/src/components/right-panel/design/CSSRawView.tsx +412 -0
- package/src/components/right-panel/design/DesignCSSTabToggle.tsx +51 -0
- package/src/components/right-panel/design/DesignPanel.tsx +275 -0
- package/src/components/right-panel/design/ElementBreadcrumb.tsx +51 -0
- package/src/components/right-panel/design/GradientEditor.tsx +726 -0
- package/src/components/right-panel/design/LayoutSection.tsx +1948 -0
- package/src/components/right-panel/design/PositionSection.tsx +865 -0
- package/src/components/right-panel/design/PropertiesSection.tsx +86 -0
- package/src/components/right-panel/design/SVGSection.tsx +361 -0
- package/src/components/right-panel/design/ShadowBlurSection.tsx +227 -0
- package/src/components/right-panel/design/SizeSection.tsx +183 -0
- package/src/components/right-panel/design/TextSection.tsx +719 -0
- package/src/components/right-panel/design/icons.tsx +948 -0
- package/src/components/right-panel/design/inputs/BoxModelPreview.tsx +467 -0
- package/src/components/right-panel/design/inputs/ColorInput.tsx +43 -0
- package/src/components/right-panel/design/inputs/CompactInput.tsx +333 -0
- package/src/components/right-panel/design/inputs/DraggableLabel.tsx +118 -0
- package/src/components/right-panel/design/inputs/IconToggleGroup.tsx +54 -0
- package/src/components/right-panel/design/inputs/LinkedInputPair.tsx +174 -0
- package/src/components/right-panel/design/inputs/SectionHeader.tsx +79 -0
- package/src/components/right-panel/variables/VariablesPanel.tsx +388 -0
- package/src/hooks/useBridge.ts +95 -0
- package/src/hooks/useChangeTracker.ts +563 -0
- package/src/hooks/useClaudeAPI.ts +118 -0
- package/src/hooks/useDOMTree.ts +25 -0
- package/src/hooks/useKeyboardShortcuts.ts +76 -0
- package/src/hooks/usePostMessage.ts +589 -0
- package/src/hooks/useProjectScan.ts +204 -0
- package/src/hooks/useResizable.ts +20 -0
- package/src/hooks/useSelectedElement.ts +51 -0
- package/src/hooks/useTargetUrl.ts +81 -0
- package/src/inspector/DOMTraverser.ts +71 -0
- package/src/inspector/ElementSelector.ts +23 -0
- package/src/inspector/HoverHighlighter.ts +54 -0
- package/src/inspector/SelectionHighlighter.ts +27 -0
- package/src/inspector/StyleExtractor.ts +19 -0
- package/src/inspector/inspector.ts +17 -0
- package/src/inspector/messaging.ts +30 -0
- package/src/lib/apiBase.ts +15 -0
- package/src/lib/classifyElement.ts +430 -0
- package/src/lib/claude-bin.ts +197 -0
- package/src/lib/claude-stream.ts +158 -0
- package/src/lib/clientProjectScanner.ts +344 -0
- package/src/lib/componentMatcher.ts +156 -0
- package/src/lib/constants.ts +573 -0
- package/src/lib/cssVariableUtils.ts +409 -0
- package/src/lib/diffParser.ts +206 -0
- package/src/lib/folderPicker.ts +84 -0
- package/src/lib/gradientParser.ts +160 -0
- package/src/lib/projectScanner.ts +355 -0
- package/src/lib/promptBuilder.ts +402 -0
- package/src/lib/shadowParser.ts +124 -0
- package/src/lib/tailwindClassParser.ts +248 -0
- package/src/lib/textShadowUtils.ts +106 -0
- package/src/lib/utils.ts +299 -0
- package/src/lib/validatePath.ts +40 -0
- package/src/proxy.ts +92 -0
- package/src/server/terminal-server.ts +104 -0
- package/src/store/changeSlice.ts +288 -0
- package/src/store/claudeSlice.ts +222 -0
- package/src/store/componentSlice.ts +90 -0
- package/src/store/consoleSlice.ts +51 -0
- package/src/store/cssVariableSlice.ts +94 -0
- package/src/store/elementSlice.ts +78 -0
- package/src/store/index.ts +35 -0
- package/src/store/terminalSlice.ts +30 -0
- package/src/store/treeSlice.ts +69 -0
- package/src/store/uiSlice.ts +327 -0
- package/src/types/changelog.ts +49 -0
- package/src/types/claude.ts +131 -0
- package/src/types/component.ts +49 -0
- package/src/types/cssVariables.ts +18 -0
- package/src/types/element.ts +21 -0
- package/src/types/file-system-access.d.ts +27 -0
- package/src/types/gradient.ts +12 -0
- package/src/types/messages.ts +392 -0
- package/src/types/shadow.ts +8 -0
- package/src/types/tree.ts +9 -0
- package/tsconfig.json +42 -0
- package/tsconfig.server.json +12 -0
|
@@ -0,0 +1,467 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useState, useRef, useCallback } from 'react'
|
|
4
|
+
import { parseCSSValue, formatCSSValue } from '@/lib/utils'
|
|
5
|
+
import { useEditorStore } from '@/store'
|
|
6
|
+
|
|
7
|
+
interface BoxModelPreviewProps {
|
|
8
|
+
margin: { top: string; right: string; bottom: string; left: string }
|
|
9
|
+
border: { top: string; right: string; bottom: string; left: string }
|
|
10
|
+
padding: { top: string; right: string; bottom: string; left: string }
|
|
11
|
+
width: string
|
|
12
|
+
height: string
|
|
13
|
+
onChange: (property: string, value: string) => void
|
|
14
|
+
onReset?: (property: string) => void
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// All box-model properties for "Reset All"
|
|
18
|
+
const BOX_MODEL_PROPERTIES = [
|
|
19
|
+
'marginTop',
|
|
20
|
+
'marginRight',
|
|
21
|
+
'marginBottom',
|
|
22
|
+
'marginLeft',
|
|
23
|
+
'borderTopWidth',
|
|
24
|
+
'borderRightWidth',
|
|
25
|
+
'borderBottomWidth',
|
|
26
|
+
'borderLeftWidth',
|
|
27
|
+
'paddingTop',
|
|
28
|
+
'paddingRight',
|
|
29
|
+
'paddingBottom',
|
|
30
|
+
'paddingLeft',
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
// Editable value label with drag-to-scrub + double-click to edit
|
|
34
|
+
function ScrubValue({
|
|
35
|
+
value,
|
|
36
|
+
property,
|
|
37
|
+
color,
|
|
38
|
+
onChange,
|
|
39
|
+
}: {
|
|
40
|
+
value: string
|
|
41
|
+
property: string
|
|
42
|
+
color: string
|
|
43
|
+
onChange: (property: string, value: string) => void
|
|
44
|
+
onReset?: (property: string) => void
|
|
45
|
+
}) {
|
|
46
|
+
const spanRef = useRef<HTMLSpanElement>(null)
|
|
47
|
+
const inputRef = useRef<HTMLInputElement>(null)
|
|
48
|
+
const isDragging = useRef(false)
|
|
49
|
+
const hasDragged = useRef(false)
|
|
50
|
+
const dragStartX = useRef(0)
|
|
51
|
+
const dragStartVal = useRef(0)
|
|
52
|
+
const [isEditing, setIsEditing] = useState(false)
|
|
53
|
+
const [editValue, setEditValue] = useState('')
|
|
54
|
+
|
|
55
|
+
const parsed = parseCSSValue(value)
|
|
56
|
+
const num = parsed.number
|
|
57
|
+
const unit = parsed.unit || 'px'
|
|
58
|
+
|
|
59
|
+
const hasChange = useEditorStore((s) => {
|
|
60
|
+
const sp = s.selectorPath
|
|
61
|
+
return sp
|
|
62
|
+
? s.styleChanges.some(
|
|
63
|
+
(c) => c.elementSelector === sp && c.property === property,
|
|
64
|
+
)
|
|
65
|
+
: false
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
const handlePointerDown = useCallback(
|
|
69
|
+
(e: React.PointerEvent) => {
|
|
70
|
+
if (isEditing) return
|
|
71
|
+
e.preventDefault()
|
|
72
|
+
isDragging.current = true
|
|
73
|
+
hasDragged.current = false
|
|
74
|
+
dragStartX.current = e.clientX
|
|
75
|
+
dragStartVal.current = num
|
|
76
|
+
spanRef.current?.setPointerCapture(e.pointerId)
|
|
77
|
+
document.body.style.cursor = 'ew-resize'
|
|
78
|
+
document.body.style.userSelect = 'none'
|
|
79
|
+
},
|
|
80
|
+
[num, isEditing],
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
const handlePointerMove = useCallback(
|
|
84
|
+
(e: React.PointerEvent) => {
|
|
85
|
+
if (!isDragging.current) return
|
|
86
|
+
hasDragged.current = true
|
|
87
|
+
const delta = e.clientX - dragStartX.current
|
|
88
|
+
// Base: 2 per pixel. Shift = 10x, Alt/Option = 0.1x
|
|
89
|
+
const multiplier = e.shiftKey ? 10 : e.altKey ? 0.1 : 1
|
|
90
|
+
const next = Math.max(
|
|
91
|
+
0,
|
|
92
|
+
Math.round((dragStartVal.current + delta * 2 * multiplier) * 100) / 100,
|
|
93
|
+
)
|
|
94
|
+
onChange(property, formatCSSValue(next, unit))
|
|
95
|
+
},
|
|
96
|
+
[onChange, property, unit],
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
const handlePointerUp = useCallback((e: React.PointerEvent) => {
|
|
100
|
+
if (!isDragging.current) return
|
|
101
|
+
isDragging.current = false
|
|
102
|
+
spanRef.current?.releasePointerCapture(e.pointerId)
|
|
103
|
+
document.body.style.cursor = ''
|
|
104
|
+
document.body.style.userSelect = ''
|
|
105
|
+
}, [])
|
|
106
|
+
|
|
107
|
+
// Double-click enters inline edit mode
|
|
108
|
+
const handleDoubleClick = useCallback(() => {
|
|
109
|
+
setEditValue(String(num))
|
|
110
|
+
setIsEditing(true)
|
|
111
|
+
// Focus the input after React renders it
|
|
112
|
+
requestAnimationFrame(() => {
|
|
113
|
+
inputRef.current?.focus()
|
|
114
|
+
inputRef.current?.select()
|
|
115
|
+
})
|
|
116
|
+
}, [num])
|
|
117
|
+
|
|
118
|
+
const commitEdit = useCallback(() => {
|
|
119
|
+
setIsEditing(false)
|
|
120
|
+
const n = parseFloat(editValue)
|
|
121
|
+
if (!isNaN(n)) {
|
|
122
|
+
onChange(property, formatCSSValue(Math.max(0, n), unit))
|
|
123
|
+
}
|
|
124
|
+
}, [editValue, onChange, property, unit])
|
|
125
|
+
|
|
126
|
+
const handleEditKeyDown = useCallback(
|
|
127
|
+
(e: React.KeyboardEvent) => {
|
|
128
|
+
if (e.key === 'Enter') {
|
|
129
|
+
commitEdit()
|
|
130
|
+
} else if (e.key === 'Escape') {
|
|
131
|
+
setIsEditing(false)
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
[commitEdit],
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
if (isEditing) {
|
|
138
|
+
return (
|
|
139
|
+
<input
|
|
140
|
+
ref={inputRef}
|
|
141
|
+
type="text"
|
|
142
|
+
inputMode="numeric"
|
|
143
|
+
value={editValue}
|
|
144
|
+
onChange={(e) => setEditValue(e.target.value)}
|
|
145
|
+
onBlur={commitEdit}
|
|
146
|
+
onKeyDown={handleEditKeyDown}
|
|
147
|
+
className="text-[10px] leading-none tabular-nums bg-transparent border-none outline-none text-center"
|
|
148
|
+
style={{
|
|
149
|
+
color,
|
|
150
|
+
width: '30px',
|
|
151
|
+
padding: '1px 2px',
|
|
152
|
+
borderRadius: '2px',
|
|
153
|
+
background: 'rgba(255,255,255,0.08)',
|
|
154
|
+
}}
|
|
155
|
+
/>
|
|
156
|
+
)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return (
|
|
160
|
+
<span
|
|
161
|
+
ref={spanRef}
|
|
162
|
+
onPointerDown={handlePointerDown}
|
|
163
|
+
onPointerMove={handlePointerMove}
|
|
164
|
+
onPointerUp={handlePointerUp}
|
|
165
|
+
onDoubleClick={handleDoubleClick}
|
|
166
|
+
className="text-[10px] leading-none select-none tabular-nums"
|
|
167
|
+
style={{
|
|
168
|
+
color: hasChange ? color : `${color}99`,
|
|
169
|
+
cursor: 'ew-resize',
|
|
170
|
+
minWidth: '14px',
|
|
171
|
+
textAlign: 'center',
|
|
172
|
+
fontWeight: hasChange ? 600 : 400,
|
|
173
|
+
}}
|
|
174
|
+
title="Drag to scrub, double-click to edit"
|
|
175
|
+
>
|
|
176
|
+
{num}
|
|
177
|
+
</span>
|
|
178
|
+
)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export function BoxModelPreview({
|
|
182
|
+
margin,
|
|
183
|
+
border,
|
|
184
|
+
padding,
|
|
185
|
+
width,
|
|
186
|
+
height,
|
|
187
|
+
onChange,
|
|
188
|
+
onReset,
|
|
189
|
+
}: BoxModelPreviewProps) {
|
|
190
|
+
const contentW = parseCSSValue(width).number
|
|
191
|
+
const contentH = parseCSSValue(height).number
|
|
192
|
+
|
|
193
|
+
// Check if any box-model property has a tracked change
|
|
194
|
+
const hasAnyChange = useEditorStore((s) => {
|
|
195
|
+
const sp = s.selectorPath
|
|
196
|
+
if (!sp) return false
|
|
197
|
+
return s.styleChanges.some(
|
|
198
|
+
(c) =>
|
|
199
|
+
c.elementSelector === sp && BOX_MODEL_PROPERTIES.includes(c.property),
|
|
200
|
+
)
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
const handleResetAll = useCallback(() => {
|
|
204
|
+
if (!onReset) return
|
|
205
|
+
for (const prop of BOX_MODEL_PROPERTIES) {
|
|
206
|
+
onReset(prop)
|
|
207
|
+
}
|
|
208
|
+
}, [onReset])
|
|
209
|
+
|
|
210
|
+
// Dim-display helper for the content dimension text
|
|
211
|
+
const dimText =
|
|
212
|
+
(contentW ? `${contentW}` : '–') + ' × ' + (contentH ? `${contentH}` : '–')
|
|
213
|
+
|
|
214
|
+
return (
|
|
215
|
+
<div className="pt-1.5" style={{ borderTop: '1px solid var(--border)' }}>
|
|
216
|
+
<div className="flex items-center justify-between mb-1">
|
|
217
|
+
<span
|
|
218
|
+
className="text-[10px] font-medium"
|
|
219
|
+
style={{ color: 'var(--text-muted)' }}
|
|
220
|
+
>
|
|
221
|
+
Box Model
|
|
222
|
+
</span>
|
|
223
|
+
{hasAnyChange && (
|
|
224
|
+
<button
|
|
225
|
+
type="button"
|
|
226
|
+
onClick={handleResetAll}
|
|
227
|
+
className="text-[9px] px-1.5 py-0.5 rounded hover:opacity-80"
|
|
228
|
+
style={{
|
|
229
|
+
color: 'var(--accent)',
|
|
230
|
+
background: 'rgba(74, 158, 255, 0.10)',
|
|
231
|
+
border: 'none',
|
|
232
|
+
cursor: 'pointer',
|
|
233
|
+
}}
|
|
234
|
+
>
|
|
235
|
+
Reset
|
|
236
|
+
</button>
|
|
237
|
+
)}
|
|
238
|
+
</div>
|
|
239
|
+
|
|
240
|
+
{/* Margin layer */}
|
|
241
|
+
<div
|
|
242
|
+
className="relative flex flex-col items-center"
|
|
243
|
+
style={{
|
|
244
|
+
background: 'rgba(251, 191, 36, 0.06)',
|
|
245
|
+
border: '1px solid rgba(251, 191, 36, 0.20)',
|
|
246
|
+
borderRadius: '4px',
|
|
247
|
+
padding: '6px 8px',
|
|
248
|
+
}}
|
|
249
|
+
>
|
|
250
|
+
{/* Margin label */}
|
|
251
|
+
<span
|
|
252
|
+
className="absolute top-0.5 left-1.5 text-[8px] uppercase tracking-wider"
|
|
253
|
+
style={{ color: 'rgba(251, 191, 36, 0.50)' }}
|
|
254
|
+
>
|
|
255
|
+
margin
|
|
256
|
+
</span>
|
|
257
|
+
|
|
258
|
+
{/* Margin top */}
|
|
259
|
+
<div className="flex justify-center py-0.5">
|
|
260
|
+
<ScrubValue
|
|
261
|
+
value={margin.top}
|
|
262
|
+
property="marginTop"
|
|
263
|
+
color="#fbbf24"
|
|
264
|
+
onChange={onChange}
|
|
265
|
+
onReset={onReset}
|
|
266
|
+
/>
|
|
267
|
+
</div>
|
|
268
|
+
|
|
269
|
+
{/* Margin left + border layer + margin right */}
|
|
270
|
+
<div className="flex items-center w-full">
|
|
271
|
+
<div
|
|
272
|
+
className="flex justify-center px-1"
|
|
273
|
+
style={{ minWidth: '20px' }}
|
|
274
|
+
>
|
|
275
|
+
<ScrubValue
|
|
276
|
+
value={margin.left}
|
|
277
|
+
property="marginLeft"
|
|
278
|
+
color="#fbbf24"
|
|
279
|
+
onChange={onChange}
|
|
280
|
+
onReset={onReset}
|
|
281
|
+
/>
|
|
282
|
+
</div>
|
|
283
|
+
|
|
284
|
+
{/* Border layer */}
|
|
285
|
+
<div
|
|
286
|
+
className="relative flex-1 flex flex-col items-center"
|
|
287
|
+
style={{
|
|
288
|
+
background: 'rgba(163, 163, 163, 0.08)',
|
|
289
|
+
border: '1px solid rgba(163, 163, 163, 0.25)',
|
|
290
|
+
borderRadius: '3px',
|
|
291
|
+
padding: '5px 6px',
|
|
292
|
+
}}
|
|
293
|
+
>
|
|
294
|
+
<span
|
|
295
|
+
className="absolute top-0.5 left-1 text-[8px] uppercase tracking-wider"
|
|
296
|
+
style={{ color: 'rgba(163, 163, 163, 0.50)' }}
|
|
297
|
+
>
|
|
298
|
+
border
|
|
299
|
+
</span>
|
|
300
|
+
|
|
301
|
+
{/* Border top */}
|
|
302
|
+
<div className="flex justify-center py-0.5">
|
|
303
|
+
<ScrubValue
|
|
304
|
+
value={border.top}
|
|
305
|
+
property="borderTopWidth"
|
|
306
|
+
color="#a3a3a3"
|
|
307
|
+
onChange={onChange}
|
|
308
|
+
onReset={onReset}
|
|
309
|
+
/>
|
|
310
|
+
</div>
|
|
311
|
+
|
|
312
|
+
{/* Border left + padding layer + border right */}
|
|
313
|
+
<div className="flex items-center w-full">
|
|
314
|
+
<div
|
|
315
|
+
className="flex justify-center px-1"
|
|
316
|
+
style={{ minWidth: '16px' }}
|
|
317
|
+
>
|
|
318
|
+
<ScrubValue
|
|
319
|
+
value={border.left}
|
|
320
|
+
property="borderLeftWidth"
|
|
321
|
+
color="#a3a3a3"
|
|
322
|
+
onChange={onChange}
|
|
323
|
+
onReset={onReset}
|
|
324
|
+
/>
|
|
325
|
+
</div>
|
|
326
|
+
|
|
327
|
+
{/* Padding layer */}
|
|
328
|
+
<div
|
|
329
|
+
className="relative flex-1 flex flex-col items-center"
|
|
330
|
+
style={{
|
|
331
|
+
background: 'rgba(74, 222, 128, 0.06)',
|
|
332
|
+
border: '1px solid rgba(74, 222, 128, 0.20)',
|
|
333
|
+
borderRadius: '2px',
|
|
334
|
+
padding: '4px 4px',
|
|
335
|
+
}}
|
|
336
|
+
>
|
|
337
|
+
<span
|
|
338
|
+
className="absolute top-0 left-1 text-[8px] uppercase tracking-wider"
|
|
339
|
+
style={{ color: 'rgba(74, 222, 128, 0.50)' }}
|
|
340
|
+
>
|
|
341
|
+
padding
|
|
342
|
+
</span>
|
|
343
|
+
|
|
344
|
+
{/* Padding top */}
|
|
345
|
+
<div className="flex justify-center py-0.5">
|
|
346
|
+
<ScrubValue
|
|
347
|
+
value={padding.top}
|
|
348
|
+
property="paddingTop"
|
|
349
|
+
color="#4ade80"
|
|
350
|
+
onChange={onChange}
|
|
351
|
+
onReset={onReset}
|
|
352
|
+
/>
|
|
353
|
+
</div>
|
|
354
|
+
|
|
355
|
+
{/* Padding left + content + padding right */}
|
|
356
|
+
<div className="flex items-center w-full">
|
|
357
|
+
<div
|
|
358
|
+
className="flex justify-center px-0.5"
|
|
359
|
+
style={{ minWidth: '14px' }}
|
|
360
|
+
>
|
|
361
|
+
<ScrubValue
|
|
362
|
+
value={padding.left}
|
|
363
|
+
property="paddingLeft"
|
|
364
|
+
color="#4ade80"
|
|
365
|
+
onChange={onChange}
|
|
366
|
+
onReset={onReset}
|
|
367
|
+
/>
|
|
368
|
+
</div>
|
|
369
|
+
|
|
370
|
+
{/* Content box */}
|
|
371
|
+
<div
|
|
372
|
+
className="flex-1 flex items-center justify-center py-2"
|
|
373
|
+
style={{
|
|
374
|
+
background: 'rgba(74, 158, 255, 0.08)',
|
|
375
|
+
border: '1px solid rgba(74, 158, 255, 0.25)',
|
|
376
|
+
borderRadius: '2px',
|
|
377
|
+
minHeight: '24px',
|
|
378
|
+
}}
|
|
379
|
+
>
|
|
380
|
+
<span
|
|
381
|
+
className="text-[9px] tabular-nums whitespace-nowrap"
|
|
382
|
+
style={{ color: 'rgba(74, 158, 255, 0.7)' }}
|
|
383
|
+
>
|
|
384
|
+
{dimText}
|
|
385
|
+
</span>
|
|
386
|
+
</div>
|
|
387
|
+
|
|
388
|
+
<div
|
|
389
|
+
className="flex justify-center px-0.5"
|
|
390
|
+
style={{ minWidth: '14px' }}
|
|
391
|
+
>
|
|
392
|
+
<ScrubValue
|
|
393
|
+
value={padding.right}
|
|
394
|
+
property="paddingRight"
|
|
395
|
+
color="#4ade80"
|
|
396
|
+
onChange={onChange}
|
|
397
|
+
onReset={onReset}
|
|
398
|
+
/>
|
|
399
|
+
</div>
|
|
400
|
+
</div>
|
|
401
|
+
|
|
402
|
+
{/* Padding bottom */}
|
|
403
|
+
<div className="flex justify-center py-0.5">
|
|
404
|
+
<ScrubValue
|
|
405
|
+
value={padding.bottom}
|
|
406
|
+
property="paddingBottom"
|
|
407
|
+
color="#4ade80"
|
|
408
|
+
onChange={onChange}
|
|
409
|
+
onReset={onReset}
|
|
410
|
+
/>
|
|
411
|
+
</div>
|
|
412
|
+
</div>
|
|
413
|
+
|
|
414
|
+
<div
|
|
415
|
+
className="flex justify-center px-1"
|
|
416
|
+
style={{ minWidth: '16px' }}
|
|
417
|
+
>
|
|
418
|
+
<ScrubValue
|
|
419
|
+
value={border.right}
|
|
420
|
+
property="borderRightWidth"
|
|
421
|
+
color="#a3a3a3"
|
|
422
|
+
onChange={onChange}
|
|
423
|
+
onReset={onReset}
|
|
424
|
+
/>
|
|
425
|
+
</div>
|
|
426
|
+
</div>
|
|
427
|
+
|
|
428
|
+
{/* Border bottom */}
|
|
429
|
+
<div className="flex justify-center py-0.5">
|
|
430
|
+
<ScrubValue
|
|
431
|
+
value={border.bottom}
|
|
432
|
+
property="borderBottomWidth"
|
|
433
|
+
color="#a3a3a3"
|
|
434
|
+
onChange={onChange}
|
|
435
|
+
onReset={onReset}
|
|
436
|
+
/>
|
|
437
|
+
</div>
|
|
438
|
+
</div>
|
|
439
|
+
|
|
440
|
+
<div
|
|
441
|
+
className="flex justify-center px-1"
|
|
442
|
+
style={{ minWidth: '20px' }}
|
|
443
|
+
>
|
|
444
|
+
<ScrubValue
|
|
445
|
+
value={margin.right}
|
|
446
|
+
property="marginRight"
|
|
447
|
+
color="#fbbf24"
|
|
448
|
+
onChange={onChange}
|
|
449
|
+
onReset={onReset}
|
|
450
|
+
/>
|
|
451
|
+
</div>
|
|
452
|
+
</div>
|
|
453
|
+
|
|
454
|
+
{/* Margin bottom */}
|
|
455
|
+
<div className="flex justify-center py-0.5">
|
|
456
|
+
<ScrubValue
|
|
457
|
+
value={margin.bottom}
|
|
458
|
+
property="marginBottom"
|
|
459
|
+
color="#fbbf24"
|
|
460
|
+
onChange={onChange}
|
|
461
|
+
onReset={onReset}
|
|
462
|
+
/>
|
|
463
|
+
</div>
|
|
464
|
+
</div>
|
|
465
|
+
</div>
|
|
466
|
+
)
|
|
467
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useEditorStore } from '@/store'
|
|
4
|
+
import { VariableColorPicker } from '@/components/common/VariableColorPicker'
|
|
5
|
+
|
|
6
|
+
interface ColorInputProps {
|
|
7
|
+
value: string
|
|
8
|
+
property: string
|
|
9
|
+
onChange: (property: string, value: string) => void
|
|
10
|
+
varExpression?: string
|
|
11
|
+
label?: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function ColorInput({
|
|
15
|
+
value,
|
|
16
|
+
property,
|
|
17
|
+
onChange,
|
|
18
|
+
varExpression,
|
|
19
|
+
label,
|
|
20
|
+
}: ColorInputProps) {
|
|
21
|
+
const selectorPath = useEditorStore((s) => s.selectorPath)
|
|
22
|
+
const detachProperty = useEditorStore((s) => s.detachProperty)
|
|
23
|
+
const reattachProperty = useEditorStore((s) => s.reattachProperty)
|
|
24
|
+
const tailwindEntry = useEditorStore((s) => s.tailwindClassMap[property])
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<VariableColorPicker
|
|
28
|
+
label={label || ''}
|
|
29
|
+
property={property}
|
|
30
|
+
value={value}
|
|
31
|
+
varExpression={varExpression}
|
|
32
|
+
tailwindClassName={tailwindEntry?.className}
|
|
33
|
+
onChange={onChange}
|
|
34
|
+
onDetach={() => selectorPath && detachProperty(selectorPath, property)}
|
|
35
|
+
onReattach={(expr) => {
|
|
36
|
+
if (selectorPath) {
|
|
37
|
+
reattachProperty(selectorPath, property)
|
|
38
|
+
onChange(property, expr)
|
|
39
|
+
}
|
|
40
|
+
}}
|
|
41
|
+
/>
|
|
42
|
+
)
|
|
43
|
+
}
|