fix: flash modals stack up and are not visible until scrolled to top (#35808)

Co-authored-by: Mrugesh Mohapatra <1884376+raisedadead@users.noreply.github.com>
This commit is contained in:
Hemakshi Sachdev
2019-06-09 05:25:55 +05:30
committed by mrugesh
parent 8f633299ef
commit b1956a7ba1
6 changed files with 205 additions and 59 deletions

161
client/package-lock.json generated
View File

@ -1064,6 +1064,48 @@
"loose-envify": "^1.3.1",
"object-assign": "^4.1.1"
}
},
"react-transition-group": {
"version": "2.9.0",
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.9.0.tgz",
"integrity": "sha512-+HzNTCHpeQyl4MJ/bdE0u6XRMe9+XG/+aL4mCxVN4DnPBQ0/5bfHWPDuOZUzYdMj94daZaZdCCc1Dzt9R/xSSg==",
"requires": {
"dom-helpers": "^3.4.0",
"loose-envify": "^1.4.0",
"prop-types": "^15.6.2",
"react-lifecycles-compat": "^3.0.4"
},
"dependencies": {
"@babel/runtime": {
"version": "7.4.5",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.4.5.tgz",
"integrity": "sha512-TuI4qpWZP6lGOGIuGWtp9sPluqYICmbk8T/1vpSysqJxRPkudh/ofFWyqdcMsDf2s7KvDL4/YHgKyvcS3g9CJQ==",
"requires": {
"regenerator-runtime": "^0.13.2"
}
},
"dom-helpers": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-3.4.0.tgz",
"integrity": "sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA==",
"requires": {
"@babel/runtime": "^7.1.2"
}
},
"loose-envify": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
"requires": {
"js-tokens": "^3.0.0 || ^4.0.0"
}
}
}
},
"regenerator-runtime": {
"version": "0.13.2",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.2.tgz",
"integrity": "sha512-S/TQAZJO+D3m9xeN1WTI8dLKBBiRgXBlTJvbWjCThHWZj9EvHK70Ff50/tYj2J/fvBY6JtFVwRuazHN2E7M9BA=="
}
}
},
@ -16213,6 +16255,65 @@
"prop-types-extra": "^1.0.1",
"react-transition-group": "^2.2.0",
"warning": "^3.0.0"
},
"dependencies": {
"@babel/runtime": {
"version": "7.4.5",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.4.5.tgz",
"integrity": "sha512-TuI4qpWZP6lGOGIuGWtp9sPluqYICmbk8T/1vpSysqJxRPkudh/ofFWyqdcMsDf2s7KvDL4/YHgKyvcS3g9CJQ==",
"requires": {
"regenerator-runtime": "^0.13.2"
}
},
"loose-envify": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
"requires": {
"js-tokens": "^3.0.0 || ^4.0.0"
}
},
"react-is": {
"version": "16.8.6",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.8.6.tgz",
"integrity": "sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA=="
},
"react-transition-group": {
"version": "2.9.0",
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.9.0.tgz",
"integrity": "sha512-+HzNTCHpeQyl4MJ/bdE0u6XRMe9+XG/+aL4mCxVN4DnPBQ0/5bfHWPDuOZUzYdMj94daZaZdCCc1Dzt9R/xSSg==",
"requires": {
"dom-helpers": "^3.4.0",
"loose-envify": "^1.4.0",
"prop-types": "^15.6.2",
"react-lifecycles-compat": "^3.0.4"
},
"dependencies": {
"dom-helpers": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-3.4.0.tgz",
"integrity": "sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA==",
"requires": {
"@babel/runtime": "^7.1.2"
}
},
"prop-types": {
"version": "15.7.2",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz",
"integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==",
"requires": {
"loose-envify": "^1.4.0",
"object-assign": "^4.1.1",
"react-is": "^16.8.1"
}
}
}
},
"regenerator-runtime": {
"version": "0.13.2",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.2.tgz",
"integrity": "sha512-S/TQAZJO+D3m9xeN1WTI8dLKBBiRgXBlTJvbWjCThHWZj9EvHK70Ff50/tYj2J/fvBY6JtFVwRuazHN2E7M9BA=="
}
}
},
"react-prop-types": {
@ -16374,24 +16475,58 @@
}
},
"react-transition-group": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.4.0.tgz",
"integrity": "sha512-Xv5d55NkJUxUzLCImGSanK8Cl/30sgpOEMGc5m86t8+kZwrPxPCPcFqyx83kkr+5Lz5gs6djuvE5By+gce+VjA==",
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.1.0.tgz",
"integrity": "sha512-/OITbogb3emGN49WaP7468QGSde7er5w6eIHldIDCSQBq/9QTSCzs8OgpgmOnaUXCXzBUcK1zoZ6DqRlM8CJtA==",
"requires": {
"dom-helpers": "^3.3.1",
"loose-envify": "^1.3.1",
"prop-types": "^15.6.2",
"react-lifecycles-compat": "^3.0.4"
"dom-helpers": "^3.4.0",
"loose-envify": "^1.4.0",
"prop-types": "^15.6.2"
},
"dependencies": {
"prop-types": {
"version": "15.6.2",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.2.tgz",
"integrity": "sha512-3pboPvLiWD7dkI3qf3KbUe6hKFKa52w+AE0VCqECtf+QHAKgOL37tTaNCnuX1nAAQ4ZhyP+kYVKf8rLmJ/feDQ==",
"@babel/runtime": {
"version": "7.4.5",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.4.5.tgz",
"integrity": "sha512-TuI4qpWZP6lGOGIuGWtp9sPluqYICmbk8T/1vpSysqJxRPkudh/ofFWyqdcMsDf2s7KvDL4/YHgKyvcS3g9CJQ==",
"requires": {
"loose-envify": "^1.3.1",
"object-assign": "^4.1.1"
"regenerator-runtime": "^0.13.2"
}
},
"dom-helpers": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-3.4.0.tgz",
"integrity": "sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA==",
"requires": {
"@babel/runtime": "^7.1.2"
}
},
"loose-envify": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
"requires": {
"js-tokens": "^3.0.0 || ^4.0.0"
}
},
"prop-types": {
"version": "15.7.2",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz",
"integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==",
"requires": {
"loose-envify": "^1.4.0",
"object-assign": "^4.1.1",
"react-is": "^16.8.1"
}
},
"react-is": {
"version": "16.8.6",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.8.6.tgz",
"integrity": "sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA=="
},
"regenerator-runtime": {
"version": "0.13.2",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.2.tgz",
"integrity": "sha512-S/TQAZJO+D3m9xeN1WTI8dLKBBiRgXBlTJvbWjCThHWZj9EvHK70Ff50/tYj2J/fvBY6JtFVwRuazHN2E7M9BA=="
}
}
},

View File

@ -44,15 +44,16 @@
"react": "^16.8.6",
"react-calendar-heatmap": "^1.8.0",
"react-dom": "^16.8.6",
"react-instantsearch-dom": "^5.5.0",
"react-ga": "^2.5.7",
"react-helmet": "^5.2.0",
"react-instantsearch-dom": "^5.5.0",
"react-monaco-editor": "^0.25.1",
"react-redux": "^5.0.7",
"react-reflex": "^3.0.13",
"react-responsive": "^6.1.1",
"react-spinkit": "^3.0.0",
"react-stripe-elements": "^2.0.3",
"react-transition-group": "^4.1.0",
"react-youtube": "^7.9.0",
"redux": "^4.0.1",
"redux-actions": "^2.6.5",

View File

@ -1,7 +1,24 @@
.flash-message {
display: flex;
justify-content: space-around;
align-items: center;
flex-direction: row-reverse;
margin-bottom: 0px;
position: fixed;
width: 100%;
z-index: 1000;
}
.flash-message-enter {
opacity: 0;
}
.flash-message-enter-active {
opacity: 1;
transition: opacity 500ms;
}
.flash-message-exit {
opacity: 1;
}
.flash-message-exit-active {
opacity: 0;
transition: opacity 100ms;
}

View File

@ -1,35 +1,30 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Alert } from '@freecodecamp/react-bootstrap';
import { TransitionGroup, CSSTransition } from 'react-transition-group';
import './flash.css';
function createDismissHandler(fn, id) {
return () => fn(id);
}
function Flash({ messages, onClose }) {
return messages.map(({ type, message, id }) => (
<Alert
bsStyle={type}
className='flash-message'
key={id}
onDismiss={createDismissHandler(onClose, id)}
>
<div dangerouslySetInnerHTML={{ __html: message }} />
</Alert>
));
function Flash({ flashMessage, onClose }) {
const { type, message, id } = flashMessage;
return (
<TransitionGroup>
<CSSTransition classNames='flash-message' key={id} timeout={500}>
<Alert bsStyle={type} className='flash-message' onDismiss={onClose}>
<div dangerouslySetInnerHTML={{ __html: message }} />
</Alert>
</CSSTransition>
</TransitionGroup>
);
}
Flash.displayName = 'FlashMessages';
Flash.propTypes = {
messages: PropTypes.arrayOf(
PropTypes.shape({
id: PropTypes.string,
type: PropTypes.string,
message: PropTypes.string
})
),
flashMessage: PropTypes.shape({
id: PropTypes.string,
type: PropTypes.string,
message: PropTypes.string
}),
onClose: PropTypes.func.isRequired
};

View File

@ -6,7 +6,7 @@ import { createTypes } from '../../../utils/createTypes';
export const ns = 'flash';
const initialState = {
messages: []
message: {}
};
const types = createTypes(['createFlashMessage', 'removeFlashMessage'], ns);
@ -19,17 +19,17 @@ export const createFlashMessage = createAction(
);
export const removeFlashMessage = createAction(types.removeFlashMessage);
export const flashMessagesSelector = state => state[ns].messages;
export const flashMessageSelector = state => state[ns].message;
export const reducer = handleActions(
{
[types.createFlashMessage]: (state, { payload }) => ({
...state,
messages: [...state.messages, payload]
message: payload
}),
[types.removeFlashMessage]: (state, { payload }) => ({
[types.removeFlashMessage]: state => ({
...state,
messages: state.messages.filter(msg => msg.id !== payload)
message: {}
})
},
initialState

View File

@ -13,7 +13,7 @@ import {
onlineStatusChange,
isOnlineSelector
} from '../../redux';
import { flashMessagesSelector, removeFlashMessage } from '../Flash/redux';
import { flashMessageSelector, removeFlashMessage } from '../Flash/redux';
import { isBrowser } from '../../../utils';
@ -58,14 +58,12 @@ const metaKeywords = [
const propTypes = {
children: PropTypes.node.isRequired,
fetchUser: PropTypes.func.isRequired,
flashMessages: PropTypes.arrayOf(
PropTypes.shape({
id: PropTypes.string,
type: PropTypes.string,
message: PropTypes.string
})
),
hasMessages: PropTypes.bool,
flashMessage: PropTypes.shape({
id: PropTypes.string,
type: PropTypes.string,
message: PropTypes.string
}),
hasMessage: PropTypes.bool,
isOnline: PropTypes.bool.isRequired,
isSignedIn: PropTypes.bool,
landingPage: PropTypes.bool,
@ -78,12 +76,12 @@ const propTypes = {
const mapStateToProps = createSelector(
isSignedInSelector,
flashMessagesSelector,
flashMessageSelector,
isOnlineSelector,
(isSignedIn, flashMessages, isOnline) => ({
(isSignedIn, flashMessage, isOnline) => ({
isSignedIn,
flashMessages,
hasMessages: !!flashMessages.length,
flashMessage,
hasMessage: !!flashMessage.message,
isOnline
})
);
@ -128,8 +126,8 @@ class DefaultLayout extends Component {
render() {
const {
children,
hasMessages,
flashMessages = [],
hasMessage,
flashMessage,
isOnline,
isSignedIn,
landingPage,
@ -162,8 +160,8 @@ class DefaultLayout extends Component {
className={`default-layout ${landingPage ? 'landing-page' : ''}`}
>
<OfflineWarning isOnline={isOnline} isSignedIn={isSignedIn} />
{hasMessages ? (
<Flash messages={flashMessages} onClose={removeFlashMessage} />
{hasMessage && flashMessage ? (
<Flash flashMessage={flashMessage} onClose={removeFlashMessage} />
) : null}
{children}
</div>