@rakeyshgidwani/roger-ui-bank-theme-stan-design 0.1.3 → 0.1.5

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.
Files changed (164) hide show
  1. package/CHANGELOG.md +1 -1
  2. package/dist/index.d.ts +131 -131
  3. package/dist/index.esm.js +148 -148
  4. package/dist/index.js +148 -148
  5. package/dist/styles.css +1 -1
  6. package/package.json +1 -1
  7. package/src/components/ui/accessibility-demo.tsx +271 -0
  8. package/src/components/ui/advanced-component-architecture-demo.tsx +916 -0
  9. package/src/components/ui/advanced-transition-system-demo.tsx +670 -0
  10. package/src/components/ui/advanced-transition-system.tsx +395 -0
  11. package/src/components/ui/animation/animated-container.tsx +166 -0
  12. package/src/components/ui/animation/index.ts +19 -0
  13. package/src/components/ui/animation/staggered-container.tsx +68 -0
  14. package/src/components/ui/animation-demo.tsx +250 -0
  15. package/src/components/ui/badge.tsx +33 -0
  16. package/src/components/ui/battery-conscious-animation-demo.tsx +568 -0
  17. package/src/components/ui/border-radius-shadow-demo.tsx +187 -0
  18. package/src/components/ui/button.tsx +36 -0
  19. package/src/components/ui/card.tsx +207 -0
  20. package/src/components/ui/checkbox.tsx +30 -0
  21. package/src/components/ui/color-preview.tsx +411 -0
  22. package/src/components/ui/data-display/chart.tsx +653 -0
  23. package/src/components/ui/data-display/data-grid-simple.tsx +76 -0
  24. package/src/components/ui/data-display/data-grid.tsx +680 -0
  25. package/src/components/ui/data-display/list.tsx +456 -0
  26. package/src/components/ui/data-display/table.tsx +482 -0
  27. package/src/components/ui/data-display/timeline.tsx +441 -0
  28. package/src/components/ui/data-display/tree.tsx +602 -0
  29. package/src/components/ui/data-display/types.ts +536 -0
  30. package/src/components/ui/enterprise-mobile-experience-demo.tsx +749 -0
  31. package/src/components/ui/enterprise-mobile-experience.tsx +464 -0
  32. package/src/components/ui/feedback/alert.tsx +157 -0
  33. package/src/components/ui/feedback/progress.tsx +292 -0
  34. package/src/components/ui/feedback/skeleton.tsx +185 -0
  35. package/src/components/ui/feedback/toast.tsx +280 -0
  36. package/src/components/ui/feedback/types.ts +125 -0
  37. package/src/components/ui/font-preview.tsx +288 -0
  38. package/src/components/ui/form-demo.tsx +553 -0
  39. package/src/components/ui/hardware-acceleration-demo.tsx +547 -0
  40. package/src/components/ui/input.tsx +35 -0
  41. package/src/components/ui/label.tsx +16 -0
  42. package/src/components/ui/layout-demo.tsx +367 -0
  43. package/src/components/ui/layouts/adaptive-layout.tsx +139 -0
  44. package/src/components/ui/layouts/desktop-layout.tsx +224 -0
  45. package/src/components/ui/layouts/index.ts +10 -0
  46. package/src/components/ui/layouts/mobile-layout.tsx +162 -0
  47. package/src/components/ui/layouts/tablet-layout.tsx +197 -0
  48. package/src/components/ui/mobile-form-validation.tsx +451 -0
  49. package/src/components/ui/mobile-input-demo.tsx +201 -0
  50. package/src/components/ui/mobile-input.tsx +281 -0
  51. package/src/components/ui/mobile-skeleton-loading-demo.tsx +638 -0
  52. package/src/components/ui/navigation/breadcrumb.tsx +158 -0
  53. package/src/components/ui/navigation/index.ts +36 -0
  54. package/src/components/ui/navigation/menu.tsx +374 -0
  55. package/src/components/ui/navigation/navigation-demo.tsx +324 -0
  56. package/src/components/ui/navigation/pagination.tsx +272 -0
  57. package/src/components/ui/navigation/sidebar.tsx +383 -0
  58. package/src/components/ui/navigation/stepper.tsx +303 -0
  59. package/src/components/ui/navigation/tabs.tsx +205 -0
  60. package/src/components/ui/navigation/types.ts +299 -0
  61. package/src/components/ui/overlay/backdrop.tsx +81 -0
  62. package/src/components/ui/overlay/focus-manager.tsx +143 -0
  63. package/src/components/ui/overlay/index.ts +36 -0
  64. package/src/components/ui/overlay/modal.tsx +270 -0
  65. package/src/components/ui/overlay/overlay-manager.tsx +110 -0
  66. package/src/components/ui/overlay/popover.tsx +462 -0
  67. package/src/components/ui/overlay/portal.tsx +79 -0
  68. package/src/components/ui/overlay/tooltip.tsx +303 -0
  69. package/src/components/ui/overlay/types.ts +196 -0
  70. package/src/components/ui/performance-demo.tsx +596 -0
  71. package/src/components/ui/semantic-input-system-demo.tsx +502 -0
  72. package/src/components/ui/semantic-input-system-demo.tsx.disabled +873 -0
  73. package/src/components/ui/tablet-layout.tsx +192 -0
  74. package/src/components/ui/theme-customizer.tsx +386 -0
  75. package/src/components/ui/theme-preview.tsx +310 -0
  76. package/src/components/ui/theme-switcher.tsx +264 -0
  77. package/src/components/ui/theme-toggle.tsx +38 -0
  78. package/src/components/ui/token-demo.tsx +195 -0
  79. package/src/components/ui/touch-demo.tsx +462 -0
  80. package/src/components/ui/touch-friendly-interface-demo.tsx +519 -0
  81. package/src/components/ui/touch-friendly-interface.tsx +296 -0
  82. package/src/hooks/index.ts +190 -0
  83. package/src/hooks/use-accessibility-support.ts +518 -0
  84. package/src/hooks/use-adaptive-layout.ts +289 -0
  85. package/src/hooks/use-advanced-patterns.ts +294 -0
  86. package/src/hooks/use-advanced-transition-system.ts +393 -0
  87. package/src/hooks/use-animation-profile.ts +288 -0
  88. package/src/hooks/use-battery-animations.ts +384 -0
  89. package/src/hooks/use-battery-conscious-loading.ts +475 -0
  90. package/src/hooks/use-battery-optimization.ts +330 -0
  91. package/src/hooks/use-battery-status.ts +299 -0
  92. package/src/hooks/use-component-performance.ts +344 -0
  93. package/src/hooks/use-device-loading-states.ts +459 -0
  94. package/src/hooks/use-device.tsx +110 -0
  95. package/src/hooks/use-enterprise-mobile-experience.ts +488 -0
  96. package/src/hooks/use-form-feedback.ts +403 -0
  97. package/src/hooks/use-form-performance.ts +513 -0
  98. package/src/hooks/use-frame-rate.ts +251 -0
  99. package/src/hooks/use-gestures.ts +338 -0
  100. package/src/hooks/use-hardware-acceleration.ts +341 -0
  101. package/src/hooks/use-input-accessibility.ts +455 -0
  102. package/src/hooks/use-input-performance.ts +506 -0
  103. package/src/hooks/use-layout-performance.ts +319 -0
  104. package/src/hooks/use-loading-accessibility.ts +535 -0
  105. package/src/hooks/use-loading-performance.ts +473 -0
  106. package/src/hooks/use-memory-usage.ts +287 -0
  107. package/src/hooks/use-mobile-form-layout.ts +464 -0
  108. package/src/hooks/use-mobile-form-validation.ts +518 -0
  109. package/src/hooks/use-mobile-keyboard-optimization.ts +472 -0
  110. package/src/hooks/use-mobile-layout.ts +302 -0
  111. package/src/hooks/use-mobile-optimization.ts +406 -0
  112. package/src/hooks/use-mobile-skeleton.ts +402 -0
  113. package/src/hooks/use-mobile-touch.ts +414 -0
  114. package/src/hooks/use-performance-throttling.ts +348 -0
  115. package/src/hooks/use-performance.ts +316 -0
  116. package/src/hooks/use-reusable-architecture.ts +414 -0
  117. package/src/hooks/use-semantic-input-types.ts +357 -0
  118. package/src/hooks/use-semantic-input.ts +565 -0
  119. package/src/hooks/use-tablet-layout.ts +384 -0
  120. package/src/hooks/use-touch-friendly-input.ts +524 -0
  121. package/src/hooks/use-touch-friendly-interface.ts +331 -0
  122. package/src/hooks/use-touch-optimization.ts +375 -0
  123. package/src/index.ts +279 -279
  124. package/src/lib/utils.ts +6 -0
  125. package/src/themes/README.md +272 -0
  126. package/src/themes/ThemeContext.tsx +31 -0
  127. package/src/themes/ThemeProvider.tsx +232 -0
  128. package/src/themes/accessibility/index.ts +27 -0
  129. package/src/themes/accessibility.ts +259 -0
  130. package/src/themes/aria-patterns.ts +420 -0
  131. package/src/themes/base-themes.ts +55 -0
  132. package/src/themes/colorManager.ts +380 -0
  133. package/src/themes/examples/dark-theme.ts +154 -0
  134. package/src/themes/examples/minimal-theme.ts +108 -0
  135. package/src/themes/focus-management.ts +701 -0
  136. package/src/themes/fontLoader.ts +201 -0
  137. package/src/themes/high-contrast.ts +621 -0
  138. package/src/themes/index.ts +19 -0
  139. package/src/themes/inheritance.ts +227 -0
  140. package/src/themes/keyboard-navigation.ts +550 -0
  141. package/src/themes/motion-reduction.ts +662 -0
  142. package/src/themes/navigation.ts +238 -0
  143. package/src/themes/screen-reader.ts +645 -0
  144. package/src/themes/systemThemeDetector.ts +182 -0
  145. package/src/themes/themeCSSUpdater.ts +262 -0
  146. package/src/themes/themePersistence.ts +238 -0
  147. package/src/themes/themes/default.ts +586 -0
  148. package/src/themes/themes/harvey.ts +554 -0
  149. package/src/themes/themes/stan-design.ts +683 -0
  150. package/src/themes/types.ts +460 -0
  151. package/src/themes/useSystemTheme.ts +48 -0
  152. package/src/themes/useTheme.ts +87 -0
  153. package/src/themes/validation.ts +462 -0
  154. package/src/tokens/index.ts +34 -0
  155. package/src/tokens/tokenExporter.ts +397 -0
  156. package/src/tokens/tokenGenerator.ts +276 -0
  157. package/src/tokens/tokenManager.ts +248 -0
  158. package/src/tokens/tokenValidator.ts +543 -0
  159. package/src/tokens/types.ts +78 -0
  160. package/src/utils/bundle-analyzer.ts +260 -0
  161. package/src/utils/bundle-splitting.ts +483 -0
  162. package/src/utils/lazy-loading.ts +441 -0
  163. package/src/utils/performance-monitor.ts +513 -0
  164. package/src/utils/tree-shaking.ts +274 -0
@@ -0,0 +1,251 @@
1
+ import { useState, useEffect, useCallback, useRef, useMemo } from 'react'
2
+
3
+ export interface FrameRateMetrics {
4
+ fps: number
5
+ frameTime: number
6
+ frameCount: number
7
+ averageFPS: number
8
+ minFPS: number
9
+ maxFPS: number
10
+ droppedFrames: number
11
+ performanceScore: 'excellent' | 'good' | 'fair' | 'poor'
12
+ }
13
+
14
+ export interface FrameRateThresholds {
15
+ warning: number
16
+ critical: number
17
+ excellent: number
18
+ }
19
+
20
+ export interface FrameRateCallbacks {
21
+ onFPSWarning?: (fps: number) => void
22
+ onFPSDrop?: (fps: number, previousFPS: number) => void
23
+ onPerformanceScoreChange?: (score: FrameRateMetrics['performanceScore']) => void
24
+ }
25
+
26
+ export interface FrameRateOptions {
27
+ updateInterval?: number
28
+ sampleSize?: number
29
+ thresholds?: Partial<FrameRateThresholds>
30
+ callbacks?: FrameRateCallbacks
31
+ }
32
+
33
+ const DEFAULT_THRESHOLDS: FrameRateThresholds = {
34
+ warning: 45,
35
+ critical: 30,
36
+ excellent: 55
37
+ }
38
+
39
+ const DEFAULT_SAMPLE_SIZE = 60 // 1 second at 60fps
40
+
41
+ export const useFrameRate = (options: FrameRateOptions = {}) => {
42
+ const {
43
+ updateInterval = 1000,
44
+ sampleSize = DEFAULT_SAMPLE_SIZE,
45
+ thresholds = {},
46
+ callbacks = {}
47
+ } = options
48
+
49
+ const [metrics, setMetrics] = useState<FrameRateMetrics>({
50
+ fps: 60,
51
+ frameTime: 16.67,
52
+ frameCount: 0,
53
+ averageFPS: 60,
54
+ minFPS: 60,
55
+ maxFPS: 60,
56
+ droppedFrames: 0,
57
+ performanceScore: 'excellent'
58
+ })
59
+
60
+ const [isMonitoring, setIsMonitoring] = useState(false)
61
+ const frameCountRef = useRef(0)
62
+ const lastTimeRef = useRef(performance.now())
63
+ const animationIdRef = useRef<number>()
64
+ const intervalIdRef = useRef<NodeJS.Timeout>()
65
+ const fpsHistoryRef = useRef<number[]>([])
66
+ const previousFPSRef = useRef(60)
67
+
68
+ // Merge default thresholds with custom ones
69
+ const finalThresholds = useMemo(() => ({
70
+ ...DEFAULT_THRESHOLDS,
71
+ ...thresholds
72
+ }), [thresholds])
73
+
74
+ // Calculate performance score based on FPS
75
+ const calculatePerformanceScore = useCallback((fps: number): FrameRateMetrics['performanceScore'] => {
76
+ if (fps >= finalThresholds.excellent) return 'excellent'
77
+ if (fps >= finalThresholds.warning) return 'good'
78
+ if (fps >= finalThresholds.critical) return 'fair'
79
+ return 'poor'
80
+ }, [finalThresholds])
81
+
82
+ // Update FPS history and calculate statistics
83
+ const updateFPSHistory = useCallback((fps: number) => {
84
+ fpsHistoryRef.current.push(fps)
85
+
86
+ // Keep only the last sampleSize frames
87
+ if (fpsHistoryRef.current.length > sampleSize) {
88
+ fpsHistoryRef.current.shift()
89
+ }
90
+
91
+ // Calculate statistics
92
+ const averageFPS = Math.round(
93
+ fpsHistoryRef.current.reduce((sum, f) => sum + f, 0) / fpsHistoryRef.current.length
94
+ )
95
+ const minFPS = Math.min(...fpsHistoryRef.current)
96
+ const maxFPS = Math.max(...fpsHistoryRef.current)
97
+
98
+ return { averageFPS, minFPS, maxFPS }
99
+ }, [sampleSize])
100
+
101
+ // Detect dropped frames
102
+ const detectDroppedFrames = useCallback((fps: number, previousFPS: number) => {
103
+ if (fps < previousFPS && previousFPS >= finalThresholds.warning) {
104
+ const drop = previousFPS - fps
105
+ if (drop > 10) { // Significant drop
106
+ return drop
107
+ }
108
+ }
109
+ return 0
110
+ }, [finalThresholds.warning])
111
+
112
+ // Frame rate monitoring
113
+ const measureFrameRate = useCallback(() => {
114
+ frameCountRef.current++
115
+ const currentTime = performance.now()
116
+
117
+ if (currentTime - lastTimeRef.current >= updateInterval) {
118
+ const fps = Math.round((frameCountRef.current * 1000) / (currentTime - lastTimeRef.current))
119
+ const frameTime = 1000 / fps
120
+
121
+ // Update FPS history and get statistics
122
+ const { averageFPS, minFPS, maxFPS } = updateFPSHistory(fps)
123
+
124
+ // Detect dropped frames
125
+ const droppedFrames = detectDroppedFrames(fps, previousFPSRef.current)
126
+
127
+ // Calculate performance score
128
+ const performanceScore = calculatePerformanceScore(fps)
129
+
130
+ const newMetrics: FrameRateMetrics = {
131
+ fps,
132
+ frameTime,
133
+ frameCount: frameCountRef.current,
134
+ averageFPS,
135
+ minFPS,
136
+ maxFPS,
137
+ droppedFrames,
138
+ performanceScore
139
+ }
140
+
141
+ setMetrics(newMetrics)
142
+
143
+ // Check thresholds and call callbacks
144
+ if (fps <= finalThresholds.critical && callbacks.onFPSWarning) {
145
+ callbacks.onFPSWarning(fps)
146
+ }
147
+
148
+ if (droppedFrames > 0 && callbacks.onFPSDrop) {
149
+ callbacks.onFPSDrop(fps, previousFPSRef.current)
150
+ }
151
+
152
+ // Check if performance score changed
153
+ if (performanceScore !== metrics.performanceScore && callbacks.onPerformanceScoreChange) {
154
+ callbacks.onPerformanceScoreChange(performanceScore)
155
+ }
156
+
157
+ // Reset counters
158
+ frameCountRef.current = 0
159
+ lastTimeRef.current = currentTime
160
+ previousFPSRef.current = fps
161
+ }
162
+
163
+ animationIdRef.current = requestAnimationFrame(measureFrameRate)
164
+ }, [updateInterval, finalThresholds.critical, callbacks, metrics.performanceScore, updateFPSHistory, detectDroppedFrames, calculatePerformanceScore])
165
+
166
+ // Start monitoring
167
+ const startMonitoring = useCallback(() => {
168
+ if (isMonitoring) return
169
+
170
+ setIsMonitoring(true)
171
+ measureFrameRate()
172
+ }, [isMonitoring, measureFrameRate])
173
+
174
+ // Stop monitoring
175
+ const stopMonitoring = useCallback(() => {
176
+ setIsMonitoring(false)
177
+
178
+ if (animationIdRef.current) {
179
+ cancelAnimationFrame(animationIdRef.current)
180
+ }
181
+
182
+ if (intervalIdRef.current) {
183
+ clearInterval(intervalIdRef.current)
184
+ }
185
+ }, [])
186
+
187
+ // Reset statistics
188
+ const resetStats = useCallback(() => {
189
+ fpsHistoryRef.current = []
190
+ setMetrics(prev => ({
191
+ ...prev,
192
+ averageFPS: 60,
193
+ minFPS: 60,
194
+ maxFPS: 60,
195
+ droppedFrames: 0
196
+ }))
197
+ }, [])
198
+
199
+ // Auto-start monitoring on mount
200
+ useEffect(() => {
201
+ startMonitoring()
202
+ return () => stopMonitoring()
203
+ }, [startMonitoring, stopMonitoring])
204
+
205
+ // Performance insights
206
+ const getPerformanceInsights = useCallback(() => {
207
+ const insights: string[] = []
208
+
209
+ if (metrics.fps < finalThresholds.critical) {
210
+ insights.push('Critical FPS drop detected - consider reducing animation complexity')
211
+ } else if (metrics.fps < finalThresholds.warning) {
212
+ insights.push('FPS below optimal range - monitor for performance issues')
213
+ }
214
+
215
+ if (metrics.droppedFrames > 0) {
216
+ insights.push(`${metrics.droppedFrames} frames dropped - check for heavy operations`)
217
+ }
218
+
219
+ if (metrics.averageFPS < 50) {
220
+ insights.push('Average FPS is low - consider performance optimizations')
221
+ }
222
+
223
+ return insights
224
+ }, [metrics, finalThresholds])
225
+
226
+ return {
227
+ // Metrics
228
+ metrics,
229
+
230
+ // Controls
231
+ startMonitoring,
232
+ stopMonitoring,
233
+ isMonitoring,
234
+ resetStats,
235
+
236
+ // Performance analysis
237
+ performanceScore: metrics.performanceScore,
238
+ getPerformanceInsights,
239
+
240
+ // Raw values
241
+ fps: metrics.fps,
242
+ frameTime: metrics.frameTime,
243
+ averageFPS: metrics.averageFPS,
244
+ minFPS: metrics.minFPS,
245
+ maxFPS: metrics.maxFPS,
246
+ droppedFrames: metrics.droppedFrames,
247
+
248
+ // Thresholds
249
+ thresholds: finalThresholds
250
+ }
251
+ }
@@ -0,0 +1,338 @@
1
+ import { useState, useEffect, useCallback, useRef, useMemo } from 'react'
2
+
3
+ export interface GestureConfig {
4
+ minSwipeDistance: number
5
+ maxSwipeTime: number
6
+ minPinchDistance: number
7
+ enableHapticFeedback: boolean
8
+ enableSoundFeedback: boolean
9
+ }
10
+
11
+ export interface SwipeGesture {
12
+ direction: 'up' | 'down' | 'left' | 'right'
13
+ distance: number
14
+ velocity: number
15
+ duration: number
16
+ }
17
+
18
+ export interface PinchGesture {
19
+ scale: number
20
+ center: { x: number; y: number }
21
+ distance: number
22
+ }
23
+
24
+ export interface GestureCallbacks {
25
+ onSwipe?: (gesture: SwipeGesture) => void
26
+ onPinch?: (gesture: PinchGesture) => void
27
+ onTap?: (position: { x: number; y: number }) => void
28
+ onLongPress?: (position: { x: number; y: number }) => void
29
+ onTouchStart?: (position: { x: number; y: number }) => void
30
+ onTouchEnd?: (position: { x: number; y: number }) => void
31
+ }
32
+
33
+ export interface GestureState {
34
+ isTracking: boolean
35
+ currentGesture: 'none' | 'swipe' | 'pinch' | 'tap' | 'longPress'
36
+ lastGesture: SwipeGesture | PinchGesture | null
37
+ touchCount: number
38
+ }
39
+
40
+ export const useGestures = (
41
+ elementRef: React.RefObject<HTMLElement>,
42
+ callbacks: GestureCallbacks = {},
43
+ config: Partial<GestureConfig> = {}
44
+ ) => {
45
+ const defaultConfig: GestureConfig = {
46
+ minSwipeDistance: 50,
47
+ maxSwipeTime: 300,
48
+ minPinchDistance: 20,
49
+ enableHapticFeedback: true,
50
+ enableSoundFeedback: false,
51
+ ...config
52
+ }
53
+
54
+ const [gestureState, setGestureState] = useState<GestureState>({
55
+ isTracking: false,
56
+ currentGesture: 'none',
57
+ lastGesture: null,
58
+ touchCount: 0
59
+ })
60
+
61
+ const touchStartRef = useRef<{ x: number; y: number; time: number } | null>(null)
62
+ const touchStartDistanceRef = useRef<number>(0)
63
+ const touchStartCenterRef = useRef<{ x: number; y: number } | null>(null)
64
+ const longPressTimerRef = useRef<NodeJS.Timeout | null>(null)
65
+ const lastTapTimeRef = useRef<number>(0)
66
+
67
+ // Haptic feedback function
68
+ const triggerHapticFeedback = useCallback(() => {
69
+ if (defaultConfig.enableHapticFeedback && 'vibrate' in navigator) {
70
+ try {
71
+ navigator.vibrate(50)
72
+ } catch (error) {
73
+ // Fallback for older browsers
74
+ console.warn('Haptic feedback not supported')
75
+ }
76
+ }
77
+ }, [defaultConfig.enableHapticFeedback])
78
+
79
+ // Sound feedback function
80
+ const triggerSoundFeedback = useCallback(() => {
81
+ if (defaultConfig.enableSoundFeedback) {
82
+ // Create a simple beep sound
83
+ const audioContext = new (window.AudioContext || (window as any).webkitAudioContext)()
84
+ const oscillator = audioContext.createOscillator()
85
+ const gainNode = audioContext.createGain()
86
+
87
+ oscillator.connect(gainNode)
88
+ gainNode.connect(audioContext.destination)
89
+
90
+ oscillator.frequency.setValueAtTime(800, audioContext.currentTime)
91
+ gainNode.gain.setValueAtTime(0.1, audioContext.currentTime)
92
+
93
+ oscillator.start(audioContext.currentTime)
94
+ oscillator.stop(audioContext.currentTime + 0.1)
95
+ }
96
+ }, [defaultConfig.enableSoundFeedback])
97
+
98
+ // Calculate distance between two points
99
+ const calculateDistance = useCallback((p1: { x: number; y: number }, p2: { x: number; y: number }) => {
100
+ const dx = p2.x - p1.x
101
+ const dy = p2.y - p1.y
102
+ return Math.sqrt(dx * dx + dy * dy)
103
+ }, [])
104
+
105
+ // Calculate center point between two touches
106
+ const calculateCenter = useCallback((touches: TouchList) => {
107
+ let x = 0
108
+ let y = 0
109
+ for (let i = 0; i < touches.length; i++) {
110
+ x += touches[i].clientX
111
+ y += touches[i].clientY
112
+ }
113
+ return { x: x / touches.length, y: y / touches.length }
114
+ }, [])
115
+
116
+ // Handle touch start
117
+ const handleTouchStart = useCallback((event: TouchEvent) => {
118
+ event.preventDefault()
119
+
120
+ const touches = event.touches
121
+ const touchCount = touches.length
122
+
123
+ if (touchCount === 1) {
124
+ const touch = touches[0]
125
+ touchStartRef.current = {
126
+ x: touch.clientX,
127
+ y: touch.clientY,
128
+ time: Date.now()
129
+ }
130
+
131
+ // Start long press timer
132
+ longPressTimerRef.current = setTimeout(() => {
133
+ if (touchStartRef.current) {
134
+ const position = { x: touch.clientX, y: touch.clientY }
135
+ setGestureState(prev => ({ ...prev, currentGesture: 'longPress' }))
136
+ callbacks.onLongPress?.(position)
137
+ triggerHapticFeedback()
138
+ }
139
+ }, 500)
140
+
141
+ callbacks.onTouchStart?.({ x: touch.clientX, y: touch.clientY })
142
+ } else if (touchCount === 2) {
143
+ // Pinch gesture start
144
+ const center = calculateCenter(touches)
145
+ const distance = calculateDistance(
146
+ { x: touches[0].clientX, y: touches[0].clientY },
147
+ { x: touches[1].clientX, y: touches[1].clientY }
148
+ )
149
+
150
+ touchStartCenterRef.current = center
151
+ touchStartDistanceRef.current = distance
152
+ }
153
+
154
+ setGestureState(prev => ({ ...prev, isTracking: true, touchCount }))
155
+ }, [callbacks, calculateCenter, calculateDistance, triggerHapticFeedback])
156
+
157
+ // Handle touch move
158
+ const handleTouchMove = useCallback((event: TouchEvent) => {
159
+ event.preventDefault()
160
+
161
+ const touches = event.touches
162
+ const touchCount = touches.length
163
+
164
+ if (touchCount === 1 && touchStartRef.current) {
165
+ // Cancel long press timer on movement
166
+ if (longPressTimerRef.current) {
167
+ clearTimeout(longPressTimerRef.current)
168
+ longPressTimerRef.current = null
169
+ }
170
+ } else if (touchCount === 2 && touchStartCenterRef.current && touchStartDistanceRef.current) {
171
+ // Handle pinch gesture
172
+ const center = calculateCenter(touches)
173
+ const distance = calculateDistance(
174
+ { x: touches[0].clientX, y: touches[0].clientY },
175
+ { x: touches[1].clientX, y: touches[1].clientY }
176
+ )
177
+
178
+ const scale = distance / touchStartDistanceRef.current
179
+
180
+ if (Math.abs(scale - 1) * 100 > defaultConfig.minPinchDistance) {
181
+ const pinchGesture: PinchGesture = {
182
+ scale,
183
+ center,
184
+ distance
185
+ }
186
+
187
+ setGestureState(prev => ({ ...prev, currentGesture: 'pinch', lastGesture: pinchGesture }))
188
+ callbacks.onPinch?.(pinchGesture)
189
+ }
190
+ }
191
+ }, [callbacks, calculateCenter, calculateDistance, defaultConfig.minPinchDistance])
192
+
193
+ // Handle touch end
194
+ const handleTouchEnd = useCallback((event: TouchEvent) => {
195
+ event.preventDefault()
196
+
197
+ const touches = event.changedTouches
198
+ const touchCount = touches.length
199
+
200
+ if (touchCount === 1 && touchStartRef.current) {
201
+ const touch = touches[0]
202
+ const endTime = Date.now()
203
+ const duration = endTime - touchStartRef.current.time
204
+
205
+ // Calculate swipe gesture
206
+ const dx = touch.clientX - touchStartRef.current.x
207
+ const dy = touch.clientY - touchStartRef.current.y
208
+ const distance = Math.sqrt(dx * dx + dy * dy)
209
+
210
+ if (distance >= defaultConfig.minSwipeDistance && duration <= defaultConfig.maxSwipeTime) {
211
+ // Determine swipe direction
212
+ let direction: 'up' | 'down' | 'left' | 'right'
213
+ if (Math.abs(dx) > Math.abs(dy)) {
214
+ direction = dx > 0 ? 'right' : 'left'
215
+ } else {
216
+ direction = dy > 0 ? 'down' : 'up'
217
+ }
218
+
219
+ const velocity = distance / duration
220
+ const swipeGesture: SwipeGesture = {
221
+ direction,
222
+ distance,
223
+ velocity,
224
+ duration
225
+ }
226
+
227
+ setGestureState(prev => ({ ...prev, currentGesture: 'swipe', lastGesture: swipeGesture }))
228
+ callbacks.onSwipe?.(swipeGesture)
229
+ triggerHapticFeedback()
230
+ triggerSoundFeedback()
231
+ } else if (distance < 10 && duration < 200) {
232
+ // Handle tap gesture
233
+ const currentTime = Date.now()
234
+ const timeSinceLastTap = currentTime - lastTapTimeRef.current
235
+
236
+ if (timeSinceLastTap < 300) {
237
+ // Double tap detected
238
+ const position = { x: touch.clientX, y: touch.clientY }
239
+ callbacks.onTap?.(position)
240
+ triggerHapticFeedback()
241
+ }
242
+
243
+ lastTapTimeRef.current = currentTime
244
+ }
245
+
246
+ // Clear long press timer
247
+ if (longPressTimerRef.current) {
248
+ clearTimeout(longPressTimerRef.current)
249
+ longPressTimerRef.current = null
250
+ }
251
+
252
+ touchStartRef.current = null
253
+ callbacks.onTouchEnd?.({ x: touch.clientX, y: touch.clientY })
254
+ } else if (touchCount === 0) {
255
+ // All touches ended
256
+ touchStartCenterRef.current = null
257
+ touchStartDistanceRef.current = 0
258
+ }
259
+
260
+ setGestureState(prev => ({ ...prev, isTracking: false, currentGesture: 'none' }))
261
+ }, [callbacks, defaultConfig.minSwipeDistance, defaultConfig.maxSwipeTime, triggerHapticFeedback, triggerSoundFeedback])
262
+
263
+ // Set up event listeners
264
+ useEffect(() => {
265
+ const element = elementRef.current
266
+ if (!element) return
267
+
268
+ element.addEventListener('touchstart', handleTouchStart, { passive: false })
269
+ element.addEventListener('touchmove', handleTouchMove, { passive: false })
270
+ element.addEventListener('touchend', handleTouchEnd, { passive: false })
271
+
272
+ return () => {
273
+ element.removeEventListener('touchstart', handleTouchStart)
274
+ element.removeEventListener('touchmove', handleTouchMove)
275
+ element.removeEventListener('touchend', handleTouchEnd)
276
+
277
+ // Clean up timers
278
+ if (longPressTimerRef.current) {
279
+ clearTimeout(longPressTimerRef.current)
280
+ }
281
+ }
282
+ }, [elementRef, handleTouchStart, handleTouchMove, handleTouchEnd])
283
+
284
+ // Utility functions
285
+ const resetGestureState = useCallback(() => {
286
+ setGestureState({
287
+ isTracking: false,
288
+ currentGesture: 'none',
289
+ lastGesture: null,
290
+ touchCount: 0
291
+ })
292
+ }, [])
293
+
294
+ const isGestureActive = useMemo(() => gestureState.isTracking, [gestureState.isTracking])
295
+ const currentGestureType = useMemo(() => gestureState.currentGesture, [gestureState.currentGesture])
296
+
297
+ return {
298
+ gestureState,
299
+ isGestureActive,
300
+ currentGestureType,
301
+ resetGestureState,
302
+ triggerHapticFeedback,
303
+ triggerSoundFeedback
304
+ }
305
+ }
306
+
307
+ // Convenience hooks for specific gestures
308
+ export const useSwipeGesture = (
309
+ elementRef: React.RefObject<HTMLElement>,
310
+ onSwipe: (gesture: SwipeGesture) => void,
311
+ config?: Partial<GestureConfig>
312
+ ) => {
313
+ return useGestures(elementRef, { onSwipe }, config)
314
+ }
315
+
316
+ export const usePinchGesture = (
317
+ elementRef: React.RefObject<HTMLElement>,
318
+ onPinch: (gesture: PinchGesture) => void,
319
+ config?: Partial<GestureConfig>
320
+ ) => {
321
+ return useGestures(elementRef, { onPinch }, config)
322
+ }
323
+
324
+ export const useTapGesture = (
325
+ elementRef: React.RefObject<HTMLElement>,
326
+ onTap: (position: { x: number; y: number }) => void,
327
+ config?: Partial<GestureConfig>
328
+ ) => {
329
+ return useGestures(elementRef, { onTap }, config)
330
+ }
331
+
332
+ export const useLongPressGesture = (
333
+ elementRef: React.RefObject<HTMLElement>,
334
+ onLongPress: (position: { x: number; y: number }) => void,
335
+ config?: Partial<GestureConfig>
336
+ ) => {
337
+ return useGestures(elementRef, { onLongPress }, config)
338
+ }