@@ -4,6 +4,7 @@ import { AnimatePresence, motion, type HTMLMotionProps } from 'motion/react';
44import {
55 useCallback ,
66 useEffect ,
7+ useLayoutEffect ,
78 useRef ,
89 useState ,
910 type ReactNode ,
@@ -12,12 +13,18 @@ import { createPortal } from 'react-dom';
1213import { MaskedScrollArea } from '../../masked-scrollarea' ;
1314import { useMacosVersion } from '@/utils/use-utils-bridge' ;
1415import { commitGlobalCSSVar } from '@/embedded/chat/global-css-var' ;
16+ import {
17+ resolveActivityCenterAnchorHeight ,
18+ resolveActivityCenterExitY ,
19+ shouldPadActivityCenterSafeArea ,
20+ } from './layout' ;
1521
1622export const ActivityCenterBanner = ( {
1723 header,
1824 children,
1925 visible,
2026 className,
27+ style,
2128 ...props
2229} : HTMLMotionProps < 'div' > & {
2330 header : ( expanded : boolean ) => ReactNode ;
@@ -27,34 +34,64 @@ export const ActivityCenterBanner = ({
2734 const ref = useRef < HTMLDivElement > ( null ) ;
2835 const macosVersion = useMacosVersion ( ) ;
2936 const [ open , setOpen ] = useState ( true ) ;
37+ const [ anchorHeight , setAnchorHeight ] = useState ( 0 ) ;
38+ const [ measuredVisibleHeight , setMeasuredVisibleHeight ] = useState ( 0 ) ;
3039
3140 // banner enter with a spring animation,
3241 // add extra space to avoid bottom side bounce into viewport
3342 const safeArea = 60 ;
3443 const headerHeight = 36 ;
3544
36- const commitHeight = useCallback ( ( height : number ) => {
37- commitGlobalCSSVar (
38- 'activityCenterHeight' ,
39- `${ ( height ? height - safeArea : 0 ) . toFixed ( 0 ) } px`
40- ) ;
41- } , [ ] ) ;
45+ const usesSafeAreaPadding = shouldPadActivityCenterSafeArea ( {
46+ anchorHeight,
47+ measuredVisibleHeight,
48+ } ) ;
4249
4350 const detectHeight = useCallback ( ( ) => {
4451 const el = ref . current ;
4552 if ( ! el || ! visible ) {
46- commitHeight ( 0 ) ;
53+ setMeasuredVisibleHeight ( 0 ) ;
54+ setAnchorHeight ( 0 ) ;
4755 return ;
4856 }
57+
58+ const paddingBottom =
59+ parseFloat ( window . getComputedStyle ( el ) . paddingBottom ) || 0 ;
4960 const height = el . getBoundingClientRect ( ) . height ;
50- commitHeight ( height ) ;
51- } , [ visible , commitHeight ] ) ;
61+ const nextMeasuredVisibleHeight = Math . max ( 0 , height - paddingBottom ) ;
62+
63+ setMeasuredVisibleHeight ( nextMeasuredVisibleHeight ) ;
64+ setAnchorHeight ( previousAnchorHeight =>
65+ resolveActivityCenterAnchorHeight ( {
66+ previousAnchorHeight,
67+ measuredVisibleHeight : nextMeasuredVisibleHeight ,
68+ isOpen : open ,
69+ } )
70+ ) ;
71+ } , [ open , visible ] ) ;
5272
5373 // const debouncedDetectHeight = useMemo(
5474 // () => debounce(detectHeight, 100),
5575 // [detectHeight]
5676 // );
5777
78+ useLayoutEffect ( ( ) => {
79+ detectHeight ( ) ;
80+ } , [ detectHeight ] ) ;
81+
82+ useEffect ( ( ) => {
83+ commitGlobalCSSVar (
84+ 'activityCenterHeight' ,
85+ `${ ( visible ? anchorHeight : 0 ) . toFixed ( 0 ) } px`
86+ ) ;
87+ } , [ anchorHeight , visible ] ) ;
88+
89+ useEffect ( ( ) => {
90+ return ( ) => {
91+ commitGlobalCSSVar ( 'activityCenterHeight' , '0px' ) ;
92+ } ;
93+ } , [ ] ) ;
94+
5895 useEffect ( ( ) => {
5996 if ( ! visible ) {
6097 detectHeight ( ) ;
@@ -64,18 +101,17 @@ export const ActivityCenterBanner = ({
64101 if ( ! el ) return ;
65102
66103 const dispose = observeResize ( el , detectHeight ) ;
67- return ( ) => {
68- detectHeight ( ) ;
69- dispose ( ) ;
70- } ;
104+ return dispose ;
71105 } , [ visible , detectHeight ] ) ;
72106
73107 return createPortal (
74108 < AnimatePresence >
75109 { visible && (
76110 < motion . div
77111 initial = { { y : 'calc(100% + 20px)' } }
78- exit = { { y : 'calc(100% + 20px)' } }
112+ exit = { {
113+ y : resolveActivityCenterExitY ( { anchorHeight, safeArea } ) ,
114+ } }
79115 animate = { { y : 0 } }
80116 transition = { { type : 'spring' , stiffness : 100 , damping : 15 } }
81117 className = { cn (
@@ -91,7 +127,13 @@ export const ActivityCenterBanner = ({
91127 : 'bg-surface-card' ,
92128 ''
93129 ) }
94- style = { { paddingBottom : safeArea , bottom : - safeArea } }
130+ style = { {
131+ paddingBottom : usesSafeAreaPadding ? safeArea : 0 ,
132+ ...( anchorHeight > 0
133+ ? { top : `calc(100dvh - ${ anchorHeight . toFixed ( 0 ) } px)` }
134+ : { bottom : - safeArea } ) ,
135+ ...style ,
136+ } }
95137 { ...props }
96138 ref = { ref }
97139 >
@@ -126,9 +168,12 @@ export const ActivityCenterBanner = ({
126168 { /* Shadow below the banner */ }
127169 < div
128170 className = { cn (
129- 'absolute top-0 left-0 w-full h-[calc(100%-60px)] -z-1 rounded-t-2xl' ,
171+ 'absolute top-0 left-0 w-full -z-1 rounded-t-2xl' ,
130172 'shadow-[0_-24px_24px_rgba(0,0,0,0.1)]'
131173 ) }
174+ style = { {
175+ height : `calc(100% - ${ usesSafeAreaPadding ? safeArea : 0 } px)` ,
176+ } }
132177 />
133178 </ motion . div >
134179 ) }
0 commit comments