@pagamio/frontend-commons-lib 0.8.336 → 0.8.338

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -18,4 +18,4 @@ export interface ProductCardProps {
18
18
  }) => React.ReactNode;
19
19
  onNavigate?: (productUrl: string) => void;
20
20
  }
21
- export declare function ProductCard({ product, onAddToCart, isAddingToCart, storeId, basePath, featured, linkSuffix, currency, renderLink, onNavigate, }: ProductCardProps): import("react/jsx-runtime").JSX.Element;
21
+ export declare function ProductCard({ product, onAddToCart, isAddingToCart, basePath, featured, linkSuffix, currency, renderLink, onNavigate, }: ProductCardProps): import("react/jsx-runtime").JSX.Element;
@@ -1,17 +1,20 @@
1
1
  import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { HiOutlineShoppingCart, HiOutlineViewGrid } from 'react-icons/hi';
2
+ import { HiOutlineAdjustments, HiOutlineShoppingCart } from 'react-icons/hi';
3
3
  import { cn } from '../../helpers/utils';
4
4
  import { formatPrice } from '../../helpers/utils';
5
5
  import ImageComponent from '../ui/ImageComponent';
6
6
  const LOW_STOCK_THRESHOLD = 5;
7
- export function ProductCard({ product, onAddToCart, isAddingToCart, storeId, basePath = '/shop', featured = false, linkSuffix, currency = 'ZAR', renderLink, onNavigate, }) {
7
+ export function ProductCard({ product, onAddToCart, isAddingToCart, basePath = '/shop', featured = false, linkSuffix, currency = 'ZAR', renderLink, onNavigate, }) {
8
8
  // Non-stock products (services / fees / digital goods) have no
9
9
  // StockItem rows by design — they're always available
10
10
  const isNonStock = product.isStockItem === false;
11
11
  const hasStock = isNonStock || product.totalStock > 0;
12
12
  const isLowStock = !isNonStock && hasStock && product.totalStock <= LOW_STOCK_THRESHOLD;
13
13
  const isBackorderable = product.allowBackorders === true;
14
- const canAddToCart = (hasStock || isBackorderable) && !!storeId;
14
+ // `storeId` is informational only the backend auto-picks a fulfillment
15
+ // store when one isn't explicitly supplied. The CTA stays enabled as
16
+ // long as the product has stock (or is back-orderable).
17
+ const canAddToCart = hasStock || isBackorderable;
15
18
  const hasVariants = product.variants && product.variants.length > 0;
16
19
  const priceDisplay = (() => {
17
20
  if (!hasVariants) {
@@ -39,7 +42,19 @@ export function ProductCard({ product, onAddToCart, isAddingToCart, storeId, bas
39
42
  }
40
43
  return (_jsx("a", { href: productUrl, className: className, tabIndex: tabIndex, "aria-label": ariaLabel, children: children }));
41
44
  };
42
- return (_jsxs("div", { className: cn('group relative flex flex-col overflow-hidden bg-background transition-shadow duration-200', 'rounded-lg sm:rounded-xl', 'hover:shadow-lg', featured ? 'border-2 border-foreground shadow-md' : 'border border-border shadow-sm'), children: [_jsxs(LinkWrapper, { className: "relative block aspect-[4/3] w-full overflow-hidden bg-muted", tabIndex: -1, ariaLabel: `View ${product.name}`, children: [product.imageUrl ? (_jsx(ImageComponent, { src: product.imageUrl, alt: product.name, fill: true, className: "object-cover", sizes: "(max-width: 640px) 50vw, (max-width: 1024px) 33vw, 25vw" })) : (_jsx("div", { className: "flex h-full w-full items-center justify-center bg-muted text-muted-foreground", children: _jsx("svg", { className: "h-8 w-8", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 1, d: "M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" }) }) })), !hasStock && !isBackorderable && (_jsx("div", { className: "absolute inset-0 flex items-center justify-center bg-black/40", children: _jsx("span", { className: "rounded-full bg-white/90 px-3 py-1 text-[11px] font-bold uppercase tracking-wide text-foreground sm:px-4 sm:py-1.5 sm:text-xs", children: "Out of Stock" }) })), !hasStock && isBackorderable && (_jsx("div", { className: "absolute left-2 top-2 z-10 rounded-full bg-warning px-2 py-0.5 text-[10px] font-bold uppercase tracking-wide text-white sm:left-3 sm:top-3 sm:px-2.5 sm:py-1", children: "Backorder" })), featured && hasStock && (_jsx("div", { className: "absolute left-2 top-2 z-10 rounded-full bg-foreground px-2 py-0.5 text-[10px] font-bold uppercase tracking-wide text-white sm:left-3 sm:top-3 sm:px-2.5 sm:py-1", children: "Popular" }))] }), _jsxs("div", { className: "flex flex-1 flex-col p-1.5 sm:p-2", children: [_jsx(LinkWrapper, { children: _jsx("h3", { className: "line-clamp-2 text-xs font-medium leading-snug text-foreground transition-colors hover:text-primary", children: product.name }) }), _jsx("div", { className: "flex-1" }), _jsxs("div", { className: "mt-1", children: [_jsxs("div", { className: "flex items-baseline justify-between gap-1", children: [_jsx("span", { className: "text-xs font-bold text-foreground sm:text-sm", children: priceDisplay }), hasVariants && (_jsxs("span", { className: "text-[10px] font-medium text-muted-foreground", children: [product.variants.length, " opt"] }))] }), isLowStock && _jsxs("p", { className: "mt-0.5 text-[10px] font-medium text-warning", children: ["Only ", product.totalStock, " left"] })] }), _jsx("button", { type: "button", disabled: hasVariants ? !hasStock && !isBackorderable : !canAddToCart || isAddingToCart, onClick: (e) => {
45
+ const ctaDisabled = hasVariants ? !hasStock && !isBackorderable : !canAddToCart || !!isAddingToCart;
46
+ const ctaLabel = hasVariants
47
+ ? !hasStock && !isBackorderable
48
+ ? 'Out of Stock'
49
+ : 'Options'
50
+ : isAddingToCart
51
+ ? 'Adding…'
52
+ : hasStock
53
+ ? 'Add to cart'
54
+ : isBackorderable
55
+ ? 'Backorder'
56
+ : 'Sold Out';
57
+ return (_jsxs("article", { className: cn('group flex h-full flex-col overflow-hidden bg-background', 'rounded-lg border', 'hover:shadow-md', featured ? 'border-foreground shadow-md' : 'border-border'), children: [_jsxs(LinkWrapper, { className: "relative block aspect-square w-full overflow-hidden bg-muted", tabIndex: -1, ariaLabel: `View ${product.name}`, children: [product.imageUrl ? (_jsx(ImageComponent, { src: product.imageUrl, alt: product.name, fill: true, className: "object-cover", sizes: "(max-width: 640px) 50vw, (max-width: 1024px) 33vw, 25vw" })) : (_jsx("div", { className: "flex h-full w-full items-center justify-center bg-muted text-muted-foreground", children: _jsx("svg", { className: "h-8 w-8", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 1, d: "M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" }) }) })), featured && hasStock && (_jsx("span", { className: "absolute left-2 top-2 z-10 rounded-md bg-foreground px-2 py-0.5 text-[10px] font-bold uppercase tracking-wide text-background", children: "Popular" })), !hasStock && isBackorderable && (_jsx("span", { className: "absolute left-2 top-2 z-10 rounded-md bg-warning px-2 py-0.5 text-[10px] font-bold uppercase tracking-wide text-white", children: "Backorder" })), !hasStock && !isBackorderable && (_jsx("span", { className: "absolute inset-0 flex items-center justify-center bg-black/40 text-[11px] font-bold uppercase tracking-wide text-white", children: "Out of stock" }))] }), _jsxs("section", { className: "flex flex-1 flex-col gap-1 p-3", children: [_jsxs(LinkWrapper, { className: "contents", children: [_jsx("span", { className: "text-base font-bold leading-none text-foreground", children: priceDisplay }), _jsx("span", { className: "line-clamp-2 min-h-[2.5rem] text-sm leading-snug text-foreground transition-colors group-hover:text-primary", children: product.name })] }), isLowStock && _jsxs("span", { className: "text-[10px] font-medium text-warning", children: ["Only ", product.totalStock, " left"] }), _jsxs("button", { type: "button", disabled: ctaDisabled, onClick: (e) => {
43
58
  e.preventDefault();
44
59
  e.stopPropagation();
45
60
  if (hasVariants) {
@@ -53,13 +68,9 @@ export function ProductCard({ product, onAddToCart, isAddingToCart, storeId, bas
53
68
  else if (onAddToCart && canAddToCart) {
54
69
  onAddToCart(product);
55
70
  }
56
- }, className: cn('mt-1.5 flex h-7 w-full items-center justify-center gap-1 rounded-md text-[11px] font-medium transition-colors sm:h-8', hasVariants
57
- ? hasStock || isBackorderable
58
- ? 'bg-foreground text-background hover:bg-foreground/90 active:bg-foreground/80'
59
- : 'cursor-not-allowed bg-muted text-muted-foreground'
60
- : canAddToCart && !isAddingToCart
61
- ? isBackorderable && !hasStock
62
- ? 'bg-warning text-white hover:bg-warning/90'
63
- : 'bg-foreground text-background hover:bg-foreground/90 active:bg-foreground/80'
64
- : 'cursor-not-allowed bg-muted text-muted-foreground'), children: hasVariants ? (_jsxs(_Fragment, { children: [_jsx(HiOutlineViewGrid, { className: "h-3.5 w-3.5" }), _jsx("span", { children: !hasStock && !isBackorderable ? 'Out of Stock' : 'Options' })] })) : isAddingToCart ? (_jsxs(_Fragment, { children: [_jsx("span", { className: "h-3.5 w-3.5 animate-spin rounded-full border-2 border-background/30 border-t-background" }), _jsx("span", { children: "Adding..." })] })) : (_jsxs(_Fragment, { children: [_jsx(HiOutlineShoppingCart, { className: "h-3.5 w-3.5" }), _jsx("span", { children: !storeId ? 'Select Store' : hasStock ? 'Add' : isBackorderable ? 'Backorder' : 'Sold Out' })] })) })] })] }));
71
+ }, title: hasVariants ? `Choose · ${product.variants.length} options` : undefined, className: cn('mt-auto inline-flex w-full items-center justify-center gap-1 whitespace-nowrap rounded-md px-2 py-1.5 text-xs font-semibold transition-colors', 'disabled:cursor-not-allowed disabled:opacity-50', hasVariants
72
+ ? 'border border-primary text-primary hover:bg-primary hover:text-primary-foreground'
73
+ : isBackorderable && !hasStock
74
+ ? 'bg-warning text-white hover:bg-warning/90'
75
+ : 'bg-primary text-primary-foreground hover:bg-primary/90'), children: [isAddingToCart ? (_jsx("span", { className: "h-3.5 w-3.5 shrink-0 animate-spin rounded-full border-2 border-current/30 border-t-current" })) : hasVariants ? (_jsx(HiOutlineAdjustments, { className: "h-3.5 w-3.5 shrink-0" })) : (_jsx(HiOutlineShoppingCart, { className: "h-3.5 w-3.5 shrink-0" })), _jsx("span", { className: "truncate", children: ctaLabel })] })] })] }));
65
76
  }
@@ -2,7 +2,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import EmptyState from '../ui/EmptyState';
3
3
  import { ProductCard } from './ProductCard';
4
4
  import { ProductCardSkeleton } from './ProductCardSkeleton';
5
- const GRID_CLASSES = 'grid grid-cols-2 gap-1.5 sm:grid-cols-4 sm:gap-2 md:grid-cols-5 lg:grid-cols-7 xl:grid-cols-8 2xl:grid-cols-10';
5
+ const GRID_CLASSES = 'grid grid-cols-2 gap-3 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-4 xl:grid-cols-5';
6
6
  export function ProductGrid({ products, isLoading, onAddToCart, addingProductId, storeId, emptyMessage = 'No products found', basePath, linkSuffix, skeletonCount = 24, currency, renderLink, onNavigate, }) {
7
7
  if (isLoading) {
8
8
  return (_jsx("div", { className: GRID_CLASSES, children: Array.from({ length: skeletonCount }, (_, i) => (_jsx(ProductCardSkeleton, {}, i))) }));
@@ -31,7 +31,7 @@ export type CardProps = React.HTMLAttributes<HTMLDivElement | HTMLAnchorElement>
31
31
  /** Renders the card as a clickable <a> element */
32
32
  href?: string;
33
33
  };
34
- export declare const Card: React.ForwardRefExoticComponent<CardProps & React.RefAttributes<HTMLDivElement | HTMLAnchorElement>>;
34
+ export declare const Card: React.ForwardRefExoticComponent<CardProps & React.RefAttributes<HTMLAnchorElement | HTMLDivElement>>;
35
35
  export declare const CardHeader: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLDivElement> & React.RefAttributes<HTMLDivElement>>;
36
36
  export declare const CardTitle: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLHeadingElement> & React.RefAttributes<HTMLHeadingElement>>;
37
37
  export declare const CardDescription: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLParagraphElement> & React.RefAttributes<HTMLParagraphElement>>;
package/lib/styles.css CHANGED
@@ -1142,6 +1142,9 @@ video {
1142
1142
  .grid {
1143
1143
  display: grid;
1144
1144
  }
1145
+ .contents {
1146
+ display: contents;
1147
+ }
1145
1148
  .hidden {
1146
1149
  display: none;
1147
1150
  }
@@ -1315,6 +1318,9 @@ video {
1315
1318
  .min-h-9 {
1316
1319
  min-height: 2.25rem;
1317
1320
  }
1321
+ .min-h-\[2\.5rem\] {
1322
+ min-height: 2.5rem;
1323
+ }
1318
1324
  .min-h-\[200px\] {
1319
1325
  min-height: 200px;
1320
1326
  }
@@ -2300,6 +2306,9 @@ video {
2300
2306
  .border-t-background {
2301
2307
  border-top-color: hsl(var(--background));
2302
2308
  }
2309
+ .border-t-current {
2310
+ border-top-color: currentColor;
2311
+ }
2303
2312
  .\!bg-transparent {
2304
2313
  background-color: transparent !important;
2305
2314
  }
@@ -4443,9 +4452,6 @@ video {
4443
4452
  --tw-text-opacity: 1;
4444
4453
  color: rgb(17 24 39 / var(--tw-text-opacity, 1));
4445
4454
  }
4446
- .hover\:text-primary:hover {
4447
- color: hsl(var(--primary));
4448
- }
4449
4455
  .hover\:text-primary-700:hover {
4450
4456
  --tw-text-opacity: 1;
4451
4457
  color: rgb(23 14 49 / var(--tw-text-opacity, 1));
@@ -4817,9 +4823,6 @@ video {
4817
4823
  --tw-scale-y: .95;
4818
4824
  transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
4819
4825
  }
4820
- .active\:bg-foreground\/80:active {
4821
- background-color: hsl(var(--foreground) / 0.8);
4822
- }
4823
4826
  .enabled\:hover\:bg-gray-100:hover:enabled {
4824
4827
  --tw-bg-opacity: 1;
4825
4828
  background-color: rgb(243 244 246 / var(--tw-bg-opacity, 1));
@@ -4875,6 +4878,9 @@ video {
4875
4878
  --tw-text-opacity: 1;
4876
4879
  color: rgb(17 24 39 / var(--tw-text-opacity, 1));
4877
4880
  }
4881
+ .group:hover .group-hover\:text-primary {
4882
+ color: hsl(var(--primary));
4883
+ }
4878
4884
  .group:focus .group-focus\:outline-none {
4879
4885
  outline: 2px solid transparent;
4880
4886
  outline-offset: 2px;
@@ -6461,8 +6467,8 @@ video {
6461
6467
  grid-template-columns: repeat(2, minmax(0, 1fr));
6462
6468
  }
6463
6469
 
6464
- .sm\:grid-cols-4 {
6465
- grid-template-columns: repeat(4, minmax(0, 1fr));
6470
+ .sm\:grid-cols-3 {
6471
+ grid-template-columns: repeat(3, minmax(0, 1fr));
6466
6472
  }
6467
6473
 
6468
6474
  .sm\:flex-row {
@@ -6567,11 +6573,6 @@ video {
6567
6573
  padding-bottom: 0.25rem;
6568
6574
  }
6569
6575
 
6570
- .sm\:py-1\.5 {
6571
- padding-top: 0.375rem;
6572
- padding-bottom: 0.375rem;
6573
- }
6574
-
6575
6576
  .sm\:py-3 {
6576
6577
  padding-top: 0.75rem;
6577
6578
  padding-bottom: 0.75rem;
@@ -6736,8 +6737,8 @@ video {
6736
6737
  grid-template-columns: repeat(3, minmax(0, 1fr));
6737
6738
  }
6738
6739
 
6739
- .md\:grid-cols-5 {
6740
- grid-template-columns: repeat(5, minmax(0, 1fr));
6740
+ .md\:grid-cols-4 {
6741
+ grid-template-columns: repeat(4, minmax(0, 1fr));
6741
6742
  }
6742
6743
 
6743
6744
  .md\:flex-row {
@@ -6942,10 +6943,6 @@ video {
6942
6943
  grid-template-columns: repeat(4, minmax(0, 1fr));
6943
6944
  }
6944
6945
 
6945
- .lg\:grid-cols-7 {
6946
- grid-template-columns: repeat(7, minmax(0, 1fr));
6947
- }
6948
-
6949
6946
  .lg\:flex-row {
6950
6947
  flex-direction: row;
6951
6948
  }
@@ -6993,8 +6990,8 @@ video {
6993
6990
  grid-template-columns: repeat(4, minmax(0, 1fr));
6994
6991
  }
6995
6992
 
6996
- .xl\:grid-cols-8 {
6997
- grid-template-columns: repeat(8, minmax(0, 1fr));
6993
+ .xl\:grid-cols-5 {
6994
+ grid-template-columns: repeat(5, minmax(0, 1fr));
6998
6995
  }
6999
6996
 
7000
6997
  .xl\:px-24 {
@@ -7011,10 +7008,6 @@ video {
7011
7008
  .\32xl\:hidden {
7012
7009
  display: none;
7013
7010
  }
7014
-
7015
- .\32xl\:grid-cols-10 {
7016
- grid-template-columns: repeat(10, minmax(0, 1fr));
7017
- }
7018
7011
  }
7019
7012
  .rtl\:before\:-translate-x-5:where([dir="rtl"], [dir="rtl"] *)::before {
7020
7013
  content: var(--tw-content);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@pagamio/frontend-commons-lib",
3
3
  "description": "Pagamio library for Frontend reusable components like the form engine and table container",
4
- "version": "0.8.336",
4
+ "version": "0.8.338",
5
5
  "publishConfig": {
6
6
  "access": "public",
7
7
  "provenance": false