← Files WixARCHIVED FILE

skills/wix-headless-templates/portfolio/project/src/components/portfolio/CollectionsView.tsx

3.17 KB · Oct 8, 2026 · 12:02 UTC

↓ Download file

See the change to this file →

// REFERENCE gallery surface: collections grid on the @theme tokens.
// Correct and complete; per the skill's model you design and build your own on useCollections.
import type { ComponentType, ReactNode } from "react";
import { useCollections } from "../../hooks/portfolio/useCollections";
import { imgAttrs } from "../../wix/media";
import type { CollectionSummary } from "../../wix/portfolio/types";

export interface LinkLikeProps {
  href: string;
  className?: string;
  children?: ReactNode;
}

const PlainLink = ({ href, className, children }: LinkLikeProps) => (
  <a href={href} className={className}>
    {children}
  </a>
);

export interface CollectionCardProps {
  collection: CollectionSummary;
  collectionHref?: (slug: string) => string;
  LinkComponent?: ComponentType<LinkLikeProps>;
}

export function CollectionCard({
  collection,
  collectionHref = (slug) => `/portfolio/${slug}`,
  LinkComponent = PlainLink,
}: CollectionCardProps) {
  return (
    <LinkComponent href={collectionHref(collection.slug)} className="group block no-underline">
      <div className="relative aspect-[4/3] overflow-hidden rounded-lg bg-secondary">
        {collection.imageUrl && (
          <img
            {...imgAttrs(collection.imageUrl, "(min-width: 1024px) 33vw, (min-width: 640px) 50vw, 100vw", 0.75)}
            alt={collection.title}
            className="h-full w-full object-cover transition-transform duration-300 group-hover:scale-[1.03]"
          />
        )}
      </div>
      <p className="mt-3 text-sm font-medium text-foreground">{collection.title}</p>
      {collection.description && (
        <p className="mt-0.5 text-xs text-muted-foreground">{collection.description}</p>
      )}
    </LinkComponent>
  );
}

export interface CollectionsViewProps {
  initialCollections?: CollectionSummary[];
  emptyMessage?: string;
  collectionHref?: CollectionCardProps["collectionHref"];
  LinkComponent?: ComponentType<LinkLikeProps>;
  CardComponent?: ComponentType<CollectionCardProps>;
}

export default function CollectionsView({
  initialCollections,
  emptyMessage = "No collections yet — check back soon.",
  collectionHref,
  LinkComponent,
  CardComponent = CollectionCard,
}: CollectionsViewProps) {
  const { collections, error } = useCollections({ initialCollections });

  return (
    <div>
      {error && <p className="mb-4 text-sm text-destructive">{error}</p>}
      {collections === null ? (
        <div className="grid grid-cols-1 gap-8 sm:grid-cols-2 lg:grid-cols-3" aria-busy="true">
          {Array.from({ length: 6 }, (_, i) => (
            <div key={i}>
              <div className="aspect-[4/3] animate-pulse rounded-lg bg-secondary" />
              <div className="mt-3 h-3.5 w-2/3 animate-pulse rounded bg-secondary" />
            </div>
          ))}
        </div>
      ) : collections.length === 0 ? (
        <p className="py-16 text-center text-muted-foreground">{emptyMessage}</p>
      ) : (
        <div className="grid grid-cols-1 gap-8 sm:grid-cols-2 lg:grid-cols-3">
          {collections.map((c) => (
            <CardComponent key={c.id} collection={c} collectionHref={collectionHref} LinkComponent={LinkComponent} />
          ))}
        </div>
      )}
    </div>
  );
}

SHA-256: 3f038fd1444c1bd03db7c8e5a4c8d46f2f1950f000858606d2e080855db1dbbd