diff --git a/packages/pl-fe/src/components/ui/layout.tsx b/packages/pl-fe/src/components/ui/layout.tsx index c1c4b22a3..e5edfa5ab 100644 --- a/packages/pl-fe/src/components/ui/layout.tsx +++ b/packages/pl-fe/src/components/ui/layout.tsx @@ -54,6 +54,37 @@ const useMinWidth = (query: string) => { return matches; }; +interface WindowControlsOverlay extends EventTarget { + visible: boolean; + getTitlebarAreaRect(): DOMRect; +} + +declare global { + interface Navigator { + windowControlsOverlay?: WindowControlsOverlay; + } +} + +const useWindowControlsOverlay = () => { + const getRect = (): DOMRect | null => { + const overlay = navigator.windowControlsOverlay; + return overlay?.visible ? overlay.getTitlebarAreaRect() : null; + }; + + const [rect, setRect] = useState(getRect); + + useEffect(() => { + const overlay = navigator.windowControlsOverlay; + if (!overlay) return; + + const update = () => setRect(overlay.visible ? overlay.getTitlebarAreaRect() : null); + overlay.addEventListener('geometrychange', update); + return () => overlay.removeEventListener('geometrychange', update); + }, []); + + return rect; +}; + /** Layout container, to hold Sidebar, Main, and Aside. */ const Layout: LayoutComponent = ({ children, fullWidth }) => (
@@ -70,6 +101,8 @@ const Layout: LayoutComponent = ({ children, fullWidth }) => ( /** Left sidebar container in the UI. */ const Sidebar: React.FC = ({ children, shrink }) => { const isVisible = useMinWidth(`(min-width: ${breakpoints.lg})`); + const wcoRect = useWindowControlsOverlay(); + const offsetTop = wcoRect && wcoRect.x > 0 ? 16 + wcoRect.y : 16; if (!isVisible) { return null; @@ -77,7 +110,7 @@ const Sidebar: React.FC = ({ children, shrink }) => { return (
- + {children}
@@ -106,6 +139,8 @@ const Main: React.FC> = ({ children, classN /** Right sidebar container in the UI. */ const Aside: React.FC = ({ children }) => { const isVisible = useMinWidth(`(min-width: ${breakpoints.xl})`); + const wcoRect = useWindowControlsOverlay(); + const offsetTop = wcoRect && wcoRect.x + wcoRect.width < window.innerWidth ? 16 + wcoRect.y : 16; if (!isVisible) { return null; @@ -113,7 +148,7 @@ const Aside: React.FC = ({ children }) => { return ( diff --git a/packages/pl-fe/src/init/pl-fe-head.tsx b/packages/pl-fe/src/init/pl-fe-head.tsx index ab5a176b5..ebcb55fb1 100644 --- a/packages/pl-fe/src/init/pl-fe-head.tsx +++ b/packages/pl-fe/src/init/pl-fe-head.tsx @@ -1,5 +1,5 @@ import clsx from 'clsx'; -import React, { useEffect } from 'react'; +import React, { useEffect, useMemo } from 'react'; import InlineStyle from '@/components/inline-style'; import { useFrontendConfig } from '@/hooks/use-frontend-config'; @@ -25,6 +25,7 @@ const PlFeHead = () => { } = useSettings(); const frontendConfig = useFrontendConfig(); const theme = useTheme(); + const [wcoVisible, setWcoVisible] = React.useState(false); const withModals = useHasModals(); @@ -45,6 +46,22 @@ const PlFeHead = () => { } }, [dsn]); + useEffect(() => { + const overlay = navigator.windowControlsOverlay; + if (!overlay) return; + + const update = () => setWcoVisible(overlay.visible); + overlay.addEventListener('geometrychange', update); + return () => overlay.removeEventListener('geometrychange', update); + }, []); + + const color = useMemo(() => { + if (wcoVisible) { + return window.getComputedStyle(document.body, null).getPropertyValue('background-color'); + } + return frontendConfig.brandColor; + }, [frontendConfig.brandColor, theme, wcoVisible]); + return ( <> @@ -53,10 +70,11 @@ const PlFeHead = () => { className={clsx(`text-${themeSettings?.interfaceSize ?? 'md'}`, { dark: theme === 'dark', 'black dark': theme === 'black', + 'window-controls-overlay': wcoVisible, })} /> - + {`:root { ${themeCss} }`} {['dark', 'black'].includes(theme) && ( diff --git a/packages/pl-fe/src/styles/new/layout.scss b/packages/pl-fe/src/styles/new/layout.scss index 723016bc2..d827c779a 100644 --- a/packages/pl-fe/src/styles/new/layout.scss +++ b/packages/pl-fe/src/styles/new/layout.scss @@ -1,4 +1,5 @@ @use 'mixins'; +@use 'variables'; html { height: 100%; @@ -373,6 +374,14 @@ body { &--chats { @apply xl:pb-16; } + + &:is(.window-controls-overlay *) { + padding-top: env(titlebar-area-height); + + @media (min-width: variables.$breakpoint-lg) { + padding-top: 0; + } + } } &__aside {