* WIP

* Converted block-error-boundary to TS

* Convert CheckboxList to TS

* Converted Chip and Removable Chip components to TS

* Change type to React.FC<T>

* Fix tests

* Implement Lucio feedback
This commit is contained in:
Alex Florisca 2021-12-03 17:23:25 +00:00 committed by GitHub
parent 48a29c5bba
commit 0a8415e3a6
8 changed files with 205 additions and 193 deletions

View File

@ -2,9 +2,12 @@
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import PropTypes from 'prop-types';
import { WC_BLOCKS_IMAGE_URL } from '@woocommerce/block-settings';
/**
* Internal dependencies
*/
import type { BlockErrorProps } from './types';
const BlockError = ( {
imageUrl = `${ WC_BLOCKS_IMAGE_URL }/block-error.svg`,
header = __( 'Oops!', 'woo-gutenberg-products-block' ),
@ -15,7 +18,7 @@ const BlockError = ( {
errorMessage,
errorMessagePrefix = __( 'Error:', 'woo-gutenberg-products-block' ),
button,
} ) => {
}: BlockErrorProps ): JSX.Element => {
return (
<div className="wc-block-error wc-block-components-error">
{ imageUrl && (
@ -52,37 +55,4 @@ const BlockError = ( {
);
};
BlockError.propTypes = {
/**
* Error message to display below the content.
*/
errorMessage: PropTypes.node,
/**
* Text to display as the heading of the error block.
* If it's `null` or an empty string, no header will be displayed.
* If it's not defined, the default header will be used.
*/
header: PropTypes.string,
/**
* URL of the image to display.
* If it's `null` or an empty string, no image will be displayed.
* If it's not defined, the default image will be used.
*/
imageUrl: PropTypes.string,
/**
* Text to display in the error block below the header.
* If it's `null` or an empty string, nothing will be displayed.
* If it's not defined, the default text will be used.
*/
text: PropTypes.node,
/**
* Text preceeding the error message.
*/
errorMessagePrefix: PropTypes.string,
/**
* Button cta.
*/
button: PropTypes.node,
};
export default BlockError;

View File

@ -1,104 +0,0 @@
/**
* External dependencies
*/
import PropTypes from 'prop-types';
import { Component } from 'react';
/**
* Internal dependencies
*/
import BlockError from './block-error';
import './style.scss';
class BlockErrorBoundary extends Component {
state = { errorMessage: '', hasError: false };
static getDerivedStateFromError( error ) {
if (
typeof error.statusText !== 'undefined' &&
typeof error.status !== 'undefined'
) {
return {
errorMessage: (
<>
<strong>{ error.status }</strong>:&nbsp;
{ error.statusText }
</>
),
hasError: true,
};
}
return { errorMessage: error.message, hasError: true };
}
render() {
const {
header,
imageUrl,
showErrorMessage,
text,
errorMessagePrefix,
renderError,
button,
} = this.props;
const { errorMessage, hasError } = this.state;
if ( hasError ) {
if ( typeof renderError === 'function' ) {
return renderError( { errorMessage } );
}
return (
<BlockError
errorMessage={ showErrorMessage ? errorMessage : null }
header={ header }
imageUrl={ imageUrl }
text={ text }
errorMessagePrefix={ errorMessagePrefix }
button={ button }
/>
);
}
return this.props.children;
}
}
BlockErrorBoundary.propTypes = {
/**
* Text to display as the heading of the error block.
* If it's `null` or an empty string, no header will be displayed.
* If it's not defined, the default header will be used.
*/
header: PropTypes.string,
/**
* URL of the image to display.
* If it's `null` or an empty string, no image will be displayed.
* If it's not defined, the default image will be used.
*/
imageUrl: PropTypes.string,
/**
* Whether to display the JS error message.
*/
showErrorMessage: PropTypes.bool,
/**
* Text to display in the error block below the header.
* If it's `null` or an empty string, nothing will be displayed.
* If it's not defined, the default text will be used.
*/
text: PropTypes.node,
/**
* Text preceeding the error message.
*/
errorMessagePrefix: PropTypes.string,
/**
* Render function to show a custom error component.
*/
renderError: PropTypes.func,
};
BlockErrorBoundary.defaultProps = {
showErrorMessage: true,
};
export default BlockErrorBoundary;

View File

@ -0,0 +1,72 @@
/**
* External dependencies
*/
import { Component } from 'react';
/**
* Internal dependencies
*/
import BlockError from './block-error';
import './style.scss';
import type {
DerivedStateReturn,
ReactError,
BlockErrorBoundaryProps,
} from './types';
class BlockErrorBoundary extends Component< BlockErrorBoundaryProps > {
state = { errorMessage: '', hasError: false };
static getDerivedStateFromError( error: ReactError ): DerivedStateReturn {
if (
typeof error.statusText !== 'undefined' &&
typeof error.status !== 'undefined'
) {
return {
errorMessage: (
<>
<strong>{ error.status }</strong>:&nbsp;
{ error.statusText }
</>
),
hasError: true,
};
}
return { errorMessage: error.message, hasError: true };
}
render(): JSX.Element | React.ReactNode {
const {
header,
imageUrl,
showErrorMessage = true,
text,
errorMessagePrefix,
renderError,
button,
} = this.props;
const { errorMessage, hasError } = this.state;
if ( hasError ) {
if ( typeof renderError === 'function' ) {
return renderError( { errorMessage } );
}
return (
<BlockError
errorMessage={ showErrorMessage ? errorMessage : null }
header={ header }
imageUrl={ imageUrl }
text={ text }
errorMessagePrefix={ errorMessagePrefix }
button={ button }
/>
);
}
return this.props.children;
}
}
export default BlockErrorBoundary;

View File

@ -0,0 +1,57 @@
interface BlockErrorBase {
/**
* URL of the image to display.
* If it's `null` or an empty string, no image will be displayed.
* If it's not defined, the default image will be used.
*/
imageUrl?: string;
/**
* Text to display as the heading of the error block.
* If it's `null` or an empty string, no header will be displayed.
* If it's not defined, the default header will be used.
*/
header?: string;
/**
* Text to display in the error block below the header.
* If it's `null` or an empty string, nothing will be displayed.
* If it's not defined, the default text will be used.
*/
text: React.ReactNode;
/**
* Text preceeding the error message.
*/
errorMessagePrefix?: string;
/**
* Button cta.
*/
button: React.ReactNode;
}
export interface BlockErrorProps extends BlockErrorBase {
/**
* Error message to display below the content.
*/
errorMessage: React.ReactNode;
}
type RenderErrorProps = {
errorMessage: React.ReactNode;
};
export interface BlockErrorBoundaryProps extends BlockErrorBase {
/**
* Override the default error with a function that takes the error message and returns a React component
*/
renderError: ( props: RenderErrorProps ) => React.ReactNode;
}
export interface DerivedStateReturn {
errorMessage: JSX.Element | string;
hasError: boolean;
}
export interface ReactError {
status: string;
statusText: string;
message: string;
}

View File

@ -2,7 +2,6 @@
* External dependencies
*/
import { __, _n, sprintf } from '@wordpress/i18n';
import PropTypes from 'prop-types';
import { Fragment, useMemo, useState } from '@wordpress/element';
import classNames from 'classnames';
@ -11,6 +10,21 @@ import classNames from 'classnames';
*/
import './style.scss';
interface CheckboxListOptions {
label: React.ReactNode;
value: string;
}
interface CheckboxListProps {
className?: string;
isLoading?: boolean;
isDisabled?: boolean;
limit?: number;
checked?: string[];
onChange?: ( value: string ) => void;
options?: CheckboxListOptions[];
}
/**
* Component used to show a list of checkboxes in a group.
*
@ -25,13 +39,13 @@ import './style.scss';
*/
const CheckboxList = ( {
className,
onChange = () => {},
onChange = () => void 0,
options = [],
checked = [],
isLoading = false,
isDisabled = false,
limit = 10,
} ) => {
}: CheckboxListProps ): JSX.Element => {
const [ showExpanded, setShowExpanded ] = useState( false );
const placeholder = useMemo( () => {
@ -167,19 +181,4 @@ const CheckboxList = ( {
);
};
CheckboxList.propTypes = {
onChange: PropTypes.func,
options: PropTypes.arrayOf(
PropTypes.shape( {
label: PropTypes.node.isRequired,
value: PropTypes.string.isRequired,
} )
),
checked: PropTypes.array,
className: PropTypes.string,
isLoading: PropTypes.bool,
isDisabled: PropTypes.bool,
limit: PropTypes.number,
};
export default CheckboxList;

View File

@ -1,7 +1,6 @@
/**
* External dependencies
*/
import PropTypes from 'prop-types';
import classNames from 'classnames';
/**
@ -9,7 +8,32 @@ import classNames from 'classnames';
*/
import './style.scss';
/** @typedef {import('react')} React */
export interface ChipProps {
/**
* Text for chip content.
*/
text: string;
/**
* Screenreader text for the content.
*/
screenReaderText?: string;
/**
* The element type for the chip. Default 'li'.
*/
element?: string;
/**
* CSS class used.
*/
className?: string;
/**
* React children.
*/
children?: React.ReactNode | React.ReactNode[];
/**
* Radius size.
*/
radius?: 'none' | 'small' | 'medium' | 'large';
}
/**
* Component used to render a "chip" -- a list item containing some text.
@ -17,16 +41,8 @@ import './style.scss';
* Each chip defaults to a list element but this can be customized by providing
* a wrapperElement.
*
* @param {Object} props Incoming props for the component.
* @param {string} props.text Text for chip content.
* @param {string} props.screenReaderText Screenreader text for the content.
* @param {string} props.element The element type for the chip.
* @param {string} props.className CSS class used.
* @param {string} props.radius Radius size.
* @param {React.ReactChildren|null} props.children React children.
* @param {Object} props.props Rest of props passed through to component.
*/
const Chip = ( {
const Chip: React.FC< ChipProps > = ( {
text,
screenReaderText = '',
element = 'li',
@ -47,7 +63,6 @@ const Chip = ( {
);
return (
// @ts-ignore
<Wrapper className={ wrapperClassName } { ...props }>
<span
aria-hidden={ showScreenReaderText }
@ -62,13 +77,4 @@ const Chip = ( {
</Wrapper>
);
};
Chip.propTypes = {
text: PropTypes.node.isRequired,
screenReaderText: PropTypes.string,
element: PropTypes.elementType,
className: PropTypes.string,
radius: PropTypes.oneOf( [ 'none', 'small', 'medium', 'large' ] ),
};
export default Chip;

View File

@ -1,7 +1,6 @@
/**
* External dependencies
*/
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { __, sprintf } from '@wordpress/i18n';
import { Icon, noAlt } from '@woocommerce/icons';
@ -9,7 +8,30 @@ import { Icon, noAlt } from '@woocommerce/icons';
/**
* Internal dependencies
*/
import Chip from './chip.js';
import Chip, { ChipProps } from './chip';
interface RemovableChipProps extends ChipProps {
/**
* Aria label content.
*/
ariaLabel?: string;
/**
* CSS class used.
*/
className?: string;
/**
* Whether action is disabled or not.
*/
disabled?: boolean;
/**
* Function to call when remove event is fired.
*/
onRemove?: () => void;
/**
* Whether to expand click area for remove event.
*/
removeOnAnyClick?: boolean;
}
/**
* Component used to render a "chip" -- an item containing some text with
@ -25,11 +47,11 @@ import Chip from './chip.js';
* @param {string} props.screenReaderText The screen reader text for the chip.
* @param {Object} props.props Rest of props passed into component.
*/
const RemovableChip = ( {
const RemovableChip: React.FC< RemovableChipProps > = ( {
ariaLabel = '',
className = '',
disabled = false,
onRemove = () => void null,
onRemove = () => void 0,
removeOnAnyClick = false,
text,
screenReaderText = '',
@ -57,7 +79,7 @@ const RemovableChip = ( {
'aria-label': ariaLabel,
disabled,
onClick: onRemove,
onKeyDown: ( e ) => {
onKeyDown: ( e: React.KeyboardEvent ) => {
if ( e.key === 'Backspace' || e.key === 'Delete' ) {
onRemove();
}
@ -92,14 +114,4 @@ const RemovableChip = ( {
);
};
RemovableChip.propTypes = {
text: PropTypes.node.isRequired,
ariaLabel: PropTypes.string,
className: PropTypes.string,
disabled: PropTypes.bool,
onRemove: PropTypes.func,
removeOnAnyClick: PropTypes.bool,
screenReaderText: PropTypes.string,
};
export default RemovableChip;