chore: improve animation of blurred overlay

This commit is contained in:
Mahyar Khanbolooki
2025-07-12 23:09:22 +03:30
parent 3ac533956e
commit 817af6e2e0
6 changed files with 67 additions and 40 deletions
+13 -13
View File
@@ -19,7 +19,7 @@
"clsx": "^2.1.1", "clsx": "^2.1.1",
"cmdk": "^1.1.1", "cmdk": "^1.1.1",
"dmenu-plus-frontned": "file:", "dmenu-plus-frontned": "file:",
"framer-motion": "^12.20.1", "framer-motion": "^12.23.3",
"iconsax-react": "^0.0.8", "iconsax-react": "^0.0.8",
"lucide-react": "^0.525.0", "lucide-react": "^0.525.0",
"next": "15.3.4", "next": "15.3.4",
@@ -4078,13 +4078,13 @@
} }
}, },
"node_modules/framer-motion": { "node_modules/framer-motion": {
"version": "12.20.1", "version": "12.23.3",
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.20.1.tgz", "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.23.3.tgz",
"integrity": "sha512-NW2t2GHQcNvLHq18JyNVY15VKrwru+nkNyhLdqf4MbxbGhxZcSDi68iNcAy6O1nG0yYAQJbLioBIH1Kmg8Xr1g==", "integrity": "sha512-llmLkf44zuIZOPSrE4bl4J0UTg6bav+rlKEfMRKgvDMXqBrUtMg6cspoQeRVK3nqRLxTmAJhfGXk39UDdZD7Kw==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"motion-dom": "^12.20.1", "motion-dom": "^12.23.2",
"motion-utils": "^12.19.0", "motion-utils": "^12.23.2",
"tslib": "^2.4.0" "tslib": "^2.4.0"
}, },
"peerDependencies": { "peerDependencies": {
@@ -5418,18 +5418,18 @@
} }
}, },
"node_modules/motion-dom": { "node_modules/motion-dom": {
"version": "12.20.1", "version": "12.23.2",
"resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.20.1.tgz", "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.23.2.tgz",
"integrity": "sha512-XyveLJ9dmQTmaEsP9RlcuoNFxWlRIGdasdPJBB4aOwPr8bRcJdhltudAbiEjRQBmsGD30sjJdaEjhkHsAHapLQ==", "integrity": "sha512-73j6xDHX/NvVh5L5oS1ouAVnshsvmApOq4F3VZo5MkYSD/YVsVLal4Qp9wvVgJM9uU2bLZyc0Sn8B9c/MMKk4g==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"motion-utils": "^12.19.0" "motion-utils": "^12.23.2"
} }
}, },
"node_modules/motion-utils": { "node_modules/motion-utils": {
"version": "12.19.0", "version": "12.23.2",
"resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.19.0.tgz", "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.23.2.tgz",
"integrity": "sha512-BuFTHINYmV07pdWs6lj6aI63vr2N4dg0vR+td0rtrdpWOhBzIkEklZyLcvKBoEtwSqx8Jg06vUB5RS0xDiUybw==", "integrity": "sha512-cIEXlBlXAOUyiAtR0S+QPQUM9L3Diz23Bo+zM420NvSd/oPQJwg6U+rT+WRTpp0rizMsBGQOsAwhWIfglUcZfA==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/ms": { "node_modules/ms": {
+1 -1
View File
@@ -20,7 +20,7 @@
"clsx": "^2.1.1", "clsx": "^2.1.1",
"cmdk": "^1.1.1", "cmdk": "^1.1.1",
"dmenu-plus-frontned": "file:", "dmenu-plus-frontned": "file:",
"framer-motion": "^12.20.1", "framer-motion": "^12.23.3",
"iconsax-react": "^0.0.8", "iconsax-react": "^0.0.8",
"lucide-react": "^0.525.0", "lucide-react": "^0.525.0",
"next": "15.3.4", "next": "15.3.4",
+2 -2
View File
@@ -234,7 +234,7 @@ const MenuIndex = () => {
</VerticalScrollView> </VerticalScrollView>
</section> </section>
<AnimatedBottomSheet title="فیلتر ها" visible={filterModal} changeDelay="150" onClick={toggleFilterModal}> <AnimatedBottomSheet title="فیلتر ها" visible={filterModal} outDelay={150} onClick={toggleFilterModal}>
<div className="ps-8.5 pe-[31px] mt-[89px]"> <div className="ps-8.5 pe-[31px] mt-[89px]">
<SearchComboBox <SearchComboBox
title="محتویات" title="محتویات"
@@ -277,7 +277,7 @@ const MenuIndex = () => {
</div> </div>
</AnimatedBottomSheet> </AnimatedBottomSheet>
<AnimatedBottomSheet title="مرتب کردن بر اساس" visible={sortingModal} changeDelay="150" onClick={toggleSortingModal}> <AnimatedBottomSheet title="مرتب کردن بر اساس" visible={sortingModal} inDelay={150} onClick={toggleSortingModal}>
<div className="px-8.5 py-10 justify-between"> <div className="px-8.5 py-10 justify-between">
{sortings.map((v, i) => { {sortings.map((v, i) => {
return ( return (
@@ -9,7 +9,7 @@ type Props = {
const AnimatedBottomSheet = (props: Props) => { const AnimatedBottomSheet = (props: Props) => {
return ( return (
<BlurredOverlayContainer {...props} changeDelay="150"> <BlurredOverlayContainer {...props} inDelay={150}>
<div <div
data-visible={props.visible} data-visible={props.visible}
className={clsx( className={clsx(
+1 -1
View File
@@ -76,7 +76,7 @@ function SideMenu({ menuState, toggleMenuState, nightModeState, togglenightModeS
return ( return (
<div className='w-full h-dvh'> <div className='w-full h-dvh'>
<BlurredOverlayContainer bgOpacity='40' visible={menuStateMemo} onClick={toggleMenuState} changeDelay='300'> <BlurredOverlayContainer bgOpacity={40} visible={menuStateMemo} onClick={toggleMenuState} outDelay={150}>
<div <div
data-isvisible={menuStateMemo} data-isvisible={menuStateMemo}
onClick={(e) => { e.stopPropagation(); }} onClick={(e) => { e.stopPropagation(); }}
@@ -1,20 +1,26 @@
'use client' 'use client'
import React, { useEffect, useState } from 'react' import React, { ReactNode, useEffect, useState } from 'react'
import { motion, Variants } from "framer-motion";
import clsx from 'clsx';
export type BlurredOverlayContainerProps = { export type BlurredOverlayContainerProps = {
visible: boolean visible: boolean
bgOpacity?: string bgOpacity?: number
changeDelay?: string inDelay?: number
effectdelay?: string outDelay?: number
} & React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement> transitionDelay?: number
children: ReactNode
} & React.ComponentProps<typeof motion.div>
const BlurredOverlayContainer = ({ const BlurredOverlayContainer = ({
visible = true, visible = true,
bgOpacity = '30', bgOpacity = 30,
changeDelay = '0', inDelay = 0,
effectdelay = '0', outDelay = 0,
transitionDelay: transitionDelay = 0,
children, children,
className,
onClick, onClick,
...props ...props
}: BlurredOverlayContainerProps) => { }: BlurredOverlayContainerProps) => {
@@ -23,34 +29,55 @@ const BlurredOverlayContainer = ({
useEffect(() => { useEffect(() => {
if (visible) { if (visible) {
// Show immediately or after delay // Show immediately or after delay
const timeout = setTimeout(() => setLoaded(true), +changeDelay) const timeout = setTimeout(() => setLoaded(true), inDelay)
return () => clearTimeout(timeout) return () => clearTimeout(timeout)
} else { } else {
// Hide with delay (matches opacity transition duration) // Hide with delay (matches opacity transition duration)
const timeout = setTimeout(() => setLoaded(false), +changeDelay) // 300ms matches Tailwind transition const timeout = setTimeout(() => setLoaded(false), outDelay) // 300ms matches Tailwind transition
return () => clearTimeout(timeout) return () => clearTimeout(timeout)
} }
}, [visible, changeDelay]) }, [visible, inDelay, outDelay])
const zClass = const variants: Variants = {
visible || loaded ? 'z-50' : !visible && !loaded ? '-z-50' : 'z-0' hidden: {
opacity: 0,
transition: {
delay: transitionDelay / 1000,
duration: 0.3,
ease: 'easeInOut',
},
},
visible: {
opacity: 1,
transition: {
delay: transitionDelay / 1000,
duration: 0.3,
ease: 'easeInOut',
},
},
};
return ( return (
<div <motion.div
data-visible={visible} data-visible={visible}
data-loaded={loaded} data-loaded={loaded}
className={props.className + ` className={clsx(
fixed inset-0 flex items-center justify-center h-dvh w-full className,
backdrop-blur-sm visible || loaded ? 'z-50' : !visible && !loaded ? '-z-50' : 'z-0',
opacity-0 transition-opacity duration-300 ease-in-out delay-${effectdelay} 'fixed inset-0 flex items-center justify-center h-dvh w-full backdrop-blur-sm',
${zClass} )}
${visible ? 'opacity-100' : 'opacity-0'}`} style={{
style={{ backgroundColor: `rgba(0, 0, 0, ${+bgOpacity / 100})` }} backgroundColor: `rgba(0, 0, 0, ${bgOpacity / 100})`,
}}
initial={visible ? 'visible' : 'hidden'}
animate={visible ? 'visible' : 'hidden'}
variants={variants}
{...props} {...props}
> >
<div className='w-full h-dvh overflow-hidden' onClick={onClick}></div> <div className='w-full h-dvh overflow-hidden' onClick={onClick}></div>
{children} {children}
</div> </motion.div>
) )
} }