Skip to main content

Components

Pre-built React components with SDK integration. These handle common storefront patterns like product cards, cart drawers, and checkout modals.

ProductCard

Display a product with image, price, and add-to-cart functionality.
import { ProductCard } from '@shoppex/ui';
import type { Product } from '@shoppex/sdk';

// @validate
const product = {
  uniqid: 'prod_123',
  title: 'Example Product',
  slug: 'example-product',
  price: '9.99',
  currency: 'USD',
  images: [],
} satisfies Product;

<ProductCard
  product={product}
  href={`/product/${product.slug}`}
/>

Props

product
Product
required
Product object from SDK
href
string
Link URL for the product
showRating
boolean
default:"true"
Show star rating
showBadge
boolean
default:"true"
Show type badge (Subscription, Variants, Group)
showAddToCart
boolean
default:"true"
Show add to cart button
showDescription
boolean
default:"true"
Show product description
centerContent
boolean
default:"false"
Center align title and description
aspectRatio
'square' | '4/3' | '3/4' | '16/9'
default:"'4/3'"
Image aspect ratio
Custom link component (for React Router, Next.js, etc.)
For frameworks like React Router or Next.js:
import { Link } from 'react-router-dom';

<ProductCard
  product={product}
  href={`/product/${product.slug}`}
  LinkComponent={({ href, children, className }) => (
    <Link to={href} className={className}>{children}</Link>
  )}
/>

Custom Rendering

<ProductCard
  product={product}
  renderImage={(p) => <CustomImage src={p.images[0]} />}
  renderBadge={(p) => p.bestseller && <Badge>Bestseller</Badge>}
  renderPrice={(p) => <CustomPrice product={p} />}
  renderActions={(p) => <QuickViewButton product={p} />}
/>

AddToCartButton

Smart button that shows quantity controls when product is in cart.
import { AddToCartButton } from '@shoppex/ui';
import type { Product } from '@shoppex/sdk';

// @validate
const product = {
  uniqid: 'prod_123',
  title: 'Example Product',
  price: '9.99',
  currency: 'USD',
  images: [],
} satisfies Product;

<AddToCartButton product={product} />

Props

product
Product
required
Product object
variantId
string
Specific variant ID
showQuantityControls
boolean
default:"true"
Show +/- controls when in cart
onAddToCart
() => void
Callback after adding to cart

Behavior

  1. Not in cart: Shows “Add to Cart” button
  2. In cart: Shows quantity controls (- qty +)
  3. Out of stock: Shows disabled “Out of Stock” button

CartDrawer

Slide-over cart sidebar with full cart management.
import { CartDrawer } from '@shoppex/ui';
import { useState } from 'react';

// @validate
function App() {
  const [isOpen, setIsOpen] = useState(false);

  return (
    <>
      <button onClick={() => setIsOpen(true)}>Open Cart</button>
      <CartDrawer
        isOpen={isOpen}
        onClose={() => setIsOpen(false)}
        checkoutHref="/checkout"
      />
    </>
  );
}

Props

isOpen
boolean
required
Whether the drawer is open
onClose
() => void
required
Called when drawer should close
checkoutHref
string
default:"'/checkout'"
Checkout page URL
title
string
default:"'Cart'"
Drawer title
emptyMessage
string
default:"'Your cart is empty'"
Message when cart is empty
checkoutLabel
string
default:"'Checkout'"
Checkout button label
showTaxNote
boolean
default:"true"
Show “Shipping and taxes calculated at checkout” note
Custom link component

Features

  • Escape key closes drawer
  • Body scroll lock when open
  • Real-time cart sync
  • Quantity +/- controls
  • Remove item button
  • Subtotal calculation

CheckoutModal

Email collection modal for starting checkout.
import { CheckoutModal } from '@shoppex/ui';
import { useState } from 'react';

// @validate
function Example() {
  const [showModal, setShowModal] = useState(false);
  const appliedCoupon = 'WELCOME10';

  return (
    <CheckoutModal
      isOpen={showModal}
      onClose={() => setShowModal(false)}
      coupon={appliedCoupon}
    />
  );
}

Props

isOpen
boolean
required
Whether the modal is open
onClose
() => void
required
Called when modal should close
coupon
string
Pre-filled coupon code
autoRedirect
boolean
default:"true"
Automatically redirect to checkout on success
onSuccess
(result) => void
Callback on successful checkout start
onError
(error: string) => void
Callback on checkout error

Custom Redirect

<CheckoutModal
  isOpen={showModal}
  onClose={() => setShowModal(false)}
  autoRedirect={false}
  onSuccess={({ redirectUrl, invoiceId }) => {
    // Track event before redirect
    analytics.track('checkout_started', { invoiceId });
    window.location.href = redirectUrl;
  }}
/>

SearchCommand

Cmd+K style search command palette.
import { SearchCommand, SearchBar } from '@shoppex/ui';
import { useState, useEffect } from 'react';

// @validate
function App() {
  const [isOpen, setIsOpen] = useState(false);

  // Open with Cmd+K
  useEffect(() => {
    const handler = (e: KeyboardEvent) => {
      if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
        e.preventDefault();
        setIsOpen(true);
      }
    };
    window.addEventListener('keydown', handler);
    return () => window.removeEventListener('keydown', handler);
  }, []);

  return (
    <>
      <SearchBar onClick={() => setIsOpen(true)} />
      <SearchCommand
        isOpen={isOpen}
        onClose={() => setIsOpen(false)}
        getProductHref={(p) => `/product/${p.slug ?? p.uniqid}`}
      />
    </>
  );
}

Props

isOpen
boolean
required
Whether the palette is open
onClose
() => void
required
Called when palette should close
onSelect
(product: Product) => void
Called when a product is selected
getProductHref
(product: Product) => string
Generate product link URL
hideOutOfStock
boolean
default:"false"
Hide out of stock products
maxResults
number
default:"10"
Maximum search results

PriceDisplay

Formatted price with discount and subscription support.
import { PriceDisplay } from '@shoppex/ui';
import type { Product } from '@shoppex/sdk';

// @validate
const product = {
  uniqid: 'prod_123',
  title: 'Example Product',
  price: '9.99',
  currency: 'USD',
  images: [],
} satisfies Product;

<PriceDisplay product={product} />

Props

product
Product
required
Product object
size
'sm' | 'md' | 'lg'
default:"'md'"
Text size
showOriginalPrice
boolean
default:"true"
Show strikethrough original price if discounted
showSubscriptionInterval
boolean
default:"true"
Show “/month” for subscriptions

Example Output

  • Regular: $99.99
  • Discounted: ~~149.99  149.99~~ **99.99** -33%
  • Subscription: $9.99/month
  • Range: 49.9949.99 – 99.99

QuantityStepper

Standalone quantity input with +/- controls.
import { QuantityStepper } from '@shoppex/ui';
import { useState } from 'react';

// @validate
function QuantityInput() {
  const [qty, setQty] = useState(1);

  return (
    <QuantityStepper
      value={qty}
      onChange={setQty}
      min={1}
      max={10}
      size="md"
    />
  );
}

Props

value
number
required
Current quantity
onChange
(value: number) => void
required
Called when quantity changes
min
number
default:"1"
Minimum value
max
number
default:"Infinity"
Maximum value
size
'sm' | 'md' | 'lg'
default:"'md'"
Control size

CouponInput

Coupon code input with validation.
import { CouponInput } from '@shoppex/ui';
import { useCheckout } from '@shoppex/ui';
import { useState } from 'react';

// @validate
function CouponSection() {
  const [appliedCoupon, setAppliedCoupon] = useState<string>();
  const { validateCoupon } = useCheckout();

  return (
    <CouponInput
      appliedCode={appliedCoupon}
      onApply={async (code) => {
        const result = await validateCoupon(code);
        if (result.valid) {
          setAppliedCoupon(code);
          return true;
        }
        return false;
      }}
      onRemove={() => setAppliedCoupon(undefined)}
    />
  );
}

Props

onApply
(code: string) => Promise<boolean>
required
Validate and apply coupon. Return true if valid.
appliedCode
string
Currently applied coupon code
onRemove
() => void
Called when coupon is removed