Make layout responsive

This commit is contained in:
Yasa Akbulut 2020-03-13 02:01:49 +01:00 committed by Jannis Mattheis
parent e858d5ad3f
commit c46bbdc01f
3 changed files with 226 additions and 89 deletions

View File

@ -1,7 +1,7 @@
import AppBar from '@material-ui/core/AppBar'; import AppBar from '@material-ui/core/AppBar';
import Button from '@material-ui/core/Button'; import Button from '@material-ui/core/Button';
import IconButton from '@material-ui/core/IconButton'; import IconButton from '@material-ui/core/IconButton';
import {Theme, WithStyles, withStyles} from '@material-ui/core/styles'; import {createStyles, Theme, WithStyles, withStyles} from '@material-ui/core/styles';
import Toolbar from '@material-ui/core/Toolbar'; import Toolbar from '@material-ui/core/Toolbar';
import Typography from '@material-ui/core/Typography'; import Typography from '@material-ui/core/Typography';
import AccountCircle from '@material-ui/icons/AccountCircle'; import AccountCircle from '@material-ui/icons/AccountCircle';
@ -10,31 +10,59 @@ import DevicesOther from '@material-ui/icons/DevicesOther';
import ExitToApp from '@material-ui/icons/ExitToApp'; import ExitToApp from '@material-ui/icons/ExitToApp';
import Highlight from '@material-ui/icons/Highlight'; import Highlight from '@material-ui/icons/Highlight';
import GitHubIcon from '@material-ui/icons/GitHub'; import GitHubIcon from '@material-ui/icons/GitHub';
import MenuIcon from '@material-ui/icons/Menu';
import Apps from '@material-ui/icons/Apps'; import Apps from '@material-ui/icons/Apps';
import SupervisorAccount from '@material-ui/icons/SupervisorAccount'; import SupervisorAccount from '@material-ui/icons/SupervisorAccount';
import React, {Component, CSSProperties} from 'react'; import React, {Component, CSSProperties} from 'react';
import {Link} from 'react-router-dom'; import {Link} from 'react-router-dom';
import {observer} from 'mobx-react'; import {observer} from 'mobx-react';
import {Hidden, PropTypes, withWidth} from '@material-ui/core';
import {Breakpoint} from '@material-ui/core/styles/createBreakpoints';
const styles = (theme: Theme) => ({ const styles = (theme: Theme) =>
appBar: { createStyles({
zIndex: theme.zIndex.drawer + 1, appBar: {
}, zIndex: theme.zIndex.drawer + 1,
title: { [theme.breakpoints.down('xs')]: {
flex: 1, paddingBottom: 10,
display: 'flex', },
alignItems: 'center', },
}, toolbar: {
titleName: { justifyContent: 'space-between',
paddingRight: 10, [theme.breakpoints.down('xs')]: {
}, flexWrap: 'wrap',
link: { },
color: 'inherit', },
textDecoration: 'none', menuButtons: {
}, display: 'flex',
}); [theme.breakpoints.down('sm')]: {
flex: 1,
},
justifyContent: 'center',
[theme.breakpoints.down('xs')]: {
flexBasis: '100%',
marginTop: 5,
order: 1,
justifyContent: 'space-between',
},
},
title: {
[theme.breakpoints.up('md')]: {
flex: 1,
},
display: 'flex',
alignItems: 'center',
},
titleName: {
paddingRight: 10,
},
link: {
color: 'inherit',
textDecoration: 'none',
},
});
type Styles = WithStyles<'link' | 'titleName' | 'title' | 'appBar'>; type Styles = WithStyles<'link' | 'menuButtons' | 'toolbar' | 'titleName' | 'title' | 'appBar'>;
interface IProps extends Styles { interface IProps extends Styles {
loggedIn: boolean; loggedIn: boolean;
@ -45,16 +73,31 @@ interface IProps extends Styles {
showSettings: VoidFunction; showSettings: VoidFunction;
logout: VoidFunction; logout: VoidFunction;
style: CSSProperties; style: CSSProperties;
width: Breakpoint;
setNavOpen: (open: boolean) => void;
} }
@observer @observer
class Header extends Component<IProps> { class Header extends Component<IProps> {
public render() { public render() {
const {classes, version, name, loggedIn, admin, toggleTheme, logout, style} = this.props; const {
classes,
version,
name,
loggedIn,
admin,
toggleTheme,
logout,
style,
setNavOpen,
width,
} = this.props;
const position = width === 'xs' ? 'sticky' : 'fixed';
return ( return (
<AppBar position="absolute" style={style} className={classes.appBar}> <AppBar position={position} style={style} className={classes.appBar}>
<Toolbar> <Toolbar className={classes.toolbar}>
<div className={classes.title}> <div className={classes.title}>
<Link to="/" className={classes.link}> <Link to="/" className={classes.link}>
<Typography variant="h5" className={classes.titleName} color="inherit"> <Typography variant="h5" className={classes.titleName} color="inherit">
@ -69,65 +112,108 @@ class Header extends Component<IProps> {
</Typography> </Typography>
</a> </a>
</div> </div>
{loggedIn && this.renderButtons(name, admin, logout)} {loggedIn && this.renderButtons(name, admin, logout, width, setNavOpen)}
<IconButton onClick={toggleTheme} color="inherit"> <div>
<Highlight /> <IconButton onClick={toggleTheme} color="inherit">
</IconButton> <Highlight />
<a href="https://github.com/gotify/server" className={classes.link}>
<IconButton color="inherit">
<GitHubIcon />
</IconButton> </IconButton>
</a>
<a href="https://github.com/gotify/server" className={classes.link}>
<IconButton color="inherit">
<GitHubIcon />
</IconButton>
</a>
</div>
</Toolbar> </Toolbar>
</AppBar> </AppBar>
); );
} }
private renderButtons(name: string, admin: boolean, logout: VoidFunction) { private renderButtons(
name: string,
admin: boolean,
logout: VoidFunction,
width: Breakpoint,
setNavOpen: (open: boolean) => void
) {
const {classes, showSettings} = this.props; const {classes, showSettings} = this.props;
return ( return (
<div> <div className={classes.menuButtons}>
{admin ? ( <Hidden smUp implementation="css">
<ResponsiveButton
icon={<MenuIcon />}
onClick={() => setNavOpen(true)}
label="menu"
width={width}
color="inherit"
/>
</Hidden>
{admin && (
<Link className={classes.link} to="/users" id="navigate-users"> <Link className={classes.link} to="/users" id="navigate-users">
<Button color="inherit"> <ResponsiveButton
<SupervisorAccount /> icon={<SupervisorAccount />}
&nbsp;users label="users"
</Button> width={width}
color="inherit"
/>
</Link> </Link>
) : (
''
)} )}
<Link className={classes.link} to="/applications" id="navigate-apps"> <Link className={classes.link} to="/applications" id="navigate-apps">
<Button color="inherit"> <ResponsiveButton icon={<Chat />} label="apps" width={width} color="inherit" />
<Chat />
&nbsp;apps
</Button>
</Link> </Link>
<Link className={classes.link} to="/clients" id="navigate-clients"> <Link className={classes.link} to="/clients" id="navigate-clients">
<Button color="inherit"> <ResponsiveButton
<DevicesOther /> icon={<DevicesOther />}
&nbsp;clients label="clients"
</Button> width={width}
color="inherit"
/>
</Link> </Link>
<Link className={classes.link} to="/plugins" id="navigate-plugins"> <Link className={classes.link} to="/plugins" id="navigate-plugins">
<Button color="inherit"> <ResponsiveButton
<Apps /> icon={<Apps />}
&nbsp;plugins label="plugins"
</Button> width={width}
color="inherit"
/>
</Link> </Link>
<Button color="inherit" onClick={showSettings} id="changepw"> <ResponsiveButton
<AccountCircle /> icon={<AccountCircle />}
&nbsp; label={name}
{name} onClick={showSettings}
</Button> id="changepw"
<Button color="inherit" onClick={logout} id="logout"> width={width}
<ExitToApp /> color="inherit"
&nbsp;Logout />
</Button> <ResponsiveButton
icon={<ExitToApp />}
label="Logout"
onClick={logout}
id="logout"
width={width}
color="inherit"
/>
</div> </div>
); );
} }
} }
export default withStyles(styles, {withTheme: true})(Header); const ResponsiveButton: React.FC<{
width: Breakpoint;
color: PropTypes.Color;
label: string;
id?: string;
onClick?: () => void;
icon: React.ReactNode;
}> = ({width, icon, children, label, ...rest}) => {
if (width === 'xs' || width === 'sm') {
return <IconButton {...rest}>{icon}</IconButton>;
}
return (
<Button startIcon={icon} {...rest}>
{label}
</Button>
);
};
export default withWidth()(withStyles(styles, {withTheme: true})(Header));

View File

@ -29,6 +29,9 @@ const styles = (theme: Theme) => ({
marginTop: 64, marginTop: 64,
padding: theme.spacing(4), padding: theme.spacing(4),
width: '100%', width: '100%',
[theme.breakpoints.down('xs')]: {
marginTop: 0,
},
}, },
}); });
@ -63,6 +66,12 @@ class Layout extends React.Component<
private showSettings = false; private showSettings = false;
@observable @observable
private version = Layout.defaultVersion; private version = Layout.defaultVersion;
@observable
private navOpen = false;
private setNavOpen(open: boolean) {
this.navOpen = open;
}
public componentDidMount() { public componentDidMount() {
if (this.version === Layout.defaultVersion) { if (this.version === Layout.defaultVersion) {
@ -105,7 +114,7 @@ class Layout extends React.Component<
message={connectionErrorMessage} message={connectionErrorMessage}
/> />
)} )}
<div style={{display: 'flex'}}> <div style={{display: 'flex', flexDirection: 'column'}}>
<CssBaseline /> <CssBaseline />
<Header <Header
style={{top: !connectionErrorMessage ? 0 : 64}} style={{top: !connectionErrorMessage ? 0 : 64}}
@ -116,27 +125,41 @@ class Layout extends React.Component<
toggleTheme={this.toggleTheme.bind(this)} toggleTheme={this.toggleTheme.bind(this)}
showSettings={() => (this.showSettings = true)} showSettings={() => (this.showSettings = true)}
logout={logout} logout={logout}
setNavOpen={this.setNavOpen.bind(this)}
/> />
<Navigation loggedIn={loggedIn} /> <div style={{display: 'flex'}}>
<Navigation
<main className={classes.content}> loggedIn={loggedIn}
<Switch> navOpen={this.navOpen}
{authenticating ? ( setNavOpen={this.setNavOpen.bind(this)}
<Route path="/"> />
<LoadingSpinner /> <main className={classes.content}>
</Route> <Switch>
) : null} {authenticating ? (
<Route exact path="/login" render={loginRoute} /> <Route path="/">
{loggedIn ? null : <Redirect to="/login" />} <LoadingSpinner />
<Route exact path="/" component={Messages} /> </Route>
<Route exact path="/messages/:id" component={Messages} /> ) : null}
<Route exact path="/applications" component={Applications} /> <Route exact path="/login" render={loginRoute} />
<Route exact path="/clients" component={Clients} /> {loggedIn ? null : <Redirect to="/login" />}
<Route exact path="/users" component={Users} /> <Route exact path="/" component={Messages} />
<Route exact path="/plugins" component={Plugins} /> <Route exact path="/messages/:id" component={Messages} />
<Route exact path="/plugins/:id" component={PluginDetailView} /> <Route
</Switch> exact
</main> path="/applications"
component={Applications}
/>
<Route exact path="/clients" component={Clients} />
<Route exact path="/users" component={Users} />
<Route exact path="/plugins" component={Plugins} />
<Route
exact
path="/plugins/:id"
component={PluginDetailView}
/>
</Switch>
</main>
</div>
{showSettings && ( {showSettings && (
<SettingsDialog fClose={() => (this.showSettings = false)} /> <SettingsDialog fClose={() => (this.showSettings = false)} />
)} )}

View File

@ -8,7 +8,9 @@ import {Link} from 'react-router-dom';
import {observer} from 'mobx-react'; import {observer} from 'mobx-react';
import {inject, Stores} from '../inject'; import {inject, Stores} from '../inject';
import {mayAllowPermission, requestPermission} from '../snack/browserNotification'; import {mayAllowPermission, requestPermission} from '../snack/browserNotification';
import {Button, Typography} from '@material-ui/core'; import {Button, Hidden, IconButton, Typography} from '@material-ui/core';
import {DrawerProps} from '@material-ui/core/Drawer/Drawer';
import CloseIcon from '@material-ui/icons/Close';
const styles = (theme: Theme): StyleRules<'drawerPaper' | 'toolbar' | 'link'> => ({ const styles = (theme: Theme): StyleRules<'drawerPaper' | 'toolbar' | 'link'> => ({
drawerPaper: { drawerPaper: {
@ -28,6 +30,8 @@ type Styles = WithStyles<'drawerPaper' | 'toolbar' | 'link'>;
interface IProps { interface IProps {
loggedIn: boolean; loggedIn: boolean;
navOpen: boolean;
setNavOpen: (open: boolean) => void;
} }
@observer @observer
@ -38,7 +42,7 @@ class Navigation extends Component<
public state = {showRequestNotification: mayAllowPermission()}; public state = {showRequestNotification: mayAllowPermission()};
public render() { public render() {
const {classes, loggedIn, appStore} = this.props; const {classes, loggedIn, appStore, navOpen, setNavOpen} = this.props;
const {showRequestNotification} = this.state; const {showRequestNotification} = this.state;
const apps = appStore.getItems(); const apps = appStore.getItems();
@ -48,6 +52,7 @@ class Navigation extends Component<
: apps.map((app) => { : apps.map((app) => {
return ( return (
<Link <Link
onClick={() => setNavOpen(false)}
className={`${classes.link} item`} className={`${classes.link} item`}
to={'/messages/' + app.id} to={'/messages/' + app.id}
key={app.id}> key={app.id}>
@ -68,12 +73,13 @@ class Navigation extends Component<
]; ];
return ( return (
<Drawer <ResponsiveDrawer
variant="permanent"
classes={{paper: classes.drawerPaper}} classes={{paper: classes.drawerPaper}}
navOpen={navOpen}
setNavOpen={setNavOpen}
id="message-navigation"> id="message-navigation">
<div className={classes.toolbar} /> <div className={classes.toolbar} />
<Link className={classes.link} to="/"> <Link className={classes.link} to="/" onClick={() => setNavOpen(false)}>
<ListItem button disabled={!loggedIn} className="all"> <ListItem button disabled={!loggedIn} className="all">
<ListItemText primary="All Messages" /> <ListItemText primary="All Messages" />
</ListItem> </ListItem>
@ -92,9 +98,31 @@ class Navigation extends Component<
</Button> </Button>
) : null} ) : null}
</Typography> </Typography>
</Drawer> </ResponsiveDrawer>
); );
} }
} }
const ResponsiveDrawer: React.FC<
DrawerProps & {navOpen: boolean; setNavOpen: (open: boolean) => void}
> = ({navOpen, setNavOpen, children, ...rest}) => {
return (
<>
<Hidden smUp implementation="css">
<Drawer variant="temporary" open={navOpen} {...rest}>
<IconButton onClick={() => setNavOpen(false)}>
<CloseIcon />
</IconButton>
{children}
</Drawer>
</Hidden>
<Hidden xsDown implementation="css">
<Drawer variant="permanent" {...rest}>
{children}
</Drawer>
</Hidden>
</>
);
};
export default withStyles(styles, {withTheme: true})(inject('appStore')(Navigation)); export default withStyles(styles, {withTheme: true})(inject('appStore')(Navigation));