@pagamio/frontend-commons-lib 0.8.335 → 0.8.337
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.
- package/lib/components/ecommerce/ProductCard.js +23 -11
- package/lib/components/ecommerce/ProductGrid.js +1 -1
- package/lib/components/ui/Card.d.ts +1 -1
- package/lib/components/ui/FilterComponent.js +2 -2
- package/lib/shared/hooks/usePagamioTable.js +7 -3
- package/lib/styles.css +18 -25
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
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';
|
|
@@ -39,7 +39,23 @@ export function ProductCard({ product, onAddToCart, isAddingToCart, storeId, bas
|
|
|
39
39
|
}
|
|
40
40
|
return (_jsx("a", { href: productUrl, className: className, tabIndex: tabIndex, "aria-label": ariaLabel, children: children }));
|
|
41
41
|
};
|
|
42
|
-
|
|
42
|
+
// CTA state — kept identical to the previous behaviour so production code
|
|
43
|
+
// paths (backorder, select-store, sold-out, adding spinner) still work.
|
|
44
|
+
const ctaDisabled = hasVariants ? !hasStock && !isBackorderable : !canAddToCart || !!isAddingToCart;
|
|
45
|
+
const ctaLabel = hasVariants
|
|
46
|
+
? !hasStock && !isBackorderable
|
|
47
|
+
? 'Out of Stock'
|
|
48
|
+
: 'Options'
|
|
49
|
+
: isAddingToCart
|
|
50
|
+
? 'Adding…'
|
|
51
|
+
: !storeId
|
|
52
|
+
? 'Select Store'
|
|
53
|
+
: hasStock
|
|
54
|
+
? 'Add to cart'
|
|
55
|
+
: isBackorderable
|
|
56
|
+
? 'Backorder'
|
|
57
|
+
: 'Sold Out';
|
|
58
|
+
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
59
|
e.preventDefault();
|
|
44
60
|
e.stopPropagation();
|
|
45
61
|
if (hasVariants) {
|
|
@@ -53,13 +69,9 @@ export function ProductCard({ product, onAddToCart, isAddingToCart, storeId, bas
|
|
|
53
69
|
else if (onAddToCart && canAddToCart) {
|
|
54
70
|
onAddToCart(product);
|
|
55
71
|
}
|
|
56
|
-
}, className: cn('mt-
|
|
57
|
-
?
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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' })] })) })] })] }));
|
|
72
|
+
}, 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
|
|
73
|
+
? 'border border-primary text-primary hover:bg-primary hover:text-primary-foreground'
|
|
74
|
+
: isBackorderable && !hasStock
|
|
75
|
+
? 'bg-warning text-white hover:bg-warning/90'
|
|
76
|
+
: '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
77
|
}
|
|
@@ -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-
|
|
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<
|
|
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>>;
|
|
@@ -64,11 +64,11 @@ const FilterComponent = ({ filters, selectedFilters, showApplyFilterButton = tru
|
|
|
64
64
|
return (_jsx(DatePicker, { value: value || null, onChange: (date) => handleFilterChange(name, date), placeholder: filter.placeholder ?? `Select ${name}` }));
|
|
65
65
|
}
|
|
66
66
|
if (type === 'multi-select') {
|
|
67
|
-
return (_jsx(MultiSelect, { options: options
|
|
67
|
+
return (_jsx(MultiSelect, { options: Array.isArray(options) ? options : [], placeholder: filter.placeholder ?? `Select ${name}`, value: value || [], onChange: (val) => handleFilterChange(name, val), className: "w-full" }));
|
|
68
68
|
}
|
|
69
69
|
// Default: select
|
|
70
70
|
const currentValue = value || '';
|
|
71
|
-
return (_jsxs(Select, { value: currentValue || undefined, onValueChange: (val) => handleFilterChange(name, val === '__clear__' ? '' : val), children: [_jsx(SelectTrigger, { className: "h-9 w-full border-border bg-background text-sm text-foreground", "aria-label": filter.placeholder ?? name, children: _jsx(SelectValue, { placeholder: filter.placeholder ?? `Select ${name}` }) }), _jsxs(SelectContent, { children: [currentValue && (_jsx(SelectItem, { value: "__clear__", className: "text-muted-foreground", children: filter.placeholder ?? 'All' })), (options
|
|
71
|
+
return (_jsxs(Select, { value: currentValue || undefined, onValueChange: (val) => handleFilterChange(name, val === '__clear__' ? '' : val), children: [_jsx(SelectTrigger, { className: "h-9 w-full border-border bg-background text-sm text-foreground", "aria-label": filter.placeholder ?? name, children: _jsx(SelectValue, { placeholder: filter.placeholder ?? `Select ${name}` }) }), _jsxs(SelectContent, { children: [currentValue && (_jsx(SelectItem, { value: "__clear__", className: "text-muted-foreground", children: filter.placeholder ?? 'All' })), (Array.isArray(options) ? options : []).map((opt) => (_jsx(SelectItem, { value: opt.value, children: opt.label }, opt.value)))] })] }));
|
|
72
72
|
};
|
|
73
73
|
return (
|
|
74
74
|
// Fluid width: full width on mobile, fluid 180–260px on wider
|
|
@@ -79,8 +79,12 @@ function processResponse(response, mapping) {
|
|
|
79
79
|
const totalPath = mapping.total ?? '';
|
|
80
80
|
const extractedData = dataPath ? getNestedValue(response, dataPath) : response;
|
|
81
81
|
const extractedTotal = totalPath ? getNestedValue(response, totalPath) : undefined;
|
|
82
|
-
|
|
83
|
-
|
|
82
|
+
// Guard against the API returning a non-array at the data path (single-entity
|
|
83
|
+
// response, error envelope, accidentally wrapped paginated response). Downstream
|
|
84
|
+
// consumers iterate `data` directly, so we must hand back an array.
|
|
85
|
+
const fallback = mapping.defaultValues?.data ?? [];
|
|
86
|
+
const data = Array.isArray(extractedData) ? extractedData : fallback;
|
|
87
|
+
const total = extractedTotal ?? data.length;
|
|
84
88
|
return { data, total };
|
|
85
89
|
}
|
|
86
90
|
function buildQueryParams(config, pagination, sorting, filtering) {
|
|
@@ -195,7 +199,7 @@ export const usePagamioTable = ({ queryKey, queryFn, enabled: queryEnabled = tru
|
|
|
195
199
|
return { serverData: processed.data, serverTotal: processed.total };
|
|
196
200
|
}, [rawResponse, responseMapping, isServerMode]);
|
|
197
201
|
// ── Client-side data processing ──────────────────────────────────
|
|
198
|
-
const rawData = isServerMode ? serverData : (clientData
|
|
202
|
+
const rawData = isServerMode ? serverData : Array.isArray(clientData) ? clientData : [];
|
|
199
203
|
const processedData = useMemo(() => {
|
|
200
204
|
let items = [...rawData];
|
|
201
205
|
if (searchMode === 'client' && searchQuery) {
|
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-
|
|
6465
|
-
grid-template-columns: repeat(
|
|
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-
|
|
6740
|
-
grid-template-columns: repeat(
|
|
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-
|
|
6997
|
-
grid-template-columns: repeat(
|
|
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.
|
|
4
|
+
"version": "0.8.337",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public",
|
|
7
7
|
"provenance": false
|