Commit df6d22d3 authored by Andrew Hrdy's avatar Andrew Hrdy

Loading spinner, swipe to close, sidebar animation

parent 46604bd5
image: node:8.11.2
image: node:10.16.0
stages:
- build
......
......@@ -9547,6 +9547,14 @@
"tiny-warning": "^1.0.0"
}
},
"react-swipeable": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/react-swipeable/-/react-swipeable-5.5.0.tgz",
"integrity": "sha512-izPgNufVnm8zA7jpYhTwWJRfY2KIXUMWas8TcnoPODGA1EpgR5HaaTaR9uxyOgq00DVzOFvtE9IWEaBvtVVheA==",
"requires": {
"prop-types": "^15.6.2"
}
},
"react-transition-group": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.3.0.tgz",
......
import * as React from 'react';
import { CircularProgress, Typography } from '@material-ui/core';
const LoadingSpinner = () => (
<div className={'loading-spinner-container'}>
<CircularProgress className={'loading-spinner'} />
<Typography className={'loading-text'} variant={'h6'}>
Loading
</Typography>
</div>
);
export default LoadingSpinner;
\ No newline at end of file
......@@ -2,7 +2,6 @@ import * as React from 'react';
import { removeBrackets } from '../utils/nameUtils';
import * as phoneFormatter from 'phone-formatter';
import { IFacility } from '../models/facility.model';
import { Link } from 'react-router-dom';
import TextwTitle from './TextwTitle';
import WeekHours from './WeekHours';
......@@ -13,6 +12,8 @@ import Typography from '@material-ui/core/Typography';
import Divider from '@material-ui/core/Divider';
import CloseIcon from '@material-ui/icons/Close';
import IconButton from '@material-ui/core/IconButton';
import Slide from '@material-ui/core/Slide';
import classNames = require('classnames');
class Sidebar extends React.Component<SidebarProps> {
......@@ -21,20 +22,20 @@ class Sidebar extends React.Component<SidebarProps> {
}
render() {
const {facility, closeSidebar} = this.props;
const {facility, isVisible, closeSidebar} = this.props;
return (
<div className={'sidebar-container'}>
<Paper className={'sidebar-root'}>
<div className={classNames('sidebar-container', isVisible && 'sidebar-container-open')}>
<Paper className={classNames('sidebar-root', isVisible ? 'sidebar-root-open' : 'sidebar-root-closed')}>
<IconButton onClick={closeSidebar} className={'sidebar-close-btn'}>
<CloseIcon />
</IconButton>
<div className={'sidebar-row1'}>
<Avatar className={'sidebar-avatar'} src={facility.logo} />
<Avatar className={'sidebar-avatar'} src={facility?.logo} />
<div className={'sidebar-title'}>
<Typography variant="h4">{removeBrackets(facility.facility_name)}</Typography>
<Typography variant="h4">{removeBrackets(facility?.facility_name)}</Typography>
</div>
</div>
......@@ -43,11 +44,11 @@ class Sidebar extends React.Component<SidebarProps> {
<div className={'sidebar-scroll'}>
<div className={'sidebar-label-holder'}>
<TextwTitle label="Building"
content={facility.facility_location && facility.facility_location.building} />
content={facility?.facility_location && facility?.facility_location.building} />
<TextwTitle label="Address"
content={facility.facility_location && facility.facility_location.address} />
content={facility?.facility_location && facility?.facility_location.address} />
<TextwTitle label="Phone Number"
content={facility.phone_number ? phoneFormatter.format(facility.phone_number, '(NNN) NNN-NNNN') : 'Unknown'} />
content={facility?.phone_number ? phoneFormatter.format(facility?.phone_number, '(NNN) NNN-NNNN') : 'Unknown'} />
<TextwTitle label="Hours" content={<WeekHours facility={facility} />} />
</div>
</div>
......@@ -59,6 +60,7 @@ class Sidebar extends React.Component<SidebarProps> {
export interface SidebarProps {
facility: IFacility;
isVisible: boolean;
closeSidebar: () => void;
}
......
import * as React from 'react';
import { connect } from 'react-redux';
import { IFacility, CampusRegion } from '../models/facility.model';
import { useSelector, useDispatch } from 'react-redux';
import { IFacility } from '../models/facility.model';
import { ApplicationState } from '../store';
import { Dispatch } from 'redux';
import { fetchFacilities } from '../store/facility/facility.actions';
import { fetchAlerts } from '../store/alert/alert.actions';
import facilityUtils from '../utils/facilityUtils';
......@@ -11,10 +10,67 @@ import CardContainer from '../components/CardContainer';
import AppBar from '../components/AppBar';
import Sidebar from '../components/Sidebar';
import { setSelectedFacility } from '../store/ui/ui.actions';
import LoadingSpinner from '../components/LoadingSpinner';
import { SearchBarState } from '../store/ui/ui.reducer';
class DesktopLayout extends React.Component<DesktopLayoutProps> {
const DesktopLayout2 = () => {
const facilities: IFacility[] = useSelector((state: ApplicationState) => state.facilities.facilities);
const searchTerm: SearchBarState = useSelector((state: ApplicationState) => state.ui.search);
const selectedFacilitySlug: string = useSelector((state: ApplicationState) => state.ui.selectedFacility);
const isFetching = useSelector((state: ApplicationState) => state.facilities.isFetching);
const dispatch = useDispatch();
const fetch = () => {
dispatch(fetchFacilities());
dispatch(fetchAlerts());
};
const selectFacility = (slug: string) => dispatch(setSelectedFacility(slug));
const selectedFacility = facilities.find(f => f.slug === selectedFacilitySlug);
const [sidebarFacility, setSidebarFacility] = React.useState(selectedFacility);
if ((sidebarFacility === undefined && selectedFacility !== undefined) || (selectedFacility !== undefined && sidebarFacility.slug !== selectedFacility.slug)) {
setSidebarFacility(selectedFacility);
}
React.useEffect(() => {
fetch();
}, []);
const closeSidebar = () => {
selectFacility('');
};
const showSpinner = isFetching && (!facilities || facilities.length === 0);
return (
<div className={'layout-root'}>
<AppBar />
{showSpinner && <LoadingSpinner />}
<div className={'layout-container'}>
<div className={'layout-main-content'}>
<div className={'layout-card-container'}>
<CardContainer facilities={facilityUtils.filterFacilities(facilities, searchTerm.searchTerm, searchTerm.campusRegion)} showFavoriteIcons={true} />
</div>
</div>
<Sidebar facility={sidebarFacility} closeSidebar={closeSidebar} isVisible={selectedFacility !== undefined} />
</div>
</div>
);
};
/*class DesktopLayout extends React.Component<DesktopLayoutProps, DesktopLayoutState> {
constructor(props: DesktopLayoutProps) {
super(props);
this.state = {
sidebarFacility: undefined
};
}
componentWillMount = () => {
......@@ -23,12 +79,22 @@ class DesktopLayout extends React.Component<DesktopLayoutProps> {
}
render() {
const {facilities, selectedFacility, searchTerm, campusRegion, closeSidebar} = this.props;
const {facilities, isFetching, selectedFacility, searchTerm, campusRegion, closeSidebar} = this.props;
const showSpinner = isFetching && (!facilities || facilities.length === 0);
if ((this.state.sidebarFacility === undefined && selectedFacility !== '') || (selectedFacility !== '' && this.state.sidebarFacility.slug !== selectedFacility)) {
this.setState({
sidebarFacility: facilities.find(facility => facility.slug === selectedFacility)
});
}
return (
<div className={'layout-root'}>
<AppBar />
{showSpinner && <LoadingSpinner />}
<div className={'layout-container'}>
<div className={'layout-main-content'}>
<div className={'layout-card-container'}>
......@@ -36,9 +102,7 @@ class DesktopLayout extends React.Component<DesktopLayoutProps> {
</div>
</div>
{
selectedFacility !== '' && <Sidebar facility={facilities.find(facility => facility.slug === selectedFacility)} closeSidebar={closeSidebar} />
}
<Sidebar facility={this.state.sidebarFacility} closeSidebar={closeSidebar} isVisible={selectedFacility !== ''} />
</div>
</div>
);
......@@ -47,6 +111,7 @@ class DesktopLayout extends React.Component<DesktopLayoutProps> {
export interface DesktopLayoutProps {
facilities: IFacility[];
isFetching: boolean;
selectedFacility: string;
searchTerm: string;
campusRegion: CampusRegion;
......@@ -55,8 +120,13 @@ export interface DesktopLayoutProps {
closeSidebar: () => void;
}
export interface DesktopLayoutState {
sidebarFacility: IFacility;
}
const mapStateToProps = (state: ApplicationState) => ({
facilities: state.facilities.facilities,
isFetching: state.facilities.isFetching,
selectedFacility: state.ui.selectedFacility,
searchTerm: state.ui.search.searchTerm,
campusRegion: state.ui.search.campusRegion
......@@ -68,4 +138,6 @@ const mapDispatchToProps = (dispatch: Dispatch) => ({
closeSidebar: () => dispatch(setSelectedFacility(''))
});
export default connect(mapStateToProps, mapDispatchToProps)(DesktopLayout);
export default connect(mapStateToProps, mapDispatchToProps)(DesktopLayout);*/
export default DesktopLayout2;
......@@ -23,8 +23,6 @@ const FacilityDetail = (props: FacilityDetailProps) => {
const removeFavorite = (slug: string) => dispatch(removeFavoriteFacility(slug));
return (
<Paper className={'detail-paper'}>
<FavoriteButton slug={props.facility.slug} initialState={isFavorite}
......
......@@ -11,6 +11,8 @@ import FacilityDetail from './FacilityDetail';
import { Drawer } from '@material-ui/core';
import { SearchBarState } from '../store/ui/ui.reducer';
import { setSelectedFacility } from '../store/ui/ui.actions';
import { useSwipeable } from 'react-swipeable';
import LoadingSpinner from '../components/LoadingSpinner';
const MobileLayout = () => {
......@@ -18,6 +20,8 @@ const MobileLayout = () => {
const searchTerm: SearchBarState = useSelector((state: ApplicationState) => state.ui.search);
const selectedFacilitySlug: string = useSelector((state: ApplicationState) => state.ui.selectedFacility);
const isFetching = useSelector((state: ApplicationState) => state.facilities.isFetching);
const dispatch = useDispatch();
const fetch = () => {
dispatch(fetchFacilities());
......@@ -41,13 +45,22 @@ const MobileLayout = () => {
selectFacility('');
};
const handlers = useSwipeable({
onSwipedDown: () => closeDrawer(),
preventDefaultTouchmoveEvent: true
});
const showSpinner = isFetching && (!facilities || facilities.length === 0);
return (
<div className={'mobile-layout-root'}>
<AppBar isMobile={true} />
{showSpinner && <LoadingSpinner />}
<CardContainer facilities={facilityUtils.filterFacilities(facilities, searchTerm.searchTerm, searchTerm.campusRegion)} showFavoriteIcons={false} />
<Drawer anchor={'bottom'} open={selectedFacilitySlug !== ''} onClose={closeDrawer} transitionDuration={250}>
<Drawer {...handlers} anchor={'bottom'} open={selectedFacilitySlug !== ''} onClose={closeDrawer} transitionDuration={250}>
<FacilityDetail facility={drawerFacility} onClose={closeDrawer} />
</Drawer>
</div>
......
.loading-spinner-container {
height: 100vh;
width: 100vw;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
position: fixed;
top: 0
}
.loading-spinner {
width: 40% !important;
height: auto !important;
max-width: 100px;
margin-bottom: 10px;
@media (prefers-color-scheme: dark) {
color: white !important;
}
}
@keyframes ellipsis {
to {
width: 20px;
}
}
.loading-text {
@media (prefers-color-scheme: dark) {
color: white;
}
&:after {
overflow: hidden;
display: inline-block;
vertical-align: bottom;
animation: ellipsis steps(4, end) 1.5s infinite;
content: "\2026";
width: 0px;
}
}
\ No newline at end of file
......@@ -23,7 +23,15 @@
height: calc(100% - 64px);
width: 400px;
@include transition(300ms cubic-bezier(0.820, 0.165, 0.340, 0.930));
@include transition(300ms cubic-bezier(0.820, 0.165, 0.340, 0.930) !important);
&.sidebar-root-closed {
transform: translateX(400px);
}
&.sidebar-root-open {
transform: translateX(0);
}
}
.sidebar-close-btn {
......@@ -40,7 +48,11 @@
}
.sidebar-container {
flex: 1 0 400px;
transition-delay: .1s;
&.sidebar-container-open {
flex: 1 0 400px;
}
}
.sidebar-divider {
......
......@@ -7,6 +7,7 @@
@import './components/facilityCategory';
@import './components/facilityStatus';
@import './components/favoriteButton';
@import './components//loadingSpinner.scss';
@import './components/sidebar';
@import './components/textwTitle';
@import './components/weekHours';
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment