chore: button component (#81)
* chore: add button component with storybook * chore: add stories button * refactor: remove github custom logo * fix: fix build * chore: changes based on PR review
This commit is contained in:
parent
1dd06c6baf
commit
75a6de5ac7
|
|
@ -13,6 +13,10 @@ export const parameters = {
|
||||||
date: /Date$/,
|
date: /Date$/,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
darkMode: {
|
||||||
|
dark: { ...themes.dark, backgroundColor: 'black' },
|
||||||
|
// light: { ...themes.normal, backgroundColor: 'white' },
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const { darkTheme: darkThemeClassName } = dripStitches;
|
const { darkTheme: darkThemeClassName } = dripStitches;
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@
|
||||||
"@emotion/styled": "^11.10.5",
|
"@emotion/styled": "^11.10.5",
|
||||||
"@ethersproject/providers": "^5.7.2",
|
"@ethersproject/providers": "^5.7.2",
|
||||||
"@radix-ui/colors": "^0.1.8",
|
"@radix-ui/colors": "^0.1.8",
|
||||||
|
"@react-icons/all-files": "^4.1.0",
|
||||||
"@reduxjs/toolkit": "^1.9.1",
|
"@reduxjs/toolkit": "^1.9.1",
|
||||||
"@stitches/react": "^1.2.8",
|
"@stitches/react": "^1.2.8",
|
||||||
"formik": "^2.2.9",
|
"formik": "^2.2.9",
|
||||||
|
|
@ -57,7 +58,6 @@
|
||||||
"ethers": "^5.7.2",
|
"ethers": "^5.7.2",
|
||||||
"postcss": "^8.4.21",
|
"postcss": "^8.4.21",
|
||||||
"prettier": "^2.8.0",
|
"prettier": "^2.8.0",
|
||||||
"react-icons": "^4.7.1",
|
|
||||||
"react-query": "^3.39.2",
|
"react-query": "^3.39.2",
|
||||||
"storybook-dark-mode": "^2.0.5",
|
"storybook-dark-mode": "^2.0.5",
|
||||||
"tailwindcss": "^3.2.4",
|
"tailwindcss": "^3.2.4",
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
import { dripStitches } from '../../../theme/stitches'; //TODO replace with absolute path
|
||||||
|
import { Flex, Grid } from '../../layout';
|
||||||
|
|
||||||
|
const { styled } = dripStitches;
|
||||||
|
|
||||||
|
export const StyledButtonContentGrid = styled(Grid, {
|
||||||
|
gap: '$0h',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const StyledButtonContentFlex = styled(Flex, {
|
||||||
|
alignItems: 'center',
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,57 @@
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { ButtonProps } from '.';
|
||||||
|
import {
|
||||||
|
StyledButtonContentFlex,
|
||||||
|
StyledButtonContentGrid,
|
||||||
|
} from './button-content.styled';
|
||||||
|
import { ButtonIcon } from './button-icon';
|
||||||
|
|
||||||
|
export type ButtonContentProps = Pick<
|
||||||
|
ButtonProps,
|
||||||
|
| 'leftIcon'
|
||||||
|
| 'rightIcon'
|
||||||
|
| 'topIcon'
|
||||||
|
| 'bottomIcon'
|
||||||
|
| 'children'
|
||||||
|
| 'iconSpacing'
|
||||||
|
>;
|
||||||
|
|
||||||
|
export const ButtonContent: React.FC<ButtonContentProps> = (props) => {
|
||||||
|
const {
|
||||||
|
leftIcon,
|
||||||
|
rightIcon,
|
||||||
|
topIcon,
|
||||||
|
bottomIcon,
|
||||||
|
children,
|
||||||
|
iconSpacing = '1h',
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const midNode = (
|
||||||
|
<>
|
||||||
|
{leftIcon && (
|
||||||
|
<ButtonIcon css={{ marginRight: `$${iconSpacing}` }}>
|
||||||
|
{leftIcon}
|
||||||
|
</ButtonIcon>
|
||||||
|
)}
|
||||||
|
{children}
|
||||||
|
{rightIcon && (
|
||||||
|
<ButtonIcon css={{ marginLeft: `$${iconSpacing}` }}>
|
||||||
|
{rightIcon}
|
||||||
|
</ButtonIcon>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!topIcon && !bottomIcon) {
|
||||||
|
return midNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledButtonContentGrid>
|
||||||
|
{topIcon && <ButtonIcon>{topIcon}</ButtonIcon>}
|
||||||
|
<StyledButtonContentFlex>{midNode}</StyledButtonContentFlex>
|
||||||
|
{bottomIcon && <ButtonIcon>{bottomIcon}</ButtonIcon>}
|
||||||
|
</StyledButtonContentGrid>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
import { dripStitches } from '../../../theme/stitches'; //TODO replace with absolute path
|
||||||
|
|
||||||
|
const { styled } = dripStitches;
|
||||||
|
|
||||||
|
export const StyledButtonIconSpan = styled('span', {
|
||||||
|
all: 'unset',
|
||||||
|
display: 'inline-flex',
|
||||||
|
alignSelf: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
lineHeight: 1.3,
|
||||||
|
flexShrink: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
export type ButtonIconSpanProps = React.ComponentProps<
|
||||||
|
typeof StyledButtonIconSpan
|
||||||
|
>;
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
|
||||||
|
import {
|
||||||
|
ButtonIconSpanProps,
|
||||||
|
StyledButtonIconSpan,
|
||||||
|
} from './button-icon.styled';
|
||||||
|
|
||||||
|
export const ButtonIcon: React.FC<ButtonIconSpanProps> = (props) => {
|
||||||
|
const { children, className, ...rest } = props;
|
||||||
|
|
||||||
|
const _children = React.isValidElement(children)
|
||||||
|
? React.cloneElement(children, {
|
||||||
|
'aria-hidden': true,
|
||||||
|
focusable: false,
|
||||||
|
})
|
||||||
|
: children;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledButtonIconSpan {...rest} className={className}>
|
||||||
|
{_children}
|
||||||
|
</StyledButtonIconSpan>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
import { dripStitches } from '../../../theme/stitches'; //TODO replace with absolute path
|
||||||
|
import { Flex, Grid } from '../../layout';
|
||||||
|
|
||||||
|
const { styled } = dripStitches;
|
||||||
|
|
||||||
|
export const StyledButtonSpinnerBox = styled(Flex, {
|
||||||
|
alignItems: 'center',
|
||||||
|
fontSize: '$md',
|
||||||
|
lineHeight: 'normal',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const StyledButtonSpinnerDotsBox = styled(Grid, {
|
||||||
|
gap: '$1',
|
||||||
|
gridAutoFlow: 'column',
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const StyledButtonSpinnerDot = styled('div', {
|
||||||
|
all: 'unset',
|
||||||
|
width: '$2',
|
||||||
|
height: '$2',
|
||||||
|
borderRadius: '$full',
|
||||||
|
backgroundColor: '$slate1',
|
||||||
|
opacity: 0.2,
|
||||||
|
});
|
||||||
|
|
||||||
|
export interface ButtonSpinnerProps
|
||||||
|
extends React.HTMLAttributes<HTMLDivElement> {
|
||||||
|
label?: string;
|
||||||
|
spacing?: string;
|
||||||
|
placement?: 'start' | 'end';
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,69 @@
|
||||||
|
import { dripStitches } from '../../../theme/stitches'; //TODO replace with absolute path
|
||||||
|
import React, { HTMLAttributes, useMemo } from 'react';
|
||||||
|
|
||||||
|
import {
|
||||||
|
ButtonSpinnerProps,
|
||||||
|
StyledButtonSpinnerBox,
|
||||||
|
StyledButtonSpinnerDot,
|
||||||
|
StyledButtonSpinnerDotsBox,
|
||||||
|
} from './button-spinner.styled';
|
||||||
|
|
||||||
|
export const ButtonSpinner: React.FC<ButtonSpinnerProps> = (props) => {
|
||||||
|
const {
|
||||||
|
label,
|
||||||
|
placement,
|
||||||
|
spacing = '$2',
|
||||||
|
children,
|
||||||
|
className,
|
||||||
|
...rest
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const marginProp = placement === 'start' ? 'marginRight' : 'marginLeft';
|
||||||
|
|
||||||
|
const spinnerStyles = useMemo(
|
||||||
|
() => ({
|
||||||
|
position: label ? 'relative' : 'absolute',
|
||||||
|
[marginProp]: label ? spacing : 0,
|
||||||
|
}),
|
||||||
|
[label, marginProp, spacing]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledButtonSpinnerBox className={className} css={spinnerStyles} {...rest}>
|
||||||
|
{children || <ButtonSpinnerDots />}
|
||||||
|
</StyledButtonSpinnerBox>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const { keyframes } = dripStitches;
|
||||||
|
|
||||||
|
const blink = keyframes({
|
||||||
|
'0%': { opacity: 0.2 },
|
||||||
|
'50%': { opacity: 1 },
|
||||||
|
'100%': { opacity: 0.2 },
|
||||||
|
});
|
||||||
|
|
||||||
|
const ButtonSpinnerDots: React.FC<HTMLAttributes<HTMLDivElement>> = (props) => {
|
||||||
|
return (
|
||||||
|
<StyledButtonSpinnerDotsBox {...props}>
|
||||||
|
<StyledButtonSpinnerDot
|
||||||
|
css={{
|
||||||
|
animation: `${blink} infinite linear 2s`,
|
||||||
|
animationDelay: '0ms',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<StyledButtonSpinnerDot
|
||||||
|
css={{
|
||||||
|
animation: `${blink} infinite linear 2s`,
|
||||||
|
animationDelay: '250ms',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<StyledButtonSpinnerDot
|
||||||
|
css={{
|
||||||
|
animation: `${blink} infinite linear 2s`,
|
||||||
|
animationDelay: '500ms',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</StyledButtonSpinnerDotsBox>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,94 @@
|
||||||
|
import { Flex } from '../../layout';
|
||||||
|
import { Button } from './button';
|
||||||
|
import { IconButton } from './icon-button';
|
||||||
|
import { Icon as IconComponent } from '../icon';
|
||||||
|
import { dripStitches } from '../../../theme/stitches';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: 'Components/Button',
|
||||||
|
component: Button,
|
||||||
|
};
|
||||||
|
|
||||||
|
const { styled } = dripStitches;
|
||||||
|
|
||||||
|
const StoryFlex = styled(Flex, {
|
||||||
|
display: 'flex',
|
||||||
|
gap: '$2',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const Default = () => (
|
||||||
|
<StoryFlex>
|
||||||
|
<Button colorScheme="blue">Primary</Button>
|
||||||
|
<Button>Default</Button>
|
||||||
|
<Button
|
||||||
|
colorScheme="blue"
|
||||||
|
variant="outline"
|
||||||
|
css={{ py: '$1', borderRadius: '$md' }}
|
||||||
|
>
|
||||||
|
Use for NFA
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
colorScheme="gray"
|
||||||
|
variant="outline"
|
||||||
|
css={{ py: '$1', borderRadius: '$md' }}
|
||||||
|
>
|
||||||
|
NFA Repo
|
||||||
|
</Button>
|
||||||
|
</StoryFlex>
|
||||||
|
);
|
||||||
|
export const Icon = () => (
|
||||||
|
<>
|
||||||
|
<IconButton
|
||||||
|
aria-label="Add"
|
||||||
|
colorScheme="gray"
|
||||||
|
variant="link"
|
||||||
|
icon={<IconComponent name="info" />}
|
||||||
|
/>
|
||||||
|
<IconButton
|
||||||
|
aria-label="Add"
|
||||||
|
colorScheme="gray"
|
||||||
|
variant="link"
|
||||||
|
icon={<IconComponent name="back" />}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const ConnectorButtons = () => (
|
||||||
|
<StoryFlex>
|
||||||
|
<Button
|
||||||
|
size="lg"
|
||||||
|
iconSpacing="40"
|
||||||
|
variant="ghost"
|
||||||
|
rightIcon={<IconComponent name="github" css={{ color: 'white' }} />}
|
||||||
|
>
|
||||||
|
GitHub
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
disabled
|
||||||
|
size="lg"
|
||||||
|
iconSpacing="40"
|
||||||
|
variant="ghost"
|
||||||
|
rightIcon={<IconComponent name="github" css={{ color: 'white' }} />}
|
||||||
|
>
|
||||||
|
GitHub
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
size="lg"
|
||||||
|
iconSpacing="40"
|
||||||
|
variant="ghost"
|
||||||
|
rightIcon={<IconComponent name="ethereum" />}
|
||||||
|
>
|
||||||
|
Connect Ethereum Wallet
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
disabled
|
||||||
|
size="lg"
|
||||||
|
iconSpacing="40"
|
||||||
|
variant="ghost"
|
||||||
|
rightIcon={<IconComponent name="ethereum" />}
|
||||||
|
>
|
||||||
|
Connect Ethereum Wallet
|
||||||
|
</Button>
|
||||||
|
</StoryFlex>
|
||||||
|
);
|
||||||
|
|
@ -0,0 +1,249 @@
|
||||||
|
import { dripStitches } from '../../../theme/stitches';
|
||||||
|
import { CSS } from '@stitches/react';
|
||||||
|
|
||||||
|
type StyledButtonProps = React.ComponentProps<typeof StyledButton>;
|
||||||
|
export interface ButtonProps extends StyledButtonProps {
|
||||||
|
/**
|
||||||
|
* If `true`, the button will show a spinner.
|
||||||
|
*/
|
||||||
|
isLoading?: boolean;
|
||||||
|
/**
|
||||||
|
* If `true`, the button will be styled in its active state.
|
||||||
|
*/
|
||||||
|
isActive?: boolean;
|
||||||
|
/**
|
||||||
|
* If `true`, the button will be disabled.
|
||||||
|
*/
|
||||||
|
isDisabled?: boolean;
|
||||||
|
/**
|
||||||
|
* The label to show in the button when `isLoading` is true
|
||||||
|
* If no text is passed, it only shows the spinner
|
||||||
|
*/
|
||||||
|
loadingText?: string;
|
||||||
|
/**
|
||||||
|
* If `true`, the button will take up the full width of its container.
|
||||||
|
*/
|
||||||
|
isFullWidth?: boolean;
|
||||||
|
/**
|
||||||
|
* The html button type to use.
|
||||||
|
*/
|
||||||
|
type?: 'button' | 'reset' | 'submit';
|
||||||
|
/**
|
||||||
|
* If added, the button will show an icon before the button's label.
|
||||||
|
* @type React.ReactElement
|
||||||
|
*/
|
||||||
|
leftIcon?: React.ReactElement;
|
||||||
|
/**
|
||||||
|
* If added, the button will show an icon after the button's label.
|
||||||
|
* @type React.ReactElement
|
||||||
|
*/
|
||||||
|
rightIcon?: React.ReactElement;
|
||||||
|
/**
|
||||||
|
* If added, the button will show an icon on top side from the button's label.
|
||||||
|
* @type React.ReactElement
|
||||||
|
*/
|
||||||
|
topIcon?: React.ReactElement;
|
||||||
|
/**
|
||||||
|
* If added, the button will show an icon on bottom side from the button's label.
|
||||||
|
* @type React.ReactElement
|
||||||
|
*/
|
||||||
|
bottomIcon?: React.ReactElement;
|
||||||
|
/**
|
||||||
|
* The space between the button icon and label.
|
||||||
|
* @type SystemProps["marginRight"]
|
||||||
|
*/
|
||||||
|
iconSpacing?: string;
|
||||||
|
/**
|
||||||
|
* Replace the spinner component when `isLoading` is set to `true`
|
||||||
|
* @type React.ReactElement
|
||||||
|
*/
|
||||||
|
spinner?: React.ReactElement;
|
||||||
|
/**
|
||||||
|
* It determines the placement of the spinner when isLoading is true
|
||||||
|
* @default "start"
|
||||||
|
*/
|
||||||
|
spinnerPlacement?: 'start' | 'end';
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ButtonColor = 'gray' | 'blue';
|
||||||
|
export type ButtonVariant = 'solid' | 'outline' | 'ghost' | 'link';
|
||||||
|
|
||||||
|
export type GetButtonCompoundVariantOptions = {
|
||||||
|
color?: ButtonColor;
|
||||||
|
variant?: ButtonVariant;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getButtonCompoundVariant = ({
|
||||||
|
color = 'gray',
|
||||||
|
variant = 'solid',
|
||||||
|
}: GetButtonCompoundVariantOptions): CSS => {
|
||||||
|
switch (variant) {
|
||||||
|
case 'solid':
|
||||||
|
return {
|
||||||
|
color: 'white',
|
||||||
|
transition: '$all-200',
|
||||||
|
backgroundColor: `$${color}9`,
|
||||||
|
'&:focus, &:hover': {
|
||||||
|
backgroundColor: `$${color}10`,
|
||||||
|
},
|
||||||
|
'&:focus, &:active': {
|
||||||
|
backgroundColor: `$${color}11`,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
case 'outline':
|
||||||
|
return {
|
||||||
|
color: `$${color}11`,
|
||||||
|
transition: '$all-200',
|
||||||
|
backgroundColor: `$${color}4`,
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: `$${color}5`,
|
||||||
|
},
|
||||||
|
'&:focus, &:active': {
|
||||||
|
backgroundColor: `$${color}6`,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
case 'link':
|
||||||
|
return {
|
||||||
|
color: `$${color}11`,
|
||||||
|
transition: '$all-200',
|
||||||
|
height: 'auto',
|
||||||
|
px: '0',
|
||||||
|
'&:hover, &:focus': {
|
||||||
|
textDecoration: 'underline',
|
||||||
|
color: `$${color}12`,
|
||||||
|
'&:disabled': {
|
||||||
|
textDecoration: 'none',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
case 'ghost':
|
||||||
|
return {
|
||||||
|
color: `$slate11`,
|
||||||
|
transition: '$all-200',
|
||||||
|
|
||||||
|
'&:hover, &:focus, &:active': {
|
||||||
|
color: `$slate12`,
|
||||||
|
},
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: `$${color}4`,
|
||||||
|
},
|
||||||
|
'&:focus, &:active': {
|
||||||
|
backgroundColor: `$${color}3`,
|
||||||
|
},
|
||||||
|
|
||||||
|
'&:disabled': {
|
||||||
|
backgroundColor: `initial`,
|
||||||
|
'&:hover': {
|
||||||
|
color: `$${color}11`,
|
||||||
|
backgroundColor: `initial`,
|
||||||
|
},
|
||||||
|
'& img, & svg': {
|
||||||
|
filter: 'grayscale(100%)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
default:
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const { styled } = dripStitches;
|
||||||
|
|
||||||
|
export const StyledButton = styled('button', {
|
||||||
|
all: 'unset',
|
||||||
|
cursor: 'pointer',
|
||||||
|
position: 'relative',
|
||||||
|
display: 'inline-flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
verticalAlign: 'middle',
|
||||||
|
userSelect: 'none',
|
||||||
|
fontWeight: '$medium',
|
||||||
|
'&:disabled': {
|
||||||
|
cursor: 'not-allowed',
|
||||||
|
opacity: '0.4',
|
||||||
|
},
|
||||||
|
|
||||||
|
variants: {
|
||||||
|
size: {
|
||||||
|
sm: {
|
||||||
|
borderRadius: '$md',
|
||||||
|
fontSize: '$xs',
|
||||||
|
p: '$1 $3',
|
||||||
|
},
|
||||||
|
md: {
|
||||||
|
borderRadius: '$lg',
|
||||||
|
fontSize: '$sm',
|
||||||
|
p: '$3 $3h',
|
||||||
|
},
|
||||||
|
lg: {
|
||||||
|
borderRadius: '$xl',
|
||||||
|
fontSize: '$md',
|
||||||
|
p: '$4 $5',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
variant: {
|
||||||
|
solid: {
|
||||||
|
color: '$gray1',
|
||||||
|
},
|
||||||
|
ghost: {},
|
||||||
|
outline: {},
|
||||||
|
link: {},
|
||||||
|
unstyled: {},
|
||||||
|
},
|
||||||
|
colorScheme: {
|
||||||
|
gray: {},
|
||||||
|
blue: {},
|
||||||
|
red: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
compoundVariants: [
|
||||||
|
{
|
||||||
|
colorScheme: 'gray',
|
||||||
|
variant: 'solid',
|
||||||
|
css: getButtonCompoundVariant({ color: 'gray', variant: 'solid' }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
colorScheme: 'blue',
|
||||||
|
variant: 'solid',
|
||||||
|
css: getButtonCompoundVariant({ color: 'blue', variant: 'solid' }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
colorScheme: 'gray',
|
||||||
|
variant: 'outline',
|
||||||
|
css: getButtonCompoundVariant({ color: 'gray', variant: 'outline' }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
colorScheme: 'blue',
|
||||||
|
variant: 'outline',
|
||||||
|
css: getButtonCompoundVariant({ color: 'blue', variant: 'outline' }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
colorScheme: 'gray',
|
||||||
|
variant: 'ghost',
|
||||||
|
css: getButtonCompoundVariant({ color: 'gray', variant: 'ghost' }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
colorScheme: 'blue',
|
||||||
|
variant: 'ghost',
|
||||||
|
css: getButtonCompoundVariant({ color: 'blue', variant: 'ghost' }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
colorScheme: 'gray',
|
||||||
|
variant: 'link',
|
||||||
|
css: getButtonCompoundVariant({ color: 'gray', variant: 'link' }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
colorScheme: 'blue',
|
||||||
|
variant: 'link',
|
||||||
|
css: getButtonCompoundVariant({ color: 'blue', variant: 'link' }),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
defaultVariants: {
|
||||||
|
colorScheme: 'gray',
|
||||||
|
variant: 'solid',
|
||||||
|
size: 'md',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,75 @@
|
||||||
|
import { ButtonProps, StyledButton } from './button.styled';
|
||||||
|
import { ButtonContent } from './button-content';
|
||||||
|
import { ButtonSpinner } from './button-spinner';
|
||||||
|
import { forwardRef } from 'react';
|
||||||
|
|
||||||
|
export const Button = forwardRef<ButtonProps, 'button'>((props, ref) => {
|
||||||
|
const {
|
||||||
|
isActive,
|
||||||
|
isLoading,
|
||||||
|
isDisabled,
|
||||||
|
spinnerPlacement = 'start',
|
||||||
|
spinner,
|
||||||
|
loadingText,
|
||||||
|
iconSpacing,
|
||||||
|
topIcon,
|
||||||
|
bottomIcon,
|
||||||
|
rightIcon,
|
||||||
|
leftIcon,
|
||||||
|
isFullWidth,
|
||||||
|
children,
|
||||||
|
...ownProps
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const contentProps = {
|
||||||
|
rightIcon,
|
||||||
|
leftIcon,
|
||||||
|
bottomIcon,
|
||||||
|
topIcon,
|
||||||
|
iconSpacing,
|
||||||
|
children,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledButton
|
||||||
|
ref={ref}
|
||||||
|
disabled={isDisabled || isLoading}
|
||||||
|
data-active={isActive}
|
||||||
|
data-loading={isLoading}
|
||||||
|
css={{
|
||||||
|
width: isFullWidth ? '100%' : undefined,
|
||||||
|
}}
|
||||||
|
{...ownProps}
|
||||||
|
>
|
||||||
|
{isLoading && spinnerPlacement === 'start' && (
|
||||||
|
<ButtonSpinner
|
||||||
|
label={loadingText}
|
||||||
|
placement={spinnerPlacement}
|
||||||
|
spacing={iconSpacing}
|
||||||
|
>
|
||||||
|
{spinner}
|
||||||
|
</ButtonSpinner>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{isLoading ? (
|
||||||
|
loadingText || (
|
||||||
|
<span style={{ opacity: 0 }}>
|
||||||
|
<ButtonContent {...contentProps} />
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
) : (
|
||||||
|
<ButtonContent {...contentProps} />
|
||||||
|
)}
|
||||||
|
|
||||||
|
{isLoading && spinnerPlacement === 'end' && (
|
||||||
|
<ButtonSpinner
|
||||||
|
label={loadingText}
|
||||||
|
placement={spinnerPlacement}
|
||||||
|
spacing={iconSpacing}
|
||||||
|
>
|
||||||
|
{spinner}
|
||||||
|
</ButtonSpinner>
|
||||||
|
)}
|
||||||
|
</StyledButton>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,91 @@
|
||||||
|
import React, { forwardRef, useMemo } from 'react';
|
||||||
|
|
||||||
|
import { Button } from './button';
|
||||||
|
import { ButtonProps } from './button.styled';
|
||||||
|
|
||||||
|
type OmittedProps =
|
||||||
|
| 'leftIcon'
|
||||||
|
| 'isFullWidth'
|
||||||
|
| 'rightIcon'
|
||||||
|
| 'loadingText'
|
||||||
|
| 'iconSpacing'
|
||||||
|
| 'spinnerPlacement';
|
||||||
|
|
||||||
|
type BaseButtonProps = Omit<ButtonProps, OmittedProps>;
|
||||||
|
|
||||||
|
export interface IconButtonProps extends BaseButtonProps {
|
||||||
|
/**
|
||||||
|
* The icon to be used in the button.
|
||||||
|
* @type React.ReactElement
|
||||||
|
*/
|
||||||
|
icon?: React.ReactElement;
|
||||||
|
/**
|
||||||
|
* If `true`, the button will be perfectly round. Else, it'll be slightly round
|
||||||
|
*/
|
||||||
|
isRound?: boolean;
|
||||||
|
/**
|
||||||
|
* A11y: A label that describes the button
|
||||||
|
*/
|
||||||
|
'aria-label': string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const IconButton = forwardRef<IconButtonProps, 'button'>(
|
||||||
|
function IconButton(props, ref) {
|
||||||
|
const {
|
||||||
|
icon,
|
||||||
|
children,
|
||||||
|
isRound,
|
||||||
|
'aria-label': ariaLabel,
|
||||||
|
size = 'md',
|
||||||
|
...rest
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Passing the icon as prop or children should work
|
||||||
|
*/
|
||||||
|
const element = icon || children;
|
||||||
|
const _children = React.isValidElement(element)
|
||||||
|
? React.cloneElement(element, {
|
||||||
|
'aria-hidden': true,
|
||||||
|
focusable: false,
|
||||||
|
})
|
||||||
|
: null;
|
||||||
|
|
||||||
|
const { minWidth, fontSize } = useMemo(() => {
|
||||||
|
const props = {
|
||||||
|
sm: {
|
||||||
|
minWidth: '$8',
|
||||||
|
fontSize: '$lg',
|
||||||
|
},
|
||||||
|
md: {
|
||||||
|
minWidth: '$11',
|
||||||
|
fontSize: '$xl',
|
||||||
|
},
|
||||||
|
lg: {
|
||||||
|
minWidth: '$14',
|
||||||
|
fontSize: '$2xl',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return props[size as 'sm' | 'md' | 'lg'];
|
||||||
|
}, [size]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
ref={ref}
|
||||||
|
aria-label={ariaLabel}
|
||||||
|
size={size}
|
||||||
|
{...rest}
|
||||||
|
css={{
|
||||||
|
padding: 0,
|
||||||
|
minWidth,
|
||||||
|
fontSize,
|
||||||
|
borderRadius: isRound ? '$full' : undefined,
|
||||||
|
...(rest.css ?? {}),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{_children}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
export * from './button.styled';
|
||||||
|
export * from './button';
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
import { IconStyles as IS } from '../icon.styles';
|
||||||
|
|
||||||
|
export const EthereumIcon: React.FC<IS.CustomProps> = (props) => (
|
||||||
|
<IS.Custom
|
||||||
|
{...props}
|
||||||
|
viewBox="0 0 22 36"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M10.9728 0L10.7329 0.820393V24.6242L10.9728 24.8651L21.9453 18.3339L10.9728 0Z"
|
||||||
|
fill="#343434"
|
||||||
|
/>
|
||||||
|
<path d="M10.9727 0L0 18.3339L10.9727 24.8651V13.3114V0Z" fill="#8C8C8C" />
|
||||||
|
<path
|
||||||
|
d="M10.9727 26.9571L10.8376 27.1231V35.6024L10.9727 35.9997L21.952 20.4292L10.9727 26.9571Z"
|
||||||
|
fill="#3C3C3B"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M10.9727 35.9997V26.9571L0 20.4292L10.9727 35.9997Z"
|
||||||
|
fill="#8C8C8C"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M10.9727 24.8652L21.9452 18.3339L10.9727 13.3115V24.8652Z"
|
||||||
|
fill="#141414"
|
||||||
|
/>
|
||||||
|
<path d="M0 18.3339L10.9727 24.8652V13.3115L0 18.3339Z" fill="#393939" />
|
||||||
|
</IS.Custom>
|
||||||
|
);
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
export * from './metamask-icon';
|
||||||
|
export * from './ethereum-icon';
|
||||||
|
|
@ -1,16 +1,12 @@
|
||||||
import { Icon, IconProps } from '@chakra-ui/react';
|
import { IconStyles as IS } from '../icon.styles';
|
||||||
|
|
||||||
export const MetamaskIcon: React.FC<IconProps> = (props) => {
|
export const MetamaskIcon: React.FC<IS.CustomProps> = (props) => {
|
||||||
const { width = '1.5em', height = '1.5em' } = props;
|
|
||||||
return (
|
return (
|
||||||
<Icon
|
<IS.Custom
|
||||||
width={width}
|
|
||||||
height={height}
|
|
||||||
{...props}
|
{...props}
|
||||||
viewBox="0 0 256 240"
|
viewBox="0 0 256 240"
|
||||||
version="1.1"
|
fill="none"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
preserveAspectRatio="xMidYMid"
|
|
||||||
>
|
>
|
||||||
<title>MetaMask</title>
|
<title>MetaMask</title>
|
||||||
<g>
|
<g>
|
||||||
|
|
@ -131,6 +127,6 @@ export const MetamaskIcon: React.FC<IconProps> = (props) => {
|
||||||
points="163.383898 33.1117385 147.744691 75.3505047 144.425852 132.411352 143.155934 150.295986 143.055195 195.983514 112.943788 195.983514 112.846176 150.381702 111.572114 132.395585 108.251786 75.3505047 92.6150854 33.1117385"
|
points="163.383898 33.1117385 147.744691 75.3505047 144.425852 132.411352 143.155934 150.295986 143.055195 195.983514 112.943788 195.983514 112.846176 150.381702 111.572114 132.395585 108.251786 75.3505047 92.6150854 33.1117385"
|
||||||
></polygon>
|
></polygon>
|
||||||
</g>
|
</g>
|
||||||
</Icon>
|
</IS.Custom>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
import { IoLogoGithub } from '@react-icons/all-files/io5/IoLogoGithub';
|
||||||
|
import { IoArrowBackCircleSharp } from '@react-icons/all-files/io5/IoArrowBackCircleSharp';
|
||||||
|
import { IoInformationCircleSharp } from '@react-icons/all-files/io5/IoInformationCircleSharp';
|
||||||
|
import { MetamaskIcon, EthereumIcon } from './custom';
|
||||||
|
|
||||||
|
export const IconLibrary = Object.freeze({
|
||||||
|
back: IoArrowBackCircleSharp,
|
||||||
|
ethereum: EthereumIcon,
|
||||||
|
github: IoLogoGithub,
|
||||||
|
info: IoInformationCircleSharp,
|
||||||
|
metamask: MetamaskIcon, //remove if not used
|
||||||
|
});
|
||||||
|
|
||||||
|
export type IconName = keyof typeof IconLibrary;
|
||||||
|
|
||||||
|
export type IconType<Name extends IconName> = typeof IconLibrary[Name];
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
import { Icon } from './icon';
|
||||||
|
import { styled } from '@stitches/react';
|
||||||
|
import { Flex } from '../../layout';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: 'Components/Icons',
|
||||||
|
component: Icon,
|
||||||
|
};
|
||||||
|
|
||||||
|
const StoryFlex = styled(Flex, {
|
||||||
|
display: 'flex',
|
||||||
|
gap: '$2',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
color: 'white',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ConnectorIcons = () => (
|
||||||
|
<StoryFlex>
|
||||||
|
<Icon name="metamask" />
|
||||||
|
<Icon name="github" />
|
||||||
|
<Icon name="ethereum" />
|
||||||
|
</StoryFlex>
|
||||||
|
);
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
import { dripStitches } from '../../../theme/stitches'; //TODO replace for absolute path
|
||||||
|
|
||||||
|
const { styled } = dripStitches;
|
||||||
|
|
||||||
|
export abstract class IconStyles {
|
||||||
|
static readonly Container = styled('span', {
|
||||||
|
transition: 'transform $default',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
|
||||||
|
variants: {
|
||||||
|
size: {
|
||||||
|
sm: {
|
||||||
|
fontSize: '$md',
|
||||||
|
},
|
||||||
|
md: {
|
||||||
|
fontSize: '$2xl',
|
||||||
|
},
|
||||||
|
lg: {
|
||||||
|
fontSize: '$3xl',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
rotate: {
|
||||||
|
true: {
|
||||||
|
transform: 'rotate(-180deg)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
defaultVariants: {
|
||||||
|
rotate: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
static readonly Custom = styled('svg');
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace IconStyles {
|
||||||
|
export type CustomProps = React.ComponentProps<typeof IconStyles.Custom>;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
import { forwardRef } from 'react';
|
||||||
|
import { IconStyles } from './icon.styles';
|
||||||
|
import { IconLibrary, IconName, IconType } from './icon-library';
|
||||||
|
|
||||||
|
export type IconProps = {
|
||||||
|
name: IconName;
|
||||||
|
} & React.ComponentProps<typeof IconStyles.Container>;
|
||||||
|
|
||||||
|
export const Icon: React.FC<IconProps> = forwardRef<HTMLSpanElement, IconProps>(
|
||||||
|
(props, ref) => {
|
||||||
|
const { name, ...rest } = props;
|
||||||
|
const IconElement: IconType<typeof name> = IconLibrary[name];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<IconStyles.Container {...rest} ref={ref}>
|
||||||
|
<IconElement style={{ width: '1em', height: '1em' }} />
|
||||||
|
</IconStyles.Container>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
Icon.displayName = 'Icon';
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
export * from './icon';
|
||||||
|
export * from './icon-library';
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
export * from './button';
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
export * from './metamask-icon';
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
import { FaWallet } from 'react-icons/fa';
|
|
||||||
import { IoExitOutline } from 'react-icons/io5';
|
|
||||||
import { AiOutlineCopy } from 'react-icons/ai';
|
|
||||||
import { MetamaskIcon } from './custom';
|
|
||||||
|
|
||||||
export const IconLibrary = Object.freeze({
|
|
||||||
copy: AiOutlineCopy,
|
|
||||||
'log-out': IoExitOutline,
|
|
||||||
metamask: MetamaskIcon,
|
|
||||||
wallet: FaWallet,
|
|
||||||
});
|
|
||||||
|
|
||||||
export type IconName = keyof typeof IconLibrary;
|
|
||||||
|
|
||||||
export type IconType<Name extends IconName> = typeof IconLibrary[Name];
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
import {
|
|
||||||
forwardRef,
|
|
||||||
IconProps as IconPropsChakra,
|
|
||||||
Icon as IconChakra,
|
|
||||||
} from '@chakra-ui/react';
|
|
||||||
import { IconLibrary, IconName, IconType } from './icon-types';
|
|
||||||
|
|
||||||
export type IconComponentProps = IconPropsChakra & { name: IconName };
|
|
||||||
|
|
||||||
export const Icon = forwardRef<IconComponentProps, 'svg'>(
|
|
||||||
({ name, ...iconProps }, ref) => {
|
|
||||||
const IconElement: IconType<typeof name> = IconLibrary[name];
|
|
||||||
return <IconChakra as={IconElement} {...iconProps} ref={ref} />;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
export * from './icon';
|
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
import { dripStitches } from '../../theme/stitches'; //TODO replace with absolute path
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const { styled } = dripStitches;
|
||||||
|
|
||||||
|
export const Flex = styled('div', {
|
||||||
|
display: 'flex',
|
||||||
|
});
|
||||||
|
|
||||||
|
export type FlexProps = React.ComponentProps<typeof Flex>;
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
import { dripStitches } from '../../theme/stitches'; //TODO replace with absolute path
|
||||||
|
|
||||||
|
const { styled } = dripStitches;
|
||||||
|
|
||||||
|
export const Grid = styled('div', {
|
||||||
|
display: 'grid',
|
||||||
|
});
|
||||||
|
|
||||||
|
export type GridProps = React.ComponentProps<typeof Grid>;
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
export * from './grid.styled';
|
||||||
|
export * from './flex.styled';
|
||||||
|
|
@ -9,7 +9,7 @@ import {
|
||||||
Button,
|
Button,
|
||||||
Flex,
|
Flex,
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import { Icon } from '../icon';
|
import { Icon } from '../core/icon';
|
||||||
import { WalletType } from './wallet.utils';
|
import { WalletType } from './wallet.utils';
|
||||||
|
|
||||||
const WalletMenu: React.FC = () => {
|
const WalletMenu: React.FC = () => {
|
||||||
|
|
@ -41,7 +41,6 @@ const WalletMenu: React.FC = () => {
|
||||||
_hover={{ bg: 'custom.gray.100' }}
|
_hover={{ bg: 'custom.gray.100' }}
|
||||||
bg={'custom.gray.200'}
|
bg={'custom.gray.200'}
|
||||||
onClick={handleCopyAccount}
|
onClick={handleCopyAccount}
|
||||||
icon={<Icon name="copy" />}
|
|
||||||
>
|
>
|
||||||
Copy Account
|
Copy Account
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
|
@ -49,7 +48,6 @@ const WalletMenu: React.FC = () => {
|
||||||
_hover={{ bg: 'custom.gray.100' }}
|
_hover={{ bg: 'custom.gray.100' }}
|
||||||
bg={'custom.gray.200'}
|
bg={'custom.gray.200'}
|
||||||
onClick={handleDisconnect}
|
onClick={handleDisconnect}
|
||||||
icon={<Icon name="log-out" />}
|
|
||||||
>
|
>
|
||||||
Disconnect
|
Disconnect
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
|
@ -72,7 +70,6 @@ const ConnectionMenu: React.FC = () => {
|
||||||
<Button
|
<Button
|
||||||
borderRadius="50px"
|
borderRadius="50px"
|
||||||
as={MenuButton}
|
as={MenuButton}
|
||||||
leftIcon={<Icon name="wallet" />}
|
|
||||||
isLoading={state === 'loading'}
|
isLoading={state === 'loading'}
|
||||||
disabled={state === 'loading'}
|
disabled={state === 'loading'}
|
||||||
>
|
>
|
||||||
|
|
@ -83,7 +80,6 @@ const ConnectionMenu: React.FC = () => {
|
||||||
_hover={{ bg: 'custom.gray.100' }}
|
_hover={{ bg: 'custom.gray.100' }}
|
||||||
bg={'custom.gray.200'}
|
bg={'custom.gray.200'}
|
||||||
onClick={handleConnectWallet}
|
onClick={handleConnectWallet}
|
||||||
icon={<Icon name={WalletType.metamask} />}
|
|
||||||
>
|
>
|
||||||
Metamask
|
Metamask
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom/client';
|
import ReactDOM from 'react-dom/client';
|
||||||
import { App } from './app';
|
import { App } from './app';
|
||||||
import { ChakraProvider } from '@chakra-ui/react';
|
|
||||||
import { theme } from './theme';
|
|
||||||
import { Provider as ReduxProvider } from 'react-redux';
|
import { Provider as ReduxProvider } from 'react-redux';
|
||||||
import { store } from './store';
|
import { store } from './store';
|
||||||
import { QueryClient, QueryClientProvider } from 'react-query';
|
import { QueryClient, QueryClientProvider } from 'react-query';
|
||||||
|
|
@ -19,11 +17,9 @@ const root = ReactDOM.createRoot(
|
||||||
root.render(
|
root.render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<ReduxProvider store={store}>
|
<ReduxProvider store={store}>
|
||||||
<ChakraProvider theme={theme} resetCSS>
|
<QueryClientProvider client={queryClient}>
|
||||||
<QueryClientProvider client={queryClient}>
|
<App />
|
||||||
<App />
|
</QueryClientProvider>
|
||||||
</QueryClientProvider>
|
|
||||||
</ChakraProvider>
|
|
||||||
</ReduxProvider>
|
</ReduxProvider>
|
||||||
</React.StrictMode>
|
</React.StrictMode>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,43 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import { ComponentStory, ComponentMeta } from '@storybook/react';
|
|
||||||
|
|
||||||
import { Button } from './Button';
|
|
||||||
|
|
||||||
//TODO: remove example stories
|
|
||||||
|
|
||||||
// More on default export: https://storybook.js.org/docs/react/writing-stories/introduction#default-export
|
|
||||||
export default {
|
|
||||||
title: 'Example/Button',
|
|
||||||
component: Button,
|
|
||||||
// More on argTypes: https://storybook.js.org/docs/react/api/argtypes
|
|
||||||
argTypes: {
|
|
||||||
backgroundColor: { control: 'color' },
|
|
||||||
},
|
|
||||||
} as ComponentMeta<typeof Button>;
|
|
||||||
|
|
||||||
// More on component templates: https://storybook.js.org/docs/react/writing-stories/introduction#using-args
|
|
||||||
const Template: ComponentStory<typeof Button> = (args) => <Button {...args} />;
|
|
||||||
|
|
||||||
export const Primary = Template.bind({});
|
|
||||||
// More on args: https://storybook.js.org/docs/react/writing-stories/args
|
|
||||||
Primary.args = {
|
|
||||||
primary: true,
|
|
||||||
label: 'Button',
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Secondary = Template.bind({});
|
|
||||||
Secondary.args = {
|
|
||||||
label: 'Button',
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Large = Template.bind({});
|
|
||||||
Large.args = {
|
|
||||||
size: 'large',
|
|
||||||
label: 'Button',
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Small = Template.bind({});
|
|
||||||
Small.args = {
|
|
||||||
size: 'small',
|
|
||||||
label: 'Button',
|
|
||||||
};
|
|
||||||
|
|
@ -1,52 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import './button.css';
|
|
||||||
|
|
||||||
interface ButtonProps {
|
|
||||||
/**
|
|
||||||
* Is this the principal call to action on the page?
|
|
||||||
*/
|
|
||||||
primary?: boolean;
|
|
||||||
/**
|
|
||||||
* What background color to use
|
|
||||||
*/
|
|
||||||
backgroundColor?: string;
|
|
||||||
/**
|
|
||||||
* How large should the button be?
|
|
||||||
*/
|
|
||||||
size?: 'small' | 'medium' | 'large';
|
|
||||||
/**
|
|
||||||
* Button contents
|
|
||||||
*/
|
|
||||||
label: string;
|
|
||||||
/**
|
|
||||||
* Optional click handler
|
|
||||||
*/
|
|
||||||
onClick?: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Primary UI component for user interaction
|
|
||||||
*/
|
|
||||||
export const Button = ({
|
|
||||||
primary = false,
|
|
||||||
size = 'medium',
|
|
||||||
backgroundColor,
|
|
||||||
label,
|
|
||||||
...props
|
|
||||||
}: ButtonProps) => {
|
|
||||||
const mode = primary
|
|
||||||
? 'storybook-button--primary'
|
|
||||||
: 'storybook-button--secondary';
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className={['storybook-button', `storybook-button--${size}`, mode].join(
|
|
||||||
' '
|
|
||||||
)}
|
|
||||||
style={{ backgroundColor }}
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
{label}
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
.storybook-button {
|
|
||||||
font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
|
||||||
font-weight: 700;
|
|
||||||
border: 0;
|
|
||||||
border-radius: 3em;
|
|
||||||
cursor: pointer;
|
|
||||||
display: inline-block;
|
|
||||||
line-height: 1;
|
|
||||||
}
|
|
||||||
.storybook-button--primary {
|
|
||||||
color: white;
|
|
||||||
background-color: #1ea7fd;
|
|
||||||
}
|
|
||||||
.storybook-button--secondary {
|
|
||||||
color: #333;
|
|
||||||
background-color: transparent;
|
|
||||||
box-shadow: rgba(0, 0, 0, 0.15) 0px 0px 0px 1px inset;
|
|
||||||
}
|
|
||||||
.storybook-button--small {
|
|
||||||
font-size: 12px;
|
|
||||||
padding: 10px 16px;
|
|
||||||
}
|
|
||||||
.storybook-button--medium {
|
|
||||||
font-size: 14px;
|
|
||||||
padding: 11px 20px;
|
|
||||||
}
|
|
||||||
.storybook-button--large {
|
|
||||||
font-size: 16px;
|
|
||||||
padding: 12px 24px;
|
|
||||||
}
|
|
||||||
|
|
@ -101,6 +101,10 @@ export const createDripStitches = <
|
||||||
...zIndices,
|
...zIndices,
|
||||||
...(theme?.zIndices || {}),
|
...(theme?.zIndices || {}),
|
||||||
},
|
},
|
||||||
|
transitions: {
|
||||||
|
'all-200': 'all 200ms',
|
||||||
|
...(theme?.transitions || {}),
|
||||||
|
},
|
||||||
...(theme || {}),
|
...(theme || {}),
|
||||||
},
|
},
|
||||||
themeMap,
|
themeMap,
|
||||||
|
|
|
||||||
10
ui/yarn.lock
10
ui/yarn.lock
|
|
@ -2933,6 +2933,11 @@
|
||||||
resolved "https://registry.yarnpkg.com/@radix-ui/colors/-/colors-0.1.8.tgz#b08c62536fc462a87632165fb28e9b18f9bd047e"
|
resolved "https://registry.yarnpkg.com/@radix-ui/colors/-/colors-0.1.8.tgz#b08c62536fc462a87632165fb28e9b18f9bd047e"
|
||||||
integrity sha512-jwRMXYwC0hUo0mv6wGpuw254Pd9p/R6Td5xsRpOmaWkUHlooNWqVcadgyzlRumMq3xfOTXwJReU0Jv+EIy4Jbw==
|
integrity sha512-jwRMXYwC0hUo0mv6wGpuw254Pd9p/R6Td5xsRpOmaWkUHlooNWqVcadgyzlRumMq3xfOTXwJReU0Jv+EIy4Jbw==
|
||||||
|
|
||||||
|
"@react-icons/all-files@^4.1.0":
|
||||||
|
version "4.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@react-icons/all-files/-/all-files-4.1.0.tgz#477284873a0821928224b6fc84c62d2534d6650b"
|
||||||
|
integrity sha512-hxBI2UOuVaI3O/BhQfhtb4kcGn9ft12RWAFVMUeNjqqhLsHvFtzIkFaptBJpFDANTKoDfdVoHTKZDlwKCACbMQ==
|
||||||
|
|
||||||
"@reduxjs/toolkit@^1.9.1":
|
"@reduxjs/toolkit@^1.9.1":
|
||||||
version "1.9.1"
|
version "1.9.1"
|
||||||
resolved "https://registry.yarnpkg.com/@reduxjs/toolkit/-/toolkit-1.9.1.tgz#4c34dc4ddcec161535288c60da5c19c3ef15180e"
|
resolved "https://registry.yarnpkg.com/@reduxjs/toolkit/-/toolkit-1.9.1.tgz#4c34dc4ddcec161535288c60da5c19c3ef15180e"
|
||||||
|
|
@ -10808,11 +10813,6 @@ react-focus-lock@^2.9.1:
|
||||||
use-callback-ref "^1.3.0"
|
use-callback-ref "^1.3.0"
|
||||||
use-sidecar "^1.1.2"
|
use-sidecar "^1.1.2"
|
||||||
|
|
||||||
react-icons@^4.7.1:
|
|
||||||
version "4.7.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/react-icons/-/react-icons-4.7.1.tgz#0f4b25a5694e6972677cb189d2a72eabea7a8345"
|
|
||||||
integrity sha512-yHd3oKGMgm7zxo3EA7H2n7vxSoiGmHk5t6Ou4bXsfcgWyhfDKMpyKfhHR6Bjnn63c+YXBLBPUql9H4wPJM6sXw==
|
|
||||||
|
|
||||||
react-inspector@^5.1.0:
|
react-inspector@^5.1.0:
|
||||||
version "5.1.1"
|
version "5.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/react-inspector/-/react-inspector-5.1.1.tgz#58476c78fde05d5055646ed8ec02030af42953c8"
|
resolved "https://registry.yarnpkg.com/react-inspector/-/react-inspector-5.1.1.tgz#58476c78fde05d5055646ed8ec02030af42953c8"
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue