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,23 +39,25 @@ const HeaderWithSupport = memo(function HeaderWithSupport({
|
||||
|
||||
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">
|
||||
<ArrowRight
|
||||
onClick={onBack}
|
||||
className="absolute top-5 right-5 cursor-pointer"
|
||||
/>
|
||||
<p className="text-B16 font-bold ">{title}</p>
|
||||
{!hideSupportButton && (
|
||||
<div
|
||||
className="absolute top-4 h-8 w-8 left-5 rounded-full bg-WHITE shadow-xl flex items-center justify-center cursor-pointer"
|
||||
onClick={handleSupportClick}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onKeyDown={handleKeyDown}
|
||||
>
|
||||
<Headset size={20} />
|
||||
</div>
|
||||
)}
|
||||
<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
|
||||
onClick={onBack}
|
||||
className="absolute top-5 right-5 cursor-pointer"
|
||||
/>
|
||||
<p className="text-B16 font-bold ">{title}</p>
|
||||
{!hideSupportButton && (
|
||||
<div
|
||||
className="absolute top-4 h-8 w-8 left-5 rounded-full bg-WHITE shadow-xl flex items-center justify-center cursor-pointer"
|
||||
onClick={handleSupportClick}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onKeyDown={handleKeyDown}
|
||||
>
|
||||
<Headset size={20} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<CallDialog
|
||||
|
||||
@ -65,7 +65,7 @@ const MyLayout = React.memo(({ children }: { children: React.ReactNode }) => {
|
||||
}`}
|
||||
>
|
||||
<div
|
||||
className={`pb-[calc(50px+env(safe-area-inset-bottom))] ${
|
||||
className={`pb-[calc(50px_+_env(safe-area-inset-bottom))] ${
|
||||
expandOnDesktop ? "lg:pb-0" : ""
|
||||
}`}
|
||||
>
|
||||
@ -364,34 +364,36 @@ const MobileBottomNavigation = ({
|
||||
return (
|
||||
<>
|
||||
<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" : ""
|
||||
}`}
|
||||
>
|
||||
{navItems.map((item) => (
|
||||
<Link
|
||||
key={item.link}
|
||||
to={item.link}
|
||||
prefetch="intent"
|
||||
className="relative"
|
||||
>
|
||||
{typeof item.icon === "string" ? (
|
||||
<img
|
||||
src={item.icon}
|
||||
className="h-6 w-6"
|
||||
alt={item.link}
|
||||
loading="eager"
|
||||
/>
|
||||
) : (
|
||||
item.icon
|
||||
)}
|
||||
{item.badge ? (
|
||||
<span className="absolute -top-1.5 -right-1.5 min-w-[16px] h-4 px-1 rounded-full bg-RED text-white text-[10px] font-bold flex items-center justify-center leading-none">
|
||||
{item.badge > 99 ? "99+" : item.badge}
|
||||
</span>
|
||||
) : null}
|
||||
</Link>
|
||||
))}
|
||||
<div className="h-[50px] flex gap-[48px] px-[20px] items-center justify-center">
|
||||
{navItems.map((item) => (
|
||||
<Link
|
||||
key={item.link}
|
||||
to={item.link}
|
||||
prefetch="intent"
|
||||
className="relative"
|
||||
>
|
||||
{typeof item.icon === "string" ? (
|
||||
<img
|
||||
src={item.icon}
|
||||
className="h-6 w-6"
|
||||
alt={item.link}
|
||||
loading="eager"
|
||||
/>
|
||||
) : (
|
||||
item.icon
|
||||
)}
|
||||
{item.badge ? (
|
||||
<span className="absolute -top-1.5 -right-1.5 min-w-[16px] h-4 px-1 rounded-full bg-RED text-white text-[10px] font-bold flex items-center justify-center leading-none">
|
||||
{item.badge > 99 ? "99+" : item.badge}
|
||||
</span>
|
||||
) : null}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
@ -20,17 +20,19 @@ const ProfilePagesHeader = memo(function ProfilePagesHeader({
|
||||
}, [navigate]);
|
||||
|
||||
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">
|
||||
{!hideBackButton && (
|
||||
<ArrowRight
|
||||
onClick={handleBack}
|
||||
className="absolute top-5 right-5 cursor-pointer"
|
||||
/>
|
||||
)}
|
||||
<p className="text-B16 font-bold">{title}</p>
|
||||
{rightContent && (
|
||||
<div className="absolute top-4 left-4">{rightContent}</div>
|
||||
)}
|
||||
<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 && (
|
||||
<ArrowRight
|
||||
onClick={handleBack}
|
||||
className="absolute top-5 right-5 cursor-pointer"
|
||||
/>
|
||||
)}
|
||||
<p className="text-B16 font-bold">{title}</p>
|
||||
{rightContent && (
|
||||
<div className="absolute top-4 left-4">{rightContent}</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
@ -42,7 +42,7 @@ const SearchTopBar: React.FC<SearchTopBarProps> = ({
|
||||
};
|
||||
|
||||
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">
|
||||
<AppInput
|
||||
inputIcon={SearchIcon}
|
||||
|
||||
@ -14,7 +14,7 @@ export const CartHeader = ({ cart, step, setStep }: CartHeaderProps) => {
|
||||
const navigate = useNavigate();
|
||||
return (
|
||||
<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"}>
|
||||
{step === 0
|
||||
@ -29,7 +29,7 @@ export const CartHeader = ({ cart, step, setStep }: CartHeaderProps) => {
|
||||
if (step > 0) setStep((p) => p - 1);
|
||||
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 && (
|
||||
<>
|
||||
|
||||
@ -41,7 +41,7 @@ const ExploreTopBar = memo(function ExploreTopBar({
|
||||
}, [setSearchValue]);
|
||||
|
||||
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">
|
||||
<AppInput
|
||||
inputIcon={Search}
|
||||
|
||||
@ -57,7 +57,7 @@ const HomePageTopBar = memo(() => {
|
||||
);
|
||||
|
||||
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="absolute right-0"
|
||||
|
||||
@ -17,10 +17,10 @@ const ChatHeader = ({
|
||||
const { theme } = useRootData();
|
||||
const navigate = useNavigate();
|
||||
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
|
||||
onClick={() => safeGoBack(navigate)}
|
||||
className="absolute top-5 right-5"
|
||||
className="absolute top-[calc(1.25rem_+_env(safe-area-inset-top))] right-5"
|
||||
/>
|
||||
{isConnected ? (
|
||||
<Link
|
||||
|
||||
@ -91,7 +91,7 @@ const MessageInput = ({
|
||||
</button>
|
||||
</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 && (
|
||||
<div className="bg-GRAY3 rounded-lg p-2 flex justify-between items-center mb-2">
|
||||
<div className="flex items-center">
|
||||
|
||||
@ -254,7 +254,7 @@ export default function ThreadChatPage({ threadId }: ThreadChatPageProps) {
|
||||
) : (
|
||||
<>
|
||||
<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 */}
|
||||
<MessageList
|
||||
|
||||
@ -275,7 +275,9 @@ export const meta: MetaFunction = () => {
|
||||
},
|
||||
{ name: "author", content: "Vitrown" },
|
||||
{ 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
|
||||
{ property: "og:type", content: "website" },
|
||||
|
||||
Loading…
Reference in New Issue
Block a user