UNPKG

orcs-design-system

Version:
565 lines 19.6 kB
import React from "react"; import styled, { ThemeProvider, css } from "styled-components"; import PropTypes from "prop-types"; import { space, layout, color, border, compose } from "styled-system"; import shouldForwardProp from "@styled-system/should-forward-prop"; import Icon from "../Icon"; import Loading from "../Loading"; import { themeGet } from "@styled-system/theme-get"; import { Link } from "react-router-dom"; import { omit } from "lodash"; import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; export const VARIANT_COLORS = { default: { background: "colors.primary", color: "colors.white", borderColor: "colors.primary", hovered: { background: "colors.primaryDark", color: "colors.white", borderColor: "colors.primaryDark" } }, success: { background: "colors.success", color: "colors.white", borderColor: "colors.success", hovered: { background: "colors.successDark", color: "colors.white", borderColor: "colors.successDark" }, focused: { outline: "colors.successDarker" } }, successAlternate: { background: "colors.greyLightest", color: "colors.success", borderColor: "colors.greyLightest", hovered: { background: "colors.greyLighter", color: "colors.success", borderColor: "colors.greyLighter" }, focused: { outline: "colors.successLight" } }, danger: { background: "colors.danger", color: "colors.white", borderColor: "colors.danger", hovered: { background: "colors.dangerDark", color: "colors.white", borderColor: "colors.dangerDark" }, focused: { outline: "colors.dangerDarker" } }, dangerAlternate: { background: "colors.greyLightest", color: "colors.danger", borderColor: "colors.greyLightest", hovered: { background: "colors.greyLighter", color: "colors.danger", borderColor: "colors.greyLighter" }, focused: { outline: "colors.dangerLight" } }, disabled: { background: "colors.greyLighter", color: "colors.grey", borderColor: "colors.greyLighter", hovered: { background: "colors.greyLighter", color: "colors.grey", borderColor: "colors.greyLighter" } }, ghost: { background: "colors.primaryLightest", color: "colors.primaryDark", borderColor: "colors.primaryLightest", hovered: { background: "colors.primaryLighter", color: "colors.primaryDarker", borderColor: "colors.primaryLighter" }, focused: { outline: "colors.primaryLight" } } }; const getOutlineStyle = color => css(["outline:0;box-shadow:", ";"], props => [themeGet("shadows.thinOutline")(props), themeGet(color)(props)].join(" ")); const addVariantColors = key => { const variantStyle = VARIANT_COLORS[key]; return css(["background:", ";color:", ";border-color:", ";&:hover{background:", ";color:", ";border-color:", ";}", ""], themeGet(variantStyle.background), themeGet(variantStyle.color), themeGet(variantStyle.borderColor), themeGet(variantStyle.hovered.background), themeGet(variantStyle.hovered.color), themeGet(variantStyle.hovered.borderColor), variantStyle.focused && css(["&:focus{", "}"], getOutlineStyle(variantStyle.focused.outline))); }; const getVariantStyle = props => { const styles = Object.keys(VARIANT_COLORS).reduce((obj, key) => { obj[key] = css(["", ""], addVariantColors(key)); return obj; }, {}); if (props.disabled) return styles.disabled; return styles[props.variant] || styles.default; }; const getSpace = getter => props => themeGet(`space.${getter(props)}`)(props); const buttonStyles = css(["background:", ";color:", ";border-color:", ";display:flex;align-items:center;justify-content:center;appearance:none;box-shadow:none;margin:0;text-decoration:none;text-align:center;font-family:", ";font-weight:", ";border-radius:", ";transition:", ";border-width:", ";cursor:", ";width:", ";height:auto;font-size:", ";padding:", " ", ";svg{margin-right:", ";margin-left:", ";}&:hover{background:", ";border-color:", ";border-width:", ";border-style:solid;}&:focus{outline:0;box-shadow:", " ", ";}", " ", ""], themeGet(VARIANT_COLORS.default.background), themeGet(VARIANT_COLORS.default.color), themeGet("colors.primary"), themeGet("fonts.main"), themeGet("fontWeights.2"), themeGet("radii.2"), themeGet("transition.transitionDefault"), themeGet("borderWidths.1"), props => props.disabled ? "not-allowed" : props.isLoading ? "progress" : "pointer", props => props.fullWidth ? "100%" : "auto", props => { let fontSize = 2; if (props.large && props.iconOnly) fontSize = 5; if (props.large) fontSize = 3; if (props.small) fontSize = 1; return themeGet(`fontSizes.${fontSize}`)(props); }, getSpace(props => props.large ? "s" : props.small ? "xxs" : "xs"), getSpace(props => props.large ? "r" : props.small ? "s" : "between"), getSpace(props => !props.iconLeft ? "" : props.small ? "xs" : "s"), getSpace(props => !props.iconRight ? "" : props.small ? "xs" : "s"), themeGet(VARIANT_COLORS.default.hovered.background), themeGet("colors.primaryDark"), themeGet("borderWidths.1"), themeGet("shadows.thinOutline"), themeGet("colors.primaryDarker"), getVariantStyle, compose(space, layout, color, border)); const attrs = props => ({ "data-testid": props.dataTestId || props["data-testid"], disabled: props.disabled || props.variant == "disabled", className: `${props.className || ""} variant-${props.variant || "default"}` }); const StyledButton = styled("button").withConfig({ shouldForwardProp }).attrs(attrs).withConfig({ displayName: "Button__StyledButton", componentId: "sc-10uojnk-0" })(["", ""], buttonStyles); const linkStyles = css(["width:", ";display:", ";align-items:", ";"], props => props.width || "fit-content", props => props.height || props.width ? "flex" : props.display || "inline-block", props => props.alignItems || "center"); const StyledButtonLink = styled.a.withConfig({ shouldForwardProp }).attrs(attrs).withConfig({ displayName: "Button__StyledButtonLink", componentId: "sc-10uojnk-1" })(["", " ", ""], buttonStyles, linkStyles); const StyledReactButtonLink = styled(Link).withConfig({ shouldForwardProp }).attrs(attrs).withConfig({ displayName: "Button__StyledReactButtonLink", componentId: "sc-10uojnk-2" })(["", " ", ""], buttonStyles, linkStyles); const buttonPropTypes = { /** Large button */ large: PropTypes.bool, /** Small button */ small: PropTypes.bool, /** Specifies alternate button colours/styles. */ variant: PropTypes.oneOf(["success", "successAlternate", "danger", "dangerAlternate", "ghost", "disabled", "default"]), /** Full width button that takes up all available space of parent */ fullWidth: PropTypes.bool, /** Adds a spinner animation to indicate loading or processing */ isLoading: PropTypes.bool, /** Styles button to fit an icon on the left of text. Uses Icon component. */ iconLeft: PropTypes.bool, /** Styles button to fit an icon on the right of text. Uses Icon component. */ iconRight: PropTypes.bool, /** New functionality to specify an `Icon` on the left side without having to include it as a child. */ leftIcon: PropTypes.array, /** New functionality to specify an `Icon` on the right side without having to include it as a child. */ rightIcon: PropTypes.array, /** Styles button to suit having only an icon. Uses Icon component. */ iconOnly: PropTypes.bool, /** Specifies whether the button is disabled. */ disabled: PropTypes.bool, /** The text label on the button is passed as a child. Keep this text short and descriptive. Use an action word or confirmation if possible. */ children: PropTypes.node, /** Adds additional styling to the rendered `<button>` using `space`, `layout`, `color` and `border` prop categories */ ButtonStyles: PropTypes.object, /** Specifies the `data-testid` attribute for testing. */ dataTestId: PropTypes.string, /** Specifies aria-label for iconOnly buttons. This is only required if the iconOnly button is used, as it doesn't have supporting text for accessibility.*/ ariaLabel: (props, propName) => { if (props.iconOnly && (props[propName] == null || props[propName] === "")) { return new Error(`Missing prop \`${propName}\` not specified for Button component. When \`iconOnly\` is true, \`${propName}\` is required.`); } if (props[propName] && typeof props[propName] !== "string") { return new Error(`Invalid propType for \`${propName}\` supplied to Button component. Expected \`string\`, received \`${typeof props[propName]}\`.`); } return null; }, /** Specifies the color theme object. */ theme: PropTypes.object }; const renderButton = (ButtonComponent, _ref) => { let { ariaLabel, ...props } = _ref; return /*#__PURE__*/_jsxs(ButtonComponent, { borderStyle: "solid", ...props.ButtonStyles, ...props, "aria-label": ariaLabel, ref: props.ref, children: [props.leftIcon && /*#__PURE__*/_jsx(Icon, { icon: props.leftIcon, mr: props.small ? "xxs" : "xs" }), props.children, props.rightIcon && /*#__PURE__*/_jsx(Icon, { icon: props.rightIcon, ml: props.small ? "xxs" : "xs" }), props.isLoading && /*#__PURE__*/_jsx(Loading, { inverted: true, ml: "s" })] }); }; export const ButtonLink = /*#__PURE__*/React.forwardRef((props, ref) => { const { theme } = props; const component = renderButton(props.to ? StyledReactButtonLink : StyledButtonLink, { ...omit(props, "isLoading"), ref }); return theme ? /*#__PURE__*/_jsx(ThemeProvider, { theme: theme, children: component }) : component; }); ButtonLink.propTypes = { ...buttonPropTypes, target: PropTypes.string, /** Link to navigate user to */ href: PropTypes.string }; ButtonLink.defaultProps = { variant: "default" }; export const Button = /*#__PURE__*/React.forwardRef((props, ref) => { const { theme } = props; const component = renderButton(StyledButton, { ...props, ref }); return theme ? /*#__PURE__*/_jsx(ThemeProvider, { theme: theme, children: component }) : component; }); Button.__docgenInfo = { "description": "", "methods": [], "displayName": "Button", "props": { "variant": { "defaultValue": { "value": "\"default\"", "computed": false }, "description": "Specifies alternate button colours/styles.", "type": { "name": "enum", "value": [{ "value": "\"success\"", "computed": false }, { "value": "\"successAlternate\"", "computed": false }, { "value": "\"danger\"", "computed": false }, { "value": "\"dangerAlternate\"", "computed": false }, { "value": "\"ghost\"", "computed": false }, { "value": "\"disabled\"", "computed": false }, { "value": "\"default\"", "computed": false }] }, "required": false }, "large": { "description": "Large button", "type": { "name": "bool" }, "required": false }, "small": { "description": "Small button", "type": { "name": "bool" }, "required": false }, "fullWidth": { "description": "Full width button that takes up all available space of parent", "type": { "name": "bool" }, "required": false }, "isLoading": { "description": "Adds a spinner animation to indicate loading or processing", "type": { "name": "bool" }, "required": false }, "iconLeft": { "description": "Styles button to fit an icon on the left of text. Uses Icon component.", "type": { "name": "bool" }, "required": false }, "iconRight": { "description": "Styles button to fit an icon on the right of text. Uses Icon component.", "type": { "name": "bool" }, "required": false }, "leftIcon": { "description": "New functionality to specify an `Icon` on the left side without having to include it as a child.", "type": { "name": "array" }, "required": false }, "rightIcon": { "description": "New functionality to specify an `Icon` on the right side without having to include it as a child.", "type": { "name": "array" }, "required": false }, "iconOnly": { "description": "Styles button to suit having only an icon. Uses Icon component.", "type": { "name": "bool" }, "required": false }, "disabled": { "description": "Specifies whether the button is disabled.", "type": { "name": "bool" }, "required": false }, "children": { "description": "The text label on the button is passed as a child. Keep this text short and descriptive. Use an action word or confirmation if possible.", "type": { "name": "node" }, "required": false }, "ButtonStyles": { "description": "Adds additional styling to the rendered `<button>` using `space`, `layout`, `color` and `border` prop categories", "type": { "name": "object" }, "required": false }, "dataTestId": { "description": "Specifies the `data-testid` attribute for testing.", "type": { "name": "string" }, "required": false }, "ariaLabel": { "description": "Specifies aria-label for iconOnly buttons. This is only required if the iconOnly button is used, as it doesn't have supporting text for accessibility.", "type": { "name": "custom", "raw": "(props, propName) => {\n if (props.iconOnly && (props[propName] == null || props[propName] === \"\")) {\n return new Error(\n `Missing prop \\`${propName}\\` not specified for Button component. When \\`iconOnly\\` is true, \\`${propName}\\` is required.`\n );\n }\n if (props[propName] && typeof props[propName] !== \"string\") {\n return new Error(\n `Invalid propType for \\`${propName}\\` supplied to Button component. Expected \\`string\\`, received \\`${typeof props[\n propName\n ]}\\`.`\n );\n }\n return null;\n}" }, "required": false }, "theme": { "description": "Specifies the color theme object.", "type": { "name": "object" }, "required": false }, "onClick": { "description": "Function to run when the `Button` is clicked", "type": { "name": "func" }, "required": false } } }; export default Button; Button.propTypes = { ...buttonPropTypes, /** Function to run when the `Button` is clicked */ onClick: PropTypes.func }; Button.defaultProps = { variant: "default" }; ButtonLink.__docgenInfo = { "description": "", "methods": [], "displayName": "ButtonLink", "props": { "variant": { "defaultValue": { "value": "\"default\"", "computed": false }, "description": "Specifies alternate button colours/styles.", "type": { "name": "enum", "value": [{ "value": "\"success\"", "computed": false }, { "value": "\"successAlternate\"", "computed": false }, { "value": "\"danger\"", "computed": false }, { "value": "\"dangerAlternate\"", "computed": false }, { "value": "\"ghost\"", "computed": false }, { "value": "\"disabled\"", "computed": false }, { "value": "\"default\"", "computed": false }] }, "required": false }, "large": { "description": "Large button", "type": { "name": "bool" }, "required": false }, "small": { "description": "Small button", "type": { "name": "bool" }, "required": false }, "fullWidth": { "description": "Full width button that takes up all available space of parent", "type": { "name": "bool" }, "required": false }, "isLoading": { "description": "Adds a spinner animation to indicate loading or processing", "type": { "name": "bool" }, "required": false }, "iconLeft": { "description": "Styles button to fit an icon on the left of text. Uses Icon component.", "type": { "name": "bool" }, "required": false }, "iconRight": { "description": "Styles button to fit an icon on the right of text. Uses Icon component.", "type": { "name": "bool" }, "required": false }, "leftIcon": { "description": "New functionality to specify an `Icon` on the left side without having to include it as a child.", "type": { "name": "array" }, "required": false }, "rightIcon": { "description": "New functionality to specify an `Icon` on the right side without having to include it as a child.", "type": { "name": "array" }, "required": false }, "iconOnly": { "description": "Styles button to suit having only an icon. Uses Icon component.", "type": { "name": "bool" }, "required": false }, "disabled": { "description": "Specifies whether the button is disabled.", "type": { "name": "bool" }, "required": false }, "children": { "description": "The text label on the button is passed as a child. Keep this text short and descriptive. Use an action word or confirmation if possible.", "type": { "name": "node" }, "required": false }, "ButtonStyles": { "description": "Adds additional styling to the rendered `<button>` using `space`, `layout`, `color` and `border` prop categories", "type": { "name": "object" }, "required": false }, "dataTestId": { "description": "Specifies the `data-testid` attribute for testing.", "type": { "name": "string" }, "required": false }, "ariaLabel": { "description": "Specifies aria-label for iconOnly buttons. This is only required if the iconOnly button is used, as it doesn't have supporting text for accessibility.", "type": { "name": "custom", "raw": "(props, propName) => {\n if (props.iconOnly && (props[propName] == null || props[propName] === \"\")) {\n return new Error(\n `Missing prop \\`${propName}\\` not specified for Button component. When \\`iconOnly\\` is true, \\`${propName}\\` is required.`\n );\n }\n if (props[propName] && typeof props[propName] !== \"string\") {\n return new Error(\n `Invalid propType for \\`${propName}\\` supplied to Button component. Expected \\`string\\`, received \\`${typeof props[\n propName\n ]}\\`.`\n );\n }\n return null;\n}" }, "required": false }, "theme": { "description": "Specifies the color theme object.", "type": { "name": "object" }, "required": false }, "target": { "description": "", "type": { "name": "string" }, "required": false }, "href": { "description": "Link to navigate user to", "type": { "name": "string" }, "required": false } } };