Compare commits
7
Commits
0de5abb879
...
69c54d3b17
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
69c54d3b17 | ||
|
|
c4411ebc28 | ||
|
|
69d17a6f8f | ||
|
|
d292bfc74c | ||
|
|
d090f57540 | ||
|
|
7a78adc7f3 | ||
|
|
ab1b07ff28 |
+76
-8
@@ -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
|
||||
|
||||
@@ -222,14 +244,17 @@ time the page renders, with zero code changes.
|
||||
|
||||
`SectionTitle`'s `enTitle` prop is optional for exactly this reason — these
|
||||
dynamic groups only have one `title` string from the API, not a Persian/
|
||||
English pair, so the inline English-caps subtitle just doesn't render for
|
||||
them. Instead, each carousel shows an English caption *under* the title
|
||||
(`SectionTitle`'s separate `enSubtitle` prop, `opacity-50`, its own line) —
|
||||
`StoreSpecialCarousels.englishLabelFromKey()` formats the group's `key`
|
||||
(e.g. `top-selling` → "TOP SELLING") rather than inventing a translation of
|
||||
`title`; there's no real localized-English field to translate from, and a
|
||||
made-up translation would be fabricated data. Skips rendering if the key
|
||||
has no letters at all (fell back to a bare numeric id).
|
||||
English pair. Each carousel still shows an English caption using that same
|
||||
`enTitle` slot (inline, beside the title — the same treatment as every
|
||||
other section heading on the site, e.g. BlogCarousel's "آخرین مطالب وبلاگ /
|
||||
LATEST BLOG"; there was a brief detour through a separate "below the title,
|
||||
opacity-50, own line" `enSubtitle` prop, which the user then asked to match
|
||||
the existing inline convention instead — don't reintroduce that below-title
|
||||
variant). `StoreSpecialCarousels.englishLabelFromKey()` formats the group's
|
||||
`key` (e.g. `top-selling` → "TOP SELLING") rather than inventing a
|
||||
translation of `title`; there's no real localized-English field to
|
||||
translate from, and a made-up translation would be fabricated data. Skips
|
||||
rendering if the key has no letters at all (fell back to a bare numeric id).
|
||||
|
||||
**Banner interleaving**: the two static-image banner sections (`TwoBanners`,
|
||||
`ThreeBanners`) are rendered *inside* `StoreSpecialCarousels`, one inserted
|
||||
@@ -238,6 +263,14 @@ cycling through the two if there end up being more than two carousels. Don't
|
||||
move them back out to `page.tsx` as fixed-position siblings — that only
|
||||
works for a known, fixed carousel count, which this deliberately isn't.
|
||||
|
||||
`SectionTitle`'s title/action row uses `items-center` (not `items-end`) —
|
||||
needed once carousel titles could carry more content (the inline `enTitle`
|
||||
caption) than a single line, so bottom-aligning the row left the action
|
||||
button visibly offset from the title block. The `action` button itself is
|
||||
styled like the site's other primary buttons (filled `bg-red`, white text,
|
||||
`rounded-lg` — the same look as `ProductCard`'s "افزودن به سبد"), not a
|
||||
plain text link.
|
||||
|
||||
**"مشاهده همه" (view all)**: each carousel's action button links to
|
||||
`/products?specialId={group.id}`. `store-specials` groups are admin-curated,
|
||||
already-complete lists (the endpoint has no pagination) — there's no
|
||||
@@ -263,6 +296,41 @@ 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-[#f1f1f1]` (exact hex given by the user, same neutral
|
||||
gray as `CartButton`'s background — not swapped for a design-token color;
|
||||
an earlier version used a red-tinted `#9a21291a`, since replaced) 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
|
||||
E-Commerce Team" → `https://meshkee.com`) intentionally matches the credit
|
||||
badge Meshkee's own storefronts use elsewhere (reference: safeteb.com) —
|
||||
same markup shape (frame + logo + two-line label), same animated corner
|
||||
border (four 1px lines that draw in on hover, staggered L→B→R→T, and
|
||||
un-draw in reverse on mouse-leave via matching `transition-delay`s on the
|
||||
resting state). `public/images/meshkee-logo-white.png` is that reference
|
||||
site's own logo asset, pulled down and committed here since it's the
|
||||
platform's shared credit-widget logo, not something unique to safeteb.com.
|
||||
If this ever needs to change, re-derive the exact hover-timing values from
|
||||
a live Meshkee site rather than guessing new ones — the staggered reverse
|
||||
un-draw is the distinctive part, not just "a border that appears."
|
||||
|
||||
## Route map
|
||||
|
||||
| Route | Notes |
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 32 KiB |
+1
-1
@@ -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>
|
||||
|
||||
@@ -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-10 w-10 shrink-0 items-center justify-center rounded-xl bg-[#f1f1f1] 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>
|
||||
)}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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-[#dddddd] bg-[#f1f1f1] 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,11 +111,36 @@ 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>
|
||||
<p className="font-display tracking-wide uppercase">
|
||||
Designed for Sanihome
|
||||
</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-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-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
|
||||
src="/images/meshkee-logo-white.png"
|
||||
alt="Meshkee"
|
||||
width={42}
|
||||
height={42}
|
||||
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-black/70 transition-colors duration-300 group-hover:text-black">
|
||||
Meshkee E-Commerce Team
|
||||
</span>
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
@@ -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 = [
|
||||
@@ -26,8 +27,8 @@ function Logo({ src, large = false }: { src?: string | null; large?: boolean })
|
||||
alt="سانیهوم"
|
||||
className={
|
||||
large
|
||||
? "h-[42px] w-auto object-contain md:h-[46px]"
|
||||
: "h-20 w-auto object-contain md:h-24"
|
||||
? "h-[65px] w-auto object-contain"
|
||||
: "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-10 w-10 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>
|
||||
|
||||
|
||||
@@ -11,8 +11,9 @@ import { productHref, type ProductGroup, type StaticImageWithUrl } from "@/lib/m
|
||||
// localized English field to pull a real translation from, and inventing
|
||||
// one would be fabricated data. `key` is real data and usually a readable
|
||||
// English slug (e.g. "top-selling"), so it's what gets formatted into the
|
||||
// English caption under the title. Skip it if the key has no letters at
|
||||
// all (e.g. it fell back to a bare numeric id).
|
||||
// English caption beside the title (same inline treatment as every other
|
||||
// section heading on the site, e.g. BlogCarousel's "LATEST BLOG"). Skip it
|
||||
// if the key has no letters at all (e.g. it fell back to a bare numeric id).
|
||||
function englishLabelFromKey(key: string): string | undefined {
|
||||
const label = key.replace(/[-_]+/g, " ").trim();
|
||||
return /[a-zA-Z]/.test(label) ? label.toUpperCase() : undefined;
|
||||
@@ -58,7 +59,7 @@ export function StoreSpecialCarousels({
|
||||
<div className="mx-auto max-w-7xl px-4">
|
||||
<SectionTitle
|
||||
title={group.title}
|
||||
enSubtitle={englishLabelFromKey(group.key)}
|
||||
enTitle={englishLabelFromKey(group.key)}
|
||||
action="مشاهده همه"
|
||||
actionHref={`/products?specialId=${group.id}`}
|
||||
/>
|
||||
|
||||
@@ -1,24 +1,16 @@
|
||||
import { ChevronLeftIcon } from "@/components/ui/icons";
|
||||
|
||||
type SectionTitleProps = {
|
||||
title: string;
|
||||
enTitle?: string;
|
||||
// Shown on its own line under the title (not inline like enTitle), at 50%
|
||||
// opacity — used for the homepage's dynamic store-specials carousels.
|
||||
enSubtitle?: string;
|
||||
subtitle?: string;
|
||||
action?: string;
|
||||
actionHref?: string;
|
||||
};
|
||||
|
||||
export function SectionTitle({
|
||||
title,
|
||||
enTitle,
|
||||
enSubtitle,
|
||||
subtitle,
|
||||
action,
|
||||
actionHref,
|
||||
}: SectionTitleProps) {
|
||||
export function SectionTitle({ title, enTitle, subtitle, action, actionHref }: SectionTitleProps) {
|
||||
return (
|
||||
<div className="mb-6 flex items-end justify-between gap-4">
|
||||
<div className="mb-6 flex items-center justify-between gap-4">
|
||||
<div>
|
||||
<div className="flex flex-wrap items-baseline gap-x-3 gap-y-1">
|
||||
<h2 className="text-xl font-bold text-red-dark md:text-2xl">{title}</h2>
|
||||
@@ -32,15 +24,6 @@ export function SectionTitle({
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{enSubtitle && (
|
||||
<p
|
||||
className="font-display mt-1 text-xs font-semibold tracking-wide text-foreground uppercase opacity-50 md:text-sm"
|
||||
lang="en"
|
||||
dir="ltr"
|
||||
>
|
||||
{enSubtitle}
|
||||
</p>
|
||||
)}
|
||||
{subtitle && (
|
||||
<p className="mt-1 text-sm text-muted">{subtitle}</p>
|
||||
)}
|
||||
@@ -48,9 +31,10 @@ export function SectionTitle({
|
||||
{action && actionHref && (
|
||||
<a
|
||||
href={actionHref}
|
||||
className="shrink-0 text-sm font-medium text-red transition-colors hover:text-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>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user