Migrate @woocommerce/notice to TS

This commit is contained in:
Chi-Hsuan Huang 2022-05-20 13:09:25 +08:00
parent 2885a0ae86
commit 286358d724
11 changed files with 122 additions and 50 deletions

View File

@ -20,6 +20,7 @@
}, },
"main": "build/index.js", "main": "build/index.js",
"module": "build-module/index.js", "module": "build-module/index.js",
"types": "build-types",
"react-native": "src/index", "react-native": "src/index",
"dependencies": { "dependencies": {
"@wordpress/a11y": "^3.5.0", "@wordpress/a11y": "^3.5.0",
@ -45,11 +46,14 @@
"lint:fix": "eslint src --fix" "lint:fix": "eslint src --fix"
}, },
"devDependencies": { "devDependencies": {
"@automattic/data-stores": "^2.0.1",
"@babel/core": "^7.17.5", "@babel/core": "^7.17.5",
"@woocommerce/eslint-plugin": "workspace:*", "@woocommerce/eslint-plugin": "workspace:*",
"@woocommerce/data": "workspace:*",
"eslint": "^8.12.0", "eslint": "^8.12.0",
"jest": "^27.5.1", "jest": "^27.5.1",
"jest-cli": "^27.5.1", "jest-cli": "^27.5.1",
"redux": "^4.2.0",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"ts-jest": "^27.1.3", "ts-jest": "^27.1.3",
"typescript": "^4.6.2" "typescript": "^4.6.2"

View File

@ -2,22 +2,25 @@
* External dependencies * External dependencies
*/ */
import { uniqueId } from 'lodash'; import { uniqueId } from 'lodash';
import { Status, Action as WPNoticeAction } from '@wordpress/notices';
/** /**
* Internal dependencies * Internal dependencies
*/ */
import { DEFAULT_CONTEXT, DEFAULT_STATUS } from './constants'; import { DEFAULT_CONTEXT, DEFAULT_STATUS } from './constants';
/** export type Options = {
* @typedef {Object} WPNoticeAction Object describing a user action option associated with a notice. id: string;
* context: string;
* @property {string} label Message to use as action label. isDismissible: boolean;
* @property {?string} url Optional URL of resource if action incurs type: string;
* browser navigation. speak: boolean;
* @property {?Function} onClick Optional function to invoke when action is actions: Array< WPNoticeAction >;
* triggered by user. icon: null | JSX.Element;
* explicitDismiss: boolean;
*/ onDismiss: ( () => void ) | null;
__unstableHTML?: boolean;
};
/** /**
* Returns an action object used in signalling that a notice is to be created. * Returns an action object used in signalling that a notice is to be created.
@ -46,30 +49,43 @@ import { DEFAULT_CONTEXT, DEFAULT_STATUS } from './constants';
* can't be dismissed by clicking * can't be dismissed by clicking
* the body of the notice. * the body of the notice.
* @param {Function} [options.onDismiss] Called when the notice is dismissed. * @param {Function} [options.onDismiss] Called when the notice is dismissed.
* @param {boolean} [options.__unstableHTML] Notice message as raw HTML.
* *
* @return {Object} Action object. * @return {Object} WPNoticeAction object.
*/ */
export function createNotice( status = DEFAULT_STATUS, content, options = {} ) { export function createNotice(
const { status: Status = DEFAULT_STATUS,
speak = true, content: string,
isDismissible = true, {
context = DEFAULT_CONTEXT, speak,
id = uniqueId( context ), isDismissible,
actions = [], context,
type = 'default', id,
actions,
type,
__unstableHTML, __unstableHTML,
icon = null, icon,
explicitDismiss = false, explicitDismiss,
onDismiss = null, onDismiss,
} = options; }: Options = {
speak: true,
isDismissible: true,
context: DEFAULT_CONTEXT,
id: uniqueId( DEFAULT_CONTEXT ),
actions: [],
type: 'default',
icon: null,
explicitDismiss: false,
onDismiss: null,
}
) {
// The supported value shape of content is currently limited to plain text // The supported value shape of content is currently limited to plain text
// strings. To avoid setting expectation that e.g. a WPElement could be // strings. To avoid setting expectation that e.g. a WPElement could be
// supported, cast to a string. // supported, cast to a string.
content = String( content ); content = String( content );
return { return {
type: 'CREATE_NOTICE', type: 'CREATE_NOTICE' as const,
context, context,
notice: { notice: {
id, id,
@ -98,7 +114,7 @@ export function createNotice( status = DEFAULT_STATUS, content, options = {} ) {
* *
* @return {Object} Action object. * @return {Object} Action object.
*/ */
export function createSuccessNotice( content, options ) { export function createSuccessNotice( content: string, options: Options ) {
return createNotice( 'success', content, options ); return createNotice( 'success', content, options );
} }
@ -113,7 +129,7 @@ export function createSuccessNotice( content, options ) {
* *
* @return {Object} Action object. * @return {Object} Action object.
*/ */
export function createInfoNotice( content, options ) { export function createInfoNotice( content: string, options: Options ) {
return createNotice( 'info', content, options ); return createNotice( 'info', content, options );
} }
@ -128,7 +144,7 @@ export function createInfoNotice( content, options ) {
* *
* @return {Object} Action object. * @return {Object} Action object.
*/ */
export function createErrorNotice( content, options ) { export function createErrorNotice( content: string, options: Options ) {
return createNotice( 'error', content, options ); return createNotice( 'error', content, options );
} }
@ -143,7 +159,7 @@ export function createErrorNotice( content, options ) {
* *
* @return {Object} Action object. * @return {Object} Action object.
*/ */
export function createWarningNotice( content, options ) { export function createWarningNotice( content: string, options: Options ) {
return createNotice( 'warning', content, options ); return createNotice( 'warning', content, options );
} }
@ -156,10 +172,12 @@ export function createWarningNotice( content, options ) {
* *
* @return {Object} Action object. * @return {Object} Action object.
*/ */
export function removeNotice( id, context = DEFAULT_CONTEXT ) { export function removeNotice( id: string, context: string = DEFAULT_CONTEXT ) {
return { return {
type: 'REMOVE_NOTICE', type: 'REMOVE_NOTICE' as const,
id, id,
context, context,
}; };
} }
export type Action = ReturnType< typeof createNotice | typeof removeNotice >;

View File

@ -12,4 +12,4 @@ export const DEFAULT_CONTEXT = 'global';
* *
* @type {string} * @type {string}
*/ */
export const DEFAULT_STATUS = 'info'; export const DEFAULT_STATUS = 'info' as const;

View File

@ -3,8 +3,13 @@
*/ */
import { speak } from '@wordpress/a11y'; import { speak } from '@wordpress/a11y';
export type Action = {
message: string;
ariaLive?: string;
};
export default { export default {
SPEAK( action ) { SPEAK( action: Action ) {
speak( action.message, action.ariaLive || 'assertive' ); speak( action.message, action.ariaLive || 'assertive' );
}, },
}; };

View File

@ -2,6 +2,8 @@
* External dependencies * External dependencies
*/ */
import { registerStore } from '@wordpress/data'; import { registerStore } from '@wordpress/data';
import { SelectFromMap, DispatchFromMap } from '@automattic/data-stores';
import { Reducer, AnyAction } from 'redux';
/** /**
* Internal dependencies * Internal dependencies
@ -9,11 +11,24 @@ import { registerStore } from '@wordpress/data';
import reducer from './reducer'; import reducer from './reducer';
import * as actions from './actions'; import * as actions from './actions';
import * as selectors from './selectors'; import * as selectors from './selectors';
import { State } from './types';
export * from './types';
export const STORE_NAME = 'core/notices2';
// NOTE: This uses core/notices2, if this file is copied back upstream // NOTE: This uses core/notices2, if this file is copied back upstream
// to Gutenberg this needs to be changed back to core/notices. // to Gutenberg this needs to be changed back to core/notices.
export default registerStore( 'core/notices2', { export default registerStore< State >( STORE_NAME, {
reducer, reducer: reducer as Reducer< State, AnyAction >,
actions, actions,
selectors, selectors,
} ); } );
declare module '@wordpress/data' {
// TODO: convert action.js to TS
function dispatch(
key: typeof STORE_NAME
): DispatchFromMap< typeof actions >;
function select(
key: typeof STORE_NAME
): SelectFromMap< typeof selectors >;
}

View File

@ -2,22 +2,24 @@
* External dependencies * External dependencies
*/ */
import { reject } from 'lodash'; import { reject } from 'lodash';
import type { Reducer } from 'redux';
/** /**
* Internal dependencies * Internal dependencies
*/ */
import onSubKey from './utils/on-sub-key'; import onSubKey from './utils/on-sub-key';
import { Action } from './actions';
import { Notices } from './types';
/** /**
* Reducer returning the next notices state. The notices state is an object * Reducer returning the next notices state. The notices state is an array of notice objects
* where each key is a context, its value an array of notice objects.
* *
* @param {Object} state Current state. * @param {Array} state Current state.
* @param {Object} action Dispatched action. * @param {Object} action Dispatched action.
* *
* @return {Object} Updated state. * @return {Object} Updated state.
*/ */
const notices = onSubKey( 'context' )( ( state = [], action ) => { const notices: Reducer< Notices, Action > = ( state = [], action ) => {
switch ( action.type ) { switch ( action.type ) {
case 'CREATE_NOTICE': case 'CREATE_NOTICE':
// Avoid duplicates on ID. // Avoid duplicates on ID.
@ -29,8 +31,12 @@ const notices = onSubKey( 'context' )( ( state = [], action ) => {
case 'REMOVE_NOTICE': case 'REMOVE_NOTICE':
return reject( state, { id: action.id } ); return reject( state, { id: action.id } );
} }
return state; return state;
} ); };
export default notices; export type State = {
[ context: string ]: Notices;
};
// Creates a combined reducer object where each key is a context, its value an array of notice objects.
export default onSubKey( 'context' )( notices );

View File

@ -2,6 +2,7 @@
* Internal dependencies * Internal dependencies
*/ */
import { DEFAULT_CONTEXT } from './constants'; import { DEFAULT_CONTEXT } from './constants';
import { State } from './reducer';
/** @typedef {import('./actions').WPNoticeAction} WPNoticeAction */ /** @typedef {import('./actions').WPNoticeAction} WPNoticeAction */
@ -14,7 +15,7 @@ import { DEFAULT_CONTEXT } from './constants';
* *
* @type {Array} * @type {Array}
*/ */
const DEFAULT_NOTICES = []; const DEFAULT_NOTICES: [ ] = [];
/** /**
* @typedef {Object} WPNotice Notice object. * @typedef {Object} WPNotice Notice object.
@ -51,6 +52,6 @@ const DEFAULT_NOTICES = [];
* *
* @return {WPNotice[]} Array of notices. * @return {WPNotice[]} Array of notices.
*/ */
export function getNotices( state, context = DEFAULT_CONTEXT ) { export function getNotices( state: State, context: string = DEFAULT_CONTEXT ) {
return state[ context ] || DEFAULT_NOTICES; return state[ context ] || DEFAULT_NOTICES;
} }

View File

@ -0,0 +1,10 @@
/**
* Internal dependencies
*/
import { createNotice } from './actions';
export type Notices = Array< ReturnType< typeof createNotice >[ 'notice' ] >;
export type State = {
[ context: string ]: Notices;
};

View File

@ -1,3 +1,14 @@
/**
* External dependencies
*/
import type { Reducer } from 'redux';
/**
* Internal dependencies
*/
import { State, Notices } from '../types';
import { Action } from '../actions';
/** /**
* Higher-order reducer creator which creates a combined reducer object, keyed * Higher-order reducer creator which creates a combined reducer object, keyed
* by a property on the action object. * by a property on the action object.
@ -6,10 +17,9 @@
* *
* @return {Function} Higher-order reducer. * @return {Function} Higher-order reducer.
*/ */
export const onSubKey = ( actionProperty ) => ( reducer ) => ( export const onSubKey = ( actionProperty: keyof Action ) => (
state = {}, reducer: Reducer< Notices, Action >
action ) => ( state: State = {}, action: Action ) => {
) => {
// Retrieve subkey from action. Do not track if undefined; useful for cases // Retrieve subkey from action. Do not track if undefined; useful for cases
// where reducer is scoped by action shape. // where reducer is scoped by action shape.
const key = action[ actionProperty ]; const key = action[ actionProperty ];

View File

@ -2,6 +2,9 @@
"extends": "../tsconfig", "extends": "../tsconfig",
"compilerOptions": { "compilerOptions": {
"rootDir": "src", "rootDir": "src",
"outDir": "build-module" "outDir": "build-module",
"declaration": true,
"declarationMap": true,
"declarationDir": "./build-types"
} }
} }