@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,573 @@
|
|
|
1
|
+
import type { Breakpoint } from '@/types/changelog'
|
|
2
|
+
|
|
3
|
+
export const MESSAGE_TYPES = {
|
|
4
|
+
// Inspector → Editor
|
|
5
|
+
INSPECTOR_READY: 'INSPECTOR_READY',
|
|
6
|
+
ELEMENT_SELECTED: 'ELEMENT_SELECTED',
|
|
7
|
+
ELEMENT_HOVERED: 'ELEMENT_HOVERED',
|
|
8
|
+
DOM_UPDATED: 'DOM_UPDATED',
|
|
9
|
+
DOM_TREE: 'DOM_TREE',
|
|
10
|
+
PAGE_LINKS: 'PAGE_LINKS',
|
|
11
|
+
HEARTBEAT_RESPONSE: 'HEARTBEAT_RESPONSE',
|
|
12
|
+
CSS_VARIABLES: 'CSS_VARIABLES',
|
|
13
|
+
COMPONENTS_DETECTED: 'COMPONENTS_DETECTED',
|
|
14
|
+
VARIANT_APPLIED: 'VARIANT_APPLIED',
|
|
15
|
+
CONSOLE_MESSAGE: 'CONSOLE_MESSAGE',
|
|
16
|
+
ELEMENT_INSERTED: 'ELEMENT_INSERTED',
|
|
17
|
+
// Editor → Inspector
|
|
18
|
+
SELECT_ELEMENT: 'SELECT_ELEMENT',
|
|
19
|
+
PREVIEW_CHANGE: 'PREVIEW_CHANGE',
|
|
20
|
+
REVERT_CHANGE: 'REVERT_CHANGE',
|
|
21
|
+
REVERT_ALL: 'REVERT_ALL',
|
|
22
|
+
SET_BREAKPOINT: 'SET_BREAKPOINT',
|
|
23
|
+
REQUEST_DOM_TREE: 'REQUEST_DOM_TREE',
|
|
24
|
+
REQUEST_PAGE_LINKS: 'REQUEST_PAGE_LINKS',
|
|
25
|
+
HEARTBEAT: 'HEARTBEAT',
|
|
26
|
+
REQUEST_CSS_VARIABLES: 'REQUEST_CSS_VARIABLES',
|
|
27
|
+
SET_SELECTION_MODE: 'SET_SELECTION_MODE',
|
|
28
|
+
REQUEST_COMPONENTS: 'REQUEST_COMPONENTS',
|
|
29
|
+
APPLY_VARIANT: 'APPLY_VARIANT',
|
|
30
|
+
REVERT_VARIANT: 'REVERT_VARIANT',
|
|
31
|
+
INSERT_ELEMENT: 'INSERT_ELEMENT',
|
|
32
|
+
REMOVE_INSERTED_ELEMENT: 'REMOVE_INSERTED_ELEMENT',
|
|
33
|
+
} as const
|
|
34
|
+
|
|
35
|
+
export const BREAKPOINTS: Record<
|
|
36
|
+
Breakpoint,
|
|
37
|
+
{ label: string; deviceName: string; width: number }
|
|
38
|
+
> = {
|
|
39
|
+
mobile: { label: 'Mobile', deviceName: 'iPhone SE', width: 375 },
|
|
40
|
+
tablet: { label: 'Tablet', deviceName: 'iPad Mini', width: 768 },
|
|
41
|
+
desktop: { label: 'Desktop', deviceName: 'Laptop', width: 1280 },
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Get the breakpoint range string for a given breakpoint.
|
|
46
|
+
* E.g., tablet (768px) affects down to mobile's max (430px) → "768px > 430px"
|
|
47
|
+
* Mobile is the lowest, so it just shows "375px".
|
|
48
|
+
* Desktop has no upper bound, so it shows "1280px+".
|
|
49
|
+
*/
|
|
50
|
+
const LOWER_BOUND_MAP: Record<Breakpoint, number | null> = {
|
|
51
|
+
desktop: 1024, // affects down to tablet upper range
|
|
52
|
+
tablet: 430, // affects down to mobile upper range (iPhone 14 Pro Max)
|
|
53
|
+
mobile: 0, // lowest breakpoint
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function getBreakpointRange(bp: Breakpoint): string {
|
|
57
|
+
const width = BREAKPOINTS[bp].width
|
|
58
|
+
const lowerBound = LOWER_BOUND_MAP[bp]
|
|
59
|
+
if (bp === 'mobile') return `${width}px`
|
|
60
|
+
if (lowerBound != null && lowerBound > 0)
|
|
61
|
+
return `${width}px > ${lowerBound}px`
|
|
62
|
+
return `${width}px`
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function getBreakpointDeviceInfo(bp: Breakpoint): {
|
|
66
|
+
deviceName: string
|
|
67
|
+
range: string
|
|
68
|
+
} {
|
|
69
|
+
return {
|
|
70
|
+
deviceName: BREAKPOINTS[bp].deviceName,
|
|
71
|
+
range: getBreakpointRange(bp),
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export const CSS_PROPERTIES = {
|
|
76
|
+
size: [
|
|
77
|
+
'width',
|
|
78
|
+
'height',
|
|
79
|
+
'min-width',
|
|
80
|
+
'min-height',
|
|
81
|
+
'max-width',
|
|
82
|
+
'max-height',
|
|
83
|
+
'overflow',
|
|
84
|
+
'box-sizing',
|
|
85
|
+
],
|
|
86
|
+
spacing: [
|
|
87
|
+
'margin-top',
|
|
88
|
+
'margin-right',
|
|
89
|
+
'margin-bottom',
|
|
90
|
+
'margin-left',
|
|
91
|
+
'padding-top',
|
|
92
|
+
'padding-right',
|
|
93
|
+
'padding-bottom',
|
|
94
|
+
'padding-left',
|
|
95
|
+
],
|
|
96
|
+
typography: [
|
|
97
|
+
'font-family',
|
|
98
|
+
'font-size',
|
|
99
|
+
'font-weight',
|
|
100
|
+
'line-height',
|
|
101
|
+
'letter-spacing',
|
|
102
|
+
'text-align',
|
|
103
|
+
'text-decoration',
|
|
104
|
+
'text-transform',
|
|
105
|
+
'color',
|
|
106
|
+
],
|
|
107
|
+
border: [
|
|
108
|
+
'border-width',
|
|
109
|
+
'border-style',
|
|
110
|
+
'border-color',
|
|
111
|
+
'border-radius',
|
|
112
|
+
'border-top-width',
|
|
113
|
+
'border-right-width',
|
|
114
|
+
'border-bottom-width',
|
|
115
|
+
'border-left-width',
|
|
116
|
+
'border-top-left-radius',
|
|
117
|
+
'border-top-right-radius',
|
|
118
|
+
'border-bottom-right-radius',
|
|
119
|
+
'border-bottom-left-radius',
|
|
120
|
+
],
|
|
121
|
+
background: [
|
|
122
|
+
'background-color',
|
|
123
|
+
'background-image',
|
|
124
|
+
'background-size',
|
|
125
|
+
'background-position',
|
|
126
|
+
'background-repeat',
|
|
127
|
+
'background-attachment',
|
|
128
|
+
'background-clip',
|
|
129
|
+
],
|
|
130
|
+
layout: [
|
|
131
|
+
'display',
|
|
132
|
+
'flex-direction',
|
|
133
|
+
'justify-content',
|
|
134
|
+
'align-items',
|
|
135
|
+
'align-content',
|
|
136
|
+
'flex-wrap',
|
|
137
|
+
'gap',
|
|
138
|
+
'row-gap',
|
|
139
|
+
'column-gap',
|
|
140
|
+
'grid-template-columns',
|
|
141
|
+
'grid-template-rows',
|
|
142
|
+
],
|
|
143
|
+
position: ['position', 'top', 'right', 'bottom', 'left', 'z-index'],
|
|
144
|
+
appearance: ['opacity'],
|
|
145
|
+
shadow: ['box-shadow'],
|
|
146
|
+
'flex-item': [
|
|
147
|
+
'flex-grow',
|
|
148
|
+
'flex-shrink',
|
|
149
|
+
'flex-basis',
|
|
150
|
+
'align-self',
|
|
151
|
+
'order',
|
|
152
|
+
],
|
|
153
|
+
transform: ['transform'],
|
|
154
|
+
filter: ['filter'],
|
|
155
|
+
svg: ['fill', 'stroke'],
|
|
156
|
+
} as const
|
|
157
|
+
|
|
158
|
+
export const ALL_EDITABLE_PROPERTIES = Object.values(CSS_PROPERTIES).flat()
|
|
159
|
+
|
|
160
|
+
export const DARK_MODE_TOKENS = {
|
|
161
|
+
bgPrimary: '#1e1e1e',
|
|
162
|
+
bgSecondary: '#252526',
|
|
163
|
+
bgTertiary: '#2d2d30',
|
|
164
|
+
bgHover: '#3c3c3c',
|
|
165
|
+
bgActive: '#37373d',
|
|
166
|
+
textPrimary: '#e0e0e0',
|
|
167
|
+
textSecondary: '#a0a0a0',
|
|
168
|
+
textMuted: '#6a6a6a',
|
|
169
|
+
accent: '#4a9eff',
|
|
170
|
+
accentHover: '#5aafff',
|
|
171
|
+
border: '#3c3c3c',
|
|
172
|
+
borderHover: '#555555',
|
|
173
|
+
success: '#4ec9b0',
|
|
174
|
+
warning: '#dcdcaa',
|
|
175
|
+
error: '#f44747',
|
|
176
|
+
} as const
|
|
177
|
+
|
|
178
|
+
export const LOCAL_STORAGE_KEYS = {
|
|
179
|
+
RECENT_URLS: 'dev-editor:recent-urls',
|
|
180
|
+
PANEL_SIZES: 'dev-editor:panel-sizes',
|
|
181
|
+
PANEL_VISIBILITY: 'dev-editor:panel-visibility',
|
|
182
|
+
CHANGES_PREFIX: 'dev-editor:changes:',
|
|
183
|
+
CLAUDE_PROJECT_ROOT: 'dev-editor:claude:project-root',
|
|
184
|
+
CLAUDE_PORT_ROOTS: 'dev-editor:claude:port-roots',
|
|
185
|
+
CLAUDE_CLI_AVAILABLE: 'dev-editor:claude:cli-available',
|
|
186
|
+
CLAUDE_PROJECT_SCANS: 'dev-editor:claude:project-scans',
|
|
187
|
+
} as const
|
|
188
|
+
|
|
189
|
+
export const PROXY_HEADER = 'x-dev-editor-target'
|
|
190
|
+
|
|
191
|
+
export const HEARTBEAT_INTERVAL_MS = 5000
|
|
192
|
+
export const HEARTBEAT_TIMEOUT_MS = 3000
|
|
193
|
+
export const RECONNECT_MAX_RETRIES = 5
|
|
194
|
+
export const RECONNECT_BASE_DELAY_MS = 1000
|
|
195
|
+
|
|
196
|
+
export function buildInstructionsFooter(
|
|
197
|
+
changeCount: number,
|
|
198
|
+
elementCount: number,
|
|
199
|
+
opts?: { framework?: string | null; cssStrategy?: string[] | null },
|
|
200
|
+
): string {
|
|
201
|
+
const lines: string[] = []
|
|
202
|
+
const framework = opts?.framework ?? null
|
|
203
|
+
const cssStrategy = opts?.cssStrategy ?? null
|
|
204
|
+
const usesTailwind = cssStrategy?.includes('tailwind') ?? false
|
|
205
|
+
|
|
206
|
+
lines.push('---')
|
|
207
|
+
lines.push(
|
|
208
|
+
`Summary: ${changeCount} change${changeCount !== 1 ? 's' : ''} across ${elementCount} element${elementCount !== 1 ? 's' : ''}`,
|
|
209
|
+
)
|
|
210
|
+
lines.push('')
|
|
211
|
+
lines.push('## Instructions for Claude Code')
|
|
212
|
+
lines.push(
|
|
213
|
+
'Apply these visual changes to the source files. For each style change,',
|
|
214
|
+
)
|
|
215
|
+
lines.push('find the element matching the selector and update its styles.')
|
|
216
|
+
|
|
217
|
+
if (framework === 'flutter') {
|
|
218
|
+
// Flutter-specific guidance
|
|
219
|
+
lines.push('')
|
|
220
|
+
lines.push('### Flutter / Dart Styling')
|
|
221
|
+
lines.push(
|
|
222
|
+
'Flutter does NOT use CSS. Styles are applied via widget properties and style objects.',
|
|
223
|
+
)
|
|
224
|
+
lines.push('')
|
|
225
|
+
lines.push('**Spacing**')
|
|
226
|
+
lines.push(
|
|
227
|
+
'- padding → EdgeInsets.all(n), EdgeInsets.symmetric(horizontal: n, vertical: n), EdgeInsets.only(left: n, top: n, ...)',
|
|
228
|
+
)
|
|
229
|
+
lines.push(
|
|
230
|
+
'- margin → wrap in Padding widget or use Container(margin: EdgeInsets.all(n))',
|
|
231
|
+
)
|
|
232
|
+
lines.push(
|
|
233
|
+
'- gap (in Row/Column) → use SizedBox(width: n) or SizedBox(height: n) between children, or MainAxisAlignment.spaceEvenly',
|
|
234
|
+
)
|
|
235
|
+
lines.push('')
|
|
236
|
+
lines.push('**Typography**')
|
|
237
|
+
lines.push('- font-size → TextStyle(fontSize: n)')
|
|
238
|
+
lines.push(
|
|
239
|
+
'- font-weight → TextStyle(fontWeight: FontWeight.w400/w500/w600/w700/bold)',
|
|
240
|
+
)
|
|
241
|
+
lines.push(
|
|
242
|
+
'- line-height → TextStyle(height: n) where n is multiplier (1.5 = 150%)',
|
|
243
|
+
)
|
|
244
|
+
lines.push('- color → TextStyle(color: Color(0xFF______)) or Colors.blue')
|
|
245
|
+
lines.push(
|
|
246
|
+
'- text-align → Text("...", textAlign: TextAlign.center/left/right)',
|
|
247
|
+
)
|
|
248
|
+
lines.push('')
|
|
249
|
+
lines.push('**Colors**')
|
|
250
|
+
lines.push(
|
|
251
|
+
'- background-color → Container(color: Color(0xFF______)) or ColoredBox',
|
|
252
|
+
)
|
|
253
|
+
lines.push(
|
|
254
|
+
'- border-color → BoxDecoration(border: Border.all(color: Color(0xFF______)))',
|
|
255
|
+
)
|
|
256
|
+
lines.push(
|
|
257
|
+
'- Use Color(0xAARRGGBB) for hex, Colors.blue for Material palette',
|
|
258
|
+
)
|
|
259
|
+
lines.push('')
|
|
260
|
+
lines.push('**Layout**')
|
|
261
|
+
lines.push('- display: flex → Row (horizontal) or Column (vertical)')
|
|
262
|
+
lines.push(
|
|
263
|
+
'- justify-content → MainAxisAlignment.start/center/end/spaceBetween/spaceAround/spaceEvenly',
|
|
264
|
+
)
|
|
265
|
+
lines.push('- align-items → CrossAxisAlignment.start/center/end/stretch')
|
|
266
|
+
lines.push('- flex-wrap → use Wrap widget instead of Row/Column')
|
|
267
|
+
lines.push('- flex: 1 → Expanded(child: ...) or Flexible(child: ...)')
|
|
268
|
+
lines.push('')
|
|
269
|
+
lines.push('**Sizing**')
|
|
270
|
+
lines.push(
|
|
271
|
+
'- width/height → SizedBox(width: n, height: n) or Container(width: n, height: n)',
|
|
272
|
+
)
|
|
273
|
+
lines.push('- width: 100% → double.infinity')
|
|
274
|
+
lines.push(
|
|
275
|
+
'- max-width → ConstrainedBox(constraints: BoxConstraints(maxWidth: n))',
|
|
276
|
+
)
|
|
277
|
+
lines.push('')
|
|
278
|
+
lines.push('**Borders**')
|
|
279
|
+
lines.push(
|
|
280
|
+
'- border-radius → BoxDecoration(borderRadius: BorderRadius.circular(n))',
|
|
281
|
+
)
|
|
282
|
+
lines.push('- border-width → BoxDecoration(border: Border.all(width: n))')
|
|
283
|
+
lines.push('')
|
|
284
|
+
lines.push('**Effects**')
|
|
285
|
+
lines.push('- opacity → Opacity(opacity: 0.5, child: ...)')
|
|
286
|
+
lines.push(
|
|
287
|
+
'- box-shadow → BoxDecoration(boxShadow: [BoxShadow(blurRadius: n, offset: Offset(x, y))])',
|
|
288
|
+
)
|
|
289
|
+
} else if (framework === 'react-native') {
|
|
290
|
+
// React Native-specific guidance
|
|
291
|
+
lines.push('')
|
|
292
|
+
lines.push('### React Native Styling')
|
|
293
|
+
lines.push(
|
|
294
|
+
'React Native does NOT use CSS classes or Tailwind. Styles are applied via the `style` prop with StyleSheet objects.',
|
|
295
|
+
)
|
|
296
|
+
lines.push('')
|
|
297
|
+
lines.push('**How to Apply**')
|
|
298
|
+
lines.push(
|
|
299
|
+
'Find the component and update its StyleSheet.create() definitions or inline style prop.',
|
|
300
|
+
)
|
|
301
|
+
lines.push('```')
|
|
302
|
+
lines.push('const styles = StyleSheet.create({')
|
|
303
|
+
lines.push(' container: { padding: 16, backgroundColor: "#fff" },')
|
|
304
|
+
lines.push('});')
|
|
305
|
+
lines.push('<View style={styles.container} />')
|
|
306
|
+
lines.push('```')
|
|
307
|
+
lines.push('')
|
|
308
|
+
lines.push('**Spacing** — Uses raw numbers (dp, not px). No units needed.')
|
|
309
|
+
lines.push(
|
|
310
|
+
'- padding → padding, paddingHorizontal, paddingVertical, paddingTop, paddingRight, paddingBottom, paddingLeft',
|
|
311
|
+
)
|
|
312
|
+
lines.push(
|
|
313
|
+
'- margin → margin, marginHorizontal, marginVertical, marginTop, marginRight, marginBottom, marginLeft',
|
|
314
|
+
)
|
|
315
|
+
lines.push('- gap → gap (RN 0.71+), rowGap, columnGap')
|
|
316
|
+
lines.push('')
|
|
317
|
+
lines.push('**Typography**')
|
|
318
|
+
lines.push('- font-size → fontSize: n')
|
|
319
|
+
lines.push('- font-weight → fontWeight: "400"/"500"/"600"/"700"/"bold"')
|
|
320
|
+
lines.push('- line-height → lineHeight: n (absolute value, not multiplier)')
|
|
321
|
+
lines.push('- color → color: "#hex" or "rgb(r,g,b)"')
|
|
322
|
+
lines.push('- text-align → textAlign: "left"/"center"/"right"')
|
|
323
|
+
lines.push('')
|
|
324
|
+
lines.push('**Colors** — Use hex strings or rgba')
|
|
325
|
+
lines.push('- background-color → backgroundColor: "#hex"')
|
|
326
|
+
lines.push('- border-color → borderColor: "#hex"')
|
|
327
|
+
lines.push('')
|
|
328
|
+
lines.push(
|
|
329
|
+
'**Layout** — Flexbox by default (flex-direction defaults to column, not row)',
|
|
330
|
+
)
|
|
331
|
+
lines.push('- display: flex → display: "flex" (default, usually omitted)')
|
|
332
|
+
lines.push(
|
|
333
|
+
'- flex-direction → flexDirection: "row"/"column" (NOTE: default is "column", opposite of web)',
|
|
334
|
+
)
|
|
335
|
+
lines.push(
|
|
336
|
+
'- justify-content → justifyContent: "flex-start"/"center"/"flex-end"/"space-between"/"space-around"',
|
|
337
|
+
)
|
|
338
|
+
lines.push(
|
|
339
|
+
'- align-items → alignItems: "flex-start"/"center"/"flex-end"/"stretch"',
|
|
340
|
+
)
|
|
341
|
+
lines.push('- flex: 1 → flex: 1')
|
|
342
|
+
lines.push('')
|
|
343
|
+
lines.push('**Sizing**')
|
|
344
|
+
lines.push(
|
|
345
|
+
'- width/height → width: n / height: n (number for dp, "100%" for percentage)',
|
|
346
|
+
)
|
|
347
|
+
lines.push('')
|
|
348
|
+
lines.push('**Borders**')
|
|
349
|
+
lines.push('- border-radius → borderRadius: n (number only, no px)')
|
|
350
|
+
lines.push('- border-width → borderWidth: n')
|
|
351
|
+
lines.push('')
|
|
352
|
+
lines.push('**Effects**')
|
|
353
|
+
lines.push('- opacity → opacity: n (0-1)')
|
|
354
|
+
lines.push(
|
|
355
|
+
'- box-shadow → iOS: shadowColor/shadowOffset/shadowOpacity/shadowRadius; Android: elevation: n',
|
|
356
|
+
)
|
|
357
|
+
} else if (usesTailwind) {
|
|
358
|
+
// Tailwind CSS guidance (web frameworks)
|
|
359
|
+
lines.push('')
|
|
360
|
+
lines.push('### How to Apply in React / Next.js')
|
|
361
|
+
lines.push(
|
|
362
|
+
'Styles in React are applied via the `className` prop on JSX elements.',
|
|
363
|
+
)
|
|
364
|
+
lines.push(
|
|
365
|
+
'Find the component that renders the matching element and edit its className.',
|
|
366
|
+
)
|
|
367
|
+
lines.push('')
|
|
368
|
+
lines.push('### Tailwind CSS Mappings')
|
|
369
|
+
lines.push(
|
|
370
|
+
'This project uses Tailwind CSS. Update utility classes in className — never add inline styles.',
|
|
371
|
+
)
|
|
372
|
+
lines.push('')
|
|
373
|
+
lines.push(
|
|
374
|
+
'**Spacing** (Tailwind uses a 4px = 1 unit scale: 4px=1, 8px=2, 12px=3, 16px=4, 20px=5, 24px=6, 32px=8, 40px=10, 48px=12, 64px=16)',
|
|
375
|
+
)
|
|
376
|
+
lines.push(
|
|
377
|
+
'- padding → p-{n}, px-{n}, py-{n}, pt-{n}, pr-{n}, pb-{n}, pl-{n}',
|
|
378
|
+
)
|
|
379
|
+
lines.push(
|
|
380
|
+
'- margin → m-{n}, mx-{n}, my-{n}, mt-{n}, mr-{n}, mb-{n}, ml-{n} (supports negative: -mt-4)',
|
|
381
|
+
)
|
|
382
|
+
lines.push('- gap → gap-{n}, gap-x-{n}, gap-y-{n}')
|
|
383
|
+
lines.push('')
|
|
384
|
+
lines.push('**Typography**')
|
|
385
|
+
lines.push(
|
|
386
|
+
'- font-size → text-xs(12px), text-sm(14px), text-base(16px), text-lg(18px), text-xl(20px), text-2xl(24px), text-3xl(30px), text-4xl(36px)',
|
|
387
|
+
)
|
|
388
|
+
lines.push(
|
|
389
|
+
'- font-weight → font-thin(100), font-light(300), font-normal(400), font-medium(500), font-semibold(600), font-bold(700), font-extrabold(800)',
|
|
390
|
+
)
|
|
391
|
+
lines.push(
|
|
392
|
+
'- line-height → leading-none(1), leading-tight(1.25), leading-snug(1.375), leading-normal(1.5), leading-relaxed(1.625), leading-loose(2)',
|
|
393
|
+
)
|
|
394
|
+
lines.push(
|
|
395
|
+
'- letter-spacing → tracking-tighter(-0.05em), tracking-tight(-0.025em), tracking-normal(0), tracking-wide(0.025em), tracking-wider(0.05em)',
|
|
396
|
+
)
|
|
397
|
+
lines.push(
|
|
398
|
+
'- text-align → text-left, text-center, text-right, text-justify',
|
|
399
|
+
)
|
|
400
|
+
lines.push(
|
|
401
|
+
'- text-transform → uppercase, lowercase, capitalize, normal-case',
|
|
402
|
+
)
|
|
403
|
+
lines.push('- text-decoration → underline, line-through, no-underline')
|
|
404
|
+
lines.push('')
|
|
405
|
+
lines.push('**Colors**')
|
|
406
|
+
lines.push(
|
|
407
|
+
'- color → text-{color}-{shade} (text-gray-500, text-blue-600, text-red-500)',
|
|
408
|
+
)
|
|
409
|
+
lines.push(
|
|
410
|
+
'- background-color → bg-{color}-{shade} (bg-white, bg-gray-100, bg-blue-500)',
|
|
411
|
+
)
|
|
412
|
+
lines.push('- border-color → border-{color}-{shade}')
|
|
413
|
+
lines.push(
|
|
414
|
+
'- For hex/rgb values not in the palette, use arbitrary: text-[#1a1a1a], bg-[rgb(30,30,30)]',
|
|
415
|
+
)
|
|
416
|
+
lines.push('')
|
|
417
|
+
lines.push('**Layout**')
|
|
418
|
+
lines.push(
|
|
419
|
+
'- display: flex → flex, display: grid → grid, display: none → hidden, display: block → block, display: inline-flex → inline-flex',
|
|
420
|
+
)
|
|
421
|
+
lines.push(
|
|
422
|
+
'- flex-direction → flex-row, flex-col, flex-row-reverse, flex-col-reverse',
|
|
423
|
+
)
|
|
424
|
+
lines.push(
|
|
425
|
+
'- justify-content → justify-start, justify-center, justify-end, justify-between, justify-around, justify-evenly',
|
|
426
|
+
)
|
|
427
|
+
lines.push(
|
|
428
|
+
'- align-items → items-start, items-center, items-end, items-baseline, items-stretch',
|
|
429
|
+
)
|
|
430
|
+
lines.push('- flex-wrap → flex-wrap, flex-nowrap')
|
|
431
|
+
lines.push('- flex-grow: 1 → grow or flex-1, flex-shrink: 0 → shrink-0')
|
|
432
|
+
lines.push('- order → order-first, order-last, order-none, order-{n}')
|
|
433
|
+
lines.push('')
|
|
434
|
+
lines.push('**Sizing**')
|
|
435
|
+
lines.push(
|
|
436
|
+
'- width → w-{n}(spacing scale), w-full(100%), w-screen(100vw), w-auto, w-1/2(50%), w-1/3(33.3%), w-fit',
|
|
437
|
+
)
|
|
438
|
+
lines.push('- height → h-{n}, h-full, h-screen, h-auto, h-fit')
|
|
439
|
+
lines.push(
|
|
440
|
+
'- max-width → max-w-sm(384px), max-w-md(448px), max-w-lg(512px), max-w-xl(576px), max-w-2xl(672px), max-w-full, max-w-none',
|
|
441
|
+
)
|
|
442
|
+
lines.push('- min-height → min-h-0, min-h-full, min-h-screen')
|
|
443
|
+
lines.push('')
|
|
444
|
+
lines.push('**Borders**')
|
|
445
|
+
lines.push(
|
|
446
|
+
'- border-width → border(1px), border-0, border-2(2px), border-4(4px), border-t, border-b, border-l, border-r',
|
|
447
|
+
)
|
|
448
|
+
lines.push(
|
|
449
|
+
'- border-radius → rounded-none(0), rounded-sm(2px), rounded(4px), rounded-md(6px), rounded-lg(8px), rounded-xl(12px), rounded-2xl(16px), rounded-full(9999px)',
|
|
450
|
+
)
|
|
451
|
+
lines.push(
|
|
452
|
+
'- border-style → border-solid, border-dashed, border-dotted, border-none',
|
|
453
|
+
)
|
|
454
|
+
lines.push('')
|
|
455
|
+
lines.push('**Effects**')
|
|
456
|
+
lines.push(
|
|
457
|
+
'- opacity → opacity-0, opacity-25, opacity-50, opacity-75, opacity-100',
|
|
458
|
+
)
|
|
459
|
+
lines.push(
|
|
460
|
+
'- box-shadow → shadow-sm, shadow, shadow-md, shadow-lg, shadow-xl, shadow-2xl, shadow-none',
|
|
461
|
+
)
|
|
462
|
+
lines.push('')
|
|
463
|
+
lines.push('**Position**')
|
|
464
|
+
lines.push('- position → static, relative, absolute, fixed, sticky')
|
|
465
|
+
lines.push(
|
|
466
|
+
'- top/right/bottom/left → top-{n}, right-{n}, bottom-{n}, left-{n} (uses spacing scale), inset-0(all sides 0)',
|
|
467
|
+
)
|
|
468
|
+
lines.push('- z-index → z-0, z-10, z-20, z-30, z-40, z-50, z-auto')
|
|
469
|
+
lines.push('')
|
|
470
|
+
lines.push(
|
|
471
|
+
'**Arbitrary values**: When no Tailwind scale value matches, use bracket syntax: w-[200px], mt-[13px], text-[15px], bg-[#1e1e1e], grid-cols-[1fr_2fr]',
|
|
472
|
+
)
|
|
473
|
+
lines.push('')
|
|
474
|
+
lines.push('### Responsive Breakpoints')
|
|
475
|
+
lines.push(
|
|
476
|
+
'Tailwind uses mobile-first breakpoint prefixes. Unprefixed classes apply to all sizes.',
|
|
477
|
+
)
|
|
478
|
+
lines.push(
|
|
479
|
+
'- sm: → 640px+, md: → 768px+, lg: → 1024px+, xl: → 1280px+, 2xl: → 1536px+',
|
|
480
|
+
)
|
|
481
|
+
lines.push('- Changes marked [desktop] may need lg: or xl: prefix')
|
|
482
|
+
lines.push('- Changes marked [tablet] may need md: prefix')
|
|
483
|
+
lines.push('- Changes marked [mobile] apply as the base (no prefix)')
|
|
484
|
+
lines.push(
|
|
485
|
+
'- Example: className="text-sm md:text-base lg:text-lg" (small on mobile, base on tablet, large on desktop)',
|
|
486
|
+
)
|
|
487
|
+
} else {
|
|
488
|
+
// Generic web CSS guidance (no Tailwind detected, or unknown framework)
|
|
489
|
+
lines.push('')
|
|
490
|
+
lines.push('### How to Apply')
|
|
491
|
+
lines.push(
|
|
492
|
+
'Find the element matching the selector and update its styles using the',
|
|
493
|
+
)
|
|
494
|
+
lines.push(
|
|
495
|
+
"project's CSS approach (CSS files, CSS modules, styled-components, inline styles, etc.).",
|
|
496
|
+
)
|
|
497
|
+
lines.push('')
|
|
498
|
+
lines.push('If the project uses CSS classes, update the class definitions.')
|
|
499
|
+
lines.push(
|
|
500
|
+
'If the project uses inline styles in JSX (`style={{ }}`), update the style object.',
|
|
501
|
+
)
|
|
502
|
+
lines.push('')
|
|
503
|
+
lines.push('### Responsive Breakpoints')
|
|
504
|
+
lines.push(
|
|
505
|
+
'- Changes marked [desktop] apply at wider viewports (typically @media (min-width: 1024px))',
|
|
506
|
+
)
|
|
507
|
+
lines.push(
|
|
508
|
+
'- Changes marked [tablet] apply at medium viewports (typically @media (min-width: 768px))',
|
|
509
|
+
)
|
|
510
|
+
lines.push(
|
|
511
|
+
'- Changes marked [mobile] apply at the base / smallest viewports',
|
|
512
|
+
)
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
lines.push('=== END CHANGELOG ===')
|
|
516
|
+
return lines.join('\n')
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
export interface DevicePreset {
|
|
520
|
+
name: string
|
|
521
|
+
width: number
|
|
522
|
+
category: 'phone' | 'tablet' | 'desktop'
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
export const DEVICE_PRESETS: DevicePreset[] = [
|
|
526
|
+
// Phones
|
|
527
|
+
{ name: 'iPhone SE', width: 375, category: 'phone' },
|
|
528
|
+
{ name: 'iPhone 14 Pro', width: 393, category: 'phone' },
|
|
529
|
+
{ name: 'iPhone 14 Pro Max', width: 430, category: 'phone' },
|
|
530
|
+
{ name: 'Samsung Galaxy S24', width: 360, category: 'phone' },
|
|
531
|
+
{ name: 'Samsung Galaxy S24 Ultra', width: 412, category: 'phone' },
|
|
532
|
+
{ name: 'Google Pixel 9', width: 412, category: 'phone' },
|
|
533
|
+
{ name: 'Google Pixel 9 Pro XL', width: 448, category: 'phone' },
|
|
534
|
+
{ name: 'OnePlus 12', width: 400, category: 'phone' },
|
|
535
|
+
// Tablets
|
|
536
|
+
{ name: 'iPad Mini', width: 768, category: 'tablet' },
|
|
537
|
+
{ name: 'iPad Air', width: 820, category: 'tablet' },
|
|
538
|
+
{ name: 'iPad Pro 12.9"', width: 1024, category: 'tablet' },
|
|
539
|
+
{ name: 'Samsung Galaxy Tab S9', width: 800, category: 'tablet' },
|
|
540
|
+
{ name: 'Samsung Galaxy Tab S9+', width: 930, category: 'tablet' },
|
|
541
|
+
{ name: 'Samsung Galaxy Tab S9 Ultra', width: 1038, category: 'tablet' },
|
|
542
|
+
{ name: 'Google Pixel Tablet', width: 834, category: 'tablet' },
|
|
543
|
+
{ name: 'Lenovo Tab P12 Pro', width: 960, category: 'tablet' },
|
|
544
|
+
{ name: 'Amazon Fire HD 10', width: 600, category: 'tablet' },
|
|
545
|
+
// Desktop
|
|
546
|
+
{ name: 'Laptop', width: 1280, category: 'desktop' },
|
|
547
|
+
{ name: 'Desktop', width: 1440, category: 'desktop' },
|
|
548
|
+
{ name: 'Wide', width: 1920, category: 'desktop' },
|
|
549
|
+
]
|
|
550
|
+
|
|
551
|
+
// Maps breakpoint names to device categories
|
|
552
|
+
export const BREAKPOINT_CATEGORY_MAP: Record<
|
|
553
|
+
Breakpoint,
|
|
554
|
+
DevicePreset['category']
|
|
555
|
+
> = {
|
|
556
|
+
mobile: 'phone',
|
|
557
|
+
tablet: 'tablet',
|
|
558
|
+
desktop: 'desktop',
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
export const PREVIEW_WIDTH_MIN = 320
|
|
562
|
+
export const PREVIEW_WIDTH_MAX = 1920
|
|
563
|
+
|
|
564
|
+
export const LEFT_ICON_SIDEBAR_WIDTH = 40
|
|
565
|
+
|
|
566
|
+
export const PANEL_DEFAULTS = {
|
|
567
|
+
leftWidth: 240,
|
|
568
|
+
rightWidth: 300,
|
|
569
|
+
leftMin: 180,
|
|
570
|
+
leftMax: 400,
|
|
571
|
+
rightMin: 240,
|
|
572
|
+
rightMax: 500,
|
|
573
|
+
} as const
|