Merge pull request #33134 from woocommerce/dev/33098-migrate-woo-notice-to-ts

Migrate `@woocommerce/notices` to TS
This commit is contained in:
Chi-Hsuan Huang 2022-05-25 13:20:24 +08:00 committed by GitHub
commit 63bb681c14
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 132 additions and 56 deletions

View File

@ -0,0 +1,4 @@
Significance: patch
Type: dev
Migrate @woocommerce/notices to TS

View File

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

View File

@ -2,22 +2,25 @@
* External dependencies
*/
import { uniqueId } from 'lodash';
import { Status, Action as WPNoticeAction } from '@wordpress/notices';
/**
* Internal dependencies
*/
import { DEFAULT_CONTEXT, DEFAULT_STATUS } from './constants';
/**
* @typedef {Object} WPNoticeAction Object describing a user action option associated with a notice.
*
* @property {string} label Message to use as action label.
* @property {?string} url Optional URL of resource if action incurs
* browser navigation.
* @property {?Function} onClick Optional function to invoke when action is
* triggered by user.
*
*/
export type Options = {
id: string;
context: string;
isDismissible: boolean;
type: string;
speak: boolean;
actions: Array< WPNoticeAction >;
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.
@ -46,10 +49,15 @@ import { DEFAULT_CONTEXT, DEFAULT_STATUS } from './constants';
* can't be dismissed by clicking
* the body of the notice.
* @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(
status: Status = DEFAULT_STATUS,
content: string,
options: Partial< Options > = {}
) {
const {
speak = true,
isDismissible = true,
@ -62,14 +70,13 @@ export function createNotice( status = DEFAULT_STATUS, content, options = {} ) {
explicitDismiss = false,
onDismiss = null,
} = options;
// The supported value shape of content is currently limited to plain text
// strings. To avoid setting expectation that e.g. a WPElement could be
// supported, cast to a string.
content = String( content );
return {
type: 'CREATE_NOTICE',
type: 'CREATE_NOTICE' as const,
context,
notice: {
id,
@ -98,7 +105,7 @@ export function createNotice( status = DEFAULT_STATUS, content, options = {} ) {
*
* @return {Object} Action object.
*/
export function createSuccessNotice( content, options ) {
export function createSuccessNotice( content: string, options: Options ) {
return createNotice( 'success', content, options );
}
@ -113,7 +120,7 @@ export function createSuccessNotice( content, options ) {
*
* @return {Object} Action object.
*/
export function createInfoNotice( content, options ) {
export function createInfoNotice( content: string, options: Options ) {
return createNotice( 'info', content, options );
}
@ -128,7 +135,7 @@ export function createInfoNotice( content, options ) {
*
* @return {Object} Action object.
*/
export function createErrorNotice( content, options ) {
export function createErrorNotice( content: string, options: Options ) {
return createNotice( 'error', content, options );
}
@ -143,7 +150,7 @@ export function createErrorNotice( content, options ) {
*
* @return {Object} Action object.
*/
export function createWarningNotice( content, options ) {
export function createWarningNotice( content: string, options: Options ) {
return createNotice( 'warning', content, options );
}
@ -156,10 +163,12 @@ export function createWarningNotice( content, options ) {
*
* @return {Object} Action object.
*/
export function removeNotice( id, context = DEFAULT_CONTEXT ) {
export function removeNotice( id: string, context: string = DEFAULT_CONTEXT ) {
return {
type: 'REMOVE_NOTICE',
type: 'REMOVE_NOTICE' as const,
id,
context,
};
}
export type Action = ReturnType< typeof createNotice | typeof removeNotice >;

View File

@ -12,4 +12,4 @@ export const DEFAULT_CONTEXT = 'global';
*
* @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';
export type Action = {
message: string;
ariaLive?: string;
};
export default {
SPEAK( action ) {
SPEAK( action: Action ) {
speak( action.message, action.ariaLive || 'assertive' );
},
};

View File

@ -1,19 +0,0 @@
/**
* External dependencies
*/
import { registerStore } from '@wordpress/data';
/**
* Internal dependencies
*/
import reducer from './reducer';
import * as actions from './actions';
import * as selectors from './selectors';
// NOTE: This uses core/notices2, if this file is copied back upstream
// to Gutenberg this needs to be changed back to core/notices.
export default registerStore( 'core/notices2', {
reducer,
actions,
selectors,
} );

View File

@ -0,0 +1,34 @@
/**
* External dependencies
*/
import { registerStore } from '@wordpress/data';
import { SelectFromMap, DispatchFromMap } from '@automattic/data-stores';
import { Reducer, AnyAction } from 'redux';
/**
* Internal dependencies
*/
import reducer from './reducer';
import * as actions from './actions';
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
// to Gutenberg this needs to be changed back to core/notices.
export default registerStore< State >( STORE_NAME, {
reducer: reducer as Reducer< State, AnyAction >,
actions,
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
*/
import { reject } from 'lodash';
import type { Reducer } from 'redux';
/**
* Internal dependencies
*/
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
* where each key is a context, its value an array of notice objects.
* Reducer returning the next notices state. The notices state is an array of notice objects
*
* @param {Object} state Current state.
* @param {Array} state Current state.
* @param {Object} action Dispatched action.
*
* @return {Object} Updated state.
*/
const notices = onSubKey( 'context' )( ( state = [], action ) => {
const notices: Reducer< Notices, Action > = ( state = [], action ) => {
switch ( action.type ) {
case 'CREATE_NOTICE':
// Avoid duplicates on ID.
@ -29,8 +31,12 @@ const notices = onSubKey( 'context' )( ( state = [], action ) => {
case 'REMOVE_NOTICE':
return reject( state, { id: action.id } );
}
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
*/
import { DEFAULT_CONTEXT } from './constants';
import { State } from './reducer';
/** @typedef {import('./actions').WPNoticeAction} WPNoticeAction */
@ -14,7 +15,7 @@ import { DEFAULT_CONTEXT } from './constants';
*
* @type {Array}
*/
const DEFAULT_NOTICES = [];
const DEFAULT_NOTICES: [ ] = [];
/**
* @typedef {Object} WPNotice Notice object.
@ -51,6 +52,6 @@ const DEFAULT_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;
}

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
* by a property on the action object.
@ -6,10 +17,9 @@
*
* @return {Function} Higher-order reducer.
*/
export const onSubKey = ( actionProperty ) => ( reducer ) => (
state = {},
action
) => {
export const onSubKey = ( actionProperty: keyof Action ) => (
reducer: Reducer< Notices, Action >
) => ( state: State = {}, action: Action ) => {
// Retrieve subkey from action. Do not track if undefined; useful for cases
// where reducer is scoped by action shape.
const key = action[ actionProperty ];

View File

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

View File

@ -1011,6 +1011,7 @@ importers:
packages/js/notices:
specifiers:
'@automattic/data-stores': ^2.0.1
'@babel/core': ^7.17.5
'@woocommerce/eslint-plugin': workspace:*
'@wordpress/a11y': ^3.5.0
@ -1019,6 +1020,7 @@ importers:
eslint: ^8.12.0
jest: ^27.5.1
jest-cli: ^27.5.1
redux: ^4.2.0
rimraf: ^3.0.2
ts-jest: ^27.1.3
typescript: ^4.6.2
@ -1027,11 +1029,13 @@ importers:
'@wordpress/data': 6.4.1
'@wordpress/notices': 3.4.1
devDependencies:
'@automattic/data-stores': 2.0.1_@wordpress+data@6.4.1
'@babel/core': 7.17.8
'@woocommerce/eslint-plugin': link:../eslint-plugin
eslint: 8.12.0
jest: 27.5.1
jest-cli: 27.5.1
redux: 4.2.0
rimraf: 3.0.2
ts-jest: 27.1.3_b64eba0a9f1c7068f7f3a75addda5ccd
typescript: 4.6.2
@ -34661,6 +34665,12 @@ packages:
dependencies:
'@babel/runtime': 7.17.7
/redux/4.2.0:
resolution: {integrity: sha512-oSBmcKKIuIR4ME29/AeNUnl5L+hvBq7OaJWzaptTQJAntaPvxIJqfnjbaEiCzzaIz+XmVILfqAM3Ob0aXLPfjA==}
dependencies:
'@babel/runtime': 7.17.7
dev: true
/reflect.ownkeys/0.2.0:
resolution: {integrity: sha1-dJrO7H8/34tj+SegSAnpDFwLNGA=}