Routing shell

This commit is contained in:
Max Lynch
2020-12-30 16:10:30 -06:00
parent 29ae24c6a6
commit 1a8d9c529e
7 changed files with 175 additions and 60 deletions
+50 -39
View File
@@ -1,5 +1,7 @@
import { useCallback, useEffect, useState } from 'react';
import { useDrag } from 'react-use-gesture'; import { useDrag } from 'react-use-gesture';
import { Route, Switch } from 'wouter'; import { Router, Route, Switch, useRoute } from 'wouter';
import { cog, cogOutline, home, homeOutline, list, listOutline } from 'ionicons/icons';
import Store from '../store'; import Store from '../store';
import * as actions from '../store/actions'; import * as actions from '../store/actions';
@@ -16,13 +18,16 @@ import TabBar from '../components/ui/TabBar';
import { SafeAreaProvider } from '../components/ui/SafeArea'; import { SafeAreaProvider } from '../components/ui/SafeArea';
import Notifications from '../components/Notifications'; import Notifications from '../components/Notifications';
import MenuContent from '../components/MenuContent'; import MenuContent from '../components/MenuContent';
import { useEffect, useState } from 'react';
import Home from './pages/Home'; import Home from './pages/Home';
import { cog, cogOutline, home, homeOutline, list, listOutline } from 'ionicons/icons'; import Lists from './pages/Lists';
import Settings from './pages/Settings';
import useLocation from '../hooks/useLocation';
const CurrentPage = ({ page }) => { const CurrentPage = ({ page }) => {
const currentPage = Store.useState(selectors.getCurrentPage); const currentPage = Store.useState(selectors.getCurrentPage);
const [match, params] = useRoute('/lists');
console.log('Matches?', match);
// const Page = currentPage.component; // const Page = currentPage.component;
const Page = page; const Page = page;
@@ -32,19 +37,23 @@ const CurrentPage = ({ page }) => {
setLocal(true); setLocal(true);
}, []); }, []);
console.log('Rendering current page', local);
return ( return (
<PageStack> <PageStack>
{local ? ( {local ? (
<Switch> <Switch>
<Route path="/" component={Home} /> <Route path="/" component={Home} />
<Route path="/lists" component={Lists} />
<Route path="/settings" component={Settings} />
</Switch> </Switch>
) : ( ) : (
<Page selected={true} /> <Page selected={true} />
)} )}
{/*pages.map(p => { {/*pages.map(p => {
const Page = p.component; const Page = p.component;
return <Page selected={page.id === p.id} key={p.id} />; return <Page selected={page.id === p.id} key={p.id} />;
})*/} })*/}
{/*<Page selected={true} />*/} {/*<Page selected={true} />*/}
</PageStack> </PageStack>
); );
@@ -87,39 +96,41 @@ const AppShell = ({ page }) => {
}} }}
> >
<SafeAreaProvider> <SafeAreaProvider>
<Menu open={showMenu} onClose={closeMenu}> <Router hook={useLocation}>
<MenuContent /> <Menu open={showMenu} onClose={closeMenu}>
</Menu> <MenuContent />
<Nav page={currentPage} /> </Menu>
{/*<CurrentPage page={currentPage} />*/} <Nav page={currentPage} />
<CurrentPage page={page} /> {/*<CurrentPage page={currentPage} />*/}
<TabBar> <CurrentPage page={page} />
<Tab <TabBar>
icon={homeOutline} <Tab
selectedIcon={home} icon={homeOutline}
title="Home" selectedIcon={home}
onClick={() => actions.setPage(p)} title="Home"
selected={'home' === currentPage?.id} href="/"
/> selected={'home' === currentPage?.id}
<Tab />
icon={listOutline} <Tab
selectedIcon={list} icon={listOutline}
title="Lists" selectedIcon={list}
onClick={() => actions.setPage(p)} title="Lists"
selected={'lists' === currentPage?.id} href="/lists"
/> selected={'lists' === currentPage?.id}
<Tab />
icon={cogOutline} <Tab
selectedIcon={cog} icon={cogOutline}
title="settings" selectedIcon={cog}
onClick={() => actions.setPage(p)} title="settings"
selected={'settings' === currentPage?.id} href="/settings"
/> selected={'settings' === currentPage?.id}
</TabBar> />
<Backdrop open={showMenu || showNotifications} onClose={backdropClose} /> </TabBar>
<Modal open={showNotifications} onClose={closeNotifications}> <Backdrop open={showMenu || showNotifications} onClose={backdropClose} />
<Notifications /> <Modal open={showNotifications} onClose={closeNotifications}>
</Modal> <Notifications />
</Modal>
</Router>
</SafeAreaProvider> </SafeAreaProvider>
</App> </App>
); );
+5 -2
View File
@@ -7,7 +7,10 @@ import List from '../ui/List';
import VirtualScroll from '../ui/VirtualScroll'; import VirtualScroll from '../ui/VirtualScroll';
const ListEntry = ({ list, ...props }) => ( const ListEntry = ({ list, ...props }) => (
<div {...props} className="p-4 border-solid dark:border-gray-800 border-b cursor-pointer dark:text-gray-200"> <div
{...props}
className="p-4 border-solid dark:border-gray-800 border-b cursor-pointer dark:text-gray-200"
>
<span className="text-md">{list.name}</span> <span className="text-md">{list.name}</span>
</div> </div>
); );
@@ -29,7 +32,7 @@ const AllLists = ({ onSelect }) => {
const Lists = ({ selected }) => { const Lists = ({ selected }) => {
return ( return (
<Content visible={selected} className="p-4 dark:bg-black"> <Content visible={true} className="p-4 dark:bg-black">
<List className="h-full w-full"> <List className="h-full w-full">
{selected && ( {selected && (
<AllLists <AllLists
+1 -1
View File
@@ -12,7 +12,7 @@ const Settings = ({ selected }) => {
const settings = Store.useState(selectors.getSettings); const settings = Store.useState(selectors.getSettings);
return ( return (
<Content visible={selected} className="p-4 dark:bg-black"> <Content visible={true} className="p-4 dark:bg-black">
<List> <List>
<ListItem className="flex"> <ListItem className="flex">
<span className="text-md flex-1 dark:text-gray-200">Enable Notifications</span> <span className="text-md flex-1 dark:text-gray-200">Enable Notifications</span>
+21 -18
View File
@@ -1,24 +1,27 @@
import classNames from 'classnames'; import classNames from 'classnames';
import Icon from './Icon'; import Icon from './Icon';
import { Link } from 'wouter';
const Tab = ({ title, icon, selected, selectedIcon, onClick }) => ( const Tab = ({ title, href, icon, selected, selectedIcon, onClick }) => (
<a <Link href={href}>
onClick={onClick} <a
href="#" onClick={onClick}
className={classNames('px-6 rounded-md text-sm text-center font-medium cursor-pointer', { href="#"
'text-gray-500 dark:text-white': !selected, className={classNames('px-6 rounded-md text-sm text-center font-medium cursor-pointer', {
'text-gray-800 dark:text-gray-600': selected, 'text-gray-500 dark:text-white': !selected,
})} 'text-gray-800 dark:text-gray-600': selected,
> })}
{icon && ( >
<Icon {icon && (
icon={selected ? selectedIcon : icon} <Icon
className="cursor-pointer" icon={selected ? selectedIcon : icon}
style={{ fontSize: '18px' }} className="cursor-pointer"
/> style={{ fontSize: '18px' }}
)} />
<label className="block cursor-pointer">{title}</label> )}
</a> <label className="block cursor-pointer">{title}</label>
</a>
</Link>
); );
export default Tab; export default Tab;
+86
View File
@@ -0,0 +1,86 @@
import { useEffect, useRef, useState, useCallback } from 'react';
/**
* History API docs @see https://developer.mozilla.org/en-US/docs/Web/API/History
*/
const eventPopstate = 'popstate';
const eventPushState = 'pushState';
const eventReplaceState = 'replaceState';
export const events = [eventPopstate, eventPushState, eventReplaceState];
const useLocation = ({ base = '' } = {}) => {
const getCurrentPathname = useCallback(base => {
const path = typeof location === 'undefined' ? '/' : location.pathname;
return !path.toLowerCase().indexOf(base.toLowerCase())
? path.slice(base.length) || '/'
: '~' + path;
}, []);
const [path, update] = useState(() => getCurrentPathname(base)); // @see https://reactjs.org/docs/hooks-reference.html#lazy-initial-state
const prevPath = useRef(path);
useEffect(() => {
// While History API does have `popstate` event, the only
// proper way to listen to changes via `push/replaceState`
// is to monkey-patch these methods.
//
// See https://stackoverflow.com/a/4585031
if (typeof history !== 'undefined') {
for (const type of [eventPushState, eventReplaceState]) {
const original = history[type];
history[type] = function () {
const result = original.apply(this, arguments);
const event = new Event(type);
event.arguments = arguments;
dispatchEvent(event);
return result;
};
}
} else {
console.log('No history API');
}
});
useEffect(() => {
// this function checks if the location has been changed since the
// last render and updates the state only when needed.
// unfortunately, we can't rely on `path` value here, since it can be stale,
// that's why we store the last pathname in a ref.
const checkForUpdates = () => {
const pathname = getCurrentPathname(base);
console.log('CHECK FOR UPDATES', pathname);
prevPath.current !== pathname && update((prevPath.current = pathname));
};
events.map(e => addEventListener(e, checkForUpdates));
// it's possible that an update has occurred between render and the effect handler,
// so we run additional check on mount to catch these updates. Based on:
// https://gist.github.com/bvaughn/e25397f70e8c65b0ae0d7c90b731b189
checkForUpdates();
return () => events.map(e => removeEventListener(e, checkForUpdates));
}, [base]);
// the 2nd argument of the `useLocation` return value is a function
// that allows to perform a navigation.
//
// the function reference should stay the same between re-renders, so that
// it can be passed down as an element prop without any performance concerns.
const navigate = useCallback(
(to, { replace = false } = {}) =>
history[replace ? eventReplaceState : eventPushState](
null,
'',
// handle nested routers and absolute paths
to[0] === '~' ? to.slice(1) : base + to
),
[base]
);
return [path, navigate];
};
export default useLocation;
+6
View File
@@ -0,0 +1,6 @@
import AppShell from '../components/AppShell';
import ListsPage from '../components/pages/Lists';
export default function Lists() {
return <AppShell page={ListsPage} />;
}
+6
View File
@@ -0,0 +1,6 @@
import AppShell from '../components/AppShell';
import SettingsPage from '../components/pages/Settings';
export default function Settings() {
return <AppShell page={SettingsPage} />;
}