diff --git a/src/components/Button/Button.tsx b/src/components/Button/Button.tsx index 1df740f29..53c672d12 100644 --- a/src/components/Button/Button.tsx +++ b/src/components/Button/Button.tsx @@ -1,7 +1,6 @@ -import type { ComponentPropsWithoutRef, ElementType, ForwardedRef } from 'react'; -import { type ReactNode } from 'react'; +import type { ElementType } from 'react'; +import { forwardRef, type ReactNode } from 'react'; import { twMerge } from 'tailwind-merge'; -import genericForwardRef from '../../helpers/generic-forward-ref'; import { mergeDeep } from '../../helpers/merge-deep'; import { getTheme } from '../../theme-store'; import type { DeepPartial } from '../../types'; @@ -16,6 +15,7 @@ import { Spinner } from '../Spinner'; import { ButtonBase, type ButtonBaseProps } from './ButtonBase'; import type { PositionInButtonGroup } from './ButtonGroup'; import { ButtonGroup } from './ButtonGroup'; +import type { PolymorphicComponentPropWithRef, PolymorphicRef } from '../../helpers/generic-as-prop'; export interface FlowbiteButtonTheme { base: string; @@ -67,105 +67,110 @@ export interface ButtonSizes extends Pick = { - as?: T | null; - href?: string; - color?: keyof FlowbiteColors; - fullSized?: boolean; - gradientDuoTone?: keyof ButtonGradientDuoToneColors; - gradientMonochrome?: keyof ButtonGradientColors; - target?: string; - isProcessing?: boolean; - processingLabel?: string; - processingSpinner?: ReactNode; - label?: ReactNode; - outline?: boolean; - pill?: boolean; - positionInGroup?: keyof PositionInButtonGroup; - size?: keyof ButtonSizes; - theme?: DeepPartial; -} & ComponentPropsWithoutRef; - -const ButtonComponentFn = ( +export type ButtonProps = PolymorphicComponentPropWithRef< + T, { - children, - className, - color = 'info', - disabled, - fullSized, - isProcessing = false, - processingLabel = 'Loading...', - processingSpinner, - gradientDuoTone, - gradientMonochrome, - label, - outline = false, - pill = false, - positionInGroup = 'none', - size = 'md', - theme: customTheme = {}, - ...props - }: ButtonProps, - ref: ForwardedRef, -) => { - const { buttonGroup: groupTheme, button: buttonTheme } = getTheme(); - const theme = mergeDeep(buttonTheme, customTheme); + href?: string; + color?: keyof FlowbiteColors; + fullSized?: boolean; + gradientDuoTone?: keyof ButtonGradientDuoToneColors; + gradientMonochrome?: keyof ButtonGradientColors; + target?: string; + isProcessing?: boolean; + processingLabel?: string; + processingSpinner?: ReactNode; + label?: ReactNode; + outline?: boolean; + pill?: boolean; + positionInGroup?: keyof PositionInButtonGroup; + size?: keyof ButtonSizes; + theme?: DeepPartial; + } +>; + +type ButtonComponentType = (( + props: ButtonProps, +) => React.ReactNode | null) & { displayName?: string }; + +const ButtonComponentFn: ButtonComponentType = forwardRef( + ( + { + children, + className, + color = 'info', + disabled, + fullSized, + isProcessing = false, + processingLabel = 'Loading...', + processingSpinner, + gradientDuoTone, + gradientMonochrome, + label, + outline = false, + pill = false, + positionInGroup = 'none', + size = 'md', + theme: customTheme = {}, + ...props + }: ButtonProps, + ref: PolymorphicRef, + ) => { + const { buttonGroup: groupTheme, button: buttonTheme } = getTheme(); + const theme = mergeDeep(buttonTheme, customTheme); - const theirProps = props as ButtonBaseProps; + const theirProps = props as ButtonBaseProps; - return ( - - - <> - {isProcessing && ( - - {processingSpinner || } - + - {isProcessing ? processingLabel : label} - - )} - - - - ); -}; + > + <> + {isProcessing && ( + + {processingSpinner || } + + )} + {typeof children !== 'undefined' ? ( + children + ) : ( + + {isProcessing ? processingLabel : label} + + )} + + + + ); + }, +); ButtonComponentFn.displayName = 'Button'; - -const ButtonComponent = genericForwardRef(ButtonComponentFn); - -export const Button = Object.assign(ButtonComponent, { +export const Button = Object.assign(ButtonComponentFn, { Group: ButtonGroup, }); diff --git a/src/helpers/generic-as-prop.ts b/src/helpers/generic-as-prop.ts new file mode 100644 index 000000000..53c65add8 --- /dev/null +++ b/src/helpers/generic-as-prop.ts @@ -0,0 +1,23 @@ +import type React from 'react'; + +export type AsProp = { + as?: C | null; +}; + +export type PropsToOmit = keyof (AsProp & P); + +// eslint-disable-next-line @typescript-eslint/ban-types +export type PolymorphicComponentProp = React.PropsWithChildren< + Props & AsProp +> & + Omit, PropsToOmit>; + +// eslint-disable-next-line @typescript-eslint/ban-types +export type PolymorphicComponentPropWithRef = PolymorphicComponentProp< + C, + Props +> & { + ref?: PolymorphicRef; +}; + +export type PolymorphicRef = React.ComponentPropsWithRef['ref']; diff --git a/src/helpers/generic-forward-ref.ts b/src/helpers/generic-forward-ref.ts deleted file mode 100644 index 28460fa5e..000000000 --- a/src/helpers/generic-forward-ref.ts +++ /dev/null @@ -1,13 +0,0 @@ -import type React from 'react'; -import { forwardRef } from 'react'; - -/** This allow the `forwardRef` to be used with generic components */ - -// eslint-disable-next-line @typescript-eslint/ban-types -type FixedForwardRef = ( - render: (props: P, ref: React.Ref) => JSX.Element, -) => (props: P & React.RefAttributes) => JSX.Element; - -const genericForwardRef = forwardRef as FixedForwardRef; - -export default genericForwardRef;