Feat/15089/add button link (#16115)

* 15089: add button link component
feature/default_network_editable
Garrett Bear 2 years ago committed by GitHub
parent c09e685bce
commit 91df7122f2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      ui/components/component-library/button-base/README.mdx
  2. 3
      ui/components/component-library/button-base/button-base.js
  3. 9
      ui/components/component-library/button-base/button-base.test.js
  4. 73
      ui/components/component-library/button-link/README.mdx
  5. 8
      ui/components/component-library/button-link/button-link.constants.js
  6. 45
      ui/components/component-library/button-link/button-link.js
  7. 43
      ui/components/component-library/button-link/button-link.scss
  8. 156
      ui/components/component-library/button-link/button-link.stories.js
  9. 84
      ui/components/component-library/button-link/button-link.test.js
  10. 1
      ui/components/component-library/button-link/index.js
  11. 1
      ui/components/component-library/component-library-components.scss

@ -65,6 +65,8 @@ import { ButtonBase } from '../ui/component-library';
Use the `as` box prop to change the element of `ButtonBase`. Defaults to `button`.
When an `href` prop is passed it will change the element to an anchor(`a`) tag.
Button `as` options:
- `button`

@ -30,9 +30,10 @@ export const ButtonBase = ({
iconProps,
...props
}) => {
const Tag = props?.href ? 'a' : as;
return (
<Box
as={as}
as={Tag}
paddingLeft={size === BUTTON_SIZES.AUTO ? 0 : 4}
paddingRight={size === BUTTON_SIZES.AUTO ? 0 : 4}
className={classnames(

@ -23,6 +23,15 @@ describe('ButtonBase', () => {
expect(anchor).toBe(1);
});
it('should render anchor element correctly by href only being passed', () => {
const { getByTestId, container } = render(
<ButtonBase href="#" data-testid="button-base" />,
);
expect(getByTestId('button-base')).toHaveClass('mm-button');
const anchor = container.getElementsByTagName('a').length;
expect(anchor).toBe(1);
});
it('should render button as block', () => {
const { getByTestId } = render(<ButtonBase block data-testid="block" />);
expect(getByTestId('block')).toHaveClass(`mm-button--block`);

@ -0,0 +1,73 @@
import { Story, Canvas, ArgsTable } from '@storybook/addon-docs';
import { ButtonLink } from './button-link';
# ButtonLink
The `ButtonLink` is an extension of `ButtonBase` to support link styles.
<Canvas>
<Story id="ui-components-component-library-button-link-button-link-stories-js--default-story" />
</Canvas>
## Props
The `ButtonLink` accepts all props below as well as all [Box](/docs/ui-components-ui-box-box-stories-js--default-story#props) and [ButtonBase](/docs/ui-components-component-library-button-base-button-base-stories-js--default-story#props) component props
<ArgsTable of={ButtonLink} />
### Size
Use the `size` prop and the `SIZES` object from `./ui/helpers/constants/design-system.js` to change the size of `ButtonLink`. Defaults to `SIZES.MD`
Optional: `BUTTON_SIZES` from `./button-base` 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-link-button-link-stories-js--size" />
</Canvas>
```jsx
import { SIZES } from '../../../helpers/constants/design-system';
import { ButtonLink } from '../ui/component-library/button/button-link/button-link';
<ButtonLink size={SIZES.AUTO} />
<ButtonLink size={SIZES.SM} />
<ButtonLink size={SIZES.MD} />
<ButtonLink size={SIZES.LG} />
```
### Type
Use the `type` prop and the `BUTTON_TYPES` object from `./ui/helpers/constants/design-system.js` to change the context of `ButtonLink`.
<Canvas>
<Story id="ui-components-component-library-button-link-button-link-stories-js--type" />
</Canvas>
```jsx
import { ButtonLink } from '../ui/component-library/button/button-link/button-link';
<ButtonLink>Normal</ButtonLink>
<ButtonLink danger>Danger</ButtonLink>
```
### Href
When an `href` is passed the tag element will switch to an `anchor`(`a`) tag.
<Canvas>
<Story id="ui-components-component-library-button-link-button-link-stories-js--href" />
</Canvas>
```jsx
import { ButtonLink } from '../ui/component-library/button/button-link/button-link';
<ButtonLink href="/">Href Example</ButtonLink>;
```

@ -0,0 +1,8 @@
import { SIZES } from '../../../helpers/constants/design-system';
export const BUTTON_LINK_SIZES = {
SM: SIZES.SM,
MD: SIZES.MD,
LG: SIZES.LG,
AUTO: SIZES.AUTO,
};

@ -0,0 +1,45 @@
import React from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import { ButtonBase } from '../button-base';
import { BUTTON_LINK_SIZES } from './button-link.constants';
export const ButtonLink = ({
className,
danger,
size = BUTTON_LINK_SIZES.MD,
...props
}) => {
return (
<ButtonBase
className={classnames(className, 'mm-button-link', {
'mm-button-link--type-danger': danger,
})}
size={size}
{...props}
/>
);
};
ButtonLink.propTypes = {
/**
* An additional className to apply to the ButtonLink.
*/
className: PropTypes.string,
/**
* Boolean to change button type to Danger when true
*/
danger: PropTypes.bool,
/**
* The possible size values for ButtonLink: 'SIZES.AUTO', 'SIZES.SM', 'SIZES.MD', 'SIZES.LG',
* Default value is 'SIZES.MD'.
*/
size: PropTypes.oneOf(Object.values(BUTTON_LINK_SIZES)),
/**
* ButtonLink accepts all the props from ButtonBase
*/
...ButtonBase.propTypes,
};
export default ButtonLink;

@ -0,0 +1,43 @@
.mm-button-link {
color: var(--color-primary-default);
background-color: transparent;
&:hover {
color: var(--color-primary-default);
opacity: 0.5;
}
&:active {
color: var(--color-primary-default);
opacity: 0.5;
}
&.mm-button--disabled {
&:hover {
color: var(--color-primary-default);
opacity: 0.3;
}
&:active {
opacity: 0.3;
}
}
&--type-danger {
color: var(--color-error-default);
&:hover {
color: var(--color-error-default);
opacity: 0.5;
}
&:active {
color: var(--color-error-default);
opacity: 0.5;
}
&.mm-button--disabled:hover {
color: var(--color-error-default);
}
}
}

@ -0,0 +1,156 @@
import React from 'react';
import {
ALIGN_ITEMS,
DISPLAY,
SIZES,
TEXT,
} from '../../../helpers/constants/design-system';
import Box from '../../ui/box/box';
import { ICON_NAMES } from '../icon';
import { Text } from '../text';
import { ButtonLink } from './button-link';
import { BUTTON_LINK_SIZES } from './button-link.constants';
import README from './README.mdx';
const marginSizeControlOptions = [
undefined,
0,
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
'auto',
];
export default {
title: 'Components/ComponentLibrary/ButtonLink',
id: __filename,
component: ButtonLink,
parameters: {
docs: {
page: README,
},
},
argTypes: {
as: {
control: 'select',
options: ['button', 'a'],
table: { category: 'button base props' },
},
block: {
control: 'boolean',
table: { category: 'button base props' },
},
children: {
control: 'text',
},
className: {
control: 'text',
},
danger: {
control: 'boolean',
},
disabled: {
control: 'boolean',
table: { category: 'button base props' },
},
href: {
control: 'text',
},
icon: {
control: 'select',
options: Object.values(ICON_NAMES),
table: { category: 'button base props' },
},
iconPositionRight: {
control: 'boolean',
table: { category: 'button base props' },
},
iconProps: {
control: 'object',
table: { category: 'button base props' },
},
loading: {
control: 'boolean',
table: { category: 'button base props' },
},
size: {
control: 'select',
options: Object.values(BUTTON_LINK_SIZES),
},
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 Link',
},
};
export const DefaultStory = (args) => <ButtonLink {...args} />;
DefaultStory.storyName = 'Default';
export const Size = (args) => (
<>
<Box display={DISPLAY.FLEX} alignItems={ALIGN_ITEMS.BASELINE} gap={1}>
<ButtonLink {...args} size={SIZES.SM}>
Small Button
</ButtonLink>
<ButtonLink {...args} size={SIZES.MD}>
Medium (Default) Button
</ButtonLink>
<ButtonLink {...args} size={SIZES.LG}>
Large Button
</ButtonLink>
</Box>
<Text variant={TEXT.BODY_SM}>
<ButtonLink {...args} size={SIZES.AUTO}>
Button Auto
</ButtonLink>{' '}
inherits the font-size of the parent element.
</Text>
</>
);
export const Type = (args) => (
<Box display={DISPLAY.FLEX} gap={1}>
<ButtonLink {...args}>Normal</ButtonLink>
{/* Test Anchor tag to match exactly as button */}
<ButtonLink as="a" {...args} href="#" danger>
Danger
</ButtonLink>
</Box>
);
export const Href = (args) => <ButtonLink {...args}>Href Example</ButtonLink>;
Href.args = {
href: '/metamask',
};

@ -0,0 +1,84 @@
/* eslint-disable jest/require-top-level-describe */
import { render } from '@testing-library/react';
import React from 'react';
import { SIZES } from '../../../helpers/constants/design-system';
import { ButtonLink } from './button-link';
describe('ButtonLink', () => {
it('should render button element correctly', () => {
const { getByText, getByTestId, container } = render(
<ButtonLink data-testid="button-link">Button Link</ButtonLink>,
);
expect(getByText('Button Link')).toBeDefined();
expect(container.querySelector('button')).toBeDefined();
expect(getByTestId('button-link')).toHaveClass('mm-button');
});
it('should render anchor element correctly', () => {
const { getByTestId, container } = render(
<ButtonLink as="a" data-testid="button-link" />,
);
expect(getByTestId('button-link')).toHaveClass('mm-button');
const anchor = container.getElementsByTagName('a').length;
expect(anchor).toBe(1);
});
it('should render button as block', () => {
const { getByTestId } = render(<ButtonLink block data-testid="block" />);
expect(getByTestId('block')).toHaveClass(`mm-button--block`);
});
it('should render with added classname', () => {
const { getByTestId } = render(
<ButtonLink data-testid="classname" className="mm-button--test" />,
);
expect(getByTestId('classname')).toHaveClass('mm-button--test');
});
it('should render with different size classes', () => {
const { getByTestId } = render(
<>
<ButtonLink size={SIZES.SM} data-testid={SIZES.SM} />
<ButtonLink size={SIZES.MD} data-testid={SIZES.MD} />
<ButtonLink size={SIZES.LG} data-testid={SIZES.LG} />
<ButtonLink size={SIZES.AUTO} data-testid={SIZES.AUTO} />
</>,
);
expect(getByTestId(SIZES.SM)).toHaveClass(`mm-button--size-${SIZES.SM}`);
expect(getByTestId(SIZES.MD)).toHaveClass(`mm-button--size-${SIZES.MD}`);
expect(getByTestId(SIZES.LG)).toHaveClass(`mm-button--size-${SIZES.LG}`);
expect(getByTestId(SIZES.AUTO)).toHaveClass(
`mm-button--size-${SIZES.AUTO}`,
);
});
it('should render with different types', () => {
const { getByTestId } = render(
<>
<ButtonLink danger data-testid="danger" />
</>,
);
expect(getByTestId('danger')).toHaveClass('mm-button-link--type-danger');
});
it('should render with different button states', () => {
const { getByTestId } = render(
<>
<ButtonLink loading data-testid="loading" />
<ButtonLink disabled data-testid="disabled" />
</>,
);
expect(getByTestId('loading')).toHaveClass(`mm-button--loading`);
expect(getByTestId('disabled')).toHaveClass(`mm-button--disabled`);
});
it('should render with icon', () => {
const { container } = render(
<ButtonLink data-testid="icon" icon="add-square-filled" />,
);
const icons = container.getElementsByClassName('icon').length;
expect(icons).toBe(1);
});
});

@ -0,0 +1 @@
export { ButtonLink } from './button-link';

@ -6,6 +6,7 @@
@import 'avatar-with-badge/avatar-with-badge';
@import 'base-avatar/base-avatar';
@import 'button-base/button-base';
@import 'button-link/button-link';
@import 'button-primary/button-primary';
@import 'button-secondary/button-secondary';
@import 'icon/icon';

Loading…
Cancel
Save