Skip to content

Commit

Permalink
Revert "fix(button): Fixed Typescript error when using Next.js Link i…
Browse files Browse the repository at this point in the history
…n the button `as` prop (themesberg#1244)"

This reverts commit a6698d4.
  • Loading branch information
rnicholus committed Mar 14, 2024
1 parent 31c9d9d commit f6fa786
Show file tree
Hide file tree
Showing 9 changed files with 171 additions and 184 deletions.
1 change: 1 addition & 0 deletions examples/button/button.polymorph.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ function Component() {
<Button as="span" className="cursor-pointer">
Span Button
</Button>
{/* @ts-expect-error TODO: fix `as` inference */}
<Button as={Link} href="#">
Next Link Button
</Button>
Expand Down
1 change: 1 addition & 0 deletions examples/dropdown/dropdown.customItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ function Component() {
function Component() {
return (
<Dropdown dismissOnClick={false} label="My custom item">
{/* @ts-expect-error TODO: fix `as` inference */}
<DropdownItem as={Link} href="#">
Home
</DropdownItem>
Expand Down
193 changes: 94 additions & 99 deletions src/components/Button/Button.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { ElementType } from 'react';
import { forwardRef, type ReactNode } from 'react';
import type { ComponentPropsWithoutRef, ElementType, ForwardedRef } from 'react';
import { 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';
Expand All @@ -15,7 +16,6 @@ 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;
Expand Down Expand Up @@ -67,110 +67,105 @@ export interface ButtonSizes extends Pick<FlowbiteSizes, 'xs' | 'sm' | 'lg' | 'x
[key: string]: string;
}

export type ButtonProps<T extends ElementType = 'button'> = PolymorphicComponentPropWithRef<
T,
{
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<FlowbiteButtonTheme>;
}
>;

type ButtonComponentType = (<C extends React.ElementType = 'button'>(
props: ButtonProps<C>,
) => React.ReactNode | null) & { displayName?: string };
export type ButtonProps<T extends ElementType = 'button'> = {
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<FlowbiteButtonTheme>;
} & ComponentPropsWithoutRef<T>;

const ButtonComponentFn: ButtonComponentType = forwardRef(
<T extends ElementType = 'button'>(
{
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<T>,
ref: PolymorphicRef<T>,
) => {
const { buttonGroup: groupTheme, button: buttonTheme } = getTheme();
const theme = mergeDeep(buttonTheme, customTheme);
const ButtonComponentFn = <T extends ElementType = 'button'>(
{
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<T>,
ref: ForwardedRef<T>,
) => {
const { buttonGroup: groupTheme, button: buttonTheme } = getTheme();
const theme = mergeDeep(buttonTheme, customTheme);

const theirProps = props as ButtonBaseProps<T>;
const theirProps = props as ButtonBaseProps<T>;

return (
<ButtonBase
ref={ref}
disabled={disabled}
return (
<ButtonBase
ref={ref}
disabled={disabled}
className={twMerge(
theme.base,
disabled && theme.disabled,
!gradientDuoTone && !gradientMonochrome && theme.color[color],
gradientDuoTone && !gradientMonochrome && theme.gradientDuoTone[gradientDuoTone],
!gradientDuoTone && gradientMonochrome && theme.gradient[gradientMonochrome],
outline && (theme.outline.color[color] ?? theme.outline.color.default),
theme.pill[pill ? 'on' : 'off'],
fullSized && theme.fullSized,
groupTheme.position[positionInGroup],
className,
)}
{...theirProps}
>
<span
className={twMerge(
theme.base,
disabled && theme.disabled,
!gradientDuoTone && !gradientMonochrome && theme.color[color],
gradientDuoTone && !gradientMonochrome && theme.gradientDuoTone[gradientDuoTone],
!gradientDuoTone && gradientMonochrome && theme.gradient[gradientMonochrome],
outline && (theme.outline.color[color] ?? theme.outline.color.default),
theme.pill[pill ? 'on' : 'off'],
fullSized && theme.fullSized,
groupTheme.position[positionInGroup],
className,
theme.inner.base,
theme.outline[outline ? 'on' : 'off'],
theme.outline.pill[outline && pill ? 'on' : 'off'],
theme.size[size],
outline && !theme.outline.color[color] && theme.inner.outline,
isProcessing && theme.isProcessing,
isProcessing && theme.inner.isProcessingPadding[size],
theme.inner.position[positionInGroup],
)}
{...theirProps}
>
<span
className={twMerge(
theme.inner.base,
theme.outline[outline ? 'on' : 'off'],
theme.outline.pill[outline && pill ? 'on' : 'off'],
theme.size[size],
outline && !theme.outline.color[color] && theme.inner.outline,
isProcessing && theme.isProcessing,
isProcessing && theme.inner.isProcessingPadding[size],
theme.inner.position[positionInGroup],
<>
{isProcessing && (
<span className={twMerge(theme.spinnerSlot, theme.spinnerLeftPosition[size])}>
{processingSpinner || <Spinner size={size} />}
</span>
)}
>
<>
{isProcessing && (
<span className={twMerge(theme.spinnerSlot, theme.spinnerLeftPosition[size])}>
{processingSpinner || <Spinner size={size} />}
</span>
)}
{typeof children !== 'undefined' ? (
children
) : (
<span data-testid="flowbite-button-label" className={twMerge(theme.label)}>
{isProcessing ? processingLabel : label}
</span>
)}
</>
</span>
</ButtonBase>
);
},
);
{typeof children !== 'undefined' ? (
children
) : (
<span data-testid="flowbite-button-label" className={twMerge(theme.label)}>
{isProcessing ? processingLabel : label}
</span>
)}
</>
</span>
</ButtonBase>
);
};

ButtonComponentFn.displayName = 'Button';
export const Button = Object.assign(ButtonComponentFn, {

const ButtonComponent = genericForwardRef(ButtonComponentFn);

export const Button = Object.assign(ButtonComponent, {
Group: ButtonGroup,
});
21 changes: 10 additions & 11 deletions src/components/Button/ButtonBase.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
import { createElement, type ComponentPropsWithoutRef, type ElementType, type ForwardedRef, forwardRef } from 'react';
import { createElement, type ComponentPropsWithoutRef, type ElementType, type ForwardedRef } from 'react';
import genericForwardRef from '../../helpers/generic-forward-ref';

export type ButtonBaseProps<T extends ElementType = 'button'> = {
as?: T;
href?: string;
} & ComponentPropsWithoutRef<T>;

export const ButtonBase = forwardRef(
<T extends ElementType = 'button'>(
{ children, as: Component, href, type = 'button', ...props }: ButtonBaseProps<T>,
ref: ForwardedRef<T>,
) => {
const BaseComponent = Component || (href ? 'a' : 'button');
const ButtonBaseComponent = <T extends ElementType = 'button'>(
{ children, as: Component, href, type = 'button', ...props }: ButtonBaseProps<T>,
ref: ForwardedRef<T>,
) => {
const BaseComponent = Component || (href ? 'a' : 'button');

return createElement(BaseComponent, { ref, href, type, ...props }, children);
},
);
return createElement(BaseComponent, { ref, href, type, ...props }, children);
};

ButtonBase.displayName = 'ButtonBaseComponent';
export const ButtonBase = genericForwardRef(ButtonBaseComponent);
2 changes: 1 addition & 1 deletion src/components/Dropdown/Dropdown.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ describe('Components / Dropdown', () => {
});

describe('Dropdown item render', async () => {
it('should override Dropdown.Item base component when using `as` prop', async () => {
it('should override Dropdownn.Item base component when using `as` prop', async () => {
const user = userEvent.setup();

const CustomBaseItem = ({ children }: PropsWithChildren) => {
Expand Down
9 changes: 8 additions & 1 deletion src/components/Dropdown/Dropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import type {
MutableRefObject,
ReactElement,
ReactNode,
RefCallback,
SetStateAction,
} from 'react';
import { cloneElement, useCallback, useEffect, useMemo, useRef, useState } from 'react';
Expand Down Expand Up @@ -97,7 +98,13 @@ const Trigger = ({
{children}
</button>
) : (
<Button {...buttonProps} disabled={disabled} type="button" ref={refs.setReference} {...a11yProps}>
<Button
{...buttonProps}
disabled={disabled}
type="button"
ref={refs.setReference as RefCallback<'button'>}
{...a11yProps}
>
{children}
</Button>
);
Expand Down
Loading

0 comments on commit f6fa786

Please sign in to comment.