@codesinger0/shared-components 1.1.11 → 1.1.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.
@@ -0,0 +1,237 @@
1
+ import React, { useState } from 'react';
2
+ import { motion, AnimatePresence } from 'framer-motion';
3
+ import { BookOpen, ArrowDown, X } from 'lucide-react';
4
+ import useScrollLock from '../hooks/useScrollLock';
5
+
6
+ const ArticlesList = ({
7
+ title,
8
+ subtitle,
9
+ articles = [],
10
+ className = '',
11
+ ...props
12
+ }) => {
13
+ const [selectedArticle, setSelectedArticle] = useState(null);
14
+
15
+ useScrollLock(selectedArticle !== null);
16
+
17
+ if (!articles || articles.length === 0) {
18
+ return (
19
+ <section className={`py-20 ${className}`} {...props}>
20
+ <div className="max-w-7xl mx-auto px-6 text-center" dir="rtl">
21
+ {title && <h2 className="title mb-4">{title}</h2>}
22
+ {subtitle && <p className="subtitle">{subtitle}</p>}
23
+ <p className="content-text mt-8">אין מאמרים להצגה</p>
24
+ </div>
25
+ </section>
26
+ );
27
+ }
28
+
29
+ // Function to get first 3 lines of text
30
+ const getExcerpt = (text) => {
31
+ const lines = text?.trim().split('\n').filter(line => line.trim());
32
+ return lines?.slice(0, 3).join('\n') + (lines?.length > 3 ? '...' : '');
33
+ };
34
+
35
+ return (
36
+ <>
37
+ <section className={`py-20 bg-white ${className}`} {...props} id="articles">
38
+ <div className="max-w-7xl mx-auto px-6">
39
+ {/* Header */}
40
+ {(title || subtitle) && (
41
+ <motion.div
42
+ initial={{ opacity: 0, y: 20 }}
43
+ whileInView={{ opacity: 1, y: 0 }}
44
+ viewport={{ once: true }}
45
+ className="text-center mb-16"
46
+ dir="rtl"
47
+ >
48
+ {title && (
49
+ <h2 className="title mb-6">
50
+ {title}
51
+ </h2>
52
+ )}
53
+
54
+ <div className="w-20 h-1 bg-gradient-to-r from-primary to-primary-bright mx-auto mb-6"></div>
55
+
56
+ {subtitle && (
57
+ <p className="subtitle max-w-3xl mx-auto">
58
+ {subtitle}
59
+ </p>
60
+ )}
61
+ </motion.div>
62
+ )}
63
+
64
+ {/* Articles Grid */}
65
+ <div className="grid lg:grid-cols-2 gap-8" dir="rtl">
66
+ {articles.map((article, index) => {
67
+ const isEven = index % 2 === 0;
68
+ const cardBg = isEven ? 'bg-gradient-to-br from-white to-green-50' : 'bg-gradient-to-br from-white to-sky-50';
69
+ const iconGradient = isEven ? 'from-green-500 to-green-600' : 'from-sky-400 to-sky-500';
70
+ const textHover = isEven ? 'group-hover:text-green-700' : 'group-hover:text-sky-700';
71
+ const textColor = isEven ? 'text-green-600' : 'text-sky-600';
72
+
73
+ return (
74
+ <motion.div
75
+ key={article.id || index}
76
+ initial={{ opacity: 0, y: 20 }}
77
+ whileInView={{ opacity: 1, y: 0 }}
78
+ viewport={{ once: true }}
79
+ transition={{ delay: index * 0.1 }}
80
+ >
81
+ <div
82
+ className={`h-full cursor-pointer border-none shadow-lg hover:shadow-xl transition-all duration-300 ${cardBg} group rounded-xl`}
83
+ onClick={() => setSelectedArticle(article)}
84
+ >
85
+ <div className="p-6 pb-4">
86
+ <div className="flex items-start gap-4">
87
+ <div className={`w-12 h-12 bg-gradient-to-br ${iconGradient} rounded-full flex items-center justify-center flex-shrink-0`}>
88
+ {article.icon ? (
89
+ <article.icon className="w-6 h-6 text-white" />
90
+ ) : (
91
+ <BookOpen className="w-6 h-6 text-white" />
92
+ )}
93
+ </div>
94
+ <div className="flex-1">
95
+ <h3 className={`text-xl font-semibold text-green-900 leading-tight ${textHover} transition-colors`} dir="rtl">
96
+ {article.title}
97
+ </h3>
98
+ </div>
99
+ </div>
100
+ </div>
101
+ <div className="px-6 pb-6">
102
+ <p className="text-gray-600 leading-relaxed whitespace-pre-line" dir="rtl">
103
+ {article.excerpt}
104
+ </p>
105
+ <div className="mt-6">
106
+ <span className={`inline-flex items-center ${textColor} font-medium ${textHover} transition-colors`}>
107
+ קרא עוד
108
+ <ArrowDown className="w-4 h-4 mr-2 rotate-180 group-hover:transform group-hover:-translate-y-1 transition-transform" />
109
+ </span>
110
+ </div>
111
+ </div>
112
+ </div>
113
+ </motion.div>
114
+ );
115
+ })}
116
+ </div>
117
+ </div>
118
+ </section>
119
+
120
+ {/* Article Modal */}
121
+ <AnimatePresence>
122
+ {selectedArticle && (
123
+ <div className="fixed inset-0 z-50 flex items-center justify-center supports-[height:100dvh]:h-[100dvh]">
124
+ {/* Backdrop */}
125
+ <motion.div
126
+ initial={{ opacity: 0 }}
127
+ animate={{ opacity: 1 }}
128
+ exit={{ opacity: 0 }}
129
+ transition={{ duration: 0.2 }}
130
+ className="absolute inset-0 bg-black bg-opacity-50 backdrop-blur-sm"
131
+ onClick={() => setSelectedArticle(null)}
132
+ />
133
+
134
+ {/* Modal Content */}
135
+ <motion.div
136
+ initial={{ opacity: 0, scale: 0.9, y: 20 }}
137
+ animate={{ opacity: 1, scale: 1, y: 0 }}
138
+ exit={{ opacity: 0, scale: 0.9, y: 20 }}
139
+ transition={{ type: "spring", stiffness: 300, damping: 30 }}
140
+ className="relative w-full max-w-4xl mx-4 max-h-[90vh] bg-white rounded-xl shadow-2xl overflow-hidden"
141
+ onClick={(e) => e.stopPropagation()}
142
+ >
143
+ {/* Close Button */}
144
+ <button
145
+ onClick={() => setSelectedArticle(null)}
146
+ className="absolute top-4 left-4 z-10 bg-white hover:bg-gray-100 text-gray-700 p-2 rounded-full shadow-lg transition-colors duration-200"
147
+ aria-label="סגור"
148
+ >
149
+ <X size={24} />
150
+ </button>
151
+
152
+ {/* Scrollable Content */}
153
+ <div className="overflow-y-auto max-h-[90vh] p-8 md:p-12" dir="rtl">
154
+ {/* Article Header */}
155
+ <div className="mb-8">
156
+ <div className="flex items-center gap-4 mb-6">
157
+ <div className="w-16 h-16 bg-gradient-to-br from-green-500 to-green-600 rounded-full flex items-center justify-center flex-shrink-0">
158
+ {selectedArticle.icon ? (
159
+ <selectedArticle.icon className="w-8 h-8 text-white" />
160
+ ) : (
161
+ <BookOpen className="w-8 h-8 text-white" />
162
+ )}
163
+ </div>
164
+ <h2 className="text-3xl md:text-4xl font-bold text-green-900">
165
+ {selectedArticle.title}
166
+ </h2>
167
+ </div>
168
+ <div className="w-20 h-1 bg-gradient-to-r from-green-500 to-sky-400"></div>
169
+ </div>
170
+
171
+ {/* Article Content */}
172
+ <div className="prose prose-lg max-w-none">
173
+ <ArticleFormatter text={selectedArticle.content} />
174
+ </div>
175
+ </div>
176
+ </motion.div>
177
+ </div>
178
+ )}
179
+ </AnimatePresence>
180
+
181
+ {/* Custom Styles */}
182
+ <style jsx>{`
183
+ .from-primary {
184
+ --tw-gradient-from: var(--primary);
185
+ }
186
+
187
+ .to-primary-bright {
188
+ --tw-gradient-to: var(--primary-bright);
189
+ }
190
+ `}</style>
191
+ </>
192
+ );
193
+ };
194
+
195
+ const ArticleFormatter = ({ text }) => {
196
+ // Split the text into logical sections. A section is typically a heading followed by its text.
197
+ // We assume sections are separated by two or more newlines.
198
+ const sections = text.trim().split(/\n\s*\n+/);
199
+
200
+ return (
201
+ <>
202
+ {sections.map((section, index) => {
203
+ // Find the first newline to separate a potential heading from its paragraph
204
+ const firstNewlineIndex = section.indexOf('\n');
205
+
206
+ let heading = '';
207
+ let paragraph = '';
208
+
209
+ // Heuristic: If the first line is short and there's more content, it's a heading.
210
+ if (firstNewlineIndex > 0 && firstNewlineIndex < 80) {
211
+ heading = section.substring(0, firstNewlineIndex).trim();
212
+ paragraph = section.substring(firstNewlineIndex + 1).trim();
213
+ } else {
214
+ // Otherwise, treat the whole block as a single paragraph
215
+ paragraph = section.trim();
216
+ }
217
+
218
+ return (
219
+ <div key={index} className="mb-6">
220
+ {heading && (
221
+ <h2 className="text-2xl font-semibold text-green-800 mt-10 mb-4 pb-2 border-b border-sky-200">
222
+ {heading}
223
+ </h2>
224
+ )}
225
+ {paragraph && (
226
+ <p className="text-gray-700 leading-relaxed text-lg whitespace-pre-line">
227
+ {paragraph}
228
+ </p>
229
+ )}
230
+ </div>
231
+ );
232
+ })}
233
+ </>
234
+ );
235
+ };
236
+
237
+ export default ArticlesList;
@@ -0,0 +1,110 @@
1
+ import React from 'react';
2
+ import { motion } from 'framer-motion';
3
+ import { Check } from 'lucide-react';
4
+
5
+ const TextListCards = ({
6
+ title,
7
+ subtitle,
8
+ items = [],
9
+ className = '',
10
+ ...props
11
+ }) => {
12
+ if (!items || items.length === 0) {
13
+ return (
14
+ <section className={`py-20 bg-main ${className}`} {...props}>
15
+ <div className="max-w-7xl mx-auto px-6 text-center" dir="rtl">
16
+ {title && <h2 className="title mb-4">{title}</h2>}
17
+ {subtitle && <p className="subtitle">{subtitle}</p>}
18
+ <p className="content-text mt-8">אין פריטים להצגה</p>
19
+ </div>
20
+ </section>
21
+ );
22
+ }
23
+
24
+ return (
25
+ <section className={`py-20 ${className}`} {...props}>
26
+ <div className="max-w-7xl mx-auto px-6">
27
+ {/* Header Section */}
28
+ {(title || subtitle) && (
29
+ <motion.div
30
+ initial={{ opacity: 0, y: 20 }}
31
+ whileInView={{ opacity: 1, y: 0 }}
32
+ viewport={{ once: true }}
33
+ className="text-center mb-16"
34
+ dir="rtl"
35
+ >
36
+ {title && (
37
+ <h2 className="title mb-6">
38
+ {title}
39
+ </h2>
40
+ )}
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
+ {subtitle && (
46
+ <p className="subtitle max-w-3xl mx-auto mt-6">
47
+ {subtitle}
48
+ </p>
49
+ )}
50
+ </motion.div>
51
+ )}
52
+
53
+ {/* Cards Grid */}
54
+ <div className="grid lg:grid-cols-3 gap-8">
55
+ {items.map((item, index) => (
56
+ <motion.div
57
+ key={item.title || index}
58
+ initial={{ opacity: 0, y: 20 }}
59
+ whileInView={{ opacity: 1, y: 0 }}
60
+ viewport={{ once: true }}
61
+ transition={{ delay: index * 0.2 }}
62
+ className="h-full"
63
+ >
64
+ <div className="glass-card h-full p-8 text-center hover:scale-105 transition-all duration-300">
65
+ {/* Icon */}
66
+ {item.icon && (
67
+ <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">
68
+ <item.icon className="w-8 h-8 text-white" />
69
+ </div>
70
+ )}
71
+
72
+ {/* Title */}
73
+ <h3 className="subtitle font-semibold mb-6" dir="rtl">
74
+ {item.title}
75
+ </h3>
76
+
77
+ {/* Points List */}
78
+ {item.points && item.points.length > 0 && (
79
+ <ul className="space-y-3 text-right leading-relaxed" dir="rtl">
80
+ {item.points.map((point, i) => (
81
+ <li key={i} className="flex items-start">
82
+ <Check className="w-5 h-5 text-primary mt-1 mr-3 flex-shrink-0" />
83
+ <span className="content-text">{point}</span>
84
+ </li>
85
+ ))}
86
+ </ul>
87
+ )}
88
+ </div>
89
+ </motion.div>
90
+ ))}
91
+ </div>
92
+ </div>
93
+
94
+ {/* Custom Styles */}
95
+ <style jsx>{`
96
+ .from-primary {
97
+ --tw-gradient-from: var(--primary);
98
+ --tw-gradient-to: rgb(0 153 255 / 0);
99
+ --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
100
+ }
101
+
102
+ .to-primary-bright {
103
+ --tw-gradient-to: var(--primary-bright);
104
+ }
105
+ `}</style>
106
+ </section>
107
+ );
108
+ };
109
+
110
+ export default TextListCards;
package/dist/index.js CHANGED
@@ -5,6 +5,8 @@ export { default as LargeItemCard } from './components/LargeItemCard';
5
5
  export { default as SmallItemsGrid } from './components/SmallItemsGrid';
6
6
  export { default as SmallItemCard } from './components/SmallItemCard';
7
7
  export { default as MasonryItemCard } from './components/MasonryItemCard';
8
+ export { default as TextListCards } from './components/TextListCards';
9
+ export { default as ArticlesList } from './components/ArticlesList';
8
10
  export { default as Hero } from './components/Hero'
9
11
  export { default as QAAccordion } from './components/QAAccordion'
10
12
  export { default as AdvantagesList } from './components/AdvantagesList'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codesinger0/shared-components",
3
- "version": "1.1.11",
3
+ "version": "1.1.12",
4
4
  "description": "Shared React components for customer projects",
5
5
  "main": "dist/index.js",
6
6
  "files": [