@codesinger0/shared-components 1.1.13 → 1.1.15

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,331 @@
1
+ // src/components/AccessibilityMenu.jsx
2
+ import React, { useState, useEffect } from 'react';
3
+ import {
4
+ Accessibility, ZoomIn, ZoomOut, Contrast, Sun, Moon, Link, Heading,
5
+ MousePointer2, X, CircleOff, Glasses, Palette, Square
6
+ } from 'lucide-react';
7
+ import { motion, AnimatePresence } from 'framer-motion';
8
+
9
+ export default function AccessibilityMenu() {
10
+ const [isOpen, setIsOpen] = useState(false);
11
+ const [activeOptions, setActiveOptions] = useState({});
12
+ const [fontSize, setFontSize] = useState(1);
13
+
14
+ const applyOption = (option, apply) => {
15
+ document.documentElement.classList.toggle(`accessibility-${option}`, apply);
16
+ };
17
+
18
+ const resetAll = () => {
19
+ Object.keys(activeOptions).forEach(option => {
20
+ if(activeOptions[option]){
21
+ applyOption(option, false);
22
+ }
23
+ });
24
+ setActiveOptions({});
25
+ setFontSize(1);
26
+ document.documentElement.style.setProperty('--font-size-multiplier', 1);
27
+ };
28
+
29
+ const toggleOption = (option, exclusiveGroup = null) => {
30
+ const newActiveOptions = { ...activeOptions };
31
+ const currentState = newActiveOptions[option];
32
+
33
+ if (exclusiveGroup) {
34
+ Object.keys(newActiveOptions).forEach(opt => {
35
+ if (opt.startsWith(exclusiveGroup) && opt !== option) {
36
+ newActiveOptions[opt] = false;
37
+ applyOption(opt, false);
38
+ }
39
+ });
40
+ }
41
+
42
+ newActiveOptions[option] = !currentState;
43
+ setActiveOptions(newActiveOptions);
44
+ applyOption(option, newActiveOptions[option]);
45
+ };
46
+
47
+ const changeFontSize = (amount) => {
48
+ const newSize = Math.max(0.8, Math.min(1.5, fontSize + amount));
49
+ setFontSize(newSize);
50
+ document.documentElement.style.setProperty('--font-size-multiplier', newSize);
51
+ };
52
+
53
+ const tools = [
54
+ { id: 'increase-font', label: 'הגדלת גופן', icon: ZoomIn, action: () => changeFontSize(0.1) },
55
+ { id: 'decrease-font', label: 'הקטנת גופן', icon: ZoomOut, action: () => changeFontSize(-0.1) },
56
+ { id: 'readable-font', label: 'גופן קריא', icon: Glasses, action: () => toggleOption('readable-font') },
57
+ { id: 'highlight-links', label: 'הדגשת קישורים', icon: Link, action: () => toggleOption('highlight-links') },
58
+ { id: 'highlight-headers', label: 'הדגשת כותרות', icon: Heading, action: () => toggleOption('highlight-headers') },
59
+ { id: 'contrast-high', label: 'ניגודיות גבוהה', icon: Contrast, action: () => toggleOption('contrast-high', 'contrast-') },
60
+ { id: 'contrast-dark', label: 'רקע כהה', icon: Moon, action: () => toggleOption('contrast-dark', 'contrast-') },
61
+ { id: 'contrast-light', label: 'רקע בהיר', icon: Sun, action: () => toggleOption('contrast-light', 'contrast-') },
62
+ { id: 'invert-colors', label: 'היפוך צבעים', icon: Palette, action: () => toggleOption('invert-colors', 'contrast-') },
63
+ { id: 'monochrome', label: 'מונוכרום', icon: CircleOff, action: () => toggleOption('monochrome', 'contrast-') },
64
+ { id: 'big-cursor', label: 'סמן גדול', icon: MousePointer2, action: () => toggleOption('big-cursor') },
65
+ { id: 'disable-animations', label: 'ביטול אנימציות', icon: Square, action: () => toggleOption('disable-animations') },
66
+ ];
67
+
68
+ useEffect(() => {
69
+ const handleEsc = (event) => {
70
+ if (event.key === 'Escape' && isOpen) {
71
+ setIsOpen(false);
72
+ }
73
+ };
74
+ window.addEventListener('keydown', handleEsc);
75
+ return () => window.removeEventListener('keydown', handleEsc);
76
+ }, [isOpen]);
77
+
78
+ return (
79
+ <>
80
+ <style>{`
81
+ :root { --font-size-multiplier: 1; }
82
+
83
+ /* Readable Font */
84
+ html.accessibility-readable-font body {
85
+ font-family: Arial, sans-serif !important;
86
+ }
87
+
88
+ /* Highlight Links */
89
+ html.accessibility-highlight-links a {
90
+ background-color: yellow !important;
91
+ color: black !important;
92
+ padding: 2px 4px;
93
+ border: 1px solid black;
94
+ }
95
+
96
+ /* Highlight Headers */
97
+ html.accessibility-highlight-headers h1,
98
+ html.accessibility-highlight-headers h2,
99
+ html.accessibility-highlight-headers h3,
100
+ html.accessibility-highlight-headers h4 {
101
+ background-color: #add8e6 !important;
102
+ padding: 4px;
103
+ }
104
+
105
+ /* High Contrast Mode */
106
+ html.accessibility-contrast-high {
107
+ filter: contrast(200%) !important;
108
+ }
109
+ html.accessibility-contrast-high body {
110
+ background-color: white !important;
111
+ color: black !important;
112
+ }
113
+ html.accessibility-contrast-high h1,
114
+ html.accessibility-contrast-high h2,
115
+ html.accessibility-contrast-high h3,
116
+ html.accessibility-contrast-high h4,
117
+ html.accessibility-contrast-high p,
118
+ html.accessibility-contrast-high span,
119
+ html.accessibility-contrast-high div {
120
+ color: black !important;
121
+ }
122
+ html.accessibility-contrast-high a {
123
+ color: blue !important;
124
+ text-decoration: underline !important;
125
+ }
126
+ html.accessibility-contrast-high button {
127
+ background-color: black !important;
128
+ color: white !important;
129
+ border: 2px solid black !important;
130
+ }
131
+ html.accessibility-contrast-high input,
132
+ html.accessibility-contrast-high textarea {
133
+ background-color: white !important;
134
+ color: black !important;
135
+ border: 2px solid black !important;
136
+ }
137
+ html.accessibility-contrast-high [class*="bg-green"],
138
+ html.accessibility-contrast-high [class*="bg-sky"],
139
+ html.accessibility-contrast-high [class*="bg-blue"],
140
+ html.accessibility-contrast-high [class*="bg-gradient"] {
141
+ background-color: white !important;
142
+ color: black !important;
143
+ }
144
+
145
+ /* Dark Background */
146
+ html.accessibility-contrast-dark body {
147
+ background-color: #1a1a1a !important;
148
+ color: #ffffff !important;
149
+ }
150
+ html.accessibility-contrast-dark div,
151
+ html.accessibility-contrast-dark section,
152
+ html.accessibility-contrast-dark p,
153
+ html.accessibility-contrast-dark h1,
154
+ html.accessibility-contrast-dark h2,
155
+ html.accessibility-contrast-dark h3,
156
+ html.accessibility-contrast-dark h4,
157
+ html.accessibility-contrast-dark span {
158
+ color: #ffffff !important;
159
+ }
160
+ html.accessibility-contrast-dark [class*="bg-white"],
161
+ html.accessibility-contrast-dark [class*="bg-gray"],
162
+ html.accessibility-contrast-dark [class*="bg-green"],
163
+ html.accessibility-contrast-dark [class*="bg-sky"],
164
+ html.accessibility-contrast-dark [class*="bg-main"],
165
+ html.accessibility-contrast-dark [class*="glass-card"] {
166
+ background-color: #2a2a2a !important;
167
+ }
168
+ html.accessibility-contrast-dark a {
169
+ color: #87cefa !important;
170
+ }
171
+ html.accessibility-contrast-dark button {
172
+ background-color: #333 !important;
173
+ color: #fff !important;
174
+ border: 1px solid #666 !important;
175
+ }
176
+
177
+ /* Light Background */
178
+ html.accessibility-contrast-light body {
179
+ background-color: #ffffff !important;
180
+ color: #000000 !important;
181
+ }
182
+ html.accessibility-contrast-light div,
183
+ html.accessibility-contrast-light section,
184
+ html.accessibility-contrast-light p,
185
+ html.accessibility-contrast-light h1,
186
+ html.accessibility-contrast-light h2,
187
+ html.accessibility-contrast-light h3,
188
+ html.accessibility-contrast-light h4,
189
+ html.accessibility-contrast-light span {
190
+ color: #000000 !important;
191
+ }
192
+ html.accessibility-contrast-light [class*="bg-"] {
193
+ background-color: #f8f8f8 !important;
194
+ }
195
+
196
+ /* Invert Colors */
197
+ html.accessibility-invert-colors {
198
+ filter: invert(1) !important;
199
+ }
200
+ html.accessibility-invert-colors img,
201
+ html.accessibility-invert-colors video {
202
+ filter: invert(1) !important;
203
+ }
204
+
205
+ /* Monochrome */
206
+ html.accessibility-monochrome {
207
+ filter: grayscale(100%) !important;
208
+ }
209
+
210
+ /* Big Cursor */
211
+ html.accessibility-big-cursor * {
212
+ cursor: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='48' height='48' viewport='0 0 48 48' style='fill:black;font-size:24px;'><path d='M 10,2 L 10,42 L 18,34 L 25,42 L 30,40 L 22,32 L 32,32 L 10,2 Z'/></svg>") 16 0, auto !important;
213
+ }
214
+
215
+ /* Disable Animations */
216
+ html.accessibility-disable-animations * {
217
+ animation: none !important;
218
+ transition: none !important;
219
+ }
220
+
221
+ /* Font Size Scaling */
222
+ body {
223
+ font-size: calc(1rem * var(--font-size-multiplier));
224
+ }
225
+ h1 {
226
+ font-size: calc(2.25rem * var(--font-size-multiplier));
227
+ }
228
+ h2 {
229
+ font-size: calc(1.75rem * var(--font-size-multiplier));
230
+ }
231
+ h3 {
232
+ font-size: calc(1.5rem * var(--font-size-multiplier));
233
+ }
234
+ h4 {
235
+ font-size: calc(1.25rem * var(--font-size-multiplier));
236
+ }
237
+ p, li, span, a, button, div {
238
+ font-size: calc(1em * var(--font-size-multiplier));
239
+ }
240
+ `}</style>
241
+
242
+ {/* Floating Accessibility Button */}
243
+ <div className="fixed bottom-4 left-4 z-[100]">
244
+ <motion.button
245
+ onClick={() => setIsOpen(!isOpen)}
246
+ className="bg-sky-600 text-white rounded-full p-4 shadow-lg hover:bg-sky-700 transition-colors focus:outline-none focus:ring-2 focus:ring-sky-400"
247
+ whileHover={{ scale: 1.1 }}
248
+ whileTap={{ scale: 0.9 }}
249
+ aria-label="פתח תפריט נגישות"
250
+ >
251
+ <Accessibility className="w-6 h-6" />
252
+ </motion.button>
253
+ </div>
254
+
255
+ {/* Accessibility Menu Panel */}
256
+ <AnimatePresence>
257
+ {isOpen && (
258
+ <>
259
+ {/* Backdrop */}
260
+ <motion.div
261
+ initial={{ opacity: 0 }}
262
+ animate={{ opacity: 1 }}
263
+ exit={{ opacity: 0 }}
264
+ className="fixed inset-0 bg-black bg-opacity-30 z-[98]"
265
+ onClick={() => setIsOpen(false)}
266
+ />
267
+
268
+ {/* Menu Panel */}
269
+ <motion.div
270
+ initial={{ opacity: 0, y: 50 }}
271
+ animate={{ opacity: 1, y: 0 }}
272
+ exit={{ opacity: 0, y: 50 }}
273
+ className="fixed bottom-20 left-4 z-[99] w-[90vw] max-w-lg"
274
+ dir="rtl"
275
+ >
276
+ <div className="bg-white rounded-xl shadow-2xl border-2 border-sky-200 p-4">
277
+ {/* Header */}
278
+ <div className="flex justify-between items-center mb-4">
279
+ <h3 className="font-bold text-lg text-sky-800">תפריט נגישות</h3>
280
+ <div className="flex gap-2">
281
+ <button
282
+ onClick={resetAll}
283
+ className="px-3 py-1 text-sm text-sky-700 hover:bg-sky-50 rounded-md transition-colors"
284
+ >
285
+ איפוס
286
+ </button>
287
+ <button
288
+ onClick={() => setIsOpen(false)}
289
+ className="p-1 hover:bg-gray-100 rounded-md transition-colors"
290
+ aria-label="סגור תפריט נגישות"
291
+ >
292
+ <X className="w-5 h-5" />
293
+ </button>
294
+ </div>
295
+ </div>
296
+
297
+ {/* Font Size Display */}
298
+ <div className="mb-4 text-center text-sm text-gray-600">
299
+ גודל גופן: {Math.round(fontSize * 100)}%
300
+ </div>
301
+
302
+ {/* Tools Grid */}
303
+ <div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-2">
304
+ {tools.map(tool => (
305
+ <button
306
+ key={tool.id}
307
+ onClick={tool.action}
308
+ className={`flex flex-col items-center justify-center h-20 text-center gap-1 rounded-lg border-2 transition-all ${
309
+ activeOptions[tool.id]
310
+ ? 'bg-sky-100 border-sky-500 text-sky-800'
311
+ : 'bg-white border-gray-300 hover:border-sky-300 hover:bg-sky-50'
312
+ }`}
313
+ >
314
+ <tool.icon className="w-6 h-6 mb-1" />
315
+ <span className="text-xs px-1">{tool.label}</span>
316
+ </button>
317
+ ))}
318
+ </div>
319
+
320
+ {/* Footer Info */}
321
+ <div className="mt-4 pt-3 border-t border-gray-200 text-center text-xs text-gray-500">
322
+ לחץ ESC לסגירת התפריט
323
+ </div>
324
+ </div>
325
+ </motion.div>
326
+ </>
327
+ )}
328
+ </AnimatePresence>
329
+ </>
330
+ );
331
+ }
@@ -0,0 +1,180 @@
1
+ import React from 'react';
2
+ import { motion } from 'framer-motion';
3
+ import { MessageCircle } from 'lucide-react';
4
+
5
+ const FloatingWhatsAppButton = ({
6
+ phoneNumber,
7
+ message = 'שלום, אשמח לקבל מידע נוסף',
8
+ position = 'bottom-left', // 'bottom-left' or 'bottom-right'
9
+ bottomOffset = '1.5rem', // distance from bottom
10
+ sideOffset = '1.5rem', // distance from left/right edge
11
+ size = 'large', // 'small', 'medium', 'large'
12
+ className = '',
13
+ onBeforeOpen = null, // callback before opening WhatsApp
14
+ ...props
15
+ }) => {
16
+ // Size configurations
17
+ const sizeClasses = {
18
+ small: 'w-12 h-12',
19
+ medium: 'w-14 h-14',
20
+ large: 'w-16 h-16'
21
+ };
22
+
23
+ const iconSizes = {
24
+ small: 20,
25
+ medium: 24,
26
+ large: 28
27
+ };
28
+
29
+ // Position styles
30
+ const positionStyles = {
31
+ 'bottom-left': {
32
+ bottom: bottomOffset,
33
+ left: sideOffset,
34
+ right: 'auto'
35
+ },
36
+ 'bottom-right': {
37
+ bottom: bottomOffset,
38
+ right: sideOffset,
39
+ left: 'auto'
40
+ }
41
+ };
42
+
43
+ const openWhatsApp = () => {
44
+ // Call the callback if provided (e.g., for analytics)
45
+ if (onBeforeOpen) {
46
+ onBeforeOpen();
47
+ }
48
+
49
+ // Format phone number (remove any non-digits, ensure international format)
50
+ const cleanPhone = phoneNumber.replace(/\D/g, '');
51
+ const encodedMessage = encodeURIComponent(message);
52
+
53
+ // Open WhatsApp in new tab
54
+ window.open(
55
+ `https://wa.me/${cleanPhone}?text=${encodedMessage}`,
56
+ '_blank'
57
+ );
58
+ };
59
+
60
+ return (
61
+ <motion.button
62
+ onClick={openWhatsApp}
63
+ className={`fixed z-50 bg-green-500 hover:bg-green-600 text-white rounded-full shadow-lg hover:shadow-xl transition-all duration-300 flex items-center justify-center ${sizeClasses[size]} ${className}`}
64
+ style={positionStyles[position]}
65
+ initial={{ scale: 0, opacity: 0 }}
66
+ animate={{ scale: 1, opacity: 1 }}
67
+ whileHover={{ scale: 1.1 }}
68
+ whileTap={{ scale: 0.9 }}
69
+ transition={{
70
+ type: "spring",
71
+ stiffness: 300,
72
+ damping: 25
73
+ }}
74
+ aria-label="פתח WhatsApp"
75
+ {...props}
76
+ >
77
+ <MessageCircle size={iconSizes[size]} />
78
+ </motion.button>
79
+ );
80
+ };
81
+
82
+ // Demo Component
83
+ const Demo = () => {
84
+ const handleBeforeOpen = () => {
85
+ console.log('WhatsApp button clicked - you can add analytics here');
86
+ };
87
+
88
+ return (
89
+ <div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100 p-8" dir="rtl">
90
+ <div className="max-w-4xl mx-auto">
91
+ <h1 className="text-4xl font-bold text-gray-800 mb-6 text-center">
92
+ WhatsApp Floating Button Demo
93
+ </h1>
94
+
95
+ <div className="bg-white rounded-xl shadow-lg p-8 mb-8">
96
+ <h2 className="text-2xl font-semibold mb-4">תכונות:</h2>
97
+ <ul className="space-y-2 text-gray-700">
98
+ <li>✅ מיקום גמיש (שמאל/ימין למטה)</li>
99
+ <li>✅ שליטה מלאה על המרחקים מהקצוות</li>
100
+ <li>✅ 3 גדלים: small, medium, large</li>
101
+ <li>✅ הודעה מותאמת אישית</li>
102
+ <li>✅ Callback לפני פתיחה (לאנליטיקס)</li>
103
+ <li>✅ אנימציות חלקות עם Framer Motion</li>
104
+ </ul>
105
+ </div>
106
+
107
+ <div className="bg-white rounded-xl shadow-lg p-8">
108
+ <h2 className="text-2xl font-semibold mb-4">דוגמאות שימוש:</h2>
109
+
110
+ <div className="space-y-4 text-sm bg-gray-50 p-4 rounded-lg font-mono text-left" dir="ltr">
111
+ <div className="bg-white p-3 rounded border">
112
+ <div className="text-gray-500 mb-2">// Bottom Left (default)</div>
113
+ <code className="text-blue-600">
114
+ {`<FloatingWhatsAppButton
115
+ phoneNumber="972542397230"
116
+ message="שלום, אני מעוניין לקבוע פגישה"
117
+ position="bottom-left"
118
+ />`}
119
+ </code>
120
+ </div>
121
+
122
+ <div className="bg-white p-3 rounded border">
123
+ <div className="text-gray-500 mb-2">// Bottom Right with custom offsets</div>
124
+ <code className="text-blue-600">
125
+ {`<FloatingWhatsAppButton
126
+ phoneNumber="972542397230"
127
+ position="bottom-right"
128
+ bottomOffset="2rem"
129
+ sideOffset="2rem"
130
+ size="medium"
131
+ />`}
132
+ </code>
133
+ </div>
134
+
135
+ <div className="bg-white p-3 rounded border">
136
+ <div className="text-gray-500 mb-2">// With analytics callback</div>
137
+ <code className="text-blue-600">
138
+ {`<FloatingWhatsAppButton
139
+ phoneNumber="972542397230"
140
+ onBeforeOpen={() => {
141
+ // Send analytics event
142
+ sendAdsConversion({...});
143
+ }}
144
+ />`}
145
+ </code>
146
+ </div>
147
+ </div>
148
+ </div>
149
+
150
+ <div className="mt-8 text-center text-gray-600">
151
+ <p>גלול למטה כדי לראות את הכפתור בפעולה! 👇</p>
152
+ <div className="mt-4 h-96"></div>
153
+ </div>
154
+ </div>
155
+
156
+ {/* Demo Buttons */}
157
+ <FloatingWhatsAppButton
158
+ phoneNumber="972542397230"
159
+ message="שלום, אני מעוניין לקבוע פגישה לטיפול פסיכולוגי"
160
+ position="bottom-left"
161
+ bottomOffset="1.5rem"
162
+ sideOffset="1.5rem"
163
+ size="large"
164
+ onBeforeOpen={handleBeforeOpen}
165
+ />
166
+
167
+ <FloatingWhatsAppButton
168
+ phoneNumber="972542397230"
169
+ message="היי! רציתי לשאול משהו"
170
+ position="bottom-right"
171
+ bottomOffset="1.5rem"
172
+ sideOffset="1.5rem"
173
+ size="medium"
174
+ onBeforeOpen={handleBeforeOpen}
175
+ />
176
+ </div>
177
+ );
178
+ };
179
+
180
+ export default FloatingWhatsAppButton;
@@ -63,7 +63,7 @@ const FixedWidthHeroVideo = ({ youtubeVideoId = "dQw4w9WgXcQ", useYoutube = fals
63
63
  </section>
64
64
 
65
65
  {/* CSS Animation Styles */}
66
- <style jsx>{`
66
+ <style>{`
67
67
  @keyframes fadeInUp {
68
68
  from {
69
69
  opacity: 0;
package/dist/index.js CHANGED
@@ -16,6 +16,8 @@ export { default as Menu } from './components/Menu'
16
16
  export { default as ProductsDisplay } from './components/products/ProductsDisplay'
17
17
  export { default as ProductsSidebar } from './components/products/ProductsSidebar'
18
18
  export { default as MyOrdersDisplay } from './components/MyOrdersDisplay'
19
+ export { default as AccessibilityMenu } from './components/AccessibilityMenu'
20
+ export { default as FloatingWhatsAppButton } from './components/FloatingWhatsAppButton'
19
21
 
20
22
  // Modals
21
23
  export { default as ItemDetailsModal } from './components/modals/ItemDetailsModal'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codesinger0/shared-components",
3
- "version": "1.1.13",
3
+ "version": "1.1.15",
4
4
  "description": "Shared React components for customer projects",
5
5
  "main": "dist/index.js",
6
6
  "files": [