@codesinger0/shared-components 1.0.10 → 1.0.12
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/dist/components/CTAButton.jsx +17 -0
- package/dist/components/LargeItemCard.jsx +1 -1
- package/dist/components/SmallButton.jsx +35 -0
- package/dist/components/SmallItemCard.jsx +124 -0
- package/dist/components/SmallItemGrid.jsx +308 -0
- package/dist/context/ItemModalContext.jsx +40 -0
- package/dist/index.js +8 -1
- package/package.json +4 -2
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
const CTAButton = ({ children, onClick, className = '', ...props }) => {
|
|
4
|
+
return (
|
|
5
|
+
<button
|
|
6
|
+
className={`btn-primary text-big rounded-sm px-6 py-3 transition-all duration-200 hover:brightness-90 inline-flex items-center gap-2 ${className}`}
|
|
7
|
+
onClick={onClick}
|
|
8
|
+
dir="rtl"
|
|
9
|
+
{...props}
|
|
10
|
+
>
|
|
11
|
+
{children}
|
|
12
|
+
<span className="text-xl">←</span>
|
|
13
|
+
</button>
|
|
14
|
+
);
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export default CTAButton;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
const SmallButton = ({
|
|
4
|
+
children,
|
|
5
|
+
onClick,
|
|
6
|
+
disabled = false,
|
|
7
|
+
className = '',
|
|
8
|
+
...props
|
|
9
|
+
}) => {
|
|
10
|
+
return (
|
|
11
|
+
<button
|
|
12
|
+
className={`
|
|
13
|
+
btn-primary content-text px-3 py-1
|
|
14
|
+
rounded-md font-medium
|
|
15
|
+
transition-all duration-200
|
|
16
|
+
focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2
|
|
17
|
+
hover:brightness-90
|
|
18
|
+
disabled:opacity-50 disabled:cursor-not-allowed
|
|
19
|
+
${className}
|
|
20
|
+
`}
|
|
21
|
+
onClick={(e) => {
|
|
22
|
+
onClick?.(e);
|
|
23
|
+
// remove focus right after click so the ring disappears
|
|
24
|
+
e.currentTarget.blur();
|
|
25
|
+
}}
|
|
26
|
+
disabled={disabled}
|
|
27
|
+
dir="rtl"
|
|
28
|
+
{...props}
|
|
29
|
+
>
|
|
30
|
+
{children}
|
|
31
|
+
</button>
|
|
32
|
+
);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export default SmallButton;
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { ShoppingCart } from 'lucide-react';
|
|
4
|
+
import SmallButton from './SmallButton';
|
|
5
|
+
|
|
6
|
+
const SmallItemCard = ({
|
|
7
|
+
imageUrl,
|
|
8
|
+
label,
|
|
9
|
+
description,
|
|
10
|
+
price,
|
|
11
|
+
discountPrice,
|
|
12
|
+
className = '',
|
|
13
|
+
onClick,
|
|
14
|
+
onAddToCart,
|
|
15
|
+
...props
|
|
16
|
+
}) => {
|
|
17
|
+
// Calculate discount percentage
|
|
18
|
+
const hasDiscount = discountPrice && discountPrice < price;
|
|
19
|
+
const discountPercentage = hasDiscount
|
|
20
|
+
? Math.round(((price - discountPrice) / price) * 100)
|
|
21
|
+
: 0;
|
|
22
|
+
|
|
23
|
+
const handleAddToCartClick = (event) => {
|
|
24
|
+
event.stopPropagation();
|
|
25
|
+
onAddToCart()
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<div
|
|
30
|
+
className={`glass-card cursor-pointer transition-all duration-300 hover:scale-104 ${className}`}
|
|
31
|
+
onClick={onClick}
|
|
32
|
+
dir="rtl"
|
|
33
|
+
{...props}
|
|
34
|
+
>
|
|
35
|
+
{/* Image Container */}
|
|
36
|
+
<div className="relative w-full h-40 overflow-hidden rounded-t-xl">
|
|
37
|
+
{imageUrl ? (
|
|
38
|
+
<img
|
|
39
|
+
src={imageUrl}
|
|
40
|
+
alt={label}
|
|
41
|
+
className="w-full h-full object-cover transition-transform duration-300 hover:scale-110"
|
|
42
|
+
onError={(e) => {
|
|
43
|
+
e.target.style.display = 'none';
|
|
44
|
+
e.target.nextElementSibling.style.display = 'flex';
|
|
45
|
+
}}
|
|
46
|
+
/>
|
|
47
|
+
) : null}
|
|
48
|
+
|
|
49
|
+
{/* Fallback for missing image */}
|
|
50
|
+
<div
|
|
51
|
+
className="w-full h-full bg-gradient-to-br from-gray-100 to-gray-200 flex items-center justify-center"
|
|
52
|
+
style={{ display: imageUrl ? 'none' : 'flex' }}
|
|
53
|
+
>
|
|
54
|
+
<div className="text-center text-gray-400">
|
|
55
|
+
<div className="text-2xl mb-1">🍰</div>
|
|
56
|
+
<div className="text-xs font-medium">תמונה</div>
|
|
57
|
+
</div>
|
|
58
|
+
</div>
|
|
59
|
+
|
|
60
|
+
{/* Discount Badge */}
|
|
61
|
+
{hasDiscount && (
|
|
62
|
+
<div className="absolute top-2 left-2 bg-red-500 text-white px-2 py-1 rounded-full text-xs font-bold shadow-lg">
|
|
63
|
+
-{discountPercentage}%
|
|
64
|
+
</div>
|
|
65
|
+
)}
|
|
66
|
+
</div>
|
|
67
|
+
|
|
68
|
+
{/* Content */}
|
|
69
|
+
<div className="p-4 flex flex-col">
|
|
70
|
+
{/* Name */}
|
|
71
|
+
<h3 className="subtitle font-semibold line-clamp-2 flex-shrink-0">
|
|
72
|
+
{label}
|
|
73
|
+
</h3>
|
|
74
|
+
|
|
75
|
+
{/* Description */}
|
|
76
|
+
{description && (
|
|
77
|
+
<div className="flex-grow">
|
|
78
|
+
<p className="caption-text line-clamp-2 leading-relaxed">
|
|
79
|
+
{description}
|
|
80
|
+
</p>
|
|
81
|
+
</div>
|
|
82
|
+
)}
|
|
83
|
+
|
|
84
|
+
{/* Price Section */}
|
|
85
|
+
<div className="mb-3 flex-shrink-0 leading-relaxed">
|
|
86
|
+
{hasDiscount ? (
|
|
87
|
+
<div className="space-y-2">
|
|
88
|
+
<div className="flex items-center justify-between">
|
|
89
|
+
<div className="flex items-center gap-2">
|
|
90
|
+
<span className="price-tag text-green-600">
|
|
91
|
+
₪{discountPrice}
|
|
92
|
+
</span>
|
|
93
|
+
<span className="text-sm text-gray-400 line-through">
|
|
94
|
+
₪{price}
|
|
95
|
+
</span>
|
|
96
|
+
</div>
|
|
97
|
+
</div>
|
|
98
|
+
<div className="text-xs text-green-600 font-medium">
|
|
99
|
+
חיסכון ₪{price - discountPrice}
|
|
100
|
+
</div>
|
|
101
|
+
</div>
|
|
102
|
+
) : (
|
|
103
|
+
<div className="flex items-center justify-between">
|
|
104
|
+
<span className="price-tag">
|
|
105
|
+
₪{price}
|
|
106
|
+
</span>
|
|
107
|
+
</div>
|
|
108
|
+
)}
|
|
109
|
+
</div>
|
|
110
|
+
|
|
111
|
+
<SmallButton
|
|
112
|
+
onClick={handleAddToCartClick}
|
|
113
|
+
className="w-full flex items-center justify-center gap-2"
|
|
114
|
+
>
|
|
115
|
+
<ShoppingCart size={16} />
|
|
116
|
+
הוסף לעגלה
|
|
117
|
+
</SmallButton>
|
|
118
|
+
|
|
119
|
+
</div>
|
|
120
|
+
</div>
|
|
121
|
+
);
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
export default SmallItemCard;
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
import React, { useState, useEffect, useCallback } from 'react';
|
|
2
|
+
import SmallItemCard from './SmallItemCard';
|
|
3
|
+
import CTAButton from './elements/CTAButton';
|
|
4
|
+
import useEmblaCarousel from 'embla-carousel-react';
|
|
5
|
+
import Autoplay from 'embla-carousel-autoplay';
|
|
6
|
+
import { useItemModal } from '../context/ItemModalContext'
|
|
7
|
+
|
|
8
|
+
const SmallItemsGrid = ({
|
|
9
|
+
maxRows = null,
|
|
10
|
+
showMoreType = 'button',
|
|
11
|
+
headerText = '',
|
|
12
|
+
className = '',
|
|
13
|
+
items,
|
|
14
|
+
onAddToCart,
|
|
15
|
+
autoplay = true,
|
|
16
|
+
isLighter = false,
|
|
17
|
+
...props
|
|
18
|
+
}) => {
|
|
19
|
+
|
|
20
|
+
const [showAllItems, setShowAllItems] = useState(false);
|
|
21
|
+
const { openModal } = useItemModal()
|
|
22
|
+
|
|
23
|
+
// Embla carousel setup
|
|
24
|
+
const autoplayOptions = {
|
|
25
|
+
delay: 3000,
|
|
26
|
+
stopOnInteraction: true,
|
|
27
|
+
stopOnMouseEnter: true,
|
|
28
|
+
rootNode: (emblaRoot) => emblaRoot.parentElement,
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const plugins = autoplay ? [Autoplay(autoplayOptions)] : [];
|
|
32
|
+
|
|
33
|
+
const [emblaRef, emblaApi] = useEmblaCarousel(
|
|
34
|
+
{
|
|
35
|
+
loop: true,
|
|
36
|
+
dragFree: true,
|
|
37
|
+
containScroll: 'trimSnaps',
|
|
38
|
+
slidesToScroll: 1,
|
|
39
|
+
direction: 'rtl'
|
|
40
|
+
},
|
|
41
|
+
plugins
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
const [prevBtnEnabled, setPrevBtnEnabled] = useState(false);
|
|
45
|
+
const [nextBtnEnabled, setNextBtnEnabled] = useState(false);
|
|
46
|
+
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
47
|
+
const [scrollSnaps, setScrollSnaps] = useState([]);
|
|
48
|
+
|
|
49
|
+
const scrollPrev = useCallback(() => emblaApi && emblaApi.scrollPrev(), [emblaApi]);
|
|
50
|
+
const scrollNext = useCallback(() => emblaApi && emblaApi.scrollNext(), [emblaApi]);
|
|
51
|
+
const scrollTo = useCallback((index) => emblaApi && emblaApi.scrollTo(index), [emblaApi]);
|
|
52
|
+
|
|
53
|
+
const onSelect = useCallback(() => {
|
|
54
|
+
if (!emblaApi) return;
|
|
55
|
+
setSelectedIndex(emblaApi.selectedScrollSnap());
|
|
56
|
+
setPrevBtnEnabled(emblaApi.canScrollPrev());
|
|
57
|
+
setNextBtnEnabled(emblaApi.canScrollNext());
|
|
58
|
+
}, [emblaApi]);
|
|
59
|
+
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
if (!emblaApi) return;
|
|
62
|
+
onSelect();
|
|
63
|
+
setScrollSnaps(emblaApi.scrollSnapList());
|
|
64
|
+
emblaApi.on('select', onSelect);
|
|
65
|
+
emblaApi.on('reInit', onSelect);
|
|
66
|
+
}, [emblaApi, onSelect]);
|
|
67
|
+
|
|
68
|
+
const handleCardClick = (item) => {
|
|
69
|
+
openModal(item)
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const toggleShowMore = () => {
|
|
73
|
+
setShowAllItems(!showAllItems);
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
// Calculate items per row based on CSS grid configuration
|
|
77
|
+
const getItemsPerRow = () => {
|
|
78
|
+
return window.innerWidth >= 768 ? 5 : 2; // md:grid-cols-5, grid-cols-2
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
// Calculate max items to show based on rows
|
|
82
|
+
const calculateMaxItems = () => {
|
|
83
|
+
if (!maxRows) return null;
|
|
84
|
+
return maxRows * getItemsPerRow();
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
// Determine if we need overflow handling
|
|
88
|
+
const maxItemsToShow = calculateMaxItems();
|
|
89
|
+
const needsOverflow = maxRows && items.length > maxItemsToShow;
|
|
90
|
+
|
|
91
|
+
// Calculate displayed items for button mode
|
|
92
|
+
const displayedItems = needsOverflow && !showAllItems
|
|
93
|
+
? items.slice(0, maxItemsToShow)
|
|
94
|
+
: items;
|
|
95
|
+
|
|
96
|
+
const GridComponent = ({ items: gridItems }) => (
|
|
97
|
+
<div className="grid grid-cols-2 md:grid-cols-5 gap-4 md:gap-6">
|
|
98
|
+
{gridItems.map(item => (
|
|
99
|
+
<SmallItemCard
|
|
100
|
+
key={item.id}
|
|
101
|
+
imageUrl={item.imageUrl}
|
|
102
|
+
label={item.name}
|
|
103
|
+
description={item.description}
|
|
104
|
+
price={item.price}
|
|
105
|
+
discountPrice={item.discountPrice}
|
|
106
|
+
onClick={() => handleCardClick(item)}
|
|
107
|
+
onAddToCart={() => onAddToCart(item)}
|
|
108
|
+
className="hover:shadow-xl"
|
|
109
|
+
/>
|
|
110
|
+
))}
|
|
111
|
+
</div>
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
return (
|
|
115
|
+
<section className={`py-8 px-0 bg-main ${className}`} {...props}>
|
|
116
|
+
<div className="max-w-6xl mx-auto">
|
|
117
|
+
<h2 className={`${isLighter ? 'lighterTitle' : 'title'} text-center mb-12`}>{headerText}</h2>
|
|
118
|
+
|
|
119
|
+
{showMoreType === 'carousel' ? (
|
|
120
|
+
// Embla Carousel Mode - infinite with autoscroll
|
|
121
|
+
<div className="embla" dir="rtl">
|
|
122
|
+
<div className="embla__viewport" ref={emblaRef}>
|
|
123
|
+
<div className="embla__container">
|
|
124
|
+
{items.map(item => (
|
|
125
|
+
<div key={item.id} className="embla__slide">
|
|
126
|
+
<SmallItemCard
|
|
127
|
+
imageUrl={item.imageUrl}
|
|
128
|
+
label={item.name}
|
|
129
|
+
description={item.description}
|
|
130
|
+
price={item.price}
|
|
131
|
+
discountPrice={item.discountPrice}
|
|
132
|
+
onClick={() => handleCardClick(item)}
|
|
133
|
+
onAddToCart={() => onAddToCart(item)}
|
|
134
|
+
className="h-full flex flex-col" />
|
|
135
|
+
</div>
|
|
136
|
+
))}
|
|
137
|
+
</div>
|
|
138
|
+
</div>
|
|
139
|
+
|
|
140
|
+
{/* Dots indicator */}
|
|
141
|
+
<div className="embla__dots">
|
|
142
|
+
{scrollSnaps.map((_, index) => (
|
|
143
|
+
<button
|
|
144
|
+
key={index}
|
|
145
|
+
className={`embla__dot ${index === selectedIndex ? 'embla__dot--selected' : ''}`}
|
|
146
|
+
onClick={() => scrollTo(index)}
|
|
147
|
+
/>
|
|
148
|
+
))}
|
|
149
|
+
</div>
|
|
150
|
+
|
|
151
|
+
{/* Navigation buttons */}
|
|
152
|
+
{!isLighter && <div className="embla__controls">
|
|
153
|
+
<button
|
|
154
|
+
className="embla__button embla__button--prev"
|
|
155
|
+
onClick={scrollPrev}
|
|
156
|
+
disabled={!prevBtnEnabled}
|
|
157
|
+
>
|
|
158
|
+
<svg className="embla__button__svg" viewBox="0 0 24 24">
|
|
159
|
+
<path d="M9 18l6-6-6-6" />
|
|
160
|
+
</svg>
|
|
161
|
+
</button>
|
|
162
|
+
<button
|
|
163
|
+
className="embla__button embla__button--next"
|
|
164
|
+
onClick={scrollNext}
|
|
165
|
+
disabled={!nextBtnEnabled}
|
|
166
|
+
>
|
|
167
|
+
<svg className="embla__button__svg" viewBox="0 0 24 24">
|
|
168
|
+
<path d="M15 18l-6-6 6-6" />
|
|
169
|
+
</svg>
|
|
170
|
+
</button>
|
|
171
|
+
</div>}
|
|
172
|
+
</div>
|
|
173
|
+
) : (
|
|
174
|
+
// Grid Mode (default or button)
|
|
175
|
+
<>
|
|
176
|
+
<GridComponent items={displayedItems} />
|
|
177
|
+
|
|
178
|
+
{/* Show More Button */}
|
|
179
|
+
{needsOverflow && showMoreType === 'button' && (
|
|
180
|
+
<div className="text-center mt-8">
|
|
181
|
+
<CTAButton onClick={toggleShowMore}>
|
|
182
|
+
{showAllItems ? 'ראה פחות' : `ראה עוד (${items.length - maxItemsToShow} נוספים)`}
|
|
183
|
+
</CTAButton>
|
|
184
|
+
</div>
|
|
185
|
+
)}
|
|
186
|
+
</>
|
|
187
|
+
)}
|
|
188
|
+
</div>
|
|
189
|
+
|
|
190
|
+
{/* Embla Carousel Styles */}
|
|
191
|
+
<style jsx>{`
|
|
192
|
+
.embla {
|
|
193
|
+
max-width: 100%;
|
|
194
|
+
margin: 0 auto;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
.embla__viewport {
|
|
198
|
+
overflow: hidden;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
.embla__container {
|
|
202
|
+
display: flex;
|
|
203
|
+
user-select: none;
|
|
204
|
+
-webkit-touch-callout: none;
|
|
205
|
+
-khtml-user-select: none;
|
|
206
|
+
-webkit-tap-highlight-color: transparent;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
.embla__slide {
|
|
210
|
+
flex: 0 0 60%;
|
|
211
|
+
min-width: 0;
|
|
212
|
+
padding-left: 0.6rem;
|
|
213
|
+
padding-right: 0.6rem;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
@media (min-width: 768px) {
|
|
217
|
+
.embla__slide {
|
|
218
|
+
flex: 0 0 33.333333%;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
@media (min-width: 1024px) {
|
|
223
|
+
.embla__slide {
|
|
224
|
+
flex: 0 0 25%;
|
|
225
|
+
min-width: 200px;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
@media (min-width: 1280px) {
|
|
230
|
+
.embla__slide {
|
|
231
|
+
flex: 0 0 25%;
|
|
232
|
+
min-width: 260px;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
.embla__controls {
|
|
237
|
+
display: flex;
|
|
238
|
+
justify-content: center;
|
|
239
|
+
gap: 1rem;
|
|
240
|
+
margin-top: 2rem;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
.embla__button {
|
|
244
|
+
background-color: var(--primary);
|
|
245
|
+
color: white;
|
|
246
|
+
border: none;
|
|
247
|
+
width: 2.5rem;
|
|
248
|
+
height: 2.5rem;
|
|
249
|
+
border-radius: 50%;
|
|
250
|
+
cursor: pointer;
|
|
251
|
+
display: flex;
|
|
252
|
+
align-items: center;
|
|
253
|
+
justify-content: center;
|
|
254
|
+
transition: all 0.2s ease;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
.embla__button:hover:not(:disabled) {
|
|
258
|
+
background-color: color-mix(in srgb, var(--primary) 80%, black);
|
|
259
|
+
transform: scale(1.1);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
.embla__button:disabled {
|
|
263
|
+
opacity: 0.5;
|
|
264
|
+
cursor: not-allowed;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
.embla__button__svg {
|
|
268
|
+
width: 1rem;
|
|
269
|
+
height: 1rem;
|
|
270
|
+
fill: none;
|
|
271
|
+
stroke: currentColor;
|
|
272
|
+
stroke-width: 2;
|
|
273
|
+
stroke-linecap: round;
|
|
274
|
+
stroke-linejoin: round;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
.embla__dots {
|
|
278
|
+
display: flex;
|
|
279
|
+
justify-content: center;
|
|
280
|
+
gap: 0.5rem;
|
|
281
|
+
margin-top: 1rem;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
.embla__dot {
|
|
285
|
+
background-color: var(--primary);
|
|
286
|
+
border: none;
|
|
287
|
+
width: 0.5rem;
|
|
288
|
+
height: 0.5rem;
|
|
289
|
+
border-radius: 50%;
|
|
290
|
+
cursor: pointer;
|
|
291
|
+
opacity: 0.3;
|
|
292
|
+
transition: all 0.2s ease;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
.embla__dot--selected {
|
|
296
|
+
opacity: 1;
|
|
297
|
+
transform: scale(1.2);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
.embla__dot:hover {
|
|
301
|
+
opacity: 0.7;
|
|
302
|
+
}
|
|
303
|
+
`}</style>
|
|
304
|
+
</section>
|
|
305
|
+
);
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
export default SmallItemsGrid;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// src/context/ItemModalContext.js
|
|
2
|
+
import React, { createContext, useContext, useState } from 'react';
|
|
3
|
+
|
|
4
|
+
const ItemModalContext = createContext();
|
|
5
|
+
|
|
6
|
+
export const useItemModal = () => {
|
|
7
|
+
const context = useContext(ItemModalContext);
|
|
8
|
+
if (!context) {
|
|
9
|
+
throw new Error('useItemModal must be used within an ItemModalProvider');
|
|
10
|
+
}
|
|
11
|
+
return context;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const ItemModalProvider = ({ children }) => {
|
|
15
|
+
const [selectedItem, setSelectedItem] = useState(null);
|
|
16
|
+
const [isModalOpen, setIsModalOpen] = useState(false);
|
|
17
|
+
|
|
18
|
+
const openModal = (item) => {
|
|
19
|
+
console.log('Opening modal for item:', item);
|
|
20
|
+
setSelectedItem(item);
|
|
21
|
+
setIsModalOpen(true);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const closeModal = () => {
|
|
25
|
+
setIsModalOpen(false);
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const value = {
|
|
29
|
+
selectedItem,
|
|
30
|
+
isModalOpen,
|
|
31
|
+
openModal,
|
|
32
|
+
closeModal
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<ItemModalContext.Provider value={value}>
|
|
37
|
+
{children}
|
|
38
|
+
</ItemModalContext.Provider>
|
|
39
|
+
);
|
|
40
|
+
};
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,9 @@
|
|
|
1
1
|
export { default as RoundButton } from './components/RoundButton';
|
|
2
|
-
export { default as
|
|
2
|
+
export { default as CTAButton } from './components/CTAButton';
|
|
3
|
+
export { default as SmallButton } from './components/SmallButton';
|
|
4
|
+
export { default as LargeItemCard } from './components/LargeItemCard';
|
|
5
|
+
export { default as SmallItemGrid } from './components/SmallItemGrid';
|
|
6
|
+
export { default as SmallItemCard } from './components/SmallItemCard';
|
|
7
|
+
|
|
8
|
+
// Context
|
|
9
|
+
export { ItemModalProvider, useItemModal } from './context/ItemModalContext';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@codesinger0/shared-components",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.12",
|
|
4
4
|
"description": "Shared React components for customer projects",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"files": [
|
|
@@ -12,7 +12,9 @@
|
|
|
12
12
|
"peerDependencies": {
|
|
13
13
|
"react": ">=18.0.0",
|
|
14
14
|
"lucide-react": ">=0.263.1",
|
|
15
|
-
"framer-motion": ">=12.23.12"
|
|
15
|
+
"framer-motion": ">=12.23.12",
|
|
16
|
+
"embla-carousel-autoplay": ">=8.6.0",
|
|
17
|
+
"embla-carousel-react": ">=8.6.0"
|
|
16
18
|
},
|
|
17
19
|
"repository": {
|
|
18
20
|
"type": "git",
|