1- import React from 'react' ;
1+ import React , { useLayoutEffect , useRef , useState } from 'react' ;
2+ import { createPortal } from 'react-dom' ;
23import { AppBar , Theme , ThemeProvider , Box , useMediaQuery , Select , MenuItem , SelectChangeEvent } from '@mui/material' ;
3- import { Palette , colors , DarkTheme , BaseTheme } from 'styles/Theme' ;
4+ import { Palette , colors , DarkTheme , BaseTheme , ZIndex } from 'styles/Theme' ;
45import { When , Unless } from 'react-if' ;
56import { BodyText } from 'components/common/Typography' ;
67import { elevations } from 'components/common' ;
@@ -21,6 +22,58 @@ const AuthoringBottomNav = ({ currentLanguage, setCurrentLanguage, languages, pa
2122 } = useFormContext < EngagementUpdateData > ( ) ;
2223 const isMediumScreenOrLarger = useMediaQuery ( ( theme : Theme ) => theme . breakpoints . up ( 'md' ) ) ;
2324 const padding = { xs : '1rem 1rem' , md : '1rem 1.5rem 1rem 2rem' , lg : '1rem 3rem 1rem 2rem' } ;
25+ const [ portalEl , setPortalEl ] = useState < HTMLElement | null > ( null ) ;
26+ const [ atFooter , setAtFooter ] = useState ( false ) ;
27+ const navRef = useRef < HTMLDivElement | null > ( null ) ;
28+ const placeholderRef = useRef < HTMLDivElement | null > ( null ) ;
29+
30+ useLayoutEffect ( ( ) => {
31+ const footer = document . querySelector ( 'footer' ) ;
32+ const hostParent = footer ?. parentElement ?? document . body ;
33+ const placeholder = document . createElement ( 'div' ) ;
34+ placeholder . id = 'authoring-bottom-nav-portal' ;
35+ placeholder . style . position = 'relative' ;
36+ placeholder . style . zIndex = `${ ZIndex . sideNav } ` ; // keep above page content
37+ placeholder . style . width = '100%' ;
38+ placeholder . style . top = '2em' ; // slight offset to avoid any potential overlap with footer content
39+ hostParent . insertBefore ( placeholder , footer ?? null ) ;
40+ placeholderRef . current = placeholder ;
41+ setPortalEl ( placeholder ) ;
42+
43+ let footerObserver : IntersectionObserver | null = null ;
44+ if ( footer ) {
45+ footerObserver = new IntersectionObserver (
46+ ( entries ) => {
47+ const intersecting = entries . some ( ( entry ) => entry . isIntersecting ) ;
48+ setAtFooter ( intersecting ) ;
49+ } ,
50+ { threshold : 0 } ,
51+ ) ;
52+ footerObserver . observe ( footer ) ;
53+ }
54+
55+ return ( ) => {
56+ footerObserver ?. disconnect ( ) ;
57+ placeholder . remove ( ) ;
58+ setPortalEl ( null ) ;
59+ placeholderRef . current = null ;
60+ } ;
61+ } , [ ] ) ;
62+
63+ useLayoutEffect ( ( ) => {
64+ if ( ! portalEl || ! navRef . current ) return ;
65+ const updateHeight = ( ) => {
66+ portalEl . style . height = `${ navRef . current ?. offsetHeight ?? 0 } px` ;
67+ } ;
68+ updateHeight ( ) ;
69+ const resizeObserver = new ResizeObserver ( updateHeight ) ;
70+ resizeObserver . observe ( navRef . current ) ;
71+ globalThis . addEventListener ( 'resize' , updateHeight , { passive : true } ) ;
72+ return ( ) => {
73+ resizeObserver . disconnect ( ) ;
74+ globalThis . removeEventListener ( 'resize' , updateHeight ) ;
75+ } ;
76+ } , [ portalEl ] ) ;
2477
2578 const buttonStyles = {
2679 height : '2.6rem' ,
@@ -31,10 +84,28 @@ const AuthoringBottomNav = ({ currentLanguage, setCurrentLanguage, languages, pa
3184 fontSize : '0.9rem' ,
3285 } ;
3386
34- return (
87+ if ( ! portalEl ) return null ;
88+
89+ const fixedPosition = {
90+ position : 'fixed' as const ,
91+ bottom : 0 ,
92+ left : 0 ,
93+ right : 0 ,
94+ width : '100%' ,
95+ } ;
96+ const absolutePosition = {
97+ position : 'absolute' as const ,
98+ bottom : 0 ,
99+ left : 0 ,
100+ right : 0 ,
101+ width : '100%' ,
102+ } ;
103+
104+ return createPortal (
35105 < AppBar
36106 component = { 'nav' }
37- position = "fixed"
107+ position = "static"
108+ ref = { navRef }
38109 sx = { {
39110 backgroundColor : 'transparent' ,
40111 borderTopRightRadius : '16px' ,
@@ -44,9 +115,8 @@ const AuthoringBottomNav = ({ currentLanguage, setCurrentLanguage, languages, pa
44115 backgroundClip : 'padding-box' ,
45116 overflow : 'hidden' ,
46117 top : 'auto' ,
47- left : 0 ,
48- bottom : 0 ,
49118 boxShadow : elevations . default ,
119+ ...( atFooter ? absolutePosition : fixedPosition ) ,
50120 } }
51121 data-testid = "appbar-authoring-bottom-nav"
52122 >
@@ -130,7 +200,8 @@ const AuthoringBottomNav = ({ currentLanguage, setCurrentLanguage, languages, pa
130200 < Box style = { { width : '25rem' , display : 'flex' } } > </ Box >
131201 </ ThemeProvider >
132202 </ Box >
133- </ AppBar >
203+ </ AppBar > ,
204+ portalEl ,
134205 ) ;
135206} ;
136207
0 commit comments