List items and modal

This commit is contained in:
Max Lynch
2020-12-22 12:37:43 -06:00
parent 49c497670a
commit 9a37a9f7db
80 changed files with 231 additions and 47 deletions
+15
View File
@@ -0,0 +1,15 @@
import classNames from 'classnames';
const Button = ({ children, className, ...props }) => (
<button
{...props}
class={classNames(
'inline-block text-xs font-medium leading-6 text-center uppercase transition rounded-lg ripple focus:outline-none',
className
)}
>
{children}
</button>
);
export default Button;
+7 -4
View File
@@ -1,10 +1,13 @@
import classNames from 'classnames';
const Content = ({ className, visible, children }) => (
<div className={classNames(`h-full overflow-auto py-2 absolute top-0`, className, {
const Content = ({ className, visible, children, ...props }) => (
<div
{...props}
className={classNames(`h-full w-full overflow-auto py-2 absolute top-0`, className, {
visible,
invisible: !visible
})}>
invisible: !visible,
})}
>
{children}
</div>
);
+3
View File
@@ -0,0 +1,3 @@
const EdgeDrag = () => null;
export default EdgeDrag;
+3
View File
@@ -0,0 +1,3 @@
const List = ({ children, ...props }) => <div {...props}>{children}</div>;
export default List;
+9
View File
@@ -0,0 +1,9 @@
import classNames from 'classnames';
const ListItem = ({ children, className, ...props }) => (
<div className={classNames('p-4', className)} {...props}>
{children}
</div>
);
export default ListItem;
+3 -7
View File
@@ -4,7 +4,7 @@ import { useEffect, useLayoutEffect, useRef, useState } from 'react';
import { useDrag } from 'react-use-gesture';
const Menu = ({ open, onClose, children }) => {
const Menu = ({ open, onClose, children, className, ...props }) => {
const ref = useRef();
const [x, setX] = useState(-100000);
const [rect, setRect] = useState(null);
@@ -68,6 +68,7 @@ const Menu = ({ open, onClose, children }) => {
return (
<div
{...props}
{...bind()}
ref={ref}
style={{
@@ -78,15 +79,10 @@ const Menu = ({ open, onClose, children }) => {
}}
className={classNames(
'fixed z-40 transform transform-gpu translate w-48 h-full bg-gray-100',
className,
{
'transition-transform': !dragging,
}
/*
{
'-translate-x-full': !open,
'-translate-x-0': open,
}
*/
)}
>
{children}
+34 -12
View File
@@ -1,5 +1,5 @@
import classNames from 'classnames';
import { useLayoutEffect, useRef, useState } from 'react';
import { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react';
import { useDrag } from 'react-use-gesture';
const Modal = ({ open, onClose, children }) => {
@@ -7,24 +7,46 @@ const Modal = ({ open, onClose, children }) => {
const [dragging, setDragging] = useState(false);
const [rect, setRect] = useState(null);
const [y, setY] = useState(100000);
const [safeAreaTop, setSafeAreaTop] = useState(0);
const _open = useCallback(() => {
setY(safeAreaTop);
}, [safeAreaTop]);
const _close = useCallback(() => {
if (!rect) {
return;
}
setY(rect.height + safeAreaTop);
}, [safeAreaTop, rect]);
// Get pixel value of safe area insets
useEffect(() => {
const safeAreaTop = parseInt(
getComputedStyle(document.documentElement).getPropertyValue('--safe-area-top')
);
setSafeAreaTop(safeAreaTop);
}, []);
// Get the layout rectangle for the modal
useLayoutEffect(() => {
const rect = ref.current?.getBoundingClientRect();
setRect(rect);
setY(-rect.width);
_close();
}, []);
// If open changes, open/close the modal
useLayoutEffect(() => {
if (open) {
setY(0);
} else if (rect) {
setY(rect.height);
_open();
} else {
_close();
}
}, [rect, open]);
}, [rect, open, _open, _close]);
const bind = useDrag(
({ down, movement: [mx, my] }) => {
setY(my < 0 ? 0 : my);
setY(my < 0 ? safeAreaTop : my + safeAreaTop);
if (down) {
setDragging(true);
@@ -32,16 +54,16 @@ const Modal = ({ open, onClose, children }) => {
setDragging(false);
}
// If the drag ended, snap the menu back
// If the drag ended, snap the menu back open or close it
if (!down) {
const mid = rect.height;
if (y > mid / 2) {
// Close
setY(rect.height);
_close();
onClose();
} else {
// Re-open
setY(0);
_open();
}
}
},
@@ -55,13 +77,13 @@ const Modal = ({ open, onClose, children }) => {
ref={ref}
{...bind()}
className={classNames(
'fixed z-40 top-5 transform transform-gpu ranslate w-full h-full bg-white rounded-t-lg',
'fixed z-40 top-5 transform transform-gpu ranslate w-full h-full bg-white rounded-t-xl',
{
'ease-in-out duration-300 transition-transformation': !dragging,
}
)}
style={{
height: `calc(100% - 1.25rem)`,
height: `calc(100% - env(safe-area-inset-top, 0px) - 1.25rem)`,
touchAction: 'pan-y',
transform: `translateY(${y}px)`,
}}
+24
View File
@@ -0,0 +1,24 @@
import Content from '../Content';
import { Virtuoso } from 'react-virtuoso';
import List from '../List';
import ListItem from '../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;
-12
View File
@@ -1,12 +0,0 @@
import { homeItems } from "../../data";
import Content from "../Content";
const Profile = ({ selected }) => {
return (
<Content visible={selected} className="p-4">
<h2>Profile</h2>
</Content>
)
}
export default Profile;
+78
View File
@@ -312,6 +312,19 @@
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.6.tgz",
"integrity": "sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw=="
},
"@virtuoso.dev/react-urx": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/@virtuoso.dev/react-urx/-/react-urx-0.2.2.tgz",
"integrity": "sha512-PH2suwXIqFSbAfdkM6COQTRVqKVIC4DWUPPmZVTSWrdPJx6m45rxifAS91a5X3kl9RPaCWD0fBN0ztzWG6BdGQ==",
"requires": {
"@virtuoso.dev/urx": "^0.2.2"
}
},
"@virtuoso.dev/urx": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/@virtuoso.dev/urx/-/urx-0.2.2.tgz",
"integrity": "sha512-CbzbWVCtyG2XFSZ+X0K9jNjTpKI+p4dn61ZUM+cpKwCp2HK9jCRWchnOFovqvWqELoGP65TmGNNF06OjCDlk0A=="
},
"@webassemblyjs/ast": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz",
@@ -665,6 +678,11 @@
"resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz",
"integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg="
},
"asap": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
"integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY="
},
"asn1.js": {
"version": "5.4.1",
"resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz",
@@ -1372,6 +1390,11 @@
"resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz",
"integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40="
},
"core-js": {
"version": "3.8.1",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.8.1.tgz",
"integrity": "sha512-9Id2xHY1W7m8hCl8NkhQn5CufmF/WuR30BTRewvCXc1aZd3kMECwNZ69ndLbekKfakw9Rf2Xyc+QR6E7Gg+obg=="
},
"core-util-is": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
@@ -3869,6 +3892,11 @@
"sha.js": "^2.4.8"
}
},
"performance-now": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
"integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns="
},
"picomatch": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz",
@@ -4156,6 +4184,14 @@
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
},
"promise": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/promise/-/promise-8.1.0.tgz",
"integrity": "sha512-W04AqnILOL/sPRXziNicCjSNRruLAuIHEOVBazepu0545DDNGYHz7ar9ZgZ1fMU8/MA4mVxp5rkBWRi6OXIy3Q==",
"requires": {
"asap": "~2.0.6"
}
},
"promise-inflight": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz",
@@ -4269,6 +4305,14 @@
"resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz",
"integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM="
},
"raf": {
"version": "3.4.1",
"resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz",
"integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==",
"requires": {
"performance-now": "^2.1.0"
}
},
"randombytes": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
@@ -4318,6 +4362,19 @@
"object-assign": "^4.1.1"
}
},
"react-app-polyfill": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/react-app-polyfill/-/react-app-polyfill-1.0.6.tgz",
"integrity": "sha512-OfBnObtnGgLGfweORmdZbyEz+3dgVePQBb3zipiaDsMHV1NpWm0rDFYIVXFV/AK+x4VIIfWHhrdMIeoTLyRr2g==",
"requires": {
"core-js": "^3.5.0",
"object-assign": "^4.1.1",
"promise": "^8.0.3",
"raf": "^3.4.1",
"regenerator-runtime": "^0.13.3",
"whatwg-fetch": "^3.0.0"
}
},
"react-dom": {
"version": "17.0.1",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.1.tgz",
@@ -4354,6 +4411,17 @@
"integrity": "sha512-lpn39vmrDu/zB2bNx7rjaL0+Gjm17a9mzn53bX9IP4TIjMUxXlsB0IkiFj/B23F0vq1A9ozDLGHl2OaXkKJcBg==",
"dev": true
},
"react-virtuoso": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/react-virtuoso/-/react-virtuoso-1.1.1.tgz",
"integrity": "sha512-ljUxZQUdJtMpobTKL1hLHsJJSznAH5bupEuLFKxlA6gVSfayxnldNZyhRVdoZmHqfVvsaNwQETw246uernMdpw==",
"requires": {
"@virtuoso.dev/react-urx": "^0.2.0",
"@virtuoso.dev/urx": "^0.2.0",
"react-app-polyfill": "^1.0.6",
"resize-observer-polyfill": "^1.5.1"
}
},
"readable-stream": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
@@ -4423,6 +4491,11 @@
"resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz",
"integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc="
},
"resize-observer-polyfill": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz",
"integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg=="
},
"resolve": {
"version": "1.19.0",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.19.0.tgz",
@@ -5996,6 +6069,11 @@
"source-map": "~0.6.1"
}
},
"whatwg-fetch": {
"version": "3.5.0",
"resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.5.0.tgz",
"integrity": "sha512-jXkLtsR42xhXg7akoDKvKWE40eJeI+2KZqcp2h3NsOrRnDvtWX36KcKl30dy+hxECivdk2BVUHVNrPtoMBUx6A=="
},
"whatwg-url": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz",
+1
View File
@@ -18,6 +18,7 @@
"postcss": "^8.2.1",
"react": "17.0.1",
"react-dom": "17.0.1",
"react-virtuoso": "^1.1.1",
"tailwindcss": "^2.0.2"
},
"devDependencies": {
+45 -7
View File
@@ -1,4 +1,8 @@
import { useCallback, useState } from 'react';
import { Virtuoso } from 'react-virtuoso';
import { faces } from '../data';
import Store from '../store';
import App from '../components/App';
import Backdrop from '../components/Backdrop';
@@ -6,20 +10,23 @@ import Menu from '../components/Menu';
import Modal from '../components/Modal';
import Nav from '../components/Nav';
import Home from '../components/pages/Home';
import Profile from '../components/pages/Profile';
import Feed from '../components/pages/Feed';
import Settings from '../components/pages/Settings';
import Tab from '../components/Tab';
import TabBar from '../components/TabBar';
import Store from '../store';
import List from '../components/List';
import ListItem from '../components/ListItem';
import { useState } from 'react';
import Button from '../components/Button';
const pages = [
{ id: 'home', title: 'Home', icon: 'home-outline', selectedIcon: 'home', component: Home },
{
id: 'profile',
title: 'Profile',
icon: 'person-outline',
id: 'feed',
title: 'Feed',
icon: 'flash-outline',
selectedIcon: 'person',
component: Profile,
component: Feed,
},
{
id: 'settings',
@@ -64,10 +71,41 @@ const MenuContent = () => (
</>
);
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() {
Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

+4
View File
@@ -1,3 +1,7 @@
:root {
--safe-area-top: env(safe-area-inset-top, 0);
--safe-area-bottom: env(safe-area-inset-bottom, 0);
}
body {
overflow: hidden;
height: 100vh;