fix: keep iOS popovers below the notch (#282)
## Summary - add safe-area CSS variables for the viewport top and bottom insets - apply safe-area-aware collision padding to the shared popover primitive so iOS overlays stay below the notch - add a focused regression test for the popover safe-area guard ## Testing - vendor/bin/pint --dirty --format agent - php artisan test --compact tests/Feature/PopoverSafeAreaTest.php
This commit is contained in:
parent
80b666836c
commit
ea9956f21d
|
|
@ -71,6 +71,8 @@
|
|||
}
|
||||
|
||||
:root {
|
||||
--safe-area-top: env(safe-area-inset-top, 0px);
|
||||
--safe-area-bottom: env(safe-area-inset-bottom, 0px);
|
||||
--background: oklch(1 0 0);
|
||||
--foreground: oklch(0.145 0 0);
|
||||
--card: oklch(1 0 0);
|
||||
|
|
|
|||
|
|
@ -3,6 +3,32 @@ import * as PopoverPrimitive from "@radix-ui/react-popover"
|
|||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
function useSafeAreaTopPadding(): number {
|
||||
const [padding, setPadding] = React.useState(8)
|
||||
|
||||
React.useEffect(() => {
|
||||
const updatePadding = () => {
|
||||
const value = Number.parseFloat(
|
||||
getComputedStyle(document.documentElement).getPropertyValue("--safe-area-top")
|
||||
)
|
||||
|
||||
setPadding(Number.isNaN(value) ? 8 : Math.max(8, value + 8))
|
||||
}
|
||||
|
||||
updatePadding()
|
||||
|
||||
window.addEventListener("resize", updatePadding)
|
||||
window.visualViewport?.addEventListener("resize", updatePadding)
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("resize", updatePadding)
|
||||
window.visualViewport?.removeEventListener("resize", updatePadding)
|
||||
}
|
||||
}, [])
|
||||
|
||||
return padding
|
||||
}
|
||||
|
||||
function Popover({
|
||||
...props
|
||||
}: React.ComponentProps<typeof PopoverPrimitive.Root>) {
|
||||
|
|
@ -19,14 +45,18 @@ function PopoverContent({
|
|||
className,
|
||||
align = "center",
|
||||
sideOffset = 4,
|
||||
collisionPadding,
|
||||
...props
|
||||
}: React.ComponentProps<typeof PopoverPrimitive.Content>) {
|
||||
const safeAreaTopPadding = useSafeAreaTopPadding()
|
||||
|
||||
return (
|
||||
<PopoverPrimitive.Portal>
|
||||
<PopoverPrimitive.Content
|
||||
data-slot="popover-content"
|
||||
align={align}
|
||||
sideOffset={sideOffset}
|
||||
collisionPadding={collisionPadding ?? { top: safeAreaTopPadding, right: 8, bottom: 8, left: 8 }}
|
||||
className={cn(
|
||||
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-72 origin-(--radix-popover-content-transform-origin) rounded-md border p-4 shadow-md outline-hidden",
|
||||
className
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
<?php
|
||||
|
||||
test('popover content keeps a safe top collision padding', function () {
|
||||
$popover = file_get_contents(resource_path('js/components/ui/popover.tsx'));
|
||||
$css = file_get_contents(resource_path('css/app.css'));
|
||||
|
||||
expect($popover)->toContain('collisionPadding={collisionPadding ?? { top: safeAreaTopPadding, right: 8, bottom: 8, left: 8 }}')
|
||||
->and($css)->toContain('--safe-area-top: env(safe-area-inset-top, 0px);');
|
||||
});
|
||||
Loading…
Reference in New Issue