orcs-design-system
Version:
TeamForm's Design System, aka: ORCS
565 lines • 19.6 kB
JavaScript
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
}
}
};