@zseven-w/pen-codegen 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/README.md +55 -0
- package/package.json +25 -0
- package/src/__tests__/codegen.test.ts +89 -0
- package/src/compose-generator.ts +807 -0
- package/src/css-variables-generator.ts +138 -0
- package/src/flutter-generator.ts +581 -0
- package/src/html-generator.ts +403 -0
- package/src/index.ts +26 -0
- package/src/react-generator.ts +401 -0
- package/src/react-native-generator.ts +569 -0
- package/src/svelte-generator.ts +296 -0
- package/src/swiftui-generator.ts +754 -0
- package/src/vue-generator.ts +308 -0
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
import type { PenDocument, PenNode, ContainerProps, TextNode } from '@zseven-w/pen-types'
|
|
2
|
+
import { getActivePageChildren } from '@zseven-w/pen-core'
|
|
3
|
+
import type { PenFill, PenStroke, PenEffect, ShadowEffect } from '@zseven-w/pen-types'
|
|
4
|
+
import { isVariableRef } from '@zseven-w/pen-core'
|
|
5
|
+
import { variableNameToCSS, generateCSSVariables } from './css-variables-generator.js'
|
|
6
|
+
import { buildEllipseArcPath, isArcEllipse } from '@zseven-w/pen-core'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Converts PenDocument nodes to HTML + CSS.
|
|
10
|
+
* $variable references are output as var(--name) CSS custom properties.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
function varOrLiteral(value: string): string {
|
|
14
|
+
if (isVariableRef(value)) {
|
|
15
|
+
return `var(${variableNameToCSS(value.slice(1))})`
|
|
16
|
+
}
|
|
17
|
+
return value
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
let classCounter = 0
|
|
21
|
+
|
|
22
|
+
function resetClassCounter() {
|
|
23
|
+
classCounter = 0
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function nextClassName(prefix: string): string {
|
|
27
|
+
classCounter++
|
|
28
|
+
return `${prefix}-${classCounter}`
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function indent(depth: number): string {
|
|
32
|
+
return ' '.repeat(depth)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function fillToCSS(fills: PenFill[] | undefined): Record<string, string> {
|
|
36
|
+
if (!fills || fills.length === 0) return {}
|
|
37
|
+
const fill = fills[0]
|
|
38
|
+
if (fill.type === 'solid') {
|
|
39
|
+
return { background: varOrLiteral(fill.color) }
|
|
40
|
+
}
|
|
41
|
+
if (fill.type === 'linear_gradient') {
|
|
42
|
+
if (!fill.stops?.length) return {}
|
|
43
|
+
const angle = fill.angle ?? 180
|
|
44
|
+
const stops = fill.stops.map((s) => `${varOrLiteral(s.color)} ${Math.round(s.offset * 100)}%`).join(', ')
|
|
45
|
+
return { background: `linear-gradient(${angle}deg, ${stops})` }
|
|
46
|
+
}
|
|
47
|
+
if (fill.type === 'radial_gradient') {
|
|
48
|
+
if (!fill.stops?.length) return {}
|
|
49
|
+
const stops = fill.stops.map((s) => `${varOrLiteral(s.color)} ${Math.round(s.offset * 100)}%`).join(', ')
|
|
50
|
+
return { background: `radial-gradient(circle, ${stops})` }
|
|
51
|
+
}
|
|
52
|
+
return {}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function strokeToCSS(stroke: PenStroke | undefined): Record<string, string> {
|
|
56
|
+
if (!stroke) return {}
|
|
57
|
+
const css: Record<string, string> = {}
|
|
58
|
+
if (typeof stroke.thickness === 'string' && isVariableRef(stroke.thickness)) {
|
|
59
|
+
css['border-width'] = varOrLiteral(stroke.thickness)
|
|
60
|
+
} else {
|
|
61
|
+
const thickness = typeof stroke.thickness === 'number'
|
|
62
|
+
? stroke.thickness
|
|
63
|
+
: stroke.thickness[0]
|
|
64
|
+
css['border-width'] = `${thickness}px`
|
|
65
|
+
}
|
|
66
|
+
css['border-style'] = 'solid'
|
|
67
|
+
if (stroke.fill && stroke.fill.length > 0) {
|
|
68
|
+
const sf = stroke.fill[0]
|
|
69
|
+
if (sf.type === 'solid') {
|
|
70
|
+
css['border-color'] = varOrLiteral(sf.color)
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return css
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function effectsToCSS(effects: PenEffect[] | undefined): Record<string, string> {
|
|
77
|
+
if (!effects || effects.length === 0) return {}
|
|
78
|
+
const shadows: string[] = []
|
|
79
|
+
for (const effect of effects) {
|
|
80
|
+
if (effect.type === 'shadow') {
|
|
81
|
+
const s = effect as ShadowEffect
|
|
82
|
+
const inset = s.inner ? 'inset ' : ''
|
|
83
|
+
shadows.push(`${inset}${s.offsetX}px ${s.offsetY}px ${s.blur}px ${s.spread}px ${s.color}`)
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
if (shadows.length > 0) {
|
|
87
|
+
return { 'box-shadow': shadows.join(', ') }
|
|
88
|
+
}
|
|
89
|
+
return {}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function cornerRadiusToCSS(
|
|
93
|
+
cr: number | [number, number, number, number] | undefined,
|
|
94
|
+
): Record<string, string> {
|
|
95
|
+
if (cr === undefined) return {}
|
|
96
|
+
if (typeof cr === 'number') {
|
|
97
|
+
return cr === 0 ? {} : { 'border-radius': `${cr}px` }
|
|
98
|
+
}
|
|
99
|
+
return { 'border-radius': `${cr[0]}px ${cr[1]}px ${cr[2]}px ${cr[3]}px` }
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function layoutToCSS(node: ContainerProps): Record<string, string> {
|
|
103
|
+
const css: Record<string, string> = {}
|
|
104
|
+
if (node.layout === 'vertical') {
|
|
105
|
+
css.display = 'flex'
|
|
106
|
+
css['flex-direction'] = 'column'
|
|
107
|
+
} else if (node.layout === 'horizontal') {
|
|
108
|
+
css.display = 'flex'
|
|
109
|
+
css['flex-direction'] = 'row'
|
|
110
|
+
}
|
|
111
|
+
if (node.gap !== undefined) {
|
|
112
|
+
if (typeof node.gap === 'string' && isVariableRef(node.gap)) {
|
|
113
|
+
css.gap = varOrLiteral(node.gap)
|
|
114
|
+
} else if (typeof node.gap === 'number') {
|
|
115
|
+
css.gap = `${node.gap}px`
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (node.padding !== undefined) {
|
|
119
|
+
if (typeof node.padding === 'string' && isVariableRef(node.padding)) {
|
|
120
|
+
css.padding = varOrLiteral(node.padding)
|
|
121
|
+
} else if (typeof node.padding === 'number') {
|
|
122
|
+
css.padding = `${node.padding}px`
|
|
123
|
+
} else if (Array.isArray(node.padding)) {
|
|
124
|
+
css.padding = node.padding.map((p) => `${p}px`).join(' ')
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
if (node.justifyContent) {
|
|
128
|
+
const map: Record<string, string> = {
|
|
129
|
+
start: 'flex-start',
|
|
130
|
+
center: 'center',
|
|
131
|
+
end: 'flex-end',
|
|
132
|
+
space_between: 'space-between',
|
|
133
|
+
space_around: 'space-around',
|
|
134
|
+
}
|
|
135
|
+
css['justify-content'] = map[node.justifyContent] ?? node.justifyContent
|
|
136
|
+
}
|
|
137
|
+
if (node.alignItems) {
|
|
138
|
+
const map: Record<string, string> = {
|
|
139
|
+
start: 'flex-start',
|
|
140
|
+
center: 'center',
|
|
141
|
+
end: 'flex-end',
|
|
142
|
+
}
|
|
143
|
+
css['align-items'] = map[node.alignItems] ?? node.alignItems
|
|
144
|
+
}
|
|
145
|
+
if (node.clipContent) {
|
|
146
|
+
css.overflow = 'hidden'
|
|
147
|
+
}
|
|
148
|
+
return css
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
interface CSSRule {
|
|
152
|
+
className: string
|
|
153
|
+
properties: Record<string, string>
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function getTextContent(node: TextNode): string {
|
|
157
|
+
if (typeof node.content === 'string') return node.content
|
|
158
|
+
return node.content.map((s) => s.text).join('')
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function escapeHTML(text: string): string {
|
|
162
|
+
return text
|
|
163
|
+
.replace(/&/g, '&')
|
|
164
|
+
.replace(/</g, '<')
|
|
165
|
+
.replace(/>/g, '>')
|
|
166
|
+
.replace(/"/g, '"')
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function generateNodeHTML(
|
|
170
|
+
node: PenNode,
|
|
171
|
+
depth: number,
|
|
172
|
+
rules: CSSRule[],
|
|
173
|
+
): string {
|
|
174
|
+
const pad = indent(depth)
|
|
175
|
+
const css: Record<string, string> = {}
|
|
176
|
+
|
|
177
|
+
// Position
|
|
178
|
+
if (node.x !== undefined || node.y !== undefined) {
|
|
179
|
+
css.position = 'absolute'
|
|
180
|
+
if (node.x !== undefined) css.left = `${node.x}px`
|
|
181
|
+
if (node.y !== undefined) css.top = `${node.y}px`
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Opacity
|
|
185
|
+
if (node.opacity !== undefined && node.opacity !== 1) {
|
|
186
|
+
if (typeof node.opacity === 'string' && isVariableRef(node.opacity)) {
|
|
187
|
+
css.opacity = varOrLiteral(node.opacity)
|
|
188
|
+
} else if (typeof node.opacity === 'number') {
|
|
189
|
+
css.opacity = String(node.opacity)
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Rotation
|
|
194
|
+
if (node.rotation) {
|
|
195
|
+
css.transform = `rotate(${node.rotation}deg)`
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
switch (node.type) {
|
|
199
|
+
case 'frame':
|
|
200
|
+
case 'rectangle':
|
|
201
|
+
case 'group': {
|
|
202
|
+
if (typeof node.width === 'number') css.width = `${node.width}px`
|
|
203
|
+
if (typeof node.height === 'number') css.height = `${node.height}px`
|
|
204
|
+
Object.assign(css, fillToCSS(node.fill))
|
|
205
|
+
Object.assign(css, strokeToCSS(node.stroke))
|
|
206
|
+
Object.assign(css, cornerRadiusToCSS(node.cornerRadius))
|
|
207
|
+
Object.assign(css, effectsToCSS(node.effects))
|
|
208
|
+
Object.assign(css, layoutToCSS(node))
|
|
209
|
+
|
|
210
|
+
const className = nextClassName(node.name?.replace(/\s+/g, '-').toLowerCase() ?? node.type)
|
|
211
|
+
rules.push({ className, properties: css })
|
|
212
|
+
|
|
213
|
+
const children = node.children ?? []
|
|
214
|
+
if (children.length === 0) {
|
|
215
|
+
return `${pad}<div class="${className}"></div>`
|
|
216
|
+
}
|
|
217
|
+
const childrenHTML = children
|
|
218
|
+
.map((c) => generateNodeHTML(c, depth + 1, rules))
|
|
219
|
+
.join('\n')
|
|
220
|
+
return `${pad}<div class="${className}">\n${childrenHTML}\n${pad}</div>`
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
case 'ellipse': {
|
|
224
|
+
if (isArcEllipse(node.startAngle, node.sweepAngle, node.innerRadius)) {
|
|
225
|
+
const w = typeof node.width === 'number' ? node.width : 100
|
|
226
|
+
const h = typeof node.height === 'number' ? node.height : 100
|
|
227
|
+
const d = buildEllipseArcPath(w, h, node.startAngle ?? 0, node.sweepAngle ?? 360, node.innerRadius ?? 0)
|
|
228
|
+
const fill = node.fill?.[0]?.type === 'solid' ? varOrLiteral(node.fill[0].color) : '#000'
|
|
229
|
+
Object.assign(css, effectsToCSS(node.effects))
|
|
230
|
+
const className = nextClassName(node.name?.replace(/\s+/g, '-').toLowerCase() ?? 'arc')
|
|
231
|
+
rules.push({ className, properties: css })
|
|
232
|
+
return `${pad}<svg class="${className}" width="${w}" height="${h}" viewBox="0 0 ${w} ${h}"><path d="${d}" fill="${fill}" /></svg>`
|
|
233
|
+
}
|
|
234
|
+
if (typeof node.width === 'number') css.width = `${node.width}px`
|
|
235
|
+
if (typeof node.height === 'number') css.height = `${node.height}px`
|
|
236
|
+
css['border-radius'] = '50%'
|
|
237
|
+
Object.assign(css, fillToCSS(node.fill))
|
|
238
|
+
Object.assign(css, strokeToCSS(node.stroke))
|
|
239
|
+
Object.assign(css, effectsToCSS(node.effects))
|
|
240
|
+
|
|
241
|
+
const className = nextClassName(node.name?.replace(/\s+/g, '-').toLowerCase() ?? 'ellipse')
|
|
242
|
+
rules.push({ className, properties: css })
|
|
243
|
+
return `${pad}<div class="${className}"></div>`
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
case 'text': {
|
|
247
|
+
if (typeof node.width === 'number') css.width = `${node.width}px`
|
|
248
|
+
if (typeof node.height === 'number') css.height = `${node.height}px`
|
|
249
|
+
if (node.fill) {
|
|
250
|
+
const fill = node.fill[0]
|
|
251
|
+
if (fill?.type === 'solid') css.color = varOrLiteral(fill.color)
|
|
252
|
+
}
|
|
253
|
+
if (node.fontSize) css['font-size'] = `${node.fontSize}px`
|
|
254
|
+
if (node.fontWeight) css['font-weight'] = String(node.fontWeight)
|
|
255
|
+
if (node.fontStyle === 'italic') css['font-style'] = 'italic'
|
|
256
|
+
if (node.textAlign) css['text-align'] = node.textAlign
|
|
257
|
+
if (node.fontFamily) css['font-family'] = `'${node.fontFamily}', sans-serif`
|
|
258
|
+
if (node.lineHeight) css['line-height'] = String(node.lineHeight)
|
|
259
|
+
if (node.letterSpacing) css['letter-spacing'] = `${node.letterSpacing}px`
|
|
260
|
+
if (node.textAlignVertical === 'middle') css['vertical-align'] = 'middle'
|
|
261
|
+
else if (node.textAlignVertical === 'bottom') css['vertical-align'] = 'bottom'
|
|
262
|
+
if (node.textGrowth === 'auto') css['white-space'] = 'nowrap'
|
|
263
|
+
else if (node.textGrowth === 'fixed-width-height') css.overflow = 'hidden'
|
|
264
|
+
if (node.underline) css['text-decoration'] = 'underline'
|
|
265
|
+
if (node.strikethrough) css['text-decoration'] = 'line-through'
|
|
266
|
+
Object.assign(css, effectsToCSS(node.effects))
|
|
267
|
+
|
|
268
|
+
const className = nextClassName(node.name?.replace(/\s+/g, '-').toLowerCase() ?? 'text')
|
|
269
|
+
rules.push({ className, properties: css })
|
|
270
|
+
|
|
271
|
+
const size = node.fontSize ?? 16
|
|
272
|
+
const tag = size >= 32 ? 'h1' : size >= 24 ? 'h2' : size >= 20 ? 'h3' : 'p'
|
|
273
|
+
const text = escapeHTML(getTextContent(node))
|
|
274
|
+
return `${pad}<${tag} class="${className}">${text}</${tag}>`
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
case 'line': {
|
|
278
|
+
const w = node.x2 !== undefined ? Math.abs(node.x2 - (node.x ?? 0)) : 0
|
|
279
|
+
css.width = `${w}px`
|
|
280
|
+
if (node.stroke) {
|
|
281
|
+
const thickness = typeof node.stroke.thickness === 'number'
|
|
282
|
+
? node.stroke.thickness
|
|
283
|
+
: node.stroke.thickness[0]
|
|
284
|
+
css['border-top-width'] = `${thickness}px`
|
|
285
|
+
css['border-top-style'] = 'solid'
|
|
286
|
+
if (node.stroke.fill && node.stroke.fill.length > 0) {
|
|
287
|
+
const sf = node.stroke.fill[0]
|
|
288
|
+
if (sf.type === 'solid') css['border-top-color'] = varOrLiteral(sf.color)
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
const className = nextClassName(node.name?.replace(/\s+/g, '-').toLowerCase() ?? 'line')
|
|
292
|
+
rules.push({ className, properties: css })
|
|
293
|
+
return `${pad}<hr class="${className}" />`
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
case 'polygon':
|
|
297
|
+
case 'path': {
|
|
298
|
+
if (typeof node.width === 'number') css.width = `${node.width}px`
|
|
299
|
+
if (typeof node.height === 'number') css.height = `${node.height}px`
|
|
300
|
+
Object.assign(css, fillToCSS(node.fill))
|
|
301
|
+
const className = nextClassName(node.name?.replace(/\s+/g, '-').toLowerCase() ?? node.type)
|
|
302
|
+
rules.push({ className, properties: css })
|
|
303
|
+
if (node.type === 'path') {
|
|
304
|
+
const w = typeof node.width === 'number' ? node.width : 100
|
|
305
|
+
const h = typeof node.height === 'number' ? node.height : 100
|
|
306
|
+
const fillColor = node.fill?.[0]?.type === 'solid' ? varOrLiteral(node.fill[0].color) : 'currentColor'
|
|
307
|
+
return `${pad}<svg class="${className}" viewBox="0 0 ${w} ${h}">\n${pad} <path d="${node.d}" fill="${fillColor}" />\n${pad}</svg>`
|
|
308
|
+
}
|
|
309
|
+
return `${pad}<div class="${className}"></div>`
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
case 'image': {
|
|
313
|
+
if (typeof node.width === 'number') css.width = `${node.width}px`
|
|
314
|
+
if (typeof node.height === 'number') css.height = `${node.height}px`
|
|
315
|
+
const fit = node.objectFit === 'fit' ? 'contain' : node.objectFit === 'crop' ? 'cover' : 'fill'
|
|
316
|
+
css['object-fit'] = fit
|
|
317
|
+
Object.assign(css, cornerRadiusToCSS(node.cornerRadius))
|
|
318
|
+
Object.assign(css, effectsToCSS(node.effects))
|
|
319
|
+
const className = nextClassName(node.name?.replace(/\s+/g, '-').toLowerCase() ?? 'image')
|
|
320
|
+
rules.push({ className, properties: css })
|
|
321
|
+
return `${pad}<img class="${className}" src="${node.src}" alt="${escapeHTML(node.name ?? 'image')}" />`
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
case 'icon_font': {
|
|
325
|
+
const size = typeof node.width === 'number' ? node.width : 24
|
|
326
|
+
css.width = `${size}px`
|
|
327
|
+
css.height = `${size}px`
|
|
328
|
+
if (node.fill?.[0]?.type === 'solid') css.color = varOrLiteral(node.fill[0].color)
|
|
329
|
+
const className = nextClassName(node.name?.replace(/\s+/g, '-').toLowerCase() ?? 'icon')
|
|
330
|
+
rules.push({ className, properties: css })
|
|
331
|
+
return `${pad}<i class="${className}" data-lucide="${escapeHTML(node.iconFontName ?? 'circle')}"></i>`
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
case 'ref':
|
|
335
|
+
return `${pad}<!-- Ref: ${node.ref} -->`
|
|
336
|
+
|
|
337
|
+
default:
|
|
338
|
+
return `${pad}<!-- Unknown node -->`
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function cssRulesToString(rules: CSSRule[]): string {
|
|
343
|
+
return rules
|
|
344
|
+
.map((r) => {
|
|
345
|
+
const props = Object.entries(r.properties)
|
|
346
|
+
.map(([k, v]) => ` ${k}: ${v};`)
|
|
347
|
+
.join('\n')
|
|
348
|
+
return `.${r.className} {\n${props}\n}`
|
|
349
|
+
})
|
|
350
|
+
.join('\n\n')
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
export function generateHTMLCode(nodes: PenNode[]): { html: string; css: string } {
|
|
354
|
+
resetClassCounter()
|
|
355
|
+
const rules: CSSRule[] = []
|
|
356
|
+
|
|
357
|
+
if (nodes.length === 0) {
|
|
358
|
+
return {
|
|
359
|
+
html: '<div class="container"></div>',
|
|
360
|
+
css: '.container {\n position: relative;\n}',
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// Compute wrapper size
|
|
365
|
+
let maxW = 0
|
|
366
|
+
let maxH = 0
|
|
367
|
+
for (const node of nodes) {
|
|
368
|
+
const x = node.x ?? 0
|
|
369
|
+
const y = node.y ?? 0
|
|
370
|
+
const w = 'width' in node && typeof node.width === 'number' ? node.width : 0
|
|
371
|
+
const h = 'height' in node && typeof node.height === 'number' ? node.height : 0
|
|
372
|
+
maxW = Math.max(maxW, x + w)
|
|
373
|
+
maxH = Math.max(maxH, y + h)
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
const containerCSS: Record<string, string> = { position: 'relative' }
|
|
377
|
+
if (maxW > 0) containerCSS.width = `${maxW}px`
|
|
378
|
+
if (maxH > 0) containerCSS.height = `${maxH}px`
|
|
379
|
+
rules.push({ className: 'container', properties: containerCSS })
|
|
380
|
+
|
|
381
|
+
const childrenHTML = nodes
|
|
382
|
+
.map((n) => generateNodeHTML(n, 1, rules))
|
|
383
|
+
.join('\n')
|
|
384
|
+
|
|
385
|
+
const html = `<div class="container">\n${childrenHTML}\n</div>`
|
|
386
|
+
const css = cssRulesToString(rules)
|
|
387
|
+
|
|
388
|
+
return { html, css }
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
export function generateHTMLFromDocument(doc: PenDocument, activePageId?: string | null): { html: string; css: string } {
|
|
392
|
+
const children = activePageId !== undefined
|
|
393
|
+
? getActivePageChildren(doc, activePageId)
|
|
394
|
+
: doc.children
|
|
395
|
+
const result = generateHTMLCode(children)
|
|
396
|
+
const varsCSS = doc.variables && Object.keys(doc.variables).length > 0
|
|
397
|
+
? generateCSSVariables(doc)
|
|
398
|
+
: ''
|
|
399
|
+
return {
|
|
400
|
+
html: result.html,
|
|
401
|
+
css: varsCSS ? `${varsCSS}\n${result.css}` : result.css,
|
|
402
|
+
}
|
|
403
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// CSS Variables
|
|
2
|
+
export { variableNameToCSS, generateCSSVariables } from './css-variables-generator.js'
|
|
3
|
+
|
|
4
|
+
// React + Tailwind
|
|
5
|
+
export { generateReactCode, generateReactFromDocument } from './react-generator.js'
|
|
6
|
+
|
|
7
|
+
// HTML + CSS
|
|
8
|
+
export { generateHTMLCode, generateHTMLFromDocument } from './html-generator.js'
|
|
9
|
+
|
|
10
|
+
// Vue 3
|
|
11
|
+
export { generateVueCode, generateVueFromDocument } from './vue-generator.js'
|
|
12
|
+
|
|
13
|
+
// Svelte
|
|
14
|
+
export { generateSvelteCode, generateSvelteFromDocument } from './svelte-generator.js'
|
|
15
|
+
|
|
16
|
+
// Flutter / Dart
|
|
17
|
+
export { generateFlutterCode, generateFlutterFromDocument } from './flutter-generator.js'
|
|
18
|
+
|
|
19
|
+
// SwiftUI
|
|
20
|
+
export { generateSwiftUICode, generateSwiftUIFromDocument } from './swiftui-generator.js'
|
|
21
|
+
|
|
22
|
+
// Android Jetpack Compose
|
|
23
|
+
export { generateComposeCode, generateComposeFromDocument } from './compose-generator.js'
|
|
24
|
+
|
|
25
|
+
// React Native
|
|
26
|
+
export { generateReactNativeCode, generateReactNativeFromDocument } from './react-native-generator.js'
|