@codesinger0/shared-components 1.1.21 → 1.1.23

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,144 @@
1
+ import React from 'react';
2
+ import { Users, Monitor, Smartphone, ThumbsUp, Paintbrush } from 'lucide-react';
3
+
4
+ const IconGrid = ({ items = [], className = '' }) => {
5
+ if (!items || items.length === 0) {
6
+ return null;
7
+ }
8
+
9
+ // Calculate rows distribution
10
+ const totalItems = items.length;
11
+ const isEven = totalItems % 2 === 0;
12
+ const itemsPerRow = Math.ceil(totalItems / 2);
13
+
14
+ // Split items into rows
15
+ const topRowCount = isEven ? itemsPerRow : itemsPerRow;
16
+ const topRow = items.slice(0, topRowCount);
17
+ const bottomRow = items.slice(topRowCount);
18
+
19
+ const GridItem = ({ icon: Icon, text }) => (
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
+ <div className="w-20 h-20 mb-4 flex items-center justify-center">
22
+ <Icon className="w-full h-full text-indigo-600" strokeWidth={1.5} />
23
+ </div>
24
+ <p className="text-center text-gray-800 font-medium text-lg" dir="rtl">
25
+ {text}
26
+ </p>
27
+ </div>
28
+ );
29
+
30
+ return (
31
+ <div className={`w-full max-w-6xl mx-auto px-4 ${className}`}>
32
+ {/* Mobile: Single column */}
33
+ <div className="md:hidden space-y-4">
34
+ {items.map((item, index) => (
35
+ <GridItem key={index} {...item} />
36
+ ))}
37
+ </div>
38
+
39
+ {/* Desktop: Smart grid */}
40
+ <div className="hidden md:block space-y-4 mt-20 mb-20">
41
+ {/* Top row */}
42
+ <div
43
+ className="grid gap-4 mb-4"
44
+ style={{
45
+ gridTemplateColumns: `repeat(${topRowCount}, 1fr)`,
46
+ justifyItems: 'center'
47
+ }}
48
+ >
49
+ {topRow.map((item, index) => (
50
+ <div key={index} className="w-full max-w-sm">
51
+ <GridItem {...item} />
52
+ </div>
53
+ ))}
54
+ </div>
55
+
56
+ {/* Bottom row - centered if fewer items */}
57
+ {bottomRow.length > 0 && (
58
+ <div
59
+ className="grid gap-4"
60
+ style={{
61
+ gridTemplateColumns: `repeat(${bottomRow.length}, 1fr)`,
62
+ justifyItems: 'center',
63
+ maxWidth: bottomRow.length < topRowCount ? `${(bottomRow.length / topRowCount) * 100}%` : '100%',
64
+ margin: '0 auto'
65
+ }}
66
+ >
67
+ {bottomRow.map((item, index) => (
68
+ <div key={index} className="w-full max-w-sm">
69
+ <GridItem {...item} />
70
+ </div>
71
+ ))}
72
+ </div>
73
+ )}
74
+ </div>
75
+ </div>
76
+ );
77
+ };
78
+
79
+ // Demo
80
+ const Demo = () => {
81
+ const demoItems = [
82
+ {
83
+ icon: Users,
84
+ text: 'מתאים לכל סוגי העסקים'
85
+ },
86
+ {
87
+ icon: Monitor,
88
+ text: 'חווית משתמש UX/UI'
89
+ },
90
+ {
91
+ icon: Smartphone,
92
+ text: 'התאמה מלאה לכל המסכים'
93
+ },
94
+ {
95
+ icon: ThumbsUp,
96
+ text: 'תמיכה מלאה'
97
+ },
98
+ {
99
+ icon: Paintbrush,
100
+ text: 'עיצוב יוצר נשימה'
101
+ }
102
+ ];
103
+
104
+ return (
105
+ <div className="min-h-screen bg-gradient-to-br from-gray-50 to-gray-100 py-16">
106
+ <div className="max-w-7xl mx-auto px-4">
107
+ <h1 className="text-4xl font-bold text-center text-gray-800 mb-4" dir="rtl">
108
+ IconGrid Component
109
+ </h1>
110
+ <p className="text-center text-gray-600 mb-12" dir="rtl">
111
+ רכיב גריד חכם המתאים את עצמו למספר הפריטים
112
+ </p>
113
+
114
+ <IconGrid items={demoItems} />
115
+
116
+ <div className="mt-16 bg-white rounded-xl p-8 shadow-lg">
117
+ <h2 className="text-2xl font-bold mb-4" dir="rtl">תכונות:</h2>
118
+ <ul className="space-y-2 text-gray-700" dir="rtl">
119
+ <li>✅ מספר זוגי של פריטים - חלוקה שווה בין שורות</li>
120
+ <li>✅ מספר אי-זוגי - שורה עליונה עם פריט אחד נוסף, ממורכזת</li>
121
+ <li>✅ במובייל - עמודה אנכית בודדת</li>
122
+ <li>✅ תמיכה מלאה ב-RTL</li>
123
+ <li>✅ עיצוב רספונסיבי עם אנימציות</li>
124
+ </ul>
125
+
126
+ <h3 className="text-xl font-bold mt-6 mb-3" dir="rtl">דוגמת שימוש:</h3>
127
+ <pre className="bg-gray-50 p-4 rounded-lg overflow-x-auto text-sm">
128
+ {`const items = [
129
+ { icon: Users, text: 'טקסט 1' },
130
+ { icon: Monitor, text: 'טקסט 2' },
131
+ { icon: Smartphone, text: 'טקסט 3' },
132
+ { icon: ThumbsUp, text: 'טקסט 4' },
133
+ { icon: Paintbrush, text: 'טקסט 5' }
134
+ ];
135
+
136
+ <IconGrid items={items} />`}
137
+ </pre>
138
+ </div>
139
+ </div>
140
+ </div>
141
+ );
142
+ };
143
+
144
+ export default IconGrid;
@@ -4,6 +4,9 @@ import { motion } from 'framer-motion'
4
4
 
5
5
  const LargeItemCard = ({
6
6
  imageUrl,
7
+ videoUrl,
8
+ contentType = 'image',
9
+ mediaScale = 1,
7
10
  title,
8
11
  subtitle,
9
12
  description,
@@ -12,7 +15,8 @@ const LargeItemCard = ({
12
15
  reverse = false,
13
16
  price,
14
17
  discountPrice,
15
- amountAvailable,
18
+ amountAvailable,
19
+ absolutePosition = true,
16
20
  availableLabel = 'נותרו במלאי',
17
21
  soldOutLabel = 'אזל במלאי',
18
22
  icons = [],
@@ -26,14 +30,43 @@ const LargeItemCard = ({
26
30
  price: priceClass = 'text-price',
27
31
  } = classNames;
28
32
 
33
+ const MediaElement = ({ className, style }) => {
34
+ const scaleStyle = {
35
+ ...style,
36
+ transform: `scale(${mediaScale})`,
37
+ transformOrigin: 'center'
38
+ };
39
+ if (contentType === 'video') {
40
+ return (
41
+ <video
42
+ autoPlay
43
+ muted
44
+ loop
45
+ playsInline
46
+ className={`rounded-lg shadow-2xl border border-primary max-w-full ${className}`}
47
+ style={{ ...scaleStyle, objectFit: 'contain' }}
48
+ >
49
+ <source src={videoUrl} type="video/mp4" />
50
+ </video>
51
+ );
52
+ }
53
+ return (
54
+ <img
55
+ src={imageUrl}
56
+ alt={title}
57
+ className={className}
58
+ style={scaleStyle}
59
+ />
60
+ );
61
+ };
62
+
29
63
  return (
30
64
  <section className="relative w-full overflow-visible py-16 px-4">
31
65
  {/* Background Section with Text Content */}
32
66
  <div className="glass-card relative" style={{ minHeight: '48vh', overflow: 'visible' }}>
33
67
 
34
- <div className="max-w-7xl mx-auto px-6 pt-16">
68
+ <div className="max-w-7xl mx-auto px-6 py-16">
35
69
  <div className="grid grid-cols-1 lg:grid-cols-2 gap-12 items-center">
36
-
37
70
  {/* Text Content */}
38
71
  <div
39
72
  className={`text-center lg:text-right space-y-6 ${reverse ? 'lg:order-2' : 'lg:order-1'}`}
@@ -85,13 +118,15 @@ const LargeItemCard = ({
85
118
  {soldOutLabel}
86
119
  </div>
87
120
  ) : (
88
- <RoundButton
89
- variant="primary"
90
- onClick={onButtonClick}
91
- disabled={amountAvailable !== undefined && amountAvailable <= 0}
92
- >
93
- {buttonLabel}
94
- </RoundButton>
121
+ buttonLabel && (
122
+ <RoundButton
123
+ variant="primary"
124
+ onClick={onButtonClick}
125
+ disabled={amountAvailable !== undefined && amountAvailable <= 0}
126
+ >
127
+ {buttonLabel}
128
+ </RoundButton>
129
+ )
95
130
  )}
96
131
  </div>
97
132
 
@@ -107,70 +142,75 @@ const LargeItemCard = ({
107
142
  )}
108
143
  </div>
109
144
 
110
- {/* Space for image positioning (desktop uses floating image, keep spacer) */}
111
- <div className={`hidden lg:block relative lg:h-full ${reverse ? 'lg:order-1' : 'lg:order-2'}`}>
112
- {/* reserved for absolutely positioned image on desktop */}
113
- </div>
145
+ {/* Desktop image/video positioning */}
146
+ {!absolutePosition && (
147
+ <div className={`hidden lg:block ${reverse ? 'lg:order-1' : 'lg:order-2'}`}>
148
+ <motion.div
149
+ key={'image'}
150
+ initial={{ opacity: 0, x: reverse ? -50 : 50 }}
151
+ whileInView={{ opacity: 1, x: 0 }}
152
+ viewport={{ once: true }}
153
+ transition={{ delay: 0.5 }}
154
+ className="h-full flex items-center justify-center"
155
+ >
156
+ <MediaElement
157
+ className="rounded-lg shadow-2xl border border-primary max-w-full"
158
+ style={{
159
+ maxHeight: '450px',
160
+ width: 'auto',
161
+ height: 'auto'
162
+ }}
163
+ />
164
+ </motion.div>
165
+ </div>
166
+ )}
114
167
  </div>
115
-
116
168
  </div>
117
169
 
118
170
  {/* full-width mobile image */}
119
171
  <div className="block lg:hidden mt-8 w-full">
120
- <motion.div
121
- key={'image'}
122
- initial={{ opacity: 0, y: 50 }}
123
- whileInView={{ opacity: 1, y: 0 }}
124
- viewport={{ once: true }}
125
- transition={{ delay: 1 * 0.5 }}
126
- className="h-full"
127
- >
128
- <img
129
- src={imageUrl}
130
- alt={title}
131
- className="w-full h-auto rounded-none shadow-2xls"
132
- />
133
- </motion.div>
172
+ <MediaElement
173
+ className="w-full h-auto rounded-none shadow-2xls"
174
+ />
134
175
  </div>
135
176
  </div>
136
177
 
137
- {/* Floating Image Container (desktop only, unchanged behavior) */}
138
- <div
139
- className="hidden lg:block absolute"
140
- style={{
141
- zIndex: 10,
142
- width: '30vw',
143
- maxWidth: '40vw',
144
- top: '2rem',
145
- left: reverse ? '4rem' : 'auto',
146
- right: reverse ? 'auto' : '4rem',
147
- transform: 'none'
148
- }}
149
- >
150
- <div className="relative">
151
- <motion.div
152
- key={'image'}
153
- initial={{ opacity: 0, x: reverse ? -50 : 50 }}
154
- whileInView={{ opacity: 1, x: 0 }}
155
- viewport={{ once: true }}
156
- transition={{ delay: 1 * 0.5 }}
157
- className="h-full"
158
- >
159
- <img
160
- src={imageUrl}
161
- alt={title}
162
- className="rounded-lg shadow-2xl border border-primary"
163
- style={{
164
- maxHeight: '450px',
165
- maxWidth: '100%',
166
- width: 'auto',
167
- height: 'auto',
168
- objectFit: 'contain'
169
- }}
170
- />
171
- </motion.div>
178
+ {/* Floating Image Container (desktop only, absolute positioned) */}
179
+ {absolutePosition && (
180
+ <div
181
+ className="hidden lg:block absolute"
182
+ style={{
183
+ zIndex: 10,
184
+ width: '30vw',
185
+ maxWidth: '40vw',
186
+ top: '2rem',
187
+ left: reverse ? '4rem' : 'auto',
188
+ right: reverse ? 'auto' : '4rem',
189
+ transform: 'none'
190
+ }}
191
+ >
192
+ <div className="relative">
193
+ <motion.div
194
+ key={'image'}
195
+ initial={{ opacity: 0, x: reverse ? -50 : 50 }}
196
+ whileInView={{ opacity: 1, x: 0 }}
197
+ viewport={{ once: true }}
198
+ transition={{ delay: 0.5 }}
199
+ className="h-full"
200
+ >
201
+ <MediaElement
202
+ className="rounded-lg shadow-2xl border border-primary"
203
+ style={{
204
+ maxHeight: '450px',
205
+ maxWidth: '100%',
206
+ width: 'auto',
207
+ height: 'auto'
208
+ }}
209
+ />
210
+ </motion.div>
211
+ </div>
172
212
  </div>
173
- </div>
213
+ )}
174
214
  </section>
175
215
  );
176
216
  };
@@ -5,6 +5,7 @@ const QAAccordion = ({
5
5
  title = "שאלות נפוצות",
6
6
  subtitle = "מענה לשאלות הנפוצות ביותר",
7
7
  qaItems = [],
8
+ onClickCta = () => {},
8
9
  className = ""
9
10
  }) => {
10
11
  const [openIndex, setOpenIndex] = useState(null);
@@ -65,7 +66,6 @@ const QAAccordion = ({
65
66
  }
66
67
  ];
67
68
 
68
-
69
69
  const items = qaItems.length > 0 ? qaItems : defaultQAItems;
70
70
 
71
71
  return (
@@ -187,7 +187,7 @@ const QAAccordion = ({
187
187
  <p className="content-text mb-4">
188
188
  לא מצאתם את התשובה שחיפשתם?
189
189
  </p>
190
- <button className="btn-primary">
190
+ <button className="btn-primary" onClick={onClickCta}>
191
191
  צרו קשר
192
192
  </button>
193
193
  </div>
@@ -30,7 +30,7 @@ export default function VideoCard({ videoUrl, title, description, index }) {
30
30
  onMouseLeave={() => setShowControls(false)}
31
31
  >
32
32
  {/* Video Container */}
33
- <div className="max-h-20 overflow-hidden">
33
+ <div className="relative w-full aspect-video overflow-hidden">
34
34
  <video
35
35
  ref={videoRef}
36
36
  autoPlay
package/dist/index.js CHANGED
@@ -13,6 +13,7 @@ export { default as AdvantagesList } from './components/AdvantagesList'
13
13
  export { default as ShoppingCartModal } from './components/cart/ShoppingCartModal'
14
14
  export { default as FloatingCartButton } from './components/cart/FloatingCartButton'
15
15
  export { default as VideoCard } from './components/VideoCard'
16
+ export { default as IconGrid } from './components/IconGrid'
16
17
  export { default as Menu } from './components/Menu'
17
18
  export { default as ProductsDisplay } from './components/products/ProductsDisplay'
18
19
  export { default as ProductsSidebar } from './components/products/ProductsSidebar'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codesinger0/shared-components",
3
- "version": "1.1.21",
3
+ "version": "1.1.23",
4
4
  "description": "Shared React components for customer projects",
5
5
  "main": "dist/index.js",
6
6
  "files": [