@ -3,6 +3,7 @@
import Image , { type StaticImageData } from "next/image" ;
import Image , { type StaticImageData } from "next/image" ;
import type { HTMLAttributes , ReactNode } from "react" ;
import type { HTMLAttributes , ReactNode } from "react" ;
import { useCallback , useEffect , useRef , useState } from "react" ;
import { useCallback , useEffect , useRef , useState } from "react" ;
import { createPortal } from "react-dom" ;
import Button from "./button" ;
import Button from "./button" ;
import { LoadingSkeleton } from "./loading-skeleton" ;
import { LoadingSkeleton } from "./loading-skeleton" ;
import { LoadingThreeDot } from "./loading-three-dot" ;
import { LoadingThreeDot } from "./loading-three-dot" ;
@ -41,6 +42,7 @@ export type InformationSheetProps = Omit<
HTMLAttributes < HTMLDivElement > ,
HTMLAttributes < HTMLDivElement > ,
"children" | "title"
"children" | "title"
> & {
> & {
isOpen? : boolean ;
icon? : InformationSheetIcon | null ;
icon? : InformationSheetIcon | null ;
title? : ReactNode | ( ( controls : { close : ( ) = > void } ) = > ReactNode ) ;
title? : ReactNode | ( ( controls : { close : ( ) = > void } ) = > ReactNode ) ;
description? : ReactNode ;
description? : ReactNode ;
@ -182,6 +184,7 @@ function resolveIcon(icon: InformationSheetIcon | null | undefined) {
}
}
export function InformationSheet ( {
export function InformationSheet ( {
isOpen = true ,
icon ,
icon ,
title ,
title ,
description ,
description ,
@ -200,36 +203,63 @@ export function InformationSheet({
locale === "ur" ||
locale === "ur" ||
locale === "he" ||
locale === "he" ||
locale === "ks" ;
locale === "ks" ;
const [ isVisible , setIsVisible ] = useState ( true ) ;
const [ mounted , setMounted ] = useState ( false ) ;
const [ isRendered , setIsRendered ] = useState ( isOpen ) ;
const [ isClosing , setIsClosing ] = useState ( false ) ;
const [ isClosing , setIsClosing ] = useState ( false ) ;
const isClosingRef = useRef ( false ) ;
const isMountedRef = useRef ( true ) ;
const isMountedRef = useRef ( true ) ;
const prevIsOpenRef = useRef ( isOpen ) ;
const timerRef = useRef < number | null > ( null ) ;
useEffect ( ( ) = > {
useEffect ( ( ) = > {
isMountedRef . current = true ;
isMountedRef . current = true ;
setMounted ( true ) ;
return ( ) = > {
return ( ) = > {
isMountedRef . current = false ;
isMountedRef . current = false ;
if ( timerRef . current ) clearTimeout ( timerRef . current ) ;
} ;
} ;
} , [ ] ) ;
} , [ ] ) ;
const resolvedIcon = resolveIcon ( icon ) ;
const closeSheet = useCallback ( ( ) = > {
const closeSheet = useCallback ( ( ) = > {
if ( isClosing ) {
if ( isClosingRef . current ) {
return ;
return ;
}
}
isClosingRef . current = true ;
setIsClosing ( true ) ;
setIsClosing ( true ) ;
window . setTimeout ( ( ) = > {
if ( timerRef . current ) clearTimeout ( timerRef . current ) ;
timerRef . current = window . setTimeout ( ( ) = > {
if ( isMountedRef . current ) {
if ( isMountedRef . current ) {
setIsVisible ( false ) ;
setIsRendered ( false ) ;
setIsClosing ( false ) ;
isClosingRef . current = false ;
}
}
onClose ? . ( ) ;
onClose ? . ( ) ;
} , EXIT_ANIMATION_MS ) ;
} , EXIT_ANIMATION_MS ) ;
} , [ isClosing , onClose ] ) ;
} , [ onClose ] ) ;
// Synchronize external isOpen prop changes safely without bounce
useEffect ( ( ) = > {
if ( prevIsOpenRef . current !== isOpen ) {
prevIsOpenRef . current = isOpen ;
if ( isOpen ) {
if ( timerRef . current ) clearTimeout ( timerRef . current ) ;
isClosingRef . current = false ;
setIsClosing ( false ) ;
setIsRendered ( true ) ;
} else if ( ! isClosingRef . current && isRendered ) {
closeSheet ( ) ;
}
}
} , [ isOpen , isRendered , closeSheet ] ) ;
const resolvedIcon = resolveIcon ( icon ) ;
useHardwareBackHandler ( ( ) = > {
useHardwareBackHandler ( ( ) = > {
closeSheet ( ) ;
closeSheet ( ) ;
return true ;
return true ;
} , isVisible && ! isClosing ) ;
} , isRendered && ! isClosing ) ;
const controls = { close : closeSheet } ;
const controls = { close : closeSheet } ;
const resolvedTitle = typeof title === "function" ? title ( controls ) : title ;
const resolvedTitle = typeof title === "function" ? title ( controls ) : title ;
@ -243,7 +273,7 @@ export function InformationSheet({
) ;
) ;
useEffect ( ( ) = > {
useEffect ( ( ) = > {
if ( ! isVisible ) {
if ( ! isRendered ) {
return ;
return ;
}
}
@ -257,13 +287,13 @@ export function InformationSheet({
document . body . style . overflow = previousBodyOverflow ;
document . body . style . overflow = previousBodyOverflow ;
document . documentElement . style . overflow = previousHtmlOverflow ;
document . documentElement . style . overflow = previousHtmlOverflow ;
} ;
} ;
} , [ isVisible ] ) ;
} , [ isRendered ] ) ;
if ( ! isVisible ) {
if ( ! isRendered || ! mounted ) {
return null ;
return null ;
}
}
return (
const content = (
< div
< div
className = { [
className = { [
"fixed inset-0 z-50 flex items-end justify-center overflow-y-auto" ,
"fixed inset-0 z-50 flex items-end justify-center overflow-y-auto" ,
@ -396,6 +426,8 @@ export function InformationSheet({
< / section >
< / section >
< / div >
< / div >
) ;
) ;
return createPortal ( content , document . body ) ;
}
}
export default InformationSheet ;
export default InformationSheet ;