11import { getComponentName } from '../utils'
2+ import { toCamel } from '../utils/to-camel'
23import { getProps } from './props'
34import { getPositionProps } from './props/position'
45import { 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