CardProduct

The CardProduct component provides flexible product display with multiple variants (grid, carousel, list, cart, summary) tailored for various site layouts and functionalities.

Component

Storybook

Storybook - CardProduct Documentation

Figma Demo

Guidelines

Variations

CardProduct is offered in multiple variants tailored for various site layouts and functionalities. It includes:

  • Grid: The default variant is the standard grid product card that appears in PLP/marketplace settings with grid view enabled. It includes pricing and cart widgets.
  • Carousel: The carousel variant is intended for “discovery” settings such as carousels. It includes a primary CTA with no pricing or cart widgets.
  • List: The default variant is the standard grid product card that appears in PLP/marketplace settings with list view enabled. It includes pricing and cart widgets.
  • Cart: These cards appear within the main cart view and allow users to expand the card and edit quantity via expand/collapse functionality. They may also be removed from the cart outright.
    • Hidden content: these cards do not show categorization from ProductInfo.
  • CartPreview: These cards appear only within the Task Panel as a “cart preview” for users to see items in their cart before going to the dedicated cart page.
    • Hidden content: these cards hide the categorization from ProductInfo and do not show ProductDetailBadges.
  • Summary: These cards are intended to reflect products already submitted through the cart. They display necessary product details, what was ordered, and the price.
    • Hidden content: these cards do not show stock status as part of ProductDetailBadges.

Usage

Card variants have straightforward naming that makes their usage clear. Be sure to use the right variant for the right context.

  • Default/grid: product card for PLP settings (grid view)

  • List: product card for PLP settings (list view)

  • Carousel: product card for carousels

  • Cart: product card for the cart page

  • Cart Preview: product card for the Task Panel cart preview

  • Summary: product card for post-order submission page

Component Specs

API

  • ad: boolean | Default: false
    Indicates if the product is an ad.
  • categories: string[] | Default: []
  • containerType: string | Default: undefined
    The type of container the product is in.
  • hasDiscount: boolean | Default: false
  • handleBtnClick: function | Default: undefined
    Callback function for the button click event (carousel).
  • handleDiscountClick: function | Default: undefined
  • handlePriceScheduleClick: function | Default: undefined
  • href: string | Default: undefined
    The URL to navigate to when the card is clicked.
  • imageUrl: string | Default: undefined
  • itemsInCart: number | Default: undefined
    The number of items in the cart.
  • materialType: string | Default: undefined
  • packagingDetail: boolean | Default: false
    Indicates if the product has packaging details.
  • priceDetails: object[] | Default: []
  • priceSchedule: string | Default: undefined
    The price schedule of the product.
  • sizeUnitBadges: string[] | Default: []
  • sku: string | Default: undefined
  • stockStatus: string | Default: undefined
    The stock status of the product.
  • testId: string | Default: undefined
  • title: string | Default: undefined
  • variant: string | Default: grid
    The variant of the card.

Example

<script>
	import { CardProduct } from '@getprovi/craft-svelte';

	const priceDetails = [
		{
			sizeUnitType: 'unit',
			sizeUnitLabel: '750ml',
			pricePerOz: '$0.50',
			frontlinePrice: '$10.00',
			hasDiscount: true,
			discountPrice: '$8.00',
			cartWidgetProps: {
				quantity: 1,
				value: 10,
				handleChange: () => console.log('handleChange'),
				handleInput: () => console.log('handleInput'),
				handleMinusClick: () => console.log('handleMinusClick'),
				handlePlusClick: () => console.log('handlePlusClick')
			}
		},
		{
			sizeUnitType: 'case',
			sizeUnitLabel: 'Case of 12',
			pricePerOz: '$0.40',
			frontlinePrice: '$10.00',
			minBottle: true,
			cartWidgetProps: {
				quantity: 1,
				value: 10,
				handleChange: () => console.log('handleChange'),
				handleInput: () => console.log('handleInput'),
				handleMinusClick: () => console.log('handleMinusClick'),
				handlePlusClick: () => console.log('handlePlusClick')
			}
		}
	];
</script>

<CardProduct
	ad
	categories={['Subtype', 'Style']}
	href="#"
	imageUrl=""
	materialType="aluminum"
	{priceDetails}
	sizeUnitBadges={['750ml', '12 per case', '6 pack']}
	sku="123456789"
	stockStatus="in"
	testId="test-card-product"
	title="Product title here may wrap to two lines if needed before it needs to truncate would truncate here"
/>

<CardProduct
	ad
	categories={['Subtype', 'Style']}
	containerType="can"
	hasDiscount
	href="#"
	imageUrl=""
	itemsInCart={undefined}
	materialType="aluminum"
	packagingDetail
	priceSchedule="changing"
	sizeUnitBadges={['750ml', '12 per case', '6 pack']}
	sku="123456789"
	stockStatus="in"
	testId="test-card-product"
	title="Product title here may wrap to two lines if needed before it needs to truncate would truncate here"
	variant="carousel"
	handleBtnClick={() => console.log('Button clicked')}
	handleDiscountClick={() => console.log('Discount clicked')}
	handlePriceScheduleClick={() => console.log('Price schedule clicked')}
/>

<CardProduct
	variant="cart-preview"
	imageUrl=""
	itemsInCart="1 Pack"
	{href}
	{sizeUnitBadges}
	{title}
	{priceDetails}
	totalPrice="$350.28"
	totalDiscountPrice="$330.28"
/>

Types

interface SharedProps {
	ad?: boolean;
	href: string | undefined | null;
	imageUrl: ComponentProps<ProductImage>['src'];
	testId?: string;
	variant?: 'grid' | 'carousel' | 'list' | 'cart' | 'summary';
}

interface Grid extends SharedProps, Omit<GridProps, 'src'> {
	itemsInCart?: never;
	handleBtnClick?: never;
	message?: ProductPriceGroup['message'];
	messageDescription?: ProductPriceGroup['messageDescription'];
	priceDetails?: PriceDetails[];
}

interface Carousel extends SharedProps, Omit<CarouselProps, 'src'> {
	message?: never;
	messageDescription?: never;
	priceDetails?: never;
	itemsInCart?: string;
	handleBtnClick: () => void;
}

interface List extends SharedProps, Omit<ListProps, 'src'> {
	variant: 'list';
	itemsInCart?: never;
	handleBtnClick?: never;
	handleRemoveClick?: never;
	removeBtnType?: never;
	totalPrice?: never;
	totalDiscountPrice?: never;
}

interface Cart extends SharedProps, Omit<CartProps, 'src'> {
	variant: 'cart';
	ad?: never;
	categories?: never;
	message?: never;
	messageDescription?: never;
	hasDiscount?: never;
	priceSchedule?: never;
	handleBtnClick?: never;
	handleDiscountClick?: never;
	handlePriceScheduleClick?: never;
}

interface CartPreview extends SharedProps, Omit<CartPreviewProps, 'src'> {
	variant: 'cart-preview';
	ad?: never;
	categories?: never;
	containerType?: never;
	materialType?: never;
	sku?: never;
	stockStatus?: never;
	packagingDetail?: never;
	message?: never;
	messageDescription?: never;
	hasDiscount?: never;
	priceSchedule?: never;
	handleBtnClick?: never;
	handleDiscountClick?: never;
	handlePriceScheduleClick?: never;
}

interface Summary extends SharedProps, Omit<SummaryProps, 'src'> {
	variant: 'summary';
	ad?: never;
	categories?: never;
	message?: never;
	messageDescription?: never;
	hasDiscount?: never;
	priceSchedule?: never;
	handleBtnClick?: never;
	handleDiscountClick?: never;
	handlePriceScheduleClick?: never;
	handleRemoveClick?: never;
	removeBtnType?: never;
	stockStatus?: never;
}

type Variant = Grid | Carousel | List | Cart | CartPreview | Summary;