From 75a6de5ac7a1457918354f705c68a8af27db7aca Mon Sep 17 00:00:00 2001 From: Camila Sosa Morales Date: Mon, 23 Jan 2023 15:01:02 -0500 Subject: [PATCH] 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 --- ui/.storybook/preview.js | 4 + ui/package.json | 2 +- .../core/button/button-content.styled.ts | 12 + .../components/core/button/button-content.tsx | 57 ++++ .../core/button/button-icon.styled.ts | 17 ++ ui/src/components/core/button/button-icon.tsx | 23 ++ .../core/button/button-spinner.styled.ts | 33 +++ .../components/core/button/button-spinner.tsx | 69 +++++ .../components/core/button/button.stories.tsx | 94 +++++++ .../components/core/button/button.styled.ts | 249 ++++++++++++++++++ ui/src/components/core/button/button.tsx | 75 ++++++ ui/src/components/core/button/icon-button.tsx | 91 +++++++ ui/src/components/core/button/index.ts | 2 + .../core/icon/custom/ethereum-icon.tsx | 29 ++ ui/src/components/core/icon/custom/index.ts | 2 + .../{ => core}/icon/custom/metamask-icon.tsx | 14 +- ui/src/components/core/icon/icon-library.tsx | 16 ++ ui/src/components/core/icon/icon.stories.tsx | 23 ++ ui/src/components/core/icon/icon.styles.ts | 42 +++ ui/src/components/core/icon/icon.tsx | 22 ++ ui/src/components/core/icon/index.ts | 2 + ui/src/components/core/index.ts | 1 + ui/src/components/icon/custom/index.ts | 1 - ui/src/components/icon/icon-types.tsx | 15 -- ui/src/components/icon/icon.tsx | 16 -- ui/src/components/icon/index.ts | 1 - ui/src/components/layout/flex.styled.ts | 10 + ui/src/components/layout/grid.styled.ts | 9 + ui/src/components/layout/index.ts | 2 + .../wallet-button/wallet-button.tsx | 6 +- ui/src/index.tsx | 10 +- ui/src/stories/Button.stories.tsx | 43 --- ui/src/stories/Button.tsx | 52 ---- ui/src/stories/button.css | 30 --- ui/src/theme/stitches/themes.ts | 4 + ui/yarn.lock | 10 +- 36 files changed, 903 insertions(+), 185 deletions(-) create mode 100644 ui/src/components/core/button/button-content.styled.ts create mode 100644 ui/src/components/core/button/button-content.tsx create mode 100644 ui/src/components/core/button/button-icon.styled.ts create mode 100644 ui/src/components/core/button/button-icon.tsx create mode 100644 ui/src/components/core/button/button-spinner.styled.ts create mode 100644 ui/src/components/core/button/button-spinner.tsx create mode 100644 ui/src/components/core/button/button.stories.tsx create mode 100644 ui/src/components/core/button/button.styled.ts create mode 100644 ui/src/components/core/button/button.tsx create mode 100644 ui/src/components/core/button/icon-button.tsx create mode 100644 ui/src/components/core/button/index.ts create mode 100644 ui/src/components/core/icon/custom/ethereum-icon.tsx create mode 100644 ui/src/components/core/icon/custom/index.ts rename ui/src/components/{ => core}/icon/custom/metamask-icon.tsx (93%) create mode 100644 ui/src/components/core/icon/icon-library.tsx create mode 100644 ui/src/components/core/icon/icon.stories.tsx create mode 100644 ui/src/components/core/icon/icon.styles.ts create mode 100644 ui/src/components/core/icon/icon.tsx create mode 100644 ui/src/components/core/icon/index.ts create mode 100644 ui/src/components/core/index.ts delete mode 100644 ui/src/components/icon/custom/index.ts delete mode 100644 ui/src/components/icon/icon-types.tsx delete mode 100644 ui/src/components/icon/icon.tsx delete mode 100644 ui/src/components/icon/index.ts create mode 100644 ui/src/components/layout/flex.styled.ts create mode 100644 ui/src/components/layout/grid.styled.ts create mode 100644 ui/src/components/layout/index.ts delete mode 100644 ui/src/stories/Button.stories.tsx delete mode 100644 ui/src/stories/Button.tsx delete mode 100644 ui/src/stories/button.css diff --git a/ui/.storybook/preview.js b/ui/.storybook/preview.js index 6b02860..745e73e 100644 --- a/ui/.storybook/preview.js +++ b/ui/.storybook/preview.js @@ -13,6 +13,10 @@ export const parameters = { date: /Date$/, }, }, + darkMode: { + dark: { ...themes.dark, backgroundColor: 'black' }, + // light: { ...themes.normal, backgroundColor: 'white' }, + }, }; const { darkTheme: darkThemeClassName } = dripStitches; diff --git a/ui/package.json b/ui/package.json index bbdca31..08a9f5a 100644 --- a/ui/package.json +++ b/ui/package.json @@ -19,6 +19,7 @@ "@emotion/styled": "^11.10.5", "@ethersproject/providers": "^5.7.2", "@radix-ui/colors": "^0.1.8", + "@react-icons/all-files": "^4.1.0", "@reduxjs/toolkit": "^1.9.1", "@stitches/react": "^1.2.8", "formik": "^2.2.9", @@ -57,7 +58,6 @@ "ethers": "^5.7.2", "postcss": "^8.4.21", "prettier": "^2.8.0", - "react-icons": "^4.7.1", "react-query": "^3.39.2", "storybook-dark-mode": "^2.0.5", "tailwindcss": "^3.2.4", diff --git a/ui/src/components/core/button/button-content.styled.ts b/ui/src/components/core/button/button-content.styled.ts new file mode 100644 index 0000000..f8cc60f --- /dev/null +++ b/ui/src/components/core/button/button-content.styled.ts @@ -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', +}); diff --git a/ui/src/components/core/button/button-content.tsx b/ui/src/components/core/button/button-content.tsx new file mode 100644 index 0000000..81a168f --- /dev/null +++ b/ui/src/components/core/button/button-content.tsx @@ -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 = (props) => { + const { + leftIcon, + rightIcon, + topIcon, + bottomIcon, + children, + iconSpacing = '1h', + } = props; + + const midNode = ( + <> + {leftIcon && ( + + {leftIcon} + + )} + {children} + {rightIcon && ( + + {rightIcon} + + )} + + ); + + if (!topIcon && !bottomIcon) { + return midNode; + } + + return ( + + {topIcon && {topIcon}} + {midNode} + {bottomIcon && {bottomIcon}} + + ); +}; diff --git a/ui/src/components/core/button/button-icon.styled.ts b/ui/src/components/core/button/button-icon.styled.ts new file mode 100644 index 0000000..655c891 --- /dev/null +++ b/ui/src/components/core/button/button-icon.styled.ts @@ -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 +>; diff --git a/ui/src/components/core/button/button-icon.tsx b/ui/src/components/core/button/button-icon.tsx new file mode 100644 index 0000000..6950c28 --- /dev/null +++ b/ui/src/components/core/button/button-icon.tsx @@ -0,0 +1,23 @@ +import * as React from 'react'; + +import { + ButtonIconSpanProps, + StyledButtonIconSpan, +} from './button-icon.styled'; + +export const ButtonIcon: React.FC = (props) => { + const { children, className, ...rest } = props; + + const _children = React.isValidElement(children) + ? React.cloneElement(children, { + 'aria-hidden': true, + focusable: false, + }) + : children; + + return ( + + {_children} + + ); +}; diff --git a/ui/src/components/core/button/button-spinner.styled.ts b/ui/src/components/core/button/button-spinner.styled.ts new file mode 100644 index 0000000..9106126 --- /dev/null +++ b/ui/src/components/core/button/button-spinner.styled.ts @@ -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 { + label?: string; + spacing?: string; + placement?: 'start' | 'end'; +} diff --git a/ui/src/components/core/button/button-spinner.tsx b/ui/src/components/core/button/button-spinner.tsx new file mode 100644 index 0000000..1cb8638 --- /dev/null +++ b/ui/src/components/core/button/button-spinner.tsx @@ -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 = (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 ( + + {children || } + + ); +}; + +const { keyframes } = dripStitches; + +const blink = keyframes({ + '0%': { opacity: 0.2 }, + '50%': { opacity: 1 }, + '100%': { opacity: 0.2 }, +}); + +const ButtonSpinnerDots: React.FC> = (props) => { + return ( + + + + + + ); +}; diff --git a/ui/src/components/core/button/button.stories.tsx b/ui/src/components/core/button/button.stories.tsx new file mode 100644 index 0000000..b6b127d --- /dev/null +++ b/ui/src/components/core/button/button.stories.tsx @@ -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 = () => ( + + + + + + +); +export const Icon = () => ( + <> + } + /> + } + /> + +); + +export const ConnectorButtons = () => ( + + + + + + +); diff --git a/ui/src/components/core/button/button.styled.ts b/ui/src/components/core/button/button.styled.ts new file mode 100644 index 0000000..3d6300d --- /dev/null +++ b/ui/src/components/core/button/button.styled.ts @@ -0,0 +1,249 @@ +import { dripStitches } from '../../../theme/stitches'; +import { CSS } from '@stitches/react'; + +type StyledButtonProps = React.ComponentProps; +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', + }, +}); diff --git a/ui/src/components/core/button/button.tsx b/ui/src/components/core/button/button.tsx new file mode 100644 index 0000000..592372b --- /dev/null +++ b/ui/src/components/core/button/button.tsx @@ -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((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 ( + + {isLoading && spinnerPlacement === 'start' && ( + + {spinner} + + )} + + {isLoading ? ( + loadingText || ( + + + + ) + ) : ( + + )} + + {isLoading && spinnerPlacement === 'end' && ( + + {spinner} + + )} + + ); +}); diff --git a/ui/src/components/core/button/icon-button.tsx b/ui/src/components/core/button/icon-button.tsx new file mode 100644 index 0000000..9baf9f8 --- /dev/null +++ b/ui/src/components/core/button/icon-button.tsx @@ -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; + +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( + 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 ( + + ); + } +); diff --git a/ui/src/components/core/button/index.ts b/ui/src/components/core/button/index.ts new file mode 100644 index 0000000..3925955 --- /dev/null +++ b/ui/src/components/core/button/index.ts @@ -0,0 +1,2 @@ +export * from './button.styled'; +export * from './button'; diff --git a/ui/src/components/core/icon/custom/ethereum-icon.tsx b/ui/src/components/core/icon/custom/ethereum-icon.tsx new file mode 100644 index 0000000..afb923b --- /dev/null +++ b/ui/src/components/core/icon/custom/ethereum-icon.tsx @@ -0,0 +1,29 @@ +import { IconStyles as IS } from '../icon.styles'; + +export const EthereumIcon: React.FC = (props) => ( + + + + + + + + +); diff --git a/ui/src/components/core/icon/custom/index.ts b/ui/src/components/core/icon/custom/index.ts new file mode 100644 index 0000000..e0ce0d3 --- /dev/null +++ b/ui/src/components/core/icon/custom/index.ts @@ -0,0 +1,2 @@ +export * from './metamask-icon'; +export * from './ethereum-icon'; diff --git a/ui/src/components/icon/custom/metamask-icon.tsx b/ui/src/components/core/icon/custom/metamask-icon.tsx similarity index 93% rename from ui/src/components/icon/custom/metamask-icon.tsx rename to ui/src/components/core/icon/custom/metamask-icon.tsx index b3c8f1d..b6e812d 100644 --- a/ui/src/components/icon/custom/metamask-icon.tsx +++ b/ui/src/components/core/icon/custom/metamask-icon.tsx @@ -1,16 +1,12 @@ -import { Icon, IconProps } from '@chakra-ui/react'; +import { IconStyles as IS } from '../icon.styles'; -export const MetamaskIcon: React.FC = (props) => { - const { width = '1.5em', height = '1.5em' } = props; +export const MetamaskIcon: React.FC = (props) => { return ( - MetaMask @@ -131,6 +127,6 @@ export const MetamaskIcon: React.FC = (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" > - + ); }; diff --git a/ui/src/components/core/icon/icon-library.tsx b/ui/src/components/core/icon/icon-library.tsx new file mode 100644 index 0000000..ed7a545 --- /dev/null +++ b/ui/src/components/core/icon/icon-library.tsx @@ -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 = typeof IconLibrary[Name]; diff --git a/ui/src/components/core/icon/icon.stories.tsx b/ui/src/components/core/icon/icon.stories.tsx new file mode 100644 index 0000000..e71542d --- /dev/null +++ b/ui/src/components/core/icon/icon.stories.tsx @@ -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 = () => ( + + + + + +); diff --git a/ui/src/components/core/icon/icon.styles.ts b/ui/src/components/core/icon/icon.styles.ts new file mode 100644 index 0000000..e3e6ec7 --- /dev/null +++ b/ui/src/components/core/icon/icon.styles.ts @@ -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; +} diff --git a/ui/src/components/core/icon/icon.tsx b/ui/src/components/core/icon/icon.tsx new file mode 100644 index 0000000..4596dc5 --- /dev/null +++ b/ui/src/components/core/icon/icon.tsx @@ -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; + +export const Icon: React.FC = forwardRef( + (props, ref) => { + const { name, ...rest } = props; + const IconElement: IconType = IconLibrary[name]; + + return ( + + + + ); + } +); + +Icon.displayName = 'Icon'; diff --git a/ui/src/components/core/icon/index.ts b/ui/src/components/core/icon/index.ts new file mode 100644 index 0000000..c4e02ac --- /dev/null +++ b/ui/src/components/core/icon/index.ts @@ -0,0 +1,2 @@ +export * from './icon'; +export * from './icon-library'; diff --git a/ui/src/components/core/index.ts b/ui/src/components/core/index.ts new file mode 100644 index 0000000..1a78866 --- /dev/null +++ b/ui/src/components/core/index.ts @@ -0,0 +1 @@ +export * from './button'; diff --git a/ui/src/components/icon/custom/index.ts b/ui/src/components/icon/custom/index.ts deleted file mode 100644 index b166728..0000000 --- a/ui/src/components/icon/custom/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './metamask-icon'; diff --git a/ui/src/components/icon/icon-types.tsx b/ui/src/components/icon/icon-types.tsx deleted file mode 100644 index 3f362f7..0000000 --- a/ui/src/components/icon/icon-types.tsx +++ /dev/null @@ -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 = typeof IconLibrary[Name]; diff --git a/ui/src/components/icon/icon.tsx b/ui/src/components/icon/icon.tsx deleted file mode 100644 index eeb8115..0000000 --- a/ui/src/components/icon/icon.tsx +++ /dev/null @@ -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( - ({ name, ...iconProps }, ref) => { - const IconElement: IconType = IconLibrary[name]; - return ; - } -); - diff --git a/ui/src/components/icon/index.ts b/ui/src/components/icon/index.ts deleted file mode 100644 index bd7846d..0000000 --- a/ui/src/components/icon/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './icon'; diff --git a/ui/src/components/layout/flex.styled.ts b/ui/src/components/layout/flex.styled.ts new file mode 100644 index 0000000..efc902e --- /dev/null +++ b/ui/src/components/layout/flex.styled.ts @@ -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; diff --git a/ui/src/components/layout/grid.styled.ts b/ui/src/components/layout/grid.styled.ts new file mode 100644 index 0000000..9608a07 --- /dev/null +++ b/ui/src/components/layout/grid.styled.ts @@ -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; diff --git a/ui/src/components/layout/index.ts b/ui/src/components/layout/index.ts new file mode 100644 index 0000000..baec0ec --- /dev/null +++ b/ui/src/components/layout/index.ts @@ -0,0 +1,2 @@ +export * from './grid.styled'; +export * from './flex.styled'; diff --git a/ui/src/components/wallet-button/wallet-button.tsx b/ui/src/components/wallet-button/wallet-button.tsx index 9d86966..0ac6b1d 100644 --- a/ui/src/components/wallet-button/wallet-button.tsx +++ b/ui/src/components/wallet-button/wallet-button.tsx @@ -9,7 +9,7 @@ import { Button, Flex, } from '@chakra-ui/react'; -import { Icon } from '../icon'; +import { Icon } from '../core/icon'; import { WalletType } from './wallet.utils'; const WalletMenu: React.FC = () => { @@ -41,7 +41,6 @@ const WalletMenu: React.FC = () => { _hover={{ bg: 'custom.gray.100' }} bg={'custom.gray.200'} onClick={handleCopyAccount} - icon={} > Copy Account @@ -49,7 +48,6 @@ const WalletMenu: React.FC = () => { _hover={{ bg: 'custom.gray.100' }} bg={'custom.gray.200'} onClick={handleDisconnect} - icon={} > Disconnect @@ -72,7 +70,6 @@ const ConnectionMenu: React.FC = () => { - ); -}; diff --git a/ui/src/stories/button.css b/ui/src/stories/button.css deleted file mode 100644 index dc91dc7..0000000 --- a/ui/src/stories/button.css +++ /dev/null @@ -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; -} diff --git a/ui/src/theme/stitches/themes.ts b/ui/src/theme/stitches/themes.ts index b01cc8c..eae1c2c 100644 --- a/ui/src/theme/stitches/themes.ts +++ b/ui/src/theme/stitches/themes.ts @@ -101,6 +101,10 @@ export const createDripStitches = < ...zIndices, ...(theme?.zIndices || {}), }, + transitions: { + 'all-200': 'all 200ms', + ...(theme?.transitions || {}), + }, ...(theme || {}), }, themeMap, diff --git a/ui/yarn.lock b/ui/yarn.lock index fbdb693..dcf9382 100644 --- a/ui/yarn.lock +++ b/ui/yarn.lock @@ -2933,6 +2933,11 @@ resolved "https://registry.yarnpkg.com/@radix-ui/colors/-/colors-0.1.8.tgz#b08c62536fc462a87632165fb28e9b18f9bd047e" 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": version "1.9.1" 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-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: version "5.1.1" resolved "https://registry.yarnpkg.com/react-inspector/-/react-inspector-5.1.1.tgz#58476c78fde05d5055646ed8ec02030af42953c8"