Skip to content

Commit

Permalink
Merge pull request #59 from reactioncommerce/willopez-add-multi-select
Browse files Browse the repository at this point in the history
feat: add Select component
  • Loading branch information
willopez authored Aug 19, 2019
2 parents 6dfed35 + b5598b9 commit 07b8ef0
Show file tree
Hide file tree
Showing 23 changed files with 926 additions and 89 deletions.
72 changes: 0 additions & 72 deletions .snyk
Original file line number Diff line number Diff line change
Expand Up @@ -2,78 +2,6 @@
version: v1.12.0
# ignores vulnerabilities until expiry date; change duration by modifying expiry date
ignore:
'npm:hoek:20180212':
- boom > hoek:
reason: No path to fix
expires: '2018-08-31T14:45:41.655Z'
- cryptiles > boom > hoek:
reason: No path to fix
expires: '2018-08-31T14:45:41.655Z'
- hawk > hoek:
reason: No path to fix
expires: '2018-08-31T14:45:41.655Z'
- hawk > boom > hoek:
reason: No path to fix
expires: '2018-08-31T14:45:41.655Z'
- hawk > sntp > hoek:
reason: No path to fix
expires: '2018-08-31T14:45:41.655Z'
- hawk > cryptiles > boom > hoek:
reason: No path to fix
expires: '2018-08-31T14:45:41.655Z'
- node-pre-gyp > hawk > hoek:
reason: No path to fix
expires: '2018-08-31T14:45:41.656Z'
- node-pre-gyp > hawk > boom > hoek:
reason: No path to fix
expires: '2018-08-31T14:45:41.656Z'
- node-pre-gyp > hawk > sntp > hoek:
reason: No path to fix
expires: '2018-08-31T14:45:41.656Z'
- node-pre-gyp > hawk > cryptiles > boom > hoek:
reason: No path to fix
expires: '2018-08-31T14:45:41.656Z'
- node-pre-gyp > request > hawk > hoek:
reason: No path to fix
expires: '2018-08-31T14:45:41.656Z'
- node-pre-gyp > request > hawk > boom > hoek:
reason: No path to fix
expires: '2018-08-31T14:45:41.656Z'
- node-pre-gyp > request > hawk > sntp > hoek:
reason: No path to fix
expires: '2018-08-31T14:45:41.656Z'
- node-pre-gyp > request > hawk > cryptiles > boom > hoek:
reason: No path to fix
expires: '2018-08-31T14:45:41.656Z'
'npm:stringstream:20180511':
- node-pre-gyp > request > stringstream:
reason: No way to fix
expires: '2018-08-31T14:45:41.656Z'
'npm:cryptiles:20180710':
- cryptiles:
reason: No way to fix
expires: '2018-08-31T14:45:41.656Z'
- hawk > cryptiles:
reason: No way to fix
expires: '2018-08-31T14:45:41.656Z'
- node-pre-gyp > hawk > cryptiles:
reason: No way to fix
expires: '2018-08-31T14:45:41.656Z'
- node-pre-gyp > request > hawk > cryptiles:
reason: No way to fix
expires: '2018-08-31T14:45:41.656Z'
'npm:deep-extend:20180409':
- node-pre-gyp > rc > deep-extend:
reason: No way to fix
expires: '2018-08-31T14:45:41.656Z'
'npm:extend:20180424':
- node-pre-gyp > request > extend:
reason: No way to fix
expires: '2018-08-31T14:45:41.656Z'
'npm:sshpk:20180409':
- node-pre-gyp > request > http-signature > sshpk:
reason: No way to fix
expires: '2018-08-31T14:45:41.656Z'
'npm:mem:20180117':
- libnpx > yargs > os-locale > mem:
reason: No update available
Expand Down
4 changes: 3 additions & 1 deletion package/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,15 @@
"dependencies": {
"@babel/runtime": "~7.3.1",
"accounting-js": "~1.1.1",
"clsx": "^1.0.4",
"lodash.debounce": "~4.0.8",
"lodash.get": "~4.4.2",
"lodash.isempty": "~4.4.0",
"lodash.isequal": "~4.5.0",
"lodash.uniqueid": "~4.0.1",
"mdi-material-ui": "~5.8.0",
"react-is": "~16.4.1"
"react-is": "~16.4.1",
"react-select": "^3.0.4"
},
"devDependencies": {
"@babel/cli": "~7.2.3",
Expand Down
8 changes: 7 additions & 1 deletion package/src/components/Chip/Chip.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ const Chip = React.forwardRef(function Chip(props, ref) {
<MuiChip
classes={{
containedPrimary: classes.containedPrimary,
outlinedPrimary: classes.outlinedPrimary
outlinedPrimary: classes.outlinedPrimary,
root: classes.root,
sizeSmall: classes.sizeSmall
}}
color="primary"
ref={ref}
Expand All @@ -42,6 +44,10 @@ const Chip = React.forwardRef(function Chip(props, ref) {

return (
<MuiChip
classes={{
root: classes.root,
sizeSmall: classes.sizeSmall
}}
color={color}
ref={ref}
{...otherProps}
Expand Down
1 change: 0 additions & 1 deletion package/src/components/ConfirmDialog/ConfirmDialog.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import React from "react";
import { render, fireEvent } from "../../tests/";
import "@testing-library/jest-dom/extend-expect";
import Button from "../Button";
import ConfirmDialog from "./ConfirmDialog";

Expand Down
178 changes: 178 additions & 0 deletions package/src/components/Select/Select.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
import React, { useState } from "react";
import PropTypes from "prop-types";
import ReactSelect from "react-select";
import AsyncSelect from "react-select/async";
import { makeStyles, useTheme } from "@material-ui/core/styles";
import {
Control,
Menu,
MultiValue,
NoOptionsMessage,
Option,
Placeholder,
ValueContainer
} from "./helpers";

const useStyles = makeStyles((theme) => ({
root: {
flexGrow: 1,
minWidth: 290
},
input: {
display: "flex",
padding: theme.spacing(0.25),
height: "auto",
cursor: "pointer",
fontSize: theme.typography.fontSize,
background: theme.palette.colors.black02,
border: `1px solid ${theme.palette.colors.black20}`,
borderRadius: theme.shape.borderRadius
},
valueContainer: {
display: "flex",
alignItems: "center",
flexWrap: "wrap",
flex: 1,
overflow: "hidden",
paddingLeft: theme.spacing(0.5)
},
chip: {
margin: theme.spacing(0.5, 0.25)
},
noOptionsMessage: {
padding: theme.spacing(1, 2),
color: theme.palette.colors.black20
},
placeholder: {
position: "absolute",
left: theme.spacing(1),
fontSize: theme.typography.fontSize,
color: theme.palette.colors.black20
},
paper: {
position: "absolute",
zIndex: 1,
marginTop: theme.spacing(1),
left: 0,
right: 0
},
divider: {
height: theme.spacing(2)
}
}));

// Custom components for various aspects of the select
const components = {
Control,
Menu,
MultiValue,
NoOptionsMessage,
Option,
Placeholder,
ValueContainer
};

/**
* @name Select
* @summary A Select component that supports selecting single or multiple option(s), and
* loading options synchronously or asynchronously.
* @param {Object} props - component props
* @returns {React.Component} A React component
*/
const Select = React.forwardRef(function Select(props, ref) {
const defaultClasses = useStyles();
const theme = useTheme();
const [value, setValue] = useState(null);
const { classes, isAsync, onSelection, ...otherProps } = props;
const SelectComponent = isAsync ? AsyncSelect : ReactSelect;

/**
*
* @param {String} selectedValue The selected value
* @returns {undefined} nothing
*/
function handleChangeMulti(selectedValue) {
setValue(selectedValue);
onSelection && onSelection(selectedValue);
}

const selectStyles = {
input: (base) => ({
...base,
"color": theme.palette.text.primary,
"& input": {
font: "inherit"
}
})
};

return (
<div className={defaultClasses.root}>
<SelectComponent
classes={{ ...defaultClasses, ...classes }}
components={components}
inputId="react-select-multiple"
onChange={handleChangeMulti}
ref={ref}
styles={selectStyles}
innerRef={ref}
TextFieldProps={{
InputLabelProps: {
htmlFor: "react-select-multiple",
shrink: true
}
}}
value={value}
{...otherProps}
/>
</div>
);
});

Select.defaultProps = {
placeholder: "Select options"
};

Select.propTypes = {
/**
* When provided options will be cached
*/
cacheOptions: PropTypes.bool, // eslint-disable-line react/boolean-prop-naming
/**
* Additional classes to customize the Select component
*/
classes: PropTypes.string,
/**
* The defaultOptions prop determines "when" your remote request is initially fired.
* There are two valid values for this property.
* Providing an option array to this prop will populate the initial set of options
* used when opening the select, at which point the remote load only occurs
* when filtering the options (typing in the control).
* Providing the prop by itself (or with 'true') tells the control to immediately
* fire the remote request, described by your loadOptions,
* to get those initial values for the Select.
*/
defaultOptions: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.object), PropTypes.bool]),
/**
* Set to true if options will be fetched asynchronously.
*/
isAsync: PropTypes.bool,
/**
* A function that returns a Promise which will load the options
*/
loadOptions: PropTypes.func,
/**
* Function to call when the selected value changes
*/
onSelection: PropTypes.func,
/**
* The select options
*/
options: PropTypes.arrayOf(PropTypes.object),
/**
* The placeholder string
*/
placeholder: PropTypes.string
};

export default Select;
82 changes: 82 additions & 0 deletions package/src/components/Select/Select.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
### Overview
The `Select` component provides functionality to select single or multiple value(s) with
autocompletion; options may be provided synchronously or asynchronously. Under the hood, the [react-select](https://react-select.com) component is used.

**NOTE**: In addition to the props listed above, all props supported by `react-select` will be passed through. To see the full list click [here](https://react-select.com/props)

### Usage

#### Basic Single Select
Single value select with options provided synchronously:
```jsx
const options = [
{ value: "mens", label: "Mens" },
{ value: "womens", label: "Womens" },
{ value: "kids", label: "Kids" }
];

// Log selected value
function handleOnSelection(value) {
console.log("Selected value: ", value);
}

<Select
onSelection={handleOnSelection}
options={options}
placeholder="Select a tag"
/>
```

#### Basic Multi Select
Multi value select with options provided synchronously:
```jsx
const options = [
{ value: "mens", label: "Mens" },
{ value: "womens", label: "Womens" },
{ value: "kids", label: "Kids" }
];

// Log selected value
function handleOnSelection(value) {
console.log("Selected value: ", value);
}

<Select
isMulti
onSelection={handleOnSelection}
options={options}
placeholder="Select tags"
/>
```

#### Async options
Multi value select with options provided asynchronously:
```jsx
import options from "./helpers/tagData";

const filterOptions = (inputValue) => options.filter((i) => {
return i.label.toLowerCase().includes(inputValue.toLowerCase());
});

const promiseOptions = (inputValue) =>
new Promise((resolve) => {
setTimeout(() => {
resolve(filterOptions(inputValue));
}, 1000);
});

// Log selected value
function handleOnSelection(value) {
console.log("Selected value: ", value);
}

<Select
isMulti
isAsync
cacheOptions
defaultOptions
loadOptions={promiseOptions}
onSelection={handleOnSelection}
placeholder="Search tags"
/>
```
Loading

0 comments on commit 07b8ef0

Please sign in to comment.