From ef2c1bd5d18853085d38f45e4c2c8600d7252972 Mon Sep 17 00:00:00 2001 From: Lena Morita Date: Tue, 14 Mar 2023 22:49:46 +0900 Subject: [PATCH 1/4] withNotices: Convert to TypeScript --- .../with-notices/{index.js => index.tsx} | 54 ++++++++++++++----- packages/components/tsconfig.json | 3 +- 2 files changed, 43 insertions(+), 14 deletions(-) rename packages/components/src/higher-order/with-notices/{index.js => index.tsx} (56%) diff --git a/packages/components/src/higher-order/with-notices/index.js b/packages/components/src/higher-order/with-notices/index.tsx similarity index 56% rename from packages/components/src/higher-order/with-notices/index.js rename to packages/components/src/higher-order/with-notices/index.tsx index c4057e6b6fff3e..5a09d067f6f548 100644 --- a/packages/components/src/higher-order/with-notices/index.js +++ b/packages/components/src/higher-order/with-notices/index.tsx @@ -13,25 +13,53 @@ import { createHigherOrderComponent } from '@wordpress/compose'; * Internal dependencies */ import NoticeList from '../../notice/list'; +import type { NoticeListProps } from '../../notice/types'; /** * Override the default edit UI to include notices if supported. * - * @param {WPComponent} OriginalComponent Original component. + * Wrapping the original component with `withNotices` encapsulates the component + * with the additional props `noticeOperations` and `noticeUI`. * - * @return {WPComponent} Wrapped component. + * ```jsx + * import { withNotices, Button } from '@wordpress/components'; + * + * const MyComponentWithNotices = withNotices( + * ( { noticeOperations, noticeUI } ) => { + * const addError = () => + * noticeOperations.createErrorNotice( 'Error message' ); + * return ( + *
+ * { noticeUI } + * + *
+ * ); + * } + * ); + * ``` + * + * @param OriginalComponent Original component. + * + * @return Wrapped component. */ export default createHigherOrderComponent( ( OriginalComponent ) => { - function Component( props, ref ) { - const [ noticeList, setNoticeList ] = useState( [] ); + function Component( + props: { [ key: string ]: any }, + ref: React.ForwardedRef< any > + ) { + const [ noticeList, setNoticeList ] = useState< + NoticeListProps[ 'notices' ] + >( [] ); const noticeOperations = useMemo( () => { /** * Function passed down as a prop that adds a new notice. * - * @param {Object} notice Notice to add. + * @param notice Notice to add. */ - const createNotice = ( notice ) => { + const createNotice = ( notice: typeof noticeList[ number ] ) => { const noticeToAdd = notice.id ? notice : { ...notice, id: uuid() }; @@ -44,9 +72,10 @@ export default createHigherOrderComponent( ( OriginalComponent ) => { /** * Function passed as a prop that adds a new error notice. * - * @param {string} msg Error message of the notice. + * @param msg Error message of the notice. */ - createErrorNotice: ( msg ) => { + createErrorNotice: ( msg: string ) => { + // @ts-expect-error TODO: Missing `id`, potentially a bug createNotice( { status: 'error', content: msg, @@ -56,9 +85,9 @@ export default createHigherOrderComponent( ( OriginalComponent ) => { /** * Removes a notice by id. * - * @param {string} id Id of the notice to remove. + * @param id Id of the notice to remove. */ - removeNotice: ( id ) => { + removeNotice: ( id: string ) => { setNoticeList( ( current ) => current.filter( ( notice ) => notice.id !== id ) ); @@ -93,7 +122,8 @@ export default createHigherOrderComponent( ( OriginalComponent ) => { ); } - let isForwardRef; + let isForwardRef: boolean; + // @ts-expect-error - `render` will only be present when OriginalComponent was wrapped with forwardRef(). const { render } = OriginalComponent; // Returns a forwardRef if OriginalComponent appears to be a forwardRef. if ( typeof render === 'function' ) { @@ -101,4 +131,4 @@ export default createHigherOrderComponent( ( OriginalComponent ) => { return forwardRef( Component ); } return Component; -} ); +}, 'withNotices' ); diff --git a/packages/components/tsconfig.json b/packages/components/tsconfig.json index aa2434d026ca17..6aaadae65b49cc 100644 --- a/packages/components/tsconfig.json +++ b/packages/components/tsconfig.json @@ -45,7 +45,6 @@ "src/**/stories/**/*.js", // only exclude js files, tsx files should be checked "src/**/test/**/*.js", // only exclude js files, ts{x} files should be checked "src/index.js", - "src/duotone-picker", - "src/higher-order/with-notices" + "src/duotone-picker" ] } From e894f4a8c8adf3991311036a993d1b824ce35aee Mon Sep 17 00:00:00 2001 From: Lena Morita Date: Wed, 15 Mar 2023 16:14:40 +0900 Subject: [PATCH 2/4] Make type exportable --- .../src/higher-order/with-notices/index.tsx | 46 ++++++------------- .../with-notices/test/{index.js => index.tsx} | 17 ++++--- .../src/higher-order/with-notices/types.ts | 35 ++++++++++++++ 3 files changed, 60 insertions(+), 38 deletions(-) rename packages/components/src/higher-order/with-notices/test/{index.js => index.tsx} (91%) create mode 100644 packages/components/src/higher-order/with-notices/types.ts diff --git a/packages/components/src/higher-order/with-notices/index.tsx b/packages/components/src/higher-order/with-notices/index.tsx index 5a09d067f6f548..734fd8b2b1056c 100644 --- a/packages/components/src/higher-order/with-notices/index.tsx +++ b/packages/components/src/higher-order/with-notices/index.tsx @@ -13,7 +13,7 @@ import { createHigherOrderComponent } from '@wordpress/compose'; * Internal dependencies */ import NoticeList from '../../notice/list'; -import type { NoticeListProps } from '../../notice/types'; +import type { WithNoticeProps } from './types'; /** * Override the default edit UI to include notices if supported. @@ -50,52 +50,34 @@ export default createHigherOrderComponent( ( OriginalComponent ) => { ref: React.ForwardedRef< any > ) { const [ noticeList, setNoticeList ] = useState< - NoticeListProps[ 'notices' ] + WithNoticeProps[ 'noticeList' ] >( [] ); - const noticeOperations = useMemo( () => { - /** - * Function passed down as a prop that adds a new notice. - * - * @param notice Notice to add. - */ - const createNotice = ( notice: typeof noticeList[ number ] ) => { - const noticeToAdd = notice.id - ? notice - : { ...notice, id: uuid() }; - setNoticeList( ( current ) => [ ...current, noticeToAdd ] ); - }; + const noticeOperations = useMemo< + WithNoticeProps[ 'noticeOperations' ] + >( () => { + const createNotice: WithNoticeProps[ 'noticeOperations' ][ 'createNotice' ] = + ( notice ) => { + const noticeToAdd = notice.id + ? notice + : { ...notice, id: uuid() }; + setNoticeList( ( current ) => [ ...current, noticeToAdd ] ); + }; return { createNotice, - - /** - * Function passed as a prop that adds a new error notice. - * - * @param msg Error message of the notice. - */ - createErrorNotice: ( msg: string ) => { + createErrorNotice: ( msg ) => { // @ts-expect-error TODO: Missing `id`, potentially a bug createNotice( { status: 'error', content: msg, } ); }, - - /** - * Removes a notice by id. - * - * @param id Id of the notice to remove. - */ - removeNotice: ( id: string ) => { + removeNotice: ( id ) => { setNoticeList( ( current ) => current.filter( ( notice ) => notice.id !== id ) ); }, - - /** - * Removes all notices - */ removeAllNotices: () => { setNoticeList( [] ); }, diff --git a/packages/components/src/higher-order/with-notices/test/index.js b/packages/components/src/higher-order/with-notices/test/index.tsx similarity index 91% rename from packages/components/src/higher-order/with-notices/test/index.js rename to packages/components/src/higher-order/with-notices/test/index.tsx index a403f3c3a80942..cbad7270414e3e 100644 --- a/packages/components/src/higher-order/with-notices/test/index.js +++ b/packages/components/src/higher-order/with-notices/test/index.tsx @@ -24,25 +24,30 @@ import { * Internal dependencies */ import withNotices from '..'; +import type { WithNoticeProps } from '../types'; // Implementation detail of Notice component used to query the dismissal button. const stockDismissText = 'Dismiss this notice'; -function noticesFrom( list ) { +function noticesFrom( list: string[] ) { return list.map( ( item ) => ( { id: item, content: item } ) ); } -function isComponentLike( object ) { +function isComponentLike( object: any ) { return typeof object === 'function'; } -function isForwardRefLike( { render: renderMethod } ) { +function isForwardRefLike( { render: renderMethod }: any ) { return typeof renderMethod === 'function'; } const content = 'Base content'; -const BaseComponent = ( { noticeOperations, noticeUI, notifications } ) => { +const BaseComponent = ( { + noticeOperations, + noticeUI, + notifications, +}: WithNoticeProps & { notifications: ReturnType< typeof noticesFrom > } ) => { useEffect( () => { if ( notifications ) { notifications.forEach( ( item ) => @@ -78,8 +83,8 @@ describe( 'withNotices return type', () => { } ); describe( 'withNotices operations', () => { - let handle; - const Handle = ( props ) => { + let handle: React.MutableRefObject< any >; + const Handle = ( props: any ) => { handle = useRef(); return ; }; diff --git a/packages/components/src/higher-order/with-notices/types.ts b/packages/components/src/higher-order/with-notices/types.ts new file mode 100644 index 00000000000000..e914d21d82df7d --- /dev/null +++ b/packages/components/src/higher-order/with-notices/types.ts @@ -0,0 +1,35 @@ +/** + * Internal dependencies + */ +import type { NoticeListProps } from '../../notice/types'; + +export type WithNoticeProps = { + noticeList: NoticeListProps[ 'notices' ]; + noticeOperations: { + /** + * Function passed down as a prop that adds a new notice. + * + * @param notice Notice to add. + */ + createNotice: ( + notice: NoticeListProps[ 'notices' ][ number ] + ) => void; + /** + * Function passed as a prop that adds a new error notice. + * + * @param msg Error message of the notice. + */ + createErrorNotice: ( msg: string ) => void; + /** + * Removes a notice by id. + * + * @param id Id of the notice to remove. + */ + removeNotice: ( id: string ) => void; + /** + * Removes all notices + */ + removeAllNotices: () => void; + }; + noticeUI: false | JSX.Element; +}; From a6dac33a1f10d045e31bd139b570ea29af3c04be Mon Sep 17 00:00:00 2001 From: Lena Morita Date: Wed, 15 Mar 2023 17:32:45 +0900 Subject: [PATCH 3/4] Add changelog --- packages/components/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index 02b83fadfcfc20..51f1a54b440c83 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -22,6 +22,7 @@ - `withFocusReturn` HOC: Convert to TypeScript ([#48748](https://github.com/WordPress/gutenberg/pull/48748)). - `navigateRegions` HOC: Convert to TypeScript ([#48632](https://github.com/WordPress/gutenberg/pull/48632)). - `withSpokenMessages`: HOC: Convert to TypeScript ([#48163](https://github.com/WordPress/gutenberg/pull/48163)). +- `withNotices`: HOC: Convert to TypeScript ([#49088](https://github.com/WordPress/gutenberg/pull/49088)). - `ToolbarButton`: Convert to TypeScript ([#47750](https://github.com/WordPress/gutenberg/pull/47750)). - `DimensionControl(Experimental)`: Convert to TypeScript ([#47351](https://github.com/WordPress/gutenberg/pull/47351)). - `PaletteEdit`: Convert to TypeScript ([#47764](https://github.com/WordPress/gutenberg/pull/47764)). From 0483ac2dcc2d6291441edeae7bf9a72fc68acf61 Mon Sep 17 00:00:00 2001 From: Lena Morita Date: Wed, 15 Mar 2023 21:52:01 +0900 Subject: [PATCH 4/4] Add `noticeList` to readme --- packages/components/src/higher-order/with-notices/README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/components/src/higher-order/with-notices/README.md b/packages/components/src/higher-order/with-notices/README.md index 84c484eba101b2..17e1b0e12113b9 100644 --- a/packages/components/src/higher-order/with-notices/README.md +++ b/packages/components/src/higher-order/with-notices/README.md @@ -2,7 +2,7 @@ `withNotices` is a React [higher-order component](https://facebook.github.io/react/docs/higher-order-components.html) used typically in adding the ability to post notice messages within the original component. -Wrapping the original component with `withNotices` encapsulates the component with the additional props `noticeOperations` and `noticeUI`. +Wrapping the original component with `withNotices` encapsulates the component with the additional props `noticeOperations`, `noticeUI`, and `noticeList`. **noticeOperations** Contains a number of useful functions to add notices to your site. @@ -34,6 +34,9 @@ _Parameters_ #**noticeUi** The rendered `NoticeList`. +#**noticeList** +The array of notice objects to be displayed. + ## Usage ```jsx