Desktop responsive: search page, cart list, masonry grid
- MondrianProductList: add desktop 4-column masonry (mobile 2-col unchanged); improves home "for you", search results and seller feed at lg+ - search: hide mobile SearchTopBar at lg, add results heading + centered 1320px container - cart list: responsive 2-column grid of cart cards + desktop heading - MyLayout: desktop chrome gate now covers / , /search/* and /cart Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
e1c5899002
commit
9650073b04
@ -96,8 +96,17 @@ const MondrianProductList = ({
|
|||||||
|
|
||||||
const oddHeight = useMemo(() => [220, 260, 320, 200, 280], []);
|
const oddHeight = useMemo(() => [220, 260, 320, 200, 280], []);
|
||||||
const evenHeight = useMemo(() => [280, 200, 260, 220, 320], []);
|
const evenHeight = useMemo(() => [280, 200, 260, 220, 320], []);
|
||||||
|
|
||||||
|
// Desktop masonry: distribute products across 4 columns (round-robin).
|
||||||
|
const desktopColumns = useMemo(() => {
|
||||||
|
const cols: MondrianList[][] = [[], [], [], []];
|
||||||
|
products.forEach((p, i) => cols[i % 4].push(p));
|
||||||
|
return cols;
|
||||||
|
}, [products]);
|
||||||
|
const desktopHeights = useMemo(() => [300, 360, 260, 340, 280, 320], []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="px-4 flex flex-col w-full">
|
<div className="px-4 lg:px-0 flex flex-col w-full">
|
||||||
{!isCommonList ? (
|
{!isCommonList ? (
|
||||||
<>
|
<>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
@ -113,7 +122,8 @@ const MondrianProductList = ({
|
|||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<div className="flex gap-1 w-full">
|
{/* Mobile / tablet: original two-column masonry (unchanged) */}
|
||||||
|
<div className="flex gap-1 w-full lg:hidden">
|
||||||
{/* Column 1 (Odd products - index 0, 2, ...) */}
|
{/* Column 1 (Odd products - index 0, 2, ...) */}
|
||||||
<div className="flex flex-col gap-1 w-[calc(50%)]">
|
<div className="flex flex-col gap-1 w-[calc(50%)]">
|
||||||
{oddProducts.map((product, index: number) => {
|
{oddProducts.map((product, index: number) => {
|
||||||
@ -146,6 +156,26 @@ const MondrianProductList = ({
|
|||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Desktop: 4-column masonry */}
|
||||||
|
<div className="hidden lg:flex gap-4 w-full">
|
||||||
|
{desktopColumns.map((col, ci) => (
|
||||||
|
<div key={ci} className="flex flex-col gap-4 flex-1 min-w-0">
|
||||||
|
{col.map((product, index) => (
|
||||||
|
<ProductCard
|
||||||
|
key={product.id}
|
||||||
|
product={product}
|
||||||
|
height={
|
||||||
|
desktopHeights[(ci + index) % desktopHeights.length]
|
||||||
|
}
|
||||||
|
storeId={storeId}
|
||||||
|
storePage={storePage}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
{hasNextPage && <MondrianSkeleton isMini={true} />}
|
{hasNextPage && <MondrianSkeleton isMini={true} />}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -27,10 +27,12 @@ import productsOutline from "~/assets/icons/navbar/products-outline.svg";
|
|||||||
*/
|
*/
|
||||||
const MyLayout = React.memo(({ children }: { children: React.ReactNode }) => {
|
const MyLayout = React.memo(({ children }: { children: React.ReactNode }) => {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
// Desktop chrome is rolled out per-page. Only routes listed here get the
|
// Desktop chrome is rolled out per-page. Only routes matched here get the
|
||||||
// full-width desktop layout; everything else keeps the current (mobile)
|
// full-width desktop layout; everything else keeps the current (mobile)
|
||||||
// experience at all widths so nothing regresses while we build the rest.
|
// experience at all widths so nothing regresses while we build the rest.
|
||||||
const isDesktopReady = location.pathname === "/";
|
const p = location.pathname;
|
||||||
|
const isDesktopReady =
|
||||||
|
p === "/" || p.startsWith("/search/") || p === "/cart";
|
||||||
const isThread = !!location.pathname.split("threads")[1];
|
const isThread = !!location.pathname.split("threads")[1];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -59,11 +59,16 @@ export default function CartIndex() {
|
|||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<div className="bg-background">
|
<div className="bg-background">
|
||||||
{/* Header */}
|
{/* Header (mobile) */}
|
||||||
|
<div className="lg:hidden">
|
||||||
<ProfilePagesHeader hideBackButton={true} title="لیست سبد خرید" />
|
<ProfilePagesHeader hideBackButton={true} title="لیست سبد خرید" />
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Cart Content */}
|
{/* Cart Content */}
|
||||||
<div className="flex flex-col gap-4 p-4">
|
<div className="flex flex-col gap-4 p-4 lg:max-w-[1320px] lg:mx-auto lg:w-full lg:px-8 lg:py-8">
|
||||||
|
<h1 className="hidden lg:block text-[28px] font-extrabold mb-2">
|
||||||
|
سبد خرید
|
||||||
|
</h1>
|
||||||
<UiProvider
|
<UiProvider
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
isError={isError}
|
isError={isError}
|
||||||
@ -73,7 +78,7 @@ export default function CartIndex() {
|
|||||||
}}
|
}}
|
||||||
type="cart"
|
type="cart"
|
||||||
>
|
>
|
||||||
<div className="space-y-4 w-full">
|
<div className="space-y-4 w-full lg:space-y-0 lg:grid lg:grid-cols-2 lg:gap-5">
|
||||||
{carts.map((cart) => {
|
{carts.map((cart) => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import React, { useState, useEffect } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import { MetaFunction } from "@remix-run/node";
|
import { MetaFunction } from "@remix-run/node";
|
||||||
import { useNavigate, useParams } from "@remix-run/react";
|
import { Link, useNavigate, useParams } from "@remix-run/react";
|
||||||
import { onSearch } from "../utils/helpers";
|
import { onSearch } from "../utils/helpers";
|
||||||
import { PackageSearch } from "lucide-react";
|
import { PackageSearch } from "lucide-react";
|
||||||
import { useSearchInfinite } from "../requestHandler/use-search-hooks";
|
import { useSearchInfinite } from "../requestHandler/use-search-hooks";
|
||||||
@ -31,7 +31,8 @@ export default function Search() {
|
|||||||
} = useSearchInfinite(query);
|
} = useSearchInfinite(query);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-6 bg-WHITE">
|
<div className="flex flex-col gap-6 bg-WHITE lg:max-w-[1320px] lg:mx-auto lg:w-full lg:px-8 lg:py-6">
|
||||||
|
<div className="lg:hidden">
|
||||||
<SearchTopBar
|
<SearchTopBar
|
||||||
searchValue={searchValue}
|
searchValue={searchValue}
|
||||||
setSearchValue={setSearchValue}
|
setSearchValue={setSearchValue}
|
||||||
@ -40,6 +41,22 @@ export default function Search() {
|
|||||||
maximumPrice={1000000}
|
maximumPrice={1000000}
|
||||||
onSearch={() => onSearch(searchValue, navigate)}
|
onSearch={() => onSearch(searchValue, navigate)}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Desktop results heading */}
|
||||||
|
<div className="hidden lg:block">
|
||||||
|
<nav className="flex items-center gap-2 text-[13px] text-GRAY">
|
||||||
|
<Link to="/" className="hover:text-BLACK">
|
||||||
|
خانه
|
||||||
|
</Link>
|
||||||
|
<span>/</span>
|
||||||
|
<span>جستجو</span>
|
||||||
|
</nav>
|
||||||
|
<h1 className="text-[26px] font-extrabold mt-2">
|
||||||
|
نتایج برای «<span className="text-VITROWN_BLUE">{query}</span>»
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
<MondrianProductList
|
<MondrianProductList
|
||||||
products={
|
products={
|
||||||
data?.pages
|
data?.pages
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user