Feat/15089/add button link (#16115)
* 15089: add button link componentfeature/default_network_editable
parent
c09e685bce
commit
91df7122f2
@ -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'; |
Loading…
Reference in new issue