UNPKG

vcc-ui

Version:

VCC UI is a collection of React UI Components that can be used for developing front-end applications at Volvo Car Corporation.

298 lines (286 loc) 6.32 kB
import React from "react"; import PropTypes from "prop-types"; import { useFela } from "react-fela"; import { Click } from "../click"; import { Block } from "../block"; import { Arrow } from "../arrow"; import { Icon } from "../icon"; import { Inline } from "../inline"; import { Spinner } from "../spinner"; import { getThemeStyle } from "../../get-theme-style"; const intentStyleMap = ({ tokens }) => ({ primary: { color: tokens.buttonPrimaryColor, defaultForeground: tokens.buttonPrimaryForeground, highlight: tokens.buttonPrimaryHoverBackground, activeBackground: tokens.buttonPrimaryActiveBackground }, "primary-light": { color: tokens.buttonPrimaryLightColor, defaultForeground: tokens.buttonPrimaryLightForeground, activeBackground: tokens.buttonPrimaryLightActiveBackground }, secondary: { color: tokens.buttonSecondaryColor, defaultForeground: tokens.buttonSecondaryForeground, activeBackground: tokens.buttonSecondaryActiveBackground }, destructive: { color: tokens.buttonDestructiveColor, defaultForeground: tokens.buttonDestructiveForeground, highlight: tokens.buttonDestructiveHoverBackground, activeBackground: tokens.buttonDestructiveActiveBackground } }); const arrowTransform = { up: "translateY(-3px)", down: "translateY(3px)", right: "translateX(5px)", left: "translateX(-5px)" }; const button = ({ disabled = false, intent = "primary", variant = "default", loading, arrow, theme }) => { const { color, defaultForeground, activeBackground, highlight } = intentStyleMap(theme)[intent]; return { display: "inline-flex", flexShrink: 0, justifyContent: "center", alignItems: "baseline", padding: `${theme.tokens.buttonPaddingVertical}px ${ theme.tokens.buttonPaddingHorizontal }px`, textAlign: "center", fontSize: 15, ...(loading && { position: "relative" }), lineHeight: 1.2, transition: "all 0.3s ease-out", transitionProperty: "background, fill, stroke, color, border-color", fontWeight: 400, ":focus": { outline: `2px solid ${color}33`, ":active": { background: activeBackground } }, extend: [ { condition: !disabled, style: { extend: [ { condition: variant === "default", style: { borderWidth: 1, borderStyle: "solid", borderColor: color, background: color, color: defaultForeground, fill: defaultForeground, stroke: defaultForeground, ":hover": { background: highlight } } }, { condition: variant === "outline", style: { borderWidth: 1, borderStyle: "solid", borderColor: color, color: color, fill: color, stroke: color, ":hover": { background: color, color: defaultForeground, borderColor: highlight, fill: defaultForeground, stroke: defaultForeground } } }, { condition: variant === "text", style: { borderWidth: 1, borderStyle: "solid", borderColor: "transparent", fill: color, color: color, stroke: color, ":hover": { color: highlight } } }, { condition: arrow, style: { ":hover svg": { transform: arrowTransform[arrow] } } } ] } }, { condition: loading, style: { pointerEvents: "none" } }, { condition: disabled, style: { cursor: "not-allowed", fill: "lightgrey", color: "lightgrey", stroke: "lightgrey", ":focus": { outline: "2px solid #CCCCCC33" }, extend: [ { condition: variant === "default", style: { background: "lightgrey", color: "grey", stroke: "grey", fill: "grey" } }, { condition: variant === "outline", style: { borderWidth: 1, borderStyle: "solid", borderColor: "lightgrey" } } ] } } ] }; }; export function Button({ children, intent, variant, loading, disabled, arrow, iconBefore, iconAfter, ...props }) { const { theme } = useFela(); const styleProps = { disabled, intent, variant, loading, theme, arrow }; return ( <Click disabled={disabled || loading} {...props} extend={[button(styleProps), getThemeStyle("button", theme, styleProps)]} > {loading && ( <Block extend={{ position: "absolute", margin: "0 auto", top: 10 }} > <Spinner color="auto" size={30} /> </Block> )} {loading ? ( <Inline extend={{ visibility: "hidden" }}> <InnerButton iconBefore={iconBefore} iconAfter={iconAfter} arrow={arrow} > {children} </InnerButton> </Inline> ) : ( <InnerButton iconBefore={iconBefore} iconAfter={iconAfter} arrow={arrow} > {children} </InnerButton> )} </Click> ); } const InnerButton = ({ children, iconBefore, iconAfter, arrow }) => ( <React.Fragment> {iconBefore && ( <Inline extend={{ marginRight: 12, position: "relative", bottom: "-3px" }} > <Icon size="m" type={iconBefore} /> </Inline> )} {arrow && arrow === "left" && ( <Inline extend={{ marginRight: 8 }}> <Arrow direction={"left"} /> </Inline> )} {children} {arrow && arrow !== "left" && ( <Inline extend={{ marginLeft: 8 }}> <Arrow direction={arrow} /> </Inline> )} {iconAfter && ( <Inline extend={{ marginLeft: 12, position: "relative", bottom: "-3px" }}> <Icon size="m" type={iconAfter} /> </Inline> )} </React.Fragment> ); Button.propTypes = { /** A JSX node */ children: PropTypes.node, intent: PropTypes.oneOf([ "primary", "primary-light", "secondary", "destructive" ]), variant: PropTypes.oneOf(["default", "outline", "text"]), loading: PropTypes.bool, disabled: PropTypes.bool, arrow: PropTypes.oneOf(["up", "down", "right", "left"]), /** Add an icon before button text */ iconBefore: PropTypes.string, /** Add an icon after button text */ iconAfter: PropTypes.string, /** Changes the rendered element from `<button>` to `<a>` */ href: PropTypes.string };