1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-12-23 09:52:26 +01:00

Feat/16290/add button component (#16305)

* add button component

* add all button props

* update tests

* add button type prop

* fix base button size const

* add href prop to button base

* Update ui/components/component-library/button/README.mdx

Co-authored-by: George Marshall <george.marshall@consensys.net>

* Update ui/components/component-library/button/README.mdx

Co-authored-by: George Marshall <george.marshall@consensys.net>

* update tests

* Update ui/components/component-library/button-primary/button-primary.js

Co-authored-by: George Marshall <george.marshall@consensys.net>

* Update ui/components/component-library/button/README.mdx

Co-authored-by: George Marshall <george.marshall@consensys.net>

* Update ui/components/component-library/button/button.stories.js

Co-authored-by: George Marshall <george.marshall@consensys.net>

* Update ui/components/component-library/button/button.js

Co-authored-by: George Marshall <george.marshall@consensys.net>

* Update ui/components/component-library/button/button.stories.js

Co-authored-by: George Marshall <george.marshall@consensys.net>

* update button props on readme

* linting issue fix

Co-authored-by: George Marshall <george.marshall@consensys.net>
This commit is contained in:
Garrett Bear 2022-11-09 13:55:13 -08:00 committed by GitHub
parent 6cca9892b2
commit a28d727caf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 665 additions and 17 deletions

View File

@ -43,12 +43,12 @@ import { ButtonLink } from '../ui/component-library/button/button-link/button-li
<ButtonLink size={SIZES.LG} /> <ButtonLink size={SIZES.LG} />
``` ```
### Type ### Danger
Use the `type` prop and the `BUTTON_TYPES` object from `./ui/helpers/constants/design-system.js` to change the context of `ButtonLink`. Use the `danger` boolean prop to change the `ButtonPrimary` to danger color.
<Canvas> <Canvas>
<Story id="ui-components-component-library-button-link-button-link-stories-js--type" /> <Story id="ui-components-component-library-button-link-button-link-stories-js--danger" />
</Canvas> </Canvas>
```jsx ```jsx

View File

@ -139,7 +139,7 @@ export const Size = (args) => (
</> </>
); );
export const Type = (args) => ( export const Danger = (args) => (
<Box display={DISPLAY.FLEX} gap={1}> <Box display={DISPLAY.FLEX} gap={1}>
<ButtonLink {...args}>Normal</ButtonLink> <ButtonLink {...args}>Normal</ButtonLink>
{/* Test Anchor tag to match exactly as button */} {/* Test Anchor tag to match exactly as button */}

View File

@ -53,7 +53,7 @@ describe('ButtonLink', () => {
); );
}); });
it('should render with different types', () => { it('should render as danger', () => {
const { getByTestId } = render( const { getByTestId } = render(
<> <>
<ButtonLink danger data-testid="danger" /> <ButtonLink danger data-testid="danger" />

View File

@ -41,12 +41,12 @@ import { ButtonPrimary } from '../ui/component-library/button/button-primary/but
<ButtonPrimary size={SIZES.LG} /> <ButtonPrimary size={SIZES.LG} />
``` ```
### Type ### Danger
Use the `type` prop and the `BUTTON_TYPES` object from `./ui/helpers/constants/design-system.js` to change the context of `ButtonPrimary`. Use the `danger` boolean prop to change the `ButtonPrimary` to danger color.
<Canvas> <Canvas>
<Story id="ui-components-component-library-button-primary-button-primary-stories-js--type" /> <Story id="ui-components-component-library-button-primary-button-primary-stories-js--danger" />
</Canvas> </Canvas>
```jsx ```jsx

View File

@ -28,7 +28,7 @@ ButtonPrimary.propTypes = {
*/ */
className: PropTypes.string, className: PropTypes.string,
/** /**
* Boolean to change button type to Danger when true * When true, `ButtonPrimary` color becomes Danger.
*/ */
danger: PropTypes.bool, danger: PropTypes.bool,
/** /**

View File

@ -122,7 +122,7 @@ export const Size = (args) => (
</Box> </Box>
); );
export const Type = (args) => ( export const Danger = (args) => (
<Box display={DISPLAY.FLEX} gap={1}> <Box display={DISPLAY.FLEX} gap={1}>
<ButtonPrimary {...args}>Normal</ButtonPrimary> <ButtonPrimary {...args}>Normal</ButtonPrimary>
{/* Test Anchor tag to match exactly as button */} {/* Test Anchor tag to match exactly as button */}

View File

@ -66,7 +66,7 @@ describe('ButtonPrimary', () => {
); );
}); });
it('should render with different types', () => { it('should render as danger', () => {
const { getByTestId } = render( const { getByTestId } = render(
<> <>
<ButtonPrimary danger data-testid="danger" /> <ButtonPrimary danger data-testid="danger" />

View File

@ -41,12 +41,12 @@ import { ButtonSecondary } from '../ui/component-library/button/button-secondary
<ButtonSecondary size={SIZES.LG} /> <ButtonSecondary size={SIZES.LG} />
``` ```
### Type ### Danger
Use the `type` prop and the `BUTTON_TYPES` object from `./ui/helpers/constants/design-system.js` to change the context of `ButtonSecondary`. Use the `danger` boolean prop to change the `ButtonPrimary` to danger color.
<Canvas> <Canvas>
<Story id="ui-components-component-library-button-secondary-button-secondary-stories-js--type" /> <Story id="ui-components-component-library-button-secondary-button-secondary-stories-js--danger" />
</Canvas> </Canvas>
```jsx ```jsx

View File

@ -28,7 +28,7 @@ ButtonSecondary.propTypes = {
*/ */
className: PropTypes.string, className: PropTypes.string,
/** /**
* Boolean to change button type to Danger when true * When true, ButtonSecondary color becomes Danger.
*/ */
danger: PropTypes.bool, danger: PropTypes.bool,
/** /**

View File

@ -122,7 +122,7 @@ export const Size = (args) => (
</Box> </Box>
); );
export const Type = (args) => ( export const Danger = (args) => (
<Box display={DISPLAY.FLEX} gap={1}> <Box display={DISPLAY.FLEX} gap={1}>
<ButtonSecondary {...args}>Normal</ButtonSecondary> <ButtonSecondary {...args}>Normal</ButtonSecondary>
{/* Test Anchor tag to match exactly as button */} {/* Test Anchor tag to match exactly as button */}

View File

@ -68,7 +68,7 @@ describe('ButtonSecondary', () => {
); );
}); });
it('should render with different types', () => { it('should render as danger', () => {
const { getByTestId } = render( const { getByTestId } = render(
<> <>
<ButtonSecondary danger data-testid="danger" /> <ButtonSecondary danger data-testid="danger" />

View File

@ -0,0 +1,178 @@
import { Story, Canvas, ArgsTable } from '@storybook/addon-docs';
import { Button } from './button';
# Button
The `Button` is used for user actions it unifies `ButtonPrimary`, `ButtonSecondary` and `ButtonLink`
<Canvas>
<Story id="ui-components-component-library-button-button-stories-js--default-story" />
</Canvas>
## Props
The `Button` accepts all props below as well as all [ButtonPrimary](/ui-components-component-library-button-primary-button-primary-stories-js--default-story), [ButtonSecondary](/ui-components-component-library-button-secondary-button-secondary-stories-js--default-story), [ButtonLink](/ui-components-component-library-button-link-button-link-stories-js--default-story), and [Box](/docs/ui-components-ui-box-box-stories-js--default-story#props) component props
<ArgsTable of={Button} />
### Type
Use the `type` prop and the `BUTTON_TYPES` object from `./button.constants.js` to change the `Button` type.
Possible types include:
- `BUTTON_TYPES.PRIMARY`
- `BUTTON_TYPES.SECONDARY`
- `BUTTON_TYPES.LINK`
<Canvas>
<Story id="ui-components-component-library-button-button-stories-js--type" />
</Canvas>
```jsx
import { Button, BUTTON_TYPES } from '../ui/component-library/button';
<Button type={BUTTON_TYPES.PRIMARY}>Button Primary</Button>
<Button type={BUTTON_TYPES.SECONDARY}>Button Secondary</Button>
<Button type={BUTTON_TYPES.LINK}>Button Link</Button>
```
### Size
Use the `size` prop and the `SIZES` object from `./ui/helpers/constants/design-system.js` to change the size of `Button`. Defaults to `SIZES.MD`
Optional: `BUTTON_SIZES` from `./button` object can be used instead of `SIZES`.
Possible sizes include:
- `SIZES.AUTO` inherits the font-size of the parent element.
- `SIZES.SM` 32px
- `SIZES.MD` 40px
- `SIZES.LG` 48px
<Canvas>
<Story id="ui-components-component-library-button-button-stories-js--size" />
</Canvas>
```jsx
import { SIZES } from '../../../helpers/constants/design-system';
import { Button } from '../ui/component-library/button/button/button';
<Button size={SIZES.AUTO} />
<Button size={SIZES.SM} />
<Button size={SIZES.MD} />
<Button size={SIZES.LG} />
```
### Danger
Use the `danger` boolean prop to change the `Button` to danger color.
<Canvas>
<Story id="ui-components-component-library-button-button-stories-js--danger" />
</Canvas>
```jsx
import { Button } from '../ui/component-library/button/button/button';
<Button>Normal</Button>
<Button danger>Danger</Button>
```
### Href
When an `href` is passed the tag element will switch to an `anchor`(`a`) tag.
<Canvas>
<Story id="ui-components-component-library-button-button-stories-js--href" />
</Canvas>
```jsx
import { Button } from '../ui/component-library/button/button/button';
<Button href="/">Href Example</Button>;
```
### Block
Use boolean `block` prop to quickly enable a full width block button
<Canvas>
<Story id="ui-components-component-library-button-button-stories-js--block" />
</Canvas>
```jsx
import { DISPLAY } from '../../../helpers/constants/design-system';
import { Button } from '../ui/component-library';
<Button>Default Button</Button>
<Button block>Block Button</Button>
```
### As
Use the `as` box prop to change the element of `Button`. Defaults to `button`.
When an `href` prop is passed it will change the element to an anchor(`a`) tag.
Button `as` options:
- `button`
- `a`
<Canvas>
<Story id="ui-components-component-library-button-button-stories-js--as" />
</Canvas>
```jsx
import { Button } from '../ui/component-library';
<Button as="button">Button Element</Button>
<Button as="a" href="#">
Anchor Element
</Button>
```
### Disabled
Use the boolean `disabled` prop to disable button
<Canvas>
<Story id="ui-components-component-library-button-button-stories-js--disabled" />
</Canvas>
```jsx
import { Button } from '../ui/component-library';
<Button disabled>Disabled Button</Button>;
```
### Loading
Use the boolean `loading` prop to set loading spinner
<Canvas>
<Story id="ui-components-component-library-button-button-stories-js--loading" />
</Canvas>
```jsx
import { Button } from '../ui/component-library';
<Button loading>Loading Button</Button>;
```
### Icon
Use the `icon` prop and the `ICON_NAMES` object from `./ui/components/component-library/icon` to select icon.
<Canvas>
<Story id="ui-components-component-library-button-button-stories-js--icon" />
</Canvas>
```jsx
import { Button } from '../ui/component-library';
import { ICON_NAMES } from '../icon';
<Button icon={ICON_NAMES.ADD_SQUARE_FILLED}>Button</Button>;
```

View File

@ -0,0 +1,51 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Button should render button element correctly 1`] = `
<div>
<button
class="box mm-button mm-button--size-md mm-button-primary box--padding-right-4 box--padding-left-4 box--display-inline-flex box--flex-direction-row box--justify-content-center box--align-items-center"
data-testid="button"
>
<span
class="box text mm-button__content text--body-md text--color-inherit box--gap-2 box--flex-direction-row box--justify-content-center box--align-items-center box--display-flex"
>
Button
</span>
</button>
</div>
`;
exports[`Button should render with different button types 1`] = `
<div>
<button
class="box mm-button mm-button--size-md mm-button-primary box--padding-right-4 box--padding-left-4 box--display-inline-flex box--flex-direction-row box--justify-content-center box--align-items-center"
data-testid="primary"
>
<span
class="box text mm-button__content text--body-md text--color-inherit box--gap-2 box--flex-direction-row box--justify-content-center box--align-items-center box--display-flex"
>
Button
</span>
</button>
<button
class="box mm-button mm-button--size-md mm-button-secondary box--padding-right-4 box--padding-left-4 box--display-inline-flex box--flex-direction-row box--justify-content-center box--align-items-center"
data-testid="secondary"
>
<span
class="box text mm-button__content text--body-md text--color-inherit box--gap-2 box--flex-direction-row box--justify-content-center box--align-items-center box--display-flex"
>
Button
</span>
</button>
<button
class="box mm-button mm-button--size-md mm-button-link box--padding-right-4 box--padding-left-4 box--display-inline-flex box--flex-direction-row box--justify-content-center box--align-items-center"
data-testid="link"
>
<span
class="box text mm-button__content text--body-md text--color-inherit box--gap-2 box--flex-direction-row box--justify-content-center box--align-items-center box--display-flex"
>
Button
</span>
</button>
</div>
`;

View File

@ -0,0 +1,14 @@
import { SIZES } from '../../../helpers/constants/design-system';
export const BUTTON_SIZES = {
SM: SIZES.SM,
MD: SIZES.MD,
LG: SIZES.LG,
AUTO: SIZES.AUTO,
};
export const BUTTON_TYPES = {
PRIMARY: 'primary',
SECONDARY: 'secondary',
LINK: 'link',
};

View File

@ -0,0 +1,34 @@
import React from 'react';
import PropTypes from 'prop-types';
import { ButtonPrimary } from '../button-primary';
import { ButtonSecondary } from '../button-secondary';
import { ButtonLink } from '../button-link';
import { BUTTON_TYPES } from './button.constants';
export const Button = ({ type, ...props }) => {
switch (type) {
case BUTTON_TYPES.PRIMARY:
return <ButtonPrimary {...props} />;
case BUTTON_TYPES.SECONDARY:
return <ButtonSecondary {...props} />;
case BUTTON_TYPES.LINK:
return <ButtonLink {...props} />;
default:
return <ButtonPrimary {...props} />;
}
};
Button.propTypes = {
/**
* Select the type of Button.
* Possible values could be 'BUTTON_TYPES.PRIMARY', 'BUTTON_TYPES.SECONDARY', 'BUTTON_TYPES.LINK'
* Button will default to `BUTTON_TYPES.PRIMARY`
*/
type: PropTypes.oneOf(Object.values(BUTTON_TYPES)),
/**
* Button accepts all the props from ButtonPrimary (same props as ButtonSecondary & ButtonLink)
*/
...ButtonPrimary.propTypes,
};

View File

@ -0,0 +1,212 @@
import React from 'react';
import {
ALIGN_ITEMS,
DISPLAY,
FLEX_DIRECTION,
SIZES,
TEXT,
} from '../../../helpers/constants/design-system';
import { ICON_NAMES } from '../icon';
import { BUTTON_LINK_SIZES } from '../button-link/button-link.constants';
import Box from '../../ui/box/box';
import { Text } from '../text';
import README from './README.mdx';
import { Button, BUTTON_TYPES } from '.';
const marginSizeControlOptions = [
undefined,
0,
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
'auto',
];
export default {
title: 'Components/ComponentLibrary/Button',
id: __filename,
component: Button,
parameters: {
docs: {
page: README,
},
controls: { sort: 'alpha' },
},
argTypes: {
as: {
control: 'select',
options: ['button', 'a'],
},
block: {
control: 'boolean',
},
children: {
control: 'text',
},
className: {
control: 'text',
},
danger: {
control: 'boolean',
},
disabled: {
control: 'boolean',
},
href: {
control: 'text',
},
icon: {
control: 'select',
options: Object.values(ICON_NAMES),
},
iconPositionRight: {
control: 'boolean',
},
iconProps: {
control: 'object',
},
loading: {
control: 'boolean',
},
size: {
control: 'select',
options: Object.values(BUTTON_LINK_SIZES),
},
type: {
options: Object.values(BUTTON_TYPES),
control: 'select',
},
marginTop: {
options: marginSizeControlOptions,
control: 'select',
table: { category: 'box props' },
},
marginRight: {
options: marginSizeControlOptions,
control: 'select',
table: { category: 'box props' },
},
marginBottom: {
options: marginSizeControlOptions,
control: 'select',
table: { category: 'box props' },
},
marginLeft: {
options: marginSizeControlOptions,
control: 'select',
table: { category: 'box props' },
},
},
args: {
children: 'Button',
},
};
export const DefaultStory = (args) => <Button {...args} />;
DefaultStory.storyName = 'Default';
export const Type = (args) => (
<Box display={DISPLAY.FLEX} gap={1}>
<Button type={BUTTON_TYPES.PRIMARY} {...args}>
Button Primary
</Button>
<Button type={BUTTON_TYPES.SECONDARY} {...args}>
Button Secondary
</Button>
<Button type={BUTTON_TYPES.LINK} {...args}>
Button Link
</Button>
</Box>
);
export const Size = (args) => (
<>
<Box
display={DISPLAY.FLEX}
alignItems={ALIGN_ITEMS.BASELINE}
gap={1}
marginBottom={3}
>
<Button {...args} size={SIZES.SM}>
Small Button
</Button>
<Button {...args} size={SIZES.MD}>
Medium (Default) Button
</Button>
<Button {...args} size={SIZES.LG}>
Large Button
</Button>
</Box>
<Text variant={TEXT.BODY_SM}>
<Button {...args} type={BUTTON_TYPES.LINK} size={SIZES.AUTO}>
Button Auto
</Button>{' '}
inherits the font-size of the parent element. Auto size only used for
ButtonLink.
</Text>
</>
);
export const Danger = (args) => (
<Box display={DISPLAY.FLEX} gap={1}>
<Button {...args}>Normal</Button>
{/* Test Anchor tag to match exactly as button */}
<Button as="a" {...args} href="#" danger>
Danger
</Button>
</Box>
);
export const Href = (args) => <Button {...args}>Href Example</Button>;
Href.args = {
href: '/metamask',
};
export const Block = (args) => (
<>
<Button {...args} marginBottom={2}>
Default Button
</Button>
<Button {...args} block marginBottom={2}>
Block Button
</Button>
</>
);
export const As = (args) => (
<Box display={DISPLAY.FLEX} flexDirection={FLEX_DIRECTION.ROW} gap={2}>
<Button {...args}>Button Element</Button>
<Button as="a" href="#" {...args}>
Anchor Element
</Button>
</Box>
);
export const Disabled = (args) => <Button {...args}>Disabled Button</Button>;
Disabled.args = {
disabled: true,
};
export const Loading = (args) => <Button {...args}>Loading Button</Button>;
Loading.args = {
loading: true,
};
export const Icon = (args) => (
<Button {...args} icon={ICON_NAMES.ADD_SQUARE_FILLED}>
Button
</Button>
);

View File

@ -0,0 +1,157 @@
/* eslint-disable jest/require-top-level-describe */
import { render } from '@testing-library/react';
import React from 'react';
import { BUTTON_SIZES, BUTTON_TYPES } from './button.constants';
import { Button } from './button';
describe('Button', () => {
it('should render button element correctly', () => {
const { getByTestId, getByText, container } = render(
<Button data-testid="button">Button</Button>,
);
expect(getByText('Button')).toBeDefined();
expect(container.querySelector('button')).toBeDefined();
expect(getByTestId('button')).toHaveClass('mm-button');
expect(container).toMatchSnapshot();
});
it('should render anchor element correctly', () => {
const { getByTestId, container } = render(
<Button as="a" data-testid="button">
Button
</Button>,
);
expect(getByTestId('button')).toHaveClass('mm-button');
const anchor = container.getElementsByTagName('a').length;
expect(anchor).toBe(1);
});
it('should render anchor element correctly by href only being passed', () => {
const { getByTestId, container } = render(
<Button href="/metamask" data-testid="button">
Visit Site
</Button>,
);
expect(getByTestId('button')).toHaveClass('mm-button');
const anchor = container.getElementsByTagName('a').length;
expect(anchor).toBe(1);
});
it('should render button as block', () => {
const { getByTestId } = render(<Button block data-testid="block" />);
expect(getByTestId('block')).toHaveClass(`mm-button--block`);
});
it('should render with different button types', () => {
const { getByTestId, container } = render(
<>
<Button type={BUTTON_TYPES.PRIMARY} data-testid={BUTTON_TYPES.PRIMARY}>
Button
</Button>
<Button
type={BUTTON_TYPES.SECONDARY}
data-testid={BUTTON_TYPES.SECONDARY}
>
Button
</Button>
<Button type={BUTTON_TYPES.LINK} data-testid={BUTTON_TYPES.LINK}>
Button
</Button>
</>,
);
expect(getByTestId(BUTTON_TYPES.PRIMARY)).toHaveClass(
`mm-button-${BUTTON_TYPES.PRIMARY}`,
);
expect(getByTestId(BUTTON_TYPES.SECONDARY)).toHaveClass(
`mm-button-${BUTTON_TYPES.SECONDARY}`,
);
expect(getByTestId(BUTTON_TYPES.LINK)).toHaveClass(
`mm-button-${BUTTON_TYPES.LINK}`,
);
expect(container).toMatchSnapshot();
});
it('should render with different size classes', () => {
const { getByTestId } = render(
<>
<Button
size={BUTTON_SIZES.AUTO}
type={BUTTON_TYPES.LINK}
data-testid={BUTTON_SIZES.AUTO}
>
Button {BUTTON_SIZES.AUTO}
</Button>
<Button size={BUTTON_SIZES.SM} data-testid={BUTTON_SIZES.SM}>
Button {BUTTON_SIZES.SM}
</Button>
<Button size={BUTTON_SIZES.MD} data-testid={BUTTON_SIZES.MD}>
Button {BUTTON_SIZES.MD}
</Button>
<Button size={BUTTON_SIZES.LG} data-testid={BUTTON_SIZES.LG}>
Button {BUTTON_SIZES.LG}
</Button>
</>,
);
expect(getByTestId(BUTTON_SIZES.AUTO)).toHaveClass(
`mm-button--size-${BUTTON_SIZES.AUTO}`,
);
expect(getByTestId(BUTTON_SIZES.SM)).toHaveClass(
`mm-button--size-${BUTTON_SIZES.SM}`,
);
expect(getByTestId(BUTTON_SIZES.MD)).toHaveClass(
`mm-button--size-${BUTTON_SIZES.MD}`,
);
expect(getByTestId(BUTTON_SIZES.LG)).toHaveClass(
`mm-button--size-${BUTTON_SIZES.LG}`,
);
});
it('should render with added classname', () => {
const { getByTestId } = render(
<Button data-testid="classname" className="mm-button--test">
Button
</Button>,
);
expect(getByTestId('classname')).toHaveClass('mm-button--test');
});
it('should render with different button states', () => {
const { getByTestId } = render(
<>
<Button loading data-testid="loading">
Button
</Button>
<Button disabled data-testid="disabled">
Button
</Button>
</>,
);
expect(getByTestId('loading')).toHaveClass(`mm-button--loading`);
expect(getByTestId('disabled')).toHaveClass(`mm-button--disabled`);
});
it('should render with icon', () => {
const { getByTestId } = render(
<Button
data-testid="icon"
icon="add-square-filled"
iconProps={{ 'data-testid': 'base-button-icon' }}
>
Button
</Button>,
);
expect(getByTestId('base-button-icon')).toBeDefined();
});
});
it('should render as danger', () => {
const { getByTestId } = render(
<>
<Button danger data-testid="danger">
Button Danger
</Button>
</>,
);
expect(getByTestId('danger')).toHaveClass('mm-button-primary--type-danger');
});

View File

@ -0,0 +1,2 @@
export { Button } from './button';
export { BUTTON_TYPES } from './button.constants';