Lightbox gestures and keyboard navigation

This commit is contained in:
DrMint 2022-05-07 19:55:08 +02:00
parent d648509311
commit 6d2240fb55
6 changed files with 176 additions and 103 deletions

63
package-lock.json generated
View File

@ -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",

View File

@ -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"
},

View File

@ -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);
}
}
},
});

View File

@ -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 && (
<Popup setState={setState} state={state} fillViewport>
<div
className="grid grid-cols-[4em,1fr,4em] place-items-center
gap-4 w-full h-full overflow-hidden"
>
<div>
{index > 0 && (
<Button onClick={handlePrevious}>
<span className="material-icons">chevron_left</span>
</Button>
)}
</div>
<Hotkeys
keyName="left,right"
allowRepeat
onKeyDown={(keyName) => {
if (keyName === "left") {
handlePrevious();
} else {
handleNext();
}
}}
>
<Popup setState={setState} state={state} padding={false} fillViewport>
<div
{...handlers}
className={`grid grid-cols-[4em,1fr,4em] mobile:grid-cols-2
[grid-template-areas:"left_image_right"]
mobile:[grid-template-areas:"image_image""left_right"]
place-items-center first-letter:gap-4 w-full h-full overflow-hidden`}
>
<div className="[grid-area:left]">
{index > 0 && (
<Button onClick={handlePrevious}>
<span className="material-icons">chevron_left</span>
</Button>
)}
</div>
<Img className="max-h-full" image={images[index]} />
<Img
className="max-h-full [grid-area:image]"
image={images[index]}
/>
<div>
{index < images.length - 1 && (
<Button onClick={handleNext}>
<span className="material-icons">chevron_right</span>
</Button>
)}
<div className="[grid-area:right]">
{index < images.length - 1 && (
<Button onClick={handleNext}>
<span className="material-icons">chevron_right</span>
</Button>
)}
</div>
</div>
</div>
</Popup>
</Popup>
</Hotkeys>
)}
</>
);

View File

@ -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 (
<div
className={`fixed inset-0 z-50 grid place-content-center
transition-[backdrop-filter] duration-500 ${
props.state
? "[backdrop-filter:blur(2px)]"
: "pointer-events-none touch-none"
}`}
<Hotkeys
keyName="escape"
allowRepeat
onKeyDown={() => {
setState(false);
}}
>
<div
className={`fixed bg-shade inset-0 transition-all duration-500 ${
props.state ? "bg-opacity-50" : "bg-opacity-0"
}`}
onClick={() => {
props.setState(false);
}}
/>
<div
className={`p-10 grid gap-4 place-items-center transition-transform ${
props.state ? "scale-100" : "scale-0"
} ${
props.fillViewport
? "absolute inset-10"
: "relative max-h-[80vh] overflow-y-auto mobile:w-[85vw]"
} ${
props.hideBackground
? ""
: "bg-light rounded-lg shadow-2xl shadow-shade"
}`}
className={`fixed inset-0 z-50 grid place-content-center
transition-[backdrop-filter] duration-500 ${
state ? "[backdrop-filter:blur(2px)]" : "pointer-events-none touch-none"
}`}
>
{props.children}
<div
className={`fixed bg-shade inset-0 transition-all duration-500 ${
state ? "bg-opacity-50" : "bg-opacity-0"
}`}
onClick={() => {
setState(false);
}}
/>
<div
className={`${
padding && "p-10 mobile:p-6"
} grid gap-4 place-items-center transition-transform ${
state ? "scale-100" : "scale-0"
} ${
fillViewport
? "absolute inset-10"
: "relative max-h-[80vh] overflow-y-auto mobile:w-[85vw]"
} ${
hideBackground ? "" : "bg-light rounded-lg shadow-2xl shadow-shade"
}`}
>
{children}
</div>
</div>
</div>
</Hotkeys>
);
}

View File

@ -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<React.SetStateAction<boolean | undefined>>;
setConfigPanelOpen: React.Dispatch<React.SetStateAction<boolean | undefined>>;
setMainPanelReduced: React.Dispatch<
@ -31,6 +32,7 @@ interface AppLayoutState {
setPreferredLanguages: React.Dispatch<
React.SetStateAction<string[] | undefined>
>;
setMenuGestures: React.Dispatch<React.SetStateAction<boolean>>;
}
/* 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 (
<AppContext.Provider
value={{
@ -129,6 +135,7 @@ export function AppContextProvider(props: Props): JSX.Element {
currency,
playerName,
preferredLanguages,
menuGestures,
setSubPanelOpen,
setConfigPanelOpen,
setMainPanelReduced,
@ -140,6 +147,7 @@ export function AppContextProvider(props: Props): JSX.Element {
setCurrency,
setPlayerName,
setPreferredLanguages,
setMenuGestures,
}}
>
{props.children}