Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -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',
},
}
11 changes: 4 additions & 7 deletions app/brands/[slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -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: {
Expand Down Expand Up @@ -67,13 +67,10 @@ export default async function Manufacturer({ params: { slug } }: Props) {
</Box>
<div className="my-8 flex flex-col items-center gap-8 sm:flex-row">
{manufacturer.images.map(image => (
<NextImage
key={image.id}
width={250}
height={250}
<ImageLightbox
src={googleStorageImagesFolder + image.url}
alt={image.name ?? 'Manufacturer'}
className="w-2/3 sm:w-[250px]"
name={image.name}
key={image.id}
/>
))}
</div>
Expand Down
2 changes: 1 addition & 1 deletion app/components/header/mobile-menu/mobile-menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
41 changes: 41 additions & 0 deletions app/components/modal/image-lightbox.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<>
<NextImage
width={250}
height={250}
src={src}
alt={name}
className="w-2/3 cursor-zoom-in sm:w-[250px]"
onClick={open}
priority
/>

<ZoomModal isOpened={opened} close={close}>
<NextImage
className="h-auto max-h-screen w-auto"
width={1000}
height={1000}
quality={100}
src={src}
alt={name}
loading="lazy"
/>
</ZoomModal>
</>
)
}
2 changes: 2 additions & 0 deletions app/components/modal/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { ZoomModal } from './zoom-modal'
export { ImageLightbox } from './image-lightbox'
61 changes: 61 additions & 0 deletions app/components/modal/zoom-modal.tsx
Original file line number Diff line number Diff line change
@@ -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(
<AnimatePresence>
{isOpened && (
<>
<motion.div
key="overlay"
initial={{ opacity: 0, backgroundColor: 'rgba(0,0,0,0.0)' }}
animate={{ opacity: 1, backgroundColor: 'rgba(0,0,0,0.4)' }}
exit={{ opacity: 0, backgroundColor: 'rgba(0,0,0,0.0)' }}
id="modal-portal"
className="fixed inset-0 z-50 flex items-center justify-center backdrop-blur-xl"
/>
<div className="fixed inset-0 z-[51] flex items-center justify-center">
<motion.div
key="content"
ref={childrenContainer}
initial={{ scale: 0.8, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }}
exit={{ scale: 0.8, opacity: 0 }}
>
{children}
</motion.div>
</div>
</>
)}
</AnimatePresence>,
document.body,
)
}
106 changes: 80 additions & 26 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down
Loading