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