Skip to content

Commit

Permalink
Type icon component
Browse files Browse the repository at this point in the history
This has more type assertions than I like…
  • Loading branch information
sirreal committed Oct 16, 2020
1 parent c74a29f commit c091772
Show file tree
Hide file tree
Showing 3 changed files with 46 additions and 8 deletions.
3 changes: 2 additions & 1 deletion packages/components/src/dashicon/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
* @property {number} [size=20] Icon size
* @property {string} [className] Class name
*/
/** @typedef {import('react').ComponentPropsWithoutRef<'span'> & OwnProps} Props */

/** @typedef {OwnProps & import('react').ComponentPropsWithoutRef<'span'>} Props */

/**
* @param {Props} props
Expand Down
50 changes: 43 additions & 7 deletions packages/components/src/icon/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,31 @@ import { SVG } from '@wordpress/primitives';
*/
import Dashicon from '../dashicon';

/* eslint-disable jsdoc/valid-types */
/**
* @template T
* @typedef {T extends import('react').ComponentType<infer U> ? U : T extends string ? import('react').ComponentPropsWithoutRef<'span'> : {}} AdditionalProps
*/
/* eslint-enable jsdoc/valid-types */

/**
* @template P
* @typedef {string | import('react').ComponentType<P>|ReactElement} IconType
*/

/**
* @template P
* @typedef BaseProps
*
* @property {IconType<P>|null} icon The icon to render. Supported values are: Dashicons (specified as strings), functions, WPComponent instances and `null`.
* @property {number} [size] The size (width and height) of the icon.
*/

/**
* @template {{size?: number}} P
* @param {BaseProps<P> & AdditionalProps<IconType<P>>} props
* @return {JSX.Element|null} Element
*/
function Icon( { icon = null, size, ...additionalProps } ) {
// Dashicons should be 20x20 by default.
const dashiconSize = size || 20;
Expand All @@ -28,8 +53,9 @@ function Icon( { icon = null, size, ...additionalProps } ) {
);
}

if ( icon && Dashicon === icon.type ) {
return cloneElement( icon, {
// Type Assertion: We know `icon` is defined and we can check the `.type` property.
if ( icon && Dashicon === /** @type {ReactElement} */ ( icon ).type ) {
return cloneElement( /** @type {ReactElement} */ ( icon ), {
size: dashiconSize,
...additionalProps,
} );
Expand All @@ -39,13 +65,21 @@ function Icon( { icon = null, size, ...additionalProps } ) {
const iconSize = size || 24;
if ( 'function' === typeof icon ) {
if ( icon.prototype instanceof Component ) {
return createElement( icon, {
size: iconSize,
...additionalProps,
} );
return createElement(
icon,
/* eslint-disable jsdoc/no-undefined-types */
/** @type {P} */ ( {
size: iconSize,
...additionalProps,
} )
/* eslint-enable jsdoc/no-undefined-types */
);
}

return icon( { size: iconSize, ...additionalProps } );
return /** @type {import('react').FunctionComponent} */ ( icon )( {
size: iconSize,
...additionalProps,
} );
}

if ( icon && ( icon.type === 'svg' || icon.type === SVG ) ) {
Expand All @@ -70,3 +104,5 @@ function Icon( { icon = null, size, ...additionalProps } ) {
}

export default Icon;

/** @typedef {import('react').ReactElement} ReactElement */
1 change: 1 addition & 0 deletions packages/components/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"include": [
"src/base-control/**/*",
"src/dashicon/**/*",
"src/icon/**/*",
"src/utils/**/*",
"src/visually-hidden/**/*"
],
Expand Down

0 comments on commit c091772

Please sign in to comment.