initial commit
This commit is contained in:
+1
@@ -0,0 +1 @@
|
||||
export * from './validation-input-error';
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { useValidationContext } from '../../context';
|
||||
import './style.scss';
|
||||
|
||||
export const ValidationInputError = ( {
|
||||
errorMessage = '',
|
||||
propertyName = '',
|
||||
elementId = '',
|
||||
} ) => {
|
||||
const { getValidationError, getValidationErrorId } = useValidationContext();
|
||||
|
||||
if ( ! errorMessage || typeof errorMessage !== 'string' ) {
|
||||
const error = getValidationError( propertyName ) || {};
|
||||
if ( error.message && ! error.hidden ) {
|
||||
errorMessage = error.message;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="wc-block-components-validation-error" role="alert">
|
||||
<p id={ getValidationErrorId( elementId ) }>{ errorMessage }</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
ValidationInputError.propTypes = {
|
||||
errorMessage: PropTypes.string,
|
||||
propertyName: PropTypes.string,
|
||||
elementId: PropTypes.string,
|
||||
};
|
||||
|
||||
export default ValidationInputError;
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
.wc-block-components-validation-error {
|
||||
@include font-size(smaller);
|
||||
color: $alert-red;
|
||||
max-width: 100%;
|
||||
white-space: normal;
|
||||
|
||||
> p {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.wc-block-components-select + .wc-block-components-validation-error {
|
||||
margin-bottom: $gap-large;
|
||||
}
|
||||
@@ -0,0 +1,257 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import {
|
||||
createContext,
|
||||
useCallback,
|
||||
useContext,
|
||||
useState,
|
||||
} from '@wordpress/element';
|
||||
import { pickBy } from 'lodash';
|
||||
import isShallowEqual from '@wordpress/is-shallow-equal';
|
||||
|
||||
/**
|
||||
* @typedef { import('@woocommerce/type-defs/contexts').ValidationContext } ValidationContext
|
||||
* @typedef {import('react')} React
|
||||
*/
|
||||
|
||||
const ValidationContext = createContext( {
|
||||
getValidationError: () => '',
|
||||
setValidationErrors: ( errors ) => void errors,
|
||||
clearValidationError: ( property ) => void property,
|
||||
clearAllValidationErrors: () => void null,
|
||||
hideValidationError: () => void null,
|
||||
showValidationError: () => void null,
|
||||
showAllValidationErrors: () => void null,
|
||||
hasValidationErrors: false,
|
||||
getValidationErrorId: ( errorId ) => errorId,
|
||||
} );
|
||||
|
||||
/**
|
||||
* @return {ValidationContext} The context values for the validation context.
|
||||
*/
|
||||
export const useValidationContext = () => {
|
||||
return useContext( ValidationContext );
|
||||
};
|
||||
|
||||
/**
|
||||
* Validation context provider
|
||||
*
|
||||
* Any children of this context will be exposed to validation state and helpers
|
||||
* for tracking validation.
|
||||
*
|
||||
* @param {Object} props Incoming props for the component.
|
||||
* @param {JSX.Element} props.children What react elements are wrapped by this component.
|
||||
*/
|
||||
export const ValidationContextProvider = ( { children } ) => {
|
||||
const [ validationErrors, updateValidationErrors ] = useState( {} );
|
||||
|
||||
/**
|
||||
* This retrieves any validation error message that exists in state for the
|
||||
* given property name.
|
||||
*
|
||||
* @param {string} property The property the error message is for.
|
||||
*
|
||||
* @return {Object} The error object for the given property.
|
||||
*/
|
||||
const getValidationError = useCallback(
|
||||
( property ) => validationErrors[ property ],
|
||||
[ validationErrors ]
|
||||
);
|
||||
|
||||
/**
|
||||
* Provides an id for the validation error that can be used to fill out
|
||||
* aria-describedby attribute values.
|
||||
*
|
||||
* @param {string} errorId The input css id the validation error is related
|
||||
* to.
|
||||
* @return {string} The id to use for the validation error container.
|
||||
*/
|
||||
const getValidationErrorId = useCallback(
|
||||
( errorId ) => {
|
||||
const error = validationErrors[ errorId ];
|
||||
if ( ! error || error.hidden ) {
|
||||
return '';
|
||||
}
|
||||
return `validate-error-${ errorId }`;
|
||||
},
|
||||
[ validationErrors ]
|
||||
);
|
||||
|
||||
/**
|
||||
* Clears any validation error that exists in state for the given property
|
||||
* name.
|
||||
*
|
||||
* @param {string} property The name of the property to clear if exists in
|
||||
* validation error state.
|
||||
*/
|
||||
const clearValidationError = useCallback(
|
||||
/**
|
||||
* Callback that is memoized.
|
||||
*
|
||||
* @param {string} property
|
||||
*/
|
||||
( property ) => {
|
||||
updateValidationErrors(
|
||||
/**
|
||||
* Callback for validation Errors handling.
|
||||
*
|
||||
* @param {Object} prevErrors
|
||||
*/
|
||||
( prevErrors ) => {
|
||||
if ( ! prevErrors[ property ] ) {
|
||||
return prevErrors;
|
||||
}
|
||||
|
||||
const {
|
||||
// eslint-disable-next-line no-unused-vars -- this is intentional to omit the dynamic property from the returned object.
|
||||
[ property ]: clearedProperty,
|
||||
...newErrors
|
||||
} = prevErrors;
|
||||
return newErrors;
|
||||
}
|
||||
);
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
/**
|
||||
* Clears the entire validation error state.
|
||||
*/
|
||||
const clearAllValidationErrors = useCallback(
|
||||
() => void updateValidationErrors( {} ),
|
||||
[]
|
||||
);
|
||||
|
||||
/**
|
||||
* Used to record new validation errors in the state.
|
||||
*
|
||||
* @param {Object} newErrors An object where keys are the property names the
|
||||
* validation error is for and values are the
|
||||
* validation error message displayed to the user.
|
||||
*/
|
||||
const setValidationErrors = useCallback( ( newErrors ) => {
|
||||
if ( ! newErrors ) {
|
||||
return;
|
||||
}
|
||||
updateValidationErrors( ( prevErrors ) => {
|
||||
newErrors = pickBy( newErrors, ( error, property ) => {
|
||||
if ( typeof error.message !== 'string' ) {
|
||||
return false;
|
||||
}
|
||||
if ( prevErrors.hasOwnProperty( property ) ) {
|
||||
return ! isShallowEqual( prevErrors[ property ], error );
|
||||
}
|
||||
return true;
|
||||
} );
|
||||
if ( Object.values( newErrors ).length === 0 ) {
|
||||
return prevErrors;
|
||||
}
|
||||
return {
|
||||
...prevErrors,
|
||||
...newErrors,
|
||||
};
|
||||
} );
|
||||
}, [] );
|
||||
|
||||
/**
|
||||
* Used to update a validation error.
|
||||
*
|
||||
* @param {string} property The name of the property to update.
|
||||
* @param {Object} newError New validation error object.
|
||||
*/
|
||||
const updateValidationError = useCallback( ( property, newError ) => {
|
||||
updateValidationErrors( ( prevErrors ) => {
|
||||
if ( ! prevErrors.hasOwnProperty( property ) ) {
|
||||
return prevErrors;
|
||||
}
|
||||
const updatedError = {
|
||||
...prevErrors[ property ],
|
||||
...newError,
|
||||
};
|
||||
return isShallowEqual( prevErrors[ property ], updatedError )
|
||||
? prevErrors
|
||||
: {
|
||||
...prevErrors,
|
||||
[ property ]: updatedError,
|
||||
};
|
||||
} );
|
||||
}, [] );
|
||||
|
||||
/**
|
||||
* Given a property name and if an associated error exists, it sets its
|
||||
* `hidden` value to true.
|
||||
*
|
||||
* @param {string} property The name of the property to set the `hidden`
|
||||
* value to true.
|
||||
*/
|
||||
const hideValidationError = useCallback(
|
||||
( property ) =>
|
||||
void updateValidationError( property, {
|
||||
hidden: true,
|
||||
} ),
|
||||
[ updateValidationError ]
|
||||
);
|
||||
|
||||
/**
|
||||
* Given a property name and if an associated error exists, it sets its
|
||||
* `hidden` value to false.
|
||||
*
|
||||
* @param {string} property The name of the property to set the `hidden`
|
||||
* value to false.
|
||||
*/
|
||||
const showValidationError = useCallback(
|
||||
( property ) =>
|
||||
void updateValidationError( property, {
|
||||
hidden: false,
|
||||
} ),
|
||||
[ updateValidationError ]
|
||||
);
|
||||
|
||||
/**
|
||||
* Sets the `hidden` value of all errors to `false`.
|
||||
*/
|
||||
const showAllValidationErrors = useCallback(
|
||||
() =>
|
||||
void updateValidationErrors( ( prevErrors ) => {
|
||||
const updatedErrors = {};
|
||||
|
||||
Object.keys( prevErrors ).forEach( ( property ) => {
|
||||
if ( prevErrors[ property ].hidden ) {
|
||||
updatedErrors[ property ] = {
|
||||
...prevErrors[ property ],
|
||||
hidden: false,
|
||||
};
|
||||
}
|
||||
} );
|
||||
|
||||
if ( Object.values( updatedErrors ).length === 0 ) {
|
||||
return prevErrors;
|
||||
}
|
||||
|
||||
return {
|
||||
...prevErrors,
|
||||
...updatedErrors,
|
||||
};
|
||||
} ),
|
||||
[]
|
||||
);
|
||||
|
||||
const context = {
|
||||
getValidationError,
|
||||
setValidationErrors,
|
||||
clearValidationError,
|
||||
clearAllValidationErrors,
|
||||
hideValidationError,
|
||||
showValidationError,
|
||||
showAllValidationErrors,
|
||||
hasValidationErrors: Object.keys( validationErrors ).length > 0,
|
||||
getValidationErrorId,
|
||||
};
|
||||
|
||||
return (
|
||||
<ValidationContext.Provider value={ context }>
|
||||
{ children }
|
||||
</ValidationContext.Provider>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from './context';
|
||||
export * from './components';
|
||||
Reference in New Issue
Block a user