diff --git a/src/components/Button/Button.stories.tsx b/src/components/Button/Button.stories.tsx index baf5b83..ebad486 100644 --- a/src/components/Button/Button.stories.tsx +++ b/src/components/Button/Button.stories.tsx @@ -1,3 +1,3 @@ -import { Button } from "./Button"; +import Button from "./index"; export default { title: 'BasicButton' }; -export const Test = () => \ No newline at end of file +export const Test = () => ; \ No newline at end of file diff --git a/src/components/Button/Button.tsx b/src/components/Button/Button.tsx deleted file mode 100644 index c546e8a..0000000 --- a/src/components/Button/Button.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import React from 'react'; - -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 ( - - ); -}; diff --git a/src/components/Button/index.tsx b/src/components/Button/index.tsx new file mode 100644 index 0000000..f4c4e05 --- /dev/null +++ b/src/components/Button/index.tsx @@ -0,0 +1,99 @@ +import React, { FC } from 'react'; +import { ButtonElement } from './styles'; +import { + Colors, + ButtonProps, + FontColors, + ActiveColors, + DisabledColors, + DisabledFontColors +} from './types'; +import { Botton } from '../typography'; +import { + colorObjectToColorString, isBackgroundNone +} from './utils'; +import Spinner from '../../static/svg/Spinner'; +// import * as Styles from './styles'; + +enum Cursor { + DISABLED = "not-allowed", + LOADING = "progress", + DEFAULT = "pointer" +}; + +enum PaddingVertical { + lg = 16, + md = 16, + sm = 8 +}; + +enum PaddingHorizontal { + lg = 150, + md = 68, + sm = 16 +}; + +enum BorderRadius { + lg = 12, + md = 12, + sm = 4 +} + +const initialProps: ButtonProps = { + fill: "default", + size: "md", + loading: false, + disabled: false, + background: true +}; + +const Button: FC = (props = initialProps) => { + const { + children, + onClick, + disabled, + className, + fill, + leftIcon, + rightIcon, + loading, + size, + } = props; + + const cursorType = disabled ? "DISABLED" : loading ? "LOADING" : "DEFAULT"; + const colorString = colorObjectToColorString(fill, true); + const BackgroundColor = disabled ? DisabledColors[colorString] : Colors[colorString]; + const BackgroundActiveColor = disabled ? DisabledColors[colorString] : ActiveColors[colorString]; + const FontColor = disabled ? DisabledFontColors[colorString] : FontColors[colorString]; + + const styledProps = { + cursor: Cursor[cursorType], + background: BackgroundColor, + activeBackground: loading && isBackgroundNone(colorString) ? BackgroundColor : BackgroundActiveColor, + paddingVertical: PaddingVertical[size || "md"], + paddingHorizontal: PaddingHorizontal[size || "md"], + borderRadius: BorderRadius[size || "md"], + size: size || "md", + fillStyle: fill || "default", + marginLeft: leftIcon ? 6 : 0, + marginRight: loading || rightIcon ? 6 : 0, + }; + + return ( + + { leftIcon } + + { children } + + { loading && } + { rightIcon } + + ) +}; + +export default Button; \ No newline at end of file diff --git a/src/components/Button/styles.ts b/src/components/Button/styles.ts new file mode 100644 index 0000000..208e4bf --- /dev/null +++ b/src/components/Button/styles.ts @@ -0,0 +1,76 @@ +import styled from '@emotion/styled'; +import { css } from '@emotion/react'; +import { + ButtonElementProps, + Colors, + FontColors, +} from './types'; + +export const ButtonElement = styled.button` + display: flex; + align-items: center; + border: none; + cursor: ${(props) => props.cursor}; + background: ${(props) => props.background}; + padding: ${(props) => `${props.paddingVertical}px ${props.paddingHorizontal}px`}; + border-radius: ${(props) => props.borderRadius}px; + + ${(props) => typeof props.fillStyle !== "string" && props.fillStyle.full && Full} + + &:hover, &:active { + background: ${(props) => props.activeBackground}; + } + + & .semicolon-button-typography { + margin-left: ${(props) => props.marginLeft}px; + margin-right: ${(props) => props.marginRight}px; + } +`; + +export const Large = css` + border-radius: 12px; +`; + +export const Medium = css` + border-radius: 12px; +`; + +export const Small = css` + border-radius: 4px; +`; + +export const Default = css` +`; + +export const Purple = css` +`; + +export const PurpleLight = css` +`; + +export const Border = css` + border: 1px solid ${Colors.borderColor}; +`; + +export const BnDefault = css` + background: none; +`; + +export const BnPurple = css` + background: none; +`; + +export const Full = css` + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; + border-radius: 0; +`; + +export const Link = css` + &:hover, &:active { + text-decoration: underline 1px solid ${FontColors.link}; + } +`; \ No newline at end of file diff --git a/src/components/Button/types.ts b/src/components/Button/types.ts new file mode 100644 index 0000000..cbdad04 --- /dev/null +++ b/src/components/Button/types.ts @@ -0,0 +1,95 @@ +import { ReactElement, Component, MouseEvent } from 'react'; +import { colors } from '../shared/styles'; + +export type SizeType = "sm" | "md" | "lg"; + +export type BackgroundNoneSupportFillStyleType = "default" | "purple"; + +export type FillStyleType = "purpleLight" | "link" | "border" | BackgroundNoneSupportFillStyleType; + +export type BackgroundNoneFillStyleType = "bnDefault" | "bnPurple"; + +export interface FillStyleObjectType { + fillStyle?: FillStyleType, + background?: boolean, + full?: boolean +}; + +export type CursorType = "not-allowed" | "progress" | "pointer"; + +export interface ButtonProps { + leftIcon?: ReactElement ReactElement | null) | (new (props: any) => Component)>; + rightIcon?: ReactElement ReactElement | null) | (new (props: any) => Component)>; + fill?: FillStyleType | FillStyleObjectType; + size?: SizeType; + className?: string; + loading?: boolean; + disabled?: boolean; + background?: boolean; + onClick?: (event?: MouseEvent) => void; +}; + +export interface ButtonElementProps { + cursor: CursorType; + background: string; + activeBackground: string; + paddingVertical: number; + paddingHorizontal: number; + borderRadius: number; + size: SizeType; + fillStyle: FillStyleType | FillStyleObjectType; + marginLeft: number; + marginRight: number; +}; + +export const Colors = { + default: colors.grey100, + purple: colors.purple400, + purpleLight: colors.purple50, + border: colors.white, + borderColor: colors.grey100, + link: colors.white, + bnDefault: colors.white, + bnPurple: colors.white, +}; + +export const ActiveColors = { + default: colors.grey300, + purple: colors.purple500, + purpleLight: colors.purple100, + border: colors.grey100, + link: colors.white, + bnDefault: colors.grey50, + bnPurple: colors.purple50, +}; + +export const FontColors = { + default: colors.grey700, + purple: colors.white, + purpleLight: colors.purple400, + border: colors.grey700, + link: colors.blue400, + full: colors.grey700, + bnDefault: colors.grey700, + bnPurple: colors.purple400 +}; + +export const DisabledColors = { + default: colors.grey50, + purple: colors.purple50, + purpleLight: colors.grey50, + border: colors.grey50, + link: colors.grey50, + bnDefault: colors.grey50, + bnPurple: colors.grey50, +} + +export const DisabledFontColors = { + default: colors.grey300, + purple: colors.white, + purpleLight: colors.grey300, + border: colors.grey300, + link: colors.grey300, + bnDefault: colors.grey300, + bnPurple: colors.grey300, +} \ No newline at end of file diff --git a/src/components/Button/utils.ts b/src/components/Button/utils.ts new file mode 100644 index 0000000..82f4eb4 --- /dev/null +++ b/src/components/Button/utils.ts @@ -0,0 +1,95 @@ +import { + SizeType, + FillStyleType, + BackgroundNoneFillStyleType, + FillStyleObjectType, +} from './types'; +import { + Small, + Medium, + Large, + Default, + Purple, + PurpleLight, + Link, + Full, + Border, + BnDefault, + BnPurple +} from './styles'; + +export function sizeToCssObject(size: SizeType) { + switch(size) { + case "sm": + return Small; + case "md": + return Medium; + case "lg": + return Large; + default: + return Medium; + } +} + +export function colorToCssObject(color: FillStyleType | BackgroundNoneFillStyleType) { + switch(color) { + case "default": + return Default; + case "purple": + return Purple; + case "purpleLight": + return PurpleLight; + case "border": + return Border; + case "link": + return Link; + case "bnDefault": + return BnDefault; + case "bnPurple": + return BnPurple; + default: + return Default; + } +} + +export function colorObjectToCssObject(color: FillStyleType | FillStyleObjectType) { + if(typeof color === "string") { + return colorToCssObject(color); + } else { + if(color.background === true) return colorToCssObject(color.fillStyle || "default"); + else { + switch(color.fillStyle) { + case "default": + return colorToCssObject(firstCharToTypeMessage(color.fillStyle)); + case "purple": + return colorToCssObject(firstCharToTypeMessage(color.fillStyle)); + default: + return colorToCssObject(color.fillStyle || "default"); + } + } + } +} + +export function colorObjectToColorString(color?: FillStyleType | FillStyleObjectType, addedBn?: boolean) { + if(typeof color === "string") return color || "default"; + else if(typeof color === "undefined") return "default" + else if(addedBn && color.background === false) { + switch(color.fillStyle) { + case "default": + return firstCharToTypeMessage(color.fillStyle); + case "purple": + return firstCharToTypeMessage(color.fillStyle); + default: + return color.fillStyle || "default"; + } + } else return color.fillStyle || "default"; +} + +export function firstCharToTypeMessage(message: string): BackgroundNoneFillStyleType { + return "bn" + message.charAt(0).toUpperCase() + message.slice(1) as BackgroundNoneFillStyleType; +} + +export function isBackgroundNone(color: string): boolean { + if(color.indexOf("bn") !== -1) return true; + else return false; +} \ No newline at end of file diff --git a/src/components/index.ts b/src/components/index.ts index c1a579f..08faf3d 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -1,2 +1,2 @@ -export * from './Button/Button' +export * from './Button' export * from './typography' \ No newline at end of file diff --git a/src/static/svg/Spinner.tsx b/src/static/svg/Spinner.tsx new file mode 100644 index 0000000..3e41ce0 --- /dev/null +++ b/src/static/svg/Spinner.tsx @@ -0,0 +1,60 @@ +import React, { FC } from 'react'; +import { colors } from '../../components/shared/styles'; +import { FillStyleType, BackgroundNoneFillStyleType } from '../../components/Button/types'; + +interface SpinnerProps { + fill: FillStyleType | BackgroundNoneFillStyleType; +} + +const Colors = { + default: { + inColor: colors.grey500, + outColor: colors.grey300 + }, + purple: { + inColor: colors.white, + outColor: colors.purple500 + }, + purpleLight: { + inColor: colors.purple400, + outColor: colors.purple100 + }, + border: { + inColor: colors.grey500, + outColor: colors.grey300 + }, + link: { + inColor: colors.white, + outColor: colors.white + }, + bnDefault: { + inColor: colors.white, + outColor: colors.white + }, + bnPurple: { + inColor: colors.white, + outColor: colors.white + }, +} + +const Spinner: FC = (props) => { + const {outColor, inColor} = Colors[props.fill]; + + return ( + + + + + + + + + + + + + + ) +} + +export default Spinner \ No newline at end of file