Skip to content
Twilight React Playground
ثيم رائدaren

ProductGallery

componentBeginnerserverbrowserlive demo

A product page's image slider with thumbnails, an in-page zoom popup, video and 3D slides, replaceable through the registry.

import { ProductGallery, ProductGalleryProps } from '@salla.sa/twilight-theme-engine/components/product';

In plain words

The big picture area of a product page: the main image, a row of thumbnails when there are several, and a popup that zooms the image when the shopper clicks it, without leaving the page.

Pass the product (its details, which include every image) and it lays itself out as the left half of the product row, like the engine's product page.

Signature

function ProductGallery(props: ProductGalleryProps): JSX.Element

interface ProductGalleryProps {
  product: Product;
}

Try it live

A real product's image gallery: main slider, thumbnails when there is more than one image, and a zoom popup on click.Try this: click the main image: it opens an in-page lightbox instead of leaving the site.
Storefront canvas · ar · RTL

Loading products…

Controls
What a theme writes
import { useQuery } from '@tanstack/react-query';
import { product } from '@salla.sa/twilight-theme-engine/api/product';
import { ProductGallery } from '@salla.sa/twilight-theme-engine/components/product';

export function Gallery({ id }: { id: string }) {
  const { data } = useQuery(product.queries.detail(id));
  if (!data) return null;
  return (
    <div className="flex flex-col md:flex-row items-start">
      <ProductGallery product={data} />
    </div>
  );
}

Example

app/components/product/Gallery.tsx
import { useQuery } from '@tanstack/react-query';
import { product } from '@salla.sa/twilight-theme-engine/api/product';
import { ProductGallery } from '@salla.sa/twilight-theme-engine/components/product';

export function Gallery({ id }: { id: string }) {
  const { data } = useQuery(product.queries.detail(id));
  if (!data) return null;
  return (
    <div className="flex flex-col md:flex-row items-start">
      <ProductGallery product={data} />
    </div>
  );
}

How it behaves

  • Not lazy. Slides come from product.images; with none it falls back to [product.image], so a list item still shows its one picture, without thumbnails.

  • A click on an image opens the engine ImageModal. Video slides open their URL in a new tab; 3D slides render a <model-viewer> element. In the Salla mobile app (Salla.mobile.isEnabled()) a click dispatches mobile::gallery.open with the media list instead.

  • Object fit comes from the theme setting slider_background_size (default cover). promotion_title and calories are drawn over the slider.

  • registry.override('product:gallery', MyGallery) is enough to replace it (no seed registration needed, unlike the card). The lookup runs once per mount.

Gotchas

  • Nothing in the engine or the reference theme loads the definition of the model-viewer element, so unless something else on the page does, a 3D slide is an empty box 500px tall. Load it yourself if your products have 3D models.

  • product.queries.detail(id) first answers with the list card as placeholder data when a list already loaded that product: one image, no gallery. Check isPlaceholderData if the first paint must show every image.

  • The slider element id is details-slider-<product id>. Two galleries for the same product on one page share it, and the one-frame re-insert the gallery does after mounting (to restart the slider) can pick the wrong one.

  • Like the card, a replacement cannot render the engine ProductGallery inside itself: it resolves the key again and recurses.

Related

Source and docs