@codesinger0/shared-components 1.1.58 → 1.1.61
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/AdvantagesList.jsx +89 -0
- package/dist/components/ArticlesList.jsx +269 -0
- package/dist/components/DualTextCard.jsx +73 -0
- package/dist/components/FloatingWhatsAppButton.jsx +180 -0
- package/dist/components/IconGrid.jsx +144 -0
- package/dist/components/IntroSection.jsx +74 -0
- package/dist/components/Menu.jsx +268 -0
- package/dist/components/{QAAccordion 2.jsx → QAAccordion.jsx} +4 -3
- package/dist/components/SmallItemsGrid.jsx +308 -0
- package/dist/components/TextListCards.jsx +107 -0
- package/dist/components/UnderConstruction.jsx +76 -0
- package/dist/components/VideoCard.jsx +88 -0
- package/dist/context/CartContext.jsx +165 -0
- package/dist/context/ItemModalContext.jsx +40 -0
- package/dist/hooks/useScrollLock.js +52 -0
- package/dist/integrations/emailService.js +167 -0
- package/dist/styles/shared-components.css +29 -0
- package/dist/utils/ScrollManager.jsx +85 -0
- package/dist/utils/ScrollToTop.jsx +14 -0
- package/package.json +1 -1
|
@@ -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-main" 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;
|
|
@@ -0,0 +1,74 @@
|
|
|
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
|
+
{/* Text Content */}
|
|
24
|
+
<div className="text-center lg:text-right order-2 lg:order-1">
|
|
25
|
+
<h2 className={introTitleClass + " mb-6"}>
|
|
26
|
+
{introTitle}
|
|
27
|
+
</h2>
|
|
28
|
+
<p className={introContentClass + " leading-relaxed"} style={{ whiteSpace: 'pre-line' }}>
|
|
29
|
+
{introContent}
|
|
30
|
+
</p>
|
|
31
|
+
</div>
|
|
32
|
+
|
|
33
|
+
{/* Image */}
|
|
34
|
+
<motion.div
|
|
35
|
+
key={'image'}
|
|
36
|
+
initial={{ opacity: 0, x: 50 }}
|
|
37
|
+
whileInView={{ opacity: 1, x: 0 }}
|
|
38
|
+
viewport={{ once: true }}
|
|
39
|
+
transition={{ delay: 1 * 0.5 }}
|
|
40
|
+
className="h-full"
|
|
41
|
+
>
|
|
42
|
+
<div className="order-1 lg:order-2 flex justify-center lg:justify-end">
|
|
43
|
+
<div className="w-full max-w-md lg:max-w-lg">
|
|
44
|
+
<img
|
|
45
|
+
src={introImage}
|
|
46
|
+
alt={introTitle || "Introduction image"}
|
|
47
|
+
className="w-full h-auto rounded-lg shadow-lg object-cover"
|
|
48
|
+
onError={(e) => {
|
|
49
|
+
e.target.style.display = 'none';
|
|
50
|
+
console.warn('Failed to load intro image:', introImage);
|
|
51
|
+
}}
|
|
52
|
+
/>
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
55
|
+
</motion.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
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import React, { useState, useEffect } from 'react';
|
|
2
|
+
import { motion, AnimatePresence } from 'framer-motion';
|
|
3
|
+
import { Link } from 'react-router-dom';
|
|
4
|
+
import { useMediaQuery } from "react-responsive";
|
|
5
|
+
import useScrollLock from '../hooks/useScrollLock';
|
|
6
|
+
|
|
7
|
+
const Menu = ({
|
|
8
|
+
// Business info
|
|
9
|
+
businessInfo = {},
|
|
10
|
+
|
|
11
|
+
// Navigation items
|
|
12
|
+
navigationItems = [],
|
|
13
|
+
|
|
14
|
+
// Auth/Action buttons component
|
|
15
|
+
AuthButtonsComponent = null,
|
|
16
|
+
|
|
17
|
+
// User context (for admin check)
|
|
18
|
+
isAdmin = false,
|
|
19
|
+
|
|
20
|
+
// Customization
|
|
21
|
+
mobileBreakpoint = 900,
|
|
22
|
+
logoClassName = '',
|
|
23
|
+
menuItemClassName = '',
|
|
24
|
+
sidebarWidth = 'w-80',
|
|
25
|
+
|
|
26
|
+
// Callbacks
|
|
27
|
+
onMenuItemClick = () => { },
|
|
28
|
+
|
|
29
|
+
// Children to display above menu items
|
|
30
|
+
children = null,
|
|
31
|
+
}) => {
|
|
32
|
+
const [isSidebarOpen, setIsSidebarOpen] = useState(false);
|
|
33
|
+
const isMobile = useMediaQuery({ maxWidth: mobileBreakpoint });
|
|
34
|
+
|
|
35
|
+
// Close sidebar when clicking outside
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
const handleClickOutside = (event) => {
|
|
38
|
+
if (isSidebarOpen && !event.target.closest('.sidebar') && !event.target.closest('.hamburger')) {
|
|
39
|
+
setIsSidebarOpen(false);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
if (isSidebarOpen) {
|
|
44
|
+
document.addEventListener('mousedown', handleClickOutside);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return () => {
|
|
48
|
+
document.removeEventListener('mousedown', handleClickOutside);
|
|
49
|
+
};
|
|
50
|
+
}, [isSidebarOpen]);
|
|
51
|
+
|
|
52
|
+
useScrollLock(isSidebarOpen && isMobile);
|
|
53
|
+
|
|
54
|
+
const toggleSidebar = () => {
|
|
55
|
+
setIsSidebarOpen(!isSidebarOpen);
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const handleMenuItemClick = (item) => {
|
|
59
|
+
setIsSidebarOpen(false);
|
|
60
|
+
onMenuItemClick(item);
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const DesktopMenu = () => (
|
|
64
|
+
<nav className="sticky top-0 z-50 bg-menu border-b-2 border-primary">
|
|
65
|
+
{/* Parent container with two vertically stacked sections */}
|
|
66
|
+
<div className="w-full">
|
|
67
|
+
|
|
68
|
+
{/* Top section - Logo and Children content */}
|
|
69
|
+
{children && (<div className="w-full flex items-center py-4 px-6">
|
|
70
|
+
{/* Logo - takes only required space */}
|
|
71
|
+
<Link to="/" className={`flex-shrink-0 ${logoClassName}`}>
|
|
72
|
+
<div className="relative w-24 h-12 lg:w-32 lg:h-16 overflow-hidden flex items-center justify-center">
|
|
73
|
+
<img
|
|
74
|
+
src={businessInfo.logo}
|
|
75
|
+
alt={businessInfo.name}
|
|
76
|
+
className="relative z-10 w-full h-full object-contain"
|
|
77
|
+
onError={(e) => {
|
|
78
|
+
e.target.style.display = 'none';
|
|
79
|
+
e.target.nextElementSibling.style.display = 'flex';
|
|
80
|
+
}}
|
|
81
|
+
/>
|
|
82
|
+
{/* Fallback text */}
|
|
83
|
+
<div
|
|
84
|
+
className="relative z-10 w-full h-full flex items-center justify-center text-primary text-xs font-bold"
|
|
85
|
+
style={{ display: 'none' }}
|
|
86
|
+
>
|
|
87
|
+
{businessInfo.name?.slice(0, 2) || ''}
|
|
88
|
+
</div>
|
|
89
|
+
</div>
|
|
90
|
+
</Link>
|
|
91
|
+
|
|
92
|
+
{/* Children - grows to fill available space */}
|
|
93
|
+
<div className="flex-grow">
|
|
94
|
+
{children}
|
|
95
|
+
</div>
|
|
96
|
+
</div>
|
|
97
|
+
)}
|
|
98
|
+
|
|
99
|
+
{/* Bottom section - Menu navigation bar */}
|
|
100
|
+
<div className="w-full px-6 py-4 flex justify-between items-center">
|
|
101
|
+
{/* Left side - Auth buttons */}
|
|
102
|
+
<div className="flex items-center">
|
|
103
|
+
{AuthButtonsComponent && <AuthButtonsComponent />}
|
|
104
|
+
</div>
|
|
105
|
+
|
|
106
|
+
{/* Center / Right side - Menu items */}
|
|
107
|
+
<div className="flex-1 flex justify-center">
|
|
108
|
+
<div className="flex items-center space-x-6 space-x-reverse" dir="rtl">
|
|
109
|
+
{navigationItems.map((item, index) => (
|
|
110
|
+
item.adminRoute && !isAdmin ? null : (
|
|
111
|
+
<Link
|
|
112
|
+
key={index}
|
|
113
|
+
to={item.href}
|
|
114
|
+
onClick={() => handleMenuItemClick(item)}
|
|
115
|
+
className={`subtitle font-normal cursor-pointer menu-item-hover px-4 py-2 rounded-md text transition-colors duration-500 ease-in-out hover:text-primary ${menuItemClassName}`}
|
|
116
|
+
>
|
|
117
|
+
{item.label}
|
|
118
|
+
</Link>
|
|
119
|
+
)
|
|
120
|
+
))}
|
|
121
|
+
</div>
|
|
122
|
+
</div>
|
|
123
|
+
|
|
124
|
+
{/* Right Side Render logo here if no children */}
|
|
125
|
+
{!children && (
|
|
126
|
+
<Link to="/" className={`flex-shrink-0 ${logoClassName}`}>
|
|
127
|
+
<div className="relative w-24 h-12 lg:w-32 lg:h-16 overflow-hidden flex items-center justify-center">
|
|
128
|
+
<img
|
|
129
|
+
src={businessInfo.logo}
|
|
130
|
+
alt={businessInfo.name}
|
|
131
|
+
className="relative z-10 w-full h-full object-contain"
|
|
132
|
+
onError={(e) => {
|
|
133
|
+
e.target.style.display = 'none';
|
|
134
|
+
e.target.nextElementSibling.style.display = 'flex';
|
|
135
|
+
}}
|
|
136
|
+
/>
|
|
137
|
+
{/* Fallback text */}
|
|
138
|
+
<div
|
|
139
|
+
className="relative z-10 w-full h-full flex items-center justify-center text-primary text-xs font-bold"
|
|
140
|
+
style={{ display: 'none' }}
|
|
141
|
+
>
|
|
142
|
+
{businessInfo.name?.slice(0, 2) || ''}
|
|
143
|
+
</div>
|
|
144
|
+
</div>
|
|
145
|
+
</Link>
|
|
146
|
+
)}
|
|
147
|
+
</div>
|
|
148
|
+
</div>
|
|
149
|
+
</nav>
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
// Mobile Menu Component
|
|
153
|
+
const MobileMenu = () => (
|
|
154
|
+
<>
|
|
155
|
+
<nav className="sticky top-0 z-50 bg-menu px-4 py-2">
|
|
156
|
+
<div className="flex justify-between items-center">
|
|
157
|
+
{AuthButtonsComponent && <AuthButtonsComponent isMobile={true} />}
|
|
158
|
+
|
|
159
|
+
{/* Mobile Logo */}
|
|
160
|
+
<Link to="/" className={`text-primary font-bold text-xl ${logoClassName}`}>
|
|
161
|
+
<div className="text-primary font-bold text-lg">
|
|
162
|
+
<div className="w-25 h-12 overflow-hidden flex items-center justify-center">
|
|
163
|
+
<img
|
|
164
|
+
src={businessInfo.logo}
|
|
165
|
+
alt={businessInfo.name}
|
|
166
|
+
className="w-full h-full object-contain"
|
|
167
|
+
onError={(e) => {
|
|
168
|
+
e.target.style.display = 'none';
|
|
169
|
+
e.target.nextElementSibling.style.display = 'flex';
|
|
170
|
+
}}
|
|
171
|
+
/>
|
|
172
|
+
{/* Fallback text */}
|
|
173
|
+
<div
|
|
174
|
+
className="w-full h-full flex items-center justify-center text-primary text-xs font-bold"
|
|
175
|
+
style={{ display: 'none' }}
|
|
176
|
+
>
|
|
177
|
+
{businessInfo.name?.slice(0, 2) || ''}
|
|
178
|
+
</div>
|
|
179
|
+
</div>
|
|
180
|
+
</div>
|
|
181
|
+
</Link>
|
|
182
|
+
|
|
183
|
+
{/* Children next to logo */}
|
|
184
|
+
{children && (
|
|
185
|
+
<div>
|
|
186
|
+
{children}
|
|
187
|
+
</div>
|
|
188
|
+
)}
|
|
189
|
+
|
|
190
|
+
{/* Hamburger button */}
|
|
191
|
+
<button
|
|
192
|
+
className="hamburger flex flex-col justify-center items-center w-8 h-8 focus:outline-none text-main"
|
|
193
|
+
onClick={toggleSidebar}
|
|
194
|
+
aria-label="תפריט"
|
|
195
|
+
>
|
|
196
|
+
<span className={`hamburger-line block w-6 h-0.5 mb-1 ${isSidebarOpen ? 'rotate-45 translate-y-1.5' : ''}`} style={{ backgroundColor: 'var(--text)' }}></span>
|
|
197
|
+
<span className={`hamburger-line block w-6 h-0.5 mb-1 ${isSidebarOpen ? 'opacity-0' : ''}`} style={{ backgroundColor: 'var(--text)' }}></span>
|
|
198
|
+
<span className={`hamburger-line block w-6 h-0.5 ${isSidebarOpen ? '-rotate-45 -translate-y-1.5' : ''}`} style={{ backgroundColor: 'var(--text)' }}></span>
|
|
199
|
+
</button>
|
|
200
|
+
</div>
|
|
201
|
+
</nav>
|
|
202
|
+
|
|
203
|
+
{/* Overlay + Sidebar (animated) */}
|
|
204
|
+
<AnimatePresence>
|
|
205
|
+
{isSidebarOpen && (
|
|
206
|
+
<>
|
|
207
|
+
{/* Overlay fade */}
|
|
208
|
+
<motion.div
|
|
209
|
+
key="overlay"
|
|
210
|
+
initial={{ opacity: 0 }}
|
|
211
|
+
animate={{ opacity: 0.5 }}
|
|
212
|
+
exit={{ opacity: 0 }}
|
|
213
|
+
transition={{ duration: 0.2 }}
|
|
214
|
+
className="fixed inset-0 bg-black z-40 supports-[height:100dvh]:h-[100dvh]"
|
|
215
|
+
onClick={() => setIsSidebarOpen(false)}
|
|
216
|
+
aria-hidden="true"
|
|
217
|
+
/>
|
|
218
|
+
|
|
219
|
+
{/* Sidebar spring slide */}
|
|
220
|
+
<motion.aside
|
|
221
|
+
key="sidebar"
|
|
222
|
+
initial={{ x: '100%' }}
|
|
223
|
+
animate={{ x: 0 }}
|
|
224
|
+
exit={{ x: '100%' }}
|
|
225
|
+
transition={{ type: 'spring', stiffness: 320, damping: 28, mass: 0.9 }}
|
|
226
|
+
className={`sidebar fixed top-0 right-0 h-full ${sidebarWidth} bg-white shadow-lg z-50`}
|
|
227
|
+
role="dialog"
|
|
228
|
+
aria-label="תפריט צד"
|
|
229
|
+
>
|
|
230
|
+
<div className="p-6">
|
|
231
|
+
{/* Close button */}
|
|
232
|
+
<div className="flex justify-start mb-8">
|
|
233
|
+
<button
|
|
234
|
+
onClick={() => setIsSidebarOpen(false)}
|
|
235
|
+
className="text-black hover:text-gray-700 text-2xl"
|
|
236
|
+
aria-label="סגור תפריט"
|
|
237
|
+
>
|
|
238
|
+
×
|
|
239
|
+
</button>
|
|
240
|
+
</div>
|
|
241
|
+
|
|
242
|
+
{/* Menu items */}
|
|
243
|
+
<div className="flex flex-col space-y-3">
|
|
244
|
+
{navigationItems.map((item, index) => (
|
|
245
|
+
item.adminRoute && !isAdmin ? null : (
|
|
246
|
+
<Link
|
|
247
|
+
key={index}
|
|
248
|
+
to={item.href}
|
|
249
|
+
onClick={() => handleMenuItemClick(item)}
|
|
250
|
+
className={`text-lg py-3 px-2 text-black text-right ${menuItemClassName}`}
|
|
251
|
+
>
|
|
252
|
+
{item.label}
|
|
253
|
+
</Link>
|
|
254
|
+
)
|
|
255
|
+
))}
|
|
256
|
+
</div>
|
|
257
|
+
</div>
|
|
258
|
+
</motion.aside>
|
|
259
|
+
</>
|
|
260
|
+
)}
|
|
261
|
+
</AnimatePresence>
|
|
262
|
+
</>
|
|
263
|
+
);
|
|
264
|
+
|
|
265
|
+
return isMobile ? <MobileMenu /> : <DesktopMenu />;
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
export default Menu;
|
|
@@ -5,8 +5,8 @@ const QAAccordion = ({
|
|
|
5
5
|
title = "שאלות נפוצות",
|
|
6
6
|
subtitle = "מענה לשאלות הנפוצות ביותר",
|
|
7
7
|
qaItems = [],
|
|
8
|
-
showBottomCTA = true,
|
|
9
8
|
onClickCta = () => { },
|
|
9
|
+
showCTA = true,
|
|
10
10
|
className = ""
|
|
11
11
|
}) => {
|
|
12
12
|
const [openIndex, setOpenIndex] = useState(null);
|
|
@@ -184,7 +184,7 @@ const QAAccordion = ({
|
|
|
184
184
|
</div>
|
|
185
185
|
|
|
186
186
|
{/* Optional CTA */}
|
|
187
|
-
{
|
|
187
|
+
{showCTA && (
|
|
188
188
|
<div className="text-center mt-12">
|
|
189
189
|
<p className="content-text mb-4">
|
|
190
190
|
לא מצאתם את התשובה שחיפשתם?
|
|
@@ -192,7 +192,8 @@ const QAAccordion = ({
|
|
|
192
192
|
<button className="btn-primary" onClick={onClickCta}>
|
|
193
193
|
צרו קשר
|
|
194
194
|
</button>
|
|
195
|
-
</div>
|
|
195
|
+
</div>
|
|
196
|
+
)}
|
|
196
197
|
</div>
|
|
197
198
|
</section>
|
|
198
199
|
);
|