orcs-design-system
Version:
TeamForm's Design System, aka: ORCS
633 lines (631 loc) • 20.7 kB
JavaScript
import React, { forwardRef } from "react";
import PropTypes from "prop-types";
import styled, { ThemeProvider, useTheme } from "styled-components";
import { default as ReactSelect, components } from "react-select";
import AsyncSelect from "react-select/async";
import CreatableSelect from "react-select/creatable";
import { space, layout, compose } from "styled-system";
import { css } from "@styled-system/css";
import shouldForwardProp from "@styled-system/should-forward-prop";
import { themeGet } from "@styled-system/theme-get";
import { Small } from "../Typography";
import useInputFocus from "../../hooks/useInputFocus";
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
const SelectStyles = compose(space, layout);
const Wrapper = styled("div").withConfig({
shouldForwardProp,
displayName: "Select__Wrapper",
componentId: "sc-bfi43f-0"
}).attrs(props => ({
"data-testid": props["data-testid"] ? props["data-testid"] : null
}))(css({
display: "inline-block",
width: "100%"
}), SelectStyles);
const Label = styled("label").withConfig({
shouldForwardProp,
displayName: "Select__Label",
componentId: "sc-bfi43f-1"
}).attrs(props => ({
for: props.id
}))(props => css({
display: "block",
color: props.inverted ? themeGet("colors.white")(props) : props.invalid ? themeGet("colors.dangerDark")(props) : themeGet("colors.greyDarkest")(props),
fontSize: themeGet("fontSizes.1")(props),
fontWeight: props.bold ? themeGet("fontWeights.2")(props) : themeGet("fontWeights.1")(props),
mb: "xs"
}));
const MultiValueRemove = props => {
const {
innerProps,
data
} = props;
return /*#__PURE__*/_jsx(components.MultiValueRemove, {
...props,
innerProps: {
...innerProps,
"aria-label": `Remove ${data.label}`
}
});
};
MultiValueRemove.propTypes = {
data: PropTypes.object,
innerProps: PropTypes.object
};
const SELECT_TYPES = {
creatable: CreatableSelect,
async: AsyncSelect,
default: ReactSelect
};
/**
*
* This component uses React Select: <https://react-select.com/home>
*
* Usage of this component has changed since we have upgraded to the latest version of `react-select`. For example, field values are now defined as separate objects. for example:
*
* const options = [
* { value: 'chocolate', label: 'Chocolate' },
* { value: 'strawberry', label: 'Strawberry' },
* { value: 'vanilla', label: 'Vanilla' },
* { value: 'hazelnut', label: 'Hazelnut' },
* { value: 'rocky road', label: 'Rocky Road' }
* ]
* For a full list of the changes, see <https://react-select.com/upgrade-guide>.
*
*/
const Select = /*#__PURE__*/forwardRef((props, ref) => {
const theme = useTheme();
const inputRef = useInputFocus(ref, props.focus);
const {
updateStyles = s => s
} = props;
const customStyles = updateStyles({
menu: (provided, state) => ({
...provided,
opacity: state.isDisabled ? 0.7 : 1,
backgroundColor: props.inverted ? themeGet("colors.greyDarker")({
theme
}) : themeGet("colors.white")({
theme
}),
color: props.inverted ? themeGet("colors.greyLighter") : themeGet("colors.greyDarkest")({
theme
}),
fontSize: themeGet("fontSizes.1")({
theme
}),
border: props.inverted ? themeGet("borderWidths.1")({
theme
}) + " solid " + themeGet("colors.primaryLight")({
theme
}) : themeGet("borderWidths.1")({
theme
}) + " solid " + themeGet("colors.primary")({
theme
}),
borderRadius: themeGet("radii.2")({
theme
}),
marginBottom: "0",
marginTop: themeGet("space.xs")({
theme
}),
overflow: "hidden",
zIndex: 12
}),
menuList: provided => ({
...provided,
paddingTop: "0",
overflow: "auto",
color: props.inverted ? themeGet("colors.greyLightest")({
theme
}) : themeGet("colors.greyDarkest")({
theme
})
}),
control: (provided, state) => ({
...provided,
minHeight: props.height ? props.height : themeGet("appScale.inputHeightDefault")({
theme
}),
boxShadow: "none",
opacity: state.isDisabled ? 0.7 : 1,
"&:hover": {
borderColor: props.invalid ? themeGet("colors.danger")({
theme
}) : themeGet("colors.primary")({
theme
})
},
"&:focus-within": {
outline: "0",
borderColor: props.invalid ? themeGet("colors.danger")({
theme
}) : themeGet("colors.primary")({
theme
}),
boxShadow: props.invalid ? themeGet("shadows.thickOutline")({
theme
}) + " " + themeGet("colors.danger30")({
theme
}) : themeGet("shadows.thickOutline")({
theme
}) + " " + themeGet("colors.primary30")({
theme
})
},
borderColor: state.isFocused ? themeGet("colors.primary")({
theme
}) : props.inverted ? themeGet("colors.white30")({
theme
}) : themeGet("colors.black30")({
theme
}),
backgroundColor: props.inverted ? themeGet("colors.greyDarker")({
theme
}) : themeGet("colors.white")({
theme
}),
color: props.inverted ? themeGet("colors.greyLighter")({
theme
}) : themeGet("colors.greyDarkest")({
theme
}),
borderRadius: themeGet("radii.2")({
theme
}),
fontSize: themeGet("fontSizes.1")({
theme
}),
overflow: "hidden",
border: `1px solid ${props.invalid ? themeGet("colors.danger")({
theme
}) : themeGet("colors.black30")({
theme
})}`
}),
container: (provided, state) => ({
...provided,
opacity: state.isDisabled ? 0.7 : 1,
color: props.inverted ? themeGet("colors.greyLighter")({
theme
}) : themeGet("colors.grey")({
theme
}),
fontSize: themeGet("fontSizes.1")({
theme
})
}),
valueContainer: provided => ({
...provided,
padding: props.padding ? props.padding : "2px 4px",
overflow: "visible"
}),
clearIndicator: (provided, state) => ({
...provided,
opacity: state.isDisabled ? 0.7 : 1,
padding: themeGet("space.xxs")({
theme
}),
color: !state.isFocused && !props.inverted ? themeGet("colors.greyDark")({
theme
}) : state.isFocused && !props.inverted ? themeGet("colors.primary")({
theme
}) : !state.isFocused && props.inverted ? themeGet("colors.white")({
theme
}) : themeGet("colors.primaryLight")({
theme
}),
"&:hover": {
color: themeGet("colors.danger")({
theme
})
}
}),
dropdownIndicator: (provided, state) => ({
...provided,
opacity: state.isDisabled ? 0.7 : 1,
padding: themeGet("space.xxs")({
theme
}),
color: !state.isFocused && !props.inverted ? themeGet("colors.greyDark")({
theme
}) : state.isFocused && !props.inverted ? themeGet("colors.primary")({
theme
}) : !state.isFocused && props.inverted ? themeGet("colors.white")({
theme
}) : themeGet("colors.primaryLight")({
theme
}),
"&:hover": {
color: !state.isFocused && !props.inverted ? themeGet("colors.primary")({
theme
}) : state.isFocused && !props.inverted ? themeGet("colors.primaryDarkest")({
theme
}) : !state.isFocused && props.inverted ? themeGet("colors.primary")({
theme
}) : themeGet("colors.white")({
theme
})
}
}),
groupHeading: provided => ({
...provided,
textTransform: "none",
fontSize: themeGet("fontSizes.0")({
theme
}),
color: themeGet("colors.greyDark")({
theme
})
}),
multiValue: (provided, state) => ({
...provided,
opacity: state.isDisabled ? 0.7 : 1,
backgroundColor: "transparent",
color: themeGet("colors.white")({
theme
}),
alignItems: "center",
padding: "0 !important",
fontSize: themeGet("fontSizes.1")({
theme
}),
wordWrap: "break-word"
}),
multiValueLabel: (provided, state) => ({
...provided,
backgroundColor: themeGet("colors.primary")({
theme
}),
color: themeGet("colors.white")({
theme
}),
padding: state.data.isFixed ? "4px 10px 5px 10px !important" : "4px 8px 5px 10px !important",
fontSize: "1.3rem",
fontWeight: themeGet("fontWeights.2")({
theme
}),
wordWrap: "break-word",
whiteSpace: "break-spaces",
borderRadius: state.data.isFixed ? "15px" : "15px 0 0 15px"
}),
multiValueRemove: (provided, state) => {
return {
...provided,
backgroundColor: themeGet("colors.primary")({
theme
}),
color: themeGet("colors.white")({
theme
}),
borderLeft: `solid 1px ${themeGet("colors.primaryDark")({
theme
})}`,
padding: "6.5px 6px 6.5px 5px",
display: state.data.isFixed ? "none" : provided.display,
cursor: "pointer",
alignSelf: "stretch",
borderRadius: "0 15px 15px 0",
transition: themeGet("transition.transitionDefault")({
theme
}),
"&:hover": {
backgroundColor: themeGet("colors.primaryDark")({
theme
}),
color: themeGet("colors.white")({
theme
})
}
};
},
option: (provided, state) => ({
...provided,
opacity: state.isDisabled ? 0.7 : 1,
backgroundColor: !state.isFocused && !props.inverted ? themeGet("colors.white")({
theme
}) : state.isFocused && !props.inverted ? themeGet("colors.primaryLightest")({
theme
}) : !state.isFocused && props.inverted ? themeGet("colors.greyDarker")({
theme
}) : themeGet("colors.primaryDark")({
theme
}),
fontSize: themeGet("fontSizes.1")({
theme
}),
whiteSpace: "break-spaces"
}),
placeholder: (provided, state) => ({
...provided,
opacity: state.isDisabled ? 0.7 : 1,
color: props.inverted ? themeGet("colors.greyLightest")({
theme
}) : themeGet("colors.greyDarkest")({
theme
}),
fontSize: themeGet("fontSizes.1")({
theme
})
})
});
const components = {
MultiValueRemove,
...(props?.components || {})
};
const SelectComponent = SELECT_TYPES[props?.selectType] ?? SELECT_TYPES.default;
const component = /*#__PURE__*/_jsxs(Wrapper, {
inverted: props.inverted,
"data-testid": props["data-testid"],
"data-select-wrapper": "true",
...SelectStyles,
children: [props.label && /*#__PURE__*/_jsxs(Label, {
inverted: props.inverted,
bold: props.bold,
htmlFor: props.inputId,
invalid: props.invalid,
children: [props.label, props.mandatory && /*#__PURE__*/_jsx(Small, {
color: "danger",
ml: "xs",
children: "*"
})]
}), /*#__PURE__*/_jsx(SelectComponent, {
ref: inputRef,
styles: customStyles,
theme: theme,
inputId: props.inputId,
inverted: props.inverted,
isMulti: props.isMulti,
classNamePrefix: props.classNamePrefix,
onChange: props.onChange,
"aria-label": props.ariaLabel,
...props,
components: components
})]
});
return props.theme ? /*#__PURE__*/_jsx(ThemeProvider, {
theme: props.theme,
children: component
}) : component;
});
Select.propTypes = {
/** Points to options object, see example code above */
options: PropTypes.oneOfType([PropTypes.array, PropTypes.object]),
/** Specifies the label for the `Select` */
label: PropTypes.string,
// ariaLabel prop must be specified if label is not provided
ariaLabel: (props, propName) => {
if (!props.label && (props[propName] == null || props[propName] === "")) {
return new Error(`Missing prop \`${propName}\` not specified for Select component. When \`label\` is not provided, \`${propName}\` is required.`);
}
if (props[propName] && typeof props[propName] !== "string") {
return new Error(`Invalid propType \`${propName}\` supplied to Select component. Expected \`string\`, received \`${typeof props[propName]}\`.`);
}
return null;
},
/** Makes label bold */
bold: PropTypes.bool,
/** Makes select disabled */
isDisabled: PropTypes.bool,
/** Specifies the ID for the rendered Select box. If you use the label prop label will automatically point to this ID, so this is required. */
inputId: (props, propName) => {
if (props.label && (props[propName] == null || props[propName] === "")) {
return new Error(`Missing prop \`${propName}\` not specified for Select component. When \`label\` is provided, \`${propName}\` is required.`);
}
if (props[propName] && typeof props[propName] !== "string") {
return new Error(`Invalid propType \`${propName}\` supplied to Select component. Expected \`string\`, received \`${typeof props[propName]}\`.`);
}
return null;
},
/** Specifies the height of the select box control, make sure to include the unit, e.g. px */
height: PropTypes.string,
/** Specifies the padding of the value showed in the select box control, make sure to include the unit, e.g. px */
padding: PropTypes.string,
/** Specifies if the `Select` component is multi-select. */
isMulti: PropTypes.bool,
/** Styling for dark backgrounds. */
inverted: PropTypes.bool,
/** Specifies the `data-testid` attribute for testing. */
dataTestId: PropTypes.string,
/** Specifies prefix for the `#class` applied to the `Select` structures */
classNamePrefix: PropTypes.string,
/** Specifies `data-testid` for testing */
"data-testid": PropTypes.string,
/** Specifies `onChange` function for the input */
onChange: PropTypes.func,
/** Specifies the system design theme object */
theme: PropTypes.object,
/** Specify if you want react-select createable option */
selectType: PropTypes.oneOf(["default", "createable", "async"]),
/** Specify if you want to overwrite existing customStyles */
updateStyles: PropTypes.func,
/** Applies invalid input styles */
invalid: PropTypes.bool,
/** Shows asterisk to denote a mandatory field */
mandatory: PropTypes.bool,
/** Focus on input */
focus: PropTypes.bool,
/** Allows overrides of react-select components */
components: PropTypes.object
};
Select.defaultProps = {
selectType: "default"
};
/** @component */
Select.__docgenInfo = {
"description": "This component uses React Select: <https://react-select.com/home>\n\nUsage of this component has changed since we have upgraded to the latest version of `react-select`. For example, field values are now defined as separate objects. for example:\n\n const options = [\n { value: 'chocolate', label: 'Chocolate' },\n { value: 'strawberry', label: 'Strawberry' },\n { value: 'vanilla', label: 'Vanilla' },\n { value: 'hazelnut', label: 'Hazelnut' },\n { value: 'rocky road', label: 'Rocky Road' }\n ]\nFor a full list of the changes, see <https://react-select.com/upgrade-guide>.",
"methods": [],
"displayName": "Select",
"props": {
"selectType": {
"defaultValue": {
"value": "\"default\"",
"computed": false
},
"description": "Specify if you want react-select createable option",
"type": {
"name": "enum",
"value": [{
"value": "\"default\"",
"computed": false
}, {
"value": "\"createable\"",
"computed": false
}, {
"value": "\"async\"",
"computed": false
}]
},
"required": false
},
"options": {
"description": "Points to options object, see example code above",
"type": {
"name": "union",
"value": [{
"name": "array"
}, {
"name": "object"
}]
},
"required": false
},
"label": {
"description": "Specifies the label for the `Select`",
"type": {
"name": "string"
},
"required": false
},
"ariaLabel": {
"description": "",
"type": {
"name": "custom",
"raw": "(props, propName) => {\n if (!props.label && (props[propName] == null || props[propName] === \"\")) {\n return new Error(\n `Missing prop \\`${propName}\\` not specified for Select component. When \\`label\\` is not provided, \\`${propName}\\` is required.`\n );\n }\n if (props[propName] && typeof props[propName] !== \"string\") {\n return new Error(\n `Invalid propType \\`${propName}\\` supplied to Select component. Expected \\`string\\`, received \\`${typeof props[\n propName\n ]}\\`.`\n );\n }\n return null;\n}"
},
"required": false
},
"bold": {
"description": "Makes label bold",
"type": {
"name": "bool"
},
"required": false
},
"isDisabled": {
"description": "Makes select disabled",
"type": {
"name": "bool"
},
"required": false
},
"inputId": {
"description": "Specifies the ID for the rendered Select box. If you use the label prop label will automatically point to this ID, so this is required.",
"type": {
"name": "custom",
"raw": "(props, propName) => {\n if (props.label && (props[propName] == null || props[propName] === \"\")) {\n return new Error(\n `Missing prop \\`${propName}\\` not specified for Select component. When \\`label\\` is provided, \\`${propName}\\` is required.`\n );\n }\n if (props[propName] && typeof props[propName] !== \"string\") {\n return new Error(\n `Invalid propType \\`${propName}\\` supplied to Select component. Expected \\`string\\`, received \\`${typeof props[\n propName\n ]}\\`.`\n );\n }\n return null;\n}"
},
"required": false
},
"height": {
"description": "Specifies the height of the select box control, make sure to include the unit, e.g. px",
"type": {
"name": "string"
},
"required": false
},
"padding": {
"description": "Specifies the padding of the value showed in the select box control, make sure to include the unit, e.g. px",
"type": {
"name": "string"
},
"required": false
},
"isMulti": {
"description": "Specifies if the `Select` component is multi-select.",
"type": {
"name": "bool"
},
"required": false
},
"inverted": {
"description": "Styling for dark backgrounds.",
"type": {
"name": "bool"
},
"required": false
},
"dataTestId": {
"description": "Specifies the `data-testid` attribute for testing.",
"type": {
"name": "string"
},
"required": false
},
"classNamePrefix": {
"description": "Specifies prefix for the `#class` applied to the `Select` structures",
"type": {
"name": "string"
},
"required": false
},
"data-testid": {
"description": "Specifies `data-testid` for testing",
"type": {
"name": "string"
},
"required": false
},
"onChange": {
"description": "Specifies `onChange` function for the input",
"type": {
"name": "func"
},
"required": false
},
"theme": {
"description": "Specifies the system design theme object",
"type": {
"name": "object"
},
"required": false
},
"updateStyles": {
"description": "Specify if you want to overwrite existing customStyles",
"type": {
"name": "func"
},
"required": false
},
"invalid": {
"description": "Applies invalid input styles",
"type": {
"name": "bool"
},
"required": false
},
"mandatory": {
"description": "Shows asterisk to denote a mandatory field",
"type": {
"name": "bool"
},
"required": false
},
"focus": {
"description": "Focus on input",
"type": {
"name": "bool"
},
"required": false
},
"components": {
"description": "Allows overrides of react-select components",
"type": {
"name": "object"
},
"required": false
}
}
};
export default Select;