diff --git a/.eslintrc.cjs b/.eslintrc.cjs index cca6c683..4eac033f 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -20,5 +20,6 @@ module.exports = { rules: { 'react/react-in-jsx-scope': 'off', 'no-console': ['error', { allow: ['warn', 'error'] }], + 'jsx-a11y/no-noninteractive-element-interactions': 'off', }, } diff --git a/app/brands/[slug]/page.tsx b/app/brands/[slug]/page.tsx index 1326a2e8..b451989f 100644 --- a/app/brands/[slug]/page.tsx +++ b/app/brands/[slug]/page.tsx @@ -1,5 +1,4 @@ import { type Metadata } from 'next' -import NextImage from 'next/image' import { Anchor, Box, Container, Grid, GridCol, Title } from '@mantine/core' import Link from 'next/link' import { Fragment } from 'react' @@ -10,6 +9,7 @@ import { BarometerCardWithIcon } from '@/app/components/barometer-card' import { FrontRoutes } from '@/utils/routes-front' import { MD } from '@/app/components/md' import { googleStorageImagesFolder } from '@/utils/constants' +import { ImageLightbox } from '@/app/components/modal' interface Props { params: { @@ -67,13 +67,10 @@ export default async function Manufacturer({ params: { slug } }: Props) {
{manufacturer.images.map(image => ( - ))}
diff --git a/app/components/header/mobile-menu/mobile-menu.tsx b/app/components/header/mobile-menu/mobile-menu.tsx index bd99b2a3..1a1f0e9c 100644 --- a/app/components/header/mobile-menu/mobile-menu.tsx +++ b/app/components/header/mobile-menu/mobile-menu.tsx @@ -15,7 +15,7 @@ import { Center, } from '@mantine/core' import Link from 'next/link' -import * as motion from 'framer-motion/client' +import { motion } from 'motion/react' import { IconChevronRight, IconAt, IconBrandInstagram } from '@tabler/icons-react' import { useSession } from 'next-auth/react' import { AccessRole } from '@prisma/client' diff --git a/app/components/modal/image-lightbox.tsx b/app/components/modal/image-lightbox.tsx new file mode 100644 index 00000000..0b68a7fc --- /dev/null +++ b/app/components/modal/image-lightbox.tsx @@ -0,0 +1,41 @@ +'use client' + +import NextImage from 'next/image' +import { useDisclosure } from '@mantine/hooks' +import { ZoomModal } from './zoom-modal' + +interface ImageLightboxProps { + src: string + name?: string | null | undefined +} + +export function ImageLightbox({ src, name }: ImageLightboxProps) { + name ??= 'Image' + const [opened, { open, close }] = useDisclosure(false) + + return ( + <> + + + + + + + ) +} diff --git a/app/components/modal/index.ts b/app/components/modal/index.ts new file mode 100644 index 00000000..c60ff517 --- /dev/null +++ b/app/components/modal/index.ts @@ -0,0 +1,2 @@ +export { ZoomModal } from './zoom-modal' +export { ImageLightbox } from './image-lightbox' diff --git a/app/components/modal/zoom-modal.tsx b/app/components/modal/zoom-modal.tsx new file mode 100644 index 00000000..6f32bb96 --- /dev/null +++ b/app/components/modal/zoom-modal.tsx @@ -0,0 +1,61 @@ +'use client' + +import { PropsWithChildren, useEffect, useState } from 'react' +import { createPortal } from 'react-dom' +import { motion, AnimatePresence } from 'motion/react' +import { useClickOutside } from '@mantine/hooks' + +interface ZoomModalProps extends PropsWithChildren { + isOpened: boolean + close: () => void +} + +export function ZoomModal({ children, close, isOpened }: ZoomModalProps) { + const [mounted, setMounted] = useState(false) + const childrenContainer = useClickOutside(close) + + // the component is rendered on the client + useEffect(() => { + setMounted(true) + }, []) + + // setting scroll lock if the modal is opened + useEffect(() => { + if (!mounted) return undefined + document.body.style.overflow = isOpened ? 'hidden' : '' + return () => { + document.body.style.overflow = '' + } + }, [isOpened, mounted]) + + if (!mounted) return null + + return createPortal( + + {isOpened && ( + <> + +
+ + {children} + +
+ + )} +
, + document.body, + ) +} diff --git a/package-lock.json b/package-lock.json index c46f13d1..8c377ca2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32,8 +32,8 @@ "clsx": "^2.1.1", "dayjs": "^1.11.13", "dotenv": "^16.4.7", - "framer-motion": "^11.5.4", "fs-extra": "^11.2.0", + "motion": "^12.6.3", "next": "^14.2.26", "next-auth": "^4.24.7", "react": "18.3.1", @@ -68,6 +68,7 @@ "@types/jest": "^29.5.12", "@types/node": "^20.14.8", "@types/react": "18.3.3", + "@types/react-dom": "^18.3.5", "@types/traverse": "^0.6.37", "@types/validator": "^13.12.2", "@types/ws": "^8.5.13", @@ -6324,6 +6325,16 @@ "csstype": "^3.0.2" } }, + "node_modules/@types/react-dom": { + "version": "18.3.5", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.5.tgz", + "integrity": "sha512-P4t6saawp+b/dFrUr2cvkVsfvPguwsxtH6dNIYRllMsefqFzkZk5UIjzyDOv5g1dXIPdG4Sp1yCR4Z6RCUsG/Q==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^18.0.0" + } + }, "node_modules/@types/request": { "version": "2.48.12", "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.12.tgz", @@ -12009,31 +12020,6 @@ "url": "https://github.com/sponsors/rawify" } }, - "node_modules/framer-motion": { - "version": "11.11.10", - "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-11.11.10.tgz", - "integrity": "sha512-061Bt1jL/vIm+diYIiA4dP/Yld7vD47ROextS7ESBW5hr4wQFhxB5D5T5zAc3c/5me3cOa+iO5LqhA38WDln/A==", - "license": "MIT", - "dependencies": { - "tslib": "^2.4.0" - }, - "peerDependencies": { - "@emotion/is-prop-valid": "*", - "react": "^18.0.0", - "react-dom": "^18.0.0" - }, - "peerDependenciesMeta": { - "@emotion/is-prop-valid": { - "optional": true - }, - "react": { - "optional": true - }, - "react-dom": { - "optional": true - } - } - }, "node_modules/fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", @@ -16682,6 +16668,74 @@ "node": ">=10" } }, + "node_modules/motion": { + "version": "12.6.3", + "resolved": "https://registry.npmjs.org/motion/-/motion-12.6.3.tgz", + "integrity": "sha512-zw/vqUgv5F5m9fkvOl/eCv2AF1+tkeZl3fu2uIlisIaip8sm5e0CouAl6GkdiRoF+G7s29CjqMdIyPMirwUGHA==", + "license": "MIT", + "dependencies": { + "framer-motion": "^12.6.3", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/motion-dom": { + "version": "12.6.3", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.6.3.tgz", + "integrity": "sha512-gRY08RjcnzgFYLemUZ1lo/e9RkBxR+6d4BRvoeZDSeArG4XQXERSPapKl3LNQRu22Sndjf1h+iavgY0O4NrYqA==", + "license": "MIT", + "dependencies": { + "motion-utils": "^12.6.3" + } + }, + "node_modules/motion-utils": { + "version": "12.6.3", + "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.6.3.tgz", + "integrity": "sha512-R/b3Ia2VxtTNZ4LTEO5pKYau1OUNHOuUfxuP0WFCTDYdHkeTBR9UtxR1cc8mDmKr8PEhmmfnTKGz3rSMjNRoRg==", + "license": "MIT" + }, + "node_modules/motion/node_modules/framer-motion": { + "version": "12.6.3", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.6.3.tgz", + "integrity": "sha512-2hsqknz23aloK85bzMc9nSR2/JP+fValQ459ZTVElFQ0xgwR2YqNjYSuDZdFBPOwVCt4Q9jgyTt6hg6sVOALzw==", + "license": "MIT", + "dependencies": { + "motion-dom": "^12.6.3", + "motion-utils": "^12.6.3", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, "node_modules/mrmime": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.0.tgz", diff --git a/package.json b/package.json index 5f61f7fd..1bad5ebd 100644 --- a/package.json +++ b/package.json @@ -48,8 +48,8 @@ "clsx": "^2.1.1", "dayjs": "^1.11.13", "dotenv": "^16.4.7", - "framer-motion": "^11.5.4", "fs-extra": "^11.2.0", + "motion": "^12.6.3", "next": "^14.2.26", "next-auth": "^4.24.7", "react": "18.3.1", @@ -84,6 +84,7 @@ "@types/jest": "^29.5.12", "@types/node": "^20.14.8", "@types/react": "18.3.3", + "@types/react-dom": "^18.3.5", "@types/traverse": "^0.6.37", "@types/validator": "^13.12.2", "@types/ws": "^8.5.13",