From d1c32845e72076dcb62787a39fbb5e085e6d7c91 Mon Sep 17 00:00:00 2001 From: Guilherme Datilio Ribeiro Date: Wed, 15 Jan 2025 11:34:08 -0300 Subject: [PATCH] Building Strongly Typed Polymorphic Components (#18274) * feat: adding reusable polymorphicProps * feat: added PolymorphicProps to a new file * fix: fixed commnets * fix: fixed type error by ommiting ref --- .../src/components/ListItem/ListItem.tsx | 2 +- packages/react/src/components/Text/Text.tsx | 104 ++++++++++-------- .../react/src/internal/PolymorphicProps.ts | 22 ++++ 3 files changed, 79 insertions(+), 49 deletions(-) create mode 100644 packages/react/src/internal/PolymorphicProps.ts diff --git a/packages/react/src/components/ListItem/ListItem.tsx b/packages/react/src/components/ListItem/ListItem.tsx index eba39652f552..7458e3095ec0 100644 --- a/packages/react/src/components/ListItem/ListItem.tsx +++ b/packages/react/src/components/ListItem/ListItem.tsx @@ -11,7 +11,7 @@ import classnames from 'classnames'; import { usePrefix } from '../../internal/usePrefix'; import { Text } from '../Text'; -type ListItemProps = ComponentProps<'li'>; +type ListItemProps = Omit, 'ref'>; export default function ListItem({ className, diff --git a/packages/react/src/components/Text/Text.tsx b/packages/react/src/components/Text/Text.tsx index 4931dabbbb9e..c68e828319f2 100644 --- a/packages/react/src/components/Text/Text.tsx +++ b/packages/react/src/components/Text/Text.tsx @@ -6,66 +6,74 @@ */ import PropTypes from 'prop-types'; -import React, { ReactNode, useContext, useRef } from 'react'; -import { PolymorphicProps } from '../../types/common'; +import React, { ReactNode, useContext } from 'react'; import { TextDir } from './TextDirection'; import { TextDirectionContext } from './TextDirectionContext'; -import { useMergeRefs } from '@floating-ui/react'; +import { + PolymorphicComponentPropWithRef, + PolymorphicRef, +} from '../../internal/PolymorphicProps'; export interface TextBaseProps { dir?: TextDir | undefined; + children?: ReactNode; } -export type TextProps = PolymorphicProps< - T, - TextBaseProps ->; -const Text = React.forwardRef(function Text( - { as, children, dir = 'auto', ...rest }: TextProps, - ref: React.Ref -) { - // TODO: Update with context typing once its been converted to TS - const context = useContext(TextDirectionContext); - const textProps: { dir?: TextDir } = {}; - const BaseComponent = as ?? 'span'; - const value = { - ...context, - }; - - if (!context) { - textProps.dir = dir; - value.direction = dir; - } else { - const { direction: parentDirection, getTextDirection } = context; - - if (getTextDirection && getTextDirection.current) { - const text = getTextFromChildren(children); - const override = getTextDirection.current(text); - - if (parentDirection !== override) { - textProps.dir = override; - value.direction = override; - } else if (parentDirection === 'auto') { - textProps.dir = override; - } - } else if (parentDirection !== dir) { +export type TextProps = + PolymorphicComponentPropWithRef; + +type TextComponent = ( + props: TextProps +) => React.ReactElement | any; + +const Text: TextComponent = React.forwardRef( + ( + { as, children, dir = 'auto', ...rest }: TextProps, + ref?: PolymorphicRef + ) => { + // TODO: Update with context typing once its been converted to TS + const context = useContext(TextDirectionContext); + const textProps: { dir?: TextDir } = {}; + const BaseComponent = as ?? 'span'; + const value = { + ...context, + }; + + if (!context) { textProps.dir = dir; value.direction = dir; - } else if (parentDirection === 'auto') { - textProps.dir = dir; + } else { + const { direction: parentDirection, getTextDirection } = context; + + if (getTextDirection && getTextDirection.current) { + const text = getTextFromChildren(children); + const override = getTextDirection.current(text); + + if (parentDirection !== override) { + textProps.dir = override; + value.direction = override; + } else if (parentDirection === 'auto') { + textProps.dir = override; + } + } else if (parentDirection !== dir) { + textProps.dir = dir; + value.direction = dir; + } else if (parentDirection === 'auto') { + textProps.dir = dir; + } } - } - return ( - - - {children} - - - ); -}); + return ( + + + {children} + + + ); + } +); -Text.propTypes = { +(Text as React.FC).propTypes = { /** * Provide a custom element type used to render the outermost node */ diff --git a/packages/react/src/internal/PolymorphicProps.ts b/packages/react/src/internal/PolymorphicProps.ts new file mode 100644 index 000000000000..9b846f3ca48f --- /dev/null +++ b/packages/react/src/internal/PolymorphicProps.ts @@ -0,0 +1,22 @@ +type AsProp = { + as?: C; +}; + +type PropsToOmit = keyof (AsProp & P); + +// This can be used if there is NO need for "ref" +export type PolymorphicComponentProp< + C extends React.ElementType, + Props = {}, +> = React.PropsWithChildren> & + Omit, PropsToOmit>; + +// This is the type for the "ref" only +export type PolymorphicRef = + React.ComponentPropsWithRef['ref']; + +// This is a new type utitlity with ref! +export type PolymorphicComponentPropWithRef< + C extends React.ElementType, + Props = {}, +> = PolymorphicComponentProp & { ref?: PolymorphicRef };