@codesinger0/shared-components 1.1.23 → 1.1.25
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.
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
import React, { useState, useEffect, useCallback } from 'react';
|
|
2
|
+
import useEmblaCarousel from 'embla-carousel-react';
|
|
3
|
+
import Autoplay from 'embla-carousel-autoplay';
|
|
4
|
+
|
|
5
|
+
const FullscreenCarousel = ({
|
|
6
|
+
slides = [],
|
|
7
|
+
desktopHeight = '100vh',
|
|
8
|
+
mobileHeight = '100vh',
|
|
9
|
+
autoplay = true,
|
|
10
|
+
scrollInterval = 5000,
|
|
11
|
+
loop = true,
|
|
12
|
+
showDots = true,
|
|
13
|
+
showArrows = true,
|
|
14
|
+
className = '',
|
|
15
|
+
...props
|
|
16
|
+
}) => {
|
|
17
|
+
const [prevBtnEnabled, setPrevBtnEnabled] = useState(false);
|
|
18
|
+
const [nextBtnEnabled, setNextBtnEnabled] = useState(false);
|
|
19
|
+
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
20
|
+
const [scrollSnaps, setScrollSnaps] = useState([]);
|
|
21
|
+
|
|
22
|
+
// Embla carousel setup
|
|
23
|
+
const autoplayOptions = {
|
|
24
|
+
delay: scrollInterval,
|
|
25
|
+
stopOnInteraction: false,
|
|
26
|
+
stopOnMouseEnter: false,
|
|
27
|
+
rootNode: (emblaRoot) => emblaRoot.parentElement,
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const plugins = autoplay ? [Autoplay(autoplayOptions)] : [];
|
|
31
|
+
|
|
32
|
+
const [emblaRef, emblaApi] = useEmblaCarousel(
|
|
33
|
+
{
|
|
34
|
+
loop: loop,
|
|
35
|
+
direction: 'rtl',
|
|
36
|
+
skipSnaps: false,
|
|
37
|
+
},
|
|
38
|
+
plugins
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
const scrollPrev = useCallback(() => emblaApi && emblaApi.scrollPrev(), [emblaApi]);
|
|
42
|
+
const scrollNext = useCallback(() => emblaApi && emblaApi.scrollNext(), [emblaApi]);
|
|
43
|
+
const scrollTo = useCallback((index) => emblaApi && emblaApi.scrollTo(index), [emblaApi]);
|
|
44
|
+
|
|
45
|
+
const onSelect = useCallback(() => {
|
|
46
|
+
if (!emblaApi) return;
|
|
47
|
+
setSelectedIndex(emblaApi.selectedScrollSnap());
|
|
48
|
+
setPrevBtnEnabled(emblaApi.canScrollPrev());
|
|
49
|
+
setNextBtnEnabled(emblaApi.canScrollNext());
|
|
50
|
+
}, [emblaApi]);
|
|
51
|
+
|
|
52
|
+
useEffect(() => {
|
|
53
|
+
if (!emblaApi) return;
|
|
54
|
+
onSelect();
|
|
55
|
+
setScrollSnaps(emblaApi.scrollSnapList());
|
|
56
|
+
emblaApi.on('select', onSelect);
|
|
57
|
+
emblaApi.on('reInit', onSelect);
|
|
58
|
+
}, [emblaApi, onSelect]);
|
|
59
|
+
|
|
60
|
+
if (!slides || slides.length === 0) {
|
|
61
|
+
return (
|
|
62
|
+
<div className="w-full flex items-center justify-center bg-gray-100" style={{ height: desktopHeight }}>
|
|
63
|
+
<p className="text-gray-500">No slides to display</p>
|
|
64
|
+
</div>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
<div className={`fullscreen-carousel-wrapper ${className}`} {...props}>
|
|
70
|
+
<div className="fullscreen-carousel" dir="rtl">
|
|
71
|
+
<div className="fullscreen-carousel__viewport" ref={emblaRef}>
|
|
72
|
+
<div className="fullscreen-carousel__container">
|
|
73
|
+
{slides.map((slide, index) => (
|
|
74
|
+
<div key={index} className="fullscreen-carousel__slide">
|
|
75
|
+
{/* Desktop Layout - Horizontal Split */}
|
|
76
|
+
<div
|
|
77
|
+
className="hidden md:grid md:grid-cols-2 w-full h-full"
|
|
78
|
+
style={{ height: desktopHeight }}
|
|
79
|
+
>
|
|
80
|
+
<div className="w-full h-full">
|
|
81
|
+
{slide.leftContent}
|
|
82
|
+
</div>
|
|
83
|
+
<div className="w-full h-full">
|
|
84
|
+
{slide.rightContent}
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
|
|
88
|
+
{/* Mobile Layout - Vertical Split */}
|
|
89
|
+
<div
|
|
90
|
+
className="md:hidden grid grid-rows-2 w-full h-full"
|
|
91
|
+
style={{ height: mobileHeight }}
|
|
92
|
+
>
|
|
93
|
+
<div className="w-full h-full">
|
|
94
|
+
{slide.leftContent}
|
|
95
|
+
</div>
|
|
96
|
+
<div className="w-full h-full">
|
|
97
|
+
{slide.rightContent}
|
|
98
|
+
</div>
|
|
99
|
+
</div>
|
|
100
|
+
</div>
|
|
101
|
+
))}
|
|
102
|
+
</div>
|
|
103
|
+
</div>
|
|
104
|
+
|
|
105
|
+
{/* Dots indicator */}
|
|
106
|
+
{showDots && slides.length > 1 && (
|
|
107
|
+
<div className="fullscreen-carousel__dots">
|
|
108
|
+
{scrollSnaps.map((_, index) => (
|
|
109
|
+
<button
|
|
110
|
+
key={index}
|
|
111
|
+
className={`fullscreen-carousel__dot ${index === selectedIndex ? 'fullscreen-carousel__dot--selected' : ''}`}
|
|
112
|
+
onClick={() => scrollTo(index)}
|
|
113
|
+
aria-label={`Go to slide ${index + 1}`}
|
|
114
|
+
/>
|
|
115
|
+
))}
|
|
116
|
+
</div>
|
|
117
|
+
)}
|
|
118
|
+
|
|
119
|
+
{/* Navigation buttons */}
|
|
120
|
+
{showArrows && slides.length > 1 && (
|
|
121
|
+
<>
|
|
122
|
+
<button
|
|
123
|
+
className="fullscreen-carousel__button fullscreen-carousel__button--prev"
|
|
124
|
+
onClick={scrollPrev}
|
|
125
|
+
disabled={!prevBtnEnabled}
|
|
126
|
+
aria-label="Previous slide"
|
|
127
|
+
>
|
|
128
|
+
<svg className="fullscreen-carousel__button__svg" viewBox="0 0 24 24">
|
|
129
|
+
<path d="M9 18l6-6-6-6" />
|
|
130
|
+
</svg>
|
|
131
|
+
</button>
|
|
132
|
+
<button
|
|
133
|
+
className="fullscreen-carousel__button fullscreen-carousel__button--next"
|
|
134
|
+
onClick={scrollNext}
|
|
135
|
+
disabled={!nextBtnEnabled}
|
|
136
|
+
aria-label="Next slide"
|
|
137
|
+
>
|
|
138
|
+
<svg className="fullscreen-carousel__button__svg" viewBox="0 0 24 24">
|
|
139
|
+
<path d="M15 18l-6-6 6-6" />
|
|
140
|
+
</svg>
|
|
141
|
+
</button>
|
|
142
|
+
</>
|
|
143
|
+
)}
|
|
144
|
+
</div>
|
|
145
|
+
|
|
146
|
+
{/* Styles */}
|
|
147
|
+
<style jsx>{`
|
|
148
|
+
.fullscreen-carousel-wrapper {
|
|
149
|
+
position: relative;
|
|
150
|
+
width: 100%;
|
|
151
|
+
overflow: hidden;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.fullscreen-carousel {
|
|
155
|
+
position: relative;
|
|
156
|
+
width: 100%;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
.fullscreen-carousel__viewport {
|
|
160
|
+
overflow: hidden;
|
|
161
|
+
width: 100%;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
.fullscreen-carousel__container {
|
|
165
|
+
display: flex;
|
|
166
|
+
width: 100%;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.fullscreen-carousel__slide {
|
|
170
|
+
flex: 0 0 100%;
|
|
171
|
+
min-width: 0;
|
|
172
|
+
position: relative;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
.fullscreen-carousel__button {
|
|
176
|
+
display: none;
|
|
177
|
+
background-color: rgba(255, 255, 255, 0.9);
|
|
178
|
+
color: #333;
|
|
179
|
+
border: none;
|
|
180
|
+
width: 3rem;
|
|
181
|
+
height: 3rem;
|
|
182
|
+
border-radius: 50%;
|
|
183
|
+
cursor: pointer;
|
|
184
|
+
align-items: center;
|
|
185
|
+
justify-content: center;
|
|
186
|
+
transition: all 0.2s ease;
|
|
187
|
+
box-shadow: 0 2px 8px rgba(0,0,0,0.15);
|
|
188
|
+
position: absolute;
|
|
189
|
+
top: 50%;
|
|
190
|
+
transform: translateY(-50%);
|
|
191
|
+
z-index: 10;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
@media (min-width: 768px) {
|
|
195
|
+
.fullscreen-carousel__button {
|
|
196
|
+
display: flex;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
.fullscreen-carousel__button--prev {
|
|
201
|
+
right: 2rem;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
.fullscreen-carousel__button--next {
|
|
205
|
+
left: 2rem;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
.fullscreen-carousel__controls {
|
|
209
|
+
display: flex;
|
|
210
|
+
justify-content: center;
|
|
211
|
+
gap: 1rem;
|
|
212
|
+
position: absolute;
|
|
213
|
+
bottom: 4rem;
|
|
214
|
+
left: 50%;
|
|
215
|
+
transform: translateX(-50%);
|
|
216
|
+
z-index: 10;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
.fullscreen-carousel__button:hover:not(:disabled) {
|
|
220
|
+
background-color: rgba(255, 255, 255, 1);
|
|
221
|
+
transform: scale(1.1);
|
|
222
|
+
box-shadow: 0 4px 12px rgba(0,0,0,0.2);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
.fullscreen-carousel__button:disabled {
|
|
226
|
+
opacity: 0.3;
|
|
227
|
+
cursor: not-allowed;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
.fullscreen-carousel__button__svg {
|
|
231
|
+
width: 1rem;
|
|
232
|
+
height: 1rem;
|
|
233
|
+
fill: none;
|
|
234
|
+
stroke: currentColor;
|
|
235
|
+
stroke-width: 2;
|
|
236
|
+
stroke-linecap: round;
|
|
237
|
+
stroke-linejoin: round;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
.fullscreen-carousel__dots {
|
|
241
|
+
display: flex;
|
|
242
|
+
justify-content: center;
|
|
243
|
+
gap: 0.5rem;
|
|
244
|
+
position: absolute;
|
|
245
|
+
bottom: 2rem;
|
|
246
|
+
left: 50%;
|
|
247
|
+
transform: translateX(-50%);
|
|
248
|
+
z-index: 10;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
.fullscreen-carousel__dot {
|
|
252
|
+
background-color: rgba(255, 255, 255, 0.5);
|
|
253
|
+
border: none;
|
|
254
|
+
width: 0.75rem;
|
|
255
|
+
height: 0.75rem;
|
|
256
|
+
border-radius: 50%;
|
|
257
|
+
cursor: pointer;
|
|
258
|
+
opacity: 0.5;
|
|
259
|
+
transition: all 0.2s ease;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
.fullscreen-carousel__dot--selected {
|
|
263
|
+
opacity: 1;
|
|
264
|
+
background-color: rgba(255, 255, 255, 1);
|
|
265
|
+
transform: scale(1.2);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
.fullscreen-carousel__dot:hover {
|
|
269
|
+
opacity: 0.8;
|
|
270
|
+
}
|
|
271
|
+
`}</style>
|
|
272
|
+
</div>
|
|
273
|
+
);
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
export default FullscreenCarousel;
|
|
@@ -19,7 +19,7 @@ const IconGrid = ({ items = [], className = '' }) => {
|
|
|
19
19
|
const GridItem = ({ icon: Icon, text }) => (
|
|
20
20
|
<div className="flex flex-col items-center justify-center p-6 bg-gradient-to-br from-purple-50 to-indigo-50 rounded-2xl hover:shadow-lg transition-shadow duration-300">
|
|
21
21
|
<div className="w-20 h-20 mb-4 flex items-center justify-center">
|
|
22
|
-
<Icon className="w-full h-full text-
|
|
22
|
+
<Icon className="w-full h-full text-main" strokeWidth={1.5} />
|
|
23
23
|
</div>
|
|
24
24
|
<p className="text-center text-gray-800 font-medium text-lg" dir="rtl">
|
|
25
25
|
{text}
|
|
@@ -23,7 +23,7 @@ const TextListCards = ({
|
|
|
23
23
|
|
|
24
24
|
return (
|
|
25
25
|
<section className={`py-20 ${className}`} {...props}>
|
|
26
|
-
<div className="max-w-7xl mx-auto
|
|
26
|
+
<div className="max-w-7xl mx-auto">
|
|
27
27
|
{/* Header Section */}
|
|
28
28
|
{(title || subtitle) && (
|
|
29
29
|
<motion.div
|
|
@@ -39,9 +39,6 @@ const TextListCards = ({
|
|
|
39
39
|
</h2>
|
|
40
40
|
)}
|
|
41
41
|
|
|
42
|
-
{/* Decorative Line */}
|
|
43
|
-
<div className="w-20 h-1 bg-gradient-to-r from-primary to-primary-bright mx-auto mb-4"></div>
|
|
44
|
-
|
|
45
42
|
{subtitle && (
|
|
46
43
|
<p className="subtitle max-w-3xl mx-auto mt-6">
|
|
47
44
|
{subtitle}
|
|
@@ -51,7 +48,7 @@ const TextListCards = ({
|
|
|
51
48
|
)}
|
|
52
49
|
|
|
53
50
|
{/* Cards Grid */}
|
|
54
|
-
<div className="grid lg:grid-cols-3 gap-
|
|
51
|
+
<div className="grid lg:grid-cols-3 gap-6">
|
|
55
52
|
{items.map((item, index) => (
|
|
56
53
|
<motion.div
|
|
57
54
|
key={item.title || index}
|
|
@@ -61,7 +58,7 @@ const TextListCards = ({
|
|
|
61
58
|
transition={{ delay: index * 0.2 }}
|
|
62
59
|
className="h-full"
|
|
63
60
|
>
|
|
64
|
-
<div className="glass-card h-full p-
|
|
61
|
+
<div className="glass-card h-full p-6 text-center hover:scale-105 transition-all duration-300">
|
|
65
62
|
{/* Icon */}
|
|
66
63
|
{item.icon && (
|
|
67
64
|
<div className="w-16 h-16 bg-gradient-to-br from-primary to-primary-bright rounded-full flex items-center justify-center mx-auto mb-6">
|
package/dist/index.js
CHANGED
|
@@ -20,6 +20,7 @@ export { default as ProductsSidebar } from './components/products/ProductsSideba
|
|
|
20
20
|
export { default as MyOrdersDisplay } from './components/MyOrdersDisplay'
|
|
21
21
|
export { default as AccessibilityMenu } from './components/AccessibilityMenu'
|
|
22
22
|
export { default as FloatingWhatsAppButton } from './components/FloatingWhatsAppButton'
|
|
23
|
+
export { default as FullscreenCarousel } from './components/FullscreenCarousel'
|
|
23
24
|
|
|
24
25
|
// Modals
|
|
25
26
|
export { default as ItemDetailsModal } from './components/modals/ItemDetailsModal'
|