mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 09:52:26 +01:00
Fix/buttonbase ts update (#20060)
* Migrate ButtonBase to TS --------- Co-authored-by: Binij Shrestha <shresthabinij@gmail.com>
This commit is contained in:
parent
85465f53a7
commit
3f27d018c4
@ -1,7 +0,0 @@
|
|||||||
import { Size } from '../../../helpers/constants/design-system';
|
|
||||||
|
|
||||||
export const BUTTON_BASE_SIZES = {
|
|
||||||
SM: Size.SM,
|
|
||||||
MD: Size.MD,
|
|
||||||
LG: Size.LG,
|
|
||||||
};
|
|
@ -1,203 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import classnames from 'classnames';
|
|
||||||
|
|
||||||
import Box from '../../ui/box';
|
|
||||||
import { IconName, Icon, IconSize } from '../icon';
|
|
||||||
import { Text } from '..';
|
|
||||||
|
|
||||||
import {
|
|
||||||
AlignItems,
|
|
||||||
Display,
|
|
||||||
JustifyContent,
|
|
||||||
TextColor,
|
|
||||||
TextVariant,
|
|
||||||
BorderRadius,
|
|
||||||
BackgroundColor,
|
|
||||||
IconColor,
|
|
||||||
} from '../../../helpers/constants/design-system';
|
|
||||||
import { BUTTON_BASE_SIZES } from './button-base.constants';
|
|
||||||
|
|
||||||
export const ButtonBase = ({
|
|
||||||
as = 'button',
|
|
||||||
block,
|
|
||||||
children,
|
|
||||||
className,
|
|
||||||
href,
|
|
||||||
ellipsis = false,
|
|
||||||
externalLink,
|
|
||||||
size = BUTTON_BASE_SIZES.MD,
|
|
||||||
startIconName,
|
|
||||||
startIconProps,
|
|
||||||
endIconName,
|
|
||||||
endIconProps,
|
|
||||||
loading,
|
|
||||||
disabled,
|
|
||||||
iconLoadingProps,
|
|
||||||
textProps,
|
|
||||||
color = TextColor.textDefault,
|
|
||||||
...props
|
|
||||||
}) => {
|
|
||||||
const Tag = href ? 'a' : as;
|
|
||||||
if (Tag === 'a' && externalLink) {
|
|
||||||
props.target = '_blank';
|
|
||||||
props.rel = 'noopener noreferrer';
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<Text
|
|
||||||
as={Tag}
|
|
||||||
backgroundColor={BackgroundColor.backgroundAlternative}
|
|
||||||
color={loading ? TextColor.transparent : color}
|
|
||||||
variant={TextVariant.bodyMdMedium}
|
|
||||||
href={href}
|
|
||||||
paddingLeft={4}
|
|
||||||
paddingRight={4}
|
|
||||||
ellipsis={ellipsis}
|
|
||||||
className={classnames(
|
|
||||||
'mm-button-base',
|
|
||||||
{
|
|
||||||
[`mm-button-base--size-${size}`]:
|
|
||||||
Object.values(BUTTON_BASE_SIZES).includes(size),
|
|
||||||
'mm-button-base--loading': loading,
|
|
||||||
'mm-button-base--disabled': disabled,
|
|
||||||
'mm-button-base--block': block,
|
|
||||||
'mm-button-base--ellipsis': ellipsis,
|
|
||||||
},
|
|
||||||
className,
|
|
||||||
)}
|
|
||||||
disabled={disabled}
|
|
||||||
display={Display.InlineFlex}
|
|
||||||
justifyContent={JustifyContent.center}
|
|
||||||
alignItems={AlignItems.center}
|
|
||||||
borderRadius={BorderRadius.pill}
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
{startIconName && (
|
|
||||||
<Icon
|
|
||||||
name={startIconName}
|
|
||||||
size={IconSize.Sm}
|
|
||||||
marginInlineEnd={1}
|
|
||||||
{...startIconProps}
|
|
||||||
color={loading ? IconColor.transparent : startIconProps?.color}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{/*
|
|
||||||
* If children is a string and doesn't need truncation or loading
|
|
||||||
* prevent html bloat by rendering just the string
|
|
||||||
* otherwise render with wrapper to allow truncation or loading
|
|
||||||
*/}
|
|
||||||
{typeof children === 'string' && !ellipsis && !loading ? (
|
|
||||||
children
|
|
||||||
) : (
|
|
||||||
<Text
|
|
||||||
as="span"
|
|
||||||
ellipsis={ellipsis}
|
|
||||||
variant={TextVariant.inherit}
|
|
||||||
color={loading ? TextColor.transparent : color}
|
|
||||||
{...textProps}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</Text>
|
|
||||||
)}
|
|
||||||
{endIconName && (
|
|
||||||
<Icon
|
|
||||||
name={endIconName}
|
|
||||||
size={IconSize.Sm}
|
|
||||||
marginInlineStart={1}
|
|
||||||
{...endIconProps}
|
|
||||||
color={loading ? IconColor.transparent : endIconProps?.color}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{loading && (
|
|
||||||
<Icon
|
|
||||||
className="mm-button-base__icon-loading"
|
|
||||||
name={IconName.Loading}
|
|
||||||
color={color}
|
|
||||||
size={IconSize.Md}
|
|
||||||
{...iconLoadingProps}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Text>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
ButtonBase.propTypes = {
|
|
||||||
/**
|
|
||||||
* The polymorphic `as` prop allows you to change the root HTML element of the Button component between `button` and `a` tag
|
|
||||||
*/
|
|
||||||
as: PropTypes.string,
|
|
||||||
/**
|
|
||||||
* Boolean prop to quickly activate box prop display block
|
|
||||||
*/
|
|
||||||
block: PropTypes.bool,
|
|
||||||
/**
|
|
||||||
* Additional props to pass to the Text component that wraps the button children
|
|
||||||
*/
|
|
||||||
buttonTextProps: PropTypes.object,
|
|
||||||
/**
|
|
||||||
* The children to be rendered inside the ButtonBase
|
|
||||||
*/
|
|
||||||
children: PropTypes.node,
|
|
||||||
/**
|
|
||||||
* An additional className to apply to the ButtonBase.
|
|
||||||
*/
|
|
||||||
className: PropTypes.string,
|
|
||||||
/**
|
|
||||||
* Boolean to disable button
|
|
||||||
*/
|
|
||||||
disabled: PropTypes.bool,
|
|
||||||
/**
|
|
||||||
* When an `href` prop is passed, ButtonBase will automatically change the root element to be an `a` (anchor) tag
|
|
||||||
*/
|
|
||||||
href: PropTypes.string,
|
|
||||||
/**
|
|
||||||
* Used for long strings that can be cut off...
|
|
||||||
*/
|
|
||||||
ellipsis: PropTypes.bool,
|
|
||||||
/**
|
|
||||||
* Boolean indicating if the link targets external content, it will cause the link to open in a new tab
|
|
||||||
*/
|
|
||||||
externalLink: PropTypes.bool,
|
|
||||||
/**
|
|
||||||
* Add icon to start (left side) of button text passing icon name
|
|
||||||
* The name of the icon to display. Should be one of IconName
|
|
||||||
*/
|
|
||||||
startIconName: PropTypes.oneOf(Object.values(IconName)),
|
|
||||||
/**
|
|
||||||
* iconProps accepts all the props from Icon
|
|
||||||
*/
|
|
||||||
startIconProps: PropTypes.object,
|
|
||||||
/**
|
|
||||||
* Add icon to end (right side) of button text passing icon name
|
|
||||||
* The name of the icon to display. Should be one of IconName
|
|
||||||
*/
|
|
||||||
endIconName: PropTypes.oneOf(Object.values(IconName)),
|
|
||||||
/**
|
|
||||||
* iconProps accepts all the props from Icon
|
|
||||||
*/
|
|
||||||
endIconProps: PropTypes.object,
|
|
||||||
/**
|
|
||||||
* iconLoadingProps accepts all the props from Icon
|
|
||||||
*/
|
|
||||||
iconLoadingProps: PropTypes.object,
|
|
||||||
/**
|
|
||||||
* Boolean to show loading spinner in button
|
|
||||||
*/
|
|
||||||
loading: PropTypes.bool,
|
|
||||||
/**
|
|
||||||
* The size of the ButtonBase.
|
|
||||||
* Possible values could be 'Size.SM'(32px), 'Size.MD'(40px), 'Size.LG'(48px),
|
|
||||||
*/
|
|
||||||
size: PropTypes.oneOfType([
|
|
||||||
PropTypes.shape(BUTTON_BASE_SIZES),
|
|
||||||
PropTypes.string,
|
|
||||||
]),
|
|
||||||
/**
|
|
||||||
* textProps accepts all the props from Icon
|
|
||||||
*/
|
|
||||||
textProps: PropTypes.object,
|
|
||||||
/**
|
|
||||||
* ButtonBase accepts all the props from Box
|
|
||||||
*/
|
|
||||||
...Box.propTypes,
|
|
||||||
};
|
|
@ -6,7 +6,6 @@
|
|||||||
user-select: none;
|
user-select: none;
|
||||||
|
|
||||||
&--block {
|
&--block {
|
||||||
display: block;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -45,6 +44,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a.mm-button-base:hover {
|
||||||
|
color: var(--color-text-default);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@keyframes spinner {
|
@keyframes spinner {
|
||||||
|
@ -1,15 +1,14 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { StoryFn, Meta } from '@storybook/react';
|
||||||
import {
|
import {
|
||||||
AlignItems,
|
AlignItems,
|
||||||
Color,
|
BackgroundColor,
|
||||||
DISPLAY,
|
Display,
|
||||||
FLEX_DIRECTION,
|
FlexDirection,
|
||||||
Size,
|
TextColor,
|
||||||
} from '../../../helpers/constants/design-system';
|
} from '../../../helpers/constants/design-system';
|
||||||
import Box from '../../ui/box/box';
|
import { Box, TextDirection, IconName } from '..';
|
||||||
import { TextDirection, IconName } from '..';
|
import { ButtonBaseSize } from './button-base.types';
|
||||||
|
|
||||||
import { BUTTON_BASE_SIZES } from './button-base.constants';
|
|
||||||
import { ButtonBase } from './button-base';
|
import { ButtonBase } from './button-base';
|
||||||
import README from './README.mdx';
|
import README from './README.mdx';
|
||||||
|
|
||||||
@ -70,7 +69,7 @@ export default {
|
|||||||
},
|
},
|
||||||
size: {
|
size: {
|
||||||
control: 'select',
|
control: 'select',
|
||||||
options: Object.values(BUTTON_BASE_SIZES),
|
options: Object.values(ButtonBaseSize),
|
||||||
},
|
},
|
||||||
marginTop: {
|
marginTop: {
|
||||||
options: marginSizeControlOptions,
|
options: marginSizeControlOptions,
|
||||||
@ -96,27 +95,29 @@ export default {
|
|||||||
args: {
|
args: {
|
||||||
children: 'Button Base',
|
children: 'Button Base',
|
||||||
},
|
},
|
||||||
};
|
} as Meta<typeof ButtonBase>;
|
||||||
|
|
||||||
export const DefaultStory = (args) => <ButtonBase {...args} />;
|
export const DefaultStory: StoryFn<typeof ButtonBase> = (args) => (
|
||||||
|
<ButtonBase {...args} />
|
||||||
|
);
|
||||||
|
|
||||||
DefaultStory.storyName = 'Default';
|
DefaultStory.storyName = 'Default';
|
||||||
|
|
||||||
export const SizeStory = (args) => (
|
export const SizeStory: StoryFn<typeof ButtonBase> = (args) => (
|
||||||
<>
|
<>
|
||||||
<Box
|
<Box
|
||||||
display={DISPLAY.FLEX}
|
display={Display.Flex}
|
||||||
alignItems={AlignItems.baseline}
|
alignItems={AlignItems.baseline}
|
||||||
gap={1}
|
gap={1}
|
||||||
marginBottom={2}
|
marginBottom={2}
|
||||||
>
|
>
|
||||||
<ButtonBase {...args} size={Size.SM}>
|
<ButtonBase {...args} size={ButtonBaseSize.Sm}>
|
||||||
Button SM
|
Button SM
|
||||||
</ButtonBase>
|
</ButtonBase>
|
||||||
<ButtonBase {...args} size={Size.MD}>
|
<ButtonBase {...args} size={ButtonBaseSize.Md}>
|
||||||
Button MD
|
Button MD
|
||||||
</ButtonBase>
|
</ButtonBase>
|
||||||
<ButtonBase {...args} size={Size.LG}>
|
<ButtonBase {...args} size={ButtonBaseSize.Lg}>
|
||||||
Button LG
|
Button LG
|
||||||
</ButtonBase>
|
</ButtonBase>
|
||||||
</Box>
|
</Box>
|
||||||
@ -125,7 +126,7 @@ export const SizeStory = (args) => (
|
|||||||
|
|
||||||
SizeStory.storyName = 'Size';
|
SizeStory.storyName = 'Size';
|
||||||
|
|
||||||
export const Block = (args) => (
|
export const Block: StoryFn<typeof ButtonBase> = (args) => (
|
||||||
<>
|
<>
|
||||||
<ButtonBase {...args} marginBottom={2}>
|
<ButtonBase {...args} marginBottom={2}>
|
||||||
Default Button
|
Default Button
|
||||||
@ -136,8 +137,8 @@ export const Block = (args) => (
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
||||||
export const As = (args) => (
|
export const As: StoryFn<typeof ButtonBase> = (args) => (
|
||||||
<Box display={DISPLAY.FLEX} flexDirection={FLEX_DIRECTION.ROW} gap={2}>
|
<Box display={Display.Flex} flexDirection={FlexDirection.Row} gap={2}>
|
||||||
<ButtonBase {...args}>Button Element</ButtonBase>
|
<ButtonBase {...args}>Button Element</ButtonBase>
|
||||||
<ButtonBase as="a" href="#" {...args}>
|
<ButtonBase as="a" href="#" {...args}>
|
||||||
Anchor Element
|
Anchor Element
|
||||||
@ -145,13 +146,15 @@ export const As = (args) => (
|
|||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|
||||||
export const Href = (args) => <ButtonBase {...args}>Anchor Element</ButtonBase>;
|
export const Href: StoryFn<typeof ButtonBase> = (args) => (
|
||||||
|
<ButtonBase {...args}>Anchor Element</ButtonBase>
|
||||||
|
);
|
||||||
|
|
||||||
Href.args = {
|
Href.args = {
|
||||||
href: '/metamask',
|
href: '/metamask',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ExternalLink = (args) => (
|
export const ExternalLink: StoryFn<typeof ButtonBase> = (args) => (
|
||||||
<ButtonBase {...args}>Anchor element with external link</ButtonBase>
|
<ButtonBase {...args}>Anchor element with external link</ButtonBase>
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -160,7 +163,7 @@ ExternalLink.args = {
|
|||||||
externalLink: true,
|
externalLink: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Disabled = (args) => (
|
export const Disabled: StoryFn<typeof ButtonBase> = (args) => (
|
||||||
<ButtonBase {...args}>Disabled Button</ButtonBase>
|
<ButtonBase {...args}>Disabled Button</ButtonBase>
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -168,7 +171,7 @@ Disabled.args = {
|
|||||||
disabled: true,
|
disabled: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Loading = (args) => (
|
export const Loading: StoryFn<typeof ButtonBase> = (args) => (
|
||||||
<ButtonBase {...args}>Loading Button</ButtonBase>
|
<ButtonBase {...args}>Loading Button</ButtonBase>
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -176,20 +179,20 @@ Loading.args = {
|
|||||||
loading: true,
|
loading: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const StartIconName = (args) => (
|
export const StartIconName: StoryFn<typeof ButtonBase> = (args) => (
|
||||||
<ButtonBase {...args} startIconName={IconName.AddSquare}>
|
<ButtonBase {...args} startIconName={IconName.AddSquare}>
|
||||||
Button
|
Button
|
||||||
</ButtonBase>
|
</ButtonBase>
|
||||||
);
|
);
|
||||||
|
|
||||||
export const EndIconName = (args) => (
|
export const EndIconName: StoryFn<typeof ButtonBase> = (args) => (
|
||||||
<ButtonBase {...args} endIconName={IconName.Arrow2Right}>
|
<ButtonBase {...args} endIconName={IconName.Arrow2Right}>
|
||||||
Button
|
Button
|
||||||
</ButtonBase>
|
</ButtonBase>
|
||||||
);
|
);
|
||||||
|
|
||||||
export const Rtl = (args) => (
|
export const Rtl: StoryFn<typeof ButtonBase> = (args) => (
|
||||||
<Box display={DISPLAY.FLEX} flexDirection={FLEX_DIRECTION.COLUMN} gap={2}>
|
<Box display={Display.Flex} flexDirection={FlexDirection.Column} gap={2}>
|
||||||
<ButtonBase
|
<ButtonBase
|
||||||
{...args}
|
{...args}
|
||||||
startIconName={IconName.AddSquare}
|
startIconName={IconName.AddSquare}
|
||||||
@ -208,10 +211,14 @@ export const Rtl = (args) => (
|
|||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|
||||||
export const Ellipsis = (args) => (
|
export const Ellipsis: StoryFn<typeof ButtonBase> = (args) => (
|
||||||
<Box backgroundColor={Color.iconMuted} style={{ width: 150 }}>
|
<Box backgroundColor={BackgroundColor.primaryMuted} style={{ width: 150 }}>
|
||||||
<ButtonBase {...args}>Example without ellipsis</ButtonBase>
|
<ButtonBase {...args}>Example without ellipsis</ButtonBase>
|
||||||
<ButtonBase {...args} ellipsis>
|
<ButtonBase
|
||||||
|
{...args}
|
||||||
|
ellipsis
|
||||||
|
textProps={{ color: TextColor.errorDefault }}
|
||||||
|
>
|
||||||
Example with ellipsis
|
Example with ellipsis
|
||||||
</ButtonBase>
|
</ButtonBase>
|
||||||
</Box>
|
</Box>
|
@ -2,7 +2,7 @@
|
|||||||
import { render } from '@testing-library/react';
|
import { render } from '@testing-library/react';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { IconName } from '..';
|
import { IconName } from '..';
|
||||||
import { BUTTON_BASE_SIZES } from './button-base.constants';
|
import { ButtonBaseSize } from './button-base.types';
|
||||||
import { ButtonBase } from './button-base';
|
import { ButtonBase } from './button-base';
|
||||||
|
|
||||||
describe('ButtonBase', () => {
|
describe('ButtonBase', () => {
|
||||||
@ -51,17 +51,8 @@ describe('ButtonBase', () => {
|
|||||||
expect(getByTestId('button-base')).toHaveAttribute(
|
expect(getByTestId('button-base')).toHaveAttribute(
|
||||||
'href',
|
'href',
|
||||||
'https://www.test.com/',
|
'https://www.test.com/',
|
||||||
'target',
|
|
||||||
'_blank',
|
|
||||||
'rel',
|
|
||||||
'noopener noreferrer',
|
|
||||||
);
|
|
||||||
expect(getByTestId('button-base')).toHaveAttribute(
|
|
||||||
'target',
|
|
||||||
'_blank',
|
|
||||||
'rel',
|
|
||||||
'noopener noreferrer',
|
|
||||||
);
|
);
|
||||||
|
expect(getByTestId('button-base')).toHaveAttribute('target', '_blank');
|
||||||
expect(getByTestId('button-base')).toHaveAttribute(
|
expect(getByTestId('button-base')).toHaveAttribute(
|
||||||
'rel',
|
'rel',
|
||||||
'noopener noreferrer',
|
'noopener noreferrer',
|
||||||
@ -79,28 +70,19 @@ describe('ButtonBase', () => {
|
|||||||
it('should render with different size classes', () => {
|
it('should render with different size classes', () => {
|
||||||
const { getByTestId } = render(
|
const { getByTestId } = render(
|
||||||
<>
|
<>
|
||||||
<ButtonBase
|
<ButtonBase size={ButtonBaseSize.Sm} data-testid={ButtonBaseSize.Sm} />
|
||||||
size={BUTTON_BASE_SIZES.SM}
|
<ButtonBase size={ButtonBaseSize.Md} data-testid={ButtonBaseSize.Md} />
|
||||||
data-testid={BUTTON_BASE_SIZES.SM}
|
<ButtonBase size={ButtonBaseSize.Lg} data-testid={ButtonBaseSize.Lg} />
|
||||||
/>
|
|
||||||
<ButtonBase
|
|
||||||
size={BUTTON_BASE_SIZES.MD}
|
|
||||||
data-testid={BUTTON_BASE_SIZES.MD}
|
|
||||||
/>
|
|
||||||
<ButtonBase
|
|
||||||
size={BUTTON_BASE_SIZES.LG}
|
|
||||||
data-testid={BUTTON_BASE_SIZES.LG}
|
|
||||||
/>
|
|
||||||
</>,
|
</>,
|
||||||
);
|
);
|
||||||
expect(getByTestId(BUTTON_BASE_SIZES.SM)).toHaveClass(
|
expect(getByTestId(ButtonBaseSize.Sm)).toHaveClass(
|
||||||
`mm-button-base--size-${BUTTON_BASE_SIZES.SM}`,
|
`mm-button-base--size-${ButtonBaseSize.Sm}`,
|
||||||
);
|
);
|
||||||
expect(getByTestId(BUTTON_BASE_SIZES.MD)).toHaveClass(
|
expect(getByTestId(ButtonBaseSize.Md)).toHaveClass(
|
||||||
`mm-button-base--size-${BUTTON_BASE_SIZES.MD}`,
|
`mm-button-base--size-${ButtonBaseSize.Md}`,
|
||||||
);
|
);
|
||||||
expect(getByTestId(BUTTON_BASE_SIZES.LG)).toHaveClass(
|
expect(getByTestId(ButtonBaseSize.Lg)).toHaveClass(
|
||||||
`mm-button-base--size-${BUTTON_BASE_SIZES.LG}`,
|
`mm-button-base--size-${ButtonBaseSize.Lg}`,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
130
ui/components/component-library/button-base/button-base.tsx
Normal file
130
ui/components/component-library/button-base/button-base.tsx
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import classnames from 'classnames';
|
||||||
|
import { IconName, Icon, IconSize, Text } from '..';
|
||||||
|
import {
|
||||||
|
AlignItems,
|
||||||
|
Display,
|
||||||
|
JustifyContent,
|
||||||
|
TextColor,
|
||||||
|
TextVariant,
|
||||||
|
BorderRadius,
|
||||||
|
BackgroundColor,
|
||||||
|
IconColor,
|
||||||
|
} from '../../../helpers/constants/design-system';
|
||||||
|
import type { PolymorphicRef } from '../box';
|
||||||
|
import type { TextProps } from '../text';
|
||||||
|
import {
|
||||||
|
ButtonBaseProps,
|
||||||
|
ButtonBaseSize,
|
||||||
|
ButtonBaseComponent,
|
||||||
|
} from './button-base.types';
|
||||||
|
|
||||||
|
export const ButtonBase: ButtonBaseComponent = React.forwardRef(
|
||||||
|
<C extends React.ElementType = 'button' | 'a'>(
|
||||||
|
{
|
||||||
|
as,
|
||||||
|
block,
|
||||||
|
children,
|
||||||
|
className = '',
|
||||||
|
href,
|
||||||
|
ellipsis = false,
|
||||||
|
externalLink,
|
||||||
|
size = ButtonBaseSize.Md,
|
||||||
|
startIconName,
|
||||||
|
startIconProps,
|
||||||
|
endIconName,
|
||||||
|
endIconProps,
|
||||||
|
loading,
|
||||||
|
disabled,
|
||||||
|
iconLoadingProps,
|
||||||
|
textProps,
|
||||||
|
color = TextColor.textDefault,
|
||||||
|
iconColor = IconColor.iconDefault,
|
||||||
|
...props
|
||||||
|
}: ButtonBaseProps<C>,
|
||||||
|
ref?: PolymorphicRef<C>,
|
||||||
|
) => {
|
||||||
|
const tag = href ? 'a' : as || 'button';
|
||||||
|
const tagProps = href && tag === 'a' ? { href, ...props } : props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Text
|
||||||
|
as={tag}
|
||||||
|
backgroundColor={BackgroundColor.backgroundAlternative}
|
||||||
|
variant={TextVariant.bodyMdMedium}
|
||||||
|
color={loading ? TextColor.transparent : color}
|
||||||
|
ref={ref}
|
||||||
|
{...(tag === 'button' ? { disabled } : {})}
|
||||||
|
{...(href && externalLink
|
||||||
|
? { target: '_blank', rel: 'noopener noreferrer' }
|
||||||
|
: {})}
|
||||||
|
paddingLeft={4}
|
||||||
|
paddingRight={4}
|
||||||
|
ellipsis={ellipsis}
|
||||||
|
className={classnames(
|
||||||
|
'mm-button-base',
|
||||||
|
{
|
||||||
|
[`mm-button-base--size-${size}`]:
|
||||||
|
Object.values(ButtonBaseSize).includes(size),
|
||||||
|
'mm-button-base--loading': loading || false,
|
||||||
|
'mm-button-base--disabled': disabled || false,
|
||||||
|
'mm-button-base--block': block || false,
|
||||||
|
'mm-button-base--ellipsis': ellipsis,
|
||||||
|
},
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
display={Display.InlineFlex}
|
||||||
|
justifyContent={JustifyContent.center}
|
||||||
|
alignItems={AlignItems.center}
|
||||||
|
borderRadius={BorderRadius.pill}
|
||||||
|
{...(tagProps as TextProps<C>)}
|
||||||
|
>
|
||||||
|
{startIconName && (
|
||||||
|
<Icon
|
||||||
|
name={startIconName}
|
||||||
|
size={IconSize.Sm}
|
||||||
|
marginInlineEnd={1}
|
||||||
|
{...startIconProps}
|
||||||
|
color={loading ? IconColor.transparent : startIconProps?.color}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{/*
|
||||||
|
* If children is a string and doesn't need truncation or loading
|
||||||
|
* prevent html bloat by rendering just the string
|
||||||
|
* otherwise render with wrapper to allow truncation or loading
|
||||||
|
*/}
|
||||||
|
{typeof children === 'string' && !ellipsis && !loading ? (
|
||||||
|
children
|
||||||
|
) : (
|
||||||
|
<Text
|
||||||
|
as="span"
|
||||||
|
ellipsis={ellipsis}
|
||||||
|
variant={TextVariant.inherit}
|
||||||
|
color={loading ? TextColor.transparent : color}
|
||||||
|
{...textProps}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
{endIconName && (
|
||||||
|
<Icon
|
||||||
|
name={endIconName}
|
||||||
|
size={IconSize.Sm}
|
||||||
|
marginInlineStart={1}
|
||||||
|
{...endIconProps}
|
||||||
|
color={loading ? IconColor.transparent : endIconProps?.color}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{loading && (
|
||||||
|
<Icon
|
||||||
|
className="mm-button-base__icon-loading"
|
||||||
|
name={IconName.Loading}
|
||||||
|
color={iconColor}
|
||||||
|
size={IconSize.Md}
|
||||||
|
{...iconLoadingProps}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Text>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
108
ui/components/component-library/button-base/button-base.types.ts
Normal file
108
ui/components/component-library/button-base/button-base.types.ts
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
import { ReactNode } from 'react';
|
||||||
|
import type {
|
||||||
|
StyleUtilityProps,
|
||||||
|
PolymorphicComponentPropWithRef,
|
||||||
|
} from '../box';
|
||||||
|
import { IconColor } from '../../../helpers/constants/design-system';
|
||||||
|
import { TextDirection, TextProps } from '../text';
|
||||||
|
import { IconName, IconProps } from '../icon';
|
||||||
|
|
||||||
|
export enum ButtonBaseSize {
|
||||||
|
Sm = 'sm',
|
||||||
|
Md = 'md',
|
||||||
|
Lg = 'lg',
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ValidButtonTagType = 'button' | 'a';
|
||||||
|
export interface ButtonBaseStyleUtilityProps extends StyleUtilityProps {
|
||||||
|
/**
|
||||||
|
* The polymorphic `as` prop allows you to change the root HTML element of the Button component between `button` and `a` tag
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
as?: ValidButtonTagType;
|
||||||
|
/**
|
||||||
|
* Boolean prop to quickly activate box prop display block
|
||||||
|
*/
|
||||||
|
block?: boolean;
|
||||||
|
/**
|
||||||
|
* The children to be rendered inside the ButtonBase
|
||||||
|
*/
|
||||||
|
children?: ReactNode;
|
||||||
|
/**
|
||||||
|
* Boolean to disable button
|
||||||
|
*/
|
||||||
|
disabled?: boolean;
|
||||||
|
/**
|
||||||
|
* When an `href` prop is passed, ButtonBase will automatically change the root element to be an `a` (anchor) tag
|
||||||
|
*/
|
||||||
|
href?: string;
|
||||||
|
/**
|
||||||
|
* Used for long strings that can be cut off...
|
||||||
|
*/
|
||||||
|
ellipsis?: boolean;
|
||||||
|
/**
|
||||||
|
* Boolean indicating if the link targets external content, it will cause the link to open in a new tab
|
||||||
|
*/
|
||||||
|
externalLink?: boolean;
|
||||||
|
/**
|
||||||
|
* Add icon to start (left side) of button text passing icon name
|
||||||
|
* The name of the icon to display. Should be one of IconName
|
||||||
|
*/
|
||||||
|
startIconName?: IconName;
|
||||||
|
/**
|
||||||
|
* iconProps accepts all the props from Icon
|
||||||
|
*/
|
||||||
|
startIconProps?: IconProps;
|
||||||
|
/**
|
||||||
|
* Add icon to end (right side) of button text passing icon name
|
||||||
|
* The name of the icon to display. Should be one of IconName
|
||||||
|
*/
|
||||||
|
endIconName?: IconName;
|
||||||
|
/**
|
||||||
|
* iconProps accepts all the props from Icon
|
||||||
|
*/
|
||||||
|
endIconProps?: IconProps;
|
||||||
|
/**
|
||||||
|
* iconLoadingProps accepts all the props from Icon
|
||||||
|
*/
|
||||||
|
iconLoadingProps?: IconProps;
|
||||||
|
/**
|
||||||
|
* Boolean to show loading spinner in button
|
||||||
|
*/
|
||||||
|
loading?: boolean;
|
||||||
|
/**
|
||||||
|
* The size of the ButtonBase.
|
||||||
|
* Possible values could be 'Size.SM'(32px), 'Size.MD'(40px), 'Size.LG'(48px),
|
||||||
|
*/
|
||||||
|
size?: ButtonBaseSize;
|
||||||
|
/**
|
||||||
|
* textProps are additional props to pass to the Text component that wraps the button children
|
||||||
|
*/
|
||||||
|
textProps?: TextProps<'span'>;
|
||||||
|
/**
|
||||||
|
* Specifies where to display the linked URL.
|
||||||
|
*/
|
||||||
|
target?: string;
|
||||||
|
/**
|
||||||
|
* Specifies the relationship between the current document and
|
||||||
|
* the linked URL.
|
||||||
|
*/
|
||||||
|
rel?: string;
|
||||||
|
/**
|
||||||
|
* Sets the color of the button icon.
|
||||||
|
*/
|
||||||
|
iconColor?: IconColor;
|
||||||
|
/**
|
||||||
|
* Direction of the text content within the button ("ltr" or "rtl").
|
||||||
|
*/
|
||||||
|
textDirection?: TextDirection;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ButtonBaseProps<C extends React.ElementType> =
|
||||||
|
PolymorphicComponentPropWithRef<C, ButtonBaseStyleUtilityProps>;
|
||||||
|
|
||||||
|
export type ButtonBaseComponent = <
|
||||||
|
C extends React.ElementType = 'button' | 'a',
|
||||||
|
>(
|
||||||
|
props: ButtonBaseProps<C>,
|
||||||
|
) => React.ReactElement | null;
|
@ -1,2 +0,0 @@
|
|||||||
export { ButtonBase } from './button-base';
|
|
||||||
export { BUTTON_BASE_SIZES } from './button-base.constants';
|
|
3
ui/components/component-library/button-base/index.ts
Normal file
3
ui/components/component-library/button-base/index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export { ButtonBase } from './button-base';
|
||||||
|
export { ButtonBaseSize } from './button-base.types';
|
||||||
|
export type { ButtonBaseProps } from './button-base.types';
|
@ -1,11 +1,12 @@
|
|||||||
.mm-button-link {
|
.mm-button-link {
|
||||||
&:hover:not(&--disabled) {
|
&:hover:not(&--disabled) {
|
||||||
|
color: var(--color-primary-default);
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
text-decoration-thickness: 2px;
|
text-decoration-thickness: 2px;
|
||||||
text-underline-offset: 4px;
|
text-underline-offset: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:active {
|
&:active:not(&--disabled) {
|
||||||
color: var(--color-primary-alternative);
|
color: var(--color-primary-alternative);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@ export {
|
|||||||
} from './badge-wrapper';
|
} from './badge-wrapper';
|
||||||
export { Box } from './box';
|
export { Box } from './box';
|
||||||
export { Button, BUTTON_VARIANT, BUTTON_SIZES } from './button';
|
export { Button, BUTTON_VARIANT, BUTTON_SIZES } from './button';
|
||||||
export { ButtonBase, BUTTON_BASE_SIZES } from './button-base';
|
export { ButtonBase, ButtonBaseSize } from './button-base';
|
||||||
export { ButtonIcon, ButtonIconSize } from './button-icon';
|
export { ButtonIcon, ButtonIconSize } from './button-icon';
|
||||||
export { ButtonLink, BUTTON_LINK_SIZES } from './button-link';
|
export { ButtonLink, BUTTON_LINK_SIZES } from './button-link';
|
||||||
export { ButtonPrimary, BUTTON_PRIMARY_SIZES } from './button-primary';
|
export { ButtonPrimary, BUTTON_PRIMARY_SIZES } from './button-primary';
|
||||||
|
@ -3,7 +3,6 @@ import {
|
|||||||
FontWeight,
|
FontWeight,
|
||||||
FontStyle,
|
FontStyle,
|
||||||
TextVariant,
|
TextVariant,
|
||||||
TextAlign,
|
|
||||||
TextTransform,
|
TextTransform,
|
||||||
OverflowWrap,
|
OverflowWrap,
|
||||||
} from '../../../helpers/constants/design-system';
|
} from '../../../helpers/constants/design-system';
|
||||||
@ -72,7 +71,9 @@ export type ValidTagType =
|
|||||||
| 'ul'
|
| 'ul'
|
||||||
| 'label'
|
| 'label'
|
||||||
| 'input'
|
| 'input'
|
||||||
| 'header';
|
| 'header'
|
||||||
|
| 'a'
|
||||||
|
| 'button';
|
||||||
|
|
||||||
export interface TextStyleUtilityProps extends StyleUtilityProps {
|
export interface TextStyleUtilityProps extends StyleUtilityProps {
|
||||||
/**
|
/**
|
||||||
@ -117,11 +118,6 @@ export interface TextStyleUtilityProps extends StyleUtilityProps {
|
|||||||
* ./ui/helpers/constants/design-system.js
|
* ./ui/helpers/constants/design-system.js
|
||||||
*/
|
*/
|
||||||
textTransform?: TextTransform;
|
textTransform?: TextTransform;
|
||||||
/**
|
|
||||||
* The text-align of the Text component. Should use the TextAlign enum from
|
|
||||||
* ./ui/helpers/constants/design-system.js
|
|
||||||
*/
|
|
||||||
textAlign?: TextAlign;
|
|
||||||
/**
|
/**
|
||||||
* Change the dir (direction) global attribute of text to support the direction a language is written
|
* Change the dir (direction) global attribute of text to support the direction a language is written
|
||||||
* Possible values: `LEFT_TO_RIGHT` (default), `RIGHT_TO_LEFT`, `AUTO` (user agent decides)
|
* Possible values: `LEFT_TO_RIGHT` (default), `RIGHT_TO_LEFT`, `AUTO` (user agent decides)
|
||||||
|
@ -164,6 +164,7 @@ export enum IconColor {
|
|||||||
lineaMainnetInverse = 'linea-mainnet-inverse',
|
lineaMainnetInverse = 'linea-mainnet-inverse',
|
||||||
goerliInverse = 'goerli-inverse',
|
goerliInverse = 'goerli-inverse',
|
||||||
sepoliaInverse = 'sepolia-inverse',
|
sepoliaInverse = 'sepolia-inverse',
|
||||||
|
transparent = 'transparent',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum TypographyVariant {
|
export enum TypographyVariant {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user