ProductDetails
The information half of a product page: brand, name, rating, price, description, tags, share and wishlist, SKU and stock counters.
import { ProductDetails } from '@salla.sa/twilight-theme-engine/components/product';In plain words
Everything a product page says about the product, beside the gallery: brand logo, name, subtitle, rating stars, price, description (with "Read more" when it is long), tags, a share button and the sold and remaining counts.
It is lazy, so it goes inside <Suspense>.
Signature
const ProductDetails: React.LazyExoticComponent<(props: {
product: Product;
showTags?: boolean; // true
}) => JSX.Element>
// The props type is not exported: use React.ComponentProps<typeof ProductDetails>.Try it live
Loading products…
import { Suspense } from 'react';
import { ProductDetails } from '@salla.sa/twilight-theme-engine/components/product';
import type { Product } from '@salla.sa/twilight-theme-engine/types';
export function ProductInfo({ product }: { product: Product }) {
return (
<Suspense fallback={<div className="h-40 animate-pulse" />}>
<ProductDetails product={product} />
</Suspense>
);
}
Example
import { Suspense } from 'react';
import { ProductDetails } from '@salla.sa/twilight-theme-engine/components/product';
import { useTheme } from '@salla.sa/twilight-theme-engine/hooks/useTheme';
import type { Product } from '@salla.sa/twilight-theme-engine/types';
export function ProductInfo({ product }: { product: Product }) {
const { settings } = useTheme();
return (
<Suspense fallback={<div className="h-40 animate-pulse" />}>
<ProductDetails product={product} showTags={settings.show_tags} />
</Suspense>
);
}
How it behaves
It renders three hook slots with
context={{ product }}:product:single.description.startbefore everything,product:single.descriptionafter the availability section, andproduct:single.description.endat the end.The description is inserted as HTML (
dangerouslySetInnerHTML), after replacing every with a newline character, which HTML then shows as an ordinary space. Withhas_read_moreit is clamped to 5.25rem until "Read more" is pressed; the button expands only, and stays visible afterwards.Prices are printed as the API sends them with
CurrencySymbol, as inAddToCartForm, not throughuseMoney.Also shown when present: brand logo (engine
Linkto the brand),is_taxablenote,sold_quantity(thepages.products.sold_timestranslation, in Laravel plural format),can_show_remained_quantity,sku, branch availability (show_availability, dispatchesscopes::open),SallaInstallmentandSallaMetadata.
Gotchas
On the engine's product page every
product:single.description*handler renders twice:ProductPagerenders slots with the same three names aroundProductDetails(without context), andProductDetailsrenders its own (with{ product }). In one of the two,productisundefined, so a handler that reads it without a check throws there.The wishlist button calls
window.Salla.wishlist.toggle(product.id)directly and has no "added" state, unlike the card's heart, which followsuseWishlist. It is hidden below thesmbreakpoint.The Read more elements use fixed ids (
more-content,btn-show-more), duplicated if two render on one page.
Related
The product page buy box: options, notes and files, live price, quantity and the Add to cart button, submitted through Salla's SDK.
ProductGalleryA product page's image slider with thumbnails, an in-page zoom popup, video and 3D slides, replaceable through the registry.
HookSlotA named empty place in the page that renders every handler registered under its name, plus a spot where Salla apps inject content.