fix(Button): redesign of existing Button component for consistency (#3473)

This commit is contained in:
Joe Boccanfuso authored and GitHub committed 2023-06-15 12:58:21 -04:00
1 parent 3dce70e624
commit cc73f15c8c
47 files changed
+738 -397

No files matched your search

+63 -187
View File
@@ -1,181 +1,89 @@
import React, { useRef } from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import * as ButtonEnums from './ButtonEnums';
const baseClasses =
'leading-none font-sans text-center justify-center items-center outline-none transition duration-300 ease-in-out focus:outline-none';
const sizeClasses = {
[ButtonEnums.size.small]: 'h-[26px] text-[13px]',
[ButtonEnums.size.medium]: 'h-[32px] text-[14px]',
};
const layoutClasses =
'box-content inline-flex flex-row items-center justify-center gap-[5px] justify center px-[10px] outline-none rounded';
const baseFontTextClasses =
'leading-[1.2] font-sans text-center whitespace-nowrap';
const fontTextClasses = {
[ButtonEnums.type.primary]: classnames(baseFontTextClasses, 'font-semibold'),
[ButtonEnums.type.secondary]: classnames(baseFontTextClasses, 'font-400'),
};
const baseEnabledEffectClasses =
'transition duration-300 ease-in-out focus:outline-none';
const enabledEffectClasses = {
[ButtonEnums.type.primary]: classnames(
baseEnabledEffectClasses,
'hover:bg-customblue-80 active:bg-customblue-40'
),
[ButtonEnums.type.secondary]: classnames(
baseEnabledEffectClasses,
'hover:bg-customblue-50 active:bg-customblue-20'
),
};
const baseEnabledClasses = 'text-white';
const enabledClasses = {
[ButtonEnums.type.primary]: classnames(
'bg-primary-main',
baseEnabledClasses,
enabledEffectClasses[ButtonEnums.type.primary]
),
[ButtonEnums.type.secondary]: classnames(
'bg-customblue-30',
baseEnabledClasses,
enabledEffectClasses[ButtonEnums.type.secondary]
),
};
const disabledClasses =
'bg-inputfield-placeholder text-common-light cursor-default';
const defaults = {
color: 'default',
disabled: false,
fullWidth: false,
rounded: 'medium',
border: 'none',
size: 'medium',
type: 'button',
variant: 'contained',
};
const roundedClasses = {
none: '',
small: 'rounded',
medium: 'rounded-md',
large: 'rounded-lg',
full: 'rounded-full',
};
const variants = {
text: {
default:
'text-primary-light hover:bg-primary-light hover:text-white active:opacity-80 focus:bg-primary-light focus:text-white',
primary:
'text-primary-main hover:bg-primary-main hover:text-white active:opacity-80 focus:bg-primary-main focus:text-white',
secondary:
'text-secondary-light hover:bg-secondary-light hover:text-white active:opacity-80 focus:bg-secondary-light focus:text-white',
white:
'text-white hover:bg-white hover:text-black active:opacity-80 focus:bg-white focus:text-black',
black:
'text-black hover:bg-black hover:text-white focus:bg-black focus:text-white active:opacity-80',
},
outlined: {
default:
'text-primary-light hover:bg-primary-light hover:text-black focus:text-black focus:bg-primary-light active:opacity-80',
primary:
'text-primary-main hover:opacity-80 active:opacity-100 focus:opacity-80',
secondary:
'text-secondary-light hover:opacity-80 active:opacity-100 focus:opacity-80',
translucent:
'text-white hover:opacity-80 active:opacity-100 focus:opacity-80',
white:
'text-black hover:bg-primary-main focus:bg-primary-main hover:border-black focus:border-black',
black:
'text-white hover:bg-primary-main focus:bg-primary-main hover:border-black focus:border-black',
primaryActive:
'text-primary-active hover:opacity-80 active:opacity-100 focus:opacity-80',
primaryLight:
'border bg-transparent border-primary-main text-white hover:opacity-80 active:opacity-100 focus:opacity-80',
},
contained: {
default: 'text-black hover:opacity-80 active:opacity-100 focus:opacity-80',
primary: 'text-white hover:opacity-80 active:opacity-100 focus:opacity-80',
primaryDark:
'text-primary-active hover:opacity-80 active:opacity-100 focus:opacity-80',
secondary:
'text-white hover:opacity-80 active:opacity-100 focus:opacity-80',
white: 'text-black hover:opacity-80 active:opacity-100 focus:opacity-80',
black: 'text-white hover:opacity-80 active:opacity-100 focus:opacity-80',
light:
'border text-black hover:opacity-80 active:opacity-100 focus:opacity-80',
},
disabled: {
default: 'cursor-not-allowed opacity-50 text-black',
primary: 'cursor-not-allowed opacity-50 text-white',
secondary: 'cursor-not-allowed opacity-50 text-white',
white: 'cursor-not-allowed opacity-50 text-black',
black: 'cursor-not-allowed opacity-50 text-white',
light: 'cursor-not-allowed opacity-50 border text-black',
},
};
const defaultVariantBackGroundColor = {
text: {
default: '',
primary: '',
secondary: '',
white: '',
black: '',
},
outlined: {
default: 'bg-transparent',
primary: 'bg-transparent',
secondary: 'bg-transparent',
black: 'bg-black',
white: '',
},
contained: {
default: 'bg-primary-light',
primary: 'bg-primary-main',
primaryDark: 'bg-primary-dark',
secondary: 'bg-secondary-light',
white: 'bg-white',
black: 'bg-black',
light: 'bg-primary-light',
},
disabled: {
default: 'bg-primary-light',
primary: 'bg-primary-main',
secondary: 'bg-secondary-light',
white: 'bg-white',
black: 'bg-black',
light: 'bg-primary-light',
},
};
const _getVariantClass = (variant, color, bgColor = null) => {
const defaultBackgroundColor = defaultVariantBackGroundColor[variant][color];
if (!bgColor) {
bgColor = defaultBackgroundColor;
}
return `${variants[variant][color]} ${bgColor}`;
};
const borderClasses = {
none: '',
light: 'border border-primary-light',
primary: 'border border-primary-main',
primaryActive: 'border border-primary-active',
secondary: 'border border-secondary-light',
white: 'border border-white',
black: 'border border-black',
};
const sizeClasses = {
small: 'py-2 px-2 text-sm min-w-md',
medium: 'py-2 px-2 text-lg min-w-md',
large: 'py-2 px-6 text-xl min-w-md',
initial: '',
};
const fullWidthClasses = {
true: 'flex w-full',
false: 'inline-flex',
rounded: 'small',
size: ButtonEnums.size.medium,
type: ButtonEnums.type.primary,
};
const Button = ({
children,
variant = defaults.variant,
color = defaults.color,
border = defaults.border,
size = defaults.size,
rounded = defaults.rounded,
disabled = defaults.disabled,
type = defaults.type,
fullWidth = defaults.fullWidth,
bgColor = null,
startIcon: startIconProp,
endIcon: endIconProp,
name,
className,
onClick,
/** TODO: All possible props should be explicitly defined -- avoid spreading props */
...rest
}) => {
const startIcon = startIconProp && (
<div className="mr-2">
<>
{React.cloneElement(startIconProp, {
className: classnames('w-4 h-4 fill-current'),
})}
</div>
</>
);
const endIcon = endIconProp && (
<div className="ml-2">
<>
{React.cloneElement(endIconProp, {
className: classnames('w-4 h-4 fill-current'),
})}
</div>
</>
);
const buttonElement = useRef(null);
@@ -187,12 +95,11 @@ const Button = ({
};
const finalClassName = classnames(
baseClasses,
_getVariantClass(variant, color, bgColor),
borderClasses[border],
roundedClasses[rounded],
layoutClasses,
fontTextClasses[type],
disabled ? disabledClasses : enabledClasses[type],
sizeClasses[size],
fullWidthClasses[fullWidth],
children ? 'min-w-[32px]' : '', // minimum width for buttons with text; icon only button does NOT get a minimum width
className
);
@@ -202,9 +109,7 @@ const Button = ({
disabled={disabled}
ref={buttonElement}
onClick={handleOnClick}
type={type}
data-cy={`${name}-btn`}
{...rest}
>
{startIcon}
{children}
@@ -214,10 +119,11 @@ const Button = ({
};
Button.defaultProps = {
color: defaults.color,
disabled: false,
children: '',
onClick: () => {},
type: defaults.type,
size: defaults.size,
};
Button.propTypes = {
@@ -226,39 +132,11 @@ Button.propTypes = {
/** Callback to be called when the button is clicked */
onClick: PropTypes.func.isRequired,
/** Button size */
size: PropTypes.oneOf(['small', 'medium', 'large', 'initial', 'inherit']),
/** Button corner roundness */
rounded: PropTypes.oneOf(['none', 'small', 'medium', 'large', 'full']),
variant: PropTypes.oneOf(['text', 'outlined', 'contained', 'disabled']),
/* color prop must have all the possible keys of variants defined above */
color: PropTypes.oneOf([
'default',
'primary',
'primaryDark',
'primaryActive',
'secondary',
'white',
'black',
'inherit',
'light',
'translucent',
]),
border: PropTypes.oneOf([
'none',
'light',
'default',
'primary',
'primaryActive',
'secondary',
'white',
'black',
]),
/** Whether the button should have full width */
fullWidth: PropTypes.bool,
size: PropTypes.oneOf([ButtonEnums.size.medium, ButtonEnums.size.small]),
/** Whether the button should be disabled */
disabled: PropTypes.bool,
/** Button type */
type: PropTypes.string,
type: PropTypes.oneOf([ButtonEnums.type.primary, ButtonEnums.type.secondary]),
name: PropTypes.string,
/** Button start icon name - if any icon is specified */
startIcon: PropTypes.node,
@@ -266,8 +144,6 @@ Button.propTypes = {
endIcon: PropTypes.node,
/** Additional TailwindCSS classnames */
className: PropTypes.string,
/** Background color for the button to override*/
bgColor: PropTypes.string,
};
export default Button;
@@ -0,0 +1,10 @@
enum type {
primary = 'primary',
secondary = 'secondary',
}
enum size {
medium = 'medium',
small = 'small',
}
export { type, size };
@@ -1,4 +1,4 @@
import Button from '../Button';
import Button, {ButtonType, ButtonSize} from '../Button';
import { ArgsTable, Story, Canvas, Meta } from '@storybook/addon-docs';
import {
createComponentTemplate,
@@ -24,7 +24,7 @@ export const buttonTemplate = createComponentTemplate(Button);
## Overview
You can use the button component to create a button. It can be used in different
ways, the default button is a simple button with a text.
ways, the default button is a simple button with text.
<Canvas>
<Story name="Overview" args={{ children: 'Button', color: 'default' }}>
@@ -38,37 +38,29 @@ ways, the default button is a simple button with a text.
## Usage
### Variants
### Types
There can be different variants of buttons: `text`, `outlined`, `contained`, and
`disabled`.
There can be different types of buttons: `primary`, and `secondary`.
<Canvas>
<Story name="Variants">
<Story name="Types">
<div className="flex space-x-2">
<Button variant="text">Text Button</Button>
<Button variant="outlined">Outlined Button</Button>
<Button variant="contained">Contained Button</Button>
<Button variant="disabled">Disabled Button</Button>
<Button type={ButtonType.primary}>Primary Button</Button>
<Button type={ButtonType.secondary}>Secondary Button</Button>
</div>
</Story>
</Canvas>
### Colors
### Sizes
There are different colors for the button: `default`, `primary`, `secondary`,
`white`, `black`, `inherit`, `light`.
There are different sizes for the button: `small`, and `medium`. The size refers
to the button's height.
<Canvas>
<Story name="Colors">
<div className="flex space-x-2">
<Button color="default">Default Button</Button>
<Button color="primary">Primary Button</Button>
<Button color="secondary">Secondary Button</Button>
<Button color="white">White Button</Button>
<Button color="black">Black Button</Button>
<Button color="inherit">Inherit Button</Button>
<Button color="light">Light Button</Button>
<div className="flex items-center space-x-2">
<Button size={ButtonSize.small}>Small Button</Button>
<Button size={ButtonSize.medium}>Medium Button</Button>
</div>
</Story>
</Canvas>
@@ -79,19 +71,19 @@ You can mix different props together to create a button.
<Canvas>
<Story name="Custom">
<Button color="secondary" variant="outlined">
Custom Button
<Button type={ButtonType.secondary} size={ButtonSize.small}>
Small, Secondary Button
</Button>
</Story>
</Canvas>
### Disabled
You can disable the button by setting the variant to `disabled`.
You can disable the button by setting the `disabled` property to true.
<Canvas>
<Story name="Disabled">
<Button variant="disabled">Disabled Button</Button>
<Button disabled={true}>Disabled Button</Button>
</Story>
</Canvas>
@@ -159,18 +151,6 @@ End Icon is the same as start icon, but for the end of the button.
</Story>
</Canvas>
### Full width
You can make the button full width by setting the `fullWidth` prop to `true`.
<Canvas>
<Story name="Full Width">
<Button fullWidth color="secondary">
Full Width Button
</Button>
</Story>
</Canvas>
## Contribute
<Footer componentRelativePath="Button/__stories__/button.stories.mdx" />
@@ -1,2 +1,5 @@
import Button from './Button';
import * as ButtonEnums from './ButtonEnums';
export { ButtonEnums };
export default Button;