Instantly share code, notes, and snippets.
Created
June 24, 2020 14:39
-
Star
0
(0)
You must be signed in to star a gist -
Fork
0
(0)
You must be signed in to fork a gist
-
Save chrisoverstreet/65774ab578455f5fb526663282c4a4c3 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// @flow | |
import classnames from 'classnames'; | |
import css from 'styled-jsx/css'; | |
import PropTypes from 'prop-types'; | |
import React from 'react'; | |
import type { Node } from 'react'; | |
import Icon from './Icon'; | |
import Text from './Text'; | |
import { COLOR, SPACING, FONT_SIZE, LINE_HEIGHT, TRANSITION } from './theme'; | |
import type { IconKey } from './Icon'; | |
import type { TextColorName } from './Text'; | |
export type Size = 'large' | 'small'; | |
export type Variant = | |
| 'primary' | |
| 'secondary' | |
| 'tertiary' | |
| 'positive' | |
| 'negative' | |
| 'ghost'; | |
const fontColor: { [TextColorName]: string } = { | |
default: COLOR.black, | |
gray: COLOR.darkGray, | |
selected: COLOR.blue, | |
success: COLOR.green, | |
error: COLOR.red, | |
link: COLOR.blue, | |
}; | |
const fontColorOnDark: { [TextColorName]: string } = { | |
default: COLOR.white, | |
gray: COLOR.softGray, | |
selected: COLOR.blue, | |
success: COLOR.green, | |
error: COLOR.red, | |
link: COLOR.blue, | |
}; | |
const { className: glyphClassName, styles: glyphStyles } = css.resolve` | |
i { | |
font-size: ${FONT_SIZE.s16}; | |
line-height: ${LINE_HEIGHT.s16}; | |
//margin: 0 ${SPACING.xs}px 0 0 !important; | |
} | |
`; | |
// All of these props are marked as optional because we can't rely on Flow to | |
// recognize defaultProps for this component (React.forwardRef confuses Flow). | |
export type ButtonProps = {| | |
children?: Node, | |
className?: string, | |
glyph?: IconKey | Node, | |
href: string | null, | |
id?: string, | |
size?: Size, | |
style?: { [string]: any }, | |
variant?: Variant, | |
|}; | |
const LinkButton = React.forwardRef<ButtonProps, HTMLButtonElement>( | |
( | |
{ | |
children, | |
className, | |
glyph, | |
href, | |
id, | |
size = 'large', | |
style, | |
variant = 'primary', | |
}, | |
ref, | |
) => { | |
const color: TextColorName = | |
{ | |
primary: 'default', | |
secondary: 'selected', | |
tertiary: 'gray', | |
positive: 'success', | |
negative: 'error', | |
ghost: 'selected', | |
}[variant] || 'default'; | |
const opacity = 1; | |
const iconColor = | |
variant === 'primary' ? fontColorOnDark[color] : fontColor[color]; | |
return ( | |
<a | |
aria-busy={undefined} | |
aria-live="polite" | |
className={classnames('root', className, size, variant)} | |
href={href} | |
id={id} | |
ref={ref} | |
style={style} | |
> | |
{typeof glyph === 'string' && ( | |
<Icon | |
className={glyphClassName} | |
icon={glyph} | |
style={{ | |
color: iconColor, | |
}} | |
/> | |
)} | |
{glyph && typeof glyph !== 'string' && ( | |
<i | |
className={glyphClassName} | |
style={{ | |
color: iconColor, | |
}} | |
> | |
{glyph} | |
</i> | |
)} | |
<Text | |
color={color} | |
element="span" | |
onDark={variant === 'primary'} | |
size="large" | |
weight="semi" | |
> | |
{children} | |
</Text> | |
<style jsx> | |
{` | |
/* Root */ | |
.root { | |
cursor: pointer; | |
width: fit-content; | |
align-items: center; | |
border-color: ${COLOR.darkGray}; | |
border-style: solid; | |
border-width: 1px; | |
border-radius: 2px; | |
display: flex; | |
justify-content: center; | |
overflow: hidden; | |
padding: 0 ${SPACING.sm}px; | |
position: relative; | |
text-transform: capitalize; | |
transition: background-color ${TRANSITION.duration} | |
${TRANSITION.timingFunction}, | |
border-color ${TRANSITION.duration} ${TRANSITION.timingFunction}; | |
} | |
.root:hover, | |
.root:focus { | |
background-color: ${COLOR.softGray}; | |
border-color: ${COLOR.black}; | |
} | |
.root :global(i), | |
.root :global(span) { | |
opacity: ${opacity}; | |
transition: color ${TRANSITION.duration} | |
${TRANSITION.timingFunction}; | |
} | |
.large { | |
min-height: 56px; | |
padding-bottom: ${SPACING.sm}px; | |
padding-top: ${SPACING.sm}px; | |
} | |
.small { | |
min-height: 40px; | |
padding-bottom: ${SPACING.xs}px; | |
padding-top: ${SPACING.xs}px; | |
} | |
/* Icon */ | |
.root :global(i + span) { | |
padding-left: 12px; | |
} | |
/* Loading */ | |
.loading-overlay { | |
align-items: center; | |
bottom: 0; | |
display: flex; | |
height: 100%; | |
justify-content: center; | |
left: 0; | |
position: absolute; | |
width: 100%; | |
} | |
.root.loading { | |
color: ${COLOR.darkGray}; | |
} | |
.root.loading :global(i), | |
.root.loading :global(span) { | |
opacity: 0; | |
} | |
/* Primary */ | |
.root.primary { | |
background-color: ${COLOR.blue}; | |
border-color: ${COLOR.blue}; | |
} | |
.root.primary:hover, | |
.root.primary:focus { | |
background-color: ${COLOR.blueHover}; | |
border-color: ${COLOR.blueHover}; | |
} | |
.primary:not(.loading):disabled { | |
background-color: ${COLOR.lightGray}; | |
border-color: ${COLOR.lightGray}; | |
} | |
.primary:not(.loading):disabled :global(i), | |
.primary:not(.loading):disabled :global(span) { | |
color: ${COLOR.darkGray} !important; | |
} | |
.primary.loading { | |
color: ${COLOR.white}; | |
} | |
/* Ghost Styles */ | |
.ghost, | |
.ghost:hover, | |
.ghost.loading, | |
.ghost:disabled { | |
background-color: transparent; | |
border-color: transparent; | |
} | |
.ghost:hover :global(i), | |
.ghost:hover :global(span) { | |
color: ${COLOR.blueHover} !important; | |
} | |
.ghost:hover :global(span) { | |
text-decoration: underline !important; | |
} | |
/* Other Variants */ | |
.secondary, | |
.tertiary, | |
.positive, | |
.negative { | |
background-color: ${COLOR.white}; | |
} | |
.secondary:hover :global(i), | |
.secondary:hover :global(span) { | |
color: ${COLOR.blueHover} !important; | |
} | |
.tertiary:hover :global(i), | |
.tertiary:hover :global(span) { | |
color: ${COLOR.black} !important; | |
} | |
.positive:hover :global(i), | |
.positive:hover :global(span) { | |
color: ${COLOR.greenHover} !important; | |
} | |
.negative:hover :global(i), | |
.negative:hover :global(span) { | |
color: ${COLOR.redHover} !important; | |
} | |
`} | |
</style> | |
{glyphStyles} | |
</a> | |
); | |
}, | |
); | |
// $FlowFixMe | |
LinkButton.propTypes = { | |
children: PropTypes.node, | |
className: PropTypes.string, | |
glyph: PropTypes.oneOfType([PropTypes.string, PropTypes.node]), | |
href: PropTypes.string, | |
id: PropTypes.string, | |
size: PropTypes.oneOf(['large', 'small']), | |
style: PropTypes.object, | |
variant: PropTypes.oneOf([ | |
'primary', | |
'secondary', | |
'tertiary', | |
'positive', | |
'negative', | |
'ghost', | |
]), | |
}; | |
// $FlowFixMe | |
LinkButton.defaultProps = { | |
children: null, | |
className: null, | |
glyph: null, | |
href: null, | |
id: null, | |
size: 'large', | |
style: null, | |
variant: 'primary', | |
}; | |
export default LinkButton; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment