Store product listing
A storefront results page: facet sidebar with live counts, active chips, sort, grid and list views, pagination or load more, empty results with suggestions, a mobile filter sheet, plus the storefront product card, quick view and compare tray and table.
Code
<StoreListing title="Clothing" products={products} currency="USD" categoryTree={tree} defaultFilters={{ ...EMPTY_LISTING_FILTERS, category: "Clothing" }} onAddToCart={(product, variant, qty) => cart.add(variant.id, qty)} compare/>Storefront · beta
Live examples and controls: Store product listing in the lab.
Install
npx shadcn@latest add https://docs.nasaqui.com/r/store-listing.jsonStoreListing is a whole results page in one component: a facet sidebar with counts, removable filter chips, sort, grid and list views, pagination or load more, an empty state that suggests how to loosen the search, and a filter sheet on phones. It is fed a CommerceProduct[] and does the filtering, sorting and paging itself (or take control of any part).
The parts are also exported and are meant to be reused elsewhere: StoreProductCard, StoreQuickView, StoreCompareTray, StoreCompareTable, StoreOptionPicker and StorePrice.
When to use
- Category pages, search results, brand pages, collections.
StoreProductCardin any grid or carousel of products.
When not to use
- An admin product table: use the products admin.
- One product's page: use
ProductDetail.
Import
import { StoreListing, StoreProductCard } from "@fadymondy/nasaq/web";Quick start
<StoreListing
title="Clothing"
products={products}
currency="USD"
categoryTree={tree}
defaultFilters={{ ...EMPTY_LISTING_FILTERS, category: "Clothing" }}
onAddToCart={(product, variant, qty) => cart.add(variant.id, qty)}
compare
/>Prices in CommerceProduct are integer minor units (cents for USD, halalas for SAR); the components convert with the currency's own digits.
API
StoreListing
| Prop | Type | Default | Description |
|---|---|---|---|
products | CommerceProduct[] | required | The full result set. Drafts and archived items are ignored. |
currency | string | "USD" ("SAR" in Arabic) | ISO 4217 code. |
categoryTree? | ListingCategoryNode[] | none | Category facet. Node ids equal product.category. |
filters? / defaultFilters? / onFiltersChange? | ListingFilters | empty | Query, category, brands, options, price range, rating, in stock, on sale. |
sort? / defaultSort? / onSortChange? | ListingSort | relevance | Relevance, price, newest, top rated, biggest discount, name. |
view? / defaultView? / density? | grid | Grid or list; comfortable or compact. | |
pageSize? / paging? | number / "pages" | "load-more" | 12 / pages | |
loading? / error? / onRetry? | Skeleton grid and error state. | ||
onAddToCart? | (product, variant, qty) | none | Shows quick add on cards and in quick view. |
wishlistIds? / onToggleWishlist? | own state | ||
compare? / compareMax? / compareIds? / onCompareChange? | off / 4 | Compare boxes, a tray, and a table with a differences-only switch. | |
getHref? / onNavigate? | Card links. | ||
popularSearches? / onSearch? | Suggestions in the empty state. | ||
labels? | ListingLabels | locale | Override any string. |
StoreProductCard
product, currency, href?, layout? (grid, list), ratio?, wishlisted?, compared?, onToggleWishlist?, onToggleCompare?, onQuickView?, onAddToCart?(product, variant, qty), onNavigate?, maxSwatches?, menu?, priority?, labels?. Shows a second image on hover, colour swatches that preview the variant image, badges, a discount, a from-price, a heart and quick add (which asks for any option still open). Context-click, Shift+F10 or the Menu key open its actions.
StoreQuickView
product, open, onOpenChange, currency, href?, onAddToCart?, closeOnAdd?, lowStockAt?, labels?. A dialog with gallery, options, quantity, stock note and add to cart.
Model helpers
Pure functions, importable in Node and covered by tests: listingFilter, listingSort, listingFacets (disjunctive counts: a facet does not count against its own selection), listingActiveChips, listingRemoveChip, listingClear, listingRelaxations, listingPage, listingToggleCompare, listingCompareRows, listingCompareDifferences, listingToMajor, listingToMinor.
Accessibility
- Options are a radio group; arrow keys move and mirror in RTL. Unavailable values are marked, not hidden.
- Quantity steppers and results counts are live regions; the quick view is a modal dialog with focus returned to the card.
- Facet groups are fieldsets with a legend; the price range slider has named thumbs. The phone sheet applies filters only on Apply.
- The card image is not a link, so context-click works; the name is the link. Hover-only actions are also reachable by keyboard focus.
- Motion (image swap, transitions) is off under reduced motion; images lazy-load with a token placeholder.
RTL and Arabic
Logical layout, mirrored arrows and sheets, Arabic search normalisation (diacritics, alef and taa marbuta variants), and Arabic strings for every label.
Store header and footer
The storefront frame: announcement bar, sticky header with mega menu, search autocomplete and cart count, and a footer with link columns, newsletter, payments slot, social links and language and currency switches.
Store merchandising
Storefront home blocks: category tiles, hero and promo banners, a flash-deal strip with a live countdown and stock progress, a reusable product carousel for related and recently viewed items, and a brand strip.