import type { ReactNode } from "react"; import type { OptionAxis, PriceFormat, ProductBadge, ProductMedia, ProductVariant, VariantSelection } from "../Commerce/commerceTypes"; import type { DetailSectionsProps } from "../DetailSections/DetailSectionsProps"; import type { Offer, OffersListProps } from "../OffersList/OffersListProps"; import type { PincodeCheckerProps } from "../PincodeChecker/PincodeCheckerProps"; import type { RatingBreakdown } from "../ReviewSummary/ReviewSummaryProps"; import type { ReviewItem, ReviewListProps } from "../ReviewList/ReviewListProps"; import type { SpecItem } from "../SpecTable/SpecTableProps"; import type { TrustItem } from "../TrustBadges/TrustBadgesProps"; /** The catalogue record the page renders; structurally compatible with a store's product so it can be passed straight through. */ export interface ProductDetailsProduct { /** Page h1 and the accessible name of the quantity stepper and sticky bar. */ title: string; /** Muted line above the title. */ brand?: string; /** Body of the Description section. */ description?: string; /** Bullet list for the Highlights section. */ highlights?: string[]; /** Rows of the Specifications section. */ specs?: SpecItem[]; /** Every frame the product has; variants and `mediaIdsByOption` pick which ones show. */ media: ProductMedia[]; /** Option axes in display order; an empty list means the product has nothing to choose. */ axes: OptionAxis[]; /** Every sellable combination with its own price and stock; empty falls back to one variant at `price`. */ variants: ProductVariant[]; /** Price shown until a variant is picked, and the fallback when a variant carries none. */ price: number; /** List price struck through beside `price`; a picked variant's own `mrp` replaces it. */ mrp?: number; /** Average rating, 0 to 5. */ rating?: number; /** Number of ratings beside the stars; also keeps the reviews section when `reviews` is omitted. */ ratingCount?: number; /** Corner badges on the gallery, e.g. "Bestseller". */ badges?: ProductBadge[]; /** Base unit for the "per 100 g" line; needs a `pack` axis whose values carry `quantity`. */ unit?: { unit: string; per: number; }; /** Frame ids per picked option, keyed "axisId:valueId"; a variant's own `mediaIds` wins. */ mediaIdsByOption?: Record; /** Shipping and returns copy for the details section. */ returnsNote?: string; /** Country of origin line in the legal block of the details section. */ originCountry?: string; /** Manufacturer line in the legal block of the details section. */ manufacturer?: string; /** Legal-metrology net quantity such as "500 g". */ netQuantity?: string; } /** `auto` follows the component's own width (two columns from 960px, else the single-column phone page with a sticky buy bar); the window decides only until the first measure. */ export type ProductDetailsLayout = "auto" | "desktop" | "mobile"; /** Every string the page itself renders; sub-components take their own `labels` through their prop pass-throughs. */ export interface ProductDetailsLabels { /** Primary button text when the chosen variant can be bought. */ addToBag: string; /** Text of the secondary button shown when `onBuyNow` is set. */ buyNow: string; /** Primary button text when the chosen variant, or every variant, is sold out. */ notifyMe: string; /** Primary button text after the add confirmation, when `onGoToBag` is set. */ goToBag: string; /** Short confirmation in the button right after an add. */ added: string; /** Screen-reader announcement after an add. */ addedAnnouncement: string; /** Shown when `onAddToBag` rejects. */ addFailed: string; /** Prompt for an unpicked axis, given its lower-cased label: "Select a size". */ selectPrompt: (axisLabel: string) => string; /** Line under the price, e.g. "Inclusive of all taxes". */ taxNote: string; /** Accessible name of the rating link that scrolls to the reviews. */ jumpToReviews: (ratingText: string) => string; /** Accessible name of the share button; shown only when `onShare` is set. */ share: string; /** Label of the wishlist toggle. */ wishlistSave: string; } /** The composed product page: gallery, buy panel, details and reviews, wired so the picked variant drives price, stock, frames and the primary action. */ export interface ProductDetailsProps extends PriceFormat { /** Catalogue record to render; changing it re-derives variants, frames and price. */ product: ProductDetailsProduct; /** Controlled selection, axis id to option value id; nothing is preselected, because a size the shopper never chose is a wrong order. */ selection?: VariantSelection; /** Starting selection when uncontrolled, axis id to option value id. */ defaultSelection?: VariantSelection; /** Receives the new selection and the matching variant, or undefined while it is incomplete. */ onSelectionChange?: (selection: VariantSelection, variant: ProductVariant | undefined) => void; /** Controlled quantity, clamped to the variant's remaining stock and `maxQuantity`. */ quantity?: number; /** Starting quantity when uncontrolled; defaults to 1 and is clamped to the ceiling. */ defaultQuantity?: number; /** Fires with the new quantity after the stepper changes it. */ onQuantityChange?: (quantity: number) => void; /** Per-order ceiling; the variant's remaining stock lowers it further. */ maxQuantity?: number; /** Fires with the variant id and quantity; a returned promise keeps the button busy, and a rejection shows `labels.addFailed`. */ onAddToBag?: (variantId: string, quantity: number) => void | Promise; /** Adds a secondary "Buy now" beside the primary; incomplete selections get the same prompt as Add to bag. */ onBuyNow?: (variantId: string, quantity: number) => void; /** Fires from "Notify me" with the variant id, or undefined when every variant is sold out. */ onNotify?: (variantId: string | undefined) => void; /** When set, a successful add morphs the button to "Added" and then to "Go to bag", which calls this. */ onGoToBag?: () => void; /** How long "Added" shows before the button becomes "Go to bag", in ms. */ addedDuration?: number; /** Controlled wishlist state; pair with `onWishlistChange`. */ wishlisted?: boolean; /** Starting wishlist state when uncontrolled. */ defaultWishlisted?: boolean; /** Fires with the new wishlist state when the toggle is pressed. */ onWishlistChange?: (wishlisted: boolean) => void; /** Shows a share button beside the title. */ onShare?: () => void; /** Shown as one collapsed row under the price. */ offers?: Offer[]; /** Pass-through to `OffersList`, e.g. `onCopy`. */ offersProps?: Partial; /** Reassurance tiles under the delivery check. */ trustItems?: TrustItem[]; /** Shows the delivery checker when given; pass-through to `PincodeChecker`. */ pincode?: PincodeCheckerProps; /** Adds a size-guide link to the axis with `axisId` (default "size") unless that axis already has a `guide`. */ sizeGuide?: { label?: string; axisId?: string; onClick: () => void; }; /** Also fired when any axis guide link is clicked, with the axis id. */ onGuideClick?: (axisId: string) => void; /** Reviews to list; omit both this and `product.ratingCount` to leave the reviews section out. */ reviews?: ReviewItem[]; /** Counts per star for the distribution bars. */ ratingBreakdown?: RatingBreakdown; /** Pass-through to `ReviewList`, e.g. `sortSlot`, `hasMore`, `onLoadMore`, `onHelpful`. */ reviewsProps?: Partial; /** Shows "Write a review" in the summary. */ onWriteReview?: () => void; /** Pass-through to `DetailSections`; `sections` there adds or replaces a section. */ detailSectionsProps?: Partial; /** Care instructions for the details section. */ care?: string | string[]; /** Rendered above the gallery, e.g. a breadcrumb trail. */ breadcrumbs?: ReactNode; /** `auto` splits into gallery and buy panel from 960px of the component's own width, not the window. */ layout?: ProductDetailsLayout; /** Height of the add-to-bag, buy-now and quantity controls; defaults to `md` (40px) on the split layout, `lg` (48px) on the phone page. */ size?: "md" | "lg"; /** Replaces the gallery; its frames, badges and wishlist toggle go with it. */ mediaSlot?: ReactNode; /** Extra content in the buy panel under the price, e.g. an EMI line. */ metaSlot?: ReactNode; /** Extra content at the end of the buy panel. */ infoSlot?: ReactNode; /** Extra content after the reviews. */ belowFold?: ReactNode; /** Rendered last, e.g. a "You may also like" rail. */ relatedSlot?: ReactNode; /** Skeleton in place of the gallery and buy panel. */ isLoading?: boolean; /** Overrides for the page copy; unspecified strings keep the English defaults. */ labels?: Partial; /** Set on the page root, which also carries `data-layout` of `desktop` or `mobile`. */ "data-testid"?: string; }