From c865d21e670be03898ecc2a7d45b4e7ec725ff6a Mon Sep 17 00:00:00 2001 From: Ryan Kienstra Date: Fri, 22 Jul 2022 16:28:49 -0500 Subject: [PATCH 1/2] Convert Snackbar component to traditional TypeScript Though there were good JSDoc types already. --- .../src/snackbar/{index.js => index.tsx} | 37 +++++++++++-------- .../src/snackbar/{list.js => list.tsx} | 32 +++++++--------- packages/components/src/snackbar/types.ts | 34 +++++++++++++++++ packages/components/tsconfig.json | 2 + 4 files changed, 72 insertions(+), 33 deletions(-) rename packages/components/src/snackbar/{index.js => index.tsx} (82%) rename packages/components/src/snackbar/{list.js => list.tsx} (70%) create mode 100644 packages/components/src/snackbar/types.ts diff --git a/packages/components/src/snackbar/index.js b/packages/components/src/snackbar/index.tsx similarity index 82% rename from packages/components/src/snackbar/index.js rename to packages/components/src/snackbar/index.tsx index 05afc83a944d21..0835abcc2d95a7 100644 --- a/packages/components/src/snackbar/index.js +++ b/packages/components/src/snackbar/index.tsx @@ -1,6 +1,7 @@ /** * External dependencies */ +import type { ForwardedRef, MouseEvent, KeyboardEvent } from 'react'; import classnames from 'classnames'; /** @@ -8,27 +9,27 @@ import classnames from 'classnames'; */ import { speak } from '@wordpress/a11y'; import { useEffect, forwardRef, renderToString } from '@wordpress/element'; +import type { WPElement } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; import warning from '@wordpress/warning'; /** * Internal dependencies */ -import { Button } from '../'; +import { Button } from '../button'; +import type { SnackbarProps } from './types'; const noop = () => {}; const NOTICE_TIMEOUT = 10000; -/** @typedef {import('@wordpress/element').WPElement} WPElement */ - /** * Custom hook which announces the message with the given politeness, if a * valid message is provided. - * - * @param {string|WPElement} [message] Message to announce. - * @param {'polite'|'assertive'} politeness Politeness to announce. */ -function useSpokenMessage( message, politeness ) { +function useSpokenMessage( + message: string | WPElement, + politeness: 'polite' | 'assertive' +) { const spokenMessage = typeof message === 'string' ? message : renderToString( message ); @@ -54,24 +55,30 @@ function Snackbar( // actually the function to call to remove the snackbar from the UI. onDismiss = noop, listRef, - }, - ref + }: SnackbarProps, + ref: ForwardedRef< any > ) { onDismiss = onDismiss || noop; - function dismissMe( event ) { + function dismissMe( + event: + | KeyboardEvent< HTMLDivElement | HTMLSpanElement > + | MouseEvent< HTMLDivElement | HTMLSpanElement > + ) { if ( event && event.preventDefault ) { event.preventDefault(); } // Prevent focus loss by moving it to the list element. - listRef.current.focus(); + if ( listRef?.current ) { + ( listRef.current as HTMLDivElement )?.focus(); + } onDismiss(); onRemove(); } - function onActionClick( event, onClick ) { + function onActionClick( event: Event, onClick: ( event: Event ) => {} ) { event.stopPropagation(); onRemove(); @@ -119,7 +126,7 @@ function Snackbar( ref={ ref } className={ classes } onClick={ ! explicitDismiss ? dismissMe : noop } - tabIndex="0" + tabIndex={ 0 } role={ ! explicitDismiss ? 'button' : '' } onKeyPress={ ! explicitDismiss ? dismissMe : noop } aria-label={ ! explicitDismiss ? __( 'Dismiss this notice' ) : '' } @@ -135,7 +142,7 @@ function Snackbar( key={ index } href={ url } variant="tertiary" - onClick={ ( event ) => + onClick={ ( event: Event ) => onActionClick( event, onClick ) } className="components-snackbar__action" @@ -148,7 +155,7 @@ function Snackbar( {}; const SNACKBAR_VARIANTS = { @@ -41,27 +43,21 @@ const SNACKBAR_VARIANTS = { }; const SNACKBAR_REDUCE_MOTION_VARIANTS = { - init: false, - open: false, - exit: false, + init: {}, + open: {}, + exit: {}, }; -/** - * Renders a list of notices. - * - * @param {Object} $0 Props passed to the component. - * @param {Array} $0.notices Array of notices to render. - * @param {Function} $0.onRemove Function called when a notice should be removed / dismissed. - * @param {Object} $0.className Name of the class used by the component. - * @param {Object} $0.children Array of children to be rendered inside the notice list. - * - * @return {Object} The rendered notices list. - */ -function SnackbarList( { notices, className, children, onRemove = noop } ) { - const listRef = useRef(); +function SnackbarList( { + notices, + className, + children, + onRemove = noop, +}: SnackbarListProps ) { + const listRef = useRef() as MutableRefObject< HTMLDivElement >; const isReducedMotion = useReducedMotion(); className = classnames( 'components-snackbar-list', className ); - const removeNotice = ( notice ) => () => onRemove( notice.id ); + const removeNotice = ( notice: Notice ) => () => onRemove( notice.id ); return (
{ children } diff --git a/packages/components/src/snackbar/types.ts b/packages/components/src/snackbar/types.ts new file mode 100644 index 00000000000000..92e8199ba1282e --- /dev/null +++ b/packages/components/src/snackbar/types.ts @@ -0,0 +1,34 @@ +/** + * External dependencies + */ +import type { MutableRefObject } from 'react'; + +/** + * WordPress dependencies + */ +import type { WPElement } from '@wordpress/element'; + +export type SnackbarProps = { + className?: string; + children: string; + spokenMessage?: string; + politeness?: 'polite' | 'assertive'; + actions?: Array< Record< string, any > >; + onRemove?: Function; + icon?: WPElement | null; + explicitDismiss?: boolean; + onDismiss?: Function; + listRef?: MutableRefObject< HTMLDivElement >; +}; + +export type Notice = { + id: string | number; + content: string; +}; + +export type SnackbarListProps = { + notices: Array< Notice >; + onRemove: Function; + className: string; + children: Array< string >; +}; diff --git a/packages/components/tsconfig.json b/packages/components/tsconfig.json index cc5f7e3be2634b..3187510d805485 100644 --- a/packages/components/tsconfig.json +++ b/packages/components/tsconfig.json @@ -33,6 +33,7 @@ "include": [ "src/__next/**/*", "src/animate/**/*", + "src/animation/**/*", "src/base-control/**/*", "src/base-field/**/*", "src/border-box-control/**/*", @@ -81,6 +82,7 @@ "src/select-control/**/*", "src/shortcut/**/*", "src/slot-fill/**/*", + "src/snackbar/**/*", "src/style-provider/**/*", "src/spacer/**/*", "src/spinner/**/*", From 1f1f97d60a387432a83f57dc73cf570e31825634 Mon Sep 17 00:00:00 2001 From: Ryan Kienstra Date: Fri, 22 Jul 2022 16:54:50 -0500 Subject: [PATCH 2/2] Add a Notice interface, mainly copied from notices//store/actions.js --- packages/components/src/snackbar/index.tsx | 6 ++--- packages/components/src/snackbar/types.ts | 27 +++++++++++++++++----- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/packages/components/src/snackbar/index.tsx b/packages/components/src/snackbar/index.tsx index 0835abcc2d95a7..2a6b3214542a49 100644 --- a/packages/components/src/snackbar/index.tsx +++ b/packages/components/src/snackbar/index.tsx @@ -70,15 +70,13 @@ function Snackbar( } // Prevent focus loss by moving it to the list element. - if ( listRef?.current ) { - ( listRef.current as HTMLDivElement )?.focus(); - } + listRef?.current?.focus(); onDismiss(); onRemove(); } - function onActionClick( event: Event, onClick: ( event: Event ) => {} ) { + function onActionClick( event: Event, onClick?: ( event: Event ) => {} ) { event.stopPropagation(); onRemove(); diff --git a/packages/components/src/snackbar/types.ts b/packages/components/src/snackbar/types.ts index 92e8199ba1282e..bd69a088cbe114 100644 --- a/packages/components/src/snackbar/types.ts +++ b/packages/components/src/snackbar/types.ts @@ -8,12 +8,18 @@ import type { MutableRefObject } from 'react'; */ import type { WPElement } from '@wordpress/element'; +export type Action = { + label: string; + url: string; + onClick?: ( event: Event ) => {}; +}; + export type SnackbarProps = { className?: string; children: string; - spokenMessage?: string; + spokenMessage?: string | WPElement; politeness?: 'polite' | 'assertive'; - actions?: Array< Record< string, any > >; + actions?: Array< Action >; onRemove?: Function; icon?: WPElement | null; explicitDismiss?: boolean; @@ -22,13 +28,22 @@ export type SnackbarProps = { }; export type Notice = { - id: string | number; + id: string; + status: string; content: string; + spokenMessage: string | WPElement; + __unstableHTML?: string; + isDismissible: boolean; + actions: Array< Action >; + type: string; + icon?: WPElement; + explicitDismiss: boolean; + onDismiss: Function; }; export type SnackbarListProps = { notices: Array< Notice >; - onRemove: Function; - className: string; - children: Array< string >; + children?: Array< string >; + onRemove?: Function; + className?: string; };