Redesign header icons/account-cart chip, light footer, fix sticky-footer gap

- New shared SVG icon set (src/components/ui/icons.tsx): Menu, User,
  Cart, Close, ChevronLeft — replaces emoji (☰ 👤 🛒 ✕) across the
  header, and the literal "←" text arrow on every "view more"-style
  link (SectionTitle's action, the mega-menu link, BlogCarousel's
  "ادامه مطلب").
- Header desktop account/cart area redesigned as a borderless chip:
  cart icon button (right) + divider + account icon-badge/two-line
  "حساب کاربری / ورود/ثبت‌نام" (left), matching a supplied reference.
  Got the RTL DOM order backwards on the first pass (cart needs to be
  first in DOM to render rightmost) and corrected it.
- Header logo: -10px (was already doubled from an earlier request).
- Footer: background is now bg-[#9a21291a] (exact hex given, not a
  token substitute) with black text throughout instead of solid
  dark-red/white; footer logo doubled; the meshkee-credit logo gets
  brightness-0 so the white source asset stays visible on the new
  light background.
- Fixed a real layout bug: <main> had no flex-1, so on any page
  shorter than the viewport the footer stuck to the content instead of
  the bottom of the screen, leaving a gap of bare background beneath
  it. Standard sticky-footer flex fix.
- CONTEXT.md: documented the icon set, the account/cart chip's RTL DOM
  ordering (to stop this regressing again), and the footer's new
  color scheme + the flex-1 fix.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
amirhosein.ashourloo
2026-09-15 19:31:00 +03:30
co-authored by Claude Sonnet 5
parent 7a78adc7f3
commit d090f57540
8 changed files with 183 additions and 45 deletions
+41
View File
@@ -180,6 +180,28 @@ persistent memory as "for all the user's sites", not just this one)
in the array — first DOM child renders rightmost under `dir="rtl"`).
- Category tile images: 110px, no border, no border-radius
(`FeaturedCategories.tsx`).
- **No emoji as icons** — `src/components/ui/icons.tsx` has the shared
light/outline SVG set (`MenuIcon`, `UserIcon`, `CartIcon`, `CloseIcon`,
`ChevronLeftIcon`), 24x24 viewBox, `stroke="currentColor"`. Use these
instead of characters like "☰"/"👤"/"🛒"/"✕". Every "view more"-style
link/button (`SectionTitle`'s `action`, the mega-menu's "مشاهده همه",
`BlogCarousel`'s "ادامه مطلب") uses `ChevronLeftIcon`, not a literal "←"
character — keep new ones consistent with that.
- **Header account/cart cluster** (`Header.tsx`, desktop only —
`hidden ... sm:flex`): a borderless `bg-surface` chip holding, in this
exact DOM order, `CartButton`, a divider, then the login link. That DOM
order is deliberate and easy to get backwards: under `dir="rtl"` the
first DOM child renders rightmost, so cart-first-in-DOM is what actually
puts the cart icon on the visual **right** and the login block on the
**left** (this was built backwards once and corrected). Same rule inside
the login link itself — the icon badge comes before the two-line text in
DOM so the icon renders right of the text. Neither `CartButton` nor this
wrapper has a border (removed on request — don't reintroduce one).
`CartButton` is a fixed 44px (`h-11 w-11`) icon-only square everywhere it
renders (also stands alone, un-wrapped, below `sm`); its unread-count
badge sits at `-top-1.5 -right-1.5`, not the product-card cart badge's
`-left-1.5` — don't copy that positioning between the two, they're
intentionally different corners for different-looking buttons.
## Static-image slots
@@ -274,6 +296,25 @@ render a `<link rel="icon">` and browsers aren't guaranteed to prefer the
second one, so keeping both was silently serving the stale static icon.
Don't add `app/favicon.ico` (or `public/favicon.ico`) back.
## Footer: light background, black text, sticky to viewport bottom
The footer is `bg-[#9a21291a]` (a ~10%-opacity red-tint, given as an exact
hex by the user — not swapped for the closest design-token color) with
black text throughout (`text-black`, `/70`, `/50`, `/10`, `/20` opacity
steps) — it used to be a solid dark-red block with white text; don't revert
to that. The footer logo (`logoDarkUrl ?? logoUrl`) is `h-[72px]`, doubled
from its original size on request. Because `public/images/meshkee-logo-
white.png` (the credit-widget logo below) is a solid-white asset, it gets
`brightness-0` on this light background to render as a black silhouette
instead of invisible white-on-near-white — the same `opacity-60 →
group-hover:opacity-100` fade still applies on top of that.
`<main>` has `flex-1` in `layout.tsx` (`body` is `flex flex-col`) so the
footer sits flush at the bottom of the viewport on short pages instead of
leaving a gap of bare `background` color beneath it — this is the standard
sticky-footer flex pattern. Don't remove `flex-1` from `main` without
re-checking a short page (e.g. `/contact`) for that gap coming back.
## Footer: Meshkee credit widget
The footer's bottom-right link ("Designed And Developed By Meshkee
+1 -1
View File
@@ -53,7 +53,7 @@ export default async function RootLayout({ children }: LayoutProps<"/">) {
<Providers>
<TopBar />
<Header categoryTree={categoryTree} categories={categories} logoUrl={businessInfo?.logoUrl} />
<main>{children}</main>
<main className="flex-1">{children}</main>
<Footer businessInfo={businessInfo} categories={categories} />
</Providers>
</body>
+5 -4
View File
@@ -1,6 +1,7 @@
"use client";
import { useCart } from "@/context/CartContext";
import { CartIcon } from "@/components/ui/icons";
export function CartButton() {
const { itemCount, openPopup } = useCart();
@@ -9,12 +10,12 @@ export function CartButton() {
<button
type="button"
onClick={openPopup}
className="relative flex items-center gap-1.5 rounded-lg border border-border px-3 py-2 transition-colors hover:border-red hover:text-red"
aria-label="سبد خرید"
className="relative flex h-11 w-11 shrink-0 items-center justify-center rounded-xl bg-surface text-foreground transition-colors hover:text-red"
>
<span aria-hidden>🛒</span>
<span className="hidden sm:inline">سبد خرید</span>
<CartIcon className="h-5 w-5" />
{itemCount > 0 && (
<span className="absolute -top-1.5 -left-1.5 flex h-5 min-w-5 items-center justify-center rounded-full bg-red px-1 text-[10px] font-bold text-white">
<span className="absolute -top-1.5 -right-1.5 flex h-5 min-w-5 items-center justify-center rounded-full bg-red px-1 text-[10px] font-bold text-white">
{itemCount}
</span>
)}
+4 -2
View File
@@ -2,6 +2,7 @@ import { Placeholder } from "@/components/ui/Placeholder";
import { SectionTitle } from "@/components/ui/SectionTitle";
import { Carousel } from "@/components/ui/Carousel";
import { EmptyState } from "@/components/ui/EmptyState";
import { ChevronLeftIcon } from "@/components/ui/icons";
import type { BlogSummary } from "@/lib/meshkee";
function formatDate(iso?: string) {
@@ -50,8 +51,9 @@ export function BlogCarousel({ posts }: { posts: BlogSummary[] }) {
{post.abstract}
</p>
)}
<span className="mt-3 block text-xs font-medium text-red">
ادامه مطلب ←
<span className="mt-3 flex items-center gap-1 text-xs font-medium text-red">
ادامه مطلب
<ChevronLeftIcon className="h-3.5 w-3.5" />
</span>
</div>
</a>
+19 -19
View File
@@ -27,7 +27,7 @@ export function Footer({
);
return (
<footer className="border-t border-red-dark/20 bg-red-dark text-white">
<footer className="border-t border-red-dark/15 bg-[#9a21291a] text-black">
<div className="mx-auto max-w-7xl px-4 py-12">
<div className="grid gap-8 md:grid-cols-4">
<div>
@@ -37,7 +37,7 @@ export function Footer({
<img
src={businessInfo.logoDarkUrl ?? businessInfo.logoUrl ?? undefined}
alt={businessInfo.nameFa || "سانی‌هوم"}
className="h-9 w-auto object-contain"
className="h-[72px] w-auto object-contain"
/>
) : (
<span className="font-display text-lg font-bold tracking-tight">
@@ -46,16 +46,16 @@ export function Footer({
)}
</div>
{businessInfo?.about && (
<p className="text-sm leading-relaxed text-white/70">{businessInfo.about}</p>
<p className="text-sm leading-relaxed text-black/70">{businessInfo.about}</p>
)}
</div>
<div>
<h4 className="mb-3 text-sm font-bold">دسترسی سریع</h4>
<ul className="space-y-2 text-sm text-white/70">
<ul className="space-y-2 text-sm text-black/70">
{quickLinks.map((link) => (
<li key={link}>
<a href="#" className="transition-colors hover:text-white">
<a href="#" className="transition-colors hover:text-black">
{link}
</a>
</li>
@@ -66,12 +66,12 @@ export function Footer({
<div>
<h4 className="mb-3 text-sm font-bold">دسته‌بندی‌ها</h4>
{categories.length === 0 ? (
<p className="text-sm text-white/50">هنوز دسته‌بندی‌ای ثبت نشده است.</p>
<p className="text-sm text-black/50">هنوز دسته‌بندی‌ای ثبت نشده است.</p>
) : (
<ul className="space-y-2 text-sm text-white/70">
<ul className="space-y-2 text-sm text-black/70">
{categories.slice(0, 5).map((cat) => (
<li key={cat.id}>
<a href="#" className="transition-colors hover:text-white">
<a href="#" className="transition-colors hover:text-black">
{cat.name}
</a>
</li>
@@ -83,7 +83,7 @@ export function Footer({
<div>
<h4 className="mb-3 text-sm font-bold">تماس با ما</h4>
{hasContactInfo ? (
<ul className="space-y-2 text-sm text-white/70">
<ul className="space-y-2 text-sm text-black/70">
{businessInfo?.addresses.map((address) => <li key={address}>{address}</li>)}
{businessInfo?.phoneNumbers.map((phone) => (
<li key={phone}>تلفن: {phone}</li>
@@ -91,7 +91,7 @@ export function Footer({
{businessInfo?.emails.map((email) => <li key={email}>ایمیل: {email}</li>)}
</ul>
) : (
<p className="text-sm text-white/50">هنوز اطلاعات تماس ثبت نشده است.</p>
<p className="text-sm text-black/50">هنوز اطلاعات تماس ثبت نشده است.</p>
)}
{socialEntries.length > 0 && (
<div className="mt-4 flex gap-3">
@@ -101,7 +101,7 @@ export function Footer({
href={url}
target="_blank"
rel="noreferrer"
className="font-display flex h-8 w-8 items-center justify-center rounded-full border border-white/20 text-[10px] font-bold transition-colors hover:border-white hover:bg-white/10"
className="font-display flex h-8 w-8 items-center justify-center rounded-full border border-black/20 text-[10px] font-bold transition-colors hover:border-black hover:bg-black/5"
>
{socialLabels[key] ?? key.slice(0, 2).toUpperCase()}
</a>
@@ -111,20 +111,20 @@ export function Footer({
</div>
</div>
<div className="mt-10 flex flex-col items-center justify-between gap-3 border-t border-white/10 pt-6 text-xs text-white/50 md:flex-row">
<div className="mt-10 flex flex-col items-center justify-between gap-3 border-t border-black/10 pt-6 text-xs text-black/50 md:flex-row">
<p>© ۱۴۰۵ سانی‌هوم — تمامی حقوق محفوظ است.</p>
<a
href="https://meshkee.com"
target="_blank"
rel="noopener noreferrer"
dir="ltr"
className="group relative inline-flex items-center gap-2.5 px-2.5 py-1.5 text-[#9c9c9c] transition-colors duration-300 hover:text-[#e0e0e0]"
className="group relative inline-flex items-center gap-2.5 px-2.5 py-1.5 text-black/50 transition-colors duration-300 hover:text-black"
>
<span className="pointer-events-none absolute inset-0" aria-hidden="true">
<span className="absolute top-0 left-0 h-full w-px origin-top scale-y-0 bg-[#777] transition-transform duration-[280ms] delay-[420ms] group-hover:scale-y-100 group-hover:delay-0" />
<span className="absolute bottom-0 left-0 h-px w-full origin-left scale-x-0 bg-[#777] transition-transform duration-[280ms] delay-[280ms] group-hover:scale-x-100 group-hover:delay-[140ms]" />
<span className="absolute top-0 right-0 h-full w-px origin-bottom scale-y-0 bg-[#777] transition-transform duration-[280ms] delay-[140ms] group-hover:scale-y-100 group-hover:delay-[280ms]" />
<span className="absolute top-0 left-0 h-px w-full origin-right scale-x-0 bg-[#777] transition-transform duration-[280ms] delay-0 group-hover:scale-x-100 group-hover:delay-[420ms]" />
<span className="absolute top-0 left-0 h-full w-px origin-top scale-y-0 bg-black/40 transition-transform duration-[280ms] delay-[420ms] group-hover:scale-y-100 group-hover:delay-0" />
<span className="absolute bottom-0 left-0 h-px w-full origin-left scale-x-0 bg-black/40 transition-transform duration-[280ms] delay-[280ms] group-hover:scale-x-100 group-hover:delay-[140ms]" />
<span className="absolute top-0 right-0 h-full w-px origin-bottom scale-y-0 bg-black/40 transition-transform duration-[280ms] delay-[140ms] group-hover:scale-y-100 group-hover:delay-[280ms]" />
<span className="absolute top-0 left-0 h-px w-full origin-right scale-x-0 bg-black/40 transition-transform duration-[280ms] delay-0 group-hover:scale-x-100 group-hover:delay-[420ms]" />
</span>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
@@ -132,11 +132,11 @@ export function Footer({
alt="Meshkee"
width={42}
height={42}
className="relative block h-[42px] w-auto shrink-0 object-contain opacity-70 transition-opacity duration-300 group-hover:opacity-100"
className="relative block h-[42px] w-auto shrink-0 object-contain opacity-60 brightness-0 transition-opacity duration-300 group-hover:opacity-100"
/>
<span className="relative flex min-h-[42px] flex-col justify-center leading-[1.25]">
<span className="text-[11px]">Designed And Developed By</span>
<span className="text-[13px] font-semibold text-[#cfcfcf] transition-colors duration-300 group-hover:text-white">
<span className="text-[13px] font-semibold text-black/70 transition-colors duration-300 group-hover:text-black">
Meshkee E-Commerce Team
</span>
</span>
+32 -17
View File
@@ -5,6 +5,7 @@ import { createPortal } from "react-dom";
import type { Category, CategoryNode, CategoryTreeGroup } from "@/lib/meshkee";
import { EmptyState } from "@/components/ui/EmptyState";
import { CartButton } from "@/components/cart/CartButton";
import { ChevronLeftIcon, CloseIcon, MenuIcon, UserIcon } from "@/components/ui/icons";
import { SearchBar } from "./SearchBar";
const navLinks = [
@@ -27,7 +28,7 @@ function Logo({ src, large = false }: { src?: string | null; large?: boolean })
className={
large
? "h-[42px] w-auto object-contain md:h-[46px]"
: "h-20 w-auto object-contain md:h-24"
: "h-[70px] w-auto object-contain md:h-[86px]"
}
/>
</a>
@@ -107,9 +108,9 @@ function MobileDrawer({
type="button"
onClick={onClose}
aria-label="بستن منو"
className="flex h-9 w-9 items-center justify-center rounded-lg text-xl text-muted transition-colors hover:bg-red-light hover:text-red"
className="flex h-9 w-9 items-center justify-center rounded-lg text-muted transition-colors hover:bg-red-light hover:text-red"
>
✕
<CloseIcon className="h-5 w-5" />
</button>
</div>
@@ -188,7 +189,7 @@ function MobileDrawer({
href={LOGIN_URL}
className="flex items-center gap-2 px-5 py-4 text-right font-medium transition-colors hover:bg-red-light/40 hover:text-red"
>
<span aria-hidden>👤</span>
<UserIcon className="h-4 w-4" />
ورود
</a>
</nav>
@@ -231,9 +232,9 @@ export function Header({
type="button"
onClick={() => setDrawerOpen(true)}
aria-label="منو"
className="flex h-10 w-10 items-center justify-center rounded-lg border border-border text-xl transition-colors hover:border-red hover:text-red min-[1200px]:hidden"
className="flex h-10 w-10 items-center justify-center rounded-lg border border-border transition-colors hover:border-red hover:text-red min-[1200px]:hidden"
>
<span aria-hidden>☰</span>
<MenuIcon className="h-5 w-5" />
</button>
<div className="hidden min-[1200px]:block">
<Logo src={logoUrl} />
@@ -250,14 +251,27 @@ export function Header({
</div>
<div className="flex items-center justify-end gap-3 text-sm">
<a
href={LOGIN_URL}
className="hidden items-center gap-1.5 text-muted transition-colors hover:text-red sm:flex"
>
<span>👤</span>
<span>ورود</span>
</a>
<CartButton />
<div className="hidden items-center gap-2 rounded-2xl bg-surface p-1.5 sm:flex">
<CartButton />
<span className="h-8 w-px bg-border" aria-hidden />
<a
href={LOGIN_URL}
className="flex items-center gap-2 rounded-xl py-1 pr-1 transition-opacity hover:opacity-80"
>
<span className="flex h-11 w-11 shrink-0 items-center justify-center rounded-xl bg-red text-white">
<UserIcon className="h-5 w-5" />
</span>
<span className="text-right leading-tight">
<span className="block text-sm font-bold whitespace-nowrap text-foreground">
حساب کاربری
</span>
<span className="block text-xs whitespace-nowrap text-muted">ورود/ثبت‌نام</span>
</span>
</a>
</div>
<div className="sm:hidden">
<CartButton />
</div>
</div>
</div>
@@ -279,7 +293,7 @@ export function Header({
aria-haspopup="true"
className="flex shrink-0 items-center gap-2 rounded-lg bg-red px-4 py-2 font-medium text-white transition-colors hover:bg-red-dark"
>
<span aria-hidden>☰</span>
<MenuIcon className="h-4 w-4" />
دسته‌بندی‌ها
</button>
@@ -294,9 +308,10 @@ export function Header({
</div>
<a
href="/products"
className="text-xs font-medium text-red hover:text-red-dark"
className="flex items-center gap-1 text-xs font-medium text-red hover:text-red-dark"
>
مشاهده همه ←
مشاهده همه
<ChevronLeftIcon className="h-3.5 w-3.5" />
</a>
</div>
+5 -2
View File
@@ -1,3 +1,5 @@
import { ChevronLeftIcon } from "@/components/ui/icons";
type SectionTitleProps = {
title: string;
enTitle?: string;
@@ -29,9 +31,10 @@ export function SectionTitle({ title, enTitle, subtitle, action, actionHref }: S
{action && actionHref && (
<a
href={actionHref}
className="shrink-0 rounded-lg bg-red px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-red-dark"
className="flex shrink-0 items-center gap-1.5 rounded-lg bg-red px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-red-dark"
>
{action} ←
{action}
<ChevronLeftIcon className="h-4 w-4" />
</a>
)}
</div>
+76
View File
@@ -0,0 +1,76 @@
type IconProps = { className?: string };
// Shared light/outline icon set — thin stroke (1.75), rounded caps/joins,
// 24x24 viewBox, matching the chevrons already used in Carousel/ProductGallery.
// Prefer these over emoji anywhere in the UI.
export function MenuIcon({ className }: IconProps) {
return (
<svg viewBox="0 0 24 24" fill="none" className={className} aria-hidden>
<path
d="M4 7h16M4 12h16M4 17h16"
stroke="currentColor"
strokeWidth="1.75"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
}
export function UserIcon({ className }: IconProps) {
return (
<svg viewBox="0 0 24 24" fill="none" className={className} aria-hidden>
<circle cx="12" cy="8" r="3.25" stroke="currentColor" strokeWidth="1.75" />
<path
d="M4.75 19.25c0-3.45 3.24-6.25 7.25-6.25s7.25 2.8 7.25 6.25"
stroke="currentColor"
strokeWidth="1.75"
strokeLinecap="round"
/>
</svg>
);
}
export function CartIcon({ className }: IconProps) {
return (
<svg viewBox="0 0 24 24" fill="none" className={className} aria-hidden>
<path
d="M3.5 4.5h1.6l1.1 2M6.2 6.5l1.65 7.3a1.5 1.5 0 0 0 1.46 1.17h6.9a1.5 1.5 0 0 0 1.46-1.15l1.23-5.32H6.2Z"
stroke="currentColor"
strokeWidth="1.75"
strokeLinecap="round"
strokeLinejoin="round"
/>
<circle cx="10" cy="19.5" r="1.15" stroke="currentColor" strokeWidth="1.6" />
<circle cx="16.5" cy="19.5" r="1.15" stroke="currentColor" strokeWidth="1.6" />
</svg>
);
}
export function CloseIcon({ className }: IconProps) {
return (
<svg viewBox="0 0 24 24" fill="none" className={className} aria-hidden>
<path
d="M6 6l12 12M18 6 6 18"
stroke="currentColor"
strokeWidth="1.75"
strokeLinecap="round"
/>
</svg>
);
}
export function ChevronLeftIcon({ className }: IconProps) {
return (
<svg viewBox="0 0 24 24" fill="none" className={className} aria-hidden>
<path
d="M14.5 6.5 9 12l5.5 5.5"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
}