Details
The product's drawing and what the catalogue holds for it — SKU, category, supplier, reorder point and status — with how many orders hold it and when it first sold; reads productRecord(id).
Preview
import { PRODUCT_STATUS_LABELS, PRODUCT_STATUS_MAP } from "@/lib/dashboards/ecommerce/vocabulary"
import { formatDate, formatNumber } from "@/lib/format"
import { type Product } from "@/lib/sample-data"
import { DescriptionList } from "@/components/ui/description-list"
import { ProductArt } from "@/components/ui/product-art"
import { StatusBadge } from "@/components/ui/status-badge"
import { Widget } from "@/components/ui/widget"
export type ProductDetailsProps = {
product: Product
orderCount: number
/** When the oldest sale of it was placed; absent for a product nobody has bought and kept. */
firstSold?: Date
}
/**
* The product's drawing, the facts the catalogue row holds, and two the order
* book adds to them: how many orders hold it, and when it first sold.
*/
export function ProductDetails({ product, orderCount, firstSold }: ProductDetailsProps) {
return (
<Widget data-widget="widget-ecommerce-product-product-details" title="Details" description="What the catalogue holds for it" className="h-full">
{/* The drawing heads the facts in a narrow card and stands beside them in a wide one. */}
<div className="@container/details">
<div className="flex flex-col gap-4 @lg/details:flex-row @lg/details:items-start @lg/details:gap-6">
<ProductArt
name={product.name}
category={product.category}
className="w-20 shrink-0 rounded-md @lg/details:w-32"
/>
<DescriptionList
className="min-w-0 flex-1"
items={[
{ term: "SKU", description: <span className="font-mono text-xs">{product.sku}</span> },
{ term: "Category", description: product.category },
{ term: "Supplier", description: product.supplier },
{
term: "Reorder point",
description: <span className="tabular-nums">{formatNumber(product.reorderPoint)} units</span>,
},
{
term: "Status",
description: (
<StatusBadge
status={product.status}
map={PRODUCT_STATUS_MAP}
label={PRODUCT_STATUS_LABELS[product.status]}
/>
),
},
{ term: "Orders", description: <span className="tabular-nums">{formatNumber(orderCount)}</span> },
{
term: "First sold",
description: firstSold ? formatDate(firstSold, "medium", { timeZone: "UTC" }) : "Not yet",
},
]}
/>
</div>
</div>
</Widget>
)
}Install
$
npx shadcn@latest add @vibra/widget-ecommerce-product-product-detailsNeeds the @vibra registry in your components.json — set it up once.
Source
import { PRODUCT_STATUS_LABELS, PRODUCT_STATUS_MAP } from "@/lib/dashboards/ecommerce/vocabulary"
import { formatDate, formatNumber } from "@/lib/format"
import { type Product } from "@/lib/sample-data"
import { DescriptionList } from "@/components/ui/description-list"
import { ProductArt } from "@/components/ui/product-art"
import { StatusBadge } from "@/components/ui/status-badge"
import { Widget } from "@/components/ui/widget"
export type ProductDetailsProps = {
product: Product
orderCount: number
/** When the oldest sale of it was placed; absent for a product nobody has bought and kept. */
firstSold?: Date
}
/**
* The product's drawing, the facts the catalogue row holds, and two the order
* book adds to them: how many orders hold it, and when it first sold.
*/
export function ProductDetails({ product, orderCount, firstSold }: ProductDetailsProps) {
return (
<Widget data-widget="widget-ecommerce-product-product-details" title="Details" description="What the catalogue holds for it" className="h-full">
{/* The drawing heads the facts in a narrow card and stands beside them in a wide one. */}
<div className="@container/details">
<div className="flex flex-col gap-4 @lg/details:flex-row @lg/details:items-start @lg/details:gap-6">
<ProductArt
name={product.name}
category={product.category}
className="w-20 shrink-0 rounded-md @lg/details:w-32"
/>
<DescriptionList
className="min-w-0 flex-1"
items={[
{ term: "SKU", description: <span className="font-mono text-xs">{product.sku}</span> },
{ term: "Category", description: product.category },
{ term: "Supplier", description: product.supplier },
{
term: "Reorder point",
description: <span className="tabular-nums">{formatNumber(product.reorderPoint)} units</span>,
},
{
term: "Status",
description: (
<StatusBadge
status={product.status}
map={PRODUCT_STATUS_MAP}
label={PRODUCT_STATUS_LABELS[product.status]}
/>
),
},
{ term: "Orders", description: <span className="tabular-nums">{formatNumber(orderCount)}</span> },
{
term: "First sold",
description: firstSold ? formatDate(firstSold, "medium", { timeZone: "UTC" }) : "Not yet",
},
]}
/>
</div>
</div>
</Widget>
)
}Its page
On its page the card sits among the rest of the dashboard and shares its range and its data with them.
From the Product record page