feat(ui-components): add support for link to Button component (#45671)

* add active style

* add disable state

* add support for full-width

* add custom focus outline

* feat(ui-components): add support for link to Button component

* Add onClick action

* center content
This commit is contained in:
Huyen Nguyen
2022-04-25 17:47:40 +07:00
committed by GitHub
parent db94165d0f
commit 86935fa59f
4 changed files with 101 additions and 15 deletions

View File

@@ -8,7 +8,16 @@ const story = {
component: Button,
parameters: {
controls: {
include: ['children', 'variant', 'size', 'disabled', 'block']
include: [
'children',
'variant',
'size',
'disabled',
'block',
'to',
'target',
'onClick'
]
}
},
argTypes: {
@@ -25,6 +34,15 @@ const story = {
block: {
options: [true, false],
control: { type: 'radio' }
},
target: {
options: ['_self', '_blank', '_parent', '_top']
},
onClick: {
action: 'clicked'
},
to: {
control: { type: 'text' }
}
}
};
@@ -74,4 +92,10 @@ FullWidth.args = {
block: true
};
export const AsALink = Template.bind({});
AsALink.args = {
children: "I'm a link that looks like a button",
to: 'https://www.freecodecamp.org'
};
export default story;

View File

@@ -71,4 +71,32 @@ describe('Button', () => {
expect(onClick).not.toBeCalled();
});
it('should render an anchor element if the `to` prop is defined', () => {
render(<Button to='https://www.freecodecamp.org'>freeCodeCamp</Button>);
const link = screen.getByRole('link', { name: /freeCodeCamp/i });
const button = screen.queryByRole('button', { name: /freeCodeCamp/i });
expect(link).toBeInTheDocument();
expect(link).toHaveAttribute('href', 'https://www.freecodecamp.org');
// Ensure that a button element is not rendered
expect(button).not.toBeInTheDocument();
});
it('should render a button element if the `to` and `disabled` props are both defined', () => {
render(
<Button to='https://www.freecodecamp.org' disabled>
freeCodeCamp
</Button>
);
const button = screen.getByRole('button', { name: /freeCodeCamp/i });
const link = screen.queryByRole('link', { name: /freeCodeCamp/i });
expect(button).toBeInTheDocument();
expect(button).toHaveAttribute('aria-disabled', 'true');
// Ensure that a link element is not rendered
expect(link).not.toBeInTheDocument();
});
});

View File

@@ -6,6 +6,7 @@ const defaultClassNames = [
'cursor-pointer',
'inline-block',
'border-3',
'text-center',
'active:before:w-full',
'active:before:h-full',
'active:before:absolute',
@@ -38,7 +39,6 @@ const computeClassNames = ({
classNames.push('block', 'w-full');
}
// TODO: support 'link' variant
switch (variant) {
case 'danger':
classNames.push(
@@ -96,7 +96,10 @@ const computeClassNames = ({
return classNames.join(' ');
};
export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
export const Button = React.forwardRef<
HTMLButtonElement | HTMLAnchorElement,
ButtonProps
>(
(
{
variant = 'primary',
@@ -105,7 +108,9 @@ export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
onClick,
children,
disabled,
block
block,
to,
target
},
ref
) => {
@@ -128,17 +133,44 @@ export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
[onClick]
);
return (
<button
ref={ref}
className={classes}
type={type}
onClick={handleClick}
aria-disabled={disabled}
>
{children}
</button>
);
const renderButton = useCallback(() => {
return (
<button
ref={ref as React.ForwardedRef<HTMLButtonElement>}
className={classes}
type={type}
onClick={handleClick}
aria-disabled={disabled}
>
{children}
</button>
);
}, [children, classes, ref, type, handleClick, disabled]);
const renderLink = useCallback(() => {
// Render a `button` tag if `disabled` is defined to keep the component semantically correct
// as a link cannot be disabled.
if (disabled) {
return renderButton();
}
return (
<a
ref={ref as React.ForwardedRef<HTMLAnchorElement>}
className={classes}
href={to}
target={target}
>
{children}
</a>
);
}, [children, classes, ref, disabled, to, target, renderButton]);
if (to) {
return renderLink();
} else {
return renderButton();
}
}
);

View File

@@ -13,4 +13,6 @@ export interface ButtonProps
type?: 'submit' | 'button';
disabled?: boolean;
block?: boolean;
to?: string;
target?: React.HTMLAttributeAnchorTarget;
}