@fragmentsx/figma-converter 0.0.1
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/package.json +31 -0
- package/playground/index.html +12 -0
- package/playground/node_modules/.bin/tsc +17 -0
- package/playground/node_modules/.bin/tsserver +17 -0
- package/playground/node_modules/.bin/vite +17 -0
- package/playground/package.json +25 -0
- package/playground/src/123.json +527 -0
- package/playground/src/App.tsx +534 -0
- package/playground/src/doc.json +4024 -0
- package/playground/src/main.tsx +4 -0
- package/playground/tsconfig.json +12 -0
- package/playground/vite.config.ts +59 -0
- package/src/__tests__/fixtures/simple-frame.json +70 -0
- package/src/__tests__/fixtures/with-effects.json +19 -0
- package/src/__tests__/fixtures/with-image.json +16 -0
- package/src/__tests__/integration.test.ts +113 -0
- package/src/__tests__/validation.test.ts +108 -0
- package/src/converter.test.ts +206 -0
- package/src/converter.ts +165 -0
- package/src/diff/__tests__/buildBreakpointFrame.test.ts +43 -0
- package/src/diff/__tests__/computeDelta.test.ts +33 -0
- package/src/diff/__tests__/matchNodes.test.ts +52 -0
- package/src/diff/buildBreakpointFrame.ts +99 -0
- package/src/diff/computeDelta.ts +40 -0
- package/src/diff/index.ts +3 -0
- package/src/diff/matchNodes.ts +46 -0
- package/src/images/__tests__/collectImages.test.ts +30 -0
- package/src/images/collectImages.ts +29 -0
- package/src/index.ts +16 -0
- package/src/mappers/__tests__/borderRadius.test.ts +28 -0
- package/src/mappers/__tests__/layout.test.ts +71 -0
- package/src/mappers/__tests__/nodeMapper.test.ts +394 -0
- package/src/mappers/__tests__/position.test.ts +67 -0
- package/src/mappers/__tests__/size.test.ts +69 -0
- package/src/mappers/borderRadius.ts +18 -0
- package/src/mappers/index.ts +5 -0
- package/src/mappers/layout.ts +58 -0
- package/src/mappers/nodeMapper.ts +330 -0
- package/src/mappers/position.ts +71 -0
- package/src/mappers/size.ts +39 -0
- package/src/styles/__tests__/border.test.ts +19 -0
- package/src/styles/__tests__/effects.test.ts +60 -0
- package/src/styles/__tests__/gradientFallback.test.ts +96 -0
- package/src/styles/__tests__/paint.test.ts +126 -0
- package/src/styles/border.ts +40 -0
- package/src/styles/effects.ts +40 -0
- package/src/styles/gradientFallback.ts +113 -0
- package/src/styles/index.ts +3 -0
- package/src/styles/paint.ts +164 -0
- package/src/text/__tests__/textToHtml.test.ts +66 -0
- package/src/text/textToHtml.ts +83 -0
- package/src/types/figma.ts +127 -0
- package/src/types/index.ts +2 -0
- package/src/types/result.ts +115 -0
- package/src/utils/__tests__/color.test.ts +15 -0
- package/src/utils/__tests__/id.test.ts +20 -0
- package/src/utils/color.ts +20 -0
- package/src/utils/id.ts +9 -0
- package/src/utils/index.ts +2 -0
- package/src/validation.ts +94 -0
- package/tsconfig.json +15 -0
- package/vite.config.ts +26 -0
- package/vitest.config.ts +14 -0
|
@@ -0,0 +1,534 @@
|
|
|
1
|
+
import { useState, useMemo } from "react";
|
|
2
|
+
import { convertFigmaToFragment } from "@fragmentsx/figma-converter";
|
|
3
|
+
import type { ConversionResult } from "@fragmentsx/figma-converter";
|
|
4
|
+
import { createTestFragmentsClient } from "@fragmentsx/client-core";
|
|
5
|
+
import { Instance } from "@fragmentsx/render-react";
|
|
6
|
+
|
|
7
|
+
const FRAGMENT_ID = 1;
|
|
8
|
+
|
|
9
|
+
type Tab =
|
|
10
|
+
| "render"
|
|
11
|
+
| "info"
|
|
12
|
+
| "document"
|
|
13
|
+
| "imageRefs"
|
|
14
|
+
| "svgRefs"
|
|
15
|
+
| "cssChunks"
|
|
16
|
+
| "warnings";
|
|
17
|
+
|
|
18
|
+
export function App() {
|
|
19
|
+
const [jsonInput, setJsonInput] = useState("");
|
|
20
|
+
const [result, setResult] = useState<ConversionResult | null>(null);
|
|
21
|
+
const [error, setError] = useState<string | null>(null);
|
|
22
|
+
const [renderKey, setRenderKey] = useState(0);
|
|
23
|
+
const [tab, setTab] = useState<Tab>("render");
|
|
24
|
+
|
|
25
|
+
const globalManager = useMemo(() => {
|
|
26
|
+
if (!result) return null;
|
|
27
|
+
const gm = createTestFragmentsClient({
|
|
28
|
+
fragments: { [FRAGMENT_ID]: result.document as any },
|
|
29
|
+
});
|
|
30
|
+
gm.$load.loadFragment(FRAGMENT_ID);
|
|
31
|
+
|
|
32
|
+
// Инжектируем cssChunks в <head>
|
|
33
|
+
const styleId = "figma-converter-playground-css-chunks";
|
|
34
|
+
const existing = document.getElementById(styleId);
|
|
35
|
+
if (existing) existing.remove();
|
|
36
|
+
if (result.cssChunks.length > 0) {
|
|
37
|
+
const style = document.createElement("style");
|
|
38
|
+
style.id = styleId;
|
|
39
|
+
style.textContent = result.cssChunks.map((c) => c.content).join("\n");
|
|
40
|
+
document.head.appendChild(style);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return gm;
|
|
44
|
+
}, [result, renderKey]);
|
|
45
|
+
|
|
46
|
+
const handleConvert = () => {
|
|
47
|
+
setError(null);
|
|
48
|
+
try {
|
|
49
|
+
const parsed = JSON.parse(jsonInput);
|
|
50
|
+
const frames = Array.isArray(parsed)
|
|
51
|
+
? parsed.map((f: any) => ({
|
|
52
|
+
node: f.node || f,
|
|
53
|
+
width: (f.node || f).width,
|
|
54
|
+
}))
|
|
55
|
+
: [{ node: parsed, width: parsed.width }];
|
|
56
|
+
|
|
57
|
+
const conversionResult = convertFigmaToFragment(frames);
|
|
58
|
+
setResult(conversionResult);
|
|
59
|
+
setRenderKey((k) => k + 1);
|
|
60
|
+
setTab("render");
|
|
61
|
+
} catch (e: any) {
|
|
62
|
+
setError(e.message);
|
|
63
|
+
setResult(null);
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const handleLoadSample = async () => {
|
|
68
|
+
try {
|
|
69
|
+
const res = await fetch("/src/doc.json");
|
|
70
|
+
const json = await res.text();
|
|
71
|
+
setJsonInput(json);
|
|
72
|
+
} catch {
|
|
73
|
+
setError("Не удалось загрузить doc.json");
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const handleFetchFromPlugin = async () => {
|
|
78
|
+
try {
|
|
79
|
+
const res = await fetch("/api/doc");
|
|
80
|
+
if (res.status === 404) {
|
|
81
|
+
setError("Нет данных — нажмите '→ Playground' в Figma-плагине");
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
const json = await res.text();
|
|
85
|
+
setJsonInput(json);
|
|
86
|
+
} catch {
|
|
87
|
+
setError("Не удалось получить данные из плагина");
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
return (
|
|
92
|
+
<div style={styles.root}>
|
|
93
|
+
<header style={styles.header}>
|
|
94
|
+
Converter Playground
|
|
95
|
+
{result && (
|
|
96
|
+
<span style={styles.headerStats}>
|
|
97
|
+
{" "}
|
|
98
|
+
— images: {result.imageRefs.length}, svgs: {result.svgRefs.length},
|
|
99
|
+
fonts: {result.fonts.length}, warnings: {result.warnings.length}
|
|
100
|
+
</span>
|
|
101
|
+
)}
|
|
102
|
+
</header>
|
|
103
|
+
|
|
104
|
+
<div style={styles.main}>
|
|
105
|
+
{/* Левая панель — ввод */}
|
|
106
|
+
<div style={styles.left}>
|
|
107
|
+
<textarea
|
|
108
|
+
style={styles.textarea}
|
|
109
|
+
value={jsonInput}
|
|
110
|
+
onChange={(e) => setJsonInput(e.target.value)}
|
|
111
|
+
placeholder="Вставьте FigmaNode JSON..."
|
|
112
|
+
spellCheck={false}
|
|
113
|
+
/>
|
|
114
|
+
<div style={styles.buttons}>
|
|
115
|
+
<button style={styles.button} onClick={handleConvert}>
|
|
116
|
+
Конвертировать
|
|
117
|
+
</button>
|
|
118
|
+
<button
|
|
119
|
+
style={{ ...styles.button, background: "#555" }}
|
|
120
|
+
onClick={handleLoadSample}
|
|
121
|
+
>
|
|
122
|
+
doc.json
|
|
123
|
+
</button>
|
|
124
|
+
<button
|
|
125
|
+
style={{ ...styles.button, background: "#2e7d32" }}
|
|
126
|
+
onClick={handleFetchFromPlugin}
|
|
127
|
+
>
|
|
128
|
+
Из плагина
|
|
129
|
+
</button>
|
|
130
|
+
</div>
|
|
131
|
+
{error && <div style={styles.error}>{error}</div>}
|
|
132
|
+
</div>
|
|
133
|
+
|
|
134
|
+
{/* Правая панель — результат */}
|
|
135
|
+
<div style={styles.right}>
|
|
136
|
+
{result ? (
|
|
137
|
+
<>
|
|
138
|
+
<div style={styles.tabs}>
|
|
139
|
+
{(
|
|
140
|
+
[
|
|
141
|
+
"render",
|
|
142
|
+
"info",
|
|
143
|
+
"document",
|
|
144
|
+
"imageRefs",
|
|
145
|
+
"svgRefs",
|
|
146
|
+
"cssChunks",
|
|
147
|
+
"warnings",
|
|
148
|
+
] as Tab[]
|
|
149
|
+
).map((t) => (
|
|
150
|
+
<button
|
|
151
|
+
key={t}
|
|
152
|
+
style={tab === t ? styles.tabActive : styles.tab}
|
|
153
|
+
onClick={() => setTab(t)}
|
|
154
|
+
>
|
|
155
|
+
{t}
|
|
156
|
+
{t === "imageRefs" && ` (${result.imageRefs.length})`}
|
|
157
|
+
{t === "svgRefs" && ` (${result.svgRefs.length})`}
|
|
158
|
+
{t === "warnings" && ` (${result.warnings.length})`}
|
|
159
|
+
{t === "cssChunks" && ` (${result.cssChunks.length})`}
|
|
160
|
+
</button>
|
|
161
|
+
))}
|
|
162
|
+
</div>
|
|
163
|
+
<div
|
|
164
|
+
style={
|
|
165
|
+
tab === "render" ? styles.tabContentRender : styles.tabContent
|
|
166
|
+
}
|
|
167
|
+
>
|
|
168
|
+
{tab === "render" && globalManager && (
|
|
169
|
+
<Instance
|
|
170
|
+
fragmentId={String(FRAGMENT_ID)}
|
|
171
|
+
globalManager={globalManager}
|
|
172
|
+
/>
|
|
173
|
+
)}
|
|
174
|
+
{tab === "info" && <InfoTab result={result} />}
|
|
175
|
+
{tab === "document" && <JsonView data={result.document} />}
|
|
176
|
+
{tab === "imageRefs" && <ImageRefsTab result={result} />}
|
|
177
|
+
{tab === "svgRefs" && <SvgRefsTab result={result} />}
|
|
178
|
+
{tab === "cssChunks" && <CssChunksTab result={result} />}
|
|
179
|
+
{tab === "warnings" && <WarningsTab result={result} />}
|
|
180
|
+
</div>
|
|
181
|
+
</>
|
|
182
|
+
) : (
|
|
183
|
+
<div style={styles.placeholder}>
|
|
184
|
+
Вставьте JSON и нажмите "Конвертировать"
|
|
185
|
+
</div>
|
|
186
|
+
)}
|
|
187
|
+
</div>
|
|
188
|
+
</div>
|
|
189
|
+
</div>
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function InfoTab({ result }: { result: ConversionResult }) {
|
|
194
|
+
const doc = result.document;
|
|
195
|
+
const primaryFrame = doc.children[0];
|
|
196
|
+
|
|
197
|
+
function countNodes(node: any): number {
|
|
198
|
+
let count = 1;
|
|
199
|
+
if (node.children) {
|
|
200
|
+
for (const child of node.children) count += countNodes(child);
|
|
201
|
+
}
|
|
202
|
+
return count;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const totalNodes = countNodes(doc);
|
|
206
|
+
|
|
207
|
+
return (
|
|
208
|
+
<div style={{ display: "flex", flexDirection: "column", gap: "12px" }}>
|
|
209
|
+
<Section title="Документ">
|
|
210
|
+
<Row label="Fragment ID" value={doc._id} />
|
|
211
|
+
<Row label="Всего нод" value={totalNodes} />
|
|
212
|
+
<Row label="Breakpoints" value={doc.children.length} />
|
|
213
|
+
{primaryFrame && (
|
|
214
|
+
<>
|
|
215
|
+
<Row
|
|
216
|
+
label="Primary frame"
|
|
217
|
+
value={`${primaryFrame.width} x ${primaryFrame.height}`}
|
|
218
|
+
/>
|
|
219
|
+
<Row
|
|
220
|
+
label="Primary children"
|
|
221
|
+
value={primaryFrame.children?.length ?? 0}
|
|
222
|
+
/>
|
|
223
|
+
</>
|
|
224
|
+
)}
|
|
225
|
+
</Section>
|
|
226
|
+
|
|
227
|
+
<Section title="Ресурсы">
|
|
228
|
+
<Row
|
|
229
|
+
label="Растровые картинки (imageRefs)"
|
|
230
|
+
value={result.imageRefs.length}
|
|
231
|
+
/>
|
|
232
|
+
<Row label="Векторы → SVG (svgRefs)" value={result.svgRefs.length} />
|
|
233
|
+
<Row label="CSS chunks" value={result.cssChunks.length} />
|
|
234
|
+
<Row label="Шрифты" value={result.fonts.length} />
|
|
235
|
+
</Section>
|
|
236
|
+
|
|
237
|
+
{result.fonts.length > 0 && (
|
|
238
|
+
<Section title="Шрифты">
|
|
239
|
+
{result.fonts.map((f, i) => (
|
|
240
|
+
<Row key={i} label={f.family} value={f.style} />
|
|
241
|
+
))}
|
|
242
|
+
</Section>
|
|
243
|
+
)}
|
|
244
|
+
|
|
245
|
+
<Section title="Предупреждения">
|
|
246
|
+
<Row label="Количество" value={result.warnings.length} />
|
|
247
|
+
</Section>
|
|
248
|
+
</div>
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function ImageRefsTab({ result }: { result: ConversionResult }) {
|
|
253
|
+
if (result.imageRefs.length === 0)
|
|
254
|
+
return <Empty text="Нет растровых изображений" />;
|
|
255
|
+
return (
|
|
256
|
+
<div style={{ display: "flex", flexDirection: "column", gap: "8px" }}>
|
|
257
|
+
{result.imageRefs.map((ref, i) => (
|
|
258
|
+
<div key={i} style={styles.card}>
|
|
259
|
+
<div style={styles.cardTitle}>Image #{i + 1}</div>
|
|
260
|
+
<Row label="Figma imageHash" value={ref.figmaImageHash} />
|
|
261
|
+
<Row label="Fragment nodeId" value={ref.nodeId} />
|
|
262
|
+
<Row label="paintIndex" value={ref.paintIndex} />
|
|
263
|
+
<div style={styles.pipelineNote}>
|
|
264
|
+
Pipeline: figma.getImageByHash("{ref.figmaImageHash}") → upload →
|
|
265
|
+
paint.image.src
|
|
266
|
+
</div>
|
|
267
|
+
</div>
|
|
268
|
+
))}
|
|
269
|
+
</div>
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function SvgRefsTab({ result }: { result: ConversionResult }) {
|
|
274
|
+
if (result.svgRefs.length === 0) return <Empty text="Нет векторов" />;
|
|
275
|
+
return (
|
|
276
|
+
<div style={{ display: "flex", flexDirection: "column", gap: "8px" }}>
|
|
277
|
+
{result.svgRefs.map((ref, i) => (
|
|
278
|
+
<div key={i} style={styles.card}>
|
|
279
|
+
<div style={styles.cardTitle}>SVG #{i + 1}</div>
|
|
280
|
+
<Row label="Figma nodeId" value={ref.figmaNodeId} />
|
|
281
|
+
<Row label="Fragment nodeId" value={ref.nodeId} />
|
|
282
|
+
<div style={styles.pipelineNote}>
|
|
283
|
+
Pipeline: node.exportAsync(SVG) → upload .svg → paint.image.src
|
|
284
|
+
</div>
|
|
285
|
+
</div>
|
|
286
|
+
))}
|
|
287
|
+
</div>
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function CssChunksTab({ result }: { result: ConversionResult }) {
|
|
292
|
+
if (result.cssChunks.length === 0) return <Empty text="Нет CSS chunks" />;
|
|
293
|
+
return (
|
|
294
|
+
<div style={{ display: "flex", flexDirection: "column", gap: "8px" }}>
|
|
295
|
+
{result.cssChunks.map((chunk, i) => (
|
|
296
|
+
<div key={i} style={styles.card}>
|
|
297
|
+
<div style={styles.cardTitle}>{chunk.name}</div>
|
|
298
|
+
<pre style={styles.pre}>{chunk.content}</pre>
|
|
299
|
+
</div>
|
|
300
|
+
))}
|
|
301
|
+
</div>
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function WarningsTab({ result }: { result: ConversionResult }) {
|
|
306
|
+
if (result.warnings.length === 0) return <Empty text="Нет предупреждений" />;
|
|
307
|
+
return (
|
|
308
|
+
<div style={{ display: "flex", flexDirection: "column", gap: "4px" }}>
|
|
309
|
+
{result.warnings.map((w, i) => (
|
|
310
|
+
<div key={i} style={styles.warning}>
|
|
311
|
+
<strong>
|
|
312
|
+
{w.nodeType}:{w.nodeId}
|
|
313
|
+
</strong>{" "}
|
|
314
|
+
— {w.field}: {w.message}
|
|
315
|
+
</div>
|
|
316
|
+
))}
|
|
317
|
+
</div>
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function JsonView({ data }: { data: any }) {
|
|
322
|
+
return <pre style={styles.pre}>{JSON.stringify(data, null, 2)}</pre>;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function Section({
|
|
326
|
+
title,
|
|
327
|
+
children,
|
|
328
|
+
}: {
|
|
329
|
+
title: string;
|
|
330
|
+
children: React.ReactNode;
|
|
331
|
+
}) {
|
|
332
|
+
return (
|
|
333
|
+
<div style={styles.section}>
|
|
334
|
+
<div style={styles.sectionTitle}>{title}</div>
|
|
335
|
+
{children}
|
|
336
|
+
</div>
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function Row({ label, value }: { label: string; value: any }) {
|
|
341
|
+
return (
|
|
342
|
+
<div style={styles.row}>
|
|
343
|
+
<span style={styles.rowLabel}>{label}</span>
|
|
344
|
+
<span style={styles.rowValue}>{String(value)}</span>
|
|
345
|
+
</div>
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function Empty({ text }: { text: string }) {
|
|
350
|
+
return <div style={styles.placeholder}>{text}</div>;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
const styles: Record<string, React.CSSProperties> = {
|
|
354
|
+
root: {
|
|
355
|
+
display: "flex",
|
|
356
|
+
flexDirection: "column",
|
|
357
|
+
height: "100vh",
|
|
358
|
+
fontFamily: "system-ui, sans-serif",
|
|
359
|
+
margin: 0,
|
|
360
|
+
background: "#f8f9fa",
|
|
361
|
+
},
|
|
362
|
+
header: {
|
|
363
|
+
background: "#1a1a2e",
|
|
364
|
+
color: "#fff",
|
|
365
|
+
padding: "12px 20px",
|
|
366
|
+
fontSize: "16px",
|
|
367
|
+
fontWeight: 600,
|
|
368
|
+
flexShrink: 0,
|
|
369
|
+
},
|
|
370
|
+
headerStats: {
|
|
371
|
+
fontWeight: 400,
|
|
372
|
+
fontSize: "13px",
|
|
373
|
+
opacity: 0.7,
|
|
374
|
+
},
|
|
375
|
+
main: {
|
|
376
|
+
display: "flex",
|
|
377
|
+
flex: 1,
|
|
378
|
+
overflow: "hidden",
|
|
379
|
+
},
|
|
380
|
+
left: {
|
|
381
|
+
width: "40%",
|
|
382
|
+
display: "flex",
|
|
383
|
+
flexDirection: "column",
|
|
384
|
+
padding: "12px",
|
|
385
|
+
gap: "8px",
|
|
386
|
+
borderRight: "1px solid #ddd",
|
|
387
|
+
},
|
|
388
|
+
textarea: {
|
|
389
|
+
flex: 1,
|
|
390
|
+
minHeight: "200px",
|
|
391
|
+
fontFamily: "monospace",
|
|
392
|
+
fontSize: "12px",
|
|
393
|
+
padding: "8px",
|
|
394
|
+
border: "1px solid #ccc",
|
|
395
|
+
borderRadius: "6px",
|
|
396
|
+
resize: "none",
|
|
397
|
+
background: "#fff",
|
|
398
|
+
},
|
|
399
|
+
buttons: {
|
|
400
|
+
display: "flex",
|
|
401
|
+
gap: "8px",
|
|
402
|
+
},
|
|
403
|
+
button: {
|
|
404
|
+
background: "#1a1a2e",
|
|
405
|
+
color: "#fff",
|
|
406
|
+
border: "none",
|
|
407
|
+
borderRadius: "6px",
|
|
408
|
+
padding: "10px 16px",
|
|
409
|
+
cursor: "pointer",
|
|
410
|
+
fontSize: "13px",
|
|
411
|
+
fontWeight: 600,
|
|
412
|
+
flex: 1,
|
|
413
|
+
},
|
|
414
|
+
error: {
|
|
415
|
+
background: "#fee",
|
|
416
|
+
color: "#c00",
|
|
417
|
+
padding: "8px",
|
|
418
|
+
borderRadius: "6px",
|
|
419
|
+
fontSize: "13px",
|
|
420
|
+
},
|
|
421
|
+
right: {
|
|
422
|
+
width: "60%",
|
|
423
|
+
background: "#fff",
|
|
424
|
+
overflow: "auto",
|
|
425
|
+
display: "flex",
|
|
426
|
+
flexDirection: "column",
|
|
427
|
+
},
|
|
428
|
+
tabs: {
|
|
429
|
+
display: "flex",
|
|
430
|
+
borderBottom: "1px solid #ddd",
|
|
431
|
+
flexShrink: 0,
|
|
432
|
+
overflowX: "auto",
|
|
433
|
+
},
|
|
434
|
+
tab: {
|
|
435
|
+
padding: "8px 14px",
|
|
436
|
+
fontSize: "12px",
|
|
437
|
+
fontWeight: 500,
|
|
438
|
+
border: "none",
|
|
439
|
+
background: "transparent",
|
|
440
|
+
cursor: "pointer",
|
|
441
|
+
color: "#666",
|
|
442
|
+
borderBottom: "2px solid transparent",
|
|
443
|
+
whiteSpace: "nowrap",
|
|
444
|
+
},
|
|
445
|
+
tabActive: {
|
|
446
|
+
padding: "8px 14px",
|
|
447
|
+
fontSize: "12px",
|
|
448
|
+
fontWeight: 600,
|
|
449
|
+
border: "none",
|
|
450
|
+
background: "transparent",
|
|
451
|
+
cursor: "pointer",
|
|
452
|
+
color: "#1a1a2e",
|
|
453
|
+
borderBottom: "2px solid #1a1a2e",
|
|
454
|
+
whiteSpace: "nowrap",
|
|
455
|
+
},
|
|
456
|
+
tabContent: {
|
|
457
|
+
flex: 1,
|
|
458
|
+
overflow: "auto",
|
|
459
|
+
padding: "12px",
|
|
460
|
+
},
|
|
461
|
+
tabContentRender: {
|
|
462
|
+
flex: 1,
|
|
463
|
+
overflow: "auto",
|
|
464
|
+
position: "relative",
|
|
465
|
+
},
|
|
466
|
+
section: {
|
|
467
|
+
background: "#f5f5f5",
|
|
468
|
+
borderRadius: "8px",
|
|
469
|
+
padding: "12px",
|
|
470
|
+
},
|
|
471
|
+
sectionTitle: {
|
|
472
|
+
fontSize: "13px",
|
|
473
|
+
fontWeight: 600,
|
|
474
|
+
marginBottom: "8px",
|
|
475
|
+
color: "#333",
|
|
476
|
+
},
|
|
477
|
+
row: {
|
|
478
|
+
display: "flex",
|
|
479
|
+
justifyContent: "space-between",
|
|
480
|
+
padding: "3px 0",
|
|
481
|
+
fontSize: "12px",
|
|
482
|
+
},
|
|
483
|
+
rowLabel: {
|
|
484
|
+
color: "#666",
|
|
485
|
+
},
|
|
486
|
+
rowValue: {
|
|
487
|
+
fontFamily: "monospace",
|
|
488
|
+
color: "#333",
|
|
489
|
+
fontWeight: 500,
|
|
490
|
+
},
|
|
491
|
+
card: {
|
|
492
|
+
background: "#f9f9f9",
|
|
493
|
+
border: "1px solid #eee",
|
|
494
|
+
borderRadius: "8px",
|
|
495
|
+
padding: "12px",
|
|
496
|
+
},
|
|
497
|
+
cardTitle: {
|
|
498
|
+
fontSize: "13px",
|
|
499
|
+
fontWeight: 600,
|
|
500
|
+
marginBottom: "6px",
|
|
501
|
+
},
|
|
502
|
+
pipelineNote: {
|
|
503
|
+
marginTop: "8px",
|
|
504
|
+
padding: "6px 8px",
|
|
505
|
+
background: "#eef6ff",
|
|
506
|
+
borderRadius: "4px",
|
|
507
|
+
fontSize: "11px",
|
|
508
|
+
fontFamily: "monospace",
|
|
509
|
+
color: "#1565c0",
|
|
510
|
+
},
|
|
511
|
+
pre: {
|
|
512
|
+
margin: 0,
|
|
513
|
+
fontSize: "11px",
|
|
514
|
+
fontFamily: "monospace",
|
|
515
|
+
whiteSpace: "pre-wrap",
|
|
516
|
+
wordBreak: "break-all",
|
|
517
|
+
lineHeight: 1.5,
|
|
518
|
+
},
|
|
519
|
+
warning: {
|
|
520
|
+
background: "#fff8e1",
|
|
521
|
+
padding: "6px 8px",
|
|
522
|
+
borderRadius: "4px",
|
|
523
|
+
fontSize: "12px",
|
|
524
|
+
borderLeft: "3px solid #f9a825",
|
|
525
|
+
},
|
|
526
|
+
placeholder: {
|
|
527
|
+
display: "flex",
|
|
528
|
+
alignItems: "center",
|
|
529
|
+
justifyContent: "center",
|
|
530
|
+
height: "100%",
|
|
531
|
+
color: "#999",
|
|
532
|
+
fontSize: "14px",
|
|
533
|
+
},
|
|
534
|
+
};
|