diff --git a/package.json b/package.json index bda3bc43..d9bb3ec2 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,9 @@ "*.js": [ "yarn lint:js --fix" ], + "*.ts?(x)": [ + "yarn lint:js --fix" + ], "package.json": [ "yarn lint:package" ] diff --git a/src/components/ButtonToggle.stories.tsx b/src/components/ButtonToggle.stories.tsx new file mode 100644 index 00000000..4e6ccac0 --- /dev/null +++ b/src/components/ButtonToggle.stories.tsx @@ -0,0 +1,120 @@ +import { action } from '@storybook/addon-actions'; +import React, { useState } from 'react'; +import { ButtonToggle } from './ButtonToggle'; +// @ts-ignore +import chromeImg from '../images/chrome.svg'; +// @ts-ignore +import firefoxImg from '../images/firefox.svg'; +// @ts-ignore +import ieImg from '../images/ie.svg'; +// @ts-ignore +import safariImg from '../images/safari.svg'; + +const onSelectIndex = action('onSelectIndex'); + +export default { + title: 'Design System/ButtonToggle', +}; + +export const OutlineButtonsSmall = () => { + const [index, setIndex] = useState(1); + + return ( + setIndex(idx)} + /> + ); +}; + +OutlineButtonsSmall.story = { + name: 'outline buttons small', +}; + +export const OutlineThreeButtonsSmall = () => { + const [index, setIndex] = useState(1); + + return ( + setIndex(idx)} + /> + ); +}; + +OutlineThreeButtonsSmall.story = { + name: 'outline three buttons small', +}; + +export const PillWText = () => ( + +); + +PillWText.story = { + name: 'pill w/text', +}; + +export const PillWImage = () => ( + , + tooltip: 'View Chrome', + }, + { + title: Firefox, + tooltip: 'View Firefox', + }, + { + title: Safari, + tooltip: 'View Safari', + }, + { + title: IE, + tooltip: 'View IE', + }, + ]} + selectedIndex={1} + onSelectIndex={onSelectIndex} + appearance="pill" + /> +); + +PillWImage.story = { + name: 'pill w/image', +}; + +export const Tab = () => ( + +); + +Tab.story = { + name: 'tab', +}; diff --git a/src/components/ButtonToggle.tsx b/src/components/ButtonToggle.tsx new file mode 100644 index 00000000..c335f177 --- /dev/null +++ b/src/components/ButtonToggle.tsx @@ -0,0 +1,231 @@ +import { opacify, rgba } from 'polished'; +import React from 'react'; +import styled, { css } from 'styled-components'; +import { color, typography } from './shared/styles'; +import { Link } from './Link'; +// @ts-ignore +import { TooltipNote } from './tooltip/TooltipNote'; +// @ts-ignore +import WithTooltip from './tooltip/WithTooltip'; + +const activePadding = 20; +const inActivePadding = 15; + +interface StyledButtonProps { + isActive: boolean; +} + +const StyledButton = styled.button` + border: 0; + border-radius: 3em; + cursor: pointer; + display: inline-block; + overflow: hidden; + padding: 8px ${inActivePadding}px; + position: relative; + text-align: center; + text-decoration: none; + transition: background-color 150ms ease-out, color 150ms ease-out; + transform: translate3d(0, 0, 0); + vertical-align: top; + white-space: nowrap; + user-select: none; + opacity: 1; + margin: 0; + + font-size: ${typography.size.s1}px; + font-weight: ${typography.weight.extrabold}; + line-height: 1; + + color: ${color.dark}; + background-color: transparent; + + ${(props) => + props.isActive && + css` + background-color: ${color.lightest}; + box-shadow: ${opacify(0.05, color.border)} 0 0 0 1px inset; + color: ${color.darkest}; + padding-right: ${activePadding}px; + padding-left: ${activePadding}px; + `} +`; + +const TooltipWrapper = styled(WithTooltip)` + ${(props) => + props.isActive && + css` + &:first-child { + margin-right: -${activePadding - inActivePadding}px; + } + :not(:first-child):not(:last-child) { + margin-left: -${activePadding - inActivePadding}px; + margin-right: -${activePadding - inActivePadding}px; + } + &:last-child { + margin-left: -${activePadding - inActivePadding}px; + } + `} + + &:first-child ${StyledButton} { + padding-left: ${activePadding}px; + } + + &:last-child ${StyledButton} { + padding-right: ${activePadding}px; + } + + &:hover ${StyledButton} { + position: relative; + z-index: 1; + } +`; + +const PillButton = styled(Link)` + font-size: ${typography.size.s1}px; + font-weight: ${typography.weight.bold}; + line-height: 1; + display: inline-block; + vertical-align: top; + padding: 4px 8px; + border-radius: 10px; + &:hover { + background: #e3f3ff; + color: ${color.secondary}; + img { + opacity: 1; + } + } + img { + height: 1rem; + width: 1rem; + opacity: 0.3; + transition: all 150ms ease-out; + margin: -3px 0; + } + ${(props) => + props.active && + css` + background: #e3f3ff; + color: ${color.secondary}; + img { + opacity: 1; + } + `}; +`; + +const TabButton = styled(Link)` + font-size: ${typography.size.s2 - 1}px; + font-weight: ${typography.weight.bold}; + line-height: 20px; + display: inline-block; + padding: 10px 15px; + &:hover { + color: ${color.secondary}; + } + ${(props) => + props.active && + css` + color: ${color.secondary}; + box-shadow: ${color.secondary} 0 -3px 0 0 inset; + `}; +`; + +interface WrapperProps { + appearance: 'outline' | 'pill' | 'tab'; +} + +const Wrapper = styled.div` + display: inline-flex; + align-items: center; + white-space: nowrap; + + ${(props) => + props.appearance === 'outline' && + css` + background-color: ${opacify(0.05, color.border)}; + border-radius: 3em; + `} +`; + +type Title = { + title: string | React.ReactNode; + tooltip: string; +}; + +interface ButtonToggleProps { + titles: Title[]; + onSelectIndex: (index: number) => void; + selectedIndex: number; + appearance?: 'outline' | 'pill' | 'tab'; +} + +export function ButtonToggle({ + titles, + onSelectIndex, + selectedIndex, + appearance, + ...props +}: ButtonToggleProps) { + return ( + + {titles.map(({ title, tooltip }, index) => { + switch (appearance) { + case 'pill': + return ( + onSelectIndex(index)} + title={tooltip} + secondary + isButton + > + {title} + + ); + case 'tab': + return ( + onSelectIndex(index)} + title={tooltip} + secondary + isButton + > + {title} + + ); + case 'outline': + return ( + } + isActive={index === selectedIndex} + > + onSelectIndex(index)} + > + {title} + + + ); + default: + return 'this appearance is not supported'; + } + })} + + ); +} + +ButtonToggle.defaultProps = { + appearance: 'outline', +}; diff --git a/src/images/chrome.svg b/src/images/chrome.svg new file mode 100644 index 00000000..1e06dcc5 --- /dev/null +++ b/src/images/chrome.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + diff --git a/src/images/firefox.svg b/src/images/firefox.svg new file mode 100644 index 00000000..286bc70f --- /dev/null +++ b/src/images/firefox.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/images/ie.svg b/src/images/ie.svg new file mode 100644 index 00000000..7b57663d --- /dev/null +++ b/src/images/ie.svg @@ -0,0 +1,27 @@ + + + + + + + + diff --git a/src/images/safari.svg b/src/images/safari.svg new file mode 100644 index 00000000..71f8e801 --- /dev/null +++ b/src/images/safari.svg @@ -0,0 +1 @@ + \ No newline at end of file