Skip to content

Commit 91daf8e

Browse files
committed
feat: refactor UX Patterns page with reusable components
- Create LivePreview component: - Mobile frame with 9:16 aspect ratio and 400px height - Accepts children prop for embeddable content - Fallback to default content with smartphone icon - Clean, reusable design for future content integration - Create CodePreview component: - Handles copy-to-clipboard functionality with state management - Shows 'Copied!' feedback with 3-second timeout - Smooth tap animation and hover effects - Consistent styling with site brand - Create PatternAccordion component: - Encapsulates entire accordion logic and state - Integrates LivePreview and CodePreview components - Handles pattern toggle and animation states - Clean separation of concerns - Create interactionPatterns data file: - Separates data from components for better organization - Exports TypeScript interface for type safety - Centralized data management for easy updates - Refactor UX Patterns page: - Remove large inline data arrays and complex logic - Use new modular components for cleaner code - Reduce file size from 610 to ~200 lines - Improve maintainability and reusability - Benefits: - Cleaner, more maintainable code structure - Reusable components for other pages - Better separation of concerns - Easier to add new interaction patterns - Prepared for future content embedding in LivePreview
1 parent 6b5b245 commit 91daf8e

File tree

5 files changed

+421
-335
lines changed

5 files changed

+421
-335
lines changed

src/app/product/UX-patterns/page.tsx

Lines changed: 5 additions & 335 deletions
Original file line numberDiff line numberDiff line change
@@ -1,240 +1,27 @@
11
'use client';
22

3-
import { useState } from 'react';
4-
import { motion, AnimatePresence } from 'framer-motion';
3+
import { motion } from 'framer-motion';
54
import { useLanguage } from '@/contexts/LanguageContext';
6-
import { MousePointer, Smartphone, ChevronDown, CheckCircle, XCircle, ArrowLeft, Copy } from 'lucide-react';
5+
import { MousePointer, CheckCircle, XCircle, ArrowLeft } from 'lucide-react';
76
import { useRouter } from 'next/navigation';
87
import Header from '@/components/layout/Header';
98
import Footer from '@/components/layout/Footer';
109
import SectionNavigation from '@/components/ui/SectionNavigation';
1110
import { SectionTitle } from '@/components/ui/SectionTitle';
1211
import { BlurReveal } from '@/components/ui/BlurReveal';
12+
import PatternAccordion from '@/components/ui/PatternAccordion';
13+
import { interactionPatterns } from '@/data/interactionPatterns';
1314

1415
const UXPatternsPage = () => {
1516
const { t } = useLanguage();
1617
const router = useRouter();
17-
const [openPattern, setOpenPattern] = useState<string | null>(null);
18-
const [copiedPattern, setCopiedPattern] = useState<string | null>(null);
19-
20-
const handlePatternToggle = (patternId: string) => {
21-
setOpenPattern(openPattern === patternId ? null : patternId);
22-
};
23-
24-
const handleCopyCode = async (patternId: string, code: string) => {
25-
try {
26-
await navigator.clipboard.writeText(code);
27-
setCopiedPattern(patternId);
28-
setTimeout(() => setCopiedPattern(null), 3000);
29-
} catch (err) {
30-
console.error('Failed to copy code:', err);
31-
}
32-
};
3318

3419
const sections = [
3520
{ id: 'interaction-patterns', label: 'Interaction Patterns' },
3621
{ id: 'ui-kit', label: 'UI Kit' },
3722
{ id: 'micro-interactions', label: 'Micro-interactions' }
3823
];
3924

40-
const interactionPatterns = [
41-
{
42-
id: 'onLoad',
43-
title: 'While loading',
44-
description: 'Skeleton screens, loading states, and progressive disclosure patterns that keep users engaged during wait times.',
45-
tags: ['Skeleton UI', 'Loading States', 'Progressive Disclosure'],
46-
code: `// Skeleton Loading Component
47-
const SkeletonCard = () => (
48-
<div className="animate-pulse">
49-
<div className="h-4 bg-gray-300 rounded w-3/4 mb-2"></div>
50-
<div className="h-4 bg-gray-300 rounded w-1/2"></div>
51-
</div>
52-
);
53-
54-
// Usage
55-
{isLoading ? <SkeletonCard /> : <ActualContent />}`
56-
},
57-
{
58-
id: 'onScroll',
59-
title: 'Page Scroll',
60-
description: 'Default vertical scrolling as the primary interaction pattern, with parallax and reveal animations.',
61-
tags: ['Vertical Scroll', 'Parallax', 'Reveal Animations'],
62-
code: `// Scroll-triggered Animation
63-
const useScrollAnimation = () => {
64-
const [isVisible, setIsVisible] = useState(false);
65-
66-
useEffect(() => {
67-
const handleScroll = () => {
68-
const element = document.getElementById('target');
69-
if (element) {
70-
const rect = element.getBoundingClientRect();
71-
setIsVisible(rect.top < window.innerHeight);
72-
}
73-
};
74-
75-
window.addEventListener('scroll', handleScroll);
76-
return () => window.removeEventListener('scroll', handleScroll);
77-
}, []);
78-
79-
return isVisible;
80-
};`
81-
},
82-
{
83-
id: 'notify',
84-
title: 'Notify',
85-
description: 'Toast notifications, banners, and system messages that provide feedback without interrupting user flow.',
86-
tags: ['Toast', 'Banner', 'System Messages'],
87-
code: `// Toast Notification Hook
88-
const useToast = () => {
89-
const [toasts, setToasts] = useState([]);
90-
91-
const showToast = (message, type = 'info') => {
92-
const id = Date.now();
93-
setToasts(prev => [...prev, { id, message, type }]);
94-
95-
setTimeout(() => {
96-
setToasts(prev => prev.filter(toast => toast.id !== id));
97-
}, 3000);
98-
};
99-
100-
return { toasts, showToast };
101-
};`
102-
},
103-
{
104-
id: 'alert',
105-
title: 'Alert',
106-
description: 'Critical notifications and warnings that require immediate user attention and action.',
107-
tags: ['Critical Alerts', 'Warnings', 'User Attention'],
108-
code: `// Alert Modal Component
109-
const AlertModal = ({ isOpen, onClose, title, message, type }) => {
110-
if (!isOpen) return null;
111-
112-
return (
113-
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
114-
<div className="bg-white rounded-lg p-6 max-w-md mx-4">
115-
<div className="flex items-center mb-4">
116-
<AlertCircle className="w-6 h-6 text-red-500 mr-2" />
117-
<h3 className="text-lg font-semibold">{title}</h3>
118-
</div>
119-
<p className="text-gray-600 mb-4">{message}</p>
120-
<button onClick={onClose} className="bg-red-500 text-white px-4 py-2 rounded">
121-
Dismiss
122-
</button>
123-
</div>
124-
</div>
125-
);
126-
};`
127-
},
128-
{
129-
id: 'pauseAsk',
130-
title: 'Pause & Ask',
131-
description: 'Modal dialogs and popups that pause user flow to gather information or confirm actions.',
132-
tags: ['Modal', 'Popup', 'Confirmation'],
133-
code: `// Confirmation Dialog
134-
const ConfirmationDialog = ({ isOpen, onConfirm, onCancel, message }) => {
135-
if (!isOpen) return null;
136-
137-
return (
138-
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
139-
<div className="bg-white rounded-lg p-6 max-w-sm mx-4">
140-
<p className="mb-4">{message}</p>
141-
<div className="flex space-x-3">
142-
<button onClick={onCancel} className="flex-1 px-4 py-2 border rounded">
143-
Cancel
144-
</button>
145-
<button onClick={onConfirm} className="flex-1 px-4 py-2 bg-blue-500 text-white rounded">
146-
Confirm
147-
</button>
148-
</div>
149-
</div>
150-
</div>
151-
);
152-
};`
153-
},
154-
{
155-
id: 'magnify',
156-
title: 'Magnify',
157-
description: 'Bottom sheets and expandable content that provides detailed information without leaving the current context.',
158-
tags: ['Bottom Sheet', 'Expandable', 'Detail View'],
159-
code: `// Bottom Sheet Component
160-
const BottomSheet = ({ isOpen, onClose, children }) => {
161-
return (
162-
<div className={\`fixed inset-0 z-50 \${isOpen ? 'block' : 'hidden'}\`}>
163-
<div className="absolute inset-0 bg-black/50" onClick={onClose} />
164-
<div className="absolute bottom-0 left-0 right-0 bg-white rounded-t-xl max-h-96 overflow-y-auto">
165-
<div className="w-12 h-1 bg-gray-300 rounded mx-auto mt-2 mb-4" />
166-
{children}
167-
</div>
168-
</div>
169-
);
170-
};`
171-
},
172-
{
173-
id: 'screenToScreen',
174-
title: 'Screen to Screen',
175-
description: 'Navigation patterns and transitions between different screens and sections of the application.',
176-
tags: ['Navigation', 'Transitions', 'Screen Flow'],
177-
code: `// Page Transition Hook
178-
const usePageTransition = () => {
179-
const [isTransitioning, setIsTransitioning] = useState(false);
180-
181-
const navigateWithTransition = (path) => {
182-
setIsTransitioning(true);
183-
184-
setTimeout(() => {
185-
router.push(path);
186-
setIsTransitioning(false);
187-
}, 300);
188-
};
189-
190-
return { isTransitioning, navigateWithTransition };
191-
};`
192-
},
193-
{
194-
id: 'feedback',
195-
title: 'Feedback',
196-
description: 'Touch, swipe, and gesture-based interactions that provide immediate visual and haptic feedback.',
197-
tags: ['Touch', 'Swipe', 'Gestures', 'Haptic'],
198-
code: `// Touch Feedback Hook
199-
const useTouchFeedback = () => {
200-
const [isPressed, setIsPressed] = useState(false);
201-
202-
const handleTouchStart = () => {
203-
setIsPressed(true);
204-
// Add haptic feedback if available
205-
if (navigator.vibrate) {
206-
navigator.vibrate(50);
207-
}
208-
};
209-
210-
const handleTouchEnd = () => {
211-
setIsPressed(false);
212-
};
213-
214-
return { isPressed, handleTouchStart, handleTouchEnd };
215-
};`
216-
},
217-
{
218-
id: 'moreToCome',
219-
title: 'More to come',
220-
description: 'Additional interaction patterns are continuously being developed and refined based on user needs.',
221-
tags: ['Coming Soon', 'Development', 'User Needs'],
222-
code: `// Coming Soon Placeholder
223-
const ComingSoonPattern = () => (
224-
<div className="text-center py-8">
225-
<div className="w-16 h-16 bg-gray-200 rounded-full mx-auto mb-4 flex items-center justify-center">
226-
<Plus className="w-8 h-8 text-gray-400" />
227-
</div>
228-
<h3 className="text-lg font-semibold text-gray-600 mb-2">
229-
New Pattern Coming Soon
230-
</h3>
231-
<p className="text-gray-500">
232-
We&apos;re constantly developing new interaction patterns based on user needs.
233-
</p>
234-
</div>
235-
);`
236-
}
237-
];
23825

23926
return (
24027
<main className="min-h-screen relative">
@@ -357,125 +144,8 @@ const ComingSoonPattern = () => (
357144
whileInView={{ opacity: 1, y: 0 }}
358145
transition={{ duration: 0.6, delay: 0.3 }}
359146
viewport={{ once: true }}
360-
className="space-y-3"
361147
>
362-
{interactionPatterns.map((pattern, patternIndex) => (
363-
<motion.div
364-
key={pattern.id}
365-
initial={{ opacity: 0, y: 20 }}
366-
whileInView={{ opacity: 1, y: 0 }}
367-
transition={{ duration: 0.4, delay: patternIndex * 0.05 }}
368-
viewport={{ once: true }}
369-
className="bg-muted/10 border border-border/50 rounded-lg overflow-hidden hover:bg-muted/20 transition-colors"
370-
>
371-
{/* Pattern Header */}
372-
<button
373-
onClick={() => handlePatternToggle(pattern.id)}
374-
className="w-full p-4 text-left flex items-center justify-between hover:bg-muted/10 transition-colors"
375-
>
376-
<div className="flex-1">
377-
<h4 className="text-base font-semibold text-white">
378-
{pattern.title}
379-
</h4>
380-
</div>
381-
<motion.div
382-
animate={{ rotate: openPattern === pattern.id ? 180 : 0 }}
383-
transition={{ duration: 0.3 }}
384-
>
385-
<ChevronDown className="w-4 h-4 text-muted-foreground" />
386-
</motion.div>
387-
</button>
388-
389-
{/* Pattern Content */}
390-
<AnimatePresence>
391-
{openPattern === pattern.id && (
392-
<motion.div
393-
initial={{ height: 0, opacity: 0 }}
394-
animate={{ height: 'auto', opacity: 1 }}
395-
exit={{ height: 0, opacity: 0 }}
396-
transition={{ duration: 0.3, ease: 'easeInOut' }}
397-
className="overflow-hidden"
398-
>
399-
<div className="px-4 pb-4">
400-
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
401-
{/* Left Column - Description, Tags, and Code */}
402-
<div className="space-y-4">
403-
{/* Description */}
404-
<div className="space-y-3">
405-
<h5 className="text-sm font-medium text-white">
406-
Description
407-
</h5>
408-
<p className="text-muted-foreground leading-relaxed text-sm">
409-
{pattern.description}
410-
</p>
411-
412-
{/* Tags */}
413-
<div className="flex flex-wrap gap-2 mt-3">
414-
{pattern.tags.map((tag, tagIndex) => (
415-
<span
416-
key={tagIndex}
417-
className="px-2 py-1 bg-primary/20 text-primary text-xs rounded-full border border-primary/30"
418-
>
419-
{tag}
420-
</span>
421-
))}
422-
</div>
423-
</div>
424-
425-
{/* Code Preview */}
426-
{pattern.code && (
427-
<div className="space-y-3">
428-
<div className="flex items-center justify-between">
429-
<h5 className="text-sm font-medium text-white">
430-
Code Example
431-
</h5>
432-
<motion.button
433-
onClick={() => handleCopyCode(pattern.id, pattern.code)}
434-
className="flex items-center space-x-1 text-xs text-muted-foreground hover:text-white transition-colors"
435-
whileTap={{ scale: 0.95 }}
436-
>
437-
<Copy className="w-3 h-3" />
438-
<span>{copiedPattern === pattern.id ? 'Copied!' : 'Copy'}</span>
439-
</motion.button>
440-
</div>
441-
<div className="bg-muted/10 border border-border/50 rounded-lg p-4 overflow-x-auto">
442-
<pre className="text-xs text-muted-foreground font-mono leading-relaxed">
443-
<code>{pattern.code}</code>
444-
</pre>
445-
</div>
446-
</div>
447-
)}
448-
</div>
449-
450-
{/* Right Column - Live Preview */}
451-
<div className="space-y-3">
452-
<h5 className="text-sm font-medium text-white">
453-
Live Preview
454-
</h5>
455-
<div className="flex justify-center">
456-
{/* Mobile Frame with 9:16 ratio (vertical phone) */}
457-
<div className="w-[200px] bg-black rounded-[1.5rem] p-1 shadow-2xl">
458-
<div className="bg-muted/20 rounded-[1.25rem] h-[400px] flex items-center justify-center" style={{ aspectRatio: '9/16' }}>
459-
<div className="text-center space-y-3 px-4">
460-
<Smartphone className="w-8 h-8 text-muted-foreground mx-auto" />
461-
<p className="text-sm text-muted-foreground break-words font-medium">
462-
{pattern.title}
463-
</p>
464-
<p className="text-xs text-muted-foreground/70">
465-
Preview coming soon
466-
</p>
467-
</div>
468-
</div>
469-
</div>
470-
</div>
471-
</div>
472-
</div>
473-
</div>
474-
</motion.div>
475-
)}
476-
</AnimatePresence>
477-
</motion.div>
478-
))}
148+
<PatternAccordion patterns={interactionPatterns} />
479149
</motion.div>
480150
</div>
481151
</section>

0 commit comments

Comments
 (0)