import { React, PropTypes, classnames } from '@gravityforms/libraries';
import { spacerClasses } from '@gravityforms/utils';
import Button from '../../elements/Button';
import Icon from '../../elements/Icon';
const { forwardRef, useEffect, useState, createContext, useContext } = React;
const SnackbarSettingsContext = createContext( {} );
const SnackbarContext = createContext( null );
/**
* @module SnackBar
* @description Renders a SnackBar module.
*
* @since 3.4.0
*
* @param {object} props Component props.
* @param {JSX.Element} props.children React element children.
* @param {object} props.customAttributes Custom attributes for the component
* @param {string|Array|object} props.customClasses Custom classes for the component.
* @param {number} props.delay The delay for the component.
* @param {string} props.id The id for the component.
* @param {string} props.message The message for the component.
* @param {Function} props.onDismiss Callback function to run when the component is dismissed.
* @param {string|number|Array|object} props.spacing The spacing for the component, as a string, number, array, or object.
* @param {string} props.theme The theme for the component.
* @param {string} props.type The type for the component.
* @param {object|null} ref Ref to the component.
*
* @return {JSX.Element} The icon component.
*
* @example
* import SnackBar from '@gravityforms/components/react/admin/modules/SnackBar';
*
* return <SnackBar delay={ 2000 } message="Success saving settings!" />;
*
*/
const SnackBar = forwardRef( ( props, ref ) => {
const globalSettings = useContext( SnackbarSettingsContext );
const mergedProps = { ...globalSettings, ...props };
const {
ariaLive = 'polite',
children = null,
closeButtonAttributes = {},
closeButtonClasses = [],
customAttributes = {},
customClasses = [],
delay = 5000,
errorIconAttributes = {},
errorIconClasses = [],
id = '',
interactive = false,
message = '',
onDismiss = () => {},
spacing = '',
successIconAttributes = {},
successIconClasses = [],
theme = 'cosmos',
type = 'success',
...otherProps
} = mergedProps;
const [ isVisible, setIsVisible ] = useState( false );
useEffect( () => {
setTimeout( () => {
setIsVisible( true );
}, 50 );
}, [] );
useEffect( () => {
if ( interactive ) {
return;
}
const timer = setTimeout( () => {
setIsVisible( false );
}, delay );
const timerForRemove = setTimeout( () => {
onDismiss( id );
}, ( delay + 100 ) );
return () => {
clearTimeout( timer );
clearTimeout( timerForRemove );
};
}, [ interactive, delay, id, onDismiss ] );
const componentProps = {
className: classnames( {
'gform-snackbar': true,
'gform-snackbar--react': true,
'gform-snackbar--interactive': interactive,
'gform-snackbar--visible': isVisible,
[ `gform-snackbar--theme-${ theme }` ]: true,
[ `gform-snackbar--type-${ type }` ]: true,
...spacerClasses( spacing ),
}, customClasses ),
style: {
...customAttributes.style,
'--gform-snackbar-animation-delay': `${ isVisible ? 0 : delay }ms`,
},
'aria-live': ariaLive,
ref,
...customAttributes,
};
const closeButtonProps = {
customClasses: classnames( {
'gform-snackbar__close': true,
}, closeButtonClasses ),
iconPosition: 'leading',
type: 'unstyled',
...closeButtonAttributes,
};
const errorIconProps = {
customClasses: classnames( {
'gform-snackbar__type-icon': true,
'gform-snackbar__type-icon--error': true,
}, errorIconClasses ),
icon: 'delete',
iconPrefix: 'gform-icon',
spacing: [ 0, 3, 0, 0 ],
...errorIconAttributes,
};
const successIconProps = {
customClasses: classnames( {
'gform-snackbar__type-icon': true,
'gform-snackbar__type-icon--success': true,
}, successIconClasses ),
icon: 'check',
iconPrefix: 'gform-icon',
spacing: [ 0, 3, 0, 0 ],
...successIconAttributes,
};
return (
<div { ...componentProps } { ...otherProps }>
{ type === 'error' && <Icon { ...errorIconProps } /> }
{ type === 'success' && <Icon { ...successIconProps } /> }
{ message }
{ children }
{ interactive && <Button { ...closeButtonProps } onClick={ () => {
onDismiss( id );
setIsVisible( false );
} } /> }
</div>
);
} );
export const SnackbarProvider = ( { children, defaultSettings = {} } ) => {
const [ messages, setMessages ] = useState( [] );
const addMessage = ( message, type = 'success', additionalProps = {} ) => {
// Merge the global defaults with the individual message settings
const settings = { ...defaultSettings, type, ...additionalProps };
// Generate a unique ID for the message
const id = Math.random().toString( 36 ).substring( 7 );
setMessages( ( prevMessages ) => [ ...prevMessages, { id, message, ...settings } ] );
};
const removeMessage = ( id ) => {
setMessages( ( prevMessages ) => prevMessages.filter( ( message ) => message.id !== id ) );
};
return (
<SnackbarSettingsContext.Provider value={ defaultSettings }>
<SnackbarContext.Provider value={ addMessage }>
{ children }
{ messages.map( ( { id, message, type, ...otherProps }, index ) => (
<SnackBar
customAttributes={ { style: { '--gform-snackbar-index': index } } }
id={ id }
key={ id }
message={ message }
onDismiss={ removeMessage }
type={ type }
{ ...otherProps }
/>
) ) }
</SnackbarContext.Provider>
</SnackbarSettingsContext.Provider>
);
};
export const useSnackbar = () => {
return useContext( SnackbarContext );
};
SnackBar.propTypes = {
children: PropTypes.oneOfType( [
PropTypes.arrayOf( PropTypes.node ),
PropTypes.node,
] ),
customAttributes: PropTypes.object,
customClasses: PropTypes.oneOfType( [
PropTypes.string,
PropTypes.array,
PropTypes.object,
] ),
delay: PropTypes.number,
id: PropTypes.string,
message: PropTypes.string,
onDismiss: PropTypes.func,
spacing: PropTypes.oneOfType( [
PropTypes.string,
PropTypes.number,
PropTypes.array,
PropTypes.object,
] ),
theme: PropTypes.string,
type: PropTypes.string,
};
SnackBar.displayName = 'SnackBar';
export default SnackBar;