From 6d2240fb55d84b4f50e9f5c1db13c168c0ab92c0 Mon Sep 17 00:00:00 2001 From: DrMint Date: Sat, 7 May 2022 19:55:08 +0200 Subject: [PATCH] Lightbox gestures and keyboard navigation --- package-lock.json | 63 ++++++++++++--------- package.json | 3 +- src/components/AppLayout.tsx | 24 ++++---- src/components/LightBox.tsx | 94 ++++++++++++++++++++----------- src/components/Popup.tsx | 85 ++++++++++++++++++---------- src/contexts/AppLayoutContext.tsx | 10 +++- 6 files changed, 176 insertions(+), 103 deletions(-) diff --git a/package-lock.json b/package-lock.json index e54a9fe..711cb84 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,12 +14,13 @@ "@tippyjs/react": "^4.2.6", "autoprefixer": "^10.4.7", "graphql-request": "^4.2.0", + "lightgallery": "^2.4.0", "markdown-to-jsx": "^7.1.7", "next": "^12.1.6", "nodemailer": "^6.7.5", "react": "18.1.0", "react-dom": "18.1.0", - "react-hotkeys-hook": "^3.4.4", + "react-hot-keys": "^2.7.2", "react-swipeable": "^7.0.0", "turndown": "^7.1.1" }, @@ -907,7 +908,6 @@ "version": "7.17.2", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.17.2.tgz", "integrity": "sha512-hzeyJyMA1YGdJTuWU0e/j4wKXrU4OMFvY2MSlaI9B7VQb0r5cxTE3EAIS2Q7Tn2RIcDkRvTA/v2JsAEhxe99uw==", - "dev": true, "dependencies": { "regenerator-runtime": "^0.13.4" }, @@ -6275,6 +6275,14 @@ "node": ">= 0.8.0" } }, + "node_modules/lightgallery": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/lightgallery/-/lightgallery-2.4.0.tgz", + "integrity": "sha512-9i/E/w3yaqs56y3k6SWIUS3JTLpeDCZIVnIuNppzAmj5KjLGy5wrFisoDUD0HdxVUdX0wHG5mjvB4h0TXtyf/w==", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/lilconfig": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.5.tgz", @@ -6965,7 +6973,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -7574,7 +7581,6 @@ "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "dev": true, "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", @@ -7679,23 +7685,24 @@ "react": "^18.1.0" } }, - "node_modules/react-hotkeys-hook": { - "version": "3.4.4", - "resolved": "https://registry.npmjs.org/react-hotkeys-hook/-/react-hotkeys-hook-3.4.4.tgz", - "integrity": "sha512-vaORq07rWgmuF3owWRhgFV/3VL8/l2q9lz0WyVEddJnWTtKW+AOgU5YgYKuwN6h6h7bCcLG3MFsJIjCrM/5DvQ==", + "node_modules/react-hot-keys": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/react-hot-keys/-/react-hot-keys-2.7.2.tgz", + "integrity": "sha512-Z7eSh7SU6s52+zP+vkfFoNk0x4kgEmnwqDiyACKv53crK2AZ7FUaBLnf+vxLor3dvtId9murLmKOsrJeYgeHWw==", "dependencies": { - "hotkeys-js": "3.8.7" + "hotkeys-js": "^3.8.1", + "prop-types": "^15.7.2" }, "peerDependencies": { - "react": ">=16.8.1", - "react-dom": ">=16.8.1" + "@babel/runtime": ">=7.10.0", + "react": ">=16.9.0", + "react-dom": ">=16.9.0" } }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "dev": true + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, "node_modules/react-swipeable": { "version": "7.0.0", @@ -7734,8 +7741,7 @@ "node_modules/regenerator-runtime": { "version": "0.13.9", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", - "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==", - "dev": true + "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==" }, "node_modules/regexp.prototype.flags": { "version": "1.4.3", @@ -9605,7 +9611,6 @@ "version": "7.17.2", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.17.2.tgz", "integrity": "sha512-hzeyJyMA1YGdJTuWU0e/j4wKXrU4OMFvY2MSlaI9B7VQb0r5cxTE3EAIS2Q7Tn2RIcDkRvTA/v2JsAEhxe99uw==", - "dev": true, "requires": { "regenerator-runtime": "^0.13.4" } @@ -13768,6 +13773,11 @@ "type-check": "~0.4.0" } }, + "lightgallery": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/lightgallery/-/lightgallery-2.4.0.tgz", + "integrity": "sha512-9i/E/w3yaqs56y3k6SWIUS3JTLpeDCZIVnIuNppzAmj5KjLGy5wrFisoDUD0HdxVUdX0wHG5mjvB4h0TXtyf/w==" + }, "lilconfig": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.5.tgz", @@ -14265,8 +14275,7 @@ "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" }, "object-hash": { "version": "3.0.0", @@ -14677,7 +14686,6 @@ "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "dev": true, "requires": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", @@ -14749,19 +14757,19 @@ "scheduler": "^0.22.0" } }, - "react-hotkeys-hook": { - "version": "3.4.4", - "resolved": "https://registry.npmjs.org/react-hotkeys-hook/-/react-hotkeys-hook-3.4.4.tgz", - "integrity": "sha512-vaORq07rWgmuF3owWRhgFV/3VL8/l2q9lz0WyVEddJnWTtKW+AOgU5YgYKuwN6h6h7bCcLG3MFsJIjCrM/5DvQ==", + "react-hot-keys": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/react-hot-keys/-/react-hot-keys-2.7.2.tgz", + "integrity": "sha512-Z7eSh7SU6s52+zP+vkfFoNk0x4kgEmnwqDiyACKv53crK2AZ7FUaBLnf+vxLor3dvtId9murLmKOsrJeYgeHWw==", "requires": { - "hotkeys-js": "3.8.7" + "hotkeys-js": "^3.8.1", + "prop-types": "^15.7.2" } }, "react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "dev": true + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, "react-swipeable": { "version": "7.0.0", @@ -14792,8 +14800,7 @@ "regenerator-runtime": { "version": "0.13.9", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", - "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==", - "dev": true + "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==" }, "regexp.prototype.flags": { "version": "1.4.3", diff --git a/package.json b/package.json index 722fb05..58c7a70 100644 --- a/package.json +++ b/package.json @@ -21,12 +21,13 @@ "@tippyjs/react": "^4.2.6", "autoprefixer": "^10.4.7", "graphql-request": "^4.2.0", + "lightgallery": "^2.4.0", "markdown-to-jsx": "^7.1.7", "next": "^12.1.6", "nodemailer": "^6.7.5", "react": "18.1.0", "react-dom": "18.1.0", - "react-hotkeys-hook": "^3.4.4", + "react-hot-keys": "^2.7.2", "react-swipeable": "^7.0.0", "turndown": "^7.1.1" }, diff --git a/src/components/AppLayout.tsx b/src/components/AppLayout.tsx index 044ab83..34256a7 100644 --- a/src/components/AppLayout.tsx +++ b/src/components/AppLayout.tsx @@ -39,19 +39,23 @@ export default function AppLayout(props: Props): JSX.Element { const handlers = useSwipeable({ onSwipedLeft: (SwipeEventData) => { - if (SwipeEventData.velocity < sensibilitySwipe) return; - if (appLayout.mainPanelOpen) { - appLayout.setMainPanelOpen(false); - } else if (subPanel && contentPanel) { - appLayout.setSubPanelOpen(true); + if (appLayout.menuGestures) { + if (SwipeEventData.velocity < sensibilitySwipe) return; + if (appLayout.mainPanelOpen) { + appLayout.setMainPanelOpen(false); + } else if (subPanel && contentPanel) { + appLayout.setSubPanelOpen(true); + } } }, onSwipedRight: (SwipeEventData) => { - if (SwipeEventData.velocity < sensibilitySwipe) return; - if (appLayout.subPanelOpen) { - appLayout.setSubPanelOpen(false); - } else { - appLayout.setMainPanelOpen(true); + if (appLayout.menuGestures) { + if (SwipeEventData.velocity < sensibilitySwipe) return; + if (appLayout.subPanelOpen) { + appLayout.setSubPanelOpen(false); + } else { + appLayout.setMainPanelOpen(true); + } } }, }); diff --git a/src/components/LightBox.tsx b/src/components/LightBox.tsx index fa521da..eeb42c2 100644 --- a/src/components/LightBox.tsx +++ b/src/components/LightBox.tsx @@ -1,5 +1,6 @@ -import { Dispatch, SetStateAction, useCallback } from "react"; -import { useHotkeys } from "react-hotkeys-hook"; +import { Dispatch, SetStateAction } from "react"; +import Hotkeys from "react-hot-keys"; +import { useSwipeable } from "react-swipeable"; import Img from "./Img"; import Button from "./Inputs/Button"; import Popup from "./Popup"; @@ -16,46 +17,73 @@ interface Props { export default function LightBox(props: Props): JSX.Element { const { state, setState, images, index, setIndex } = props; - const handlePrevious = useCallback(() => { - setIndex((previousIndex) => (previousIndex > 0 ? previousIndex - 1 : 0)); - }, [setIndex]); - const handleNext = useCallback(() => { - setIndex((previousIndex) => - previousIndex < images.length - 1 ? previousIndex + 1 : images.length - 1 - ); - }, [images.length, setIndex]); + function handlePrevious() { + if (index > 0) setIndex(index - 1); + } - useHotkeys("left", handlePrevious); - useHotkeys("right", handleNext); + function handleNext() { + if (index < images.length - 1) setIndex(index + 1); + } + + const sensibilitySwipe = 0.5; + + const handlers = useSwipeable({ + onSwipedLeft: (SwipeEventData) => { + if (SwipeEventData.velocity < sensibilitySwipe) return; + handleNext(); + }, + onSwipedRight: (SwipeEventData) => { + if (SwipeEventData.velocity < sensibilitySwipe) return; + handlePrevious(); + }, + }); return ( <> {state && ( - -
-
- {index > 0 && ( - - )} -
+ { + if (keyName === "left") { + handlePrevious(); + } else { + handleNext(); + } + }} + > + +
+
+ {index > 0 && ( + + )} +
- + -
- {index < images.length - 1 && ( - - )} +
+ {index < images.length - 1 && ( + + )} +
-
-
+ +
)} ); diff --git a/src/components/Popup.tsx b/src/components/Popup.tsx index 628c8bf..5d0b96b 100644 --- a/src/components/Popup.tsx +++ b/src/components/Popup.tsx @@ -1,4 +1,6 @@ -import { Dispatch, SetStateAction } from "react"; +import { useAppLayout } from "contexts/AppLayoutContext"; +import { Dispatch, SetStateAction, useEffect } from "react"; +import Hotkeys from "react-hot-keys"; interface Props { setState: @@ -8,42 +10,65 @@ interface Props { children: React.ReactNode; fillViewport?: boolean; hideBackground?: boolean; + padding?: boolean; } export default function Popup(props: Props): JSX.Element { + const { + setState, + state, + children, + fillViewport, + hideBackground, + padding = true, + } = props; + + const appLayout = useAppLayout(); + + useEffect(() => { + appLayout.setMenuGestures(!state); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [state]); + return ( -
{ + setState(false); + }} >
{ - props.setState(false); - }} - /> - -
- {props.children} +
{ + setState(false); + }} + /> + +
+ {children} +
-
+ ); } diff --git a/src/contexts/AppLayoutContext.tsx b/src/contexts/AppLayoutContext.tsx index 79a750b..67b0fd2 100644 --- a/src/contexts/AppLayoutContext.tsx +++ b/src/contexts/AppLayoutContext.tsx @@ -1,6 +1,6 @@ import useDarkMode from "hooks/useDarkMode"; import useStateWithLocalStorage from "hooks/useStateWithLocalStorage"; -import React, { ReactNode, useContext } from "react"; +import React, { ReactNode, useContext, useState } from "react"; interface AppLayoutState { subPanelOpen: boolean | undefined; @@ -14,6 +14,7 @@ interface AppLayoutState { currency: string | undefined; playerName: string | undefined; preferredLanguages: string[] | undefined; + menuGestures: boolean; setSubPanelOpen: React.Dispatch>; setConfigPanelOpen: React.Dispatch>; setMainPanelReduced: React.Dispatch< @@ -31,6 +32,7 @@ interface AppLayoutState { setPreferredLanguages: React.Dispatch< React.SetStateAction >; + setMenuGestures: React.Dispatch>; } /* eslint-disable @typescript-eslint/no-empty-function */ @@ -46,6 +48,7 @@ const initialState: AppLayoutState = { currency: "USD", playerName: "", preferredLanguages: [], + menuGestures: true, setSubPanelOpen: () => {}, setMainPanelReduced: () => {}, setMainPanelOpen: () => {}, @@ -57,6 +60,7 @@ const initialState: AppLayoutState = { setCurrency: () => {}, setPlayerName: () => {}, setPreferredLanguages: () => {}, + setMenuGestures: () => {}, }; /* eslint-enable @typescript-eslint/no-empty-function */ @@ -115,6 +119,8 @@ export function AppContextProvider(props: Props): JSX.Element { string[] | undefined >("preferredLanguages", initialState.preferredLanguages); + const [menuGestures, setMenuGestures] = useState(false); + return ( {props.children}