@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.
@@ -0,0 +1,569 @@
1
+ import type { PenDocument, PenNode, ContainerProps, TextNode, ImageNode, LineNode, PathNode, PolygonNode } 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 } from './css-variables-generator.js'
6
+
7
+ /**
8
+ * Converts PenDocument nodes to React Native code with inline styles.
9
+ * $variable references are output as /* var(--name) *​/ comments.
10
+ */
11
+
12
+ /** Convert a `$variable` ref to a comment placeholder, or return the raw value. */
13
+ function varOrLiteral(value: string): string {
14
+ if (isVariableRef(value)) {
15
+ return `var(${variableNameToCSS(value.slice(1))})`
16
+ }
17
+ return value
18
+ }
19
+
20
+ function indent(depth: number): string {
21
+ return ' '.repeat(depth)
22
+ }
23
+
24
+ function kebabToPascal(name: string): string {
25
+ return name.split('-').map(s => s.charAt(0).toUpperCase() + s.slice(1)).join('')
26
+ }
27
+
28
+ /** Return a hex color string, or a comment for variable refs. */
29
+ function hexColor(value: string): string {
30
+ if (isVariableRef(value)) {
31
+ return `/* ${varOrLiteral(value)} */ '#000000'`
32
+ }
33
+ return `'${value}'`
34
+ }
35
+
36
+ /** Extract backgroundColor from fills. */
37
+ function fillToStyle(fills: PenFill[] | undefined): Record<string, string> {
38
+ if (!fills || fills.length === 0) return {}
39
+ const fill = fills[0]
40
+ if (fill.type === 'solid') {
41
+ return { backgroundColor: hexColor(fill.color) }
42
+ }
43
+ return {}
44
+ }
45
+
46
+ /** Extract text color from fills. */
47
+ function fillToTextColor(fills: PenFill[] | undefined): Record<string, string> {
48
+ if (!fills || fills.length === 0) return {}
49
+ const fill = fills[0]
50
+ if (fill.type === 'solid') {
51
+ return { color: hexColor(fill.color) }
52
+ }
53
+ return {}
54
+ }
55
+
56
+ /** Extract border styles from stroke. */
57
+ function strokeToStyle(stroke: PenStroke | undefined): Record<string, string> {
58
+ if (!stroke) return {}
59
+ const styles: Record<string, string> = {}
60
+ if (typeof stroke.thickness === 'string' && isVariableRef(stroke.thickness)) {
61
+ styles.borderWidth = `/* ${varOrLiteral(stroke.thickness)} */ 1`
62
+ } else {
63
+ const thickness = typeof stroke.thickness === 'number'
64
+ ? stroke.thickness
65
+ : stroke.thickness[0]
66
+ styles.borderWidth = String(thickness)
67
+ }
68
+ if (stroke.fill && stroke.fill.length > 0) {
69
+ const sf = stroke.fill[0]
70
+ if (sf.type === 'solid') {
71
+ styles.borderColor = hexColor(sf.color)
72
+ }
73
+ }
74
+ return styles
75
+ }
76
+
77
+ /** Extract shadow styles from effects. */
78
+ function effectsToStyle(effects: PenEffect[] | undefined): Record<string, string> {
79
+ if (!effects || effects.length === 0) return {}
80
+ const styles: Record<string, string> = {}
81
+ for (const effect of effects) {
82
+ if (effect.type === 'shadow') {
83
+ const s = effect as ShadowEffect
84
+ styles.shadowColor = `'${s.color}'`
85
+ styles.shadowOffset = `{ width: ${s.offsetX}, height: ${s.offsetY} }`
86
+ styles.shadowOpacity = '1'
87
+ styles.shadowRadius = String(s.blur)
88
+ styles.elevation = String(Math.max(1, Math.round(s.blur / 2)))
89
+ }
90
+ }
91
+ return styles
92
+ }
93
+
94
+ /** Extract borderRadius styles from corner radius. */
95
+ function cornerRadiusToStyle(
96
+ cr: number | [number, number, number, number] | undefined,
97
+ ): Record<string, string> {
98
+ if (cr === undefined) return {}
99
+ if (typeof cr === 'number') {
100
+ return cr === 0 ? {} : { borderRadius: String(cr) }
101
+ }
102
+ const [tl, tr, br, bl] = cr
103
+ if (tl === tr && tr === br && br === bl) {
104
+ return tl === 0 ? {} : { borderRadius: String(tl) }
105
+ }
106
+ return {
107
+ borderTopLeftRadius: String(tl),
108
+ borderTopRightRadius: String(tr),
109
+ borderBottomRightRadius: String(br),
110
+ borderBottomLeftRadius: String(bl),
111
+ }
112
+ }
113
+
114
+ /** Extract layout styles from container props. */
115
+ function layoutToStyle(node: ContainerProps): Record<string, string> {
116
+ const styles: Record<string, string> = {}
117
+ if (node.layout === 'vertical') {
118
+ styles.flexDirection = "'column'"
119
+ } else if (node.layout === 'horizontal') {
120
+ styles.flexDirection = "'row'"
121
+ }
122
+ if (node.gap !== undefined) {
123
+ if (typeof node.gap === 'string' && isVariableRef(node.gap)) {
124
+ styles.gap = `/* ${varOrLiteral(node.gap)} */ 0`
125
+ } else if (typeof node.gap === 'number' && node.gap > 0) {
126
+ styles.gap = String(node.gap)
127
+ }
128
+ }
129
+ if (node.padding !== undefined) {
130
+ if (typeof node.padding === 'string' && isVariableRef(node.padding)) {
131
+ styles.padding = `/* ${varOrLiteral(node.padding)} */ 0`
132
+ } else if (typeof node.padding === 'number') {
133
+ styles.padding = String(node.padding)
134
+ } else if (Array.isArray(node.padding)) {
135
+ if (node.padding.length === 2) {
136
+ styles.paddingVertical = String(node.padding[0])
137
+ styles.paddingHorizontal = String(node.padding[1])
138
+ } else if (node.padding.length === 4) {
139
+ styles.paddingTop = String(node.padding[0])
140
+ styles.paddingRight = String(node.padding[1])
141
+ styles.paddingBottom = String(node.padding[2])
142
+ styles.paddingLeft = String(node.padding[3])
143
+ }
144
+ }
145
+ }
146
+ if (node.justifyContent) {
147
+ const jcMap: Record<string, string> = {
148
+ start: "'flex-start'",
149
+ center: "'center'",
150
+ end: "'flex-end'",
151
+ space_between: "'space-between'",
152
+ space_around: "'space-around'",
153
+ }
154
+ if (jcMap[node.justifyContent]) styles.justifyContent = jcMap[node.justifyContent]
155
+ }
156
+ if (node.alignItems) {
157
+ const aiMap: Record<string, string> = {
158
+ start: "'flex-start'",
159
+ center: "'center'",
160
+ end: "'flex-end'",
161
+ }
162
+ if (aiMap[node.alignItems]) styles.alignItems = aiMap[node.alignItems]
163
+ }
164
+ if (node.clipContent) {
165
+ styles.overflow = "'hidden'"
166
+ }
167
+ return styles
168
+ }
169
+
170
+ /** Extract text-specific styles. */
171
+ function textToStyle(node: TextNode): Record<string, string> {
172
+ const styles: Record<string, string> = {}
173
+ if (node.fontSize) styles.fontSize = String(node.fontSize)
174
+ if (node.fontWeight) {
175
+ const w = typeof node.fontWeight === 'number' ? node.fontWeight : parseInt(node.fontWeight, 10)
176
+ if (!isNaN(w)) styles.fontWeight = `'${w}'`
177
+ }
178
+ if (node.fontStyle === 'italic') styles.fontStyle = "'italic'"
179
+ if (node.textAlign) {
180
+ const taMap: Record<string, string> = {
181
+ left: "'left'",
182
+ center: "'center'",
183
+ right: "'right'",
184
+ }
185
+ if (taMap[node.textAlign]) styles.textAlign = taMap[node.textAlign]
186
+ }
187
+ if (node.fontFamily) styles.fontFamily = `'${node.fontFamily}'`
188
+ if (node.letterSpacing) styles.letterSpacing = String(node.letterSpacing)
189
+ if (node.lineHeight && node.fontSize) {
190
+ styles.lineHeight = String(Math.round(node.fontSize * node.lineHeight))
191
+ }
192
+ if (node.underline && node.strikethrough) {
193
+ styles.textDecorationLine = "'underline line-through'"
194
+ } else if (node.underline) {
195
+ styles.textDecorationLine = "'underline'"
196
+ } else if (node.strikethrough) {
197
+ styles.textDecorationLine = "'line-through'"
198
+ }
199
+ return styles
200
+ }
201
+
202
+ /** Format a style object as an inline style string. */
203
+ function formatStyle(styles: Record<string, string>): string {
204
+ const entries = Object.entries(styles)
205
+ if (entries.length === 0) return '{}'
206
+ const parts = entries.map(([k, v]) => {
207
+ // Values that are already quoted, numeric, or contain special syntax
208
+ if (
209
+ v.startsWith("'") ||
210
+ v.startsWith('"') ||
211
+ v.startsWith('{') ||
212
+ v.startsWith('/*') ||
213
+ /^-?\d+(\.\d+)?$/.test(v)
214
+ ) {
215
+ return `${k}: ${v}`
216
+ }
217
+ return `${k}: ${v}`
218
+ })
219
+ return `{ ${parts.join(', ')} }`
220
+ }
221
+
222
+ function getTextContent(node: TextNode): string {
223
+ if (typeof node.content === 'string') return node.content
224
+ return node.content.map((s) => s.text).join('')
225
+ }
226
+
227
+ function escapeJSX(text: string): string {
228
+ return text
229
+ .replace(/&/g, '&amp;')
230
+ .replace(/</g, '&lt;')
231
+ .replace(/>/g, '&gt;')
232
+ .replace(/{/g, '&#123;')
233
+ .replace(/}/g, '&#125;')
234
+ }
235
+
236
+ /** Check if any node in the tree is a path or polygon. */
237
+ function hasSvgNodes(nodes: PenNode[]): boolean {
238
+ for (const node of nodes) {
239
+ if (node.type === 'path' || node.type === 'polygon') return true
240
+ if ('children' in node && node.children) {
241
+ if (hasSvgNodes(node.children)) return true
242
+ }
243
+ }
244
+ return false
245
+ }
246
+
247
+ /** Collect common position/opacity/rotation styles. */
248
+ function commonStyles(node: PenNode): Record<string, string> {
249
+ const styles: Record<string, string> = {}
250
+ if (node.x !== undefined || node.y !== undefined) {
251
+ styles.position = "'absolute'"
252
+ if (node.x !== undefined) styles.left = String(node.x)
253
+ if (node.y !== undefined) styles.top = String(node.y)
254
+ }
255
+ if (node.opacity !== undefined && node.opacity !== 1) {
256
+ if (typeof node.opacity === 'string' && isVariableRef(node.opacity)) {
257
+ styles.opacity = `/* ${varOrLiteral(node.opacity)} */ 1`
258
+ } else if (typeof node.opacity === 'number') {
259
+ styles.opacity = String(node.opacity)
260
+ }
261
+ }
262
+ if (node.rotation) {
263
+ styles.transform = `[{ rotate: '${node.rotation}deg' }]`
264
+ }
265
+ return styles
266
+ }
267
+
268
+ /** Main node renderer. Returns JSX string for a single node. */
269
+ function generateNodeRN(node: PenNode, depth: number): string {
270
+ const pad = indent(depth)
271
+
272
+ switch (node.type) {
273
+ case 'frame':
274
+ case 'rectangle':
275
+ case 'group': {
276
+ const styles: Record<string, string> = {
277
+ ...commonStyles(node),
278
+ }
279
+ if (typeof node.width === 'number') styles.width = String(node.width)
280
+ if (typeof node.height === 'number') styles.height = String(node.height)
281
+ Object.assign(styles, fillToStyle(node.fill))
282
+ Object.assign(styles, strokeToStyle(node.stroke))
283
+ Object.assign(styles, cornerRadiusToStyle(node.cornerRadius))
284
+ Object.assign(styles, effectsToStyle(node.effects))
285
+ Object.assign(styles, layoutToStyle(node))
286
+
287
+ const childNodes = node.children ?? []
288
+ const comment = node.name ? `${pad}{/* ${node.name} */}\n` : ''
289
+ if (childNodes.length === 0) {
290
+ return `${comment}${pad}<View style=${formatStyle(styles)} />`
291
+ }
292
+ const childrenJSX = childNodes
293
+ .map((c) => generateNodeRN(c, depth + 1))
294
+ .join('\n')
295
+ return `${comment}${pad}<View style=${formatStyle(styles)}>\n${childrenJSX}\n${pad}</View>`
296
+ }
297
+
298
+ case 'ellipse': {
299
+ const w = typeof node.width === 'number' ? node.width : 100
300
+ const h = typeof node.height === 'number' ? node.height : 100
301
+ const styles: Record<string, string> = {
302
+ ...commonStyles(node),
303
+ width: String(w),
304
+ height: String(h),
305
+ borderRadius: String(Math.min(w, h) / 2),
306
+ }
307
+ Object.assign(styles, fillToStyle(node.fill))
308
+ Object.assign(styles, strokeToStyle(node.stroke))
309
+ Object.assign(styles, effectsToStyle(node.effects))
310
+ return `${pad}<View style=${formatStyle(styles)} />`
311
+ }
312
+
313
+ case 'text': {
314
+ const styles: Record<string, string> = {
315
+ ...commonStyles(node),
316
+ }
317
+ if (typeof node.width === 'number') styles.width = String(node.width)
318
+ if (typeof node.height === 'number') styles.height = String(node.height)
319
+ Object.assign(styles, fillToTextColor(node.fill))
320
+ Object.assign(styles, textToStyle(node))
321
+ Object.assign(styles, effectsToStyle(node.effects))
322
+
323
+ const text = escapeJSX(getTextContent(node))
324
+ return `${pad}<Text style=${formatStyle(styles)}>${text}</Text>`
325
+ }
326
+
327
+ case 'line': {
328
+ const lineNode = node as LineNode
329
+ const w = lineNode.x2 !== undefined ? Math.abs(lineNode.x2 - (lineNode.x ?? 0)) : 0
330
+ const styles: Record<string, string> = {
331
+ ...commonStyles(node),
332
+ width: String(w),
333
+ }
334
+ if (lineNode.stroke) {
335
+ const thickness = typeof lineNode.stroke.thickness === 'number'
336
+ ? lineNode.stroke.thickness
337
+ : typeof lineNode.stroke.thickness === 'string'
338
+ ? 1
339
+ : lineNode.stroke.thickness[0]
340
+ styles.height = String(thickness)
341
+ if (lineNode.stroke.fill && lineNode.stroke.fill.length > 0) {
342
+ const sf = lineNode.stroke.fill[0]
343
+ if (sf.type === 'solid') {
344
+ styles.backgroundColor = hexColor(sf.color)
345
+ }
346
+ } else {
347
+ styles.backgroundColor = "'#999999'"
348
+ }
349
+ } else {
350
+ styles.height = '1'
351
+ styles.backgroundColor = "'#999999'"
352
+ }
353
+ return `${pad}<View style=${formatStyle(styles)} />`
354
+ }
355
+
356
+ case 'path': {
357
+ const pathNode = node as PathNode
358
+ const w = typeof pathNode.width === 'number' ? pathNode.width : 100
359
+ const h = typeof pathNode.height === 'number' ? pathNode.height : 100
360
+ const fillColor = pathNode.fill?.[0]?.type === 'solid'
361
+ ? varOrLiteral(pathNode.fill[0].color)
362
+ : 'currentColor'
363
+ const posStyles = commonStyles(node)
364
+ const posStr = Object.keys(posStyles).length > 0
365
+ ? ` style=${formatStyle(posStyles)}`
366
+ : ''
367
+ const viewTag = Object.keys(posStyles).length > 0 ? 'View' : null
368
+ const svgContent = [
369
+ `${pad}${viewTag ? `<View${posStr}>` : ''}`,
370
+ `${pad}${viewTag ? ' ' : ''}<Svg width={${w}} height={${h}} viewBox="0 0 ${w} ${h}">`,
371
+ `${pad}${viewTag ? ' ' : ' '}<SvgPath d="${pathNode.d}" fill="${fillColor}" />`,
372
+ `${pad}${viewTag ? ' ' : ''}</Svg>`,
373
+ ]
374
+ if (viewTag) svgContent.push(`${pad}</View>`)
375
+ return svgContent.filter(Boolean).join('\n')
376
+ }
377
+
378
+ case 'polygon': {
379
+ const polyNode = node as PolygonNode
380
+ const w = typeof polyNode.width === 'number' ? polyNode.width : 100
381
+ const h = typeof polyNode.height === 'number' ? polyNode.height : 100
382
+ const fillColor = polyNode.fill?.[0]?.type === 'solid'
383
+ ? varOrLiteral(polyNode.fill[0].color)
384
+ : 'none'
385
+ const sides = polyNode.polygonCount
386
+ const points = polygonPoints(sides, w, h)
387
+ const posStyles = commonStyles(node)
388
+ const posStr = Object.keys(posStyles).length > 0
389
+ ? ` style=${formatStyle(posStyles)}`
390
+ : ''
391
+ const viewTag = Object.keys(posStyles).length > 0 ? 'View' : null
392
+ const svgContent = [
393
+ `${pad}${viewTag ? `<View${posStr}>` : ''}`,
394
+ `${pad}${viewTag ? ' ' : ''}<Svg width={${w}} height={${h}} viewBox="0 0 ${w} ${h}">`,
395
+ `${pad}${viewTag ? ' ' : ' '}<SvgPolygon points="${points}" fill="${fillColor}" />`,
396
+ `${pad}${viewTag ? ' ' : ''}</Svg>`,
397
+ ]
398
+ if (viewTag) svgContent.push(`${pad}</View>`)
399
+ return svgContent.filter(Boolean).join('\n')
400
+ }
401
+
402
+ case 'image': {
403
+ const imgNode = node as ImageNode
404
+ const styles: Record<string, string> = {
405
+ ...commonStyles(node),
406
+ }
407
+ if (typeof imgNode.width === 'number') styles.width = String(imgNode.width)
408
+ if (typeof imgNode.height === 'number') styles.height = String(imgNode.height)
409
+ if (imgNode.objectFit === 'fit') {
410
+ styles.resizeMode = "'contain'"
411
+ } else if (imgNode.objectFit === 'fill') {
412
+ styles.resizeMode = "'stretch'"
413
+ } else {
414
+ styles.resizeMode = "'cover'"
415
+ }
416
+ Object.assign(styles, cornerRadiusToStyle(imgNode.cornerRadius))
417
+ Object.assign(styles, effectsToStyle(imgNode.effects))
418
+
419
+ const src = imgNode.src
420
+ if (src.startsWith('http://') || src.startsWith('https://') || src.startsWith('data:')) {
421
+ return `${pad}<Image source={{ uri: '${src}' }} style=${formatStyle(styles)} />`
422
+ }
423
+ return `${pad}<Image source={require('${src}')} style=${formatStyle(styles)} />`
424
+ }
425
+
426
+ case 'icon_font': {
427
+ const size = typeof node.width === 'number' ? node.width : 24
428
+ const color = node.fill?.[0]?.type === 'solid' ? varOrLiteral(node.fill[0].color) : 'currentColor'
429
+ const iconComp = kebabToPascal(node.iconFontName || 'circle')
430
+ return `${pad}<${iconComp} size={${size}} color="${color}" />`
431
+ }
432
+
433
+ case 'ref':
434
+ return `${pad}{/* Ref: ${node.ref} */}`
435
+
436
+ default:
437
+ return `${pad}{/* Unknown node */}`
438
+ }
439
+ }
440
+
441
+ /** Generate polygon points string for SVG. */
442
+ function polygonPoints(sides: number, w: number, h: number): string {
443
+ const cx = w / 2
444
+ const cy = h / 2
445
+ const r = Math.min(w, h) / 2
446
+ const points: string[] = []
447
+ for (let i = 0; i < sides; i++) {
448
+ const angle = (i * 2 * Math.PI) / sides - Math.PI / 2
449
+ const px = cx + r * Math.cos(angle)
450
+ const py = cy + r * Math.sin(angle)
451
+ points.push(`${px.toFixed(1)},${py.toFixed(1)}`)
452
+ }
453
+ return points.join(' ')
454
+ }
455
+
456
+ export function generateReactNativeCode(
457
+ nodes: PenNode[],
458
+ componentName = 'GeneratedDesign',
459
+ ): string {
460
+ if (nodes.length === 0) {
461
+ return [
462
+ "import React from 'react'",
463
+ "import { View } from 'react-native'",
464
+ '',
465
+ `export function ${componentName}() {`,
466
+ ' return <View style={{ position: \'relative\' }} />',
467
+ '}',
468
+ '',
469
+ ].join('\n')
470
+ }
471
+
472
+ // Compute bounding box for root wrapper
473
+ let maxW = 0
474
+ let maxH = 0
475
+ for (const node of nodes) {
476
+ const x = node.x ?? 0
477
+ const y = node.y ?? 0
478
+ const w = 'width' in node && typeof node.width === 'number' ? node.width : 0
479
+ const h = 'height' in node && typeof node.height === 'number' ? node.height : 0
480
+ maxW = Math.max(maxW, x + w)
481
+ maxH = Math.max(maxH, y + h)
482
+ }
483
+
484
+ const needsSvg = hasSvgNodes(nodes)
485
+
486
+ // Build imports
487
+ const rnImports = new Set<string>(['View'])
488
+ collectImports(nodes, rnImports)
489
+ const rnImportList = Array.from(rnImports).sort()
490
+
491
+ const lines: string[] = [
492
+ "import React from 'react'",
493
+ `import { ${rnImportList.join(', ')} } from 'react-native'`,
494
+ ]
495
+
496
+ if (needsSvg) {
497
+ const svgImports: string[] = ['default as Svg']
498
+ if (hasNodeType(nodes, 'path')) svgImports.push('Path as SvgPath')
499
+ if (hasNodeType(nodes, 'polygon')) svgImports.push('Polygon as SvgPolygon')
500
+ lines.push(`import Svg, { ${svgImports.filter((s) => s !== 'default as Svg').join(', ')} } from 'react-native-svg'`)
501
+ }
502
+
503
+ lines.push('')
504
+
505
+ // Root wrapper styles
506
+ const rootStyles: Record<string, string> = { position: "'relative'" }
507
+ if (maxW > 0) rootStyles.width = String(maxW)
508
+ if (maxH > 0) rootStyles.height = String(maxH)
509
+
510
+ const childrenJSX = nodes
511
+ .map((n) => generateNodeRN(n, 2))
512
+ .join('\n')
513
+
514
+ lines.push(`export function ${componentName}() {`)
515
+ lines.push(' return (')
516
+ lines.push(` <View style=${formatStyle(rootStyles)}>`)
517
+ lines.push(childrenJSX)
518
+ lines.push(' </View>')
519
+ lines.push(' )')
520
+ lines.push('}')
521
+ lines.push('')
522
+
523
+ return lines.join('\n')
524
+ }
525
+
526
+ /** Collect required react-native imports by walking the node tree. */
527
+ function collectImports(nodes: PenNode[], imports: Set<string>): void {
528
+ for (const node of nodes) {
529
+ switch (node.type) {
530
+ case 'text':
531
+ imports.add('Text')
532
+ break
533
+ case 'image':
534
+ imports.add('Image')
535
+ break
536
+ case 'frame':
537
+ case 'rectangle':
538
+ case 'group':
539
+ case 'ellipse':
540
+ case 'line':
541
+ // View is already included
542
+ break
543
+ }
544
+ if ('children' in node && node.children) {
545
+ collectImports(node.children, imports)
546
+ }
547
+ }
548
+ }
549
+
550
+ /** Check if any node in the tree matches a given type. */
551
+ function hasNodeType(nodes: PenNode[], type: string): boolean {
552
+ for (const node of nodes) {
553
+ if (node.type === type) return true
554
+ if ('children' in node && node.children) {
555
+ if (hasNodeType(node.children, type)) return true
556
+ }
557
+ }
558
+ return false
559
+ }
560
+
561
+ export function generateReactNativeFromDocument(
562
+ doc: PenDocument,
563
+ activePageId?: string | null,
564
+ ): string {
565
+ const children = activePageId !== undefined
566
+ ? getActivePageChildren(doc, activePageId)
567
+ : doc.children
568
+ return generateReactNativeCode(children, 'GeneratedDesign')
569
+ }