Skip to content
This repository has been archived by the owner on Feb 23, 2024. It is now read-only.

Commit

Permalink
Create OrderSelect and Label components (#905)
Browse files Browse the repository at this point in the history
* Create Label and OrderSelect components

* Update review list item so it uses 'classnames'

* Split review order select styles from generic component

* Update snapshots

* Refactor Label so we don't need to check Symbol

* Add description to Label and OrderSelect components

* Use prop-types instead of prop-types-elementtype

* Simplify Label propTypes

* Update package-lock
  • Loading branch information
Aljullu committed Aug 23, 2019
1 parent 420d834 commit a0281e9
Show file tree
Hide file tree
Showing 10 changed files with 315 additions and 67 deletions.
63 changes: 63 additions & 0 deletions assets/js/base/components/label/index.js
@@ -0,0 +1,63 @@
/**
* External dependencies
*/
import PropTypes from 'prop-types';
import { Fragment } from 'react';
import classNames from 'classnames';

/**
* Component used to render an accessible text given a label and/or a
* screenReaderLabel. The wrapper element and wrapper props can also be
* specified via props.
*/
const Label = ( { label, screenReaderLabel, wrapperElement, wrapperProps } ) => {
let Wrapper;

if ( ! label && screenReaderLabel ) {
Wrapper = wrapperElement || 'span';
wrapperProps = {
...wrapperProps,
className: classNames( wrapperProps.className, 'screen-reader-text' ),
};

return (
<Wrapper { ...wrapperProps }>
{ screenReaderLabel }
</Wrapper>
);
}

Wrapper = wrapperElement || Fragment;

if ( label && screenReaderLabel && label !== screenReaderLabel ) {
return (
<Wrapper { ...wrapperProps }>
<span aria-hidden>
{ label }
</span>
<span className="screen-reader-text">
{ screenReaderLabel }
</span>
</Wrapper>
);
}

return (
<Wrapper { ...wrapperProps }>
{ label }
</Wrapper>
);
};

Label.propTypes = {
label: PropTypes.string,
screenReaderLabel: PropTypes.string,
wrapperElement: PropTypes.elementType,
wrapperProps: PropTypes.object,
};

Label.defaultProps = {
wrapperProps: {},
};

export default Label;
62 changes: 62 additions & 0 deletions assets/js/base/components/label/test/__snapshots__/index.js.snap
@@ -0,0 +1,62 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Label with wrapperElement should render both label and screen reader label 1`] = `
<label
className="foo-bar"
data-foo="bar"
>
<span
aria-hidden={true}
>
Lorem
</span>
<span
className="screen-reader-text"
>
Ipsum
</span>
</label>
`;

exports[`Label with wrapperElement should render only the label 1`] = `
<label
className="foo-bar"
data-foo="bar"
>
Lorem
</label>
`;

exports[`Label with wrapperElement should render only the screen reader label 1`] = `
<label
className="foo-bar screen-reader-text"
data-foo="bar"
>
Ipsum
</label>
`;

exports[`Label without wrapperElement should render both label and screen reader label 1`] = `
Array [
<span
aria-hidden={true}
>
Lorem
</span>,
<span
className="screen-reader-text"
>
Ipsum
</span>,
]
`;

exports[`Label without wrapperElement should render only the label 1`] = `"Lorem"`;

exports[`Label without wrapperElement should render only the screen reader label 1`] = `
<span
className="screen-reader-text"
>
Ipsum
</span>
`;
85 changes: 85 additions & 0 deletions assets/js/base/components/label/test/index.js
@@ -0,0 +1,85 @@
/**
* External dependencies
*/
import TestRenderer from 'react-test-renderer';

/**
* Internal dependencies
*/
import Label from '../';

describe( 'Label', () => {
describe( 'without wrapperElement', () => {
test( 'should render both label and screen reader label', () => {
const component = TestRenderer.create(
<Label label="Lorem" screenReaderLabel="Ipsum" />
);

expect( component.toJSON() ).toMatchSnapshot();
} );

test( 'should render only the label', () => {
const component = TestRenderer.create(
<Label label="Lorem" />
);

expect( component.toJSON() ).toMatchSnapshot();
} );

test( 'should render only the screen reader label', () => {
const component = TestRenderer.create(
<Label screenReaderLabel="Ipsum" />
);

expect( component.toJSON() ).toMatchSnapshot();
} );
} );

describe( 'with wrapperElement', () => {
test( 'should render both label and screen reader label', () => {
const component = TestRenderer.create(
<Label
label="Lorem"
screenReaderLabel="Ipsum"
wrapperElement="label"
wrapperProps={ {
className: 'foo-bar',
'data-foo': 'bar',
} }
/>
);

expect( component.toJSON() ).toMatchSnapshot();
} );

test( 'should render only the label', () => {
const component = TestRenderer.create(
<Label
label="Lorem"
wrapperElement="label"
wrapperProps={ {
className: 'foo-bar',
'data-foo': 'bar',
} }
/>
);

expect( component.toJSON() ).toMatchSnapshot();
} );

test( 'should render only the screen reader label', () => {
const component = TestRenderer.create(
<Label
screenReaderLabel="Ipsum"
wrapperElement="label"
wrapperProps={ {
className: 'foo-bar',
'data-foo': 'bar',
} }
/>
);

expect( component.toJSON() ).toMatchSnapshot();
} );
} );
} );
18 changes: 5 additions & 13 deletions assets/js/base/components/load-more-button/index.js
Expand Up @@ -2,33 +2,25 @@
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import { Fragment } from 'react';
import PropTypes from 'prop-types';

/**
* Internal dependencies
*/
import Label from '../label';
import './style.scss';

export const LoadMoreButton = ( { onClick, label, screenReaderLabel } ) => {
const labelNode = ( screenReaderLabel && label !== screenReaderLabel ) ? (
<Fragment>
<span aria-hidden>
{ label }
</span>
<span className="screen-reader-text">
{ screenReaderLabel }
</span>
</Fragment>
) : label;

return (
<div className="wp-block-button wc-block-load-more">
<button
className="wp-block-button__link"
onClick={ onClick }
>
{ labelNode }
<Label
label={ label }
screenReaderLabel={ screenReaderLabel }
/>
</button>
</div>
);
Expand Down
63 changes: 63 additions & 0 deletions assets/js/base/components/order-select/index.js
@@ -0,0 +1,63 @@
/**
* External dependencies
*/
import PropTypes from 'prop-types';
import classNames from 'classnames';

/**
* Internal dependencies
*/
import Label from '../label';
import './style.scss';

/**
* Component used for 'Order by' selectors, which renders a label
* and a <select> with the options provided in the props.
*/
const OrderSelect = ( { className, componentId, defaultValue, label, onChange, options, screenReaderLabel, readOnly, value } ) => {
const selectId = `wc-block-order-select__select-${ componentId }`;

return (
<p className={ classNames( 'wc-block-order-select', className ) }>
<Label
label={ label }
screenReaderLabel={ screenReaderLabel }
wrapperElement="label"
wrapperProps={ {
className: 'wc-block-order-select__label',
htmlFor: selectId,
} }
/>
<select // eslint-disable-line jsx-a11y/no-onchange
id={ selectId }
className="wc-block-order-select__select"
defaultValue={ defaultValue }
onChange={ onChange }
readOnly={ readOnly }
value={ value }
>
{ options.map( ( option ) => (
<option key={ option.key } value={ option.key }>
{ option.label }
</option>
) ) }
</select>
</p>
);
};

OrderSelect.propTypes = {
componentId: PropTypes.number.isRequired,
defaultValue: PropTypes.string,
label: PropTypes.string,
onChange: PropTypes.func,
options: PropTypes.arrayOf( PropTypes.shape( {
key: PropTypes.string.isRequired,
label: PropTypes.string.isRequired,
} ) ),
readOnly: PropTypes.bool,
screenReaderLabel: PropTypes.string,
value: PropTypes.string,
};

export default OrderSelect;
9 changes: 9 additions & 0 deletions assets/js/base/components/order-select/style.scss
@@ -0,0 +1,9 @@
.wc-block-order-select {
margin-bottom: $gap-small;
}

.wc-block-order-select__label {
margin-right: $gap-small;
display: inline-block;
font-weight: normal;
}
17 changes: 5 additions & 12 deletions assets/js/base/components/review-list-item/index.js
Expand Up @@ -3,23 +3,14 @@
*/
import { __, sprintf } from '@wordpress/i18n';
import PropTypes from 'prop-types';
import classNames from 'classnames';

/**
* Internal dependencies
*/
import ReadMore from '../read-more';
import './style.scss';

function getReviewClasses( isLoading ) {
const classArray = [ 'wc-block-review-list-item__item' ];

if ( isLoading ) {
classArray.push( 'is-loading' );
}

return classArray.join( ' ' );
}

function getReviewImage( review, imageType, isLoading ) {
if ( isLoading || ! review ) {
return (
Expand Down Expand Up @@ -108,10 +99,12 @@ const ReviewListItem = ( { attributes, review = {} } ) => {
const { rating } = review;
const isLoading = ! Object.keys( review ).length > 0;
const showReviewRating = Number.isFinite( rating ) && showReviewRatingAttr;
const classes = getReviewClasses( isLoading );

return (
<li className={ classes } aria-hidden={ isLoading }>
<li
className={ classNames( 'wc-block-review-list-item__item', { 'is-loading': isLoading } ) }
aria-hidden={ isLoading }
>
{ ( showProductName || showReviewDate || showReviewerName || showReviewImage || showReviewRating ) && (
<div className="wc-block-review-list-item__info">
{ showReviewImage && getReviewImage( review, imageType, isLoading ) }
Expand Down

0 comments on commit a0281e9

Please sign in to comment.