@spaced-out/ui-design-system
Version:
Sense UI components library
126 lines (101 loc) • 2.81 kB
Flow
// @flow strict
import React, {useEffect, useReducer, useRef} from 'react';
// $FlowFixMe[untyped-import]
import {createPortal} from 'react-dom';
import type {actionTypes, contentTypes, optionTypes} from 'src/types/toast';
import {useToastPortal} from '../../hooks';
import {classify} from '../../utils/classify';
import {toastManager} from './';
import css from './ToastContainer.module.css';
const ADD = 'ADD';
const REMOVE = 'REMOVE';
type stateTypes = {
...optionTypes,
content?: contentTypes,
};
type MapperValues = {
...optionTypes,
content: contentTypes,
};
type Actions = {
type: actionTypes,
data: stateTypes,
};
const reducer = (state: stateTypes[], action: Actions) => {
const {type, data} = action;
if (type === ADD) {
if (
/* $FlowIgnore */
state.filter((i) => i.uniqueCode && i.uniqueCode === data.uniqueCode)
.length
) {
return state;
}
return [...state, data];
} else if (type === REMOVE) {
return state.filter((i) => i.id !== data.id);
}
return state;
};
export const ToastContainer = (): null | React$Portal => {
const [data, dispatch] = useReducer(reducer, []);
const toastRef = useRef<HTMLDivElement | null>(null);
const {loaded} = useToastPortal({toastRef});
const callback = (
actionType: actionTypes,
content: contentTypes,
options: optionTypes,
) => {
if (actionType === ADD) {
dispatch({type: ADD, data: {content, ...options, key: `${options.id}`}});
}
if (actionType === REMOVE) {
dispatch({type: REMOVE, data: {id: options.id}});
}
if (options.autoClose) {
window.setTimeout(() => {
dispatch({type: REMOVE, data: {id: options.id}});
}, options.timeout);
}
};
useEffect(() => {
toastManager.subscribe(callback);
}, []);
const positionMaintainer = () => {
const mapper = {};
/* $FlowIgnore */
data.map(({position, ...rest}: optionTypes) => {
if (position) {
if (!mapper[position]) {
mapper[position] = [];
}
mapper[position].push(rest);
}
});
return mapper;
};
const markup = () => {
const mapper = positionMaintainer();
return Object.keys(mapper).map((position) => {
const content = mapper[position].map(({content, id}: MapperValues) =>
// $FlowFixMe
React.cloneElement(content, {
key: id,
onClose: () => dispatch({type: REMOVE, data: {id}}),
}),
);
return (
<div
key={position.toString()}
className={classify(css.toastContainer, css[position])}
>
{content}
</div>
);
});
};
if (!toastRef.current) {
return null;
}
return loaded ? createPortal(markup(), toastRef.current) : null;
};