mirror of
https://github.com/freeCodeCamp/freeCodeCamp.git
synced 2026-04-14 07:00:51 -04:00
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:
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -13,4 +13,6 @@ export interface ButtonProps
|
||||
type?: 'submit' | 'button';
|
||||
disabled?: boolean;
|
||||
block?: boolean;
|
||||
to?: string;
|
||||
target?: React.HTMLAttributeAnchorTarget;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user