Skip to content

Commit 1a43863

Browse files
committed
Support slot
1 parent cd1eb43 commit 1a43863

File tree

4 files changed

+361
-15
lines changed

4 files changed

+361
-15
lines changed

src/codegen/Codegen.ts

Lines changed: 55 additions & 12 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,12 +355,13 @@ export class Codegen {
354355
)
355356
}
356357

357-
// Handle native Figma SLOT nodes — render as {children} in the component.
358+
// Handle native Figma SLOT nodes — render as {slotName} in the component.
358359
// 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'.
359361
if ((node.type as string) === 'SLOT') {
360362
perfEnd('buildTree()', tBuild)
361363
return {
362-
component: 'children',
364+
component: toCamel(sanitizePropertyName(node.name)),
363365
props: {},
364366
children: [],
365367
nodeType: 'SLOT',
@@ -409,15 +411,42 @@ export class Codegen {
409411

410412
// Check for native SLOT children and build their overridden content.
411413
// SLOT children contain the content placed into the component's slot.
412-
const slotChildren: NodeTree[] = []
414+
// Group by slot name to distinguish single-slot (children) vs multi-slot (named props).
415+
const slotsByName = new Map<string, NodeTree[]>()
413416
if ('children' in node) {
414417
for (const child of node.children) {
415418
if ((child.type as string) === 'SLOT' && 'children' in child) {
419+
const slotName = toCamel(sanitizePropertyName(child.name))
420+
const content: NodeTree[] = []
416421
for (const slotContent of (child as SceneNode & ChildrenMixin)
417422
.children) {
418-
slotChildren.push(await this.buildTree(slotContent))
423+
content.push(await this.buildTree(slotContent))
419424
}
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</>`
420448
}
449+
variantProps[slotName] = { __jsxSlot: true, jsx }
421450
}
422451
}
423452

@@ -653,14 +682,28 @@ export class Codegen {
653682
}
654683
const variantComments = selectorProps?.variantComments || {}
655684

656-
// Detect native SLOT children — add children: React.ReactNode to variants
657-
if (
658-
!variants.children &&
659-
childrenTrees.some(
660-
(child) => child.nodeType === 'SLOT' && child.component === 'children',
661-
)
662-
) {
663-
variants.children = 'React.ReactNode'
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+
}
664707
}
665708

666709
this.componentTrees.set(nodeId, {

0 commit comments

Comments
 (0)