diff --git a/packages/@headlessui-react/src/components/transitions/transition.test.tsx b/packages/@headlessui-react/src/components/transitions/transition.test.tsx index 6acb8a57d6..90863b5d53 100644 --- a/packages/@headlessui-react/src/components/transitions/transition.test.tsx +++ b/packages/@headlessui-react/src/components/transitions/transition.test.tsx @@ -120,7 +120,9 @@ describe('Setup API', () => { it('should be possible to use a render prop', () => { const { container } = render( - {ref => Children} + + {() => Children} + ) expect(container.firstChild).toMatchInlineSnapshot(` @@ -131,12 +133,20 @@ describe('Setup API', () => { }) it( - 'should yell at us when we forget to apply the ref when using a render prop', + 'should yell at us when we forget to forward the ref when using a render prop', suppressConsoleLogs(() => { expect.assertions(1) + function Dummy(props: any) { + return Children + } + expect(() => { - render({() => Children}) + render( + + {() => } + + ) }).toThrowErrorMatchingInlineSnapshot( `"Did you forget to passthrough the \`ref\` to the actual DOM node?"` ) @@ -253,8 +263,10 @@ describe('Setup API', () => { const { container } = render(
- {ref => } - {ref =>
Content
}
+ {() => } + + {() =>
Content
} +
) @@ -278,11 +290,15 @@ describe('Setup API', () => { it('should be possible to use render props on the Transition and Transition.Child components', () => { const { container } = render(
- - {ref => ( -
- {ref => } - {ref =>
Content
}
+ + {() => ( +
+ + {() => } + + + {() =>
Content
} +
)}
@@ -306,16 +322,24 @@ describe('Setup API', () => { }) it( - 'should yell at us when we forgot to apply the ref on one of the Transition.Child components', + 'should yell at us when we forgot to forward the ref on one of the Transition.Child components', suppressConsoleLogs(() => { expect.assertions(1) + function Dummy(props: any) { + return
+ } + expect(() => { render(
- {ref => } - {() =>
Content
}
+ + {() => Sidebar} + + + {() => Content} +
) @@ -326,21 +350,23 @@ describe('Setup API', () => { ) it( - 'should yell at us when we forgot to apply a ref on the Transition component', + 'should yell at us when we forgot to forward a ref on the Transition component', suppressConsoleLogs(() => { expect.assertions(1) + function Dummy(props: any) { + return
+ } + expect(() => { render(
- + {() => ( -
- {ref => } - - {ref =>
Content
} -
-
+ + {() => } + {() =>
Content
}
+
)}
diff --git a/packages/@headlessui-react/src/components/transitions/transition.tsx b/packages/@headlessui-react/src/components/transitions/transition.tsx index 37c035c894..71fde1b8c4 100644 --- a/packages/@headlessui-react/src/components/transitions/transition.tsx +++ b/packages/@headlessui-react/src/components/transitions/transition.tsx @@ -1,11 +1,12 @@ import * as React from 'react' +import { Props } from 'types' import { useId } from '../../hooks/use-id' import { useIsInitialRender } from '../../hooks/use-is-initial-render' import { useIsMounted } from '../../hooks/use-is-mounted' import { useIsoMorphicEffect } from '../../hooks/use-iso-morphic-effect' -import { match } from '../../utils/match' +import { Features, PropsForFeatures, render } from '../../utils/render' import { Reason, transition } from './utils/transition' type ID = ReturnType @@ -43,24 +44,9 @@ export type TransitionEvents = Partial<{ afterLeave(): void }> -type HTMLTags = keyof JSX.IntrinsicElements -type HTMLTagProps = JSX.IntrinsicElements[TTag] - -type AsShortcut = { - children?: React.ReactNode - as?: TTag -} & Omit, 'ref'> - -type AsRenderPropFunction = { - children: (ref: React.MutableRefObject) => JSX.Element -} - -type BaseConfig = Partial<{ appear: boolean }> - -type TransitionChildProps = BaseConfig & - (AsShortcut | AsRenderPropFunction) & - TransitionClasses & - TransitionEvents +type TransitionChildProps = Props & + PropsForFeatures & + Partial<{ appear: boolean } & TransitionClasses & TransitionEvents> function useTransitionContext() { const context = React.useContext(TransitionContext) @@ -156,7 +142,15 @@ function useEvents(events: TransitionEvents) { return eventsRef } -function TransitionChild(props: TransitionChildProps) { +// --- + +const DEFAULT_TRANSITION_CHILD_TAG = 'div' +type TransitionChildRenderPropArg = React.MutableRefObject +const TransitionChildRenderFeatures = Features.RenderStrategy + +function TransitionChild( + props: TransitionChildProps +) { const { // Event "handlers" beforeEnter, @@ -171,9 +165,6 @@ function TransitionChild(props: TransitionChildPr leave, leaveFrom, leaveTo, - - // .. - children, ...rest } = props const container = React.useRef(null) @@ -266,32 +257,27 @@ function TransitionChild(props: TransitionChildPr leaveToClasses, ]) - // Unmount the whole tree - if (state === TreeStates.Hidden) return null + const propsBag = {} + const propsWeControl = { ref: container } + const passthroughProps = rest - if (typeof children === 'function') { - return ( - - {(children as AsRenderPropFunction['children'])(container)} - - ) - } - - const { as: Component = 'div', ...passthroughProps } = rest as AsShortcut return ( - {/* @ts-expect-error Expression produces a union type that is too complex to represent. */} - - {children} - + {render( + { ...passthroughProps, ...propsWeControl }, + propsBag, + DEFAULT_TRANSITION_CHILD_TAG, + TransitionChildRenderFeatures, + state === TreeStates.Visible + )} ) } -export function Transition( +export function Transition( props: TransitionChildProps & { show: boolean; appear?: boolean } ) { - const { show, appear = false, ...rest } = props + const { show, appear = false, unmount, ...passthroughProps } = props if (![true, false].includes(show)) { throw new Error('A is used but it is missing a `show={true | false}` prop.') @@ -317,13 +303,22 @@ export function Transition( } }, [show, nestingBag]) + const sharedProps = { unmount } + return ( - {match(state, { - [TreeStates.Visible]: () => , - [TreeStates.Hidden]: null, - })} + {render( + { + ...sharedProps, + as: React.Fragment, + children: , + }, + null, + React.Fragment, + TransitionChildRenderFeatures, + state === TreeStates.Visible + )} )