@codesinger0/shared-components 1.1.85 → 1.1.87
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/MasonryItemCard.jsx +1 -1
- package/package.json +1 -1
- package/dist/components 2/AccessibilityMenu.jsx +0 -474
- package/dist/components 2/AdvantagesList.jsx +0 -89
- package/dist/components 2/ArticlesList.jsx +0 -269
- package/dist/components 2/DualTextCard.jsx +0 -73
- package/dist/components 2/FloatingWhatsAppButton.jsx +0 -180
- package/dist/components 2/FullscreenCarousel.jsx +0 -292
- package/dist/components 2/Hero.jsx +0 -198
- package/dist/components 2/IconGrid.jsx +0 -144
- package/dist/components 2/IntroSection.jsx +0 -74
- package/dist/components 2/LargeItemCard.jsx +0 -267
- package/dist/components 2/MasonryItemCard.jsx +0 -247
- package/dist/components 2/Menu.d.ts +0 -26
- package/dist/components 2/Menu.jsx +0 -268
- package/dist/components 2/MyOrdersDisplay.jsx +0 -311
- package/dist/components 2/QAAccordion.jsx +0 -212
- package/dist/components 2/SmallItemCard.jsx +0 -152
- package/dist/components 2/SmallItemsGrid.jsx +0 -313
- package/dist/components 2/TextListCards.jsx +0 -107
- package/dist/components 2/ToastProvider.jsx +0 -38
- package/dist/components 2/UnderConstruction.jsx +0 -76
- package/dist/components 2/VideoCard.jsx +0 -88
- package/dist/components 2/cart/CartItem.jsx +0 -101
- package/dist/components 2/cart/FloatingCartButton.jsx +0 -49
- package/dist/components 2/cart/OrderForm.jsx +0 -960
- package/dist/components 2/cart/ShoppingCartModal.jsx +0 -229
- package/dist/components 2/clubMembership/ClubMembershipModal.jsx +0 -289
- package/dist/components 2/clubMembership/ClubPromoModal.jsx +0 -108
- package/dist/components 2/elements/CTAButton.jsx +0 -17
- package/dist/components 2/elements/FixedWidthHeroVideo.jsx +0 -92
- package/dist/components 2/elements/ImageLightbox.jsx +0 -112
- package/dist/components 2/elements/RoundButton.jsx +0 -44
- package/dist/components 2/elements/SmallButton.jsx +0 -35
- package/dist/components 2/elements/Toast.jsx +0 -37
- package/dist/components 2/elements/VideoLightbox.jsx +0 -76
- package/dist/components 2/modals/ItemDetailsModal.jsx +0 -192
- package/dist/components 2/products/CategoryList.jsx +0 -24
- package/dist/components 2/products/PriceRangeSlider.jsx +0 -162
- package/dist/components 2/products/ProductsDisplay.jsx +0 -40
- package/dist/components 2/products/ProductsSidebar.jsx +0 -46
- package/dist/components 2/products/SubcategorySection.jsx +0 -37
- package/dist/context 2/CartContext.jsx +0 -165
- package/dist/context 2/ItemModalContext.jsx +0 -40
- package/dist/hooks 2/useScrollLock.js +0 -52
- package/dist/index 2.js +0 -45
- package/dist/integrations 2/emailService.js +0 -167
- package/dist/styles 2/shared-components.css +0 -29
- package/dist/utils 2/ScrollManager.jsx +0 -85
- package/dist/utils 2/ScrollToTop.jsx +0 -14
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import { motion } from "framer-motion";
|
|
2
|
-
|
|
3
|
-
const IntroSection = ({
|
|
4
|
-
introHeight = '40vh',
|
|
5
|
-
introTitle,
|
|
6
|
-
introContent,
|
|
7
|
-
introImage,
|
|
8
|
-
classNames = {}
|
|
9
|
-
}) => {
|
|
10
|
-
const {
|
|
11
|
-
introTitle: introTitleClass = 'title',
|
|
12
|
-
introContent: introContentClass = 'subtitle'
|
|
13
|
-
} = classNames;
|
|
14
|
-
|
|
15
|
-
return (
|
|
16
|
-
<>
|
|
17
|
-
{/* Introduction Section */}
|
|
18
|
-
<section className=" py-16 px-4" style={{ minHeight: introHeight }}>
|
|
19
|
-
<div className="max-w-6xl mx-auto" dir="rtl">
|
|
20
|
-
{introImage ? (
|
|
21
|
-
/* Layout with image */
|
|
22
|
-
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8 lg:gap-12 items-center">
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
{/* Image */}
|
|
26
|
-
<motion.div
|
|
27
|
-
key={'image'}
|
|
28
|
-
initial={{ opacity: 0, x: 50 }}
|
|
29
|
-
whileInView={{ opacity: 1, x: 0 }}
|
|
30
|
-
viewport={{ once: true }}
|
|
31
|
-
transition={{ delay: 1 * 0.5 }}
|
|
32
|
-
className="flex items-center justify-center lg:justify-end"
|
|
33
|
-
>
|
|
34
|
-
<div className="w-full max-w-md lg:max-w-lg">
|
|
35
|
-
<img
|
|
36
|
-
src={introImage}
|
|
37
|
-
alt={introTitle || "Introduction image"}
|
|
38
|
-
className="w-full h-auto rounded-lg shadow-lg object-cover"
|
|
39
|
-
onError={(e) => {
|
|
40
|
-
e.target.style.display = 'none';
|
|
41
|
-
console.warn('Failed to load intro image:', introImage);
|
|
42
|
-
}}
|
|
43
|
-
/>
|
|
44
|
-
</div>
|
|
45
|
-
</motion.div>
|
|
46
|
-
|
|
47
|
-
{/* Text Content */}
|
|
48
|
-
<div className="text-center lg:text-right">
|
|
49
|
-
<h2 className={introTitleClass + " mb-6"}>
|
|
50
|
-
{introTitle}
|
|
51
|
-
</h2>
|
|
52
|
-
<p className={introContentClass + " leading-relaxed"} style={{ whiteSpace: 'pre-line' }}>
|
|
53
|
-
{introContent}
|
|
54
|
-
</p>
|
|
55
|
-
</div>
|
|
56
|
-
</div>
|
|
57
|
-
) : (
|
|
58
|
-
/* Text only layout (original) */
|
|
59
|
-
<div className="text-center max-w-4xl mx-auto">
|
|
60
|
-
<h2 className="title mb-6">
|
|
61
|
-
{introTitle}
|
|
62
|
-
</h2>
|
|
63
|
-
<p className="subtitle leading-relaxed" style={{ whiteSpace: 'pre-line' }}>
|
|
64
|
-
{introContent}
|
|
65
|
-
</p>
|
|
66
|
-
</div>
|
|
67
|
-
)}
|
|
68
|
-
</div>
|
|
69
|
-
</section>
|
|
70
|
-
</>
|
|
71
|
-
);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
export default IntroSection
|
|
@@ -1,267 +0,0 @@
|
|
|
1
|
-
import React, { useState } from 'react';
|
|
2
|
-
import RoundButton from './elements/RoundButton.jsx'
|
|
3
|
-
import { motion } from 'framer-motion'
|
|
4
|
-
import ImageLightbox from './elements/ImageLightbox.jsx'
|
|
5
|
-
import VideoLightbox from './elements/VideoLightbox.jsx'
|
|
6
|
-
|
|
7
|
-
const LargeItemCard = ({
|
|
8
|
-
imageUrl,
|
|
9
|
-
videoUrl,
|
|
10
|
-
contentType = 'image',
|
|
11
|
-
mediaScale = 1,
|
|
12
|
-
title,
|
|
13
|
-
subtitle,
|
|
14
|
-
description,
|
|
15
|
-
buttonLabel,
|
|
16
|
-
onButtonClick,
|
|
17
|
-
reverse = false,
|
|
18
|
-
price,
|
|
19
|
-
discountPrice,
|
|
20
|
-
amountAvailable,
|
|
21
|
-
absolutePosition = true,
|
|
22
|
-
availableLabel = 'נותרו במלאי',
|
|
23
|
-
soldOutLabel = 'אזל במלאי',
|
|
24
|
-
icons = [],
|
|
25
|
-
classNames = {}
|
|
26
|
-
}) => {
|
|
27
|
-
const [lightboxOpen, setLightboxOpen] = useState(false);
|
|
28
|
-
const [videoLightboxOpen, setVideoLightboxOpen] = useState(false);
|
|
29
|
-
|
|
30
|
-
const {
|
|
31
|
-
card: cardClass = 'glass-card',
|
|
32
|
-
title: titleClass = 'title',
|
|
33
|
-
subtitle: subtitleClass = 'subtitle',
|
|
34
|
-
content: contentClass = 'content-text',
|
|
35
|
-
price: priceClass = 'text-price',
|
|
36
|
-
} = classNames;
|
|
37
|
-
|
|
38
|
-
const handleMediaClick = () => {
|
|
39
|
-
if (contentType === 'video') {
|
|
40
|
-
setVideoLightboxOpen(true);
|
|
41
|
-
} else {
|
|
42
|
-
setLightboxOpen(true);
|
|
43
|
-
}
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
const MediaElement = ({ className, style, clickable = false }) => {
|
|
47
|
-
const scaleStyle = {
|
|
48
|
-
...style,
|
|
49
|
-
transform: `scale(${mediaScale})`,
|
|
50
|
-
transformOrigin: 'center'
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
const mediaContent = contentType === 'video' ? (
|
|
54
|
-
<video
|
|
55
|
-
autoPlay
|
|
56
|
-
muted
|
|
57
|
-
loop
|
|
58
|
-
playsInline
|
|
59
|
-
className={`rounded-lg shadow-2xl border border-primary max-w-full ${className}`}
|
|
60
|
-
style={{ ...scaleStyle, objectFit: 'contain' }}
|
|
61
|
-
>
|
|
62
|
-
<source src={videoUrl} type="video/mp4" />
|
|
63
|
-
</video>
|
|
64
|
-
) : (
|
|
65
|
-
<img
|
|
66
|
-
src={imageUrl}
|
|
67
|
-
alt={title}
|
|
68
|
-
className={className}
|
|
69
|
-
style={scaleStyle}
|
|
70
|
-
/>
|
|
71
|
-
);
|
|
72
|
-
|
|
73
|
-
if (clickable) {
|
|
74
|
-
return (
|
|
75
|
-
<button
|
|
76
|
-
onClick={handleMediaClick}
|
|
77
|
-
className="cursor-pointer hover:opacity-90 transition-opacity duration-200 p-0 border-0 bg-transparent"
|
|
78
|
-
aria-label={contentType === 'video' ? 'Open video in fullscreen' : 'Open image in fullscreen'}
|
|
79
|
-
>
|
|
80
|
-
{mediaContent}
|
|
81
|
-
</button>
|
|
82
|
-
);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
return mediaContent;
|
|
86
|
-
};
|
|
87
|
-
|
|
88
|
-
return (
|
|
89
|
-
<section className="relative w-full overflow-visible py-16 px-4">
|
|
90
|
-
{/* Background Section with Text Content */}
|
|
91
|
-
<div className="glass-card relative" style={{ minHeight: '48vh', overflow: 'visible' }}>
|
|
92
|
-
|
|
93
|
-
<div className="max-w-7xl mx-auto px-6 py-16">
|
|
94
|
-
<div className="grid grid-cols-1 lg:grid-cols-2 gap-12 items-center">
|
|
95
|
-
{/* Text Content */}
|
|
96
|
-
<div
|
|
97
|
-
className={`text-center lg:text-right space-y-6 ${reverse ? 'lg:order-2' : 'lg:order-1'}`}
|
|
98
|
-
dir="rtl"
|
|
99
|
-
>
|
|
100
|
-
<h2 className={titleClass}>{title}</h2>
|
|
101
|
-
<h3 className={subtitleClass} style={{ whiteSpace: 'pre-line' }}>{subtitle}</h3>
|
|
102
|
-
<p className={contentClass + " max-w-md mx-auto lg:mx-0" } style={{ whiteSpace: 'pre-line' }}>{description}</p>
|
|
103
|
-
|
|
104
|
-
{/* Price Section */}
|
|
105
|
-
{price && (
|
|
106
|
-
<div className="text-center lg:text-right mt-4">
|
|
107
|
-
{(discountPrice ?? null) !== null && discountPrice < price ? (
|
|
108
|
-
<div className="space-y-1">
|
|
109
|
-
<div className="flex items-center justify-center lg:justify-start gap-2">
|
|
110
|
-
<span className={"text-2xl font-bold " + priceClass}>
|
|
111
|
-
₪{discountPrice}
|
|
112
|
-
</span>
|
|
113
|
-
<span className="text-sm content-text line-through">
|
|
114
|
-
₪{price}
|
|
115
|
-
</span>
|
|
116
|
-
<span className="bg-red-500 text-white px-2 py-1 rounded-full text-xs font-bold">
|
|
117
|
-
-{Math.round(((price - discountPrice) / price) * 100)}%
|
|
118
|
-
</span>
|
|
119
|
-
</div>
|
|
120
|
-
<div className="text-xs content-text font-medium">
|
|
121
|
-
חיסכון של ₪{price - discountPrice}
|
|
122
|
-
</div>
|
|
123
|
-
</div>
|
|
124
|
-
) : (
|
|
125
|
-
<span className={"text-2xl font-bold " + priceClass}>
|
|
126
|
-
₪{price}
|
|
127
|
-
</span>
|
|
128
|
-
)}
|
|
129
|
-
</div>
|
|
130
|
-
)}
|
|
131
|
-
|
|
132
|
-
{/* Stock Availability */}
|
|
133
|
-
{amountAvailable !== undefined && amountAvailable > 0 && (
|
|
134
|
-
<div className="text-center lg:text-right text-sm content-text font-medium">
|
|
135
|
-
{availableLabel}: {amountAvailable}
|
|
136
|
-
</div>
|
|
137
|
-
)}
|
|
138
|
-
|
|
139
|
-
<div className="py-4 mb-4">
|
|
140
|
-
{/* Sold Out Badge */}
|
|
141
|
-
{amountAvailable !== undefined && amountAvailable <= 0 ? (
|
|
142
|
-
<div className="inline-block z-20 bg-red-500 text-white px-8 py-3 rounded-lg text-lg font-bold shadow-2xl">
|
|
143
|
-
{soldOutLabel}
|
|
144
|
-
</div>
|
|
145
|
-
) : (
|
|
146
|
-
buttonLabel && (
|
|
147
|
-
<RoundButton
|
|
148
|
-
variant="primary"
|
|
149
|
-
onClick={onButtonClick}
|
|
150
|
-
disabled={amountAvailable !== undefined && amountAvailable <= 0}
|
|
151
|
-
>
|
|
152
|
-
{buttonLabel}
|
|
153
|
-
</RoundButton>
|
|
154
|
-
)
|
|
155
|
-
)}
|
|
156
|
-
</div>
|
|
157
|
-
|
|
158
|
-
{/* Icons Section */}
|
|
159
|
-
{icons.length > 0 && (
|
|
160
|
-
<div className="flex flex-wrap justify-center lg:justify-start gap-10 mt-6">
|
|
161
|
-
{icons.map((icon, index) => (
|
|
162
|
-
<div key={index} className="content-text flex flex-col items-center justify-center rounded-lg shadow-sm pb-10">
|
|
163
|
-
{icon}
|
|
164
|
-
</div>
|
|
165
|
-
))}
|
|
166
|
-
</div>
|
|
167
|
-
)}
|
|
168
|
-
</div>
|
|
169
|
-
|
|
170
|
-
{/* Desktop image/video positioning */}
|
|
171
|
-
{!absolutePosition && (
|
|
172
|
-
<div className={`hidden lg:block ${reverse ? 'lg:order-1' : 'lg:order-2'}`}>
|
|
173
|
-
<motion.div
|
|
174
|
-
key={'image'}
|
|
175
|
-
initial={{ opacity: 0, x: reverse ? -50 : 50 }}
|
|
176
|
-
whileInView={{ opacity: 1, x: 0 }}
|
|
177
|
-
viewport={{ once: true }}
|
|
178
|
-
transition={{ delay: 0.5 }}
|
|
179
|
-
className="h-full flex items-center justify-center"
|
|
180
|
-
>
|
|
181
|
-
<MediaElement
|
|
182
|
-
className="rounded-lg shadow-2xl border border-primary max-w-full"
|
|
183
|
-
style={{
|
|
184
|
-
maxHeight: '450px',
|
|
185
|
-
width: 'auto',
|
|
186
|
-
height: 'auto'
|
|
187
|
-
}}
|
|
188
|
-
clickable={true}
|
|
189
|
-
/>
|
|
190
|
-
</motion.div>
|
|
191
|
-
</div>
|
|
192
|
-
)}
|
|
193
|
-
</div>
|
|
194
|
-
</div>
|
|
195
|
-
|
|
196
|
-
{/* full-width mobile image */}
|
|
197
|
-
<div className="block lg:hidden mt-8 w-full">
|
|
198
|
-
<MediaElement
|
|
199
|
-
className="w-full h-auto rounded-none shadow-2xls"
|
|
200
|
-
clickable={true}
|
|
201
|
-
/>
|
|
202
|
-
</div>
|
|
203
|
-
</div>
|
|
204
|
-
|
|
205
|
-
{/* Floating Image Container (desktop only, absolute positioned) */}
|
|
206
|
-
{absolutePosition && (
|
|
207
|
-
<div
|
|
208
|
-
className="hidden lg:block absolute"
|
|
209
|
-
style={{
|
|
210
|
-
zIndex: 10,
|
|
211
|
-
width: '30vw',
|
|
212
|
-
maxWidth: '40vw',
|
|
213
|
-
top: '2rem',
|
|
214
|
-
left: reverse ? '4rem' : 'auto',
|
|
215
|
-
right: reverse ? 'auto' : '4rem',
|
|
216
|
-
transform: 'none'
|
|
217
|
-
}}
|
|
218
|
-
>
|
|
219
|
-
<div className="relative">
|
|
220
|
-
<motion.div
|
|
221
|
-
key={'image'}
|
|
222
|
-
initial={{ opacity: 0, x: reverse ? -50 : 50 }}
|
|
223
|
-
whileInView={{ opacity: 1, x: 0 }}
|
|
224
|
-
viewport={{ once: true }}
|
|
225
|
-
transition={{ delay: 0.5 }}
|
|
226
|
-
className="h-full"
|
|
227
|
-
>
|
|
228
|
-
<MediaElement
|
|
229
|
-
className="rounded-lg shadow-2xl border border-primary"
|
|
230
|
-
style={{
|
|
231
|
-
maxHeight: '450px',
|
|
232
|
-
maxWidth: '100%',
|
|
233
|
-
width: 'auto',
|
|
234
|
-
height: 'auto'
|
|
235
|
-
}}
|
|
236
|
-
clickable={true}
|
|
237
|
-
/>
|
|
238
|
-
</motion.div>
|
|
239
|
-
</div>
|
|
240
|
-
</div>
|
|
241
|
-
)}
|
|
242
|
-
|
|
243
|
-
{/* Image Lightbox */}
|
|
244
|
-
{contentType === 'image' && imageUrl && (
|
|
245
|
-
<ImageLightbox
|
|
246
|
-
images={[imageUrl]}
|
|
247
|
-
currentIndex={0}
|
|
248
|
-
isOpen={lightboxOpen}
|
|
249
|
-
onClose={() => setLightboxOpen(false)}
|
|
250
|
-
onNavigate={() => {}}
|
|
251
|
-
/>
|
|
252
|
-
)}
|
|
253
|
-
|
|
254
|
-
{/* Video Lightbox */}
|
|
255
|
-
{contentType === 'video' && videoUrl && (
|
|
256
|
-
<VideoLightbox
|
|
257
|
-
videoUrl={videoUrl}
|
|
258
|
-
isOpen={videoLightboxOpen}
|
|
259
|
-
onClose={() => setVideoLightboxOpen(false)}
|
|
260
|
-
title={title}
|
|
261
|
-
/>
|
|
262
|
-
)}
|
|
263
|
-
</section>
|
|
264
|
-
);
|
|
265
|
-
};
|
|
266
|
-
|
|
267
|
-
export default LargeItemCard;
|
|
@@ -1,247 +0,0 @@
|
|
|
1
|
-
import React, { useState, useEffect, useCallback } from 'react';
|
|
2
|
-
import ImageLightbox from './elements/ImageLightbox'
|
|
3
|
-
|
|
4
|
-
const MasonryImageList = ({ images = [], cols = 3, onImageClick = () => { } }) => {
|
|
5
|
-
// cols is the desktop column count; mobile will be forced to 2 via CSS
|
|
6
|
-
return (
|
|
7
|
-
<>
|
|
8
|
-
<div
|
|
9
|
-
className="masonry-columns h-full overflow-y-auto"
|
|
10
|
-
// set CSS variable so the component can control desktop column count
|
|
11
|
-
style={{ ['--masonry-cols']: cols }}
|
|
12
|
-
>
|
|
13
|
-
{images.map((img, i) => (
|
|
14
|
-
<button
|
|
15
|
-
key={i}
|
|
16
|
-
className="masonry-item w-full block mb-4 p-0 border-0 bg-transparent text-left"
|
|
17
|
-
onClick={() => onImageClick(i)}
|
|
18
|
-
aria-label={`open image ${i + 1}`}
|
|
19
|
-
>
|
|
20
|
-
<img
|
|
21
|
-
src={img.src || img}
|
|
22
|
-
alt={img.alt || `image-${i + 1}`}
|
|
23
|
-
loading="lazy"
|
|
24
|
-
className="masonry-img w-full h-auto rounded-lg block"
|
|
25
|
-
style={{ display: 'block' }}
|
|
26
|
-
/>
|
|
27
|
-
</button>
|
|
28
|
-
))}
|
|
29
|
-
</div>
|
|
30
|
-
|
|
31
|
-
<style jsx>{`
|
|
32
|
-
/* Container uses CSS columns to create the masonry stacking */
|
|
33
|
-
.masonry-columns {
|
|
34
|
-
column-gap: 0.75rem;
|
|
35
|
-
/* default: mobile 2 columns */
|
|
36
|
-
column-count: 2;
|
|
37
|
-
padding-right: 0.25rem; /* small padding so rtl scroll doesn't cut content */
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/* Desktop: use the --masonry-cols variable */
|
|
41
|
-
@media (min-width: 1024px) {
|
|
42
|
-
.masonry-columns {
|
|
43
|
-
column-count: var(--masonry-cols);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/* Make each item avoid breaking across columns and have bottom margin */
|
|
48
|
-
.masonry-item {
|
|
49
|
-
/* avoid splitting an item between columns */
|
|
50
|
-
break-inside: avoid;
|
|
51
|
-
-webkit-column-break-inside: avoid;
|
|
52
|
-
page-break-inside: avoid;
|
|
53
|
-
margin-bottom: 0.75rem;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/* Image style */
|
|
57
|
-
.masonry-img {
|
|
58
|
-
width: 100%;
|
|
59
|
-
height: auto;
|
|
60
|
-
display: block;
|
|
61
|
-
object-fit: cover;
|
|
62
|
-
/* give images a subtle shadow & transition if desired */
|
|
63
|
-
transition: transform 0.2s ease, box-shadow 0.15s ease;
|
|
64
|
-
}
|
|
65
|
-
.masonry-item:hover .masonry-img {
|
|
66
|
-
transform: translateY(-2px) scale(1.01);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/* Optional: nicer scrollbar for the image column */
|
|
70
|
-
.masonry-columns::-webkit-scrollbar {
|
|
71
|
-
width: 10px;
|
|
72
|
-
}
|
|
73
|
-
.masonry-columns::-webkit-scrollbar-thumb {
|
|
74
|
-
border-radius: 9999px;
|
|
75
|
-
background: rgba(0, 0, 0, 0.12);
|
|
76
|
-
}
|
|
77
|
-
`}</style>
|
|
78
|
-
</>
|
|
79
|
-
);
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
const MasonryItemCard = ({
|
|
83
|
-
images = [],
|
|
84
|
-
title,
|
|
85
|
-
subtitle,
|
|
86
|
-
description,
|
|
87
|
-
reverse = false,
|
|
88
|
-
textCols = 1,
|
|
89
|
-
imagesCols = 1,
|
|
90
|
-
mobileHeight = '60vh', // used for small screens
|
|
91
|
-
enableLightbox = true,
|
|
92
|
-
onImageClick,
|
|
93
|
-
className = '',
|
|
94
|
-
cols = 3, // default for desktop masonry (we want 3 per row on desktop)
|
|
95
|
-
...props
|
|
96
|
-
}) => {
|
|
97
|
-
const [lightboxOpen, setLightboxOpen] = useState(false);
|
|
98
|
-
const [currentImageIndex, setCurrentImageIndex] = useState(0);
|
|
99
|
-
|
|
100
|
-
const totalCols = textCols + imagesCols;
|
|
101
|
-
// CSS variables to be applied to grid wrapper:
|
|
102
|
-
const textPct = `${(textCols / totalCols) * 100}%`;
|
|
103
|
-
const imagesPct = `${(imagesCols / totalCols) * 100}%`;
|
|
104
|
-
|
|
105
|
-
const handleImageClick = useCallback(
|
|
106
|
-
(index) => {
|
|
107
|
-
setCurrentImageIndex(index);
|
|
108
|
-
|
|
109
|
-
if (onImageClick) onImageClick(images[index], index);
|
|
110
|
-
|
|
111
|
-
if (enableLightbox) setLightboxOpen(true);
|
|
112
|
-
},
|
|
113
|
-
[images, onImageClick, enableLightbox]
|
|
114
|
-
);
|
|
115
|
-
|
|
116
|
-
const handleLightboxClose = useCallback(() => setLightboxOpen(false), []);
|
|
117
|
-
const handleLightboxNavigate = useCallback((index) => setCurrentImageIndex(index), []);
|
|
118
|
-
|
|
119
|
-
if (!images || images.length === 0) {
|
|
120
|
-
return (
|
|
121
|
-
<section className={`py-16 px-8 ${className}`} {...props}>
|
|
122
|
-
<div className="glass-card relative min-h-[48vh]">
|
|
123
|
-
<div className="max-w-7xl mx-auto px-6 pt-16">
|
|
124
|
-
<div className="text-center" dir="rtl">
|
|
125
|
-
<h2 className="title mb-4">{title}</h2>
|
|
126
|
-
<h3 className="subtitle mb-6">{subtitle}</h3>
|
|
127
|
-
<p className="content-text">אין תמונות להצגה</p>
|
|
128
|
-
</div>
|
|
129
|
-
</div>
|
|
130
|
-
</div>
|
|
131
|
-
</section>
|
|
132
|
-
);
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
return (
|
|
136
|
-
<>
|
|
137
|
-
<section className={`relative w-full overflow-visible py-16 px-4 ${className}`} {...props}>
|
|
138
|
-
{/* NOTE: --text-frac / --image-frac are set inline so CSS can use them at lg and up */}
|
|
139
|
-
<div
|
|
140
|
-
className="glass-card relative min-h-[48vh] flex flex-col"
|
|
141
|
-
style={{ '--text-frac': textPct, '--image-frac': imagesPct }}
|
|
142
|
-
>
|
|
143
|
-
{/* top padding/content wrapper */}
|
|
144
|
-
<div className="max-w-7xl mx-auto px-6 pt-16 w-full flex-1">
|
|
145
|
-
{/* layout-grid will switch to two-column fractions on lg+ */}
|
|
146
|
-
<div className="layout-grid w-full h-full" dir="rtl">
|
|
147
|
-
{/* Text Content */}
|
|
148
|
-
<div
|
|
149
|
-
className={`text-column ${reverse ? 'lg:order-2' : 'lg:order-1'} px-2`}
|
|
150
|
-
>
|
|
151
|
-
<h2 className="title mb-6">{title}</h2>
|
|
152
|
-
<h3 className="subtitle mb-6">{subtitle}</h3>
|
|
153
|
-
<p className="content-text max-w-md mx-auto lg:mx-0">{description}</p>
|
|
154
|
-
</div>
|
|
155
|
-
|
|
156
|
-
{/* Desktop Masonry Images (visible on lg+) */}
|
|
157
|
-
<div
|
|
158
|
-
className={`image-column hidden lg:block ${reverse ? 'lg:order-1' : 'lg:order-2'} px-2 mb-8`}
|
|
159
|
-
>
|
|
160
|
-
{/* image-column fills available height and scrolls if needed */}
|
|
161
|
-
<MasonryImageList
|
|
162
|
-
images={images}
|
|
163
|
-
onImageClick={handleImageClick}
|
|
164
|
-
cols={cols /* desktop: 3 by default from caller prop */}
|
|
165
|
-
/>
|
|
166
|
-
</div>
|
|
167
|
-
</div>
|
|
168
|
-
</div>
|
|
169
|
-
|
|
170
|
-
{/* Mobile Masonry Images (visible on mobile) */}
|
|
171
|
-
<div
|
|
172
|
-
className="block lg:hidden mt-8 w-full px-4"
|
|
173
|
-
style={{ height: mobileHeight }}
|
|
174
|
-
>
|
|
175
|
-
<MasonryImageList
|
|
176
|
-
images={images}
|
|
177
|
-
onImageClick={handleImageClick}
|
|
178
|
-
cols={2} // mobile: 2 per row
|
|
179
|
-
/>
|
|
180
|
-
</div>
|
|
181
|
-
</div>
|
|
182
|
-
</section>
|
|
183
|
-
|
|
184
|
-
{/* Lightbox */}
|
|
185
|
-
{enableLightbox && (
|
|
186
|
-
<ImageLightbox
|
|
187
|
-
images={images}
|
|
188
|
-
currentIndex={currentImageIndex}
|
|
189
|
-
isOpen={lightboxOpen}
|
|
190
|
-
onClose={handleLightboxClose}
|
|
191
|
-
onNavigate={handleLightboxNavigate}
|
|
192
|
-
/>
|
|
193
|
-
)}
|
|
194
|
-
|
|
195
|
-
{/* Component styles */}
|
|
196
|
-
<style jsx>{`
|
|
197
|
-
/* The overall grid - single column by default; switches to two fractional columns at lg+ */
|
|
198
|
-
.layout-grid {
|
|
199
|
-
display: grid;
|
|
200
|
-
grid-template-columns: 1fr;
|
|
201
|
-
gap: 3rem;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
/* At large screens, use the fractional widths computed in inline styles */
|
|
205
|
-
@media (min-width: 1024px) {
|
|
206
|
-
.layout-grid {
|
|
207
|
-
/* use CSS variables set from JS: --text-frac and --image-frac */
|
|
208
|
-
grid-template-columns: var(--text-frac) var(--image-frac);
|
|
209
|
-
align-items: start;
|
|
210
|
-
gap: 3rem;
|
|
211
|
-
height: 100%;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
.text-column {
|
|
215
|
-
/* ensure text column doesn't overflow the parent */
|
|
216
|
-
height: 100%;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
.image-column {
|
|
220
|
-
/* make image column take full height of the parent wrapper and be scrollable */
|
|
221
|
-
height: 100%;
|
|
222
|
-
max-height: 100%;
|
|
223
|
-
overflow-y: auto;
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
/* Keep mobile images area scrollable if images exceed mobileHeight (we set that inline) */
|
|
228
|
-
@media (max-width: 1023px) {
|
|
229
|
-
.layout-grid {
|
|
230
|
-
gap: 1.5rem;
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
/* Optional: small visual tweak to hide scrollbar in WebKit (if you want) */
|
|
235
|
-
.image-column::-webkit-scrollbar {
|
|
236
|
-
width: 8px;
|
|
237
|
-
}
|
|
238
|
-
.image-column::-webkit-scrollbar-thumb {
|
|
239
|
-
border-radius: 9999px;
|
|
240
|
-
background: rgba(0, 0, 0, 0.2);
|
|
241
|
-
}
|
|
242
|
-
`}</style>
|
|
243
|
-
</>
|
|
244
|
-
);
|
|
245
|
-
};
|
|
246
|
-
|
|
247
|
-
export default MasonryItemCard;
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
// Menu.d.ts
|
|
2
|
-
export interface BusinessInfo {
|
|
3
|
-
name: string;
|
|
4
|
-
logo: string;
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
export interface NavigationItem {
|
|
8
|
-
label: string;
|
|
9
|
-
href: string;
|
|
10
|
-
adminRoute?: boolean;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export interface MenuProps {
|
|
14
|
-
businessInfo: BusinessInfo;
|
|
15
|
-
navigationItems: NavigationItem[];
|
|
16
|
-
AuthButtonsComponent?: React.ComponentType<{ isMobile?: boolean }>;
|
|
17
|
-
isAdmin?: boolean;
|
|
18
|
-
mobileBreakpoint?: number;
|
|
19
|
-
logoClassName?: string;
|
|
20
|
-
menuItemClassName?: string;
|
|
21
|
-
sidebarWidth?: string;
|
|
22
|
-
onMenuItemClick?: (item: NavigationItem) => void;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
declare const Menu: React.FC<MenuProps>;
|
|
26
|
-
export default Menu;
|