Skip to content

Commit

Permalink
break(button): add an element prop
Browse files Browse the repository at this point in the history
This allow to display anything as a button.

BREAKING CHANGES
This breaks types the `ref` and the `onClick` handler. So if you don't use typescript, you should be good.
Otherwise:
 * `ref` is now a `HTMLElement`
 * the event of the `onClick` handler is now a `MouseEvent<HTMLElement>`
  • Loading branch information
clementprevot committed Jun 12, 2024
1 parent e701fd5 commit 4202282
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 15 deletions.
43 changes: 43 additions & 0 deletions packages/fractal/src/components/Button/Button.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,17 @@ type ButtonProps = ComponentProps<typeof Button>

const meta = {
argTypes: {
element: {
control: 'text',
table: {
defaultValue: {
summary: [
`<a /> for components with \`href\``,
`<button /> for components with \`onClick\` and anything else if you didn't provided an \`element\` prop.`,
].join('; '),
},
},
},
icon: {
mapping: {
Cancel: <CancelIcon />,
Expand Down Expand Up @@ -141,6 +152,14 @@ const displayButtons = (
/>
</Wrapper>

<Wrapper>
<Button
element="p"
label='"Display" button as a `p` element'
variant="display"
/>
</Wrapper>

<Wrapper>
<Button fullWidth label='Full width "Display" button' variant="display" />
</Wrapper>
Expand Down Expand Up @@ -210,6 +229,14 @@ const primaryButtons = (
/>
</Wrapper>

<Wrapper>
<Button
element="p"
label="Primary button as a `p` element"
variant="display"
/>
</Wrapper>

<Wrapper>
<Button fullWidth label="Full width primary button" />
</Wrapper>
Expand Down Expand Up @@ -289,6 +316,14 @@ const secondaryButtons = (
/>
</Wrapper>

<Wrapper>
<Button
element="p"
label="Seconcary button as a `p` element"
variant="display"
/>
</Wrapper>

<Wrapper>
<Button
fullWidth
Expand Down Expand Up @@ -371,6 +406,14 @@ const textButtons = (
/>
</Wrapper>

<Wrapper>
<Button
element="p"
label="Text button as a `p` element"
variant="display"
/>
</Wrapper>

<Wrapper>
<Button fullWidth label="Full width text button" variant="text" />
</Wrapper>
Expand Down
36 changes: 27 additions & 9 deletions packages/fractal/src/components/Button/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import {
type ForwardedRef,
type MouseEvent,
type TouchEvent,
createElement,
forwardRef,
} from 'react'

Expand Down Expand Up @@ -228,11 +229,12 @@ export const variantDisabledStyles: Record<
* `Button` component is used to allow the user to make an interaction on either
* a button or a link element.
*/
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
export const Button = forwardRef<HTMLElement, ButtonProps>(
(
{
children,
disabled = false,
element,
fullWidth = false,
href,
icon,
Expand All @@ -250,7 +252,7 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
wrap = false,
...props
}: ButtonProps,
ref: ForwardedRef<HTMLButtonElement>,
ref: ForwardedRef<HTMLElement>,
) => {
const theme = useTheme(themeOverride)

Expand All @@ -270,9 +272,7 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
}

if ('ontouchstart' in document.documentElement && isFunction(onClick)) {
onClick(
event as unknown as MouseEvent<HTMLAnchorElement | HTMLButtonElement>,
)
onClick(event as unknown as MouseEvent<HTMLElement>)
}
}

Expand Down Expand Up @@ -485,10 +485,28 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
</Typography>
)

if (asLink) {
const content = iconOnly && hasIcon ? iconElement : labelElement

if (element && element !== 'a' && element !== 'button' && !asLink) {
return createElement(
element,
{
'aria-label': label,
className: classNames,
ref,
style: { ...style, ...props.style },
title: label,
...omit(['className', 'style'], props),
},
content,
)
}

if (asLink || element === 'a') {
return (
<a
{...(props.id === undefined ? {} : { id: props.id })}
ref={ref as ForwardedRef<HTMLAnchorElement>}
aria-label={label}
className={classNames}
href={href}
Expand All @@ -498,15 +516,15 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
{...(!disabled && isFunction(onClick) ? { onClick } : {})}
{...omit(['className', 'id', 'style'], props)}
>
{iconOnly && hasIcon ? iconElement : labelElement}
{content}
</a>
)
}

return (
<button
{...(props.id === undefined ? {} : { id: props.id })}
ref={ref}
ref={ref as ForwardedRef<HTMLButtonElement>}
aria-label={label}
className={classNames}
{...(props.dir === undefined
Expand All @@ -524,7 +542,7 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
props,
)}
>
{iconOnly && hasIcon ? iconElement : labelElement}
{content}
</button>
)
},
Expand Down
22 changes: 16 additions & 6 deletions packages/fractal/src/components/Button/Button.types.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import type { AllHTMLAttributes, MouseEvent, ReactNode } from 'react'
import type {
AllHTMLAttributes,
ElementType,
MouseEvent,
ReactNode,
} from 'react'

import { Themes } from '@/constants'

import { Variants } from './Button.constants'

export interface ButtonProps
extends Omit<
AllHTMLAttributes<HTMLAnchorElement | HTMLButtonElement>,
'onClick' | 'wrap'
> {
extends Omit<AllHTMLAttributes<HTMLElement>, 'onClick' | 'wrap'> {
/**
* The content of the button.
*
Expand All @@ -18,6 +20,14 @@ export interface ButtonProps
children?: ReactNode
/** Prevents the user from interacting with the button. */
disabled?: boolean
/**
* The HTML element to use to display your button.
*
* Note that this is ignored if you provide a `href` or an `onClick` props.
* `href` will make the button an `a` element and `onClick` will make it a
* `button` element.
*/
element?: ElementType
/** Indicates if the button should take all the available width. */
fullWidth?: boolean
/** The URL to link to when the button is clicked when `asLink` is used. */
Expand Down Expand Up @@ -50,7 +60,7 @@ export interface ButtonProps
*/
label?: string
/** Event handler called when the button is clicked. */
onClick?: (event: MouseEvent<HTMLAnchorElement | HTMLButtonElement>) => void
onClick?: (event: MouseEvent<HTMLElement>) => void
/** The `target` attribute of the `a` element (when a `href` is provided). */
target?: string
/**
Expand Down

0 comments on commit 4202282

Please sign in to comment.