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

Create OrderSelect and Label components #905

Merged
merged 9 commits into from Aug 23, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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 } ) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is OrderSelect? I think it would be helpful to include some doc block here explaining what the OrderSelect component is used for.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in b7ed466.

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