@pagamio/frontend-commons-lib 0.8.323 → 0.8.325
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.
|
@@ -36,7 +36,7 @@ export function ProductCard({ product, onAddToCart, isAddingToCart, storeId, bas
|
|
|
36
36
|
}
|
|
37
37
|
return (_jsx("a", { href: productUrl, className: className, tabIndex: tabIndex, "aria-label": ariaLabel, children: children }));
|
|
38
38
|
};
|
|
39
|
-
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-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) => {
|
|
39
|
+
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) => {
|
|
40
40
|
e.preventDefault();
|
|
41
41
|
e.stopPropagation();
|
|
42
42
|
if (hasVariants) {
|
|
@@ -58,5 +58,5 @@ export function ProductCard({ product, onAddToCart, isAddingToCart, storeId, bas
|
|
|
58
58
|
? isBackorderable && !hasStock
|
|
59
59
|
? 'bg-warning text-white hover:bg-warning/90'
|
|
60
60
|
: 'bg-foreground text-background hover:bg-foreground/90 active:bg-foreground/80'
|
|
61
|
-
: '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
|
|
61
|
+
: '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' })] })) })] })] }));
|
|
62
62
|
}
|
|
@@ -2,8 +2,8 @@ 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-
|
|
6
|
-
export function ProductGrid({ products, isLoading, onAddToCart, addingProductId, storeId, emptyMessage = 'No products found', basePath, linkSuffix, skeletonCount =
|
|
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';
|
|
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))) }));
|
|
9
9
|
}
|
|
@@ -3,6 +3,12 @@ import { ChevronDownIcon, Cross2Icon } from '@radix-ui/react-icons';
|
|
|
3
3
|
import { forwardRef, useCallback, useEffect, useRef, useState } from 'react';
|
|
4
4
|
import { cn } from '../../helpers';
|
|
5
5
|
import Button from './Button';
|
|
6
|
+
const DROPDOWN_MARGIN = 4;
|
|
7
|
+
const VIEWPORT_PADDING = 8;
|
|
8
|
+
// Target height when there's plenty of room. The actual height is capped to
|
|
9
|
+
// the available space, so in tight viewports the dropdown shrinks rather
|
|
10
|
+
// than overshooting.
|
|
11
|
+
const PREFERRED_DROPDOWN_HEIGHT = 224;
|
|
6
12
|
const MultiSelect = forwardRef(({ options, value, defaultValue = [], disabled, onChange, placeholder, className, field, tagPosition = 'inside' }, ref) => {
|
|
7
13
|
const [selectedValues, setSelectedValues] = useState(defaultValue);
|
|
8
14
|
const [isOpen, setIsOpen] = useState(false);
|
|
@@ -15,15 +21,29 @@ const MultiSelect = forwardRef(({ options, value, defaultValue = [], disabled, o
|
|
|
15
21
|
setSelectedValues(value);
|
|
16
22
|
}
|
|
17
23
|
}, [value]);
|
|
18
|
-
// Measure trigger position
|
|
24
|
+
// Measure trigger position and decide whether to render the dropdown
|
|
25
|
+
// below or above the trigger based on available viewport space. Cap the
|
|
26
|
+
// height to whatever room is left so the list never extends past the
|
|
27
|
+
// visible area — that's what produced the "I can't see more shifts" bug
|
|
28
|
+
// when the trigger sat near the bottom of a drawer.
|
|
19
29
|
const updateDropdownPosition = useCallback(() => {
|
|
20
30
|
if (!triggerRef.current)
|
|
21
31
|
return;
|
|
22
32
|
const rect = triggerRef.current.getBoundingClientRect();
|
|
33
|
+
const viewportH = window.innerHeight;
|
|
34
|
+
const spaceBelow = Math.max(0, viewportH - rect.bottom - DROPDOWN_MARGIN - VIEWPORT_PADDING);
|
|
35
|
+
const spaceAbove = Math.max(0, rect.top - DROPDOWN_MARGIN - VIEWPORT_PADDING);
|
|
36
|
+
const placeBelow = spaceBelow >= PREFERRED_DROPDOWN_HEIGHT || spaceBelow >= spaceAbove;
|
|
37
|
+
const available = placeBelow ? spaceBelow : spaceAbove;
|
|
38
|
+
// Never exceed the actual room — overshooting causes the list to
|
|
39
|
+
// disappear under the trigger or off the bottom of the viewport.
|
|
40
|
+
const maxHeight = Math.min(PREFERRED_DROPDOWN_HEIGHT, available);
|
|
23
41
|
setDropdownPos({
|
|
24
|
-
top: rect.bottom +
|
|
25
|
-
left: rect.left
|
|
42
|
+
top: placeBelow ? rect.bottom + DROPDOWN_MARGIN : rect.top - DROPDOWN_MARGIN,
|
|
43
|
+
left: rect.left,
|
|
26
44
|
width: rect.width,
|
|
45
|
+
maxHeight,
|
|
46
|
+
placement: placeBelow ? 'bottom' : 'top',
|
|
27
47
|
});
|
|
28
48
|
}, []);
|
|
29
49
|
// Reposition on scroll / resize while open
|
|
@@ -90,10 +110,12 @@ const MultiSelect = forwardRef(({ options, value, defaultValue = [], disabled, o
|
|
|
90
110
|
? displayPlaceholder
|
|
91
111
|
: `${selectedOptions.length} selected` })) }), _jsx(ChevronDownIcon, { className: cn('ml-2 flex-shrink-0 text-muted-foreground transition-transform duration-150', isOpen && 'rotate-180') })] }), tagPosition === 'bottom' && selectedOptions.length > 0 && (_jsx("div", { className: "flex flex-wrap gap-1 mt-2", children: tags })), isOpen && dropdownPos && (_jsx("div", { ref: ref, id: "ms-dropdown-portal", style: {
|
|
92
112
|
position: 'fixed',
|
|
93
|
-
top: dropdownPos.top,
|
|
113
|
+
top: dropdownPos.placement === 'bottom' ? dropdownPos.top : undefined,
|
|
114
|
+
bottom: dropdownPos.placement === 'top' ? window.innerHeight - dropdownPos.top : undefined,
|
|
94
115
|
left: dropdownPos.left,
|
|
95
116
|
width: dropdownPos.width,
|
|
96
|
-
|
|
117
|
+
maxHeight: dropdownPos.maxHeight,
|
|
118
|
+
}, className: cn('z-[9999] flex flex-col', 'bg-popover border border-border rounded-md shadow-lg', 'animate-in fade-in-0 zoom-in-95 duration-100'), children: _jsx("ul", { className: "overflow-y-auto p-1.5 space-y-0.5", style: { maxHeight: dropdownPos.maxHeight }, children: options.length > 0 ? (options.map((option) => {
|
|
97
119
|
const checked = internalValues.includes(option.value);
|
|
98
120
|
const id = `ms-opt-${field?.name ?? 'ms'}-${option.value}`;
|
|
99
121
|
return (_jsx("li", { children: _jsxs("label", { htmlFor: id, className: cn('flex items-center gap-2.5 px-2 py-2 rounded-md cursor-pointer text-sm', 'hover:bg-accent hover:text-accent-foreground transition-colors', checked && 'bg-primary/5'), children: [_jsx("input", { id: id, type: "checkbox", checked: checked, onChange: () => handleToggle(option.value), className: cn('w-4 h-4 rounded border border-input bg-background flex-shrink-0', 'accent-[hsl(var(--primary))] cursor-pointer', 'focus:ring-2 focus:ring-ring focus:ring-offset-0') }), _jsx("span", { className: "flex-1 font-medium text-foreground", children: option.label })] }) }, option.value));
|
package/lib/styles.css
CHANGED
|
@@ -6455,8 +6455,8 @@ video {
|
|
|
6455
6455
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
6456
6456
|
}
|
|
6457
6457
|
|
|
6458
|
-
.sm\:grid-cols-
|
|
6459
|
-
grid-template-columns: repeat(
|
|
6458
|
+
.sm\:grid-cols-4 {
|
|
6459
|
+
grid-template-columns: repeat(4, minmax(0, 1fr));
|
|
6460
6460
|
}
|
|
6461
6461
|
|
|
6462
6462
|
.sm\:flex-row {
|
|
@@ -6734,8 +6734,8 @@ video {
|
|
|
6734
6734
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
6735
6735
|
}
|
|
6736
6736
|
|
|
6737
|
-
.md\:grid-cols-
|
|
6738
|
-
grid-template-columns: repeat(
|
|
6737
|
+
.md\:grid-cols-5 {
|
|
6738
|
+
grid-template-columns: repeat(5, minmax(0, 1fr));
|
|
6739
6739
|
}
|
|
6740
6740
|
|
|
6741
6741
|
.md\:flex-row {
|
|
@@ -6940,8 +6940,8 @@ video {
|
|
|
6940
6940
|
grid-template-columns: repeat(4, minmax(0, 1fr));
|
|
6941
6941
|
}
|
|
6942
6942
|
|
|
6943
|
-
.lg\:grid-cols-
|
|
6944
|
-
grid-template-columns: repeat(
|
|
6943
|
+
.lg\:grid-cols-7 {
|
|
6944
|
+
grid-template-columns: repeat(7, minmax(0, 1fr));
|
|
6945
6945
|
}
|
|
6946
6946
|
|
|
6947
6947
|
.lg\:flex-row {
|
|
@@ -6991,8 +6991,8 @@ video {
|
|
|
6991
6991
|
grid-template-columns: repeat(4, minmax(0, 1fr));
|
|
6992
6992
|
}
|
|
6993
6993
|
|
|
6994
|
-
.xl\:grid-cols-
|
|
6995
|
-
grid-template-columns: repeat(
|
|
6994
|
+
.xl\:grid-cols-8 {
|
|
6995
|
+
grid-template-columns: repeat(8, minmax(0, 1fr));
|
|
6996
6996
|
}
|
|
6997
6997
|
|
|
6998
6998
|
.xl\:px-24 {
|
|
@@ -7009,6 +7009,10 @@ video {
|
|
|
7009
7009
|
.\32xl\:hidden {
|
|
7010
7010
|
display: none;
|
|
7011
7011
|
}
|
|
7012
|
+
|
|
7013
|
+
.\32xl\:grid-cols-10 {
|
|
7014
|
+
grid-template-columns: repeat(10, minmax(0, 1fr));
|
|
7015
|
+
}
|
|
7012
7016
|
}
|
|
7013
7017
|
.rtl\:before\:-translate-x-5:where([dir="rtl"], [dir="rtl"] *)::before {
|
|
7014
7018
|
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.325",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public",
|
|
7
7
|
"provenance": false
|