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:
Arda Samadi 2026-06-27 13:58:46 +03:30
parent bac93568f5
commit f8289898bc

View File

@ -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 { useCartCount } from "~/requestHandler/use-cart-hooks";
import { useBadgeCounts } from "~/hooks/useBadgeCounts";
import { useRootData } from "~/hooks/use-root-data";
const HomePageTopBar = memo(() => {
const [activeTab, setActiveTab] = useState(0);
const tabs = useMemo(() => ["تخفیف‌ها", "برترین فروشگاه‌ها", "برای شما"], []);
// Scroll to section when tab is clicked
const scrollToSection = useCallback((sectionId: string) => {
const section = document.getElementById(sectionId);
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 { user } = useRootData();
const isLoggedIn = !!user?.access_token;
const cartCount = useCartCount();
const { data: badgeCounts } = useBadgeCounts();
const accountUnread =
(badgeCounts?.chat || 0) + (badgeCounts?.order_updates || 0);
const scrollToTop = useCallback(() => {
window.scrollTo({ top: 0, behavior: "smooth" });
}, []);
const handleTabClick = useCallback(
(index: number) => {
setActiveTab(index);
scrollToSection(`section${index + 1}`);
},
[scrollToSection]
);
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="flex relative items-center gap-2 justify-center w-full">
<div
className="absolute right-0"
onClick={() => scrollToTop()}
role="button"
tabIndex={0}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
scrollToTop();
}
}}
<div className="bg-WHITE sticky top-0 z-30 pt-[env(safe-area-inset-top)]">
<div className="px-4 pt-2.5 pb-3 flex flex-col gap-3">
{/* Brand row */}
<div className="flex items-center gap-2.5">
<button
onClick={scrollToTop}
className="flex items-center gap-2"
aria-label="ویترون"
>
<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" />
</div>
<div className="flex gap-4 justify-center">
{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>
<Search size={19} className="shrink-0" />
<span className="text-R14">جستجوی محصول، برند یا فروشگاه</span>
</Link>
</div>
</div>
);