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:
parent
66b14cfa74
commit
b4d5f8d8db
@ -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}
|
||||||
|
|||||||
@ -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>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -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}
|
||||||
|
|||||||
@ -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 && (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@ -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}
|
||||||
|
|||||||
@ -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"
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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">
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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" },
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user