diff --git a/plugins/woocommerce-admin/client/header/utils.js b/plugins/woocommerce-admin/client/header/utils.tsx similarity index 61% rename from plugins/woocommerce-admin/client/header/utils.js rename to plugins/woocommerce-admin/client/header/utils.tsx index 10d2bae283c..96a368d1e4b 100644 --- a/plugins/woocommerce-admin/client/header/utils.js +++ b/plugins/woocommerce-admin/client/header/utils.tsx @@ -1,6 +1,7 @@ /** * External dependencies */ +import { isValidElement } from 'react'; import { Slot, Fill } from '@wordpress/components'; import { cloneElement } from '@wordpress/element'; @@ -12,10 +13,34 @@ import { cloneElement } from '@wordpress/element'; * @param {Array} props - Fill props. * @return {Node} Node. */ -const createOrderedChildren = ( children, order, props ) => { - return typeof children === 'function' - ? cloneElement( children( props ), { order } ) - : cloneElement( children, { ...props, order } ); +const createOrderedChildren = ( + children: React.ReactNode, + order: number, + props: Fill.Props +) => { + if ( typeof children === 'function' ) { + return cloneElement( children( props ), { order } ); + } else if ( isValidElement( children ) ) { + return cloneElement( children, { ...props, order } ); + } + throw Error( 'Invalid children type' ); +}; + +/** + * Sort fills by order for slot children. + * + * @param {Array} fills - slot's `Fill`s. + * @return {Node} Node. + */ +const sortFillsByOrder: Slot.Props[ 'children' ] = ( fills ) => { + // Copy fills array here because its type is readonly array that doesn't have .sort method in Typescript definition. + const sortedFills = [ ...fills ].sort( ( a, b ) => { + return a[ 0 ].props.order - b[ 0 ].props.order; + } ); + if ( isValidElement( sortedFills ) ) { + return sortedFills; + } + return null; }; /** @@ -36,10 +61,12 @@ const createOrderedChildren = ( children, order, props ) => { * @param {Array} param0.children - Node children. * @param {Array} param0.order - Node order. */ -export const WooHeaderItem = ( { children, order = 1 } ) => { +export const WooHeaderItem: React.FC< { order?: number } > & { + Slot: React.FC< Slot.Props >; +} = ( { children, order = 1 } ) => { return ( - { ( fillProps ) => { + { ( fillProps: Fill.Props ) => { return createOrderedChildren( children, order, fillProps ); } } @@ -48,11 +75,7 @@ export const WooHeaderItem = ( { children, order = 1 } ) => { WooHeaderItem.Slot = ( { fillProps } ) => ( - { ( fills ) => { - return fills.sort( ( a, b ) => { - return a[ 0 ].props.order - b[ 0 ].props.order; - } ); - } } + { sortFillsByOrder } ); @@ -75,23 +98,21 @@ WooHeaderItem.Slot = ( { fillProps } ) => ( * @param {Array} param0.children - Node children. * @param {Array} param0.order - Node order. */ -export const WooHeaderNavigationItem = ( { children, order = 1 } ) => { +export const WooHeaderNavigationItem: React.FC< { order?: number } > & { + Slot: React.FC< Slot.Props >; +} = ( { children, order = 1 } ) => { return ( - { ( fillProps ) => { + { ( fillProps: Fill.Props ) => { return createOrderedChildren( children, order, fillProps ); } } ); }; -WooHeaderNavigationItem.Slot = ( { fillProps } ) => ( +WooHeaderNavigationItem.Slot = ( { fillProps }: Slot.Props ) => ( - { ( fills ) => { - return fills.sort( ( a, b ) => { - return a[ 0 ].props.order - b[ 0 ].props.order; - } ); - } } + { sortFillsByOrder } ); @@ -112,15 +133,20 @@ WooHeaderNavigationItem.Slot = ( { fillProps } ) => ( * @param {Object} param0 * @param {Array} param0.children - Node children. */ -export const WooHeaderPageTitle = ( { children } ) => { +export const WooHeaderPageTitle: React.FC & { + Slot: React.FC< Slot.Props >; +} = ( { children } ) => { return { children }; }; WooHeaderPageTitle.Slot = ( { fillProps } ) => ( { ( fills ) => { - const last = fills.pop(); - return [ last ]; + const last = [ [ ...fills ].pop() ]; + if ( isValidElement( last ) ) { + return last; + } + return null; } } );