Routing shell
This commit is contained in:
+50
-39
@@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import AppShell from '../components/AppShell';
|
||||||
|
import ListsPage from '../components/pages/Lists';
|
||||||
|
|
||||||
|
export default function Lists() {
|
||||||
|
return <AppShell page={ListsPage} />;
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import AppShell from '../components/AppShell';
|
||||||
|
import SettingsPage from '../components/pages/Settings';
|
||||||
|
|
||||||
|
export default function Settings() {
|
||||||
|
return <AppShell page={SettingsPage} />;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user