fix(ios): respect safe-area insets so chrome clears the notch/home bar

Content was drawing under the iOS status bar and home indicator because
the app uses viewport-fit=cover + black-translucent but the fixed/sticky
chrome didn't reserve the safe areas.

- bottom nav (MyLayout): move the 50px row into an inner box and let the
  bar extend by env(safe-area-inset-bottom) so icons sit above the home
  indicator (previously the fixed height clipped the inset padding).
- top headers gain env(safe-area-inset-top): HomePageTopBar, ExploreTopBar,
  SearchTopBar, CartHeader, ChatHeader, and wrapper-padded HeaderWithSupport
  / ProfilePagesHeader (so their absolutely-positioned buttons shift too).
- chat input pads bottom by the inset; chat scroll area height accounts for
  the taller header/input.
- _index.tsx no longer overrides the viewport meta (it was dropping
  viewport-fit=cover on the home page); the global one in root.tsx wins.

env(safe-area-inset-*) is 0 on non-notch devices, so desktop/Android render
identically — only iOS gains the padding.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Arda Samadi 2026-06-18 13:42:51 +03:30
parent 66b14cfa74
commit b4d5f8d8db
11 changed files with 72 additions and 64 deletions

View File

@ -39,7 +39,8 @@ const HeaderWithSupport = memo(function HeaderWithSupport({
return ( return (
<> <>
<div className="flex flex-row h-[65px] sticky top-0 z-30 bg-WHITE w-full items-center justify-center border-b border-inner-border"> <div className="sticky top-0 z-30 bg-WHITE pt-[env(safe-area-inset-top)]">
<div className="relative flex flex-row h-[65px] bg-WHITE w-full items-center justify-center border-b border-inner-border">
<ArrowRight <ArrowRight
onClick={onBack} onClick={onBack}
className="absolute top-5 right-5 cursor-pointer" className="absolute top-5 right-5 cursor-pointer"
@ -57,6 +58,7 @@ const HeaderWithSupport = memo(function HeaderWithSupport({
</div> </div>
)} )}
</div> </div>
</div>
<CallDialog <CallDialog
isCallModalOpen={isCallModalOpen} isCallModalOpen={isCallModalOpen}

View File

@ -65,7 +65,7 @@ const MyLayout = React.memo(({ children }: { children: React.ReactNode }) => {
}`} }`}
> >
<div <div
className={`pb-[calc(50px+env(safe-area-inset-bottom))] ${ className={`pb-[calc(50px_+_env(safe-area-inset-bottom))] ${
expandOnDesktop ? "lg:pb-0" : "" expandOnDesktop ? "lg:pb-0" : ""
}`} }`}
> >
@ -364,10 +364,11 @@ const MobileBottomNavigation = ({
return ( return (
<> <>
<div <div
className={`h-[50px] z-20 bg-WHITE flex gap-[48px] px-[20px] fixed bottom-0 border-t w-full max-w-md mx-auto items-center justify-center pb-[env(safe-area-inset-bottom)] lg:left-1/2 lg:transform lg:-translate-x-1/2 ${ className={`z-20 bg-WHITE fixed bottom-0 border-t w-full max-w-md mx-auto pb-[env(safe-area-inset-bottom)] lg:left-1/2 lg:transform lg:-translate-x-1/2 ${
hideOnDesktop ? "lg:hidden" : "" hideOnDesktop ? "lg:hidden" : ""
}`} }`}
> >
<div className="h-[50px] flex gap-[48px] px-[20px] items-center justify-center">
{navItems.map((item) => ( {navItems.map((item) => (
<Link <Link
key={item.link} key={item.link}
@ -393,6 +394,7 @@ const MobileBottomNavigation = ({
</Link> </Link>
))} ))}
</div> </div>
</div>
</> </>
); );
}; };

View File

@ -20,7 +20,8 @@ const ProfilePagesHeader = memo(function ProfilePagesHeader({
}, [navigate]); }, [navigate]);
return ( return (
<div className="lg:hidden flex flex-row h-[65px] sticky top-0 z-30 bg-WHITE w-full items-center justify-center border-b border-inner-border"> <div className="lg:hidden sticky top-0 z-30 bg-WHITE pt-[env(safe-area-inset-top)]">
<div className="relative flex flex-row h-[65px] bg-WHITE w-full items-center justify-center border-b border-inner-border">
{!hideBackButton && ( {!hideBackButton && (
<ArrowRight <ArrowRight
onClick={handleBack} onClick={handleBack}
@ -32,6 +33,7 @@ const ProfilePagesHeader = memo(function ProfilePagesHeader({
<div className="absolute top-4 left-4">{rightContent}</div> <div className="absolute top-4 left-4">{rightContent}</div>
)} )}
</div> </div>
</div>
); );
}); });

View File

@ -42,7 +42,7 @@ const SearchTopBar: React.FC<SearchTopBarProps> = ({
}; };
return ( return (
<div className="flex flex-col gap-4 py-4 px-4 w-full sticky top-0 z-30 bg-WHITE"> <div className="flex flex-col gap-4 pb-4 pt-[calc(1rem_+_env(safe-area-inset-top))] px-4 w-full sticky top-0 z-30 bg-WHITE">
<div className="flex items-center justify-center gap-2 rounded-[10px] w-full relative"> <div className="flex items-center justify-center gap-2 rounded-[10px] w-full relative">
<AppInput <AppInput
inputIcon={SearchIcon} inputIcon={SearchIcon}

View File

@ -14,7 +14,7 @@ export const CartHeader = ({ cart, step, setStep }: CartHeaderProps) => {
const navigate = useNavigate(); const navigate = useNavigate();
return ( return (
<div <div
className={`flex flex-col gap-3 pt-4 sticky top-0 z-30 bg-WHITE w-full items-center justify-center ${step !== 0 ? "pb-4" : ""}`} className={`flex flex-col gap-3 pt-[calc(1rem_+_env(safe-area-inset-top))] sticky top-0 z-30 bg-WHITE w-full items-center justify-center ${step !== 0 ? "pb-4" : ""}`}
> >
<p className={"text-B16 font-bold px-4"}> <p className={"text-B16 font-bold px-4"}>
{step === 0 {step === 0
@ -29,7 +29,7 @@ export const CartHeader = ({ cart, step, setStep }: CartHeaderProps) => {
if (step > 0) setStep((p) => p - 1); if (step > 0) setStep((p) => p - 1);
else safeGoBack(navigate); else safeGoBack(navigate);
}} }}
className="absolute top-4 right-4" className="absolute top-[calc(1rem_+_env(safe-area-inset-top))] right-4"
/> />
{cart && cart.items && cart.items.length > 0 && ( {cart && cart.items && cart.items.length > 0 && (
<> <>

View File

@ -41,7 +41,7 @@ const ExploreTopBar = memo(function ExploreTopBar({
}, [setSearchValue]); }, [setSearchValue]);
return ( return (
<div className="flex flex-col gap-4 py-4 px-4 w-full sticky top-0 z-30 bg-WHITE"> <div className="flex flex-col gap-4 pb-4 pt-[calc(1rem_+_env(safe-area-inset-top))] px-4 w-full sticky top-0 z-30 bg-WHITE">
<div className="flex items-center justify-center gap-2 rounded-[10px] w-full relative"> <div className="flex items-center justify-center gap-2 rounded-[10px] w-full relative">
<AppInput <AppInput
inputIcon={Search} inputIcon={Search}

View File

@ -57,7 +57,7 @@ const HomePageTopBar = memo(() => {
); );
return ( return (
<div className="flex gap-2 flex-row h-[48px] w-full bg-WHITE items-center px-4 sticky top-0 z-30"> <div className="flex gap-2 flex-row h-[calc(48px_+_env(safe-area-inset-top))] pt-[env(safe-area-inset-top)] w-full bg-WHITE items-center px-4 sticky top-0 z-30">
<div className="flex relative items-center gap-2 justify-center w-full"> <div className="flex relative items-center gap-2 justify-center w-full">
<div <div
className="absolute right-0" className="absolute right-0"

View File

@ -17,10 +17,10 @@ const ChatHeader = ({
const { theme } = useRootData(); const { theme } = useRootData();
const navigate = useNavigate(); const navigate = useNavigate();
return ( return (
<div className="max-w-md mx-auto flex flex-row h-[65px] bg-WHITE fixed top-0 left-0 w-full items-center justify-center border-b border-inner-border z-10"> <div className="max-w-md mx-auto flex flex-row h-[calc(65px_+_env(safe-area-inset-top))] pt-[env(safe-area-inset-top)] bg-WHITE fixed top-0 left-0 w-full items-center justify-center border-b border-inner-border z-10">
<ArrowRight <ArrowRight
onClick={() => safeGoBack(navigate)} onClick={() => safeGoBack(navigate)}
className="absolute top-5 right-5" className="absolute top-[calc(1.25rem_+_env(safe-area-inset-top))] right-5"
/> />
{isConnected ? ( {isConnected ? (
<Link <Link

View File

@ -91,7 +91,7 @@ const MessageInput = ({
</button> </button>
</div> </div>
)} )}
<div className="max-w-md mx-auto fixed px-2 bottom-0 py-2 w-full bg-WHITE"> <div className="max-w-md mx-auto fixed px-2 bottom-0 pt-2 pb-[calc(0.5rem_+_env(safe-area-inset-bottom))] w-full bg-WHITE">
{replyToMessage && ( {replyToMessage && (
<div className="bg-GRAY3 rounded-lg p-2 flex justify-between items-center mb-2"> <div className="bg-GRAY3 rounded-lg p-2 flex justify-between items-center mb-2">
<div className="flex items-center"> <div className="flex items-center">

View File

@ -254,7 +254,7 @@ export default function ThreadChatPage({ threadId }: ThreadChatPageProps) {
) : ( ) : (
<> <>
<div <div
className={`hide-scrollbar flex-grow w-full max-h-[calc(100vh-${replyToMessage ? "120px" : "75px"})] flex flex-col`} className={`hide-scrollbar flex-grow w-full max-h-[calc(100vh_-_${replyToMessage ? "120px" : "75px"}_-_env(safe-area-inset-top)_-_env(safe-area-inset-bottom))] flex flex-col`}
> >
{/* Messages list */} {/* Messages list */}
<MessageList <MessageList

View File

@ -275,7 +275,9 @@ export const meta: MetaFunction = () => {
}, },
{ name: "author", content: "Vitrown" }, { name: "author", content: "Vitrown" },
{ name: "robots", content: "index, follow" }, { name: "robots", content: "index, follow" },
{ name: "viewport", content: "width=device-width, initial-scale=1" }, // viewport is defined globally in root.tsx (with viewport-fit=cover for
// iOS safe-area handling); don't override it here or the home page loses
// edge-to-edge / notch support.
// Open Graph // Open Graph
{ property: "og:type", content: "website" }, { property: "og:type", content: "website" },