105 lines
		
	
	
	
		
			2.4 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			105 lines
		
	
	
	
		
			2.4 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| import { useState, useEffect } from 'react';
 | |
| 
 | |
| import { useIntl } from 'react-intl';
 | |
| import type { IntlShape } from 'react-intl';
 | |
| 
 | |
| import classNames from 'classnames';
 | |
| 
 | |
| import { dismissAlert } from 'mastodon/actions/alerts';
 | |
| import type {
 | |
|   Alert,
 | |
|   TranslatableString,
 | |
|   TranslatableValues,
 | |
| } from 'mastodon/models/alert';
 | |
| import { useAppSelector, useAppDispatch } from 'mastodon/store';
 | |
| 
 | |
| const formatIfNeeded = (
 | |
|   intl: IntlShape,
 | |
|   message: TranslatableString,
 | |
|   values?: TranslatableValues,
 | |
| ) => {
 | |
|   if (typeof message === 'object') {
 | |
|     return intl.formatMessage(message, values);
 | |
|   }
 | |
| 
 | |
|   return message;
 | |
| };
 | |
| 
 | |
| const Alert: React.FC<{
 | |
|   alert: Alert;
 | |
|   dismissAfter: number;
 | |
| }> = ({
 | |
|   alert: { key, title, message, values, action, onClick },
 | |
|   dismissAfter,
 | |
| }) => {
 | |
|   const dispatch = useAppDispatch();
 | |
|   const intl = useIntl();
 | |
|   const [active, setActive] = useState(false);
 | |
| 
 | |
|   useEffect(() => {
 | |
|     const setActiveTimeout = setTimeout(() => {
 | |
|       setActive(true);
 | |
|     }, 1);
 | |
| 
 | |
|     return () => {
 | |
|       clearTimeout(setActiveTimeout);
 | |
|     };
 | |
|   }, []);
 | |
| 
 | |
|   useEffect(() => {
 | |
|     const dismissTimeout = setTimeout(() => {
 | |
|       setActive(false);
 | |
| 
 | |
|       // Allow CSS transition to finish before removing from the DOM
 | |
|       setTimeout(() => {
 | |
|         dispatch(dismissAlert({ key }));
 | |
|       }, 500);
 | |
|     }, dismissAfter);
 | |
| 
 | |
|     return () => {
 | |
|       clearTimeout(dismissTimeout);
 | |
|     };
 | |
|   }, [dispatch, setActive, key, dismissAfter]);
 | |
| 
 | |
|   return (
 | |
|     <div
 | |
|       className={classNames('notification-bar', {
 | |
|         'notification-bar-active': active,
 | |
|       })}
 | |
|     >
 | |
|       <div className='notification-bar-wrapper'>
 | |
|         {title && (
 | |
|           <span className='notification-bar-title'>
 | |
|             {formatIfNeeded(intl, title, values)}
 | |
|           </span>
 | |
|         )}
 | |
| 
 | |
|         <span className='notification-bar-message'>
 | |
|           {formatIfNeeded(intl, message, values)}
 | |
|         </span>
 | |
| 
 | |
|         {action && (
 | |
|           <button className='notification-bar-action' onClick={onClick}>
 | |
|             {formatIfNeeded(intl, action, values)}
 | |
|           </button>
 | |
|         )}
 | |
|       </div>
 | |
|     </div>
 | |
|   );
 | |
| };
 | |
| 
 | |
| export const AlertsController: React.FC = () => {
 | |
|   const alerts = useAppSelector((state) => state.alerts);
 | |
| 
 | |
|   if (alerts.length === 0) {
 | |
|     return null;
 | |
|   }
 | |
| 
 | |
|   return (
 | |
|     <div className='notification-list'>
 | |
|       {alerts.map((alert, idx) => (
 | |
|         <Alert key={alert.key} alert={alert} dismissAfter={5000 + idx * 1000} />
 | |
|       ))}
 | |
|     </div>
 | |
|   );
 | |
| };
 |