From 5a28917429ac40a12cad8320e6c659189d8ce08a Mon Sep 17 00:00:00 2001 From: Sonny <24420064+Sonny93@users.noreply.github.com> Date: Tue, 19 Dec 2023 16:49:31 +0100 Subject: [PATCH] Mobile swipeable menu (#13) * feat(mobile): swipe right to open menu * refactor: css for side nav bar * refactor: side menu desktop & mobile --- package-lock.json | 83 ++--------- package.json | 4 +- src/components/ButtonLink.tsx | 9 +- src/components/Footer/footer.module.scss | 2 +- src/components/Links/Links.tsx | 18 ++- src/components/Links/links.module.scss | 14 +- src/components/MobileCategoriesModal.tsx | 42 ------ src/components/SideMenu/SideMenu.tsx | 63 ++++++--- src/components/SideMenu/side-menu.module.scss | 20 +++ .../Categories/Categories.tsx | 0 .../Categories/CategoryItem.tsx | 0 .../Categories/categories.module.scss | 0 .../Favorites/FavoriteItem.tsx | 0 .../Favorites/Favorites.tsx | 0 .../Favorites/favorites.module.scss | 0 .../NavigationLinks.tsx | 2 +- .../SideNavigation/SideNavigation.tsx | 25 ++++ .../UserCard/UserCard.tsx | 0 .../UserCard/user-card.module.scss | 0 .../side-nav.module.scss} | 3 - src/pages/index.tsx | 129 +++++++++++++----- src/styles/form.module.scss | 2 +- src/styles/globals.scss | 1 + src/styles/home.module.scss | 23 ++++ 24 files changed, 255 insertions(+), 185 deletions(-) delete mode 100644 src/components/MobileCategoriesModal.tsx create mode 100644 src/components/SideMenu/side-menu.module.scss rename src/components/{SideMenu => SideNavigation}/Categories/Categories.tsx (100%) rename src/components/{SideMenu => SideNavigation}/Categories/CategoryItem.tsx (100%) rename src/components/{SideMenu => SideNavigation}/Categories/categories.module.scss (100%) rename src/components/{SideMenu => SideNavigation}/Favorites/FavoriteItem.tsx (100%) rename src/components/{SideMenu => SideNavigation}/Favorites/Favorites.tsx (100%) rename src/components/{SideMenu => SideNavigation}/Favorites/favorites.module.scss (100%) rename src/components/{SideMenu => SideNavigation}/NavigationLinks.tsx (95%) create mode 100644 src/components/SideNavigation/SideNavigation.tsx rename src/components/{SideMenu => SideNavigation}/UserCard/UserCard.tsx (100%) rename src/components/{SideMenu => SideNavigation}/UserCard/user-card.module.scss (100%) rename src/components/{SideMenu/sidemenu.module.scss => SideNavigation/side-nav.module.scss} (81%) create mode 100644 src/styles/home.module.scss diff --git a/package-lock.json b/package-lock.json index 00238b5..500511f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,6 @@ "@svgr/webpack": "^8.1.0", "@types/react-toggle": "^4.0.5", "accept-language": "^3.0.18", - "axios": "^1.6.2", "clsx": "^2.0.0", "framer-motion": "^10.16.16", "i18next": "^23.7.11", @@ -30,6 +29,7 @@ "react-i18next": "^13.5.0", "react-icons": "^4.12.0", "react-select": "^5.8.0", + "react-swipeable": "^7.0.1", "react-tabs": "^6.0.2", "react-toggle": "^4.1.3", "sass": "^1.69.5", @@ -4032,11 +4032,6 @@ "has-symbols": "^1.0.3" } }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" - }, "node_modules/at-least-node": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", @@ -4065,16 +4060,6 @@ "node": ">=4" } }, - "node_modules/axios": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.2.tgz", - "integrity": "sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==", - "dependencies": { - "follow-redirects": "^1.15.0", - "form-data": "^4.0.0", - "proxy-from-env": "^1.1.0" - } - }, "node_modules/axobject-query": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.2.1.tgz", @@ -4463,17 +4448,6 @@ "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", "dev": true }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/commander": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", @@ -4779,14 +4753,6 @@ "rimraf": "bin.js" } }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/dequal": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", @@ -5814,25 +5780,6 @@ "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", "dev": true }, - "node_modules/follow-redirects": { - "version": "1.15.3", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", - "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, "node_modules/for-each": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", @@ -5841,19 +5788,6 @@ "is-callable": "^1.1.3" } }, - "node_modules/form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/framer-motion": { "version": "10.16.16", "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-10.16.16.tgz", @@ -7351,6 +7285,7 @@ "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "peer": true, "engines": { "node": ">= 0.6" } @@ -7359,6 +7294,7 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "peer": true, "dependencies": { "mime-db": "1.52.0" }, @@ -8128,11 +8064,6 @@ "resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.5.tgz", "integrity": "sha512-IJUkICM5dP5znhCckHSv30Q4b5/JA5enCtkRHYaOVOAocnH/1BQEYTC5NMfT3AVl/iXKdr3aqQbQn9DxyWknwA==" }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" - }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -8291,6 +8222,14 @@ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, + "node_modules/react-swipeable": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/react-swipeable/-/react-swipeable-7.0.1.tgz", + "integrity": "sha512-RKB17JdQzvECfnVj9yDZsiYn3vH0eyva/ZbrCZXZR0qp66PBRhtg4F9yJcJTWYT5Adadi+x4NoG53BxKHwIYLQ==", + "peerDependencies": { + "react": "^16.8.3 || ^17 || ^18" + } + }, "node_modules/react-tabs": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/react-tabs/-/react-tabs-6.0.2.tgz", diff --git a/package.json b/package.json index fd8249e..a2ffa62 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,6 @@ "@svgr/webpack": "^8.1.0", "@types/react-toggle": "^4.0.5", "accept-language": "^3.0.18", - "axios": "^1.6.2", "clsx": "^2.0.0", "framer-motion": "^10.16.16", "i18next": "^23.7.11", @@ -35,6 +34,7 @@ "react-i18next": "^13.5.0", "react-icons": "^4.12.0", "react-select": "^5.8.0", + "react-swipeable": "^7.0.1", "react-tabs": "^6.0.2", "react-toggle": "^4.1.3", "sass": "^1.69.5", @@ -57,4 +57,4 @@ "lint-staged": { "*.js": "eslint --cache --fix" } -} +} \ No newline at end of file diff --git a/src/components/ButtonLink.tsx b/src/components/ButtonLink.tsx index 798deaf..c9a1864 100644 --- a/src/components/ButtonLink.tsx +++ b/src/components/ButtonLink.tsx @@ -3,22 +3,24 @@ import { CSSProperties, ReactNode } from 'react'; export default function ButtonLink({ href = '#', + title = '', onClick, children, style = {}, className = '', -}: { +}: Readonly<{ href?: string; + title?; onClick?: (...args: any) => any; children: ReactNode; style?: CSSProperties; className?: string; -}) { +}>) { const handleClick = (event) => { if (!href || href === '#') { event.preventDefault(); } - onClick && onClick(); + onClick && onClick?.(); }; return ( {children} diff --git a/src/components/Footer/footer.module.scss b/src/components/Footer/footer.module.scss index 04558af..e02547e 100644 --- a/src/components/Footer/footer.module.scss +++ b/src/components/Footer/footer.module.scss @@ -4,5 +4,5 @@ font-size: 0.9em; color: $grey; text-align: center; - padding: 0.75em 0 0.25em; + padding-top: 0.75em; } diff --git a/src/components/Links/Links.tsx b/src/components/Links/Links.tsx index ccd07ef..126fee4 100644 --- a/src/components/Links/Links.tsx +++ b/src/components/Links/Links.tsx @@ -1,6 +1,6 @@ import clsx from 'clsx'; +import ButtonLink from 'components/ButtonLink'; import Footer from 'components/Footer/Footer'; -import MobileCategoriesModal from 'components/MobileCategoriesModal'; import CreateItem from 'components/QuickActions/CreateItem'; import EditItem from 'components/QuickActions/EditItem'; import RemoveItem from 'components/QuickActions/RemoveItem'; @@ -10,15 +10,20 @@ import useActiveCategory from 'hooks/useActiveCategory'; import { useTranslation } from 'next-i18next'; import LinkTag from 'next/link'; import { BiSearchAlt } from 'react-icons/bi'; +import { RxHamburgerMenu } from 'react-icons/rx'; import quickActionStyles from '../QuickActions/quickactions.module.scss'; import LinkItem from './LinkItem'; import styles from './links.module.scss'; interface LinksProps { isMobile: boolean; + openSideMenu: () => void; } -export default function Links({ isMobile }: Readonly) { +export default function Links({ + isMobile, + openSideMenu, +}: Readonly) { const { t } = useTranslation('home'); const { activeCategory } = useActiveCategory(); @@ -35,7 +40,14 @@ export default function Links({ isMobile }: Readonly) { return (

- {isMobile && } + {isMobile && ( + + + + )} {name} {links.length > 0 && ( diff --git a/src/components/Links/links.module.scss b/src/components/Links/links.module.scss index ca0cd18..be78665 100644 --- a/src/components/Links/links.module.scss +++ b/src/components/Links/links.module.scss @@ -31,9 +31,13 @@ & h2 { color: $blue; - margin-bottom: 0.25em; + margin-bottom: 0.5em; font-weight: 500; + & svg { + display: flex; + } + & .links-count { color: $grey; font-weight: 300; @@ -44,7 +48,7 @@ .category-header { display: flex; - gap: 0.25em; + gap: 0.4em; align-items: center; justify-content: space-between; @@ -188,3 +192,9 @@ animation: rotate 1s both reverse infinite linear; } } + +@media (max-width: 768px) { + .links-wrapper { + padding: 0; + } +} diff --git a/src/components/MobileCategoriesModal.tsx b/src/components/MobileCategoriesModal.tsx deleted file mode 100644 index 62d198a..0000000 --- a/src/components/MobileCategoriesModal.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import PATHS from 'constants/paths'; -import { AnimatePresence } from 'framer-motion'; -import useModal from 'hooks/useModal'; -import { useTranslation } from 'next-i18next'; -import { RxHamburgerMenu } from 'react-icons/rx'; -import BlockWrapper from './BlockWrapper/BlockWrapper'; -import ButtonLink from './ButtonLink'; -import Modal from './Modal/Modal'; -import Categories from './SideMenu/Categories/Categories'; - -export default function MobileCategoriesModal() { - const { t } = useTranslation(); - const mobileModal = useModal(); - - return ( - <> - - - - - {mobileModal.isShowing && ( - - - - {t('common:category.create')} - - - - - )} - - - ); -} diff --git a/src/components/SideMenu/SideMenu.tsx b/src/components/SideMenu/SideMenu.tsx index cbdbfcb..a861056 100644 --- a/src/components/SideMenu/SideMenu.tsx +++ b/src/components/SideMenu/SideMenu.tsx @@ -1,25 +1,44 @@ -import BlockWrapper from 'components/BlockWrapper/BlockWrapper'; -import Categories from './Categories/Categories'; -import Favorites from './Favorites/Favorites'; -import NavigationLinks from './NavigationLinks'; -import UserCard from './UserCard/UserCard'; -import styles from './sidemenu.module.scss'; +import { motion } from 'framer-motion'; +import { ReactNode } from 'react'; +import { createPortal } from 'react-dom'; +import { useSwipeable } from 'react-swipeable'; +import styles from './side-menu.module.scss'; -export default function SideMenu() { - return ( -
- - - - - - - - - - - - -
+interface SideMenuProps { + close?: (...args: any) => void; + children: ReactNode; +} + +export default function SideMenu({ close, children }: Readonly) { + const handlers = useSwipeable({ + trackMouse: true, + onSwipedLeft: close, + }); + + const handleWrapperClick = (event) => + event.target.classList?.[0] === styles['side-menu-wrapper'] && + close && + close(); + + return createPortal( + + + {children} + + , + document.body, ); } diff --git a/src/components/SideMenu/side-menu.module.scss b/src/components/SideMenu/side-menu.module.scss new file mode 100644 index 0000000..113e6ba --- /dev/null +++ b/src/components/SideMenu/side-menu.module.scss @@ -0,0 +1,20 @@ +@import 'styles/colors.scss'; + +.side-menu-wrapper { + z-index: 9999; + position: absolute; + top: 0; + left: 0; + height: 100%; + width: 100%; + background-color: $black-blur; + box-shadow: 0 0 1em 0 $black-blur; +} + +.side-menu-container { + height: 100%; + width: fit-content; + max-width: 100%; + background: $light-grey; + padding: 0.75em; +} diff --git a/src/components/SideMenu/Categories/Categories.tsx b/src/components/SideNavigation/Categories/Categories.tsx similarity index 100% rename from src/components/SideMenu/Categories/Categories.tsx rename to src/components/SideNavigation/Categories/Categories.tsx diff --git a/src/components/SideMenu/Categories/CategoryItem.tsx b/src/components/SideNavigation/Categories/CategoryItem.tsx similarity index 100% rename from src/components/SideMenu/Categories/CategoryItem.tsx rename to src/components/SideNavigation/Categories/CategoryItem.tsx diff --git a/src/components/SideMenu/Categories/categories.module.scss b/src/components/SideNavigation/Categories/categories.module.scss similarity index 100% rename from src/components/SideMenu/Categories/categories.module.scss rename to src/components/SideNavigation/Categories/categories.module.scss diff --git a/src/components/SideMenu/Favorites/FavoriteItem.tsx b/src/components/SideNavigation/Favorites/FavoriteItem.tsx similarity index 100% rename from src/components/SideMenu/Favorites/FavoriteItem.tsx rename to src/components/SideNavigation/Favorites/FavoriteItem.tsx diff --git a/src/components/SideMenu/Favorites/Favorites.tsx b/src/components/SideNavigation/Favorites/Favorites.tsx similarity index 100% rename from src/components/SideMenu/Favorites/Favorites.tsx rename to src/components/SideNavigation/Favorites/Favorites.tsx diff --git a/src/components/SideMenu/Favorites/favorites.module.scss b/src/components/SideNavigation/Favorites/favorites.module.scss similarity index 100% rename from src/components/SideMenu/Favorites/favorites.module.scss rename to src/components/SideNavigation/Favorites/favorites.module.scss diff --git a/src/components/SideMenu/NavigationLinks.tsx b/src/components/SideNavigation/NavigationLinks.tsx similarity index 95% rename from src/components/SideMenu/NavigationLinks.tsx rename to src/components/SideNavigation/NavigationLinks.tsx index a8a67bd..3830300 100644 --- a/src/components/SideMenu/NavigationLinks.tsx +++ b/src/components/SideNavigation/NavigationLinks.tsx @@ -3,7 +3,7 @@ import SearchModal from 'components/SearchModal/SearchModal'; import PATHS from 'constants/paths'; import useActiveCategory from 'hooks/useActiveCategory'; import { useTranslation } from 'next-i18next'; -import styles from './sidemenu.module.scss'; +import styles from './side-nav.module.scss'; export default function NavigationLinks() { const { t } = useTranslation(); diff --git a/src/components/SideNavigation/SideNavigation.tsx b/src/components/SideNavigation/SideNavigation.tsx new file mode 100644 index 0000000..28877db --- /dev/null +++ b/src/components/SideNavigation/SideNavigation.tsx @@ -0,0 +1,25 @@ +import BlockWrapper from 'components/BlockWrapper/BlockWrapper'; +import Categories from './Categories/Categories'; +import Favorites from './Favorites/Favorites'; +import NavigationLinks from './NavigationLinks'; +import UserCard from './UserCard/UserCard'; +import styles from './side-nav.module.scss'; + +export default function SideNavigation() { + return ( +
+ + + + + + + + + + + + +
+ ); +} diff --git a/src/components/SideMenu/UserCard/UserCard.tsx b/src/components/SideNavigation/UserCard/UserCard.tsx similarity index 100% rename from src/components/SideMenu/UserCard/UserCard.tsx rename to src/components/SideNavigation/UserCard/UserCard.tsx diff --git a/src/components/SideMenu/UserCard/user-card.module.scss b/src/components/SideNavigation/UserCard/user-card.module.scss similarity index 100% rename from src/components/SideMenu/UserCard/user-card.module.scss rename to src/components/SideNavigation/UserCard/user-card.module.scss diff --git a/src/components/SideMenu/sidemenu.module.scss b/src/components/SideNavigation/side-nav.module.scss similarity index 81% rename from src/components/SideMenu/sidemenu.module.scss rename to src/components/SideNavigation/side-nav.module.scss index e715108..ca4c3a1 100644 --- a/src/components/SideMenu/sidemenu.module.scss +++ b/src/components/SideNavigation/side-nav.module.scss @@ -3,9 +3,6 @@ .side-menu { height: 100%; width: 325px; - padding: 0 25px 0 10px; - border-right: 1px solid $lightest-grey; - margin-right: 15px; display: flex; align-items: center; flex-direction: column; diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 225a621..baef7c9 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -2,20 +2,24 @@ import clsx from 'clsx'; import Links from 'components/Links/Links'; import PageTransition from 'components/PageTransition'; import SideMenu from 'components/SideMenu/SideMenu'; -import UserCard from 'components/SideMenu/UserCard/UserCard'; +import SideNavigation from 'components/SideNavigation/SideNavigation'; import * as Keys from 'constants/keys'; import PATHS from 'constants/paths'; import ActiveCategoryContext from 'contexts/activeCategoryContext'; import CategoriesContext from 'contexts/categoriesContext'; import FavoritesContext from 'contexts/favoritesContext'; import GlobalHotkeysContext from 'contexts/globalHotkeysContext'; +import { AnimatePresence } from 'framer-motion'; import { useMediaQuery } from 'hooks/useMediaQuery'; +import useModal from 'hooks/useModal'; import { getServerSideTranslation } from 'i18n'; import getUserCategories from 'lib/category/getUserCategories'; import sortCategoriesByNextId from 'lib/category/sortCategoriesByNextId'; import { useRouter } from 'next/router'; -import { useMemo, useState } from 'react'; +import { ReactNode, useCallback, useEffect, useMemo, useState } from 'react'; import { useHotkeys } from 'react-hotkeys-hook'; +import { useSwipeable } from 'react-swipeable'; +import styles from 'styles/home.module.scss'; import { CategoryWithLinks, LinkWithCategory } from 'types/types'; import { withAuthentication } from 'utils/session'; @@ -25,10 +29,63 @@ interface HomePageProps { } export default function HomePage(props: Readonly) { - const router = useRouter(); const isMobile = useMediaQuery('(max-width: 768px)'); + const { isShowing, open, close } = useModal(); + const handlers = useSwipeable({ + trackMouse: true, + onSwipedRight: open, + }); - const [globalHotkeysEnable, setGlobalHotkeysEnabled] = + useEffect(() => { + if (!isMobile && isShowing) { + close(); + } + }, [close, isMobile, isShowing]); + + return ( + + +
+ {!isMobile && ( +
+ +
+ )} + + {isShowing && ( + + + + )} + + +
+
+
+ ); +} + +function HomeProviders( + props: Readonly<{ + children: ReactNode; + categories: CategoryWithLinks[]; + activeCategory: CategoryWithLinks; + }>, +) { + const router = useRouter(); + const [globalHotkeysEnabled, setGlobalHotkeysEnabled] = useState(true); const [categories, setCategories] = useState( props.categories, @@ -36,10 +93,13 @@ export default function HomePage(props: Readonly) { const [activeCategory, setActiveCategory] = useState(props.activeCategory || categories?.[0]); - const handleChangeCategory = (category: CategoryWithLinks) => { - setActiveCategory(category); - router.push(`${PATHS.HOME}?categoryId=${category.id}`); - }; + const handleChangeCategory = useCallback( + (category: CategoryWithLinks) => { + setActiveCategory(category); + router.push(`${PATHS.HOME}?categoryId=${category.id}`); + }, + [router], + ); const favorites = useMemo( () => @@ -52,44 +112,47 @@ export default function HomePage(props: Readonly) { [categories], ); + const categoriesContextValue = useMemo( + () => ({ categories, setCategories }), + [categories], + ); + const activeCategoryContextValue = useMemo( + () => ({ activeCategory, setActiveCategory: handleChangeCategory }), + [activeCategory, handleChangeCategory], + ); + const favoritesContextValue = useMemo(() => ({ favorites }), [favorites]); + const globalHotkeysContextValue = useMemo( + () => ({ + globalHotkeysEnabled: globalHotkeysEnabled, + setGlobalHotkeysEnabled, + }), + [globalHotkeysEnabled], + ); + useHotkeys( Keys.OPEN_CREATE_LINK_KEY, () => { router.push(`${PATHS.LINK.CREATE}?categoryId=${activeCategory.id}`); }, - { enabled: globalHotkeysEnable }, + { enabled: globalHotkeysEnabled }, ); useHotkeys( Keys.OPEN_CREATE_CATEGORY_KEY, () => { router.push(PATHS.CATEGORY.CREATE); }, - { enabled: globalHotkeysEnable }, + { enabled: globalHotkeysEnabled }, ); - return ( - - - - - - {isMobile ? : } - - - - - - + + + + + {props.children} + + + + ); } diff --git a/src/styles/form.module.scss b/src/styles/form.module.scss index 823e18f..75b8e88 100644 --- a/src/styles/form.module.scss +++ b/src/styles/form.module.scss @@ -30,7 +30,7 @@ @media (max-width: 768px) { .form-container { width: 100%; - margin-top: 5em; + margin-top: 1em; padding: 0 1em; } } diff --git a/src/styles/globals.scss b/src/styles/globals.scss index 48141ba..d305a93 100644 --- a/src/styles/globals.scss +++ b/src/styles/globals.scss @@ -214,6 +214,7 @@ kbd { @media (max-width: 768px) { .App { + padding: 0.75em; flex-direction: column; } diff --git a/src/styles/home.module.scss b/src/styles/home.module.scss new file mode 100644 index 0000000..abd233d --- /dev/null +++ b/src/styles/home.module.scss @@ -0,0 +1,23 @@ +@import 'colors.scss'; + +.swipe-handler { + height: 100%; + width: 100%; + display: flex; + transition: background-color 0.15s; +} + +.side-menu { + position: absolute; + top: 0; + height: 100%; + background-color: $light-grey; + box-shadow: 0 0 1em 0 $black-blur; + padding: 0.75em; +} + +.side-bar { + padding-right: 0.75em; + border-right: 1px solid $lightest-grey; + margin-right: 5px; +}