- Drop buttonRadius prop; button now uses --radius via rounded-md - Inject @theme radius mappings into ThemeProvider so rounded-* utilities pick up --radius inside the Tailwind CDN iframe - Add shared Container that consumes --container-max-width set from the global maxWidth prop, replacing ad-hoc "container mx-auto max-w-7xl px-6" wrappers across commerce, landing, footer, navigation, and others - Simplify maxWidth options to Small/Medium/Large/X-Large/Full bleed and shift the scale up so Large (1280px) matches the previous default Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
118 lines
3.8 KiB
TypeScript
118 lines
3.8 KiB
TypeScript
import { useEffect, useState } from "react";
|
|
import { Link } from "react-router";
|
|
import { shopifyFetch } from "@/services/shopify/client";
|
|
import { GET_COLLECTIONS_QUERY } from "@/graphql/collections";
|
|
import { Typography } from "@/components/Typography";
|
|
import { Container } from "@/components/layout/Container";
|
|
|
|
export type CollectionGridProps = {
|
|
tagline: string;
|
|
heading: string;
|
|
subheading: string;
|
|
layout: "tiles" | "editorial";
|
|
limit: number;
|
|
};
|
|
|
|
type CollectionRow = {
|
|
id: string;
|
|
handle: string;
|
|
title: string;
|
|
description?: string;
|
|
image?: { url: string; altText?: string };
|
|
};
|
|
|
|
export function CollectionGrid({
|
|
tagline,
|
|
heading,
|
|
subheading,
|
|
layout,
|
|
limit,
|
|
}: CollectionGridProps) {
|
|
const [collections, setCollections] = useState<CollectionRow[]>([]);
|
|
|
|
useEffect(() => {
|
|
shopifyFetch<any>({
|
|
query: GET_COLLECTIONS_QUERY,
|
|
variables: { first: limit },
|
|
})
|
|
.then((res) => {
|
|
const list = (res.data?.collections?.edges ?? []).map((e: any) => e.node);
|
|
setCollections(list);
|
|
})
|
|
.catch(() => setCollections([]));
|
|
}, [limit]);
|
|
|
|
const isEditorial = layout === "editorial";
|
|
|
|
return (
|
|
<section className="bg-background py-20 md:py-28">
|
|
<Container>
|
|
<div className="mx-auto mb-12 max-w-2xl text-center">
|
|
{tagline ? (
|
|
<p className="mb-3 text-xs uppercase tracking-[0.2em] text-muted-foreground">
|
|
{tagline}
|
|
</p>
|
|
) : null}
|
|
<Typography variant="h2">{heading}</Typography>
|
|
{subheading ? (
|
|
<Typography variant="subtitle1" className="mt-3">
|
|
{subheading}
|
|
</Typography>
|
|
) : null}
|
|
</div>
|
|
|
|
<div
|
|
className={
|
|
isEditorial
|
|
? "grid grid-cols-1 gap-8 md:grid-cols-2"
|
|
: "grid grid-cols-2 gap-x-6 gap-y-12 md:grid-cols-3 lg:grid-cols-4"
|
|
}
|
|
>
|
|
{(collections.length === 0
|
|
? Array.from({ length: limit }).map((_, i) => ({ id: `sk-${i}` }) as any)
|
|
: collections
|
|
).map((c: CollectionRow) => (
|
|
<Link
|
|
key={c.id}
|
|
to={c.handle ? `/collections/${c.handle}` : "#"}
|
|
className="group block"
|
|
>
|
|
<div
|
|
className={`relative overflow-hidden rounded-md bg-muted ${isEditorial ? "aspect-[3/4] md:aspect-[5/6]" : "aspect-[4/5]"}`}
|
|
>
|
|
{c.image?.url ? (
|
|
<img
|
|
src={c.image.url}
|
|
alt={c.image.altText || c.title}
|
|
className="h-full w-full object-cover transition-transform duration-700 ease-out group-hover:scale-105"
|
|
/>
|
|
) : null}
|
|
{isEditorial ? (
|
|
<div className="absolute inset-0 flex items-end bg-gradient-to-t from-black/60 via-transparent to-transparent p-8">
|
|
<div>
|
|
<Typography variant="h4" className="text-white">
|
|
{c.title}
|
|
</Typography>
|
|
<span className="mt-2 inline-flex text-xs uppercase tracking-[0.2em] text-white/80">
|
|
Shop now →
|
|
</span>
|
|
</div>
|
|
</div>
|
|
) : null}
|
|
</div>
|
|
{!isEditorial ? (
|
|
<div className="mt-4 flex items-center justify-between">
|
|
<h3 className="text-sm font-medium tracking-tight">{c.title}</h3>
|
|
<span className="text-xs text-muted-foreground transition-opacity group-hover:opacity-100">
|
|
→
|
|
</span>
|
|
</div>
|
|
) : null}
|
|
</Link>
|
|
))}
|
|
</div>
|
|
</Container>
|
|
</section>
|
|
);
|
|
}
|