From 52a395f79f5597de2e2b12a2dd06243878e75ec9 Mon Sep 17 00:00:00 2001 From: yishayhaz Date: Fri, 27 Sep 2024 20:18:55 +0300 Subject: [PATCH 1/3] feat: menu types playground file --- playgrounds/react/pages/menu/test-menu.tsx | 60 ++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 playgrounds/react/pages/menu/test-menu.tsx diff --git a/playgrounds/react/pages/menu/test-menu.tsx b/playgrounds/react/pages/menu/test-menu.tsx new file mode 100644 index 0000000000..fa28cf71d2 --- /dev/null +++ b/playgrounds/react/pages/menu/test-menu.tsx @@ -0,0 +1,60 @@ +// TODO: delete this file + +import { ComponentPropsWithRef, ElementType, Fragment, forwardRef, useRef } from 'react' + +// Example components that `Menu` will be used `as` + +const MenuWithRef = ({}: { ref: React.RefObject }) => null +const MenuWithoutRef = ({}: {}) => null +const MenuWithCustomRef = ({}: { ref: React.RefObject<{ onOpen?: () => void }> }) => null +const MenuWithForwardRef = forwardRef((props, ref) => null) +const MenuWithForwardRefWithCustomRef = forwardRef<{ onOpen: () => void }, {}>((props, ref) => null) + +/** + * The `as` prop can be a few things: + * - A regular HTML tag + * - Another component + * - A React fragment + * + * So, the ref can also point to different things: + * - A reference to an HTML tag + * - A ForwardRef, which could be for an HTML tag or a custom one + * - A component with a `ref` prop (React v19+), pointing to either an HTML tag or a custom ref + */ + +type RefProp = unknown extends ComponentPropsWithRef['ref'] + ? {} + : { ref?: ComponentPropsWithRef['ref'] } + +type Menu = { + ( + props: { + as?: TTag + } & RefProp + ): JSX.Element +} + +const MenuAs = forwardRef(({ as }: any, ref) => { + const Component = as || 'div' + + return +}) as Menu + +export default function MenuExample() { + const divRef = useRef(null) + const buttonRef = useRef(null) + const customRef = useRef<{ onOpen: () => void }>(null) + + return ( + + {/* Default `as` is a div */} + + {/* ref is `never` */} + + {/* ref is `never` */} + + + + + ) +} From d0c59829ea9c2a5577016db60e523b07efd3887c Mon Sep 17 00:00:00 2001 From: yishayhaz Date: Fri, 27 Sep 2024 20:19:23 +0300 Subject: [PATCH 2/3] feat: `NewRefProp` --- packages/@headlessui-react/src/utils/render.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/@headlessui-react/src/utils/render.ts b/packages/@headlessui-react/src/utils/render.ts index 4106c4b683..38e9ba9d67 100644 --- a/packages/@headlessui-react/src/utils/render.ts +++ b/packages/@headlessui-react/src/utils/render.ts @@ -6,6 +6,7 @@ import { isValidElement, useCallback, useRef, + type ComponentPropsWithRef, type ElementType, type MutableRefObject, type ReactElement, @@ -386,6 +387,10 @@ export type RefProp = T extends (props: any, ref: Ref } : never +export type NewRefProp = unknown extends ComponentPropsWithRef['ref'] + ? {} + : { ref?: ComponentPropsWithRef['ref'] } + // TODO: add proper return type, but this is not exposed as public API so it's fine for now export function mergeProps[]>(...listOfProps: T) { if (listOfProps.length === 0) return {} From 39e686694667b71cddf1bd95789808382de4d4aa Mon Sep 17 00:00:00 2001 From: yishayhaz Date: Fri, 27 Sep 2024 20:20:09 +0300 Subject: [PATCH 3/3] refactor: implement `NewRefProp` for Menu component --- packages/@headlessui-react/src/components/menu/menu.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/@headlessui-react/src/components/menu/menu.tsx b/packages/@headlessui-react/src/components/menu/menu.tsx index 57a823eb09..f58aed8e79 100644 --- a/packages/@headlessui-react/src/components/menu/menu.tsx +++ b/packages/@headlessui-react/src/components/menu/menu.tsx @@ -70,6 +70,7 @@ import { render, useMergeRefsFn, type HasDisplayName, + type NewRefProp, type RefProp, } from '../../utils/render' import { useDescriptions } from '../description/description' @@ -1096,7 +1097,7 @@ function SeparatorFn( export interface _internal_ComponentMenu extends HasDisplayName { ( - props: MenuProps & RefProp + props: MenuProps & NewRefProp ): JSX.Element }