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
Show type badge (Subscription, Variants, Group)
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.)
Custom Link Component
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} />}
/>
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
Show +/- controls when in cart
Callback after adding to cart
Behavior
- Not in cart: Shows “Add to Cart” button
- In cart: Shows quantity controls (- qty +)
- 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
Whether the drawer is open
Called when drawer should close
checkoutHref
string
default:"'/checkout'"
Checkout page URL
emptyMessage
string
default:"'Your cart is empty'"
Message when cart is empty
checkoutLabel
string
default:"'Checkout'"
Checkout button label
Show “Shipping and taxes calculated at checkout” note
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
Whether the modal is open
Called when modal should close
Automatically redirect to checkout on success
Callback on successful checkout start
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
Whether the palette is open
Called when palette should close
onSelect
(product: Product) => void
Called when a product is selected
getProductHref
(product: Product) => string
Generate product link URL
Hide out of stock products
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
size
'sm' | 'md' | 'lg'
default:"'md'"
Text size
Show strikethrough original price if discounted
Show “/month” for subscriptions
Example Output
- Regular: $99.99
- Discounted: ~~149.99 ∗∗99.99** -33%
- Subscription: $9.99/month
- Range: 49.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
onChange
(value: number) => void
required
Called when quantity changes
size
'sm' | 'md' | 'lg'
default:"'md'"
Control size
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.
Currently applied coupon code
Called when coupon is removed