{"version":3,"file":"ShopWidget-D5w3djLK.mjs","names":[],"sources":["../../widgets/src/widgets/ShopWidget.tsx"],"sourcesContent":["import {\n  useCallback,\n  useEffect,\n  useMemo,\n  useRef,\n  useState,\n  type ComponentProps,\n} from \"react\";\nimport type React from \"react\";\nimport {\n  formatPortalPrice,\n  formatPortalPriceRange,\n  usePortalProductCatalog,\n  type PortalProductPageParam,\n  type portalProducts,\n} from \"@fluid-app/products-core\";\nimport { useInfiniteQuery } from \"@tanstack/react-query\";\nimport {\n  Button,\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuLabel,\n  DropdownMenuRadioGroup,\n  DropdownMenuRadioItem,\n  DropdownMenuSeparator,\n  DropdownMenuTrigger,\n  Badge,\n  Skeleton,\n} from \"@fluid-app/ui-primitives\";\nimport { SearchSort } from \"@fluid-app/ui-components/components/SearchSort\";\nimport { MobileActionSheet } from \"@fluid-app/ui-components/components/MobileActionSheet\";\nimport { useWidgetPreviewContext } from \"@fluid-app/portal-react/data-sources/preview-context\";\nimport { useIsMobile } from \"@fluid-app/portal-react/shell/use-mobile\";\nimport {\n  getVideoThumbnailUrl,\n  isVideoUrl,\n} from \"@fluid-app/shop-ui/utils/media-helpers\";\nimport type {\n  BackgroundValue,\n  BorderRadiusOptions,\n  ColorOptions,\n  PaddingOptions,\n} from \"@fluid-app/portal-core/types\";\nimport type { WidgetPropertySchema } from \"@fluid-app/portal-core/registries\";\nimport { ArrowUpDown, CirclePlay, ImageIcon } from \"lucide-react\";\nimport {\n  getBorderRadiusField,\n  getColorField,\n  getPaddingField,\n} from \"../core/fields\";\nimport { useWidgetInteraction } from \"../contexts/WidgetInteractionContext\";\nimport { useCanShowVolume } from \"@fluid-app/portal-react/hooks/use-can-show-volume\";\n\ntype ColumnsKey = 2 | 3 | 4 | 5 | 6;\n\ntype ShopWidgetProps = ComponentProps<\"div\"> & {\n  titleEnabled?: boolean;\n  title?: string;\n  titleColor?: ColorOptions;\n  showSearch?: boolean;\n  showSort?: boolean;\n  columns?: ColumnsKey;\n  pageSize?: number;\n  useDataSource?: boolean;\n  products?: portalProducts.Product[];\n  background?: BackgroundValue;\n  padding?: PaddingOptions;\n  borderRadius?: BorderRadiusOptions;\n  cardBackground?: ColorOptions;\n  cardTextColor?: ColorOptions;\n};\n\nconst GRID_BY_COLUMNS: Record<ColumnsKey, string> = {\n  2: \"grid grid-cols-2 gap-3 @lg:gap-4\",\n  3: \"grid grid-cols-2 gap-3 @sm:grid-cols-3 @lg:gap-4\",\n  4: \"grid grid-cols-2 gap-3 @sm:grid-cols-3 @lg:grid-cols-4 @lg:gap-4\",\n  5: \"grid grid-cols-2 gap-3 @sm:grid-cols-3 @md:grid-cols-4 @xl:grid-cols-5 @lg:gap-4\",\n  6: \"grid grid-cols-2 gap-3 @sm:grid-cols-3 @md:grid-cols-4 @xl:grid-cols-5 @2xl:grid-cols-6 @lg:gap-4\",\n};\n\nconst SORT_OPTIONS = [\n  { id: \"title_asc\", label: \"Title (A–Z)\" },\n  { id: \"title_desc\", label: \"Title (Z–A)\" },\n  { id: \"price_asc\", label: \"Price (Low to High)\" },\n  { id: \"price_desc\", label: \"Price (High to Low)\" },\n  { id: \"created_at_desc\", label: \"Recently added\" },\n  { id: \"created_at_asc\", label: \"Oldest\" },\n] as const;\n\nconst PREVIEW_PRODUCTS: ReadonlyArray<{\n  id: string;\n  name: string;\n  price: string;\n}> = [\n  { id: \"preview-1\", name: \"Premium Wireless Headphones\", price: \"$249\" },\n  { id: \"preview-2\", name: \"Smart Watch Series X\", price: \"$199\" },\n  { id: \"preview-3\", name: \"Sunglasses\", price: \"$79\" },\n  { id: \"preview-4\", name: \"Trail Running Shoes\", price: \"$129\" },\n  { id: \"preview-5\", name: \"Daypack Backpack\", price: \"$89\" },\n  { id: \"preview-6\", name: \"Stainless Bottle\", price: \"$32\" },\n  { id: \"preview-7\", name: \"Linen Throw\", price: \"$59\" },\n  { id: \"preview-8\", name: \"Ceramic Mug\", price: \"$24\" },\n  { id: \"preview-9\", name: \"Wool Blanket\", price: \"$148\" },\n  { id: \"preview-10\", name: \"Leather Wallet\", price: \"$45\" },\n  { id: \"preview-11\", name: \"Pour-Over Kettle\", price: \"$78\" },\n  { id: \"preview-12\", name: \"Linen Shirt\", price: \"$95\" },\n];\n\nfunction clampColumns(value: number | undefined): ColumnsKey {\n  const n = Math.round(value ?? 4);\n  if (n <= 2) return 2;\n  if (n >= 6) return 6;\n  return n as ColumnsKey;\n}\n\nconst INFINITE_SCROLL_THRESHOLD = 10;\n\nfunction clampPageSize(value: number | undefined): number {\n  const n = Math.round(value ?? 25);\n  return Math.max(1, n);\n}\n\n// Data sources can deliver products in legacy/shareable shape (`title`,\n// `image_url`/`imageUrl`/`thumbnail_url`) or portal shape (`name`, `images`).\n// Modeled as a typed superset so the probe doesn't lose type-safety.\ntype LegacyProductFields = {\n  title?: string;\n  image_url?: string;\n  imageUrl?: string;\n  thumbnail_url?: string;\n};\n\ntype LegacyImageFields = {\n  url?: string;\n  image_url?: string;\n  alt?: string | null;\n};\n\nfunction normalizeDataSourceProduct(\n  input: portalProducts.Product,\n): portalProducts.Product {\n  const r = input as portalProducts.Product & LegacyProductFields;\n  const name = r.name ?? r.title ?? \"\";\n\n  let images: portalProducts.ProductImage[];\n  if (Array.isArray(r.images) && r.images.length > 0) {\n    images = r.images.flatMap<portalProducts.ProductImage>((img) => {\n      const i = img as LegacyImageFields;\n      const url = i.url ?? i.image_url;\n      if (!url) return [];\n      return [{ url, alt: i.alt ?? null }];\n    });\n  } else {\n    const url = r.image_url ?? r.imageUrl ?? r.thumbnail_url;\n    images = url ? [{ url, alt: null }] : [];\n  }\n\n  // Strip undefined props from the spread so EOPT accepts the result.\n  const cleaned: Record<string, unknown> = {};\n  for (const [k, v] of Object.entries(r)) {\n    if (v !== undefined) cleaned[k] = v;\n  }\n  return { ...(cleaned as portalProducts.Product), name, images };\n}\n\nfunction getShopWidgetProductImage(\n  product: portalProducts.Product,\n): string | null {\n  if (product.images && product.images.length > 0) {\n    return product.images[0]?.url ?? null;\n  }\n  return null;\n}\n\nfunction getShopWidgetProductPrice(\n  product: portalProducts.Product,\n): string | null {\n  if (product.is_bundle) {\n    return (\n      formatPortalPriceRange(product.price_range, product.currency) ??\n      formatPortalPrice(product.price, product.currency)\n    );\n  }\n  return formatPortalPrice(\n    product.wholesale_price ?? product.price,\n    product.currency,\n  );\n}\n\nfunction getShopWidgetProductRetailPrice(\n  product: portalProducts.Product,\n): string | null {\n  if (!product.wholesale_price || product.is_bundle) return null;\n\n  const wholesalePrice = formatPortalPrice(\n    product.wholesale_price,\n    product.currency,\n  );\n  const retailPrice = formatPortalPrice(product.price, product.currency);\n\n  return retailPrice !== wholesalePrice ? retailPrice : null;\n}\n\n// Tailwind v4 @theme inline aliases --color-*; remap the underlying --card / --foreground tokens.\nfunction cardScopedStyle(\n  cardBackground: ColorOptions | undefined,\n  cardTextColor: ColorOptions | undefined,\n): React.CSSProperties {\n  const style: Record<string, string> = {};\n  if (cardBackground) style[\"--card\"] = `var(--${cardBackground})`;\n  if (cardTextColor) style[\"--foreground\"] = `var(--${cardTextColor})`;\n  return style as React.CSSProperties;\n}\n\nexport function ShopWidget({\n  titleEnabled = true,\n  title = \"\",\n  titleColor = \"foreground\",\n  showSearch = true,\n  showSort = true,\n  columns = 4,\n  pageSize = 25,\n  useDataSource = false,\n  products,\n\n  background = { type: \"solid\", color: \"background\" },\n  padding = 6,\n  borderRadius = \"lg\",\n\n  cardBackground,\n  cardTextColor,\n\n  className,\n  ...props\n}: ShopWidgetProps): React.JSX.Element {\n  const { isPreview } = useWidgetPreviewContext();\n  const safeColumns = clampColumns(columns);\n  const safePageSize = clampPageSize(pageSize);\n\n  const backgroundColor = background.color ?? \"background\";\n  const backgroundImage =\n    background.type === \"image\" &&\n    (background.resource?.image_url || background.resource?.imageUrl)\n      ? `url(${background.resource.image_url || background.resource.imageUrl})`\n      : \"none\";\n\n  return (\n    <div\n      className={`bg-${backgroundColor} p-${padding} rounded-${borderRadius} @container ${className ?? \"\"}`}\n      style={{ backgroundImage }}\n      {...props}\n    >\n      {isPreview ? (\n        <PreviewBody\n          columns={safeColumns}\n          titleEnabled={titleEnabled}\n          title={title}\n          titleColor={titleColor}\n          showSearch={showSearch}\n          showSort={showSort}\n          cardBackground={cardBackground}\n          cardTextColor={cardTextColor}\n        />\n      ) : (\n        <LiveBody\n          columns={safeColumns}\n          pageSize={safePageSize}\n          titleEnabled={titleEnabled}\n          title={title}\n          titleColor={titleColor}\n          showSearch={showSearch}\n          showSort={showSort}\n          useDataSource={useDataSource}\n          products={products}\n          cardBackground={cardBackground}\n          cardTextColor={cardTextColor}\n        />\n      )}\n    </div>\n  );\n}\n\nfunction PreviewBody({\n  columns,\n  titleEnabled,\n  title,\n  titleColor,\n  showSearch,\n  showSort,\n  cardBackground,\n  cardTextColor,\n}: {\n  columns: ColumnsKey;\n  titleEnabled: boolean;\n  title: string | undefined;\n  titleColor: ColorOptions;\n  showSearch: boolean;\n  showSort: boolean;\n  cardBackground: ColorOptions | undefined;\n  cardTextColor: ColorOptions | undefined;\n}) {\n  const hasTitle = titleEnabled && !!title;\n  const hasHeader = hasTitle || showSearch || showSort;\n  return (\n    <div>\n      {hasHeader && (\n        <div className=\"mb-6 flex flex-col gap-3 sm:flex-row sm:items-center sm:gap-2\">\n          {hasTitle && (\n            <h2\n              className={`text-${titleColor} text-2xl font-semibold tracking-tight`}\n            >\n              {title}\n            </h2>\n          )}\n          {(showSearch || showSort) && (\n            <div className=\"flex items-center gap-2 sm:ml-auto\">\n              {showSearch && (\n                <div\n                  aria-hidden\n                  className=\"bg-muted h-9 w-full max-w-sm rounded-md sm:w-64\"\n                />\n              )}\n              {showSort && (\n                <div aria-hidden className=\"bg-muted size-9 rounded-md\" />\n              )}\n            </div>\n          )}\n        </div>\n      )}\n      <div\n        className={GRID_BY_COLUMNS[columns]}\n        style={cardScopedStyle(cardBackground, cardTextColor)}\n      >\n        {PREVIEW_PRODUCTS.slice(0, columns * 2).map((p) => (\n          <ProductCardCompact\n            key={p.id}\n            name={p.name}\n            imageUrl={null}\n            price={p.price}\n            retailPrice={null}\n            cv={null}\n            qv={null}\n            isBundle={false}\n          />\n        ))}\n      </div>\n    </div>\n  );\n}\n\nfunction LiveBody({\n  useDataSource,\n  products,\n  ...rest\n}: {\n  columns: ColumnsKey;\n  pageSize: number;\n  titleEnabled: boolean;\n  title: string | undefined;\n  titleColor: ColorOptions;\n  showSearch: boolean;\n  showSort: boolean;\n  useDataSource: boolean;\n  products: portalProducts.Product[] | undefined;\n  cardBackground: ColorOptions | undefined;\n  cardTextColor: ColorOptions | undefined;\n}) {\n  // Branch at the component level so the catalog hook (which throws when\n  // PortalProductsCoreProvider is missing) is only called in catalog mode.\n  if (useDataSource) {\n    // DataAwareWidget owns loading state; undefined here just means no data source configured, not \"still loading.\"\n    return (\n      <DataSourceBody\n        columns={rest.columns}\n        titleEnabled={rest.titleEnabled}\n        title={rest.title}\n        titleColor={rest.titleColor}\n        products={products ?? []}\n        cardBackground={rest.cardBackground}\n        cardTextColor={rest.cardTextColor}\n      />\n    );\n  }\n  return <CatalogBody {...rest} />;\n}\n\nfunction DataSourceBody({\n  columns,\n  titleEnabled,\n  title,\n  titleColor,\n  products,\n  cardBackground,\n  cardTextColor,\n}: {\n  columns: ColumnsKey;\n  titleEnabled: boolean;\n  title: string | undefined;\n  titleColor: ColorOptions;\n  products: portalProducts.Product[];\n  cardBackground: ColorOptions | undefined;\n  cardTextColor: ColorOptions | undefined;\n}) {\n  const { onNavigate } = useWidgetInteraction();\n  const handleSelect = useCallback(\n    (id: string | number) => {\n      onNavigate?.(`shop/${id}`);\n    },\n    [onNavigate],\n  );\n\n  const hasTitle = titleEnabled && !!title;\n\n  return (\n    <div>\n      {hasTitle && (\n        <h2\n          className={`text-${titleColor} mb-6 text-2xl font-semibold tracking-tight`}\n        >\n          {title}\n        </h2>\n      )}\n      {products.length === 0 ? (\n        <div className=\"flex flex-col items-center justify-center py-12 text-center\">\n          <p className=\"text-muted-foreground text-sm\">No products yet.</p>\n        </div>\n      ) : (\n        <div\n          className={GRID_BY_COLUMNS[columns]}\n          style={cardScopedStyle(cardBackground, cardTextColor)}\n        >\n          {products.map((product) => {\n            if (product.id == null) return null;\n            // Defensive: older widgets may have a data source that returns\n            // non-product shareables (Medium, EnrollmentPack, Page, Library).\n            // The shop widget only knows how to navigate to product detail,\n            // so skip anything that isn't a Product. Items without\n            // `shareable_type` (static products / untagged feeds) fall through.\n            const shareableType = (product as { shareable_type?: string })\n              .shareable_type;\n            if (shareableType != null && shareableType !== \"Product\")\n              return null;\n            const productId = product.id;\n            const normalizedProduct = normalizeDataSourceProduct(product);\n            return (\n              <ProductCardCompact\n                key={productId}\n                name={normalizedProduct.name ?? \"Product\"}\n                imageUrl={getShopWidgetProductImage(normalizedProduct)}\n                price={getShopWidgetProductPrice(normalizedProduct)}\n                retailPrice={getShopWidgetProductRetailPrice(normalizedProduct)}\n                cv={\n                  normalizedProduct.cv != null\n                    ? Number(normalizedProduct.cv)\n                    : null\n                }\n                qv={\n                  normalizedProduct.qv != null\n                    ? Number(normalizedProduct.qv)\n                    : null\n                }\n                isBundle={normalizedProduct.is_bundle === true}\n                onClick={() => handleSelect(productId)}\n              />\n            );\n          })}\n        </div>\n      )}\n    </div>\n  );\n}\n\nfunction CatalogBody({\n  columns,\n  pageSize,\n  titleEnabled,\n  title,\n  titleColor,\n  showSearch,\n  showSort,\n  cardBackground,\n  cardTextColor,\n}: {\n  columns: ColumnsKey;\n  pageSize: number;\n  titleEnabled: boolean;\n  title: string | undefined;\n  titleColor: ColorOptions;\n  showSearch: boolean;\n  showSort: boolean;\n  cardBackground: ColorOptions | undefined;\n  cardTextColor: ColorOptions | undefined;\n}) {\n  const observerTarget = useRef<HTMLDivElement>(null);\n  const { onNavigate } = useWidgetInteraction();\n  const isMobile = useIsMobile();\n  const [sortOpen, setSortOpen] = useState(false);\n\n  // Cap the API batch in infinite mode so the observer has pages left to fetch.\n  const isInfinite = pageSize > INFINITE_SCROLL_THRESHOLD;\n  const apiPerPage = isInfinite ? INFINITE_SCROLL_THRESHOLD : pageSize;\n\n  const catalog = usePortalProductCatalog({ perPage: apiPerPage });\n  const {\n    data,\n    isLoading,\n    isFetchingNextPage,\n    hasNextPage,\n    fetchNextPage,\n    error,\n    isFetched,\n  } = useInfiniteQuery({\n    queryKey: catalog.queryKey,\n    queryFn: ({ pageParam, signal }) =>\n      catalog.fetchProducts(pageParam, signal),\n    getNextPageParam: catalog.getNextPageParam,\n    initialPageParam: undefined as PortalProductPageParam,\n  });\n\n  const allProducts = useMemo(\n    () => data?.pages.flatMap((page) => page.products) ?? [],\n    [data?.pages],\n  );\n\n  const visibleProducts = useMemo(\n    () => (isInfinite ? allProducts : allProducts.slice(0, pageSize)),\n    [allProducts, pageSize, isInfinite],\n  );\n  const reachedLimit = !isInfinite && visibleProducts.length >= pageSize;\n\n  const handleIntersect = useCallback(\n    (entries: IntersectionObserverEntry[]) => {\n      if (\n        entries[0]?.isIntersecting &&\n        hasNextPage &&\n        !isFetchingNextPage &&\n        !reachedLimit\n      ) {\n        fetchNextPage();\n      }\n    },\n    [hasNextPage, isFetchingNextPage, fetchNextPage, reachedLimit],\n  );\n\n  useEffect(() => {\n    const target = observerTarget.current;\n    if (!target) return;\n    const observer = new IntersectionObserver(handleIntersect, {\n      threshold: 0.1,\n      rootMargin: \"200px\",\n    });\n    observer.observe(target);\n    return () => observer.disconnect();\n  }, [handleIntersect]);\n\n  const handleSelect = useCallback(\n    (id: string | number) => {\n      onNavigate?.(`shop/${id}`);\n    },\n    [onNavigate],\n  );\n\n  const hasTitle = titleEnabled && !!title;\n  const hasHeader = hasTitle || showSearch || showSort;\n\n  return (\n    <div>\n      {hasHeader && (\n        <div className=\"mb-6 flex flex-col gap-3 sm:flex-row sm:items-center sm:gap-2\">\n          {hasTitle && (\n            <h2\n              className={`text-${titleColor} text-2xl font-semibold tracking-tight`}\n            >\n              {title}\n            </h2>\n          )}\n          {(showSearch || showSort) && (\n            <div className=\"flex items-center gap-2 sm:ml-auto\">\n              {showSearch && (\n                <div className=\"w-full max-w-sm sm:w-64\">\n                  <SearchSort\n                    searchValue={catalog.searchTerm}\n                    onSearchChange={catalog.setSearchTerm}\n                    placeholder=\"Search products\"\n                  />\n                </div>\n              )}\n              {showSort &&\n                (isMobile ? (\n                  <>\n                    <Button\n                      variant=\"outline\"\n                      size=\"icon\"\n                      className=\"border-foreground/10 size-9 shrink-0\"\n                      aria-label=\"Sort by\"\n                      onClick={() => setSortOpen(true)}\n                    >\n                      <ArrowUpDown className=\"size-3\" />\n                    </Button>\n                    <MobileActionSheet\n                      open={sortOpen}\n                      onOpenChange={setSortOpen}\n                      title=\"Sort by\"\n                      actions={SORT_OPTIONS.map((option) => ({\n                        id: option.id,\n                        label: option.label,\n                        selected: catalog.currentSort === option.id,\n                        onSelect: () => catalog.setCurrentSort(option.id),\n                      }))}\n                    />\n                  </>\n                ) : (\n                  <DropdownMenu>\n                    <DropdownMenuTrigger asChild>\n                      <Button\n                        variant=\"outline\"\n                        size=\"icon\"\n                        className=\"border-foreground/10 size-9 shrink-0\"\n                      >\n                        <ArrowUpDown className=\"size-3\" />\n                      </Button>\n                    </DropdownMenuTrigger>\n                    <DropdownMenuContent align=\"end\" className=\"w-60\">\n                      <DropdownMenuLabel>Sort by</DropdownMenuLabel>\n                      <DropdownMenuSeparator />\n                      <DropdownMenuRadioGroup\n                        value={catalog.currentSort}\n                        onValueChange={catalog.setCurrentSort}\n                      >\n                        {SORT_OPTIONS.map((opt) => (\n                          <DropdownMenuRadioItem key={opt.id} value={opt.id}>\n                            {opt.label}\n                          </DropdownMenuRadioItem>\n                        ))}\n                      </DropdownMenuRadioGroup>\n                    </DropdownMenuContent>\n                  </DropdownMenu>\n                ))}\n            </div>\n          )}\n        </div>\n      )}\n\n      {isLoading ? (\n        // Floor of 8 keeps mobile (always 2 cols) from rendering a stubby grid.\n        <SkeletonGrid columns={columns} count={Math.max(columns * 2, 8)} />\n      ) : error && visibleProducts.length === 0 ? (\n        <div className=\"bg-destructive/10 text-destructive my-6 rounded-lg px-4 py-3 text-sm\">\n          Couldn&rsquo;t load products. Try again later.\n        </div>\n      ) : isFetched && visibleProducts.length === 0 ? (\n        <div className=\"flex flex-col items-center justify-center py-12 text-center\">\n          <p className=\"text-muted-foreground text-sm\">\n            {catalog.searchTerm\n              ? `No products match “${catalog.searchTerm}”.`\n              : \"No products yet.\"}\n          </p>\n        </div>\n      ) : (\n        <>\n          <div\n            className={GRID_BY_COLUMNS[columns]}\n            style={cardScopedStyle(cardBackground, cardTextColor)}\n          >\n            {visibleProducts.map((product) => {\n              if (product.id == null) return null;\n              const productId = product.id;\n              return (\n                <ProductCardCompact\n                  key={productId}\n                  name={product.name ?? \"Product\"}\n                  imageUrl={getShopWidgetProductImage(product)}\n                  price={getShopWidgetProductPrice(product)}\n                  retailPrice={getShopWidgetProductRetailPrice(product)}\n                  cv={product.cv != null ? Number(product.cv) : null}\n                  qv={product.qv != null ? Number(product.qv) : null}\n                  isBundle={product.is_bundle === true}\n                  onClick={() => handleSelect(productId)}\n                />\n              );\n            })}\n          </div>\n          {!reachedLimit && <div ref={observerTarget} />}\n          {isFetchingNextPage && !reachedLimit && (\n            <div className=\"mt-6\">\n              <SkeletonGrid columns={columns} count={columns} />\n            </div>\n          )}\n          {error && (\n            <div className=\"bg-destructive/10 text-destructive mt-6 rounded-lg px-4 py-3 text-sm\">\n              Couldn&rsquo;t load more products. Try again later.\n            </div>\n          )}\n        </>\n      )}\n    </div>\n  );\n}\n\nfunction ProductCardCompact({\n  name,\n  imageUrl,\n  price,\n  retailPrice,\n  cv,\n  qv,\n  isBundle,\n  onClick,\n}: {\n  name: string;\n  imageUrl: string | null;\n  price: string | null;\n  retailPrice: string | null;\n  cv: number | null;\n  qv: number | null;\n  isBundle: boolean;\n  onClick?: () => void;\n}) {\n  const [imageFailed, setImageFailed] = useState(false);\n  const [isHovered, setIsHovered] = useState(false);\n  const interactive = !!onClick;\n  const isVideo = isVideoUrl(imageUrl);\n  const coverImageUrl =\n    imageUrl && !imageFailed\n      ? isVideo\n        ? getVideoThumbnailUrl(imageUrl)\n        : imageUrl\n      : null;\n  // Value presence used to be the whole rule here, which is how a customer\n  // could see comp-plan figures on a portal shop card. Presence still decides\n  // whether there is anything to render; the gate decides whether they may.\n  const canShowVolume = useCanShowVolume();\n  const showVolume = canShowVolume && (cv != null || qv != null);\n\n  return (\n    <button\n      type=\"button\"\n      onClick={onClick}\n      disabled={!interactive}\n      className={`bg-card text-card-foreground group flex min-w-0 flex-col overflow-hidden rounded-lg text-left ${interactive ? \"cursor-pointer\" : \"cursor-default\"} focus-visible:ring-ring focus-visible:ring-2 focus-visible:outline-none`}\n    >\n      <div className=\"bg-muted/40 relative aspect-square w-full overflow-hidden rounded-md @md:rounded-lg\">\n        {isVideo && imageUrl && isHovered ? (\n          <video\n            src={imageUrl}\n            className=\"absolute inset-0 h-full w-full object-cover\"\n            autoPlay\n            muted\n            loop\n            playsInline\n            onMouseLeave={() => setIsHovered(false)}\n          />\n        ) : coverImageUrl ? (\n          <img\n            src={coverImageUrl}\n            alt={name}\n            className=\"h-full w-full object-cover transition-transform duration-300 group-hover:scale-[1.02]\"\n            loading=\"lazy\"\n            decoding=\"async\"\n            onError={() => setImageFailed(true)}\n            onMouseEnter={() => isVideo && setIsHovered(true)}\n          />\n        ) : (\n          <div className=\"flex h-full w-full items-center justify-center\">\n            <ImageIcon className=\"text-muted-foreground/40 size-10\" />\n          </div>\n        )}\n        {isVideo && !isHovered && coverImageUrl && (\n          <div className=\"absolute inset-0 flex items-center justify-center\">\n            <div className=\"flex size-12 items-center justify-center rounded-full bg-black/50 backdrop-blur-sm\">\n              <CirclePlay className=\"size-8 text-white\" />\n            </div>\n          </div>\n        )}\n        {isBundle && (\n          <Badge variant=\"secondary\" className=\"absolute top-2 left-2\">\n            Bundle\n          </Badge>\n        )}\n      </div>\n      <div className=\"min-w-0 px-0.5 pt-2 pb-0.5 @md:px-1 @md:pt-2.5\">\n        <h3 className=\"text-foreground line-clamp-1 text-xs leading-snug font-semibold @md:text-sm\">\n          {name}\n        </h3>\n        <div className=\"mt-1 flex min-w-0 items-center gap-1.5\">\n          {price && (\n            <span className=\"text-foreground text-sm leading-none font-semibold @md:text-base\">\n              {price}\n            </span>\n          )}\n          {retailPrice && (\n            <span className=\"text-muted-foreground text-xs leading-none line-through @md:text-sm\">\n              {retailPrice}\n            </span>\n          )}\n        </div>\n        {showVolume && (\n          <p className=\"text-muted-foreground mt-1 truncate text-xs leading-tight\">\n            CV {cv ?? \"-\"} | QV {qv ?? \"-\"}\n          </p>\n        )}\n      </div>\n    </button>\n  );\n}\n\nfunction SkeletonGrid({\n  columns,\n  count,\n}: {\n  columns: ColumnsKey;\n  count: number;\n}) {\n  return (\n    <div className={GRID_BY_COLUMNS[columns]}>\n      {Array.from({ length: count }, (_, i) => (\n        <div key={i} className=\"space-y-3\">\n          <Skeleton className=\"aspect-square w-full rounded-lg\" />\n          <Skeleton className=\"h-4 w-3/4\" />\n          <Skeleton className=\"h-4 w-1/3\" />\n        </div>\n      ))}\n    </div>\n  );\n}\n\nexport const shopWidgetPropertySchema: WidgetPropertySchema = {\n  widgetType: \"ShopWidget\",\n  displayName: \"Shop\",\n  tabsConfig: [\n    { id: \"styling\", label: \"Styling\" },\n    { id: \"data\", label: \"Data\" },\n  ],\n  dataSourceTargetProps: [\"products\"],\n  fields: [\n    // Title group\n    {\n      key: \"titleEnabled\",\n      label: \"Widget Title\",\n      type: \"boolean\",\n      description: \"Enable the heading shown above the grid\",\n      defaultValue: true,\n      tab: \"styling\",\n      group: \"Title\",\n    },\n    {\n      key: \"title\",\n      label: \"Title\",\n      type: \"text\",\n      description: \"Heading text shown above the grid\",\n      defaultValue: \"\",\n      tab: \"styling\",\n      group: \"Title\",\n      requiresKeyToBeTrue: \"titleEnabled\",\n    },\n    getColorField({\n      key: \"titleColor\",\n      label: \"Title Color\",\n      description: \"Color of the heading text\",\n      defaultValue: \"foreground\",\n      tab: \"styling\",\n      group: \"Title\",\n      requiresKeyToBeTrue: \"titleEnabled\",\n    }),\n\n    // Display group\n    {\n      key: \"showSearch\",\n      label: \"Show Search\",\n      type: \"boolean\",\n      description: \"Display the search input above the grid\",\n      defaultValue: true,\n      tab: \"styling\",\n      group: \"Display\",\n      requiresKeyValue: { key: \"useDataSource\", value: false },\n    },\n    {\n      key: \"showSort\",\n      label: \"Show Sort\",\n      type: \"boolean\",\n      description: \"Display the sort menu above the grid\",\n      defaultValue: true,\n      tab: \"styling\",\n      group: \"Display\",\n      requiresKeyValue: { key: \"useDataSource\", value: false },\n    },\n    {\n      key: \"columns\",\n      label: \"Columns (desktop)\",\n      type: \"select\",\n      description:\n        \"Max columns at the largest breakpoint. Narrow widgets show 2 columns; wider containers scale up smoothly to the chosen maximum.\",\n      options: [\n        { label: \"2\", value: 2 },\n        { label: \"3\", value: 3 },\n        { label: \"4\", value: 4 },\n        { label: \"5\", value: 5 },\n        { label: \"6\", value: 6 },\n      ],\n      defaultValue: 4,\n      tab: \"styling\",\n      group: \"Display\",\n    },\n\n    // Design group\n    {\n      type: \"background\",\n      key: \"background\",\n      label: \"Background\",\n      description: \"Container background\",\n      defaultValue: { type: \"solid\", color: \"background\" },\n      tab: \"styling\",\n      group: \"Design\",\n    },\n    getPaddingField({\n      key: \"padding\",\n      label: \"Padding\",\n      description: \"Inner spacing around the grid\",\n      defaultValue: 6,\n      tab: \"styling\",\n      group: \"Design\",\n    }),\n    getBorderRadiusField({\n      key: \"borderRadius\",\n      label: \"Border Radius\",\n      description: \"Container corner radius\",\n      defaultValue: \"lg\",\n      tab: \"styling\",\n      group: \"Design\",\n    }),\n\n    // Card group — optional theme overrides; unset means \"inherit from theme\".\n    getColorField({\n      key: \"cardBackground\",\n      label: \"Card Background\",\n      description:\n        \"Override the card background color. Leave unset to use the theme's card color.\",\n      tab: \"styling\",\n      group: \"Card\",\n    }),\n    getColorField({\n      key: \"cardTextColor\",\n      label: \"Card Text\",\n      description:\n        \"Override title + price color inside each card. Leave unset to use the theme's foreground color.\",\n      tab: \"styling\",\n      group: \"Card\",\n    }),\n\n    // Data tab\n    {\n      key: \"useDataSource\",\n      label: \"Use Data Source\",\n      type: \"boolean\",\n      description:\n        \"Off shows all products from the catalog. On replaces the catalog with a data source — disables search and sort.\",\n      defaultValue: false,\n      tab: \"data\",\n      group: \"Data Configuration\",\n    },\n    {\n      key: \"pageSize\",\n      label: \"Products to show\",\n      type: \"number\",\n      description:\n        \"Up to 10 acts as a hard cap. Above 10 the grid switches to infinite scroll (products load in batches of 10 as you scroll).\",\n      defaultValue: 25,\n      tab: \"data\",\n      group: \"Data Configuration\",\n      requiresKeyValue: { key: \"useDataSource\", value: false },\n    },\n    {\n      key: \"dataSource\",\n      label: \"Data Source\",\n      type: \"dataSource\",\n      description: \"Configure the products rendered in the shop grid.\",\n      targetProps: [\n        {\n          key: \"products\",\n          description:\n            \"Products rendered in the shop grid, usually supplied by a data source.\",\n        },\n      ],\n      tab: \"data\",\n      group: \"Data Configuration\",\n      requiresKeyToBeTrue: \"useDataSource\",\n    },\n  ],\n};\n"],"mappings":"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"}