import { type ComponentPropsWithoutRef, type ComponentRef, type ElementType, type ForwardRefExoticComponent, type PropsWithoutRef, type ReactElement, type ReactNode, type RefAttributes, cloneElement, forwardRef, isValidElement, } from "react"; import { Primitive as RadixPrimitive } from "@radix-ui/react-primitive"; /** * Thin wrapper around `@radix-ui/react-primitive` that adds `render` prop support. * * When `render` is provided, it is converted to the equivalent `asChild` pattern: * render={} + children → asChild + {children} * * All prop merging, ref composition, and event handler chaining remain handled * by Radix's battle-tested Slot implementation — we add zero custom logic for that. */ // Match @radix-ui/react-primitive's full element set const NODES = [ "a", "button", "div", "form", "h2", "h3", "img", "input", "label", "li", "nav", "ol", "p", "select", "span", "svg", "ul", ] as const; type PrimitiveNode = (typeof NODES)[number]; type WithRenderPropProps = ComponentPropsWithoutRef & { render?: ReactElement | undefined; }; type PrimitiveProps = WithRenderPropProps< (typeof RadixPrimitive)[E] >; type WithRenderPropRuntimeProps = WithRenderPropProps & { asChild?: boolean | undefined; children?: ReactNode | undefined; }; type PrimitiveRef = ComponentRef< (typeof RadixPrimitive)[E] >; function withRenderProp(Component: T) { const Wrapped = forwardRef, WithRenderPropRuntimeProps>( ( { render, asChild, children, ...rest }: PropsWithoutRef>, ref, ) => { const Comp = Component as any; if (render && isValidElement(render)) { const renderChildren = children !== undefined ? children : ((render.props as Record).children as ReactNode); return ( {cloneElement(render, undefined, renderChildren)} ); } return ( {children} ); }, ); const componentName = typeof Component === "string" ? Component : (Component.displayName ?? Component.name ?? "Component"); Wrapped.displayName = componentName; return Wrapped as ForwardRefExoticComponent< WithRenderPropProps & RefAttributes> >; } function createPrimitive(node: E) { const RadixComp = RadixPrimitive[node]; const Component = withRenderProp(RadixComp); Component.displayName = `Primitive.${node}`; return Component as ForwardRefExoticComponent< PrimitiveProps & RefAttributes> >; } const Primitive = NODES.reduce( (acc, node) => { acc[node] = createPrimitive(node); return acc; }, {} as { [K in PrimitiveNode]: ReturnType>; }, ); export { Primitive, withRenderProp }; export type { PrimitiveProps, WithRenderPropProps };