Merge pull request #1 from mlynch/main
Getting the latest from base repo
This commit is contained in:
@@ -24,6 +24,7 @@ There are currently snippets for the following common mobile components:
|
|||||||
- [x] Content
|
- [x] Content
|
||||||
- [x] Tabs
|
- [x] Tabs
|
||||||
- [ ] Nav (in progress)
|
- [ ] Nav (in progress)
|
||||||
|
- [ ] Next.js router integration
|
||||||
- [x] Icon
|
- [x] Icon
|
||||||
- [x] Menu
|
- [x] Menu
|
||||||
- [x] Modal
|
- [x] Modal
|
||||||
|
|||||||
@@ -9,8 +9,5 @@
|
|||||||
"launchShowDuration": 0
|
"launchShowDuration": 0
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"server": {
|
|
||||||
"url": "http://192.168.86.26:3000"
|
|
||||||
},
|
|
||||||
"cordova": {}
|
"cordova": {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import Store from '../store';
|
||||||
|
import * as actions from '../store/actions';
|
||||||
|
import * as selectors from '../store/selectors';
|
||||||
|
|
||||||
|
const MenuItem = ({ children, ...props }) => (
|
||||||
|
<li {...props}>
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
className="text-gray-800 hover:text-gray-400 block px-4 py-2 rounded-md text-base font-medium"
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
|
||||||
|
const MenuContent = () => {
|
||||||
|
const menuLinks = Store.useState(selectors.getMenuLinks);
|
||||||
|
|
||||||
|
const go = page => {
|
||||||
|
actions.setPage(page);
|
||||||
|
actions.setMenuOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="p-4">
|
||||||
|
<h2 className="text-xl select-none">Menu</h2>
|
||||||
|
</div>
|
||||||
|
<ul>
|
||||||
|
{menuLinks.map(p => {
|
||||||
|
const title = typeof p.title === 'function' ? p.title() : p.title;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MenuItem key={p.id} onClick={() => go(p)}>
|
||||||
|
{title}
|
||||||
|
</MenuItem>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MenuContent;
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
import { checkmarkOutline, closeOutline } from 'ionicons/icons';
|
||||||
|
import Button from './ui/Button';
|
||||||
|
import Icon from './ui/Icon';
|
||||||
|
import List from './ui/List';
|
||||||
|
import ListItem from './ui/ListItem';
|
||||||
|
import VirtualScroll from './ui/VirtualScroll';
|
||||||
|
|
||||||
|
const NotificationItem = ({ i }) => (
|
||||||
|
<ListItem className="flex align-center">
|
||||||
|
<img
|
||||||
|
src={`/img/faces/image-${(i % 66) + 1}.png`}
|
||||||
|
alt="Notification"
|
||||||
|
className="block rounded-full w-8 h-8 mr-4"
|
||||||
|
/>
|
||||||
|
<div className="flex-1">
|
||||||
|
<span className="p-0 m-0 align-middle">You have a new friend request</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Button className="background-transparent px-1 py-1 text-green-400 text-lg">
|
||||||
|
<Icon icon={checkmarkOutline} />
|
||||||
|
</Button>
|
||||||
|
<Button className="background-transparent px-1 py-1 text-red-400 text-lg">
|
||||||
|
<Icon icon={closeOutline} />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</ListItem>
|
||||||
|
);
|
||||||
|
|
||||||
|
const Notifications = () => (
|
||||||
|
<div className="w-full h-full flex flex-col">
|
||||||
|
<div className="p-4">
|
||||||
|
<h2 className="text-xl">Notifications</h2>
|
||||||
|
</div>
|
||||||
|
<List className="flex-1">
|
||||||
|
<VirtualScroll
|
||||||
|
totalCount={1000}
|
||||||
|
overscan={200}
|
||||||
|
style={{ height: '100%', width: '100%' }}
|
||||||
|
itemContent={index => <NotificationItem i={index} />}
|
||||||
|
/>
|
||||||
|
</List>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default Notifications;
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
import { Virtuoso } from 'react-virtuoso';
|
|
||||||
|
|
||||||
import Content from '../ui/Content';
|
|
||||||
import List from '../ui/List';
|
|
||||||
import ListItem from '../ui/ListItem';
|
|
||||||
|
|
||||||
const Feed = ({ selected }) => {
|
|
||||||
return (
|
|
||||||
<Content visible={selected} className="p-4">
|
|
||||||
<List className="h-full w-full">
|
|
||||||
{selected && (
|
|
||||||
<Virtuoso
|
|
||||||
totalCount={1000}
|
|
||||||
overscan={200}
|
|
||||||
style={{ height: '100%', width: '100%' }}
|
|
||||||
itemContent={index => <ListItem>Item {index}</ListItem>}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</List>
|
|
||||||
</Content>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Feed;
|
|
||||||
@@ -1,14 +1,13 @@
|
|||||||
import { homeItems } from '../../data';
|
import Store from '../../store';
|
||||||
import Card from '../ui/Card';
|
import Card from '../ui/Card';
|
||||||
import Content from '../ui/Content';
|
import Content from '../ui/Content';
|
||||||
|
|
||||||
const PostCard = ({ title, type, text, author, image }) => (
|
import * as selectors from '../../store/selectors';
|
||||||
<Card>
|
|
||||||
|
const HomeCard = ({ title, type, text, author, image }) => (
|
||||||
|
<Card className="my-4">
|
||||||
<div>
|
<div>
|
||||||
<img
|
<img className="rounded-t-xl h-32 w-full object-cover" src={image} />
|
||||||
className="rounded-t-xl h-32 w-full object-cover"
|
|
||||||
src={image || 'https://ionic-docs-demo.herokuapp.com/assets/card-top-img.png'}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="px-4 py-4 mt-2 bg-white rounded-b-xl">
|
<div className="px-4 py-4 mt-2 bg-white rounded-b-xl">
|
||||||
<h4 className="font-bold py-0 text-s text-gray-400 uppercase">{type}</h4>
|
<h4 className="font-bold py-0 text-s text-gray-400 uppercase">{type}</h4>
|
||||||
@@ -19,10 +18,12 @@ const PostCard = ({ title, type, text, author, image }) => (
|
|||||||
);
|
);
|
||||||
|
|
||||||
const Home = ({ selected }) => {
|
const Home = ({ selected }) => {
|
||||||
|
const homeItems = Store.useState(selectors.getHomeItems);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Content visible={selected}>
|
<Content visible={selected} className="p-4">
|
||||||
{homeItems.map((i, index) => (
|
{homeItems.map((i, index) => (
|
||||||
<PostCard {...i} key={index} />
|
<HomeCard {...i} key={index} />
|
||||||
))}
|
))}
|
||||||
</Content>
|
</Content>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
import Store from '../../store';
|
||||||
|
import * as actions from '../../store/actions';
|
||||||
|
import * as selectors from '../../store/selectors';
|
||||||
|
|
||||||
|
import Content from '../ui/Content';
|
||||||
|
import List from '../ui/List';
|
||||||
|
import VirtualScroll from '../ui/VirtualScroll';
|
||||||
|
|
||||||
|
const ListItems = ({ list, onClose }) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="py-2">
|
||||||
|
<a href="#" onClick={onClose}>
|
||||||
|
All Lists
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<VirtualScroll
|
||||||
|
data={list?.items || []}
|
||||||
|
totalCount={(list?.items || []).length}
|
||||||
|
style={{ height: '100%', width: '100%' }}
|
||||||
|
itemContent={(i, item) => <ListItemEntry list={list} item={item} />}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const ListItemEntry = ({ list, item }) => (
|
||||||
|
<div
|
||||||
|
className="p-4 border-solid border-b cursor-pointer flex select-none"
|
||||||
|
onClick={() => actions.setDone(list, item, !item.done)}
|
||||||
|
>
|
||||||
|
<span className="text-md flex-1">{item.name}</span>
|
||||||
|
<input
|
||||||
|
className="pointer-events-none select-none"
|
||||||
|
type="checkbox"
|
||||||
|
checked={item.done || false}
|
||||||
|
readOnly={true}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
const ListDetail = ({ selected }) => {
|
||||||
|
const selectedList = Store.useState(selectors.getSelectedList);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Content visible={selected} className="p-4">
|
||||||
|
<List className="h-full w-full">
|
||||||
|
{selected && (
|
||||||
|
<ListItems
|
||||||
|
list={selectedList}
|
||||||
|
onClose={() => {
|
||||||
|
actions.setSelectedList(null);
|
||||||
|
actions.setPageById('lists');
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</List>
|
||||||
|
</Content>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ListDetail;
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import Store from '../../store';
|
||||||
|
import * as actions from '../../store/actions';
|
||||||
|
import * as selectors from '../../store/selectors';
|
||||||
|
|
||||||
|
import Content from '../ui/Content';
|
||||||
|
import List from '../ui/List';
|
||||||
|
import VirtualScroll from '../ui/VirtualScroll';
|
||||||
|
|
||||||
|
const ListEntry = ({ list, ...props }) => (
|
||||||
|
<div {...props} className="p-4 border-solid border-b cursor-pointer">
|
||||||
|
<span className="text-md">{list.name}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
const AllLists = ({ onSelect }) => {
|
||||||
|
const lists = Store.useState(selectors.getLists);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<VirtualScroll
|
||||||
|
data={lists}
|
||||||
|
totalCount={lists.length}
|
||||||
|
style={{ height: '100%', width: '100%' }}
|
||||||
|
itemContent={(i, list) => (
|
||||||
|
<ListEntry list={list} onClick={() => onSelect(list)} onClose={() => onSelect(null)} />
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const Lists = ({ selected }) => {
|
||||||
|
return (
|
||||||
|
<Content visible={selected} className="p-4">
|
||||||
|
<List className="h-full w-full">
|
||||||
|
{selected && (
|
||||||
|
<AllLists
|
||||||
|
onSelect={list => {
|
||||||
|
actions.setSelectedList(list);
|
||||||
|
actions.setPageById('list-detail');
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</List>
|
||||||
|
</Content>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Lists;
|
||||||
@@ -1,9 +1,32 @@
|
|||||||
|
import Store from '../../store';
|
||||||
|
import * as selectors from '../../store/selectors';
|
||||||
|
import * as actions from '../../store/actions';
|
||||||
|
|
||||||
import Content from '../ui/Content';
|
import Content from '../ui/Content';
|
||||||
|
import List from '../ui/List';
|
||||||
|
import ListItem from '../ui/ListItem';
|
||||||
|
import Toggle from '../ui/Toggle';
|
||||||
|
|
||||||
const Settings = ({ selected }) => {
|
const Settings = ({ selected }) => {
|
||||||
|
const enableNotifications = Store.useState();
|
||||||
|
const settings = Store.useState(selectors.getSettings);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Content visible={selected} className="p-4">
|
<Content visible={selected} className="p-4">
|
||||||
<h2>Settings</h2>
|
<List>
|
||||||
|
<ListItem className="flex">
|
||||||
|
<span className="text-md flex-1">Enable Notifications</span>
|
||||||
|
<Toggle
|
||||||
|
checked={settings.enableNotifications}
|
||||||
|
onChange={e =>
|
||||||
|
actions.setSettings({
|
||||||
|
...settings,
|
||||||
|
enableNotifications: e.target.checked,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</ListItem>
|
||||||
|
</List>
|
||||||
</Content>
|
</Content>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
const Card = ({ children, className, ...props }) => (
|
const Card = ({ children, className, ...props }) => (
|
||||||
<div {...props} className={classNames('m-auto px-4 py-4 max-w-xl', className)}>
|
<div {...props} className={classNames('max-w-xl', className)}>
|
||||||
<div className="bg-white shadow-md rounded-b-xl">{children}</div>
|
<div className="bg-white shadow-md rounded-b-xl">{children}</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
+9
-15
@@ -1,10 +1,12 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
import { Plugins } from '@capacitor/core';
|
import { Plugins } from '@capacitor/core';
|
||||||
import Store from '../../store';
|
import * as actions from '../../store/actions';
|
||||||
|
|
||||||
const Nav = ({ page }) => {
|
const Nav = ({ page }) => {
|
||||||
const [showMenu, setShowMenu] = useState(false);
|
const [showProfileMenu, setShowProfileMenu] = useState(false);
|
||||||
|
|
||||||
|
const title = typeof page.title === 'function' ? page.title() : page.title;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
Plugins.StatusBar.setStyle({
|
Plugins.StatusBar.setStyle({
|
||||||
@@ -23,11 +25,7 @@ const Nav = ({ page }) => {
|
|||||||
<div className="relative flex items-center justify-between h-16">
|
<div className="relative flex items-center justify-between h-16">
|
||||||
<div
|
<div
|
||||||
className="absolute inset-y-0 left-0 flex items-center sm:hidden"
|
className="absolute inset-y-0 left-0 flex items-center sm:hidden"
|
||||||
onClick={() =>
|
onClick={() => actions.setMenuOpen(true)}
|
||||||
Store.update(s => {
|
|
||||||
s.showMenu = true;
|
|
||||||
})
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
{/* Mobile menu button*/}
|
{/* Mobile menu button*/}
|
||||||
<button
|
<button
|
||||||
@@ -81,7 +79,7 @@ const Nav = ({ page }) => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex-1 flex items-center justify-center sm:items-stretch sm:justify-start">
|
<div className="flex-1 flex items-center justify-center sm:items-stretch sm:justify-start">
|
||||||
<div className="flex-shrink-0 flex items-center">
|
<div className="flex-shrink-0 flex items-center">
|
||||||
<h1 className="text-gray-50">{page.title}</h1>
|
<h1 className="text-gray-50">{title}</h1>
|
||||||
</div>
|
</div>
|
||||||
<div className="hidden sm:block sm:ml-6">
|
<div className="hidden sm:block sm:ml-6">
|
||||||
<div className="flex space-x-4">
|
<div className="flex space-x-4">
|
||||||
@@ -116,11 +114,7 @@ const Nav = ({ page }) => {
|
|||||||
<div className="absolute inset-y-0 right-0 flex items-center pr-2 sm:static sm:inset-auto sm:ml-6 sm:pr-0">
|
<div className="absolute inset-y-0 right-0 flex items-center pr-2 sm:static sm:inset-auto sm:ml-6 sm:pr-0">
|
||||||
<button
|
<button
|
||||||
className="bg-gray-800 p-1 rounded-full text-gray-400 hover:text-white focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-800 focus:ring-white"
|
className="bg-gray-800 p-1 rounded-full text-gray-400 hover:text-white focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-800 focus:ring-white"
|
||||||
onClick={() =>
|
onClick={() => actions.setNotificationsOpen(true)}
|
||||||
Store.update(s => {
|
|
||||||
s.showNotifications = true;
|
|
||||||
})
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<span className="sr-only">View notifications</span>
|
<span className="sr-only">View notifications</span>
|
||||||
{/* Heroicon name: bell */}
|
{/* Heroicon name: bell */}
|
||||||
@@ -148,7 +142,7 @@ const Nav = ({ page }) => {
|
|||||||
className="bg-gray-800 flex text-sm rounded-full focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-800 focus:ring-white"
|
className="bg-gray-800 flex text-sm rounded-full focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-800 focus:ring-white"
|
||||||
id="user-menu"
|
id="user-menu"
|
||||||
aria-haspopup="true"
|
aria-haspopup="true"
|
||||||
onClick={() => setShowMenu(!showMenu)}
|
onClick={() => setShowProfileMenu(!showProfileMenu)}
|
||||||
>
|
>
|
||||||
<span className="sr-only">Open user menu</span>
|
<span className="sr-only">Open user menu</span>
|
||||||
<img
|
<img
|
||||||
@@ -170,7 +164,7 @@ const Nav = ({ page }) => {
|
|||||||
*/}
|
*/}
|
||||||
<div
|
<div
|
||||||
className={`${
|
className={`${
|
||||||
showMenu ? '' : 'hidden'
|
showProfileMenu ? '' : 'hidden'
|
||||||
} origin-top-right absolute right-0 mt-2 w-48 rounded-md shadow-lg py-1 bg-white ring-1 ring-black ring-opacity-5`}
|
} origin-top-right absolute right-0 mt-2 w-48 rounded-md shadow-lg py-1 bg-white ring-1 ring-black ring-opacity-5`}
|
||||||
role="menu"
|
role="menu"
|
||||||
aria-orientation="vertical"
|
aria-orientation="vertical"
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
|
import { Transition } from 'react-transition-group';
|
||||||
|
|
||||||
|
const duration = 500;
|
||||||
|
|
||||||
|
const defaultStyle = {
|
||||||
|
transition: `opacity ${duration}ms ease-in-out`,
|
||||||
|
opacity: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
const transitionStyles = {
|
||||||
|
entering: { opacity: 1 },
|
||||||
|
entered: { opacity: 1 },
|
||||||
|
exiting: { opacity: 0 },
|
||||||
|
exited: { opacity: 0 },
|
||||||
|
};
|
||||||
|
|
||||||
|
const PageStack = ({ children, className, ...props }) => (
|
||||||
|
<div {...props} className={classNames('flex-1 z-0 overflow-hidden relative', className)}>
|
||||||
|
<Transition in={true} duration={duration}>
|
||||||
|
{state => (
|
||||||
|
<div
|
||||||
|
className="w-full h-full"
|
||||||
|
style={{
|
||||||
|
...defaultStyle,
|
||||||
|
...transitionStyles[state],
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Transition>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default PageStack;
|
||||||
@@ -1,12 +1,13 @@
|
|||||||
const TabBar = ({ children }) => (
|
const TabBar = ({ children }) => (
|
||||||
<nav
|
<nav
|
||||||
id="tab-bar"
|
id="tab-bar"
|
||||||
className="py-2 h-16 bg-white w-full flex justify-center items-start bg-gray-50"
|
className="py-2 h-16 bg-white w-full flex justify-center items-start bg-gray-50 z-10"
|
||||||
style={{
|
style={{
|
||||||
height: `calc(env(safe-area-inset-bottom, 0px) + 56px)`
|
height: `calc(env(safe-area-inset-bottom, 0px) + 56px)`,
|
||||||
}}>
|
}}
|
||||||
|
>
|
||||||
{children}
|
{children}
|
||||||
</nav>
|
</nav>
|
||||||
)
|
);
|
||||||
|
|
||||||
export default TabBar;
|
export default TabBar;
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import ReactToggle from 'react-toggle';
|
||||||
|
|
||||||
|
const Toggle = props => <ReactToggle {...props} icons={false} />;
|
||||||
|
|
||||||
|
export default Toggle;
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import { Virtuoso } from 'react-virtuoso';
|
||||||
|
|
||||||
|
const VirtualScroll = props => <Virtuoso {...props} />;
|
||||||
|
|
||||||
|
export default VirtualScroll;
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
// Some fake filler data
|
|
||||||
|
|
||||||
export const images = [
|
|
||||||
'https://images.unsplash.com/photo-1608091526083-86ae8489ae5c?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=2100&q=80',
|
|
||||||
'https://images.unsplash.com/photo-1608050072262-7b26ba63fb46?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=2100&q=80',
|
|
||||||
'https://images.unsplash.com/photo-1607975218223-94f82613e833?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=934&q=80',
|
|
||||||
'https://images.unsplash.com/photo-1608108707326-215150457c9f?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=2100&q=80',
|
|
||||||
'https://images.unsplash.com/photo-1608057681073-9399f209e773?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=934&q=80',
|
|
||||||
];
|
|
||||||
|
|
||||||
export const homeItems = [
|
|
||||||
{
|
|
||||||
title: 'Welcome',
|
|
||||||
type: 'Blog',
|
|
||||||
text: 'Welcome to the app!',
|
|
||||||
author: 'Max',
|
|
||||||
image: images[0],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'How to get started',
|
|
||||||
type: 'Article',
|
|
||||||
text: 'Getting started with the app is easy! Just follow these 100 steps',
|
|
||||||
author: 'Max',
|
|
||||||
image: images[1],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Need help?',
|
|
||||||
type: 'Support',
|
|
||||||
text: "We're here to help. Available between the hours of 3am and 3:01am every day",
|
|
||||||
author: 'Max',
|
|
||||||
image: images[2],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Welcome',
|
|
||||||
type: 'Blog',
|
|
||||||
text: 'Welcome to the app!',
|
|
||||||
author: 'Max',
|
|
||||||
image: images[3],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Welcome',
|
|
||||||
type: 'Blog',
|
|
||||||
text: 'Welcome to the app!',
|
|
||||||
author: 'Max',
|
|
||||||
image: images[4],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
@@ -9,8 +9,5 @@
|
|||||||
"launchShowDuration": 0
|
"launchShowDuration": 0
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"server": {
|
|
||||||
"url": "http://192.168.86.26:3000"
|
|
||||||
},
|
|
||||||
"cordova": {}
|
"cordova": {}
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+43
@@ -1582,6 +1582,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"csstype": {
|
||||||
|
"version": "3.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.5.tgz",
|
||||||
|
"integrity": "sha512-uVDi8LpBUKQj6sdxNaTetL6FpeCqTjOvAQuQUa/qAqq8oOd4ivkbhgnqayl0dnPal8Tb/yB1tF+gOvCBiicaiQ==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"cyclist": {
|
"cyclist": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz",
|
||||||
@@ -1737,6 +1743,16 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"dom-helpers": {
|
||||||
|
"version": "5.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.0.tgz",
|
||||||
|
"integrity": "sha512-Ru5o9+V8CpunKnz5LGgWXkmrH/20cGKwcHwS4m73zIvs54CN9epEmT/HLqFJW3kXpakAFkEdzgy1hzlJe3E4OQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@babel/runtime": "^7.8.7",
|
||||||
|
"csstype": "^3.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"dom-serializer": {
|
"dom-serializer": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.0.1.tgz",
|
||||||
@@ -4411,6 +4427,27 @@
|
|||||||
"prop-types": "^15.5.8"
|
"prop-types": "^15.5.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"react-toggle": {
|
||||||
|
"version": "4.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-toggle/-/react-toggle-4.1.1.tgz",
|
||||||
|
"integrity": "sha512-+wXlMcSpg8SmnIXauMaZiKpR+r2wp2gMUteroejp2UTSqGTVvZLN+m9EhMzFARBKEw7KpQOwzCyfzeHeAndQGw==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"classnames": "^2.2.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"react-transition-group": {
|
||||||
|
"version": "4.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.1.tgz",
|
||||||
|
"integrity": "sha512-Djqr7OQ2aPUiYurhPalTrVy9ddmFCCzwhqQmtN+J3+3DzLO209Fdr70QrN8Z3DsglWql6iY1lDWAfpFiBtuKGw==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@babel/runtime": "^7.5.5",
|
||||||
|
"dom-helpers": "^5.0.1",
|
||||||
|
"loose-envify": "^1.4.0",
|
||||||
|
"prop-types": "^15.6.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"react-use-gesture": {
|
"react-use-gesture": {
|
||||||
"version": "9.0.0-beta.11",
|
"version": "9.0.0-beta.11",
|
||||||
"resolved": "https://registry.npmjs.org/react-use-gesture/-/react-use-gesture-9.0.0-beta.11.tgz",
|
"resolved": "https://registry.npmjs.org/react-use-gesture/-/react-use-gesture-9.0.0-beta.11.tgz",
|
||||||
@@ -4497,6 +4534,12 @@
|
|||||||
"resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz",
|
"resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz",
|
||||||
"integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc="
|
"integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc="
|
||||||
},
|
},
|
||||||
|
"reselect": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/reselect/-/reselect-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-qUgANli03jjAyGlnbYVAV5vvnOmJnODyABz51RdBN7M4WaVu8mecZWgyQNkG8Yqe3KRGRt0l4K4B3XVEULC4CA==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"resize-observer-polyfill": {
|
"resize-observer-polyfill": {
|
||||||
"version": "1.5.1",
|
"version": "1.5.1",
|
||||||
"resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz",
|
||||||
|
|||||||
+4
-1
@@ -26,6 +26,9 @@
|
|||||||
"prettier": "^2.2.1",
|
"prettier": "^2.2.1",
|
||||||
"pullstate": "^1.20.5",
|
"pullstate": "^1.20.5",
|
||||||
"react-spring": "^8.0.27",
|
"react-spring": "^8.0.27",
|
||||||
"react-use-gesture": "^9.0.0-beta.11"
|
"react-toggle": "^4.1.1",
|
||||||
|
"react-transition-group": "^4.4.1",
|
||||||
|
"react-use-gesture": "^9.0.0-beta.11",
|
||||||
|
"reselect": "^4.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+36
-119
@@ -1,147 +1,59 @@
|
|||||||
import { useState } from 'react';
|
import { useDrag } from 'react-use-gesture';
|
||||||
import { Virtuoso } from 'react-virtuoso';
|
|
||||||
import { flash, flashOutline, cog, cogOutline, home, homeOutline } from 'ionicons/icons';
|
|
||||||
|
|
||||||
import Store from '../store';
|
import Store from '../store';
|
||||||
|
import * as actions from '../store/actions';
|
||||||
|
import * as selectors from '../store/selectors';
|
||||||
|
|
||||||
import App from '../components/ui/App';
|
import App from '../components/ui/App';
|
||||||
import Backdrop from '../components/ui/Backdrop';
|
import Backdrop from '../components/ui/Backdrop';
|
||||||
import Menu from '../components/ui/Menu';
|
import Menu from '../components/ui/Menu';
|
||||||
import Modal from '../components/ui/Modal';
|
import Modal from '../components/ui/Modal';
|
||||||
import Nav from '../components/ui/Nav';
|
import Nav from '../components/ui/Nav';
|
||||||
|
import PageStack from '../components/ui/PageStack';
|
||||||
import Tab from '../components/ui/Tab';
|
import Tab from '../components/ui/Tab';
|
||||||
import TabBar from '../components/ui/TabBar';
|
import TabBar from '../components/ui/TabBar';
|
||||||
import List from '../components/ui/List';
|
|
||||||
import ListItem from '../components/ui/ListItem';
|
|
||||||
import Button from '../components/ui/Button';
|
|
||||||
import { SafeAreaProvider } from '../components/ui/SafeArea';
|
import { SafeAreaProvider } from '../components/ui/SafeArea';
|
||||||
|
import Notifications from '../components/Notifications';
|
||||||
import Home from '../components/pages/Home';
|
import MenuContent from '../components/MenuContent';
|
||||||
import Feed from '../components/pages/Feed';
|
import { useEffect, useState } from 'react';
|
||||||
import Settings from '../components/pages/Settings';
|
|
||||||
import { useDrag } from 'react-use-gesture';
|
|
||||||
|
|
||||||
const pages = [
|
|
||||||
{ id: 'home', title: 'Home', icon: homeOutline, selectedIcon: home, component: Home },
|
|
||||||
{
|
|
||||||
id: 'feed',
|
|
||||||
title: 'Feed',
|
|
||||||
icon: flashOutline,
|
|
||||||
selectedIcon: flash,
|
|
||||||
component: Feed,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'settings',
|
|
||||||
title: 'Settings',
|
|
||||||
icon: cogOutline,
|
|
||||||
selectedIcon: cog,
|
|
||||||
component: Settings,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const CurrentPage = ({ page }) => {
|
const CurrentPage = ({ page }) => {
|
||||||
|
const pages = Store.useState(selectors.getPages);
|
||||||
|
const currentPage = Store.useState(selectors.getCurrentPage);
|
||||||
|
|
||||||
|
const Page = currentPage.component;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex-1 z-0 overflow-hidden relative">
|
<PageStack>
|
||||||
{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} />;
|
||||||
})}
|
})*/}
|
||||||
</div>
|
<Page selected={true} />
|
||||||
|
</PageStack>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const MenuItem = ({ children }) => (
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href="#"
|
|
||||||
className="text-gray-800 hover:text-gray-400 block px-4 py-2 rounded-md text-base font-medium"
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
);
|
|
||||||
const MenuContent = () => (
|
|
||||||
<>
|
|
||||||
<div className="p-4">
|
|
||||||
<h2 className="text-xl select-none">Menu</h2>
|
|
||||||
</div>
|
|
||||||
<ul>
|
|
||||||
<MenuItem>Home</MenuItem>
|
|
||||||
<MenuItem>Profile</MenuItem>
|
|
||||||
<MenuItem>Settings</MenuItem>
|
|
||||||
</ul>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
|
|
||||||
const FakeNotification = ({ i }) => (
|
|
||||||
<ListItem className="flex align-center">
|
|
||||||
<img
|
|
||||||
src={`/img/faces/image-${(i % 66) + 1}.png`}
|
|
||||||
alt="Notification"
|
|
||||||
className="block rounded-full w-8 h-8 mr-4"
|
|
||||||
/>
|
|
||||||
<div className="flex-1">
|
|
||||||
<span className="p-0 m-0 align-middle">You have a new friend request</span>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<Button className="background-transparent px-1 py-1 text-green-400 text-lg">
|
|
||||||
<ion-icon name="checkmark-outline" />
|
|
||||||
</Button>
|
|
||||||
<Button className="background-transparent px-1 py-1 text-red-400 text-lg">
|
|
||||||
<ion-icon name="close-outline" />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</ListItem>
|
|
||||||
);
|
|
||||||
|
|
||||||
const NotificationsContent = () => (
|
|
||||||
<div className="w-full h-full flex flex-col">
|
|
||||||
<div className="p-4">
|
|
||||||
<h2 className="text-xl">Notifications</h2>
|
|
||||||
</div>
|
|
||||||
<List className="flex-1">
|
|
||||||
<Virtuoso
|
|
||||||
totalCount={1000}
|
|
||||||
overscan={200}
|
|
||||||
style={{ height: '100%', width: '100%' }}
|
|
||||||
itemContent={index => <FakeNotification i={index} />}
|
|
||||||
/>
|
|
||||||
</List>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
export default function Index() {
|
export default function Index() {
|
||||||
const [page, setPage] = useState(pages[0]);
|
const showMenu = Store.useState(selectors.getMenuOpen);
|
||||||
|
const showNotifications = Store.useState(selectors.getNotificationsOpen);
|
||||||
|
const currentPage = Store.useState(selectors.getCurrentPage);
|
||||||
|
const tabs = Store.useState(selectors.getTabs);
|
||||||
|
|
||||||
const showMenu = Store.useState(s => s.showMenu);
|
const closeMenu = () => actions.setMenuOpen(false);
|
||||||
const showNotifications = Store.useState(s => s.showNotifications);
|
|
||||||
|
|
||||||
const closeMenu = () => {
|
|
||||||
Store.update(s => {
|
|
||||||
s.showMenu = false;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const backdropClose = () => {
|
const backdropClose = () => {
|
||||||
Store.update(s => {
|
actions.setMenuOpen(false);
|
||||||
s.showMenu = false;
|
actions.setNotificationsOpen(false);
|
||||||
s.showNotifications = false;
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const closeNotifications = () => {
|
const closeNotifications = () => actions.setNotificationsOpen(false);
|
||||||
Store.update(s => {
|
|
||||||
s.showNotifications = false;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// To enable edge drag detection to open the side menu
|
// To enable edge drag detection to open the side menu
|
||||||
const bind = useDrag(
|
const bind = useDrag(
|
||||||
({ down, movement: [mx], xy: [x, y], cancel }) => {
|
({ down, movement: [mx], xy: [x, y], cancel }) => {
|
||||||
if (mx > 5 && x < 50 && down) {
|
if (mx > 5 && x < 50 && down) {
|
||||||
Store.update(s => {
|
actions.setMenuOpen(true);
|
||||||
s.showMenu = true;
|
|
||||||
});
|
|
||||||
cancel();
|
cancel();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -163,16 +75,21 @@ export default function Index() {
|
|||||||
<Menu open={showMenu} onClose={closeMenu}>
|
<Menu open={showMenu} onClose={closeMenu}>
|
||||||
<MenuContent />
|
<MenuContent />
|
||||||
</Menu>
|
</Menu>
|
||||||
<Nav page={page} />
|
<Nav page={currentPage} />
|
||||||
<CurrentPage page={page} />
|
<CurrentPage page={currentPage} />
|
||||||
<TabBar>
|
<TabBar>
|
||||||
{pages.map(p => (
|
{tabs.map(p => (
|
||||||
<Tab key={p.id} {...p} onClick={() => setPage(p)} selected={p.id === page.id} />
|
<Tab
|
||||||
|
key={p.id}
|
||||||
|
{...p}
|
||||||
|
onClick={() => actions.setPage(p)}
|
||||||
|
selected={p.id === currentPage.id}
|
||||||
|
/>
|
||||||
))}
|
))}
|
||||||
</TabBar>
|
</TabBar>
|
||||||
<Backdrop open={showMenu || showNotifications} onClose={backdropClose} />
|
<Backdrop open={showMenu || showNotifications} onClose={backdropClose} />
|
||||||
<Modal open={showNotifications} onClose={closeNotifications}>
|
<Modal open={showNotifications} onClose={closeNotifications}>
|
||||||
<NotificationsContent />
|
<Notifications />
|
||||||
</Modal>
|
</Modal>
|
||||||
</SafeAreaProvider>
|
</SafeAreaProvider>
|
||||||
</App>
|
</App>
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
import Store from '.';
|
||||||
|
|
||||||
|
export const setPageById = id => {
|
||||||
|
Store.update((s, o) => {
|
||||||
|
s.currentPage = o.pages.find(p => p.id === id);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const setPage = page => {
|
||||||
|
Store.update((s, o) => {
|
||||||
|
s.currentPage = page;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const setMenuOpen = open => {
|
||||||
|
Store.update(s => {
|
||||||
|
s.menuOpen = open;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const setNotificationsOpen = open => {
|
||||||
|
Store.update(s => {
|
||||||
|
s.notificationsOpen = open;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const setSettings = settings => {
|
||||||
|
Store.update(s => {
|
||||||
|
s.settings = settings;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// App-specific actions
|
||||||
|
|
||||||
|
export const setDone = (list, item, done) => {
|
||||||
|
Store.update((s, o) => {
|
||||||
|
const listIndex = o.lists.findIndex(l => l === list);
|
||||||
|
const itemIndex = o.lists[listIndex].items.findIndex(i => i === item);
|
||||||
|
s.lists[listIndex].items[itemIndex].done = done;
|
||||||
|
if (list === o.selectedList) {
|
||||||
|
s.selectedList = s.lists[listIndex];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const setSelectedList = list => {
|
||||||
|
Store.update(s => {
|
||||||
|
s.selectedList = list;
|
||||||
|
});
|
||||||
|
};
|
||||||
+102
-2
@@ -1,10 +1,110 @@
|
|||||||
import { Store as PullStateStore } from 'pullstate';
|
import { Store as PullStateStore } from 'pullstate';
|
||||||
|
|
||||||
|
import { list, listOutline, cog, cogOutline, home, homeOutline } from 'ionicons/icons';
|
||||||
|
|
||||||
|
import Home from '../components/pages/Home';
|
||||||
|
import Lists from '../components/pages/Lists';
|
||||||
|
import Settings from '../components/pages/Settings';
|
||||||
|
import ListDetail from '../components/pages/ListDetail';
|
||||||
|
|
||||||
|
// The available pages here
|
||||||
|
const pages = [
|
||||||
|
{ id: 'home', title: 'Home', icon: homeOutline, selectedIcon: home, component: Home },
|
||||||
|
{
|
||||||
|
id: 'lists',
|
||||||
|
title: 'Lists',
|
||||||
|
icon: listOutline,
|
||||||
|
selectedIcon: list,
|
||||||
|
component: Lists,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'list-detail',
|
||||||
|
title: () => Store.getRawState().selectedList?.name,
|
||||||
|
icon: listOutline,
|
||||||
|
selectedIcon: list,
|
||||||
|
component: ListDetail,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'settings',
|
||||||
|
title: 'Settings',
|
||||||
|
icon: cogOutline,
|
||||||
|
selectedIcon: cog,
|
||||||
|
component: Settings,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const images = [
|
||||||
|
'https://images.unsplash.com/photo-1608091526083-86ae8489ae5c?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=2100&q=80',
|
||||||
|
'https://images.unsplash.com/photo-1608050072262-7b26ba63fb46?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=2100&q=80',
|
||||||
|
'https://images.unsplash.com/photo-1607975218223-94f82613e833?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=934&q=80',
|
||||||
|
'https://images.unsplash.com/photo-1608108707326-215150457c9f?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=2100&q=80',
|
||||||
|
'https://images.unsplash.com/photo-1608057681073-9399f209e773?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=934&q=80',
|
||||||
|
];
|
||||||
|
|
||||||
|
export const homeItems = [
|
||||||
|
{
|
||||||
|
title: 'Welcome',
|
||||||
|
type: 'Blog',
|
||||||
|
text: 'Welcome to the app!',
|
||||||
|
author: 'Max',
|
||||||
|
image: images[0],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'How to get started',
|
||||||
|
type: 'Article',
|
||||||
|
text: 'Getting started with the app is easy! Just follow these 100 steps',
|
||||||
|
author: 'Max',
|
||||||
|
image: images[1],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Need help?',
|
||||||
|
type: 'Support',
|
||||||
|
text: "We're here to help. Available between the hours of 3am and 3:01am every day",
|
||||||
|
author: 'Max',
|
||||||
|
image: images[2],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// Some fake lists
|
||||||
|
const lists = [
|
||||||
|
{
|
||||||
|
name: 'Groceries',
|
||||||
|
id: 'groceries',
|
||||||
|
items: [{ name: 'Apples' }, { name: 'Bananas' }, { name: 'Milk' }, { name: 'Ice Cream' }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Hardware Store',
|
||||||
|
id: 'hardware',
|
||||||
|
items: [
|
||||||
|
{ name: 'Circular Saw' },
|
||||||
|
{ name: 'Tack Cloth' },
|
||||||
|
{ name: 'Drywall' },
|
||||||
|
{ name: 'Router' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{ name: 'Work', id: 'work', items: [{ name: 'TPS Report' }, { name: 'Set up email' }] },
|
||||||
|
{ name: 'Reminders', id: 'reminders' },
|
||||||
|
];
|
||||||
|
|
||||||
const Store = new PullStateStore({
|
const Store = new PullStateStore({
|
||||||
safeAreaTop: 0,
|
safeAreaTop: 0,
|
||||||
safeAreaBottom: 0,
|
safeAreaBottom: 0,
|
||||||
showMenu: false,
|
menuOpen: false,
|
||||||
showNotifications: false,
|
notificationsOpen: false,
|
||||||
|
currentPage: pages[0],
|
||||||
|
pages,
|
||||||
|
|
||||||
|
// The pages that are linked to tabs
|
||||||
|
tabs: pages.filter(p => ['home', 'lists', 'settings'].indexOf(p.id) >= 0),
|
||||||
|
// The pages that are linked to the menu
|
||||||
|
menuLinks: pages.filter(p => ['home', 'lists', 'settings'].indexOf(p.id) >= 0),
|
||||||
|
|
||||||
|
homeItems,
|
||||||
|
lists,
|
||||||
|
selectedList: null,
|
||||||
|
settings: {
|
||||||
|
enableNotifications: true,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default Store;
|
export default Store;
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import { createSelector } from 'reselect';
|
||||||
|
|
||||||
|
const getState = state => state;
|
||||||
|
|
||||||
|
export const getMenuOpen = createSelector(getState, state => state.menuOpen);
|
||||||
|
export const getNotificationsOpen = createSelector(getState, state => state.notificationsOpen);
|
||||||
|
export const getCurrentPage = createSelector(getState, state => state.currentPage);
|
||||||
|
export const getTabs = createSelector(getState, state => state.tabs);
|
||||||
|
export const getMenuLinks = createSelector(getState, state => state.menuLinks);
|
||||||
|
export const getPages = createSelector(getState, state => state.pages);
|
||||||
|
|
||||||
|
// App specific selectors
|
||||||
|
export const getHomeItems = createSelector(getState, state => state.homeItems);
|
||||||
|
export const getLists = createSelector(getState, state => state.lists);
|
||||||
|
export const getSelectedList = createSelector(getState, state => state.selectedList);
|
||||||
|
export const getSettings = createSelector(getState, state => state.settings);
|
||||||
@@ -52,3 +52,147 @@ body {
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Styles for React Toggle */
|
||||||
|
|
||||||
|
.react-toggle {
|
||||||
|
touch-action: pan-x;
|
||||||
|
|
||||||
|
display: inline-block;
|
||||||
|
position: relative;
|
||||||
|
cursor: pointer;
|
||||||
|
background-color: transparent;
|
||||||
|
border: 0;
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
-webkit-touch-callout: none;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-khtml-user-select: none;
|
||||||
|
-moz-user-select: none;
|
||||||
|
-ms-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
|
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-toggle-screenreader-only {
|
||||||
|
border: 0;
|
||||||
|
clip: rect(0 0 0 0);
|
||||||
|
height: 1px;
|
||||||
|
margin: -1px;
|
||||||
|
overflow: hidden;
|
||||||
|
padding: 0;
|
||||||
|
position: absolute;
|
||||||
|
width: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-toggle--disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
opacity: 0.5;
|
||||||
|
-webkit-transition: opacity 0.25s;
|
||||||
|
transition: opacity 0.25s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-toggle-track {
|
||||||
|
width: 50px;
|
||||||
|
height: 24px;
|
||||||
|
padding: 0;
|
||||||
|
border-radius: 30px;
|
||||||
|
background-color: #4d4d4d;
|
||||||
|
-webkit-transition: all 0.2s ease;
|
||||||
|
-moz-transition: all 0.2s ease;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-toggle:hover:not(.react-toggle--disabled) .react-toggle-track {
|
||||||
|
background-color: #000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-toggle--checked .react-toggle-track {
|
||||||
|
background-color: #19ab27;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-toggle--checked:hover:not(.react-toggle--disabled) .react-toggle-track {
|
||||||
|
background-color: #128d15;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-toggle-track-check {
|
||||||
|
position: absolute;
|
||||||
|
width: 14px;
|
||||||
|
height: 10px;
|
||||||
|
top: 0px;
|
||||||
|
bottom: 0px;
|
||||||
|
margin-top: auto;
|
||||||
|
margin-bottom: auto;
|
||||||
|
line-height: 0;
|
||||||
|
left: 8px;
|
||||||
|
opacity: 0;
|
||||||
|
-webkit-transition: opacity 0.25s ease;
|
||||||
|
-moz-transition: opacity 0.25s ease;
|
||||||
|
transition: opacity 0.25s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-toggle--checked .react-toggle-track-check {
|
||||||
|
opacity: 1;
|
||||||
|
-webkit-transition: opacity 0.25s ease;
|
||||||
|
-moz-transition: opacity 0.25s ease;
|
||||||
|
transition: opacity 0.25s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-toggle-track-x {
|
||||||
|
position: absolute;
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
top: 0px;
|
||||||
|
bottom: 0px;
|
||||||
|
margin-top: auto;
|
||||||
|
margin-bottom: auto;
|
||||||
|
line-height: 0;
|
||||||
|
right: 10px;
|
||||||
|
opacity: 1;
|
||||||
|
-webkit-transition: opacity 0.25s ease;
|
||||||
|
-moz-transition: opacity 0.25s ease;
|
||||||
|
transition: opacity 0.25s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-toggle--checked .react-toggle-track-x {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-toggle-thumb {
|
||||||
|
transition: all 0.5s cubic-bezier(0.23, 1, 0.32, 1) 0ms;
|
||||||
|
position: absolute;
|
||||||
|
top: 1px;
|
||||||
|
left: 1px;
|
||||||
|
width: 22px;
|
||||||
|
height: 22px;
|
||||||
|
border: 1px solid #4d4d4d;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: #fafafa;
|
||||||
|
|
||||||
|
-webkit-box-sizing: border-box;
|
||||||
|
-moz-box-sizing: border-box;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
-webkit-transition: all 0.25s ease;
|
||||||
|
-moz-transition: all 0.25s ease;
|
||||||
|
transition: all 0.25s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-toggle--checked .react-toggle-thumb {
|
||||||
|
left: 27px;
|
||||||
|
border-color: #19ab27;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-toggle--focus .react-toggle-thumb {
|
||||||
|
-webkit-box-shadow: 0px 0px 3px 2px #0099e0;
|
||||||
|
-moz-box-shadow: 0px 0px 3px 2px #0099e0;
|
||||||
|
box-shadow: 0px 0px 2px 3px #0099e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-toggle:active:not(.react-toggle--disabled) .react-toggle-thumb {
|
||||||
|
-webkit-box-shadow: 0px 0px 5px 5px #0099e0;
|
||||||
|
-moz-box-shadow: 0px 0px 5px 5px #0099e0;
|
||||||
|
box-shadow: 0px 0px 5px 5px #0099e0;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user