Skip to content

Commit

Permalink
app: Move navigation to side bar
Browse files Browse the repository at this point in the history
  • Loading branch information
evanpurkhiser committed Jan 3, 2021
1 parent 833dcf2 commit c3338c4
Show file tree
Hide file tree
Showing 7 changed files with 168 additions and 116 deletions.
2 changes: 1 addition & 1 deletion src/main/main.ts
Expand Up @@ -31,7 +31,7 @@ let win: BrowserWindow | null;

const createWindow = () => {
win = new BrowserWindow({
width: 850,
width: 920,
minWidth: 700,
height: 900,
titleBarStyle: 'hiddenInset',
Expand Down
9 changes: 6 additions & 3 deletions src/main/menu.ts
@@ -1,5 +1,4 @@
import {app, Menu, shell} from 'electron';
import {set} from 'mobx';

import store from 'src/shared/store';

Expand Down Expand Up @@ -44,12 +43,16 @@ const template: Electron.MenuItemConstructorOptions[] = [
{role: 'reload'},
{role: 'forceReload'},
{role: 'togglefullscreen'},
{
accelerator: 'cmd + option + s',
label: 'Toggle Sidebar',
click: () => store.config.toggleSidebar(),
},
{
visible: false,
accelerator: 'cmd + l',
label: 'Toggle UI Theme',
click: () =>
set(store.config, {theme: store.config.theme === 'light' ? 'dark' : 'light'}),
click: () => store.config.toggleTheme(),
},
],
},
Expand Down
150 changes: 65 additions & 85 deletions src/renderer/components/Navigation.tsx
@@ -1,115 +1,78 @@
import * as React from 'react';
import {Activity, Layers, Menu, Settings} from 'react-feather';
import {Activity, Layers, Settings} from 'react-feather';
import {NavLink, useLocation} from 'react-router-dom';
import styled from '@emotion/styled';
import {AnimatePresence, motion} from 'framer-motion';
import {AnimateSharedLayout, motion} from 'framer-motion';
import {observer} from 'mobx-react';

import useDropdown from 'src/utils/useDropdown';
import store from 'src/shared/store';

import HelpButton from './HelpButton';

const items = [
{name: 'Device Status', path: '/status', icon: Activity},
{name: 'Overlays', path: '/overlay-config', icon: Layers},
{name: 'Settings', path: '/settings', icon: Settings},
] as const;

const Navigation = () => {
const dropdownRef = React.useRef(null);
const actionRef = React.useRef(null);
const [isOpen, toggleDropdown] = useDropdown(dropdownRef, actionRef);

const location = useLocation();
const Navigation = observer(() => (
<MenuContainer>
<SidebarToggle onClick={() => store.config.toggleSidebar()} />
<AnimateSharedLayout>
{items.map(item => (
<MenuItem key={item.name} to={item.path} aria-current="page">
{item.path === useLocation().pathname && <ActiveIndicator layoutId="active" />}
<item.icon size="1rem" />
{!store.config.sidebarCollapsed && item.name}
</MenuItem>
))}
</AnimateSharedLayout>
<Bottom>
<HelpButton />
</Bottom>
</MenuContainer>
));

return (
<Container>
<MenuButton onClick={() => toggleDropdown()} ref={actionRef}>
{items.find(i => location?.pathname.startsWith(i.path))?.name}
<Menu size="1rem" />
</MenuButton>
<AnimatePresence>
{isOpen && (
<MenuContainer ref={dropdownRef} key={isOpen.toString()}>
{items.map(item => (
<MenuItem key={item.name} to={item.path} onClick={() => toggleDropdown()}>
<item.icon size="1rem" />
{item.name}
</MenuItem>
))}
</MenuContainer>
)}
</AnimatePresence>
</Container>
);
};

const Container = styled('div')`
const MenuContainer = styled(motion.nav)`
position: relative;
font-size: 0.8rem;
font-weight: 500;
height: 100%;
border-right: 1px solid ${p => p.theme.border};
display: flex;
align-items: center;
`;

const MenuButton = styled('button')`
border: none;
background: none;
padding: 0.5rem;
font-size: 0.7rem;
display: grid;
grid-auto-flow: column;
grid-auto-rows: max-content;
grid-gap: 0.5rem;
align-items: center;
text-transform: uppercase;
font-weight: 600;
opacity: 0.9;
&:hover {
opacity: 1;
}
flex-direction: column;
grid-gap: 0.125rem;
padding: 0.5rem 0;
`;

const MenuContainer = styled(motion.div)`
const SidebarToggle = styled('div')`
position: absolute;
top: 38px;
right: -10px;
background: ${p => p.theme.background};
display: grid;
grid-auto-flow: row;
grid-auto-rows: max-content;
grid-gap: 0.125rem;
padding: 0.25rem 0;
border: 1px solid ${p => p.theme.border};
border-radius: 3px;
top: 0;
bottom: 0;
right: -1px;
width: 2px;
z-index: 2;
transition: background 150ms ease-in-out;
cursor: pointer;
&:before,
&:after {
&:before {
content: '';
display: block;
width: 5px;
position: absolute;
top: -16px;
right: 18px;
border: 8px solid transparent;
border-bottom-color: ${p => p.theme.background};
top: 0;
bottom: 0;
left: -1px;
}
&:before {
margin-top: -1px;
border-bottom-color: ${p => p.theme.border};
&:hover {
transition-delay: 300ms;
background: #f95757;
}
`;

MenuContainer.defaultProps = {
initial: {opacity: 0, y: 5, originX: '80%', originY: 0},
animate: {opacity: 1, y: 0},
exit: {opacity: 0, scale: 0.95},
transition: {duration: 0.2},
};

const MenuItem = styled(NavLink)`
position: relative;
padding: 0.375rem 0.75rem;
display: grid;
grid-template-columns: max-content 1fr;
grid-gap: 0.5rem;
display: flex;
gap: 0.5rem;
align-items: center;
text-transform: uppercase;
font-weight: 600;
Expand All @@ -123,4 +86,21 @@ const MenuItem = styled(NavLink)`
}
`;

const ActiveIndicator = styled(motion.div)`
position: absolute;
background: ${p => p.theme.subText};
height: 10px;
margin: 0.125rem 0;
width: 2px;
border-radius: 2px;
left: 6px;
`;

const Bottom = styled('div')`
display: flex;
align-items: flex-end;
flex-grow: 1;
padding: 0 0.5rem;
`;

export default Navigation;
2 changes: 1 addition & 1 deletion src/renderer/components/NetworkStatus.tsx
Expand Up @@ -24,7 +24,7 @@ const StatusIndicator = styled('div')<{state: NetworkState}>`
padding: 0.25rem 0.5rem;
font-size: 0.625rem;
text-transform: uppercase;
border-radius: 2px;
border-radius: 4px;
`;

export default NetworkStatus;
59 changes: 45 additions & 14 deletions src/renderer/components/Titlebar.tsx
@@ -1,32 +1,63 @@
import React from 'react';
import {Save} from 'react-feather';
import styled from '@emotion/styled';

import Navigation from './Navigation';
import useRelease from 'src/utils/useLatestRelease';

import ActionButton from './ActionButton';
import NetworkStatus from './NetworkStatus';

const Toolbar = () => (
<Container>
<Navigation />
<NetworkStatus />
</Container>
);
const Toolbar = () => {
const latestRelease = useRelease();

const hasNewVersion =
latestRelease &&
process.env.RELEASE_CHANNEL === 'stable' &&
process.env.RELEASE !== latestRelease.name;

return (
<Container>
{hasNewVersion && latestRelease && (
<NewVersionButton onClick={() => location.assign(latestRelease.html_url)}>
<Save size="1rem" /> {latestRelease.name} available
</NewVersionButton>
)}
<Version>{process.env.RELEASE}</Version>
<NetworkStatus />
</Container>
);
};

const Version = styled('div')`
align-items: center;
font-size: 0.7rem;
color: ${p => p.theme.subText};
margin-top: 4px;
`;

const Container = styled('header')`
position: sticky;
z-index: 1;
top: 0;
height: 36px;
padding: 0 0.5rem;
padding-left: 75px;
display: grid;
justify-content: end;
grid-auto-flow: column;
grid-auto-columns: max-content;
grid-gap: 0.5rem;
display: flex;
justify-content: flex-end;
gap: 0.5rem;
align-items: center;
background: ${p => p.theme.backgroundSecondary};
transition: background 300ms;
border-bottom: 1px solid ${p => p.theme.border};
-webkit-app-region: drag;
`;

const NewVersionButton = styled(ActionButton)`
position: absolute;
bottom: 1rem;
right: 1rem;
background: #ef5f73;
color: #fff;
padding: 0.375rem 0.5rem;
font-size: 0.75rem;
`;

export default Toolbar;
44 changes: 33 additions & 11 deletions src/renderer/views/Application.tsx
@@ -1,31 +1,53 @@
import {MemoryRouter, Redirect, Route, Switch} from 'react-router-dom';
import styled from '@emotion/styled';

import Footer from 'app/components/Footer';
import Titlebar from 'app/components/Titlebar';
import Devices from 'app/views/devices';
import OverlayConfig from 'app/views/overlayConfig';
import Settings from 'app/views/settings';
import Navigation from 'src/renderer/components/Navigation';

const Application = () => (
<MemoryRouter>
<Titlebar />
<Frame>
<Switch>
<Redirect from="/" to="/status" exact />
<Route path="/status" component={Devices} />
<Route path="/overlay-config" component={OverlayConfig} />
<Route path="/settings" component={Settings} />
</Switch>
<Titlebar />
<Navigation />
<Content>
<Switch>
<Redirect from="/" to="/status" exact />
<Route path="/status" component={Devices} />
<Route path="/overlay-config" component={OverlayConfig} />
<Route path="/settings" component={Settings} />
</Switch>
</Content>
</Frame>
<Footer />
</MemoryRouter>
);

const Frame = styled('main')`
const Frame = styled('div')`
flex-grow: 1;
height: 0;
display: grid;
grid-template-columns: max-content 1fr;
grid-template-rows: max-content 1fr max-content;
grid-template-areas:
'header header'
'nav main';
> nav {
grid-area: nav;
}
> header {
grid-area: header;
}
`;

const Content = styled('main')`
position: relative;
display: flex;
flex-direction: column;
flex-grow: 1;
overflow: scroll;
`;

export default Application;

0 comments on commit c3338c4

Please sign in to comment.