Refactor alerts to TypeScript, remove react-notification dependency (#34239)
				
					
				
			This commit is contained in:
		@@ -1,14 +1,11 @@
 | 
			
		||||
import { defineMessages } from 'react-intl';
 | 
			
		||||
import type { MessageDescriptor } from 'react-intl';
 | 
			
		||||
 | 
			
		||||
import { createAction } from '@reduxjs/toolkit';
 | 
			
		||||
 | 
			
		||||
import { AxiosError } from 'axios';
 | 
			
		||||
import type { AxiosResponse } from 'axios';
 | 
			
		||||
 | 
			
		||||
interface Alert {
 | 
			
		||||
  title: string | MessageDescriptor;
 | 
			
		||||
  message: string | MessageDescriptor;
 | 
			
		||||
  values?: Record<string, string | number | Date>;
 | 
			
		||||
}
 | 
			
		||||
import type { Alert } from 'mastodon/models/alert';
 | 
			
		||||
 | 
			
		||||
interface ApiErrorResponse {
 | 
			
		||||
  error?: string;
 | 
			
		||||
@@ -30,24 +27,13 @@ const messages = defineMessages({
 | 
			
		||||
  },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
export const ALERT_SHOW = 'ALERT_SHOW';
 | 
			
		||||
export const ALERT_DISMISS = 'ALERT_DISMISS';
 | 
			
		||||
export const ALERT_CLEAR = 'ALERT_CLEAR';
 | 
			
		||||
export const ALERT_NOOP = 'ALERT_NOOP';
 | 
			
		||||
export const dismissAlert = createAction<{ key: number }>('alerts/dismiss');
 | 
			
		||||
 | 
			
		||||
export const dismissAlert = (alert: Alert) => ({
 | 
			
		||||
  type: ALERT_DISMISS,
 | 
			
		||||
  alert,
 | 
			
		||||
});
 | 
			
		||||
export const clearAlerts = createAction('alerts/clear');
 | 
			
		||||
 | 
			
		||||
export const clearAlert = () => ({
 | 
			
		||||
  type: ALERT_CLEAR,
 | 
			
		||||
});
 | 
			
		||||
export const showAlert = createAction<Omit<Alert, 'key'>>('alerts/show');
 | 
			
		||||
 | 
			
		||||
export const showAlert = (alert: Alert) => ({
 | 
			
		||||
  type: ALERT_SHOW,
 | 
			
		||||
  alert,
 | 
			
		||||
});
 | 
			
		||||
const ignoreAlert = createAction('alerts/ignore');
 | 
			
		||||
 | 
			
		||||
export const showAlertForError = (error: unknown, skipNotFound = false) => {
 | 
			
		||||
  if (error instanceof AxiosError && error.response) {
 | 
			
		||||
@@ -56,7 +42,7 @@ export const showAlertForError = (error: unknown, skipNotFound = false) => {
 | 
			
		||||
 | 
			
		||||
    // Skip these errors as they are reflected in the UI
 | 
			
		||||
    if (skipNotFound && (status === 404 || status === 410)) {
 | 
			
		||||
      return { type: ALERT_NOOP };
 | 
			
		||||
      return ignoreAlert();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Rate limit errors
 | 
			
		||||
@@ -76,9 +62,9 @@ export const showAlertForError = (error: unknown, skipNotFound = false) => {
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // An aborted request, e.g. due to reloading the browser window, it not really error
 | 
			
		||||
  // An aborted request, e.g. due to reloading the browser window, is not really an error
 | 
			
		||||
  if (error instanceof AxiosError && error.code === AxiosError.ECONNABORTED) {
 | 
			
		||||
    return { type: ALERT_NOOP };
 | 
			
		||||
    return ignoreAlert();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  console.error(error);
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										105
									
								
								app/javascript/mastodon/components/alerts_controller.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										105
									
								
								app/javascript/mastodon/components/alerts_controller.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,105 @@
 | 
			
		||||
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>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
@@ -1,12 +1,12 @@
 | 
			
		||||
import { AlertsController } from 'mastodon/components/alerts_controller';
 | 
			
		||||
import ComposeFormContainer from 'mastodon/features/compose/containers/compose_form_container';
 | 
			
		||||
import LoadingBarContainer from 'mastodon/features/ui/containers/loading_bar_container';
 | 
			
		||||
import ModalContainer from 'mastodon/features/ui/containers/modal_container';
 | 
			
		||||
import NotificationsContainer from 'mastodon/features/ui/containers/notifications_container';
 | 
			
		||||
 | 
			
		||||
const Compose = () => (
 | 
			
		||||
  <>
 | 
			
		||||
    <ComposeFormContainer autoFocus withoutNavigation />
 | 
			
		||||
    <NotificationsContainer />
 | 
			
		||||
    <AlertsController />
 | 
			
		||||
    <ModalContainer />
 | 
			
		||||
    <LoadingBarContainer className='loading-bar' />
 | 
			
		||||
  </>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,20 +0,0 @@
 | 
			
		||||
import { injectIntl } from 'react-intl';
 | 
			
		||||
 | 
			
		||||
import { connect } from 'react-redux';
 | 
			
		||||
 | 
			
		||||
import { NotificationStack } from 'react-notification';
 | 
			
		||||
 | 
			
		||||
import { dismissAlert } from 'mastodon/actions/alerts';
 | 
			
		||||
import { getAlerts } from 'mastodon/selectors';
 | 
			
		||||
 | 
			
		||||
const mapStateToProps = (state, { intl }) => ({
 | 
			
		||||
  notifications: getAlerts(state, { intl }),
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const mapDispatchToProps = (dispatch) => ({
 | 
			
		||||
  onDismiss (alert) {
 | 
			
		||||
    dispatch(dismissAlert(alert));
 | 
			
		||||
  },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(NotificationStack));
 | 
			
		||||
@@ -15,6 +15,7 @@ import { focusApp, unfocusApp, changeLayout } from 'mastodon/actions/app';
 | 
			
		||||
import { synchronouslySubmitMarkers, submitMarkers, fetchMarkers } from 'mastodon/actions/markers';
 | 
			
		||||
import { fetchNotifications } from 'mastodon/actions/notification_groups';
 | 
			
		||||
import { INTRODUCTION_VERSION } from 'mastodon/actions/onboarding';
 | 
			
		||||
import { AlertsController } from 'mastodon/components/alerts_controller';
 | 
			
		||||
import { HoverCardController } from 'mastodon/components/hover_card_controller';
 | 
			
		||||
import { PictureInPicture } from 'mastodon/features/picture_in_picture';
 | 
			
		||||
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
 | 
			
		||||
@@ -33,7 +34,6 @@ import UploadArea from './components/upload_area';
 | 
			
		||||
import ColumnsAreaContainer from './containers/columns_area_container';
 | 
			
		||||
import LoadingBarContainer from './containers/loading_bar_container';
 | 
			
		||||
import ModalContainer from './containers/modal_container';
 | 
			
		||||
import NotificationsContainer from './containers/notifications_container';
 | 
			
		||||
import {
 | 
			
		||||
  Compose,
 | 
			
		||||
  Status,
 | 
			
		||||
@@ -607,7 +607,7 @@ class UI extends PureComponent {
 | 
			
		||||
          </SwitchingColumnsArea>
 | 
			
		||||
 | 
			
		||||
          {layout !== 'mobile' && <PictureInPicture />}
 | 
			
		||||
          <NotificationsContainer />
 | 
			
		||||
          <AlertsController />
 | 
			
		||||
          {!disableHoverCards && <HoverCardController />}
 | 
			
		||||
          <LoadingBarContainer className='loading-bar' />
 | 
			
		||||
          <ModalContainer />
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										14
									
								
								app/javascript/mastodon/models/alert.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								app/javascript/mastodon/models/alert.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,14 @@
 | 
			
		||||
import type { MessageDescriptor } from 'react-intl';
 | 
			
		||||
 | 
			
		||||
export type TranslatableString = string | MessageDescriptor;
 | 
			
		||||
 | 
			
		||||
export type TranslatableValues = Record<string, string | number | Date>;
 | 
			
		||||
 | 
			
		||||
export interface Alert {
 | 
			
		||||
  key: number;
 | 
			
		||||
  title?: TranslatableString;
 | 
			
		||||
  message: TranslatableString;
 | 
			
		||||
  action?: TranslatableString;
 | 
			
		||||
  values?: TranslatableValues;
 | 
			
		||||
  onClick?: () => void;
 | 
			
		||||
}
 | 
			
		||||
@@ -1,30 +0,0 @@
 | 
			
		||||
import { List as ImmutableList } from 'immutable';
 | 
			
		||||
 | 
			
		||||
import {
 | 
			
		||||
  ALERT_SHOW,
 | 
			
		||||
  ALERT_DISMISS,
 | 
			
		||||
  ALERT_CLEAR,
 | 
			
		||||
} from '../actions/alerts';
 | 
			
		||||
 | 
			
		||||
const initialState = ImmutableList([]);
 | 
			
		||||
 | 
			
		||||
let id = 0;
 | 
			
		||||
 | 
			
		||||
const addAlert = (state, alert) =>
 | 
			
		||||
  state.push({
 | 
			
		||||
    key: id++,
 | 
			
		||||
    ...alert,
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
export default function alerts(state = initialState, action) {
 | 
			
		||||
  switch(action.type) {
 | 
			
		||||
  case ALERT_SHOW:
 | 
			
		||||
    return addAlert(state, action.alert);
 | 
			
		||||
  case ALERT_DISMISS:
 | 
			
		||||
    return state.filterNot(item => item.key === action.alert.key);
 | 
			
		||||
  case ALERT_CLEAR:
 | 
			
		||||
    return state.clear();
 | 
			
		||||
  default:
 | 
			
		||||
    return state;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										24
									
								
								app/javascript/mastodon/reducers/alerts.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								app/javascript/mastodon/reducers/alerts.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,24 @@
 | 
			
		||||
import { createReducer } from '@reduxjs/toolkit';
 | 
			
		||||
 | 
			
		||||
import { showAlert, dismissAlert, clearAlerts } from 'mastodon/actions/alerts';
 | 
			
		||||
import type { Alert } from 'mastodon/models/alert';
 | 
			
		||||
 | 
			
		||||
const initialState: Alert[] = [];
 | 
			
		||||
 | 
			
		||||
let id = 0;
 | 
			
		||||
 | 
			
		||||
export const alertsReducer = createReducer(initialState, (builder) => {
 | 
			
		||||
  builder
 | 
			
		||||
    .addCase(showAlert, (state, { payload }) => {
 | 
			
		||||
      state.push({
 | 
			
		||||
        key: id++,
 | 
			
		||||
        ...payload,
 | 
			
		||||
      });
 | 
			
		||||
    })
 | 
			
		||||
    .addCase(dismissAlert, (state, { payload: { key } }) => {
 | 
			
		||||
      return state.filter((item) => item.key !== key);
 | 
			
		||||
    })
 | 
			
		||||
    .addCase(clearAlerts, () => {
 | 
			
		||||
      return [];
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
@@ -5,7 +5,7 @@ import { combineReducers } from 'redux-immutable';
 | 
			
		||||
 | 
			
		||||
import { accountsReducer } from './accounts';
 | 
			
		||||
import accounts_map from './accounts_map';
 | 
			
		||||
import alerts from './alerts';
 | 
			
		||||
import { alertsReducer } from './alerts';
 | 
			
		||||
import announcements from './announcements';
 | 
			
		||||
import { composeReducer } from './compose';
 | 
			
		||||
import contexts from './contexts';
 | 
			
		||||
@@ -45,7 +45,7 @@ const reducers = {
 | 
			
		||||
  dropdownMenu: dropdownMenuReducer,
 | 
			
		||||
  timelines,
 | 
			
		||||
  meta,
 | 
			
		||||
  alerts,
 | 
			
		||||
  alerts: alertsReducer,
 | 
			
		||||
  loadingBar: loadingBarReducer,
 | 
			
		||||
  modal: modalReducer,
 | 
			
		||||
  user_lists,
 | 
			
		||||
 
 | 
			
		||||
@@ -60,28 +60,6 @@ export const makeGetPictureInPicture = () => {
 | 
			
		||||
  }));
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const ALERT_DEFAULTS = {
 | 
			
		||||
  dismissAfter: 5000,
 | 
			
		||||
  style: false,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const formatIfNeeded = (intl, message, values) => {
 | 
			
		||||
  if (typeof message === 'object') {
 | 
			
		||||
    return intl.formatMessage(message, values);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return message;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const getAlerts = createSelector([state => state.get('alerts'), (_, { intl }) => intl], (alerts, intl) =>
 | 
			
		||||
  alerts.map(item => ({
 | 
			
		||||
    ...ALERT_DEFAULTS,
 | 
			
		||||
    ...item,
 | 
			
		||||
    action: formatIfNeeded(intl, item.action, item.values),
 | 
			
		||||
    title: formatIfNeeded(intl, item.title, item.values),
 | 
			
		||||
    message: formatIfNeeded(intl, item.message, item.values),
 | 
			
		||||
  })).toArray());
 | 
			
		||||
 | 
			
		||||
export const makeGetNotification = () => createSelector([
 | 
			
		||||
  (_, base)             => base,
 | 
			
		||||
  (state, _, accountId) => state.getIn(['accounts', accountId]),
 | 
			
		||||
 
 | 
			
		||||
@@ -12,19 +12,21 @@ import type { AsyncThunkRejectValue } from '../typed_functions';
 | 
			
		||||
const defaultFailSuffix = 'FAIL';
 | 
			
		||||
const isFailedAction = new RegExp(`${defaultFailSuffix}$`, 'g');
 | 
			
		||||
 | 
			
		||||
interface ActionWithMaybeAlertParams extends Action, AsyncThunkRejectValue {}
 | 
			
		||||
 | 
			
		||||
interface RejectedAction extends Action {
 | 
			
		||||
  payload: AsyncThunkRejectValue;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface ActionWithMaybeAlertParams extends Action, AsyncThunkRejectValue {
 | 
			
		||||
  payload?: AsyncThunkRejectValue;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function isRejectedActionWithPayload(
 | 
			
		||||
  action: unknown,
 | 
			
		||||
): action is RejectedAction {
 | 
			
		||||
  return isAsyncThunkAction(action) && isRejectedWithValue(action);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function isActionWithmaybeAlertParams(
 | 
			
		||||
function isActionWithMaybeAlertParams(
 | 
			
		||||
  action: unknown,
 | 
			
		||||
): action is ActionWithMaybeAlertParams {
 | 
			
		||||
  return isAction(action);
 | 
			
		||||
@@ -40,11 +42,12 @@ export const errorsMiddleware: Middleware<{}, RootState> =
 | 
			
		||||
        showAlertForError(action.payload.error, action.payload.skipNotFound),
 | 
			
		||||
      );
 | 
			
		||||
    } else if (
 | 
			
		||||
      isActionWithmaybeAlertParams(action) &&
 | 
			
		||||
      !action.skipAlert &&
 | 
			
		||||
      isActionWithMaybeAlertParams(action) &&
 | 
			
		||||
      !(action.payload?.skipAlert || action.skipAlert) &&
 | 
			
		||||
      action.type.match(isFailedAction)
 | 
			
		||||
    ) {
 | 
			
		||||
      dispatch(showAlertForError(action.error, action.skipNotFound));
 | 
			
		||||
      const { error, skipNotFound } = action.payload ?? action;
 | 
			
		||||
      dispatch(showAlertForError(error, skipNotFound));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return next(action);
 | 
			
		||||
 
 | 
			
		||||
@@ -9732,6 +9732,9 @@ noscript {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.notification-bar-action {
 | 
			
		||||
  display: inline-block;
 | 
			
		||||
  border: 0;
 | 
			
		||||
  background: transparent;
 | 
			
		||||
  text-transform: uppercase;
 | 
			
		||||
  margin-inline-start: 10px;
 | 
			
		||||
  cursor: pointer;
 | 
			
		||||
 
 | 
			
		||||
@@ -105,7 +105,6 @@
 | 
			
		||||
    "react-immutable-pure-component": "^2.2.2",
 | 
			
		||||
    "react-intl": "^7.0.0",
 | 
			
		||||
    "react-motion": "^0.5.2",
 | 
			
		||||
    "react-notification": "^6.8.5",
 | 
			
		||||
    "react-overlays": "^5.2.1",
 | 
			
		||||
    "react-redux": "^9.0.4",
 | 
			
		||||
    "react-redux-loading-bar": "^5.0.8",
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										12
									
								
								yarn.lock
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								yarn.lock
									
									
									
									
									
								
							@@ -2851,7 +2851,6 @@ __metadata:
 | 
			
		||||
    react-immutable-pure-component: "npm:^2.2.2"
 | 
			
		||||
    react-intl: "npm:^7.0.0"
 | 
			
		||||
    react-motion: "npm:^0.5.2"
 | 
			
		||||
    react-notification: "npm:^6.8.5"
 | 
			
		||||
    react-overlays: "npm:^5.2.1"
 | 
			
		||||
    react-redux: "npm:^9.0.4"
 | 
			
		||||
    react-redux-loading-bar: "npm:^5.0.8"
 | 
			
		||||
@@ -14791,17 +14790,6 @@ __metadata:
 | 
			
		||||
  languageName: node
 | 
			
		||||
  linkType: hard
 | 
			
		||||
 | 
			
		||||
"react-notification@npm:^6.8.5":
 | 
			
		||||
  version: 6.8.5
 | 
			
		||||
  resolution: "react-notification@npm:6.8.5"
 | 
			
		||||
  dependencies:
 | 
			
		||||
    prop-types: "npm:^15.6.2"
 | 
			
		||||
  peerDependencies:
 | 
			
		||||
    react: ^0.14.0 || ^15.0.0 || ^16.0.0
 | 
			
		||||
  checksum: 10c0/14ffb71a5b18301830699b814d1de2421f4f43f31df5b95efd95cd47548a0d7597ec58abc16a12191958cad398495eba9274193af3294863e2864d32ea79f2c6
 | 
			
		||||
  languageName: node
 | 
			
		||||
  linkType: hard
 | 
			
		||||
 | 
			
		||||
"react-overlays@npm:^5.2.1":
 | 
			
		||||
  version: 5.2.1
 | 
			
		||||
  resolution: "react-overlays@npm:5.2.1"
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user