-
Notifications
You must be signed in to change notification settings - Fork 87
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement react-select for our select input components (#515)
* install react-select * add utility for retrieving current available modelNames. This is populated via eejs.data from the server. * add an abstracted base method for generating queryString * extract model related methods to their own class * add additional exports * implement new model-select component. This uses the react-select component * implement new EventSelect that wraps model-select * test all the things * convert datetime select to new component * export build-options for components
- Loading branch information
Showing
45 changed files
with
13,345 additions
and
5,035 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,9 @@ | ||
{ | ||
"components.js": "ee-components.269a5a9408e9d4ec4a94.dist.js", | ||
"data-stores.js": "ee-data-stores.7cbf939209f1378b61ad.dist.js", | ||
"components.js": "ee-components.6251295f9ed97c5e0d58.dist.js", | ||
"data-stores.js": "ee-data-stores.8be4f554fcdf636c356b.dist.js", | ||
"eejs.js": "ee-eejs.079eec177a6d0cb54b86.dist.js", | ||
"manifest.js": "ee-manifest.3b66c2f77d3b01c1b09a.dist.js", | ||
"vendor.js": "ee-vendor.a7c50608201c56d1424c.dist.js", | ||
"wp-plugins-page.css": "ee-wp-plugins-page.6bf5b0c4985bf9cc730a.dist.css", | ||
"wp-plugins-page.js": "ee-wp-plugins-page.955c372519f507d64cb7.dist.js" | ||
"wp-plugins-page.css": "ee-wp-plugins-page.12612f6ab71570ab1af8.dist.css", | ||
"wp-plugins-page.js": "ee-wp-plugins-page.1700d8fd2144a931cb59.dist.js" | ||
} |
4,287 changes: 0 additions & 4,287 deletions
4,287
assets/dist/ee-components.269a5a9408e9d4ec4a94.dist.js
This file was deleted.
Oops, something went wrong.
11,675 changes: 11,675 additions & 0 deletions
11,675
assets/dist/ee-components.6251295f9ed97c5e0d58.dist.js
Large diffs are not rendered by default.
Oops, something went wrong.
183 changes: 181 additions & 2 deletions
183
...-data-stores.7cbf939209f1378b61ad.dist.js → ...-data-stores.8be4f554fcdf636c356b.dist.js
Large diffs are not rendered by default.
Oops, something went wrong.
2 changes: 1 addition & 1 deletion
2
...lugins-page.6bf5b0c4985bf9cc730a.dist.css → ...lugins-page.12612f6ab71570ab1af8.dist.css
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
2 changes: 1 addition & 1 deletion
2
...plugins-page.955c372519f507d64cb7.dist.js → ...plugins-page.1700d8fd2144a931cb59.dist.js
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from './select'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
/** | ||
* External dependencies | ||
*/ | ||
import { reduce } from 'lodash'; | ||
|
||
/** | ||
* A default map used for mapping options for select. | ||
* @type {Object} | ||
*/ | ||
const DEFAULT_MODEL_OPTIONS_MAP = { | ||
event: { | ||
label: 'EVT_name', | ||
value: 'EVT_ID', | ||
}, | ||
datetime: { | ||
label: 'DTT_name', | ||
value: 'DTT_ID', | ||
}, | ||
ticket: { | ||
label: 'TKT_name', | ||
value: 'TKT_ID', | ||
}, | ||
}; | ||
|
||
export const OPTION_SELECT_ALL = 'ALL'; | ||
|
||
/** | ||
* Receives an array of event entities and returns an array of simple objects | ||
* that can be passed along to the options array used for the WordPress | ||
* SelectControl component. | ||
* | ||
* @param { Array } entities | ||
* @param { string } modelName | ||
* @param { string } addAllOptionLabel If present then options array will be | ||
* prepended with an "ALL" option meaning | ||
* that all options are selected. | ||
* @param { Object } map | ||
* @return { Array } Returns an array of simple objects formatted for any | ||
* select control that receives its options in the format of an array of objects | ||
* with label and value keys. | ||
*/ | ||
const buildOptions = ( | ||
entities, | ||
modelName, | ||
addAllOptionLabel = '', | ||
map = DEFAULT_MODEL_OPTIONS_MAP, | ||
) => { | ||
const MAP_FOR_MODEL = map[ modelName ] ? map[ modelName ] : false; | ||
const generatedOptions = entities && MAP_FOR_MODEL ? | ||
reduce( entities, function( options, entity ) { | ||
if ( entity[ MAP_FOR_MODEL.label ] && | ||
entity[ MAP_FOR_MODEL.value ] ) { | ||
options.push( | ||
{ | ||
label: entity[ MAP_FOR_MODEL.label ], | ||
value: entity[ MAP_FOR_MODEL.value ], | ||
}, | ||
); | ||
} | ||
return options; | ||
}, [] ) : | ||
[]; | ||
if ( entities && addAllOptionLabel !== '' ) { | ||
generatedOptions.unshift( { | ||
label: addAllOptionLabel, | ||
value: OPTION_SELECT_ALL, | ||
} ); | ||
} | ||
return generatedOptions; | ||
}; | ||
|
||
export default buildOptions; |
111 changes: 111 additions & 0 deletions
111
assets/src/components/form/select/default-select-configuration.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
/** | ||
* External imports | ||
*/ | ||
import PropTypes from 'prop-types'; | ||
import { __ } from '@eventespresso/i18n'; | ||
|
||
export const REACT_SELECT_TYPES = { | ||
'aria-describedby': PropTypes.string, | ||
'aria-label': PropTypes.string, | ||
'aria-labelledby': PropTypes.string, | ||
autoFocus: PropTypes.bool, | ||
backspaceRemovesValue: PropTypes.bool, | ||
blurInputOnSelect: PropTypes.bool, | ||
captureMenuScroll: PropTypes.bool, | ||
className: PropTypes.string, | ||
classNamePrefix: PropTypes.string, | ||
closeMenuOnSelect: PropTypes.bool, | ||
components: PropTypes.object, | ||
controlShouldRenderValue: PropTypes.bool, | ||
delimiter: PropTypes.string, | ||
escapeClearsValue: PropTypes.bool, | ||
filterOption: PropTypes.func, | ||
formatGroupLabel: PropTypes.func, | ||
formatOptionLabel: PropTypes.func, | ||
getOptionLabel: PropTypes.func, | ||
getOptionValue: PropTypes.func, | ||
hideSelectedOptions: PropTypes.bool, | ||
id: PropTypes.string, | ||
inputValue: PropTypes.string, | ||
inputId: PropTypes.string, | ||
instanceId: PropTypes.oneOfType( [ | ||
PropTypes.number, | ||
PropTypes.string, | ||
] ), | ||
isClearable: PropTypes.bool, | ||
isDisabled: PropTypes.bool, | ||
isLoading: PropTypes.bool, | ||
isOptionDisabled: PropTypes.func, | ||
isOptionSelected: PropTypes.func, | ||
isMulti: PropTypes.bool, | ||
isSearchable: PropTypes.bool, | ||
loadingMessage: PropTypes.func, | ||
minMenuHeight: PropTypes.number, | ||
maxMenuHeight: PropTypes.number, | ||
menuIsOpen: PropTypes.bool, | ||
menuPlacement: PropTypes.oneOf( [ | ||
'auto', | ||
'bottom', | ||
'top', | ||
] ), | ||
menuPosition: PropTypes.oneOf( [ | ||
'absolute', | ||
'fixed', | ||
] ), | ||
menuPortalTarget: PropTypes.element, | ||
menuShouldBlockScroll: PropTypes.bool, | ||
menuShouldScrollIntoView: PropTypes.bool, | ||
name: PropTypes.string, | ||
noOptionsMessage: PropTypes.func, | ||
onBlur: PropTypes.func, | ||
onChange: PropTypes.func, | ||
onFocus: PropTypes.func, | ||
onInputChange: PropTypes.func, | ||
onKeyDown: PropTypes.func, | ||
onMenuOpen: PropTypes.func, | ||
onMenuClose: PropTypes.func, | ||
onMenuScrollToTop: PropTypes.func, | ||
onMenuScrollToBottom: PropTypes.func, | ||
openMenuOnFocus: PropTypes.bool, | ||
openMenuOnClick: PropTypes.bool, | ||
options: PropTypes.array, | ||
pageSize: PropTypes.number, | ||
placeholder: PropTypes.string, | ||
screenReaderStatus: PropTypes.func, | ||
styles: PropTypes.shape( { | ||
clearIndicator: PropTypes.func, | ||
container: PropTypes.func, | ||
control: PropTypes.func, | ||
dropdownIndicator: PropTypes.func, | ||
group: PropTypes.func, | ||
groupHeading: PropTypes.func, | ||
indicatorsContainer: PropTypes.func, | ||
indicatorSeparator: PropTypes.func, | ||
input: PropTypes.func, | ||
loadingIndicator: PropTypes.func, | ||
loadingMessageCSS: PropTypes.func, | ||
menu: PropTypes.func, | ||
menuList: PropTypes.func, | ||
menuPortal: PropTypes.func, | ||
multiValue: PropTypes.func, | ||
multiValueLabel: PropTypes.func, | ||
multiValueRemove: PropTypes.func, | ||
noOptionsMessageCSS: PropTypes.func, | ||
option: PropTypes.func, | ||
placeholder: PropTypes.func, | ||
singleValue: PropTypes.func, | ||
valueContainer: PropTypes.func, | ||
} ), | ||
tabIndex: PropTypes.string, | ||
tabSelectsValue: PropTypes.bool, | ||
value: PropTypes.oneOfType( [ | ||
PropTypes.object, | ||
PropTypes.array, | ||
] ), | ||
}; | ||
|
||
export const REACT_SELECT_DEFAULTS = { | ||
isClearable: true, | ||
isLoading: true, | ||
placeholder: __( 'Select...', 'event_espresso' ), | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export { ModelSelect, default as ModelEnhancedSelect } from './model-select'; | ||
export * from './model-selects'; | ||
export * from './build-options'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,170 @@ | ||
/** | ||
* External imports | ||
*/ | ||
import Select from 'react-select'; | ||
import { Component, Fragment } from '@wordpress/element'; | ||
import { isEmpty, uniqueId, find, isUndefined } from 'lodash'; | ||
import PropTypes from 'prop-types'; | ||
|
||
/** | ||
* WP dependencies | ||
*/ | ||
import { withSelect } from '@wordpress/data'; | ||
|
||
/** | ||
* Internal imports | ||
*/ | ||
import buildOptions from './build-options'; | ||
import { MODEL_NAMES } from '../../../data/model'; | ||
import { | ||
REACT_SELECT_DEFAULTS, | ||
REACT_SELECT_TYPES, | ||
} from './default-select-configuration'; | ||
|
||
/** | ||
* ModelSelect component. | ||
* This is a component that will generate a react-select input for a given | ||
* model and its entities (provided via props). | ||
* | ||
* @see https://deploy-preview-2289--react-select.netlify.com/props#prop-types | ||
* for options that can be passed through via the selectConfiguration prop. | ||
* | ||
* @param { Object } selectConfiguration An object containing options for the | ||
* react-select component. | ||
* @param { Array } modelEntities Array of model entities | ||
* @param { string } modelName The name of the Model the entities | ||
* belong to. | ||
* @param { function } mapOptionsCallback This function will receive by | ||
* default the modelEntities, the modelName (and any custom Map provided) and | ||
* is expected to return an array of options to be used for the react-select | ||
* component. | ||
* @param { Object } optionsEntityMap If provided, it is expected to be a | ||
* map of modelName fields to `label` and `value` keys used by | ||
* `mapOptionsCallback`. | ||
*/ | ||
export class ModelSelect extends Component { | ||
static propTypes = { | ||
selectConfiguration: PropTypes.shape( { | ||
...REACT_SELECT_TYPES, | ||
} ), | ||
modelEntities: PropTypes.array, | ||
modelName: PropTypes.oneOf( MODEL_NAMES ), | ||
mapOptionsCallback: PropTypes.func, | ||
optionsEntityMap: PropTypes.object, | ||
queryData: PropTypes.shape( { | ||
limit: PropTypes.number, | ||
orderBy: PropTypes.string, | ||
order: PropTypes.oneOf( [ 'asc', 'desc' ] ), | ||
} ), | ||
getQueryString: PropTypes.func, | ||
selectLabel: PropTypes.string, | ||
addAllOptionLabel: PropTypes.string, | ||
}; | ||
|
||
static defaultProps = { | ||
selectConfiguration: { | ||
...REACT_SELECT_DEFAULTS, | ||
name: uniqueId( 'model-select-' ), | ||
}, | ||
modelEntities: [], | ||
modelName: '', | ||
mapOptionsCallback: buildOptions, | ||
optionsEntityMap: null, | ||
queryData: { | ||
limit: 100, | ||
order: 'desc', | ||
}, | ||
selectLabel: '', | ||
addAllOptionLabel: '', | ||
}; | ||
|
||
static getDerivedStateFromProps( props ) { | ||
const { selectConfiguration } = props; | ||
const options = ModelSelect.getOptions( props ); | ||
const updated = { | ||
options, | ||
value: ModelSelect.getOptionObjectForValue( | ||
selectConfiguration.defaultValue, options | ||
), | ||
}; | ||
return { | ||
...REACT_SELECT_DEFAULTS, | ||
...selectConfiguration, | ||
...updated, | ||
}; | ||
} | ||
|
||
static getOptions( props ) { | ||
const { | ||
modelEntities, | ||
modelName, | ||
optionsEntityMap, | ||
mapOptionsCallback, | ||
addAllOptionLabel, | ||
} = props; | ||
if ( ! isEmpty( modelEntities ) ) { | ||
return optionsEntityMap !== null ? | ||
mapOptionsCallback( | ||
modelEntities, | ||
modelName, | ||
addAllOptionLabel, | ||
optionsEntityMap, | ||
) : | ||
mapOptionsCallback( | ||
modelEntities, | ||
modelName, | ||
addAllOptionLabel, | ||
); | ||
} | ||
return []; | ||
} | ||
|
||
static getOptionObjectForValue( value, options ) { | ||
if ( ! isEmpty( options ) ) { | ||
const match = find( options, function( option ) { | ||
return option.value === value; | ||
} ); | ||
return ! isUndefined( match ) ? | ||
match : | ||
null; | ||
} | ||
return {}; | ||
} | ||
|
||
getSelectLabel() { | ||
const { selectLabel, selectConfiguration } = this.props; | ||
return selectLabel ? | ||
<label htmlFor={ selectConfiguration.name }>{ selectLabel }</label> : | ||
''; | ||
} | ||
|
||
render() { | ||
return ( | ||
<Fragment> | ||
{ this.getSelectLabel() } | ||
<Select { ...this.state } /> | ||
</Fragment> | ||
); | ||
} | ||
} | ||
|
||
/** | ||
* The ModelSelect Component wrapped in the `withSelect` higher order component. | ||
* This subscribes the ModelSelect component to the state maintained via the | ||
* eventespresso/lists store. | ||
*/ | ||
export default withSelect( ( select, ownProps ) => { | ||
const { getQueryString, modelName, selectConfiguration } = ownProps; | ||
const queryString = getQueryString( ownProps.queryData ); | ||
const { getItems, isRequestingItems } = select( 'eventespresso/lists' ); | ||
return { | ||
...ModelSelect.defaultProps, | ||
...ownProps, | ||
modelEntities: getItems( modelName, queryString ), | ||
selectConfiguration: { | ||
...ModelSelect.defaultProps.selectConfiguration, | ||
...selectConfiguration, | ||
isLoading: isRequestingItems( modelName, queryString ), | ||
}, | ||
}; | ||
} )( ModelSelect ); |
Oops, something went wrong.