merchify-ui
Version:
React components for merchandise visualization and customization
1,498 lines (1,477 loc) • 59.3 kB
TypeScript
import React, { ButtonHTMLAttributes, ReactNode } from 'react';
import * as react_jsx_runtime from 'react/jsx-runtime';
import { ProductPriceOptions, CatalogProduct, DesignElement, ImageAlignment, ProductContext as ProductContext$1, OptionSelection, WebSocketConfig } from 'merchify';
export { ArtworkData, ImageAlignment, ProductArtAlignmentContext, ProductArtAlignmentOptions, ProductData, ProductMockupData, ProductPlacement, ProductVariant, describeProductArtAlignment, getSnapPoints } from 'merchify';
/**
* Button - A flexible button primitive with multiple variants
*
* Provides consistent button styles across the application with support for:
* - Multiple visual variants (primary, outline, ghost, option-text, option-swatch)
* - Disabled and loading states
* - Theme-aware styling
* - Full accessibility support
*
* @example
* ```tsx
* // Primary button
* <Button variant="primary">Add to Cart</Button>
*
* // Option text button (for size selectors)
* <Button
* variant="option-text"
* selected={isSelected}
* onClick={() => setSize("8x10")}
* >
* 8x10
* </Button>
*
* // Option swatch button (for color/frame selectors)
* <Button
* variant="option-swatch"
* selected={isSelected}
* style={{ backgroundColor: '#8B4513' }}
* />
* ```
*/
interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
/**
* Visual variant of the button
* - primary: Filled button with primary color
* - outline: Button with border only
* - ghost: Transparent button with hover effect
* - secondary: Muted background button
* - option-text: Rectangular option button for text choices (sizes, styles)
* - option-swatch: Circular option button for visual choices (colors, frames)
*/
variant?: "primary" | "outline" | "ghost" | "secondary" | "option-text" | "option-swatch";
/**
* Whether this option is currently selected (for option-text and option-swatch variants)
*/
selected?: boolean;
/**
* Whether the button is in a loading state
*/
loading?: boolean;
/**
* Full width button (spans container width)
*/
fullWidth?: boolean;
}
declare const Button: React.ForwardRefExoticComponent<ButtonProps & React.RefAttributes<HTMLButtonElement>>;
interface ColorSwatchChoice {
value: string;
label: string;
hex?: string;
imageUrl?: string;
selected?: boolean;
disabled?: boolean;
}
interface ColorSwatchProps {
/** Array of color choices to display */
choices: ColorSwatchChoice[];
/** Callback when a color is selected */
onChange: (value: string) => void;
/** Optional ARIA label for the color group */
ariaLabel?: string;
/** Show tooltip on selection (default: true) */
showTooltip?: boolean;
/** Custom size in pixels (default: 40) */
size?: number;
/** Custom className for the container */
className?: string;
}
/**
* ColorSwatch - Interactive color selector with visual feedback
*
* A primitive component for displaying color choices as clickable swatches with
* support for hex colors, image thumbnails, selection states, and tooltips.
* Designed for maximum accessibility and visual clarity.
*
* Features:
* - Hex color or image thumbnail backgrounds
* - Visual selection indicators (checkmark, rings)
* - Disabled state with diagonal slash
* - Hover scaling for interactivity
* - Tooltip feedback on selection
* - Screen reader announcements
* - Accessible with ARIA roles and labels
* - Dark mode support
* - Customizable size
* - Colorblind-friendly selection indicators
*
* **Visual States:**
* - Default: Simple round swatch with subtle border
* - Selected: Double ring + checkmark icon + pattern overlay
* - Disabled: Diagonal slash overlay
* - Hover: Slight scale animation
*
* **Accessibility:**
* - Keyboard navigable
* - Screen reader friendly with status announcements
* - Visible selection indicators for colorblind users
* - Proper ARIA roles and labels
*
* @example
* ```tsx
* // Basic color swatches
* <ColorSwatch
* choices={[
* { value: "red", label: "Red", hex: "#ff0000", selected: true },
* { value: "blue", label: "Blue", hex: "#0000ff" },
* { value: "green", label: "Green", hex: "#00ff00", disabled: true }
* ]}
* onChange={(value) => console.log('Selected:', value)}
* ariaLabel="Choose product color"
* />
* ```
*
* @example
* ```tsx
* // With image thumbnails (patterns/textures)
* <ColorSwatch
* choices={[
* { value: "marble", label: "Marble", imageUrl: "/textures/marble.jpg" },
* { value: "wood", label: "Wood Grain", imageUrl: "/textures/wood.jpg" }
* ]}
* onChange={handleMaterialChange}
* size={50}
* />
* ```
*
* @example
* ```tsx
* // Custom styling and no tooltips
* <ColorSwatch
* choices={colorOptions}
* onChange={setColor}
* showTooltip={false}
* size={60}
* className="gap-4 justify-center"
* />
* ```
*
* @param choices - Array of color/pattern choices to display
* @param onChange - Callback when a swatch is clicked (receives choice value)
* @param ariaLabel - Accessible label for the color group
* @param showTooltip - Show selection tooltip (default: true)
* @param size - Swatch diameter in pixels (default: 40)
* @param className - Additional CSS classes for container
*/
declare function ColorSwatch({ choices, onChange, ariaLabel, showTooltip, size, className, }: ColorSwatchProps): react_jsx_runtime.JSX.Element;
interface ProductPriceProps extends ProductPriceOptions {
contextPrice?: number;
showCents?: boolean;
}
/**
* ProductPrice - Formatted price display with currency and locale support
*
* A primitive component for displaying product prices with automatic currency
* formatting, smart cents display, and seamless Product context integration.
* Uses semantic HTML for machine-readable price data.
*
* Features:
* - Automatic currency formatting based on locale
* - Smart cents display (hides .00, shows actual cents)
* - Semantic HTML using `<data>` element with value attribute
* - Accessible with ARIA labels for screen readers
* - Works standalone or within Product context
* - Automatically uses currentPrice from context (reflects variant selection)
* - Supports international currencies and locales
* - Machine-readable price in value attribute for SEO/scrapers
*
* **Price Format:**
* - Prices are stored in cents to avoid floating point issues
* - Example: 2999 cents = $29.99
* - Automatically converts to display format
*
* **Context Integration:**
* - When used in `Product` context, automatically shows current price
* - Updates when user selects different variants
* - Falls back to prop value when used standalone
*
* @example
* ```tsx
* // Standalone usage with explicit price
* <ProductPrice price={2999} showCents={true} />
* // Output: $29.99
* ```
*
* @example
* ```tsx
* // Within Product context (automatically uses current price)
* <Product productId="shirt-123">
* <ProductOptions />
* <ProductPrice showCents={false} />
* </Product>
* // Output: $29 (or $35 if user selects a pricier variant)
* ```
*
* @example
* ```tsx
* // International currency and locale
* <ProductPrice
* price={2999}
* currency="EUR"
* locale="de-DE"
* showCurrency={true}
* />
* // Output: €29,99
* ```
*
* @example
* ```tsx
* // Custom styling for sale prices
* <div className="flex items-center gap-2">
* <ProductPrice
* price={1999}
* className="text-2xl font-bold text-green-600"
* />
* <ProductPrice
* price={2999}
* showCents={false}
* className="text-sm line-through text-gray-400"
* />
* </div>
* ```
*
* @param price - Price in cents (e.g., 2999 = $29.99). Optional if used within Product context
* @param currency - ISO 4217 currency code (default: "USD")
* @param locale - BCP 47 locale string for formatting (default: "en-US")
* @param showCurrency - Display full currency symbol (default: false, shows $ only)
* @param showCents - Display cents portion (default: true, hides if .00)
* @param className - Additional CSS classes for styling
* @param contextPrice - Internal: Override context price (used by Product component)
*/
declare function ProductPrice({ price, currency, locale, showCurrency, className, contextPrice, showCents, }: ProductPriceProps): react_jsx_runtime.JSX.Element | null;
interface FloatingActionItem {
id: string;
label: string;
children?: React.ReactNode;
onClick?: (e?: any) => void;
selected?: boolean;
disabled?: boolean;
variant?: "circular" | "pill";
}
interface FloatingActionGroupProps {
items: FloatingActionItem[];
className?: string;
scrollable?: boolean;
leftAligned?: boolean;
justify?: "start" | "end" | "center";
ariaLabel?: string;
}
/**
* FloatingActionGroup - A primitive component for displaying a horizontal group of action buttons
*
* Creates a row of circular or pill-shaped buttons with selection states and disabled states.
* Commonly used for filters, tabs, or quick actions. Supports horizontal scrolling for
* overflow scenarios.
*
* Features:
* - Two button variants: circular (icon-only) or pill (text-based)
* - Visual selection states with background effects
* - Optional horizontal scrolling with hidden scrollbars
* - Smart alignment modes (left, center, right)
* - Accessible with ARIA roles and labels
* - Dark mode support
*
* @example
* ```tsx
* // Simple button group
* <FloatingActionGroup
* items={[
* { id: '1', label: 'All', selected: true, onClick: () => setFilter('all') },
* { id: '2', label: 'Active', onClick: () => setFilter('active') },
* { id: '3', label: 'Done', onClick: () => setFilter('done') }
* ]}
* ariaLabel="Filter tasks"
* />
* ```
*
* @example
* ```tsx
* // Scrollable with custom icons
* <FloatingActionGroup
* scrollable={true}
* leftAligned={true}
* items={[
* { id: 'edit', label: 'Edit', variant: 'circular', children: <PencilIcon /> },
* { id: 'delete', label: 'Delete', variant: 'circular', children: <TrashIcon />, disabled: true }
* ]}
* />
* ```
*
* @param items - Array of action items to display
* @param className - Additional CSS classes
* @param scrollable - Enable horizontal scrolling for overflow (default: false)
* @param leftAligned - Add left padding in scrollable mode (default: false)
* @param justify - Horizontal alignment ("start", "end", or "center", default: "start")
* @param ariaLabel - Accessible label for the button group
*/
declare function FloatingActionGroup({ items, className, scrollable, leftAligned, justify, ariaLabel, }: FloatingActionGroupProps): react_jsx_runtime.JSX.Element;
interface ThemeToggleProps {
isDark?: boolean;
onToggle?: () => void;
className?: string;
position?: "bottom-right" | "bottom-left" | "top-right" | "top-left";
disabled?: boolean;
}
/**
* ThemeToggle - A primitive floating button for toggling between light and dark themes
*
* A fixed-position button that displays a sun/moon icon based on the current theme.
* Commonly used for implementing dark mode toggles in applications.
*
* Features:
* - Fixed positioning with configurable corners
* - Animated icon transitions
* - Accessible with ARIA labels
* - Responsive hover and active states
* - Built-in disabled state
*
* @example
* ```tsx
* const [isDark, setIsDark] = useState(false);
*
* <ThemeToggle
* isDark={isDark}
* onToggle={() => setIsDark(!isDark)}
* position="bottom-right"
* />
* ```
*
* @example
* ```tsx
* // With Next.js theme provider
* import { useTheme } from 'next-themes';
*
* function MyApp() {
* const { theme, setTheme } = useTheme();
* return (
* <ThemeToggle
* isDark={theme === 'dark'}
* onToggle={() => setTheme(theme === 'dark' ? 'light' : 'dark')}
* />
* );
* }
* ```
*
* @param isDark - Whether dark mode is currently active (default: false)
* @param onToggle - Callback function when toggle is clicked
* @param className - Additional CSS classes for customization
* @param position - Corner position (default: "bottom-right")
* @param disabled - Disable the toggle button (default: false)
*/
declare function ThemeToggle({ isDark, onToggle, className, position, disabled, }: ThemeToggleProps): react_jsx_runtime.JSX.Element;
interface DragHintAnimationProps {
/** The position to center the animation */
position: {
x: number;
y: number;
};
/** Whether the drag direction is horizontal or vertical */
direction: "horizontal" | "vertical";
}
/**
* DragHintAnimation - Animated hand gesture hint for draggable elements
*
* Displays an animated hand icon that demonstrates drag interaction.
* Automatically fades in and out with a smooth animation cycle.
*
* @example
* ```tsx
* <DragHintAnimation
* position={{ x: 0, y: 10 }}
* direction="horizontal"
* />
* ```
*/
declare function DragHintAnimation({ position, direction }: DragHintAnimationProps): react_jsx_runtime.JSX.Element;
/**
* ProductOptions - Product variant selector with theme-aware styling
*
* ⚠️ **IMPORTANT: Must be used within a `<Product>` context provider!**
*
* A composed component that renders interactive UI for selecting product variants
* (size, color, material, etc.). Uses inline implementations with custom theme-aware
* styling for maximum control and visual quality.
*
* **Context Requirements:**
* - Reads product options, combinations, and current selection from Product context
* - Automatically updates context when user selects options
* - Returns `null` if product has no options
*
* Features:
* - Theme-aware borders using CSS variables
* - Custom color swatches with subtle borders (ring-border/30)
* - Smooth hover transitions and opacity states
* - Size buttons with proper spacing and rounded corners
* - Theme-controlled internal spacing via CSS variables
* - Inline implementations for full style control (no primitives)
*
* Spacing:
* - Internal spacing controlled by CSS variables:
* - `--spacing-option-groups` (default: 1rem) - gap between option groups
* - `--spacing-option-items` (default: 0.5rem) - gap between label and choices
* - External spacing should be controlled by parent container
*
* @example
* ```tsx
* // ✅ CORRECT - Basic usage within Product context
* <Product productId="shirt-123">
* <ProductOptions />
* </Product>
* ```
*
* @example
* ```tsx
* // ✅ CORRECT - Recommended layout with proper spacing
* <Product productId="shirt-123">
* <div className="flex flex-col gap-6">
* <ProductImage />
* <ProductOptions />
* <ProductPrice />
* <AddToCart />
* </div>
* </Product>
* ```
*
* @example
* ```tsx
* // ❌ WRONG - Missing Product context (will throw error!)
* <ProductOptions /> // ❌ useProduct() will throw!
* ```
*
* @example
* ```css
* // Customize internal spacing via CSS
* :root {
* --spacing-option-groups: 1.5rem; // Increase spacing between Size and Color
* --spacing-option-items: 0.75rem; // Increase spacing between label and buttons
* }
* ```
*/
declare function ProductOptions({ className }: {
className?: string;
}): react_jsx_runtime.JSX.Element | null;
interface ProductCardProps {
/**
* Click handler for the card
*/
onClick?: () => void;
/**
* Additional CSS classes for the card container
*/
className?: string;
/**
* Card variant - determines the layout and styling
*/
variant?: "default" | "overlay" | "minimal";
/**
* Whether to show the product price
*/
showPrice?: boolean;
/**
* Whether to show the product category/tag
*/
showCategory?: boolean;
/**
* Children to render inside the card (for custom content)
*/
children?: React.ReactNode;
/**
* Specific variant ID to use for the mockup
*/
variantId?: string;
/**
* Specific mockup ID to use for the mockup
*/
mockupId?: string;
}
/**
* ProductCard - Display product with image, name, price, and category
*
* A composed component that renders a product card with multiple layout variants.
* Integrates with Product context for automatic data binding and supports
* clickable cards for navigation.
*
* Features:
* - Three distinct variants (default, overlay, minimal)
* - Optional artwork display via ProductImage
* - Automatic price formatting
* - Category/tag display
* - Clickable card with accessible button semantics
* - Gradient overlay for text visibility (overlay variant)
* - Responsive aspect ratios
* - Dark mode support
* - CSS custom property theming
*
* **Variants:**
* - `default` - Card with padding, rounded corners, and hover shadow
* - `overlay` - Image with gradient overlay and text at bottom (search results style)
* - `minimal` - Clean layout with image and text below (product grid style)
*
* **Context Integration:**
* - Must be used within a `Product` context provider
* - Automatically extracts product name, price, category from context
* - ProductImage inside ProductCard reads artwork from Design context (set by ArtSelector)
* - Works seamlessly with `ProductList` for catalog display
*
* @example
* ```tsx
* // In a product catalog with ProductList
* <ProductList limit={6}>
* <ProductCard
* variant="overlay"
* showCategory={true}
* onClick={() => router.push('/product/${product.id}')}
* />
* </ProductList>
* ```
*
* @example
* ```tsx
* // Standalone with explicit Product context and artwork via ArtSelector
* <Product productId="shirt-123">
* <ArtSelector artworks={[...]} />
* <ProductCard
* showPrice={true}
* variant="minimal"
* />
* </Product>
* ```
*
* @example
* ```tsx
* // With custom children for badges or actions
* <ProductCard variant="default" showPrice>
* <div className="mt-2">
* <span className="badge">New</span>
* <button className="add-to-cart-btn">Quick Add</button>
* </div>
* </ProductCard>
* ```
*
* @example
* ```tsx
* // Grid layout example
* <div className="grid grid-cols-3 gap-4">
* {products.map(product => (
* <Product key={product.id} productData={product}>
* <ProductCard variant="minimal" showPrice />
* </Product>
* ))}
* </div>
* ```
*
* @param onClick - Click handler (makes card interactive/clickable)
* @param className - Additional CSS classes for card container
* @param variant - Layout variant ("default", "overlay", or "minimal")
* @param showPrice - Display product price (default: false)
* @param showCategory - Display product category/tag (default: true)
* @param children - Custom content to render inside card (badges, buttons, etc.)
*/
declare function ProductCard({ onClick, className, variant, showPrice, showCategory, children, variantId, mockupId, }: ProductCardProps): react_jsx_runtime.JSX.Element | null;
interface ProductListProps {
endpoint?: string;
limit?: number;
children: React.ReactNode;
}
/**
* ProductList - Fetch and display a grid of products
*
* A composed component that efficiently fetches multiple products in a single
* API call and renders them in a responsive grid. Pre-fetches product data to
* avoid duplicate API calls from child Product components.
*
* Features:
* - Single API call for multiple products (efficient)
* - Pre-fetched data passed to Product components
* - Responsive grid layout (1/2/3 columns)
* - Loading state with placeholder
* - Integration with Shop context for configuration
* - Automatic endpoint inheritance from Shop context
* - Customizable limit
*
* **Performance:**
* - Fetches all products once (not per-product)
* - Passes productData to Product to skip individual fetches
* - Reduces API calls from N to 1
*
* **Context Integration:**
* - Works with Shop context for endpoint configuration
* - Falls back to props or environment variables
* - Each child Product gets pre-fetched data
*
* @example
* ```tsx
* // Basic product grid
* <ProductList limit={12}>
* <ProductCard variant="minimal" showPrice />
* </ProductList>
* ```
*
* @example
* ```tsx
* // With Shop context
* <Shop endpoint="http://localhost:3000">
* <ProductList limit={6}>
* <ProductCard variant="overlay" showCategory />
* </ProductList>
* </Shop>
* ```
*
* @example
* ```tsx
* // Custom children per product
* <ProductList limit={8}>
* <div className="space-y-2">
* <ProductImage />
* <ProductCard variant="minimal" />
* <AddToCart className="w-full" />
* </div>
* </ProductList>
* ```
*
* @example
* ```tsx
* // Standalone with explicit configuration
* <ProductList
* endpoint="http://localhost:3000"
* limit={20}
* >
* <ProductCard showPrice showCategory />
* </ProductList>
* ```
*
* @param endpoint - API endpoint URL (falls back to Shop context or env)
* @param limit - Maximum number of products to fetch (default: 10)
* @param children - Component(s) to render for each product (receives Product context)
*/
declare function ProductList({ endpoint, limit, children, }: ProductListProps): react_jsx_runtime.JSX.Element;
interface UseProductGalleryOptions {
endpoint?: string;
mode?: "mock" | "live";
limit?: number;
}
interface UseProductGalleryReturn {
products: CatalogProduct[];
loading: boolean;
error: string | null;
refetch: () => void;
}
/**
* useProductGallery - Fetch and manage product collections
*
* A React hook for fetching either specific products by ID or listing all
* available products. Handles loading states, errors, and provides refetch
* capability. Perfect for product catalogs, related products, and galleries.
*
* Features:
* - Fetch specific products by ID array
* - List all products (empty array)
* - Loading and error states
* - Refetch capability
* - Mock/live mode support
* - Automatic deduplication via memoization
* - Limit for product lists
*
* **Modes:**
* - Specific products: Pass array of IDs `['id1', 'id2']`
* - List all: Pass empty array `[]` or omit parameter
*
* **Use Cases:**
* - Product catalog pages
* - Related/recommended products
* - Search results
* - Recently viewed
* - User favorites
*
* @example
* ```tsx
* // Fetch specific products
* const { products, loading, error, refetch } = useProductGallery(
* ['BEEB77', 'RQNU68'],
* { endpoint: 'http://localhost:3000', mode: 'mock' }
* );
*
* if (loading) return <Spinner />;
* if (error) return <Error message={error} onRetry={refetch} />;
*
* return (
* <div className="grid grid-cols-3 gap-4">
* {products.map(product => (
* <ProductCard key={product.id} product={product} />
* ))}
* </div>
* );
* ```
*
* @example
* ```tsx
* // List all products with limit
* const { products, loading } = useProductGallery(
* [], // Empty array = list all
* { mode: 'mock', limit: 12 }
* );
* ```
*
* @example
* ```tsx
* // Use in Remix loader
* export async function loader() {
* // Note: In loaders, use SDK functions directly
* // This hook is for client components
* const products = await listProducts({ ... });
* return json({ products });
* }
*
* // Then in component
* function ProductGallery() {
* const { products } = useLoaderData<typeof loader>();
* // Or use hook for client-side filtering
* const { products: filtered } = useProductGallery(
* products.map(p => p.id)
* );
* }
* ```
*
* @example
* ```tsx
* // With error handling and retry
* function ProductCatalog() {
* const { products, loading, error, refetch } = useProductGallery([], {
* mode: 'live',
* limit: 20
* });
*
* if (error) {
* return (
* <div>
* <p>Failed to load: {error}</p>
* <button onClick={refetch}>Retry</button>
* </div>
* );
* }
*
* return <ProductGrid products={products} loading={loading} />;
* }
* ```
*
* @param productIds - Array of product IDs to fetch (empty array = list all)
* @param options - Configuration options
* @param options.endpoint - API endpoint URL
* @param options.mode - Data source ("mock" or "live", default: "mock")
* @param options.limit - Max products to return when listing all (default: 10)
* @returns Object with products array, loading state, error, and refetch function
*/
declare function useProductGallery(productIds?: string[], // Make optional - empty array means "list all products"
options?: UseProductGalleryOptions): UseProductGalleryReturn;
interface ProductGalleryProps extends UseProductGalleryOptions {
productIds?: string[];
children: (products: CatalogProduct[], loading: boolean, error: string | null, refetch: () => void) => React.ReactNode;
}
/**
* ProductGallery - Fetch products and provide them via render prop
*
* Two modes:
* 1. Specific products: Pass productIds array
* 2. List all products: Pass empty array or omit productIds
*
* @example Specific products
* <ProductGallery productIds={["BEEB77", "RQNU68", "UJJ2QU"]}>
* {(products, loading, error) => (
* <div className="grid grid-cols-3 gap-4">
* {products.map(product => (
* <ProductImage key={product.id} product={product} />
* ))}
* </div>
* )}
* </ProductGallery>
*
* @example List all products
* <ProductGallery limit={12}>
* {(products, loading, error) => (
* <ProductCatalog products={products} />
* )}
* </ProductGallery>
*/
declare function ProductGallery({ productIds, // Default to empty array for listing all products
children, ...options }: ProductGalleryProps): react_jsx_runtime.JSX.Element;
interface AddToCartProps {
text?: string;
loadingText?: string;
successText?: string;
disabled?: boolean;
className?: string;
quantity?: number;
onClick?: (cartDetail: any) => void;
onError?: (error: Error) => void;
simulateDelay?: number;
}
/**
* AddToCart - Add to cart button with validation and loading states
*
* A composed component that provides a complete add-to-cart experience with
* automatic validation, loading states, success feedback, and cart detail creation.
* Integrates with Product context for seamless variant and pricing handling.
*
* Features:
* - Automatic selection validation before allowing add
* - Loading state with customizable text
* - Success state with visual feedback (green background)
* - Automatic cart detail generation with variant info
* - Simulated delay for cart operations
* - Screen reader announcements for state changes
* - Accessible with ARIA labels and busy states
* - Disabled state when selection is invalid
* - CSS custom property support for theming
*
* **Validation:**
* - Ensures all required options are selected
* - Checks variant availability
* - Validates against product combinations
* - Disables button when invalid
*
* **Context Integration:**
* - Must be used within a `Product` context provider
* - Automatically uses current selection from context
* - Accesses pricing and variant data
*
* @example
* ```tsx
* // Basic usage within Product context
* <Product productId="shirt-123">
* <ProductOptions />
* <AddToCart
* onClick={(cartDetail) => {
* console.log('Added to cart:', cartDetail);
* // Add to your cart state/API
* }}
* />
* </Product>
* ```
*
* @example
* ```tsx
* // With custom text and quantity
* <AddToCart
* text="Buy Now"
* loadingText="Processing..."
* successText="Added to Cart!"
* quantity={2}
* onClick={(detail) => addToCart(detail)}
* onError={(err) => showNotification(err.message)}
* />
* ```
*
* @example
* ```tsx
* // With custom styling
* <AddToCart
* className="w-full py-4 bg-blue-600 text-white rounded-lg hover:bg-blue-700"
* text="Add to Bag"
* />
* ```
*
* @example
* ```tsx
* // Faster simulation for testing
* <AddToCart
* simulateDelay={500}
* onClick={(detail) => console.log(detail)}
* />
* ```
*
* @param text - Button text in default state (default: "Add to Bag")
* @param loadingText - Button text while adding (default: "Adding...")
* @param successText - Button text after successful add (default: "Added!")
* @param disabled - Force button to disabled state
* @param className - Additional CSS classes for button
* @param quantity - Number of items to add (default: 1)
* @param onClick - Callback with cart detail after successful add
* @param onError - Callback when add fails with error
* @param simulateDelay - Delay in ms to simulate cart operation (default: 1000)
*/
declare function AddToCart({ text, loadingText, successText, disabled, className, quantity, onClick, onError, simulateDelay, }: AddToCartProps): react_jsx_runtime.JSX.Element;
/**
* Regular artwork (photos, illustrations, logos)
* Use with ArtAlignment for positioning
*/
type RegularArtwork$1 = {
type: "regular";
src: string;
};
/**
* Seamless tiling pattern
* Use with TileCount for density control
* REQUIRES tileCount to be specified
*/
type SeamlessPattern$1 = {
type: "pattern";
src: string;
/**
* Tile density (0.25 | 0.5 | 1 | 2 | 4)
* REQUIRED for patterns - controls how many times the pattern repeats
*/
tileCount: 0.25 | 0.5 | 1 | 2 | 4;
};
/**
* Discriminated union of artwork types
* TypeScript will enforce that SeamlessPattern MUST have tileCount
*/
type Artwork$1 = RegularArtwork$1 | SeamlessPattern$1;
/**
* Base props shared across all ProductImage configurations
*/
interface ProductImageBaseProps {
productId?: string;
mockupId?: string;
variantId?: string;
gvid?: string;
images?: DesignElement[];
width?: number;
effects?: {
grain?: 1 | 2;
};
className?: string;
product?: {
id: string;
name?: string;
placements?: any[];
};
}
/**
* ProductImage props - context-based artwork provision only
*
* **⚠️ CONTEXT-ONLY COMPONENT**: This component does NOT accept artwork as a prop!
*
* **Two ways to provide artwork:**
* 1. **Context via ArtSelector** (RECOMMENDED - for interactive selection UI)
* 2. **placements prop** (for multi-placement products with colors/images)
*
* ⚠️ If no artwork is provided through any method, component shows "Select artwork to preview" placeholder
*/
interface ProductImageProps extends ProductImageBaseProps {
/**
* Simplified placement map (label -> color or image URL)
* For multi-placement products with colors
* Example: `{ Front: '#FF0000', Back: '#0000FF' }`
*
* **Note:** For artwork images, use ArtSelector component for context-based management.
* This placements prop is primarily for colors, though it can accept image URLs too.
*/
placements?: Record<string, string>;
}
declare function ProductImage({ productId, mockupId, variantId, gvid, images, width, effects, className, product, placements, }: ProductImageProps): react_jsx_runtime.JSX.Element;
interface TileCountProps {
value?: SeamlessPattern$1["tileCount"];
imageUrl?: string;
className?: string;
valueClassName?: string;
valueStyle?: React.CSSProperties;
thumbBorderColor?: string;
placement?: string;
artwork?: {
src: string;
tileCount?: SeamlessPattern$1["tileCount"];
};
}
/**
* TileCount - Interactive tile density control for seamless patterns
*
* A composed component that allows users to adjust the tile density (repetition frequency)
* of seamless pattern artwork. Features a live tiled preview and a slider with 5 preset
* density levels, making it easy to control how many times a pattern repeats.
*
* **🚨 DO NOT USE THIS COMPONENT DIRECTLY!**
* **Use `<ArtworkCustomizer />` instead - it automatically handles both regular artwork AND patterns.**
*
* **Why use ArtworkCustomizer:**
* - ✅ Single component for both regular artwork and patterns
* - ✅ TypeScript enforces correct props based on artwork type
* - ✅ Impossible to make mistakes (no manual conditionals needed)
* - ✅ Cleaner, simpler code
*
* **Only use TileCount directly if:**
* - You are 100% certain you only have seamless patterns (never regular artwork)
* - You are building a custom component that wraps TileCount
* - You have read the ArtworkCustomizer docs and understand why you need direct access
*
* **⚠️ CRITICAL: If you use TileCount directly:**
* - ONLY for seamless pattern artwork - NEVER for regular photos/illustrations
* - For regular artwork, use `ArtAlignment` component instead
* - NEVER show both TileCount and ArtAlignment at the same time
* - Check `artwork.type === 'seamless'` to decide which component to render
* - Pattern: `{artwork.type === 'seamless' ? <TileCount /> : <ArtAlignment />}`
*
* **👉 See ArtworkCustomizer for the recommended approach!**
*
* **Features:**
* - Live preview showing actual tile repetition at 64x64px
* - 5 preset tile densities (Fewest → Most)
* - Smooth slider with snap-to-preset behavior
* - Descriptive labels for each density level
* - Checkerboard background for transparent patterns
* - Theme-aware styling with CSS variables
* - Accessible slider with ARIA labels
* - Customizable thumb border color
*
* **Tile Density Values:**
* - `0.25` - "Fewest" - Pattern displays at 4x original size (120px tiles in preview)
* - `0.5` - "Less" - Pattern displays at 2x original size (100px tiles in preview)
* - `1` - "Normal" - Pattern displays at original size (80px tiles in preview)
* - `2` - "More" - Pattern displays at 0.5x original size (60px tiles in preview)
* - `4` - "Most" - Pattern displays at 0.25x original size (44px tiles in preview)
*
* **Visual Behavior:**
* - Preview box: 64x64px with 2px border in border color
* - Slider: 128px wide with 5 snap points (0, 25, 50, 75, 100)
* - Thumb: 24x24px with customizable border, hover scale effect
* - Label: "Tiles" with current density name in muted color
*
* **Context Independence:**
* This is a controlled component that doesn't depend on any context. It simply
* displays a preview and fires onChange events. Parent components handle state
* and integration with design/artwork contexts.
*
* @example
* ```tsx
* // Basic usage with state
* const [tileCount, setTileCount] = useState(1);
*
* <TileCount
* value={tileCount}
* onChange={setTileCount}
* imageUrl="https://example.com/pattern.png"
* />
* ```
*
* @example
* ```tsx
* // Custom styling with theme integration
* <TileCount
* value={tileDensity}
* onChange={handleTileChange}
* imageUrl={patternUrl}
* className="mb-6"
* valueClassName="text-primary font-semibold"
* thumbBorderColor="var(--color-primary)"
* />
* ```
*
* @example
* ```tsx
* // Without preview (slider only)
* <TileCount
* value={1}
* onChange={setTileCount}
* // No imageUrl = no preview shown
* />
* ```
*
* @param value - Current tile density (0.25, 0.5, 1, 2, or 4). Defaults to 1 (Normal). If not provided, reads from artwork or Design context
* @param imageUrl - URL of the seamless pattern image to preview. If omitted, no preview is shown
* @param className - Additional CSS classes for the container element
* @param valueClassName - CSS classes for the density label text. Defaults to "text-sm font-normal text-muted-foreground"
* @param valueStyle - Inline styles for the density label text
* @param thumbBorderColor - Border color for the slider thumb. Defaults to black (#000)
*/
declare function TileCount({ value: propValue, imageUrl, className, valueClassName, valueStyle, thumbBorderColor, placement, artwork, }: TileCountProps): react_jsx_runtime.JSX.Element;
interface CurrentSelectionDisplayProps {
/**
* Additional CSS classes to apply to the root element
*/
className?: string;
/**
* Whether to show the price in the selection display
* @default true
*/
showPrice?: boolean;
/**
* Custom message to show when no options are selected
* @default "No options selected yet. Try selecting some options above!"
*/
emptyMessage?: string;
}
/**
* CurrentSelectionDisplay - Shows the current product option selections and price
*
* Automatically displays the selected options and calculated price from the Product context.
* Useful for debugging, documentation, or showing a summary of selections to users.
*
* @example
* ```tsx
* import { Product, ProductOptions, CurrentSelectionDisplay } from '@yourorg/ui-react';
*
* <Product productId="BEEB77">
* <ProductOptions />
* <CurrentSelectionDisplay />
* </Product>
* ```
*/
declare function CurrentSelectionDisplay({ className, showPrice, emptyMessage, }: CurrentSelectionDisplayProps): react_jsx_runtime.JSX.Element;
interface PlacementDesign {
imageUrl: string;
alignment: ImageAlignment;
tiles?: 0.25 | 0.5 | 1 | 2 | 4;
}
type RegularArtwork = {
type: "regular";
src: string;
};
type SeamlessPattern = {
type: "pattern";
src: string;
tileCount: 0.25 | 0.5 | 1 | 2 | 4;
};
type Artwork = RegularArtwork | SeamlessPattern;
interface ReactProductContext extends ProductContext$1 {
updateSelection?: (selection: OptionSelection) => void;
currentPrice?: number;
placements: Record<string, PlacementDesign>;
artworks: Artwork[];
selectedArtwork?: Artwork;
setPlacementDesign: (placement: string, design: Partial<PlacementDesign>) => void;
getPlacementDesign: (placement: string) => PlacementDesign | undefined;
addArtwork: (artwork: Artwork) => void;
setSelectedArtwork: (artwork: Artwork | undefined) => void;
applyArtworkToPlacement: (placement: string, artwork?: Artwork) => void;
}
declare const ProductContext: React.Context<ReactProductContext | undefined>;
interface ProductProviderProps {
children: React.ReactNode;
productId?: string;
productData?: CatalogProduct;
endpoint?: string;
source?: string;
fetcher?: typeof fetch;
className?: string;
initialSelection?: OptionSelection;
initialPlacements?: Record<string, PlacementDesign>;
renderLoading?: () => React.ReactNode;
renderError?: (error: any) => React.ReactNode;
}
/**
* Product - Context provider for product data, variant selection, and design management
*
* A composed pattern component that provides React context for product information,
* variant selection, pricing, artwork management, and placement designs. Acts as the
* central state container for product-related components.
*
* Features:
* - Automatic product data fetching from API endpoint
* - Variant selection and combination management
* - Dynamic price calculation based on selected options
* - Design/artwork management with placement-specific designs
* - Integration with Shop context for shared artwork state
* - Loading and error states with custom render props
* - Universal provider sync for cross-framework compatibility
* - SSR-friendly with optional data prop to skip fetching
*
* **Context Provided:**
* - Product data and metadata
* - Option attributes and combinations
* - Current selection and price
* - Artwork collection and selected artwork
* - Placement designs (alignment, tiles, etc.)
* - Methods to update selection, artwork, and placements
*
* **Integration with Shop:**
* - Inherits endpoint from Shop context if available
* - Shares artwork state across all Product instances in a Shop
* - Falls back to local state if used standalone
*
* @example
* ```tsx
* // Basic usage - fetch product by ID
* <Product productId="shirt-123">
* <ProductImage />
* <ProductOptions />
* <AddToCart />
* </Product>
* ```
*
* @example
* ```tsx
* // With initial selection and custom loading
* <Product
* productId="shirt-123"
* initialSelection={{ Size: 'M', Color: 'Blue' }}
* renderLoading={() => <Spinner />}
* renderError={(err) => <ErrorMessage error={err} />}
* >
* <ProductCard />
* </Product>
* ```
*
* @example
* ```tsx
* // Standalone with explicit data (no fetching)
* <Product
* productData={myProductData}
* initialPlacements={{
* Front: { imageUrl: 'logo.png', alignment: 'center' },
* Back: { imageUrl: 'text.png', alignment: 'top' }
* }}
* >
* <ArtAlignment placement="Front" />
* <ProductImage />
* </Product>
* ```
*
* @example
* ```tsx
* // With Shop context
* <Shop endpoint="http://localhost:3000">
* <Product productId="shirt-123">
* <ProductImage />
* </Product>
* <Product productId="mug-456">
* <ProductImage />
* </Product>
* </Shop>
* // Both products share artwork state from Shop
* ```
*
* @param children - Child components that consume product context
* @param productId - Product identifier to fetch
* @param productData - Explicit product data (skips fetching if provided)
* @param endpoint - API endpoint URL (overrides Shop context)
* @param source - Source identifier for data fetching (default: "auto")
* @param fetcher - Custom fetch function (e.g., for auth headers)
* @param className - Additional CSS classes for wrapper div
* @param initialSelection - Pre-select variant options (e.g., { Size: 'M', Color: 'Blue' })
* @param initialPlacements - Pre-configure placement designs
* @param renderLoading - Custom loading UI component
* @param renderError - Custom error UI component
*/
declare function Product({ children, productId, productData, endpoint, source, fetcher, className, initialSelection, initialPlacements, renderLoading, renderError, }: ProductProviderProps): react_jsx_runtime.JSX.Element;
declare function useProduct(): ReactProductContext;
declare function useProductOptional(): ReactProductContext | undefined;
declare function useDesign(): ReactProductContext;
declare function useDesignOptional(): ReactProductContext | undefined;
interface ArtSelectorProps {
className?: string;
artworks: string[] | Artwork[];
}
/**
* ArtSelector - Interactive artwork selection gallery
*
* ⚠️ **IMPORTANT:** This component renders visible UI (a scrollable gallery of thumbnails).
* For custom artwork selection UI, use the `useShop()` hook instead.
*
* @see https://merchify-site-staging.driuqzy.workers.dev/docs/react/components/select-artwork
*
* A composed component that displays a scrollable gallery of artwork thumbnails
* with automatic dimension loading and selection state management. Works with
* both Shop and Product contexts.
*
* **🎯 Primary Purpose:**
* This component sets the `selectedArtwork` in Design context, which is then
* automatically read by components like `<ProductImage>` and `<ArtworkCustomizer>`.
*
* **Features:**
* - Renders a horizontal scrollable gallery of artwork thumbnails
* - Automatic image dimension detection on mount
* - Aspect ratio preservation in thumbnails (60px square)
* - Selection state with visual feedback (brightness filter)
* - Keyboard navigation support (Enter/Space to select)
* - Theme-aware borders using CSS variables
* - Checkerboard background pattern for transparent images
* - Focus visible ring on keyboard navigation
*
* **Context Integration:**
* - Works with `Shop` context for global artwork management
* - Also works with `Product`/`Design` context for product-specific artwork
* - **Automatically updates `selectedArtwork` in context on click**
* - Loads artwork dimensions asynchronously on mount
* - Auto-selects first artwork if none selected
* - Other components like `ProductImage` and `ArtworkCustomizer` read from this context
*
* **Visual Behavior:**
* - Selected artwork: Full brightness with accent border
* - Unselected artwork: 50% brightness, 80% on hover
* - Loading state: Shows "Loading artwork..." text
*
* @example
* ```tsx
* // Basic usage with Shop context
* <Shop>
* <ArtSelector artworks={[
* 'https://example.com/art1.jpg',
* 'https://example.com/art2.jpg',
* 'https://example.com/art3.jpg'
* ]} />
* <Product productId="shirt-123">
* <ProductImage />
* </Product>
* </Shop>
* ```
*
* @example
* ```tsx
* // Within Product context with ArtworkCustomizer
* <Product productId="shirt-123">
* <ArtSelector artworks={[
* { type: 'regular', src: 'https://example.com/photo.jpg' },
* { type: 'pattern', src: 'https://example.com/pattern.jpg', tileCount: 1 }
* ]} />
* <ProductImage />
* <ArtworkCustomizer />
* </Product>
* ```
*
* @example
* ```tsx
* // Custom artwork selector using useShop() hook (recommended for custom UI)
* function MyCustomSelector() {
* const { setSelectedArtwork } = useShop();
*
* const selectArtwork = async (url: string) => {
* const img = new Image();
* img.onload = () => {
* setSelectedArtwork({
* type: 'regular',
* src: url,
* width: img.naturalWidth,
* height: img.naturalHeight,
* aspectRatio: img.naturalWidth / img.naturalHeight,
* });
* };
* img.src = url;
* };
*
* return <button onClick={() => selectArtwork(url)}>Select</button>;
* }
* ```
*
* @param artworks - Array of artwork URLs (strings) or Artwork objects ({ type: 'regular' | 'pattern', src: string, tileCount?: number }). Note: use "artworks" (plural), not "artwork" (singular).
* @param className - Additional CSS classes for the container element
*/
declare function ArtSelector({ className, artworks: customArtworkUrls, }: ArtSelectorProps): react_jsx_runtime.JSX.Element;
interface RealtimeMockupProps {
config: WebSocketConfig;
onMockupRendered?: (mockupUrl: string) => void;
className?: string;
showStatus?: boolean;
showLogs?: boolean;
autoConnect?: boolean;
}
declare const RealtimeMockup: React.FC<RealtimeMockupProps>;
interface LightboxProps {
imageUrl: string;
alt?: string;
onClose: () => void;
className?: string;
}
declare function Lightbox({ imageUrl, alt, onClose, className, }: LightboxProps): react_jsx_runtime.JSX.Element;
/**
* usePlacementsProcessor - Process placement prop to separate colors from images
*
* Takes a placements object where values can be either color codes or image URLs,
* and separates them into distinct arrays/objects for easier processing.
*
* **Color Detection:**
* - Hex colors: `#fff`, `#FF00FF`
* - RGB/RGBA: `rgb(255, 0, 0)`, `rgba(255, 0, 0, 0.5)`
* - HSL/HSLA: `hsl(120, 100%, 50%)`, `hsla(120, 100%, 50%, 0.3)`
*
* @example
* ```tsx
* const { processedImages, colorPlacements } = usePlacementsProcessor({
* Front: 'https://example.com/art.jpg',
* Back: '#FF0000',
* Sleeve: 'rgb(0, 255, 0)'
* }, []);
*
* // Result:
* // processedImages = [{ placement: 'Front', imageUrl: 'https://example.com/art.jpg' }]
* // colorPlacements = { Back: '#FF0000', Sleeve: 'rgb(0, 255, 0)' }
* ```
*
* @param placements - Object mapping placement labels to color codes or image URLs
* @param fallbackImages - Images array to return if no placements provided
* @returns Object with processedImages array and colorPlacements object
*/
declare function usePlacementsProcessor(placements: Record<string, string> | undefined, fallbackImages: DesignElement[]): {
processedImages: DesignElement[];
colorPlacements: Record<string, string> | null;
};
interface ImageUpdateRequest {
id: string;
execute: () => void;
priority?: number;
url?: string;
}
interface ShopContextValue {
endpoint?: string;
artworks: Artwork[];
selectedArtwork?: Artwork;
addArtwork: (artwork: Artwork) => void;
setSelectedArtwork: (artwork: Artwork | undefined) => void;
queueImageUpdate: (request: ImageUpdateRequest) => void;
cancelImageUpdate: (id: string) => void;
getProduct: (productId: string) => CatalogProduct | undefined;
addProduct: (product: CatalogProduct) => void;
addProducts: (products: CatalogProduct[]) => void;
clearProductCache: () => void;
}
declare const ShopContext: React.Context<ShopContextValue | undefined>;
interface ShopProps {
children: ReactNode;
endpoint?: string;
mockupUrl?: string;
accountId?: string;
}
/**
* Shop - Global shop context provider with artwork and image throttling
*
* A pattern component that provides centralized state management for shop-wide
* concerns like artwork selection, product caching, and coordinated image generation
* throttling. Wrap your entire app (or shop section) in this provider.
*
* Features:
* - Centralized artwork state shared across all Product instances
* - Image update queueing with intelligent throttling (5 images/sec)
* - Product data caching for performance
* - LRU URL cache to bypass throttling for repeated requests
* - Per-component throttling (500ms) to prevent rapid updates
* - Priority-based queue processing
* - Automatic SDK configuration
* - Mode/endpoint configuration cascade to child components
*
* **Image Throttling:**
* - Limits mockup generation to 5 images per second (non-cached)
* - Cached URLs bypass throttling entirely
* - Per-component throttle prevents same component from spamming
* - Priority queue ensures important images load first
* - LRU cache (max 100 URLs) remembers recent requests
*
* **Artwork Management:**
* - Centralized artwork collection
* - Shared selected artwork across all products
* - Automatic sync between Product instances
*
* **Product Caching:**
* - In-memory product cache to reduce API calls
* - Add/get/clear cache methods
* - Useful for product lists and galleries
*
* @example
* ```tsx
* // Basic shop setup
* <Shop endpoint="http://localhost:3000">
* <ProductList limit={12}>
* <ProductCard variant="overlay" />
* </ProductList>
* </Shop>
* ```
*
* @example
* ```tsx
* // Multiple products sharing artwork state
* <Shop mockupUrl="https://api.example.com">
* <ArtSelector />
*
* <div class