feat(home): search-first mobile top bar (redesign)
Replace the mobile home scroll-tab bar with the redesign's search-first chrome: logo + ویترون wordmark, notification (bell) and cart (bag) icon buttons with unread dot / count badges, and a prominent tap-to-search field that opens /explore. Mobile only (stays inside lg:hidden), so the desktop DesktopHeader is unaffected. Keeps the existing pinar font and color tokens; preserves the safe-area-inset top padding. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
bac93568f5
commit
f8289898bc
@ -1,103 +1,74 @@
|
|||||||
import { useState, useEffect, useMemo, memo, useCallback } from "react";
|
import { memo, useCallback } from "react";
|
||||||
|
import { Link } from "@remix-run/react";
|
||||||
|
import { Bell, Search, ShoppingBag } from "lucide-react";
|
||||||
import logo from "../../assets/logo/SVG-07.svg";
|
import logo from "../../assets/logo/SVG-07.svg";
|
||||||
|
import { useCartCount } from "~/requestHandler/use-cart-hooks";
|
||||||
|
import { useBadgeCounts } from "~/hooks/useBadgeCounts";
|
||||||
|
import { useRootData } from "~/hooks/use-root-data";
|
||||||
|
|
||||||
const HomePageTopBar = memo(() => {
|
const HomePageTopBar = memo(() => {
|
||||||
const [activeTab, setActiveTab] = useState(0);
|
const { user } = useRootData();
|
||||||
const tabs = useMemo(() => ["تخفیفها", "برترین فروشگاهها", "برای شما"], []);
|
const isLoggedIn = !!user?.access_token;
|
||||||
|
const cartCount = useCartCount();
|
||||||
// Scroll to section when tab is clicked
|
const { data: badgeCounts } = useBadgeCounts();
|
||||||
const scrollToSection = useCallback((sectionId: string) => {
|
const accountUnread =
|
||||||
const section = document.getElementById(sectionId);
|
(badgeCounts?.chat || 0) + (badgeCounts?.order_updates || 0);
|
||||||
if (section) {
|
|
||||||
section.scrollIntoView({ behavior: "smooth" });
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// Update active tab based on scroll position with throttling
|
|
||||||
useEffect(() => {
|
|
||||||
let ticking = false;
|
|
||||||
const handleScroll = () => {
|
|
||||||
if (!ticking) {
|
|
||||||
window.requestAnimationFrame(() => {
|
|
||||||
const section1 = document.getElementById("section1");
|
|
||||||
const section2 = document.getElementById("section2");
|
|
||||||
const section3 = document.getElementById("section3");
|
|
||||||
|
|
||||||
if (section1 && section2 && section3) {
|
|
||||||
const scrollPosition = window.scrollY;
|
|
||||||
|
|
||||||
if (scrollPosition >= section3.offsetTop - 100) {
|
|
||||||
setActiveTab(2);
|
|
||||||
} else if (scrollPosition >= section2.offsetTop - 100) {
|
|
||||||
setActiveTab(1);
|
|
||||||
} else {
|
|
||||||
setActiveTab(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ticking = false;
|
|
||||||
});
|
|
||||||
ticking = true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
window.addEventListener("scroll", handleScroll, { passive: true });
|
|
||||||
return () => window.removeEventListener("scroll", handleScroll);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const scrollToTop = useCallback(() => {
|
const scrollToTop = useCallback(() => {
|
||||||
window.scrollTo({ top: 0, behavior: "smooth" });
|
window.scrollTo({ top: 0, behavior: "smooth" });
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleTabClick = useCallback(
|
|
||||||
(index: number) => {
|
|
||||||
setActiveTab(index);
|
|
||||||
scrollToSection(`section${index + 1}`);
|
|
||||||
},
|
|
||||||
[scrollToSection]
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<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="bg-WHITE sticky top-0 z-30 pt-[env(safe-area-inset-top)]">
|
||||||
<div className="flex relative items-center gap-2 justify-center w-full">
|
<div className="px-4 pt-2.5 pb-3 flex flex-col gap-3">
|
||||||
<div
|
{/* Brand row */}
|
||||||
className="absolute right-0"
|
<div className="flex items-center gap-2.5">
|
||||||
onClick={() => scrollToTop()}
|
<button
|
||||||
role="button"
|
onClick={scrollToTop}
|
||||||
tabIndex={0}
|
className="flex items-center gap-2"
|
||||||
onKeyDown={(e) => {
|
aria-label="ویترون"
|
||||||
if (e.key === "Enter" || e.key === " ") {
|
>
|
||||||
scrollToTop();
|
<img src={logo} className="w-7 h-8 object-contain" alt="" />
|
||||||
}
|
<b className="text-[21px] font-extrabold text-BLACK tracking-tight">
|
||||||
}}
|
ویترون
|
||||||
|
</b>
|
||||||
|
</button>
|
||||||
|
<span className="flex-1" />
|
||||||
|
<Link
|
||||||
|
to="/profile"
|
||||||
|
prefetch="intent"
|
||||||
|
aria-label="اعلانها"
|
||||||
|
className="relative w-10 h-10 rounded-xl bg-WHITE2 grid place-items-center text-BLACK"
|
||||||
|
>
|
||||||
|
{isLoggedIn && accountUnread > 0 ? (
|
||||||
|
<span className="absolute top-2 left-2.5 w-2 h-2 rounded-full bg-RED border-[1.5px] border-WHITE2" />
|
||||||
|
) : null}
|
||||||
|
<Bell size={21} />
|
||||||
|
</Link>
|
||||||
|
<Link
|
||||||
|
to="/cart"
|
||||||
|
prefetch="intent"
|
||||||
|
aria-label="سبد خرید"
|
||||||
|
className="relative w-10 h-10 rounded-xl bg-WHITE2 grid place-items-center text-BLACK"
|
||||||
|
>
|
||||||
|
{isLoggedIn && cartCount > 0 ? (
|
||||||
|
<span className="absolute top-1 left-1 min-w-[16px] h-4 px-1 rounded-full bg-RED text-white text-[10px] font-bold grid place-items-center border-[1.5px] border-WHITE2">
|
||||||
|
{cartCount > 99 ? "99+" : cartCount}
|
||||||
|
</span>
|
||||||
|
) : null}
|
||||||
|
<ShoppingBag size={21} />
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Search-first */}
|
||||||
|
<Link
|
||||||
|
to="/explore"
|
||||||
|
prefetch="intent"
|
||||||
|
className="flex items-center gap-2.5 h-[46px] px-3.5 bg-WHITE3 rounded-2xl text-GRAY"
|
||||||
>
|
>
|
||||||
<img src={logo} className="w-8 h-8 object-contain" alt="logo" />
|
<Search size={19} className="shrink-0" />
|
||||||
</div>
|
<span className="text-R14">جستجوی محصول، برند یا فروشگاه…</span>
|
||||||
<div className="flex gap-4 justify-center">
|
</Link>
|
||||||
{tabs.map((tab, index) => (
|
|
||||||
<div
|
|
||||||
key={index}
|
|
||||||
onClick={() => handleTabClick(index)}
|
|
||||||
onKeyDown={(e) => {
|
|
||||||
if (e.key === "Enter" || e.key === " ") {
|
|
||||||
handleTabClick(index);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
role="button"
|
|
||||||
tabIndex={index}
|
|
||||||
className="flex items-center"
|
|
||||||
>
|
|
||||||
<p
|
|
||||||
className={`text-B12 border-b-[2px] transition-all duration-300 ease-in-out py-1 ${
|
|
||||||
index === activeTab
|
|
||||||
? "text-BLACK border-BLACK font-bold"
|
|
||||||
: "text-GRAY border-transparent"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{tab}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user