Skip to content

Commit 99cdbb8

Browse files
authored
Merge pull request #30 from dev-five-git/support-slot
Support single slot
2 parents f4c74d6 + 1a43863 commit 99cdbb8

File tree

4 files changed

+547
-2
lines changed

4 files changed

+547
-2
lines changed

src/codegen/Codegen.ts

Lines changed: 83 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { getComponentName } from '../utils'
2+
import { toCamel } from '../utils/to-camel'
23
import { getProps } from './props'
34
import { getPositionProps } from './props/position'
45
import { getSelectorProps, sanitizePropertyName } from './props/selector'
@@ -354,6 +355,21 @@ export class Codegen {
354355
)
355356
}
356357

358+
// Handle native Figma SLOT nodes — render as {slotName} in the component.
359+
// SLOT is a newer Figma node type not yet in @figma/plugin-typings.
360+
// The slot name is sanitized here; addComponentTree renames single slots to 'children'.
361+
if ((node.type as string) === 'SLOT') {
362+
perfEnd('buildTree()', tBuild)
363+
return {
364+
component: toCamel(sanitizePropertyName(node.name)),
365+
props: {},
366+
children: [],
367+
nodeType: 'SLOT',
368+
nodeName: node.name,
369+
isSlot: true,
370+
}
371+
}
372+
357373
// Handle asset nodes (images/SVGs)
358374
const assetNode = checkAssetNode(node)
359375
if (assetNode) {
@@ -393,6 +409,47 @@ export class Codegen {
393409
const componentName = getComponentName(mainComponent || node)
394410
const variantProps = extractInstanceVariantProps(node)
395411

412+
// Check for native SLOT children and build their overridden content.
413+
// SLOT children contain the content placed into the component's slot.
414+
// Group by slot name to distinguish single-slot (children) vs multi-slot (named props).
415+
const slotsByName = new Map<string, NodeTree[]>()
416+
if ('children' in node) {
417+
for (const child of node.children) {
418+
if ((child.type as string) === 'SLOT' && 'children' in child) {
419+
const slotName = toCamel(sanitizePropertyName(child.name))
420+
const content: NodeTree[] = []
421+
for (const slotContent of (child as SceneNode & ChildrenMixin)
422+
.children) {
423+
content.push(await this.buildTree(slotContent))
424+
}
425+
if (content.length > 0) {
426+
slotsByName.set(slotName, content)
427+
}
428+
}
429+
}
430+
}
431+
432+
// Single SLOT → pass content as children (renders as <Comp>content</Comp>)
433+
// Multiple SLOTs → render each as a named JSX prop (renders as <Comp header={<X/>} content={<Y/>} />)
434+
let slotChildren: NodeTree[] = []
435+
if (slotsByName.size === 1) {
436+
slotChildren = [...slotsByName.values()][0]
437+
} else if (slotsByName.size > 1) {
438+
for (const [slotName, content] of slotsByName) {
439+
let jsx: string
440+
if (content.length === 1) {
441+
jsx = Codegen.renderTree(content[0], 0)
442+
} else {
443+
const children = content.map((c) => Codegen.renderTree(c, 0))
444+
const childrenStr = children
445+
.map((c) => paddingLeftMultiline(c, 1))
446+
.join('\n')
447+
jsx = `<>\n${childrenStr}\n</>`
448+
}
449+
variantProps[slotName] = { __jsxSlot: true, jsx }
450+
}
451+
}
452+
396453
// Only compute position + transform (sync, no Figma API calls)
397454
const posProps = getPositionProps(node)
398455
if (posProps?.pos) {
@@ -417,7 +474,7 @@ export class Codegen {
417474
{
418475
component: componentName,
419476
props: variantProps,
420-
children: [],
477+
children: slotChildren,
421478
nodeType: node.type,
422479
nodeName: node.name,
423480
isComponent: true,
@@ -432,7 +489,7 @@ export class Codegen {
432489
return {
433490
component: componentName,
434491
props: variantProps,
435-
children: [],
492+
children: slotChildren,
436493
nodeType: node.type,
437494
nodeName: node.name,
438495
isComponent: true,
@@ -625,6 +682,30 @@ export class Codegen {
625682
}
626683
const variantComments = selectorProps?.variantComments || {}
627684

685+
// Detect native SLOT children — single slot becomes 'children', multiple keep names.
686+
// Exclude INSTANCE_SWAP-created slots (they're already handled by selectorProps.variants).
687+
const instanceSwapNames = new Set(instanceSwapSlots.values())
688+
const nativeSlots = childrenTrees.filter(
689+
(child) =>
690+
child.nodeType === 'SLOT' &&
691+
child.isSlot &&
692+
!instanceSwapNames.has(child.component),
693+
)
694+
if (nativeSlots.length === 1) {
695+
// Single SLOT → rename to 'children' for idiomatic React
696+
nativeSlots[0].component = 'children'
697+
if (!variants.children) {
698+
variants.children = 'React.ReactNode'
699+
}
700+
} else if (nativeSlots.length > 1) {
701+
// Multiple SLOTs → keep sanitized names as individual React.ReactNode props
702+
for (const slot of nativeSlots) {
703+
if (!variants[slot.component]) {
704+
variants[slot.component] = 'React.ReactNode'
705+
}
706+
}
707+
}
708+
628709
this.componentTrees.set(nodeId, {
629710
name: getComponentName(node),
630711
node,

0 commit comments

Comments
 (0)