* Implement "Simple" Select Component

* Add value prop and disabled option.

* Expand click target of .woocommerce-simple-select-control__selector

* Fix select state.

* remove left position

Co-Authored-By: Joshua T Flowers <joshuatf@gmail.com>

* Update packages/components/src/simple-select-control/style.scss

Co-Authored-By: Joshua T Flowers <joshuatf@gmail.com>

* Update packages/components/src/simple-select-control/style.scss

Co-Authored-By: Joshua T Flowers <joshuatf@gmail.com>

* Update packages/components/src/simple-select-control/style.scss

Co-Authored-By: Joshua T Flowers <joshuatf@gmail.com>
This commit is contained in:
Justin Shreve 2019-07-17 11:46:55 -07:00 committed by Timmy Crawford
parent 2a75bcefe6
commit 4e478d9fbb
12 changed files with 408 additions and 7 deletions

View File

@ -6,7 +6,7 @@ import { __, _x, sprintf } from '@wordpress/i18n';
import { Component, Fragment } from '@wordpress/element';
import { compose } from '@wordpress/compose';
import { FormToggle } from '@wordpress/components';
import { Button, SelectControl } from 'newspack-components';
import { Button } from 'newspack-components';
import { withDispatch } from '@wordpress/data';
import { keys, pickBy } from 'lodash';
@ -18,7 +18,7 @@ import { numberFormat } from '@woocommerce/number';
/**
* Internal dependencies
*/
import { H, Card } from '@woocommerce/components';
import { H, Card, SimpleSelectControl } from '@woocommerce/components';
import withSelect from 'wc-api/with-select';
import { recordEvent } from 'lib/tracks';
@ -294,7 +294,7 @@ class BusinessDetails extends Component {
<p>{ __( 'Tell us about the business' ) }</p>
<Card>
<SelectControl
<SimpleSelectControl
label={ __( 'How many products will you add?', 'woocommerce-admin' ) }
onChange={ value => this.updateValue( 'product_count', value ) }
onFocus={ this.setDefaultValue.bind( this, 'product_count', productCountOptions ) }
@ -305,7 +305,7 @@ class BusinessDetails extends Component {
required
/>
<SelectControl
<SimpleSelectControl
label={ __( 'Currently selling elsewhere?', 'woocommerce-admin' ) }
onChange={ value => this.updateValue( 'selling_venues', value ) }
onFocus={ this.setDefaultValue.bind( this, 'selling_venues', sellingVenueOptions ) }
@ -317,7 +317,7 @@ class BusinessDetails extends Component {
/>
{ [ 'other', 'brick-mortar-other' ].includes( selling_venues ) && (
<SelectControl
<SimpleSelectControl
label={ __( 'Which platform is the store using?', 'woocommerce-admin' ) }
onChange={ value => this.updateValue( 'other_platform', value ) }
onFocus={ this.setDefaultValue.bind( this, 'other_platform', otherPlatformOptions ) }

View File

@ -25,7 +25,7 @@
color: $muriel-gray-600;
text-align: left;
button {
.muriel-button {
display: flex;
margin: $gap-smaller auto 0;
min-width: 310px;

View File

@ -22,6 +22,7 @@
{ "component": "SearchListControl" },
{ "component": "Section" },
{ "component": "SegmentedSelection" },
{ "component": "SimpleSelectControl" },
{ "component": "Spinner" },
{ "component": "SplitButton" },
{ "component": "Stepper" },

View File

@ -32,6 +32,7 @@
* [SectionHeader](components/packages/section-header.md)
* [Section](components/packages/section.md)
* [SegmentedSelection](components/packages/segmented-selection.md)
* [SimpleSelectControl](components/packages/simple-select-control.md)
* [Spinner](components/packages/spinner.md)
* [SplitButton](components/packages/split-button.md)
* [Stepper](components/packages/stepper.md)

View File

@ -0,0 +1,45 @@
`SimpleSelectControl` (component)
=================================
A component for displaying a material styled 'simple' select control.
Props
-----
### `className`
- Type: String
- Default: null
Additional class name to style the component.
### `label`
- Type: String
- Default: null
A label to use for the main select element.
### `options`
- Type: Array
- value: String - Input value for this option.
- label: String - Label for this option.
- disabled: Boolean - Disable this option in the list.
- Default: null
An array of options to use for the dropddown.
### `onChange`
- Type: Function
- Default: null
A function that receives the value of the new option that is being selected as input.
### `value`
- Type: String
- Default: null
The selected value for the control.

View File

@ -4,6 +4,7 @@
- TableCard component: add `onSearch` an `onSort` function props.
- Add new component `<List />` for displaying interactive list items.
- Fix z-index issue in `<Chart />` empty message.
- Added a new `<SimpleSelectControl />` component.
# 3.1.0
- Added support for a countLabel prop on SearchListItem to allow custom counts.

View File

@ -40,6 +40,7 @@ export { default as SearchListControl } from './search-list-control';
export { default as SearchListItem } from './search-list-control/item';
export { default as SectionHeader } from './section-header';
export { default as SegmentedSelection } from './segmented-selection';
export { default as SimpleSelectControl } from './simple-select-control';
export { default as SplitButton } from './split-button';
export { default as Spinner } from './spinner';
export { default as Stepper } from './stepper';

View File

@ -0,0 +1,50 @@
```jsx
import { SimpleSelectControl } from '@woocommerce/components';
class MySimpleSelectControl extends Component {
constructor() {
super();
this.state = {
pet: '',
};
}
render() {
const { pet } = this.state;
const petOptions = [
{
value: 'cat',
label: 'Cat',
},
{
value: 'dog',
label: 'Dog',
},
{
value: 'bunny',
label: 'Bunny',
},
{
value: 'snake',
label: 'Snake',
},
{
value: 'chinchilla',
label: 'Chinchilla',
disabled: true,
},
];
return (
<SimpleSelectControl
label="What is your favorite pet?"
onChange={ value => this.setState( { pet: value } ) }
options={ petOptions }
value={ pet }
/>
);
}
}
```

View File

@ -0,0 +1,176 @@
/**
* External dependencies
*/
import { __, sprintf } from '@wordpress/i18n';
import { Dropdown, Button, NavigableMenu, withFocusOutside } from '@wordpress/components';
import { Component } from '@wordpress/element';
import { map, find } from 'lodash';
import classNames from 'classnames';
import PropTypes from 'prop-types';
/**
* A component for displaying a material styled 'simple' select control.
*/
class SimpleSelectControl extends Component {
constructor( props ) {
super( props );
this.state = {
currentValue: props.value,
isFocused: false,
};
}
componentDidUpdate( prevProps ) {
if (
this.props.value !== prevProps.value &&
this.state.currentValue !== this.props.value
) {
/* eslint-disable react/no-did-update-set-state */
this.setState( {
currentValue: this.props.value,
} );
/* eslint-enable react/no-did-update-set-state */
}
}
handleFocusOutside() {
this.setState( { isFocused: false } );
}
handleOnClick( onToggle ) {
this.setState( { isFocused: true } );
if ( 'function' === typeof onToggle ) {
onToggle();
}
const { onClick } = this.props;
if ( 'function' === typeof onClick ) {
onClick();
}
}
handleOnFocus() {
this.setState( { isFocused: true } );
const { onFocus } = this.props;
if ( 'function' === typeof onFocus ) {
onFocus();
}
}
onChange( value ) {
this.props.onChange( value );
this.setState( { currentValue: value } );
}
render() {
const { options, label, className } = this.props;
const { currentValue, isFocused } = this.state;
const onChange = ( value ) => {
this.onChange( value );
this.handleFocusOutside();
};
const isEmpty = currentValue === '' || currentValue === null;
const currentOption = find( options, ( { value } ) => value === currentValue );
return (
<Dropdown
className={ classNames(
'woocommerce-simple-select-control__dropdown',
'components-base-control',
className,
{
'is-empty': isEmpty,
'has-value': ! isEmpty,
'is-active': isFocused,
}
) }
contentClassName="woocommerce-simple-select-control__dropdown-content"
position="center"
renderToggle={ ( { isOpen, onToggle } ) => (
<Button
className="woocommerce-simple-select-control__selector"
onClick={ () => this.handleOnClick( onToggle ) }
onFocus={ () => this.handleOnFocus() }
aria-expanded={ isOpen }
aria-label={ ! isEmpty ? sprintf(
/* translators: Label: Current Value for a Select Dropddown */
__( '%s: %s' ), label, currentOption && currentOption.label
) : label }
>
<span className="woocommerce-simple-select-control__label">{ label }</span>
<span className="woocommerce-simple-select-control__value">{ currentOption && currentOption.label }</span>
</Button>
) }
renderContent={ ( { onClose } ) => (
<NavigableMenu>
{ map( options, ( option ) => {
const optionValue = option.value;
const optionLabel = option.label;
const optionDisabled = option.disabled || false;
const isSelected = ( currentValue === optionValue );
return (
<Button
key={ optionValue }
onClick={ () => {
onChange( optionValue );
onClose();
} }
className={ classNames( {
'is-selected': isSelected,
} ) }
disabled={ optionDisabled }
role="menuitemradio"
aria-checked={ isSelected }
>
<span>
{ optionLabel }
</span>
</Button>
);
} ) }
</NavigableMenu>
) }
/>
);
}
}
SimpleSelectControl.propTypes = {
/**
* Additional class name to style the component.
*/
className: PropTypes.string,
/**
* A label to use for the main select element.
*/
label: PropTypes.string,
/**
* An array of options to use for the dropddown.
*/
options: PropTypes.arrayOf(
PropTypes.shape( {
/**
* Input value for this option.
*/
value: PropTypes.string,
/**
* Label for this option.
*/
label: PropTypes.string,
/**
* Disable the option.
*/
disabled: PropTypes.bool,
} )
),
/**
* A function that receives the value of the new option that is being selected as input.
*/
onChange: PropTypes.func,
/**
* The currently value of the select element.
*/
value: PropTypes.string,
};
export default withFocusOutside( SimpleSelectControl );

View File

@ -0,0 +1,125 @@
.woocommerce-simple-select-control__dropdown {
position: relative;
width: 100%;
height: 56px;
border: 1px solid #b0b5b8;
box-shadow: none;
box-sizing: border-box;
border-radius: 3px;
background: #fff;
font-size: 16px;
line-height: 54px;
font-weight: 400;
margin-top: $gap;
margin-bottom: $gap;
.woocommerce-simple-select-control__selector {
&.components-button {
border: 0;
background: transparent;
box-shadow: none;
justify-content: unset;
margin-left: 0;
position: absolute;
top: 0;
height: 100%;
width: 100%;
left: 0;
padding-left: $gap;
padding-right: $gap-largest;
&:focus:not(:disabled) {
box-shadow: none;
outline: none;
background-color: transparent;
outline-offset: initial;
}
&::after {
display: block;
pointer-events: none;
position: absolute;
float: right;
line-height: 56px;
font-family: dashicons, sans-serif;
font-size: 20px;
content: '\f140';
z-index: 101;
height: 24px;
width: 24px;
margin-top: 0;
top: 0;
right: $gap;
bottom: 16px;
color: #000;
}
}
}
.woocommerce-simple-select-control__dropdown-content {
&.components-popover.is-bottom {
margin-top: -$gap-largest * 2;
z-index: 999;
&::before,
&::after {
display: none;
}
}
.components-button {
display: block;
position: relative;
padding: 10px 20px 10px 40px;
width: 100%;
text-align: left;
&.is-selected {
background: $muriel-gray-100;
}
}
}
&.is-empty {
.woocommerce-simple-select-control__selector {
&.components-button {
font-size: 16px;
line-height: 54px;
height: 54px;
z-index: 100;
color: #636d75;
}
}
}
&.is-active {
box-shadow: 0 0 0 2px #673d99;
border-color: transparent;
}
&.has-value {
.woocommerce-simple-select-control__selector {
display: flex;
flex-direction: column;
&.components-button {
&::after {
bottom: 23px;
}
}
}
.woocommerce-simple-select-control__label {
margin-top: -$gap-smallest;
font-size: 12px;
line-height: 16px;
margin-top: 8px;
color: #636d75;
}
.woocommerce-simple-select-control__value {
color: #2b2d2f;
font-size: 16px;
white-space: nowrap;
}
}
}

View File

@ -1,5 +1,5 @@
```jsx
import { MySpinner } from '@woocommerce/components';
import { Spinner } from '@woocommerce/components';
const MySpinner = () => (
<div>

View File

@ -26,6 +26,7 @@
@import 'search-list-control/style.scss';
@import 'section-header/style.scss';
@import 'segmented-selection/style.scss';
@import 'simple-select-control/style.scss';
@import 'split-button/style.scss';
@import 'stepper/style.scss';
@import 'spinner/style.scss';