Replace deprecated wp.compose.withState with wp.element.useState (https://github.com/woocommerce/woocommerce-admin/pull/8338)
* Update search-list-control * Update tag * Update stories * Add packages/components changelog
This commit is contained in:
parent
1c2bc9843c
commit
f052c3fed1
|
@ -1,5 +1,7 @@
|
|||
# Unreleased
|
||||
|
||||
- Replace deprecated wp.compose.withState with wp.element.useState. #8338
|
||||
|
||||
# 9.0.0
|
||||
|
||||
- Update line-height of SelectControl label to avoid truncated descenders in some typefaces and zoom levels. #8186
|
||||
|
|
|
@ -2,23 +2,25 @@
|
|||
* External dependencies
|
||||
*/
|
||||
import moment from 'moment';
|
||||
import { withState } from '@wordpress/compose';
|
||||
import { DatePicker, H, Section } from '@woocommerce/components';
|
||||
import { createElement } from '@wordpress/element';
|
||||
|
||||
import { useState } from '@wordpress/element';
|
||||
const dateFormat = 'MM/DD/YYYY';
|
||||
|
||||
const DatePickerExample = withState( {
|
||||
after: null,
|
||||
afterText: '',
|
||||
before: null,
|
||||
beforeText: '',
|
||||
afterError: null,
|
||||
beforeError: null,
|
||||
focusedInput: 'startDate',
|
||||
} )( ( { after, afterText, afterError, setState } ) => {
|
||||
const DatePickerExample = () => {
|
||||
const [ state, setState ] = useState( {
|
||||
after: null,
|
||||
afterText: '',
|
||||
before: null,
|
||||
beforeText: '',
|
||||
afterError: null,
|
||||
beforeError: null,
|
||||
focusedInput: 'startDate',
|
||||
} );
|
||||
const { after, afterText, afterError } = state;
|
||||
|
||||
function onDatePickerUpdate( { date, text, error } ) {
|
||||
setState( {
|
||||
...state,
|
||||
after: date,
|
||||
afterText: text,
|
||||
afterError: error,
|
||||
|
@ -40,7 +42,7 @@ const DatePickerExample = withState( {
|
|||
</Section>
|
||||
</div>
|
||||
);
|
||||
} );
|
||||
};
|
||||
|
||||
export const Basic = () => <DatePickerExample />;
|
||||
|
||||
|
|
|
@ -2,23 +2,29 @@
|
|||
* External dependencies
|
||||
*/
|
||||
import moment from 'moment';
|
||||
import { withState } from '@wordpress/compose';
|
||||
import { DateRange, H, Section } from '@woocommerce/components';
|
||||
import { createElement, Fragment } from '@wordpress/element';
|
||||
import { useState } from '@wordpress/element';
|
||||
|
||||
const dateFormat = 'MM/DD/YYYY';
|
||||
|
||||
const DateRangeExample = withState( {
|
||||
after: null,
|
||||
afterText: '',
|
||||
before: null,
|
||||
beforeText: '',
|
||||
afterError: null,
|
||||
beforeError: null,
|
||||
focusedInput: 'startDate',
|
||||
} )( ( { after, afterText, before, beforeText, focusedInput, setState } ) => {
|
||||
const DateRangeExample = () => {
|
||||
const [ state, setState ] = useState( {
|
||||
after: null,
|
||||
afterText: '',
|
||||
before: null,
|
||||
beforeText: '',
|
||||
afterError: null,
|
||||
beforeError: null,
|
||||
focusedInput: 'startDate',
|
||||
} );
|
||||
|
||||
const { after, afterText, before, beforeText, focusedInput } = state;
|
||||
|
||||
function onRangeUpdate( update ) {
|
||||
setState( update );
|
||||
setState( {
|
||||
...state,
|
||||
...update,
|
||||
} );
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -40,7 +46,7 @@ const DateRangeExample = withState( {
|
|||
</Section>
|
||||
</>
|
||||
);
|
||||
} );
|
||||
};
|
||||
|
||||
export const Basic = () => <DateRangeExample />;
|
||||
|
||||
|
|
|
@ -1,47 +1,57 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { withState } from '@wordpress/compose';
|
||||
import { Fragment } from '@wordpress/element';
|
||||
import { Fragment, useState } from '@wordpress/element';
|
||||
import { Icon } from '@wordpress/icons';
|
||||
import CrossSmall from 'gridicons/dist/cross-small';
|
||||
import { EllipsisMenu, MenuItem, MenuTitle } from '@woocommerce/components';
|
||||
|
||||
const ExampleEllipsisMenu = withState( {
|
||||
showCustomers: true,
|
||||
showOrders: true,
|
||||
} )( ( { setState, showCustomers, showOrders } ) => (
|
||||
<EllipsisMenu
|
||||
label="Choose which analytics to display"
|
||||
renderContent={ ( { onToggle } ) => (
|
||||
<Fragment>
|
||||
<MenuTitle>Display stats</MenuTitle>
|
||||
<MenuItem
|
||||
isCheckbox
|
||||
isClickable
|
||||
checked={ showCustomers }
|
||||
onInvoke={ () =>
|
||||
setState( { showCustomers: ! showCustomers } )
|
||||
}
|
||||
>
|
||||
Show Customers
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
isCheckbox
|
||||
isClickable
|
||||
checked={ showOrders }
|
||||
onInvoke={ () => setState( { showOrders: ! showOrders } ) }
|
||||
>
|
||||
Show Orders
|
||||
</MenuItem>
|
||||
<MenuItem isClickable onInvoke={ onToggle }>
|
||||
<Icon icon={ <CrossSmall /> } />
|
||||
Close Menu
|
||||
</MenuItem>
|
||||
</Fragment>
|
||||
) }
|
||||
/>
|
||||
) );
|
||||
const ExampleEllipsisMenu = () => {
|
||||
const [ { showCustomers, showOrders }, setState ] = useState( {
|
||||
showCustomers: true,
|
||||
showOrders: true,
|
||||
} );
|
||||
return (
|
||||
<EllipsisMenu
|
||||
label="Choose which analytics to display"
|
||||
renderContent={ ( { onToggle } ) => (
|
||||
<Fragment>
|
||||
<MenuTitle>Display stats</MenuTitle>
|
||||
<MenuItem
|
||||
isCheckbox
|
||||
isClickable
|
||||
checked={ showCustomers }
|
||||
onInvoke={ () =>
|
||||
setState( {
|
||||
showOrders,
|
||||
showCustomers: ! showCustomers,
|
||||
} )
|
||||
}
|
||||
>
|
||||
Show Customers
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
isCheckbox
|
||||
isClickable
|
||||
checked={ showOrders }
|
||||
onInvoke={ () =>
|
||||
setState( {
|
||||
showCustomers,
|
||||
showOrders: ! showOrders,
|
||||
} )
|
||||
}
|
||||
>
|
||||
Show Orders
|
||||
</MenuItem>
|
||||
<MenuItem isClickable onInvoke={ onToggle }>
|
||||
<Icon icon={ <CrossSmall /> } />
|
||||
Close Menu
|
||||
</MenuItem>
|
||||
</Fragment>
|
||||
) }
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const Basic = () => <ExampleEllipsisMenu />;
|
||||
|
||||
|
|
|
@ -1,17 +1,19 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { withState } from '@wordpress/compose';
|
||||
import { useState } from '@wordpress/element';
|
||||
import { ImageUpload } from '@woocommerce/components';
|
||||
|
||||
const ImageUploadExample = withState( {
|
||||
image: null,
|
||||
} )( ( { setState, logo } ) => (
|
||||
<ImageUpload
|
||||
image={ logo }
|
||||
onChange={ ( image ) => setState( { logo: image } ) }
|
||||
/>
|
||||
) );
|
||||
const ImageUploadExample = () => {
|
||||
const [ image, setImage ] = useState( null );
|
||||
|
||||
return (
|
||||
<ImageUpload
|
||||
image={ image }
|
||||
onChange={ ( _image ) => setImage( _image ) }
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const Basic = () => <ImageUploadExample />;
|
||||
|
||||
|
|
|
@ -8,8 +8,13 @@ import {
|
|||
TextControl,
|
||||
withSpokenMessages,
|
||||
} from '@wordpress/components';
|
||||
import { createElement, Component, Fragment } from '@wordpress/element';
|
||||
import { compose, withInstanceId, withState } from '@wordpress/compose';
|
||||
import {
|
||||
createElement,
|
||||
Fragment,
|
||||
useState,
|
||||
useEffect,
|
||||
} from '@wordpress/element';
|
||||
import { compose, withInstanceId } from '@wordpress/compose';
|
||||
import { escapeRegExp, findIndex } from 'lodash';
|
||||
import NoticeOutlineIcon from 'gridicons/dist/notice-outline';
|
||||
import PropTypes from 'prop-types';
|
||||
|
@ -43,28 +48,33 @@ const defaultMessages = {
|
|||
|
||||
/**
|
||||
* Component to display a searchable, selectable list of items.
|
||||
*
|
||||
* @param {Object} props
|
||||
*/
|
||||
export class SearchListControl extends Component {
|
||||
constructor() {
|
||||
super( ...arguments );
|
||||
export const SearchListControl = ( props ) => {
|
||||
const [ searchValue, setSearchValue ] = useState( props.search || '' );
|
||||
const {
|
||||
isSingle,
|
||||
isLoading,
|
||||
onChange,
|
||||
selected,
|
||||
instanceId,
|
||||
messages: propsMessages,
|
||||
isCompact,
|
||||
debouncedSpeak,
|
||||
onSearch,
|
||||
className = '',
|
||||
} = props;
|
||||
|
||||
this.onSelect = this.onSelect.bind( this );
|
||||
this.onRemove = this.onRemove.bind( this );
|
||||
this.onClear = this.onClear.bind( this );
|
||||
this.isSelected = this.isSelected.bind( this );
|
||||
this.defaultRenderItem = this.defaultRenderItem.bind( this );
|
||||
this.renderList = this.renderList.bind( this );
|
||||
}
|
||||
const messages = { ...defaultMessages, ...propsMessages };
|
||||
|
||||
componentDidUpdate( prevProps ) {
|
||||
const { onSearch, search } = this.props;
|
||||
if ( search !== prevProps.search && typeof onSearch === 'function' ) {
|
||||
onSearch( search );
|
||||
useEffect( () => {
|
||||
if ( typeof onSearch === 'function' ) {
|
||||
onSearch( searchValue );
|
||||
}
|
||||
}
|
||||
}, [ onSearch, searchValue ] );
|
||||
|
||||
onRemove( id ) {
|
||||
const { isSingle, onChange, selected } = this.props;
|
||||
const onRemove = ( id ) => {
|
||||
return () => {
|
||||
if ( isSingle ) {
|
||||
onChange( [] );
|
||||
|
@ -75,13 +85,12 @@ export class SearchListControl extends Component {
|
|||
...selected.slice( i + 1 ),
|
||||
] );
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
onSelect( item ) {
|
||||
const { isSingle, onChange, selected } = this.props;
|
||||
const onSelect = ( item ) => {
|
||||
return () => {
|
||||
if ( this.isSelected( item ) ) {
|
||||
this.onRemove( item.id )();
|
||||
if ( isSelected( item ) ) {
|
||||
onRemove( item.id )();
|
||||
return;
|
||||
}
|
||||
if ( isSingle ) {
|
||||
|
@ -90,39 +99,32 @@ export class SearchListControl extends Component {
|
|||
onChange( [ ...selected, item ] );
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
onClear() {
|
||||
this.props.onChange( [] );
|
||||
}
|
||||
const isSelected = ( item ) =>
|
||||
findIndex( selected, { id: item.id } ) !== -1;
|
||||
|
||||
isSelected( item ) {
|
||||
return findIndex( this.props.selected, { id: item.id } ) !== -1;
|
||||
}
|
||||
|
||||
getFilteredList( list, search ) {
|
||||
const { isHierarchical } = this.props;
|
||||
const getFilteredList = ( list, search ) => {
|
||||
const { isHierarchical } = props;
|
||||
if ( ! search ) {
|
||||
return isHierarchical ? buildTermsTree( list ) : list;
|
||||
}
|
||||
const messages = { ...defaultMessages, ...this.props.messages };
|
||||
const re = new RegExp( escapeRegExp( search ), 'i' );
|
||||
this.props.debouncedSpeak( messages.updated );
|
||||
debouncedSpeak( messages.updated );
|
||||
const filteredList = list
|
||||
.map( ( item ) => ( re.test( item.name ) ? item : false ) )
|
||||
.filter( Boolean );
|
||||
return isHierarchical
|
||||
? buildTermsTree( filteredList, list )
|
||||
: filteredList;
|
||||
}
|
||||
};
|
||||
|
||||
defaultRenderItem( args ) {
|
||||
const defaultRenderItem = ( args ) => {
|
||||
return <SearchListItem { ...args } />;
|
||||
}
|
||||
};
|
||||
|
||||
renderList( list, depth = 0 ) {
|
||||
const { isSingle, search, instanceId } = this.props;
|
||||
const renderItem = this.props.renderItem || this.defaultRenderItem;
|
||||
const renderList = ( list, depth = 0 ) => {
|
||||
const renderItem = props.renderItem || defaultRenderItem;
|
||||
if ( ! list ) {
|
||||
return null;
|
||||
}
|
||||
|
@ -132,23 +134,20 @@ export class SearchListControl extends Component {
|
|||
<li>
|
||||
{ renderItem( {
|
||||
item,
|
||||
isSelected: this.isSelected( item ),
|
||||
onSelect: this.onSelect,
|
||||
isSelected: isSelected( item ),
|
||||
onSelect,
|
||||
isSingle,
|
||||
search,
|
||||
search: searchValue,
|
||||
depth,
|
||||
controlId: instanceId,
|
||||
} ) }
|
||||
</li>
|
||||
{ this.renderList( item.children, depth + 1 ) }
|
||||
{ renderList( item.children, depth + 1 ) }
|
||||
</Fragment>
|
||||
) );
|
||||
}
|
||||
|
||||
renderListSection() {
|
||||
const { isLoading, search } = this.props;
|
||||
const messages = { ...defaultMessages, ...this.props.messages };
|
||||
};
|
||||
|
||||
const renderListSection = () => {
|
||||
if ( isLoading ) {
|
||||
return (
|
||||
<div className="woocommerce-search-list__list is-loading">
|
||||
|
@ -156,7 +155,7 @@ export class SearchListControl extends Component {
|
|||
</div>
|
||||
);
|
||||
}
|
||||
const list = this.getFilteredList( this.props.list, search );
|
||||
const list = getFilteredList( props.list, searchValue );
|
||||
|
||||
if ( ! list.length ) {
|
||||
return (
|
||||
|
@ -169,9 +168,9 @@ export class SearchListControl extends Component {
|
|||
/>
|
||||
</span>
|
||||
<span className="woocommerce-search-list__not-found-text">
|
||||
{ search
|
||||
{ searchValue
|
||||
? // eslint-disable-next-line @wordpress/valid-sprintf
|
||||
sprintf( messages.noResults, search )
|
||||
sprintf( messages.noResults, searchValue )
|
||||
: messages.noItems }
|
||||
</span>
|
||||
</div>
|
||||
|
@ -180,15 +179,12 @@ export class SearchListControl extends Component {
|
|||
|
||||
return (
|
||||
<ul className="woocommerce-search-list__list">
|
||||
{ this.renderList( list ) }
|
||||
{ renderList( list ) }
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
|
||||
renderSelectedSection() {
|
||||
const { isLoading, isSingle, selected } = this.props;
|
||||
const messages = { ...defaultMessages, ...this.props.messages };
|
||||
};
|
||||
|
||||
const renderSelectedSection = () => {
|
||||
if ( isLoading || isSingle || ! selected ) {
|
||||
return null;
|
||||
}
|
||||
|
@ -202,7 +198,7 @@ export class SearchListControl extends Component {
|
|||
<Button
|
||||
isLink
|
||||
isDestructive
|
||||
onClick={ this.onClear }
|
||||
onClick={ onChange( [] ) }
|
||||
aria-label={ messages.clear }
|
||||
>
|
||||
{ __( 'Clear all', 'woocommerce-admin' ) }
|
||||
|
@ -216,7 +212,7 @@ export class SearchListControl extends Component {
|
|||
<Tag
|
||||
label={ item.name }
|
||||
id={ item.id }
|
||||
remove={ this.onRemove }
|
||||
remove={ onRemove }
|
||||
/>
|
||||
</li>
|
||||
) ) }
|
||||
|
@ -224,34 +220,29 @@ export class SearchListControl extends Component {
|
|||
) : null }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { className = '', isCompact, search, setState } = this.props;
|
||||
const messages = { ...defaultMessages, ...this.props.messages };
|
||||
return (
|
||||
<div
|
||||
className={ classnames( 'woocommerce-search-list', className, {
|
||||
'is-compact': isCompact,
|
||||
} ) }
|
||||
>
|
||||
{ renderSelectedSection() }
|
||||
|
||||
return (
|
||||
<div
|
||||
className={ classnames( 'woocommerce-search-list', className, {
|
||||
'is-compact': isCompact,
|
||||
} ) }
|
||||
>
|
||||
{ this.renderSelectedSection() }
|
||||
|
||||
<div className="woocommerce-search-list__search">
|
||||
<TextControl
|
||||
label={ messages.search }
|
||||
type="search"
|
||||
value={ search }
|
||||
onChange={ ( value ) => setState( { search: value } ) }
|
||||
/>
|
||||
</div>
|
||||
|
||||
{ this.renderListSection() }
|
||||
<div className="woocommerce-search-list__search">
|
||||
<TextControl
|
||||
label={ messages.search }
|
||||
type="search"
|
||||
value={ searchValue }
|
||||
onChange={ ( value ) => setSearchValue( value ) }
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
{ renderListSection() }
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
SearchListControl.propTypes = {
|
||||
/**
|
||||
|
@ -334,19 +325,12 @@ SearchListControl.propTypes = {
|
|||
* The list of currently selected items.
|
||||
*/
|
||||
selected: PropTypes.array.isRequired,
|
||||
// from withState
|
||||
search: PropTypes.string,
|
||||
setState: PropTypes.func,
|
||||
// from withSpokenMessages
|
||||
debouncedSpeak: PropTypes.func,
|
||||
// from withInstanceId
|
||||
instanceId: PropTypes.number,
|
||||
};
|
||||
|
||||
export default compose( [
|
||||
withState( {
|
||||
search: '',
|
||||
} ),
|
||||
withSpokenMessages,
|
||||
withInstanceId,
|
||||
] )( SearchListControl );
|
||||
export default compose( [ withSpokenMessages, withInstanceId ] )(
|
||||
SearchListControl
|
||||
);
|
||||
|
|
|
@ -3,13 +3,12 @@
|
|||
*/
|
||||
import { boolean } from '@storybook/addon-knobs';
|
||||
import { SearchListControl } from '@woocommerce/components';
|
||||
import { withState } from '@wordpress/compose';
|
||||
import { createElement } from '@wordpress/element';
|
||||
import { useState } from '@wordpress/element';
|
||||
|
||||
const SearchListControlExample = () => {
|
||||
const [ selected, setSelected ] = useState( [] );
|
||||
const [ loading, setLoading ] = useState( false );
|
||||
|
||||
const SearchListControlExample = withState( {
|
||||
selected: [],
|
||||
loading: false,
|
||||
} )( ( { selected, loading, setState } ) => {
|
||||
const showCount = boolean( 'Show count', false );
|
||||
const isCompact = boolean( 'Compact', false );
|
||||
const isSingle = boolean( 'Single', false );
|
||||
|
@ -30,7 +29,7 @@ const SearchListControlExample = withState( {
|
|||
|
||||
return (
|
||||
<div>
|
||||
<button onClick={ () => setState( { loading: ! loading } ) }>
|
||||
<button onClick={ () => setLoading( ! loading ) }>
|
||||
Toggle loading state
|
||||
</button>
|
||||
<SearchListControl
|
||||
|
@ -38,12 +37,12 @@ const SearchListControlExample = withState( {
|
|||
isCompact={ isCompact }
|
||||
isLoading={ loading }
|
||||
selected={ selected }
|
||||
onChange={ ( items ) => setState( { selected: items } ) }
|
||||
onChange={ ( items ) => setSelected( items ) }
|
||||
isSingle={ isSingle }
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
} );
|
||||
};
|
||||
|
||||
export const Basic = () => <SearchListControlExample />;
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { render } from '@testing-library/react';
|
||||
import { render, fireEvent } from '@testing-library/react';
|
||||
import { noop } from 'lodash';
|
||||
import { createElement } from '@wordpress/element';
|
||||
|
||||
|
@ -174,4 +174,24 @@ describe( 'SearchListControl', () => {
|
|||
);
|
||||
expect( component ).toMatchSnapshot();
|
||||
} );
|
||||
|
||||
test( 'should match options after changing search control', () => {
|
||||
const { getByLabelText, getAllByText } = render(
|
||||
<SearchListControl
|
||||
instanceId={ 1 }
|
||||
list={ list }
|
||||
search=""
|
||||
selected={ [] }
|
||||
onChange={ noop }
|
||||
debouncedSpeak={ noop }
|
||||
/>
|
||||
);
|
||||
|
||||
fireEvent.change( getByLabelText( 'Search for items' ), {
|
||||
target: {
|
||||
value: 'berry',
|
||||
},
|
||||
} );
|
||||
expect( getAllByText( 'berry' ).length ).toBe( 2 );
|
||||
} );
|
||||
} );
|
||||
|
|
|
@ -1,35 +1,37 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { withState } from '@wordpress/compose';
|
||||
import { H, Search, Section } from '@woocommerce/components';
|
||||
import { useState } from '@wordpress/element';
|
||||
|
||||
const SearchExample = withState( {
|
||||
selected: [],
|
||||
inlineSelected: [],
|
||||
} )( ( { selected, inlineSelected, setState } ) => (
|
||||
<div>
|
||||
<H>Tags Below Input</H>
|
||||
<Section component={ false }>
|
||||
<Search
|
||||
type="products"
|
||||
placeholder="Search for a product"
|
||||
selected={ selected }
|
||||
onChange={ ( items ) => setState( { selected: items } ) }
|
||||
/>
|
||||
</Section>
|
||||
<H>Tags Inline with Input</H>
|
||||
<Section component={ false }>
|
||||
<Search
|
||||
type="products"
|
||||
placeholder="Search for a product"
|
||||
selected={ inlineSelected }
|
||||
onChange={ ( items ) => setState( { inlineSelected: items } ) }
|
||||
inlineTags
|
||||
/>
|
||||
</Section>
|
||||
</div>
|
||||
) );
|
||||
const SearchExample = () => {
|
||||
const [ selected, setSelected ] = useState( [] );
|
||||
const [ inlineSelected, setInlineSelect ] = useState( [] );
|
||||
|
||||
return (
|
||||
<div>
|
||||
<H>Tags Below Input</H>
|
||||
<Section component={ false }>
|
||||
<Search
|
||||
type="products"
|
||||
placeholder="Search for a product"
|
||||
selected={ selected }
|
||||
onChange={ ( items ) => setSelected( items ) }
|
||||
/>
|
||||
</Section>
|
||||
<H>Tags Inline with Input</H>
|
||||
<Section component={ false }>
|
||||
<Search
|
||||
type="products"
|
||||
placeholder="Search for a product"
|
||||
selected={ inlineSelected }
|
||||
onChange={ ( items ) => setInlineSelect( items ) }
|
||||
inlineTags
|
||||
/>
|
||||
</Section>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const Basic = () => <SearchExample />;
|
||||
|
||||
|
|
|
@ -1,27 +1,29 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { withState } from '@wordpress/compose';
|
||||
import { SegmentedSelection } from '@woocommerce/components';
|
||||
import { useState } from '@wordpress/element';
|
||||
|
||||
const name = 'number';
|
||||
|
||||
const SegmentedSelectionExample = withState( {
|
||||
selected: 'two',
|
||||
} )( ( { selected, setState } ) => (
|
||||
<SegmentedSelection
|
||||
options={ [
|
||||
{ value: 'one', label: 'One' },
|
||||
{ value: 'two', label: 'Two' },
|
||||
{ value: 'three', label: 'Three' },
|
||||
{ value: 'four', label: 'Four' },
|
||||
] }
|
||||
selected={ selected }
|
||||
legend="Select a number"
|
||||
onSelect={ ( data ) => setState( { selected: data[ name ] } ) }
|
||||
name={ name }
|
||||
/>
|
||||
) );
|
||||
const SegmentedSelectionExample = () => {
|
||||
const [ selected, setSelected ] = useState( 'two' );
|
||||
|
||||
return (
|
||||
<SegmentedSelection
|
||||
options={ [
|
||||
{ value: 'one', label: 'One' },
|
||||
{ value: 'two', label: 'Two' },
|
||||
{ value: 'three', label: 'Three' },
|
||||
{ value: 'four', label: 'Four' },
|
||||
] }
|
||||
selected={ selected }
|
||||
legend="Select a number"
|
||||
onSelect={ ( data ) => setSelected( data[ name ] ) }
|
||||
name={ name }
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const Basic = () => <SegmentedSelectionExample />;
|
||||
|
||||
|
|
|
@ -2,11 +2,7 @@
|
|||
* External dependencies
|
||||
*/
|
||||
import { SelectControl } from '@woocommerce/components';
|
||||
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { withState } from '@wordpress/compose';
|
||||
import { useState } from '@wordpress/element';
|
||||
|
||||
const options = [
|
||||
{
|
||||
|
@ -52,16 +48,18 @@ const options = [
|
|||
},
|
||||
];
|
||||
|
||||
const SelectControlExample = withState( {
|
||||
simpleSelected: [],
|
||||
simpleMultipleSelected: [],
|
||||
singleSelected: [],
|
||||
singleSelectedShowAll: [],
|
||||
multipleSelected: [],
|
||||
inlineSelected: [],
|
||||
allOptionsIncludingSelected: options[ options.length - 1 ].key,
|
||||
} )(
|
||||
( {
|
||||
const SelectControlExample = () => {
|
||||
const [ state, setState ] = useState( {
|
||||
simpleSelected: [],
|
||||
simpleMultipleSelected: [],
|
||||
singleSelected: [],
|
||||
singleSelectedShowAll: [],
|
||||
multipleSelected: [],
|
||||
inlineSelected: [],
|
||||
allOptionsIncludingSelected: options[ options.length - 1 ].key,
|
||||
} );
|
||||
|
||||
const {
|
||||
simpleSelected,
|
||||
simpleMultipleSelected,
|
||||
singleSelected,
|
||||
|
@ -69,13 +67,14 @@ const SelectControlExample = withState( {
|
|||
multipleSelected,
|
||||
inlineSelected,
|
||||
allOptionsIncludingSelected,
|
||||
setState,
|
||||
} ) => (
|
||||
} = state;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<SelectControl
|
||||
label="Simple single value"
|
||||
onChange={ ( selected ) =>
|
||||
setState( { simpleSelected: selected } )
|
||||
setState( { ...state, simpleSelected: selected } )
|
||||
}
|
||||
options={ options }
|
||||
placeholder="Start typing to filter options..."
|
||||
|
@ -86,7 +85,7 @@ const SelectControlExample = withState( {
|
|||
label="Multiple values"
|
||||
multiple
|
||||
onChange={ ( selected ) =>
|
||||
setState( { simpleMultipleSelected: selected } )
|
||||
setState( { ...state, simpleMultipleSelected: selected } )
|
||||
}
|
||||
options={ options }
|
||||
placeholder="Start typing to filter options..."
|
||||
|
@ -96,7 +95,10 @@ const SelectControlExample = withState( {
|
|||
<SelectControl
|
||||
label="Show all options with default selected"
|
||||
onChange={ ( selected ) =>
|
||||
setState( { allOptionsIncludingSelected: selected } )
|
||||
setState( {
|
||||
...state,
|
||||
allOptionsIncludingSelected: selected,
|
||||
} )
|
||||
}
|
||||
options={ options }
|
||||
placeholder="Start typing to filter options..."
|
||||
|
@ -110,7 +112,7 @@ const SelectControlExample = withState( {
|
|||
label="Single value searchable"
|
||||
isSearchable
|
||||
onChange={ ( selected ) =>
|
||||
setState( { singleSelected: selected } )
|
||||
setState( { ...state, singleSelected: selected } )
|
||||
}
|
||||
options={ options }
|
||||
placeholder="Start typing to filter options..."
|
||||
|
@ -121,7 +123,7 @@ const SelectControlExample = withState( {
|
|||
label="Single value searchable with options on refocus"
|
||||
isSearchable
|
||||
onChange={ ( selected ) =>
|
||||
setState( { singleSelectedShowAll: selected } )
|
||||
setState( { ...state, singleSelectedShowAll: selected } )
|
||||
}
|
||||
options={ options }
|
||||
placeholder="Start typing to filter options..."
|
||||
|
@ -135,7 +137,7 @@ const SelectControlExample = withState( {
|
|||
multiple
|
||||
inlineTags
|
||||
onChange={ ( selected ) =>
|
||||
setState( { inlineSelected: selected } )
|
||||
setState( { ...state, inlineSelected: selected } )
|
||||
}
|
||||
options={ options }
|
||||
placeholder="Start typing to filter options..."
|
||||
|
@ -148,7 +150,7 @@ const SelectControlExample = withState( {
|
|||
label="Hidden options before search"
|
||||
multiple
|
||||
onChange={ ( selected ) =>
|
||||
setState( { multipleSelected: selected } )
|
||||
setState( { ...state, multipleSelected: selected } )
|
||||
}
|
||||
options={ options }
|
||||
placeholder="Start typing to filter options..."
|
||||
|
@ -156,8 +158,8 @@ const SelectControlExample = withState( {
|
|||
showClearButton
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
);
|
||||
};
|
||||
|
||||
export const Basic = () => <SelectControlExample />;
|
||||
|
||||
|
|
|
@ -1,15 +1,17 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { withState } from '@wordpress/compose';
|
||||
import { Stepper } from '@woocommerce/components';
|
||||
import { createElement } from '@wordpress/element';
|
||||
import { useState } from '@wordpress/element';
|
||||
|
||||
const BasicExamples = () => {
|
||||
const [ state, setState ] = useState( {
|
||||
currentStep: 'first',
|
||||
isComplete: false,
|
||||
isPending: false,
|
||||
} );
|
||||
const { currentStep, isComplete, isPending } = state;
|
||||
|
||||
const BasicExamples = withState( {
|
||||
currentStep: 'first',
|
||||
isComplete: false,
|
||||
isPending: false,
|
||||
} )( ( { currentStep, isComplete, isPending, setState } ) => {
|
||||
const goToStep = ( key ) => {
|
||||
setState( { currentStep: key } );
|
||||
};
|
||||
|
@ -56,7 +58,11 @@ const BasicExamples = withState( {
|
|||
{ isComplete ? (
|
||||
<button
|
||||
onClick={ () =>
|
||||
setState( { currentStep: 'first', isComplete: false } )
|
||||
setState( {
|
||||
...state,
|
||||
currentStep: 'first',
|
||||
isComplete: false,
|
||||
} )
|
||||
}
|
||||
>
|
||||
Reset
|
||||
|
@ -66,6 +72,7 @@ const BasicExamples = withState( {
|
|||
<button
|
||||
onClick={ () =>
|
||||
setState( {
|
||||
...state,
|
||||
currentStep: steps[ currentIndex - 1 ].key,
|
||||
} )
|
||||
}
|
||||
|
@ -76,6 +83,7 @@ const BasicExamples = withState( {
|
|||
<button
|
||||
onClick={ () =>
|
||||
setState( {
|
||||
...state,
|
||||
currentStep: steps[ currentIndex + 1 ].key,
|
||||
} )
|
||||
}
|
||||
|
@ -84,13 +92,17 @@ const BasicExamples = withState( {
|
|||
Next step
|
||||
</button>
|
||||
<button
|
||||
onClick={ () => setState( { isComplete: true } ) }
|
||||
onClick={ () =>
|
||||
setState( { ...state, isComplete: true } )
|
||||
}
|
||||
disabled={ currentIndex !== steps.length - 1 }
|
||||
>
|
||||
Complete
|
||||
</button>
|
||||
<button
|
||||
onClick={ () => setState( { isPending: ! isPending } ) }
|
||||
onClick={ () =>
|
||||
setState( { ...state, isPending: ! isPending } )
|
||||
}
|
||||
>
|
||||
Toggle Spinner
|
||||
</button>
|
||||
|
@ -113,7 +125,7 @@ const BasicExamples = withState( {
|
|||
/>
|
||||
</div>
|
||||
);
|
||||
} );
|
||||
};
|
||||
|
||||
export const Examples = () => <BasicExamples />;
|
||||
|
||||
|
|
|
@ -2,38 +2,37 @@
|
|||
* External dependencies
|
||||
*/
|
||||
import { TableCard } from '@woocommerce/components';
|
||||
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { withState } from '@wordpress/compose';
|
||||
import { useState } from '@wordpress/element';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { headers, rows, summary } from './index';
|
||||
|
||||
const TableCardExample = withState( {
|
||||
query: {
|
||||
paged: 1,
|
||||
},
|
||||
} )( ( { query, setState } ) => (
|
||||
<TableCard
|
||||
title="Revenue last week"
|
||||
rows={ rows }
|
||||
headers={ headers }
|
||||
onQueryChange={ ( param ) => ( value ) =>
|
||||
setState( {
|
||||
query: {
|
||||
[ param ]: value,
|
||||
},
|
||||
} ) }
|
||||
query={ query }
|
||||
rowsPerPage={ 7 }
|
||||
totalRows={ 10 }
|
||||
summary={ summary }
|
||||
/>
|
||||
) );
|
||||
const TableCardExample = () => {
|
||||
const [ { query }, setState ] = useState( {
|
||||
query: {
|
||||
paged: 1,
|
||||
},
|
||||
} );
|
||||
return (
|
||||
<TableCard
|
||||
title="Revenue last week"
|
||||
rows={ rows }
|
||||
headers={ headers }
|
||||
onQueryChange={ ( param ) => ( value ) =>
|
||||
setState( {
|
||||
query: {
|
||||
[ param ]: value,
|
||||
},
|
||||
} ) }
|
||||
query={ query }
|
||||
rowsPerPage={ 7 }
|
||||
totalRows={ 10 }
|
||||
summary={ summary }
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const Basic = () => <TableCardExample />;
|
||||
|
||||
|
|
|
@ -2,13 +2,13 @@
|
|||
* External dependencies
|
||||
*/
|
||||
import { __, sprintf } from '@wordpress/i18n';
|
||||
import { createElement, Fragment } from '@wordpress/element';
|
||||
import { createElement, Fragment, useState } from '@wordpress/element';
|
||||
import classnames from 'classnames';
|
||||
import { Button, Popover } from '@wordpress/components';
|
||||
import { Icon, cancelCircleFilled } from '@wordpress/icons';
|
||||
import { decodeEntities } from '@wordpress/html-entities';
|
||||
import PropTypes from 'prop-types';
|
||||
import { withState, withInstanceId } from '@wordpress/compose';
|
||||
import { withInstanceId } from '@wordpress/compose';
|
||||
|
||||
/**
|
||||
* This component can be used to show an item styled as a "tag", optionally with an `X` + "remove"
|
||||
|
@ -17,26 +17,24 @@ import { withState, withInstanceId } from '@wordpress/compose';
|
|||
* @param {Object} props
|
||||
* @param {number|string} props.id
|
||||
* @param {string}props.instanceId
|
||||
* @param {boolean} props.isVisible
|
||||
* @param {string} props.label
|
||||
* @param {Object} props.popoverContents
|
||||
* @param {Function} props.remove
|
||||
* @param {string} props.screenReaderLabel
|
||||
* @param {Function} props.setState
|
||||
* @param {string} props.className
|
||||
* @return {Object} -
|
||||
*/
|
||||
const Tag = ( {
|
||||
id,
|
||||
instanceId,
|
||||
isVisible,
|
||||
label,
|
||||
popoverContents,
|
||||
remove,
|
||||
screenReaderLabel,
|
||||
setState,
|
||||
className,
|
||||
} ) => {
|
||||
const [ isVisible, setIsVisible ] = useState( false );
|
||||
|
||||
screenReaderLabel = screenReaderLabel || label;
|
||||
if ( ! label ) {
|
||||
// A null label probably means something went wrong
|
||||
|
@ -61,7 +59,7 @@ const Tag = ( {
|
|||
<Button
|
||||
className="woocommerce-tag__text"
|
||||
id={ labelId }
|
||||
onClick={ () => setState( () => ( { isVisible: true } ) ) }
|
||||
onClick={ () => setIsVisible( true ) }
|
||||
>
|
||||
{ labelTextNode }
|
||||
</Button>
|
||||
|
@ -71,9 +69,7 @@ const Tag = ( {
|
|||
</span>
|
||||
) }
|
||||
{ popoverContents && isVisible && (
|
||||
<Popover
|
||||
onClose={ () => setState( () => ( { isVisible: false } ) ) }
|
||||
>
|
||||
<Popover onClose={ () => setIsVisible( false ) }>
|
||||
{ popoverContents }
|
||||
</Popover>
|
||||
) }
|
||||
|
@ -122,6 +118,4 @@ Tag.propTypes = {
|
|||
screenReaderLabel: PropTypes.string,
|
||||
};
|
||||
|
||||
export default withState( {
|
||||
isVisible: false,
|
||||
} )( withInstanceId( Tag ) );
|
||||
export default withInstanceId( Tag );
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { render } from '@testing-library/react';
|
||||
import { render, fireEvent } from '@testing-library/react';
|
||||
import { createElement } from '@wordpress/element';
|
||||
|
||||
/**
|
||||
|
@ -35,4 +35,26 @@ describe( 'Tag', () => {
|
|||
);
|
||||
expect( component ).toMatchSnapshot();
|
||||
} );
|
||||
|
||||
test( 'Do not show popoverContents by default', () => {
|
||||
const { queryByText } = render(
|
||||
<Tag label="foo" popoverContents={ <p>This is a popover</p> } />
|
||||
);
|
||||
expect( queryByText( 'This is a popover' ) ).toBeNull();
|
||||
} );
|
||||
|
||||
test( 'Show popoverContents after clicking the button', () => {
|
||||
const { queryByText, queryByRole } = render(
|
||||
<Tag
|
||||
label="foo"
|
||||
instanceId="1"
|
||||
popoverContents={ <p>This is a popover</p> }
|
||||
/>
|
||||
);
|
||||
|
||||
fireEvent.click(
|
||||
queryByRole( 'button', { id: 'woocommerce-tag__label-1' } )
|
||||
);
|
||||
expect( queryByText( 'This is a popover' ) ).toBeDefined();
|
||||
} );
|
||||
} );
|
||||
|
|
|
@ -2,91 +2,95 @@
|
|||
* External dependencies
|
||||
*/
|
||||
import { TextControlWithAffixes } from '@woocommerce/components';
|
||||
import { useState } from '@wordpress/element';
|
||||
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { withState } from '@wordpress/compose';
|
||||
const Examples = () => {
|
||||
const [ state, setState ] = useState( {
|
||||
first: '',
|
||||
second: '',
|
||||
third: '',
|
||||
fourth: '',
|
||||
fifth: '',
|
||||
} );
|
||||
const { first, second, third, fourth, fifth } = state;
|
||||
const partialUpdate = ( partial ) => {
|
||||
setState( { ...state, ...partial } );
|
||||
};
|
||||
|
||||
const Examples = withState( {
|
||||
first: '',
|
||||
second: '',
|
||||
third: '',
|
||||
fourth: '',
|
||||
fifth: '',
|
||||
} )( ( { first, second, third, fourth, fifth, setState } ) => (
|
||||
<div>
|
||||
<TextControlWithAffixes
|
||||
label="Text field without affixes"
|
||||
value={ first }
|
||||
placeholder="Placeholder"
|
||||
onChange={ ( value ) => setState( { first: value } ) }
|
||||
/>
|
||||
<TextControlWithAffixes
|
||||
label="Disabled text field without affixes"
|
||||
value={ first }
|
||||
placeholder="Placeholder"
|
||||
onChange={ ( value ) => setState( { first: value } ) }
|
||||
disabled
|
||||
/>
|
||||
<TextControlWithAffixes
|
||||
prefix="$"
|
||||
label="Text field with a prefix"
|
||||
value={ second }
|
||||
onChange={ ( value ) => setState( { second: value } ) }
|
||||
/>
|
||||
<TextControlWithAffixes
|
||||
prefix="$"
|
||||
label="Disabled text field with a prefix"
|
||||
value={ second }
|
||||
onChange={ ( value ) => setState( { second: value } ) }
|
||||
disabled
|
||||
/>
|
||||
<TextControlWithAffixes
|
||||
prefix="Prefix"
|
||||
suffix="Suffix"
|
||||
label="Text field with both affixes"
|
||||
value={ third }
|
||||
onChange={ ( value ) => setState( { third: value } ) }
|
||||
/>
|
||||
<TextControlWithAffixes
|
||||
prefix="Prefix"
|
||||
suffix="Suffix"
|
||||
label="Disabled text field with both affixes"
|
||||
value={ third }
|
||||
onChange={ ( value ) => setState( { third: value } ) }
|
||||
disabled
|
||||
/>
|
||||
<TextControlWithAffixes
|
||||
suffix="%"
|
||||
label="Text field with a suffix"
|
||||
value={ fourth }
|
||||
onChange={ ( value ) => setState( { fourth: value } ) }
|
||||
/>
|
||||
<TextControlWithAffixes
|
||||
suffix="%"
|
||||
label="Disabled text field with a suffix"
|
||||
value={ fourth }
|
||||
onChange={ ( value ) => setState( { fourth: value } ) }
|
||||
disabled
|
||||
/>
|
||||
<TextControlWithAffixes
|
||||
prefix="$"
|
||||
label="Text field with prefix and help text"
|
||||
value={ fifth }
|
||||
onChange={ ( value ) => setState( { fifth: value } ) }
|
||||
help="This is some help text."
|
||||
/>
|
||||
<TextControlWithAffixes
|
||||
prefix="$"
|
||||
label="Disabled text field with prefix and help text"
|
||||
value={ fifth }
|
||||
onChange={ ( value ) => setState( { fifth: value } ) }
|
||||
help="This is some help text."
|
||||
disabled
|
||||
/>
|
||||
</div>
|
||||
) );
|
||||
return (
|
||||
<div>
|
||||
<TextControlWithAffixes
|
||||
label="Text field without affixes"
|
||||
value={ first }
|
||||
placeholder="Placeholder"
|
||||
onChange={ ( value ) => partialUpdate( { first: value } ) }
|
||||
/>
|
||||
<TextControlWithAffixes
|
||||
label="Disabled text field without affixes"
|
||||
value={ first }
|
||||
placeholder="Placeholder"
|
||||
onChange={ ( value ) => partialUpdate( { first: value } ) }
|
||||
disabled
|
||||
/>
|
||||
<TextControlWithAffixes
|
||||
prefix="$"
|
||||
label="Text field with a prefix"
|
||||
value={ second }
|
||||
onChange={ ( value ) => partialUpdate( { second: value } ) }
|
||||
/>
|
||||
<TextControlWithAffixes
|
||||
prefix="$"
|
||||
label="Disabled text field with a prefix"
|
||||
value={ second }
|
||||
onChange={ ( value ) => partialUpdate( { second: value } ) }
|
||||
disabled
|
||||
/>
|
||||
<TextControlWithAffixes
|
||||
prefix="Prefix"
|
||||
suffix="Suffix"
|
||||
label="Text field with both affixes"
|
||||
value={ third }
|
||||
onChange={ ( value ) => partialUpdate( { third: value } ) }
|
||||
/>
|
||||
<TextControlWithAffixes
|
||||
prefix="Prefix"
|
||||
suffix="Suffix"
|
||||
label="Disabled text field with both affixes"
|
||||
value={ third }
|
||||
onChange={ ( value ) => partialUpdate( { third: value } ) }
|
||||
disabled
|
||||
/>
|
||||
<TextControlWithAffixes
|
||||
suffix="%"
|
||||
label="Text field with a suffix"
|
||||
value={ fourth }
|
||||
onChange={ ( value ) => partialUpdate( { fourth: value } ) }
|
||||
/>
|
||||
<TextControlWithAffixes
|
||||
suffix="%"
|
||||
label="Disabled text field with a suffix"
|
||||
value={ fourth }
|
||||
onChange={ ( value ) => partialUpdate( { fourth: value } ) }
|
||||
disabled
|
||||
/>
|
||||
<TextControlWithAffixes
|
||||
prefix="$"
|
||||
label="Text field with prefix and help text"
|
||||
value={ fifth }
|
||||
onChange={ ( value ) => partialUpdate( { fifth: value } ) }
|
||||
help="This is some help text."
|
||||
/>
|
||||
<TextControlWithAffixes
|
||||
prefix="$"
|
||||
label="Disabled text field with prefix and help text"
|
||||
value={ fifth }
|
||||
onChange={ ( value ) => partialUpdate( { fifth: value } ) }
|
||||
help="This is some help text."
|
||||
disabled
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const Basic = () => <Examples />;
|
||||
|
||||
|
|
|
@ -2,29 +2,24 @@
|
|||
* External dependencies
|
||||
*/
|
||||
import { TextControl } from '@woocommerce/components';
|
||||
import { createElement } from '@wordpress/element';
|
||||
import { useState } from '@wordpress/element';
|
||||
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { withState } from '@wordpress/compose';
|
||||
const Example = () => {
|
||||
const [ value, setValue ] = useState( '' );
|
||||
|
||||
const Example = withState( {
|
||||
value: '',
|
||||
} )( ( { setState, value } ) => {
|
||||
return (
|
||||
<div>
|
||||
<TextControl
|
||||
name="text-control"
|
||||
label="Enter text here"
|
||||
onChange={ ( newValue ) => setState( { value: newValue } ) }
|
||||
onChange={ ( newValue ) => setValue( newValue ) }
|
||||
value={ value }
|
||||
/>
|
||||
<br />
|
||||
<TextControl label="Disabled field" disabled value="" />
|
||||
</div>
|
||||
);
|
||||
} );
|
||||
};
|
||||
|
||||
export const Basic = () => <Example />;
|
||||
|
||||
|
|
Loading…
Reference in New Issue