@rakeyshgidwani/roger-ui-bank-theme-stan-design 0.1.4 → 0.1.6
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/CHANGELOG.md +1 -1
- package/dist/index.d.ts +131 -131
- package/dist/index.esm.js +148 -148
- package/dist/index.js +148 -148
- package/dist/styles.css +1 -1
- package/package.json +1 -1
- package/src/components/ui/accessibility-demo.tsx +271 -0
- package/src/components/ui/advanced-component-architecture-demo.tsx +916 -0
- package/src/components/ui/advanced-transition-system-demo.tsx +670 -0
- package/src/components/ui/advanced-transition-system.tsx +395 -0
- package/src/components/ui/animation/animated-container.tsx +166 -0
- package/src/components/ui/animation/index.ts +19 -0
- package/src/components/ui/animation/staggered-container.tsx +68 -0
- package/src/components/ui/animation-demo.tsx +250 -0
- package/src/components/ui/badge.tsx +33 -0
- package/src/components/ui/battery-conscious-animation-demo.tsx +568 -0
- package/src/components/ui/border-radius-shadow-demo.tsx +187 -0
- package/src/components/ui/button.tsx +36 -0
- package/src/components/ui/card.tsx +207 -0
- package/src/components/ui/checkbox.tsx +30 -0
- package/src/components/ui/color-preview.tsx +411 -0
- package/src/components/ui/data-display/chart.tsx +653 -0
- package/src/components/ui/data-display/data-grid-simple.tsx +76 -0
- package/src/components/ui/data-display/data-grid.tsx +680 -0
- package/src/components/ui/data-display/list.tsx +456 -0
- package/src/components/ui/data-display/table.tsx +482 -0
- package/src/components/ui/data-display/timeline.tsx +441 -0
- package/src/components/ui/data-display/tree.tsx +602 -0
- package/src/components/ui/data-display/types.ts +536 -0
- package/src/components/ui/enterprise-mobile-experience-demo.tsx +749 -0
- package/src/components/ui/enterprise-mobile-experience.tsx +464 -0
- package/src/components/ui/feedback/alert.tsx +157 -0
- package/src/components/ui/feedback/progress.tsx +292 -0
- package/src/components/ui/feedback/skeleton.tsx +185 -0
- package/src/components/ui/feedback/toast.tsx +280 -0
- package/src/components/ui/feedback/types.ts +125 -0
- package/src/components/ui/font-preview.tsx +288 -0
- package/src/components/ui/form-demo.tsx +553 -0
- package/src/components/ui/hardware-acceleration-demo.tsx +547 -0
- package/src/components/ui/input.tsx +35 -0
- package/src/components/ui/label.tsx +16 -0
- package/src/components/ui/layout-demo.tsx +367 -0
- package/src/components/ui/layouts/adaptive-layout.tsx +139 -0
- package/src/components/ui/layouts/desktop-layout.tsx +224 -0
- package/src/components/ui/layouts/index.ts +10 -0
- package/src/components/ui/layouts/mobile-layout.tsx +162 -0
- package/src/components/ui/layouts/tablet-layout.tsx +197 -0
- package/src/components/ui/mobile-form-validation.tsx +451 -0
- package/src/components/ui/mobile-input-demo.tsx +201 -0
- package/src/components/ui/mobile-input.tsx +281 -0
- package/src/components/ui/mobile-skeleton-loading-demo.tsx +638 -0
- package/src/components/ui/navigation/breadcrumb.tsx +158 -0
- package/src/components/ui/navigation/index.ts +36 -0
- package/src/components/ui/navigation/menu.tsx +374 -0
- package/src/components/ui/navigation/navigation-demo.tsx +324 -0
- package/src/components/ui/navigation/pagination.tsx +272 -0
- package/src/components/ui/navigation/sidebar.tsx +383 -0
- package/src/components/ui/navigation/stepper.tsx +303 -0
- package/src/components/ui/navigation/tabs.tsx +205 -0
- package/src/components/ui/navigation/types.ts +299 -0
- package/src/components/ui/overlay/backdrop.tsx +81 -0
- package/src/components/ui/overlay/focus-manager.tsx +143 -0
- package/src/components/ui/overlay/index.ts +36 -0
- package/src/components/ui/overlay/modal.tsx +270 -0
- package/src/components/ui/overlay/overlay-manager.tsx +110 -0
- package/src/components/ui/overlay/popover.tsx +462 -0
- package/src/components/ui/overlay/portal.tsx +79 -0
- package/src/components/ui/overlay/tooltip.tsx +303 -0
- package/src/components/ui/overlay/types.ts +196 -0
- package/src/components/ui/performance-demo.tsx +596 -0
- package/src/components/ui/semantic-input-system-demo.tsx +502 -0
- package/src/components/ui/semantic-input-system-demo.tsx.disabled +873 -0
- package/src/components/ui/tablet-layout.tsx +192 -0
- package/src/components/ui/theme-customizer.tsx +386 -0
- package/src/components/ui/theme-preview.tsx +310 -0
- package/src/components/ui/theme-switcher.tsx +264 -0
- package/src/components/ui/theme-toggle.tsx +38 -0
- package/src/components/ui/token-demo.tsx +195 -0
- package/src/components/ui/touch-demo.tsx +462 -0
- package/src/components/ui/touch-friendly-interface-demo.tsx +519 -0
- package/src/components/ui/touch-friendly-interface.tsx +296 -0
- package/src/hooks/index.ts +190 -0
- package/src/hooks/use-accessibility-support.ts +518 -0
- package/src/hooks/use-adaptive-layout.ts +289 -0
- package/src/hooks/use-advanced-patterns.ts +294 -0
- package/src/hooks/use-advanced-transition-system.ts +393 -0
- package/src/hooks/use-animation-profile.ts +288 -0
- package/src/hooks/use-battery-animations.ts +384 -0
- package/src/hooks/use-battery-conscious-loading.ts +475 -0
- package/src/hooks/use-battery-optimization.ts +330 -0
- package/src/hooks/use-battery-status.ts +299 -0
- package/src/hooks/use-component-performance.ts +344 -0
- package/src/hooks/use-device-loading-states.ts +459 -0
- package/src/hooks/use-device.tsx +110 -0
- package/src/hooks/use-enterprise-mobile-experience.ts +488 -0
- package/src/hooks/use-form-feedback.ts +403 -0
- package/src/hooks/use-form-performance.ts +513 -0
- package/src/hooks/use-frame-rate.ts +251 -0
- package/src/hooks/use-gestures.ts +338 -0
- package/src/hooks/use-hardware-acceleration.ts +341 -0
- package/src/hooks/use-input-accessibility.ts +455 -0
- package/src/hooks/use-input-performance.ts +506 -0
- package/src/hooks/use-layout-performance.ts +319 -0
- package/src/hooks/use-loading-accessibility.ts +535 -0
- package/src/hooks/use-loading-performance.ts +473 -0
- package/src/hooks/use-memory-usage.ts +287 -0
- package/src/hooks/use-mobile-form-layout.ts +464 -0
- package/src/hooks/use-mobile-form-validation.ts +518 -0
- package/src/hooks/use-mobile-keyboard-optimization.ts +472 -0
- package/src/hooks/use-mobile-layout.ts +302 -0
- package/src/hooks/use-mobile-optimization.ts +406 -0
- package/src/hooks/use-mobile-skeleton.ts +402 -0
- package/src/hooks/use-mobile-touch.ts +414 -0
- package/src/hooks/use-performance-throttling.ts +348 -0
- package/src/hooks/use-performance.ts +316 -0
- package/src/hooks/use-reusable-architecture.ts +414 -0
- package/src/hooks/use-semantic-input-types.ts +357 -0
- package/src/hooks/use-semantic-input.ts +565 -0
- package/src/hooks/use-tablet-layout.ts +384 -0
- package/src/hooks/use-touch-friendly-input.ts +524 -0
- package/src/hooks/use-touch-friendly-interface.ts +331 -0
- package/src/hooks/use-touch-optimization.ts +375 -0
- package/src/index.ts +279 -279
- package/src/lib/utils.ts +6 -0
- package/src/themes/README.md +272 -0
- package/src/themes/ThemeContext.tsx +31 -0
- package/src/themes/ThemeProvider.tsx +232 -0
- package/src/themes/accessibility/index.ts +27 -0
- package/src/themes/accessibility.ts +259 -0
- package/src/themes/aria-patterns.ts +420 -0
- package/src/themes/base-themes.ts +55 -0
- package/src/themes/colorManager.ts +380 -0
- package/src/themes/examples/dark-theme.ts +154 -0
- package/src/themes/examples/minimal-theme.ts +108 -0
- package/src/themes/focus-management.ts +701 -0
- package/src/themes/fontLoader.ts +201 -0
- package/src/themes/high-contrast.ts +621 -0
- package/src/themes/index.ts +19 -0
- package/src/themes/inheritance.ts +227 -0
- package/src/themes/keyboard-navigation.ts +550 -0
- package/src/themes/motion-reduction.ts +662 -0
- package/src/themes/navigation.ts +238 -0
- package/src/themes/screen-reader.ts +645 -0
- package/src/themes/systemThemeDetector.ts +182 -0
- package/src/themes/themeCSSUpdater.ts +262 -0
- package/src/themes/themePersistence.ts +238 -0
- package/src/themes/themes/default.ts +586 -0
- package/src/themes/themes/harvey.ts +554 -0
- package/src/themes/themes/stan-design.ts +683 -0
- package/src/themes/types.ts +460 -0
- package/src/themes/useSystemTheme.ts +48 -0
- package/src/themes/useTheme.ts +87 -0
- package/src/themes/validation.ts +462 -0
- package/src/tokens/index.ts +34 -0
- package/src/tokens/tokenExporter.ts +397 -0
- package/src/tokens/tokenGenerator.ts +276 -0
- package/src/tokens/tokenManager.ts +248 -0
- package/src/tokens/tokenValidator.ts +543 -0
- package/src/tokens/types.ts +78 -0
- package/src/utils/bundle-analyzer.ts +260 -0
- package/src/utils/bundle-splitting.ts +483 -0
- package/src/utils/lazy-loading.ts +441 -0
- package/src/utils/performance-monitor.ts +513 -0
- package/src/utils/tree-shaking.ts +274 -0
|
@@ -0,0 +1,464 @@
|
|
|
1
|
+
import { useState, useEffect, useCallback, useRef } from 'react'
|
|
2
|
+
|
|
3
|
+
export interface LayoutConfig {
|
|
4
|
+
enableTouchOptimization: boolean
|
|
5
|
+
enableKeyboardOptimization: boolean
|
|
6
|
+
enableResponsiveBehavior: boolean
|
|
7
|
+
enablePerformanceOptimization: boolean
|
|
8
|
+
touchTargetSize: number
|
|
9
|
+
spacingMultiplier: number
|
|
10
|
+
maxFieldWidth: number
|
|
11
|
+
enableAutoFocus: boolean
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface LayoutState {
|
|
15
|
+
isMobile: boolean
|
|
16
|
+
isTablet: boolean
|
|
17
|
+
isDesktop: boolean
|
|
18
|
+
orientation: 'portrait' | 'landscape'
|
|
19
|
+
keyboardVisible: boolean
|
|
20
|
+
viewportHeight: number
|
|
21
|
+
viewportWidth: number
|
|
22
|
+
safeAreaInsets: {
|
|
23
|
+
top: number
|
|
24
|
+
bottom: number
|
|
25
|
+
left: number
|
|
26
|
+
right: number
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface LayoutCallbacks {
|
|
31
|
+
onLayoutChange?: (layout: LayoutState) => void
|
|
32
|
+
onOrientationChange?: (orientation: 'portrait' | 'landscape') => void
|
|
33
|
+
onKeyboardVisibilityChange?: (visible: boolean) => void
|
|
34
|
+
onViewportChange?: (width: number, height: number) => void
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface MobileFormLayoutState {
|
|
38
|
+
layout: LayoutState
|
|
39
|
+
isOptimized: boolean
|
|
40
|
+
currentSpacing: number
|
|
41
|
+
currentFieldWidth: number
|
|
42
|
+
performanceMetrics: {
|
|
43
|
+
layoutUpdates: number
|
|
44
|
+
lastUpdateTime: number
|
|
45
|
+
optimizationScore: number
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export const useMobileFormLayout = (
|
|
50
|
+
callbacks: LayoutCallbacks = {},
|
|
51
|
+
config: Partial<LayoutConfig> = {}
|
|
52
|
+
) => {
|
|
53
|
+
const defaultConfig: LayoutConfig = {
|
|
54
|
+
enableTouchOptimization: true,
|
|
55
|
+
enableKeyboardOptimization: true,
|
|
56
|
+
enableResponsiveBehavior: true,
|
|
57
|
+
enablePerformanceOptimization: true,
|
|
58
|
+
touchTargetSize: 44,
|
|
59
|
+
spacingMultiplier: 1.2,
|
|
60
|
+
maxFieldWidth: 400,
|
|
61
|
+
enableAutoFocus: true,
|
|
62
|
+
...config
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const [layoutState, setLayoutState] = useState<MobileFormLayoutState>({
|
|
66
|
+
layout: {
|
|
67
|
+
isMobile: false,
|
|
68
|
+
isTablet: false,
|
|
69
|
+
isDesktop: false,
|
|
70
|
+
orientation: 'portrait',
|
|
71
|
+
keyboardVisible: false,
|
|
72
|
+
viewportHeight: window.innerHeight,
|
|
73
|
+
viewportWidth: window.innerWidth,
|
|
74
|
+
safeAreaInsets: { top: 0, bottom: 0, left: 0, right: 0 }
|
|
75
|
+
},
|
|
76
|
+
isOptimized: false,
|
|
77
|
+
currentSpacing: defaultConfig.touchTargetSize * defaultConfig.spacingMultiplier,
|
|
78
|
+
currentFieldWidth: Math.min(window.innerWidth - 32, defaultConfig.maxFieldWidth),
|
|
79
|
+
performanceMetrics: {
|
|
80
|
+
layoutUpdates: 0,
|
|
81
|
+
lastUpdateTime: 0,
|
|
82
|
+
optimizationScore: 0
|
|
83
|
+
}
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
const resizeObserverRef = useRef<ResizeObserver | null>(null)
|
|
87
|
+
const layoutUpdateTimerRef = useRef<NodeJS.Timeout | null>(null)
|
|
88
|
+
const keyboardTimerRef = useRef<NodeJS.Timeout | null>(null)
|
|
89
|
+
|
|
90
|
+
// Detect device type
|
|
91
|
+
const detectDeviceType = useCallback(() => {
|
|
92
|
+
const width = window.innerWidth
|
|
93
|
+
const userAgent = navigator.userAgent.toLowerCase()
|
|
94
|
+
|
|
95
|
+
let isMobile = false
|
|
96
|
+
let isTablet = false
|
|
97
|
+
let isDesktop = false
|
|
98
|
+
|
|
99
|
+
// Check screen size
|
|
100
|
+
if (width < 768) {
|
|
101
|
+
isMobile = true
|
|
102
|
+
} else if (width < 1024) {
|
|
103
|
+
isTablet = true
|
|
104
|
+
} else {
|
|
105
|
+
isDesktop = true
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Check user agent for mobile devices
|
|
109
|
+
if (/android|webos|iphone|ipad|ipod|blackberry|iemobile|opera mini/i.test(userAgent)) {
|
|
110
|
+
isMobile = true
|
|
111
|
+
isTablet = width >= 768 && width < 1024
|
|
112
|
+
isDesktop = false
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return { isMobile, isTablet, isDesktop }
|
|
116
|
+
}, [])
|
|
117
|
+
|
|
118
|
+
// Detect orientation
|
|
119
|
+
const detectOrientation = useCallback((): 'portrait' | 'landscape' => {
|
|
120
|
+
if (window.innerHeight > window.innerWidth) {
|
|
121
|
+
return 'portrait'
|
|
122
|
+
}
|
|
123
|
+
return 'landscape'
|
|
124
|
+
}, [])
|
|
125
|
+
|
|
126
|
+
// Get safe area insets (for devices with notches)
|
|
127
|
+
const getSafeAreaInsets = useCallback(() => {
|
|
128
|
+
const style = getComputedStyle(document.documentElement)
|
|
129
|
+
|
|
130
|
+
return {
|
|
131
|
+
top: parseInt(style.getPropertyValue('--sat') || '0'),
|
|
132
|
+
bottom: parseInt(style.getPropertyValue('--sab') || '0'),
|
|
133
|
+
left: parseInt(style.getPropertyValue('--sal') || '0'),
|
|
134
|
+
right: parseInt(style.getPropertyValue('--sar') || '0')
|
|
135
|
+
}
|
|
136
|
+
}, [])
|
|
137
|
+
|
|
138
|
+
// Detect keyboard visibility
|
|
139
|
+
const detectKeyboardVisibility = useCallback(() => {
|
|
140
|
+
if (!defaultConfig.enableKeyboardOptimization) return false
|
|
141
|
+
|
|
142
|
+
const currentHeight = window.innerHeight
|
|
143
|
+
const currentWidth = window.innerWidth
|
|
144
|
+
|
|
145
|
+
// Simple heuristic: if height decreases significantly and width stays the same
|
|
146
|
+
// it's likely the keyboard appeared
|
|
147
|
+
const heightRatio = currentHeight / layoutState.layout.viewportHeight
|
|
148
|
+
const widthRatio = currentWidth / layoutState.layout.viewportWidth
|
|
149
|
+
|
|
150
|
+
return heightRatio < 0.8 && widthRatio > 0.95
|
|
151
|
+
}, [defaultConfig.enableKeyboardOptimization, layoutState.layout.viewportHeight, layoutState.layout.viewportWidth])
|
|
152
|
+
|
|
153
|
+
// Update layout state
|
|
154
|
+
const updateLayout = useCallback(() => {
|
|
155
|
+
const deviceType = detectDeviceType()
|
|
156
|
+
const orientation = detectOrientation()
|
|
157
|
+
const safeAreaInsets = getSafeAreaInsets()
|
|
158
|
+
const keyboardVisible = detectKeyboardVisibility()
|
|
159
|
+
|
|
160
|
+
const newLayout: LayoutState = {
|
|
161
|
+
...deviceType,
|
|
162
|
+
orientation,
|
|
163
|
+
keyboardVisible,
|
|
164
|
+
viewportHeight: window.innerHeight,
|
|
165
|
+
viewportWidth: window.innerWidth,
|
|
166
|
+
safeAreaInsets
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Calculate optimal spacing and field width
|
|
170
|
+
let spacing = defaultConfig.touchTargetSize * defaultConfig.spacingMultiplier
|
|
171
|
+
let fieldWidth = Math.min(window.innerWidth - 32, defaultConfig.maxFieldWidth)
|
|
172
|
+
|
|
173
|
+
// Adjust for mobile devices
|
|
174
|
+
if (newLayout.isMobile) {
|
|
175
|
+
spacing = Math.max(spacing, 16) // Minimum spacing for mobile
|
|
176
|
+
fieldWidth = Math.min(fieldWidth, window.innerWidth - 24) // Account for safe areas
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Adjust for keyboard visibility
|
|
180
|
+
if (newLayout.keyboardVisible) {
|
|
181
|
+
spacing = Math.max(spacing * 0.8, 12) // Reduce spacing when keyboard is visible
|
|
182
|
+
fieldWidth = Math.min(fieldWidth, window.innerWidth - 16)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Adjust for orientation
|
|
186
|
+
if (newLayout.orientation === 'landscape' && newLayout.isMobile) {
|
|
187
|
+
spacing = Math.max(spacing * 0.9, 14)
|
|
188
|
+
fieldWidth = Math.min(fieldWidth, window.innerHeight - 24)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
setLayoutState(prev => {
|
|
192
|
+
const newState = {
|
|
193
|
+
...prev,
|
|
194
|
+
layout: newLayout,
|
|
195
|
+
currentSpacing: spacing,
|
|
196
|
+
currentFieldWidth: fieldWidth,
|
|
197
|
+
performanceMetrics: {
|
|
198
|
+
...prev.performanceMetrics,
|
|
199
|
+
layoutUpdates: prev.performanceMetrics.layoutUpdates + 1,
|
|
200
|
+
lastUpdateTime: Date.now()
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Calculate optimization score
|
|
205
|
+
const score = calculateOptimizationScore(newState)
|
|
206
|
+
newState.performanceMetrics.optimizationScore = score
|
|
207
|
+
newState.isOptimized = score >= 0.8
|
|
208
|
+
|
|
209
|
+
return newState
|
|
210
|
+
})
|
|
211
|
+
|
|
212
|
+
// Notify callbacks
|
|
213
|
+
callbacks.onLayoutChange?.(newLayout)
|
|
214
|
+
callbacks.onOrientationChange?.(orientation)
|
|
215
|
+
callbacks.onKeyboardVisibilityChange?.(keyboardVisible)
|
|
216
|
+
callbacks.onViewportChange?.(window.innerWidth, window.innerHeight)
|
|
217
|
+
}, [
|
|
218
|
+
detectDeviceType,
|
|
219
|
+
detectOrientation,
|
|
220
|
+
getSafeAreaInsets,
|
|
221
|
+
detectKeyboardVisibility,
|
|
222
|
+
defaultConfig.touchTargetSize,
|
|
223
|
+
defaultConfig.spacingMultiplier,
|
|
224
|
+
defaultConfig.maxFieldWidth,
|
|
225
|
+
callbacks
|
|
226
|
+
])
|
|
227
|
+
|
|
228
|
+
// Calculate optimization score
|
|
229
|
+
const calculateOptimizationScore = useCallback((state: MobileFormLayoutState): number => {
|
|
230
|
+
let score = 0
|
|
231
|
+
const { layout, currentSpacing, currentFieldWidth } = state
|
|
232
|
+
|
|
233
|
+
// Touch target optimization (40% weight)
|
|
234
|
+
if (currentSpacing >= defaultConfig.touchTargetSize) {
|
|
235
|
+
score += 0.4
|
|
236
|
+
} else if (currentSpacing >= defaultConfig.touchTargetSize * 0.8) {
|
|
237
|
+
score += 0.3
|
|
238
|
+
} else if (currentSpacing >= defaultConfig.touchTargetSize * 0.6) {
|
|
239
|
+
score += 0.2
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Field width optimization (30% weight)
|
|
243
|
+
const optimalWidth = Math.min(layout.viewportWidth - 32, defaultConfig.maxFieldWidth)
|
|
244
|
+
const widthRatio = currentFieldWidth / optimalWidth
|
|
245
|
+
if (widthRatio >= 0.9) {
|
|
246
|
+
score += 0.3
|
|
247
|
+
} else if (widthRatio >= 0.7) {
|
|
248
|
+
score += 0.2
|
|
249
|
+
} else if (widthRatio >= 0.5) {
|
|
250
|
+
score += 0.1
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Responsive behavior (20% weight)
|
|
254
|
+
if (layout.isMobile && layout.orientation === 'portrait') {
|
|
255
|
+
score += 0.2
|
|
256
|
+
} else if (layout.isMobile && layout.orientation === 'landscape') {
|
|
257
|
+
score += 0.15
|
|
258
|
+
} else if (layout.isTablet) {
|
|
259
|
+
score += 0.1
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Performance optimization (10% weight)
|
|
263
|
+
if (state.performanceMetrics.layoutUpdates < 10) {
|
|
264
|
+
score += 0.1
|
|
265
|
+
} else if (state.performanceMetrics.layoutUpdates < 20) {
|
|
266
|
+
score += 0.05
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return Math.min(score, 1)
|
|
270
|
+
}, [defaultConfig.touchTargetSize, defaultConfig.maxFieldWidth])
|
|
271
|
+
|
|
272
|
+
// Debounced layout update
|
|
273
|
+
const debouncedUpdateLayout = useCallback(() => {
|
|
274
|
+
if (layoutUpdateTimerRef.current) {
|
|
275
|
+
clearTimeout(layoutUpdateTimerRef.current)
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
layoutUpdateTimerRef.current = setTimeout(() => {
|
|
279
|
+
updateLayout()
|
|
280
|
+
}, 100) // 100ms debounce
|
|
281
|
+
}, [updateLayout])
|
|
282
|
+
|
|
283
|
+
// Handle window resize
|
|
284
|
+
const handleResize = useCallback(() => {
|
|
285
|
+
if (defaultConfig.enableResponsiveBehavior) {
|
|
286
|
+
debouncedUpdateLayout()
|
|
287
|
+
}
|
|
288
|
+
}, [defaultConfig.enableResponsiveBehavior, debouncedUpdateLayout])
|
|
289
|
+
|
|
290
|
+
// Handle orientation change
|
|
291
|
+
const handleOrientationChange = useCallback(() => {
|
|
292
|
+
if (defaultConfig.enableResponsiveBehavior) {
|
|
293
|
+
// Add delay for orientation change to complete
|
|
294
|
+
setTimeout(() => {
|
|
295
|
+
updateLayout()
|
|
296
|
+
}, 300)
|
|
297
|
+
}
|
|
298
|
+
}, [defaultConfig.enableResponsiveBehavior, updateLayout])
|
|
299
|
+
|
|
300
|
+
// Handle keyboard visibility changes
|
|
301
|
+
const handleKeyboardChange = useCallback(() => {
|
|
302
|
+
if (defaultConfig.enableKeyboardOptimization) {
|
|
303
|
+
if (keyboardTimerRef.current) {
|
|
304
|
+
clearTimeout(keyboardTimerRef.current)
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
keyboardTimerRef.current = setTimeout(() => {
|
|
308
|
+
updateLayout()
|
|
309
|
+
}, 150) // Small delay to detect keyboard changes
|
|
310
|
+
}
|
|
311
|
+
}, [defaultConfig.enableKeyboardOptimization, updateLayout])
|
|
312
|
+
|
|
313
|
+
// Set up resize observer for more precise layout detection
|
|
314
|
+
useEffect(() => {
|
|
315
|
+
if (defaultConfig.enablePerformanceOptimization && 'ResizeObserver' in window) {
|
|
316
|
+
resizeObserverRef.current = new ResizeObserver(() => {
|
|
317
|
+
handleResize()
|
|
318
|
+
})
|
|
319
|
+
|
|
320
|
+
resizeObserverRef.current.observe(document.body)
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
return () => {
|
|
324
|
+
if (resizeObserverRef.current) {
|
|
325
|
+
resizeObserverRef.current.disconnect()
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}, [defaultConfig.enablePerformanceOptimization, handleResize])
|
|
329
|
+
|
|
330
|
+
// Set up window event listeners
|
|
331
|
+
useEffect(() => {
|
|
332
|
+
if (defaultConfig.enableResponsiveBehavior) {
|
|
333
|
+
window.addEventListener('resize', handleResize)
|
|
334
|
+
window.addEventListener('orientationchange', handleOrientationChange)
|
|
335
|
+
|
|
336
|
+
// Listen for visual viewport changes (better for mobile)
|
|
337
|
+
if ('visualViewport' in window) {
|
|
338
|
+
(window as any).visualViewport.addEventListener('resize', handleKeyboardChange)
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
return () => {
|
|
343
|
+
window.removeEventListener('resize', handleResize)
|
|
344
|
+
window.removeEventListener('orientationchange', handleOrientationChange)
|
|
345
|
+
|
|
346
|
+
if ('visualViewport' in window) {
|
|
347
|
+
(window as any).visualViewport.removeEventListener('resize', handleKeyboardChange)
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}, [defaultConfig.enableResponsiveBehavior, handleResize, handleOrientationChange, handleKeyboardChange])
|
|
351
|
+
|
|
352
|
+
// Initial layout detection
|
|
353
|
+
useEffect(() => {
|
|
354
|
+
updateLayout()
|
|
355
|
+
}, [updateLayout])
|
|
356
|
+
|
|
357
|
+
// Get CSS classes for layout optimization
|
|
358
|
+
const getLayoutClasses = useCallback(() => {
|
|
359
|
+
const { layout, isOptimized } = layoutState
|
|
360
|
+
const classes = ['mobile-form-layout']
|
|
361
|
+
|
|
362
|
+
if (layout.isMobile) {
|
|
363
|
+
classes.push('layout-mobile')
|
|
364
|
+
if (layout.orientation === 'portrait') {
|
|
365
|
+
classes.push('layout-portrait')
|
|
366
|
+
} else {
|
|
367
|
+
classes.push('layout-landscape')
|
|
368
|
+
}
|
|
369
|
+
} else if (layout.isTablet) {
|
|
370
|
+
classes.push('layout-tablet')
|
|
371
|
+
} else {
|
|
372
|
+
classes.push('layout-desktop')
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
if (layout.keyboardVisible) {
|
|
376
|
+
classes.push('layout-keyboard-visible')
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
if (isOptimized) {
|
|
380
|
+
classes.push('layout-optimized')
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
return classes.join(' ')
|
|
384
|
+
}, [layoutState])
|
|
385
|
+
|
|
386
|
+
// Get CSS variables for dynamic styling
|
|
387
|
+
const getLayoutCSSVariables = useCallback(() => {
|
|
388
|
+
const { currentSpacing, currentFieldWidth } = layoutState
|
|
389
|
+
|
|
390
|
+
return {
|
|
391
|
+
'--mobile-spacing': `${currentSpacing}px`,
|
|
392
|
+
'--mobile-field-width': `${currentFieldWidth}px`,
|
|
393
|
+
'--touch-target-size': `${defaultConfig.touchTargetSize}px`,
|
|
394
|
+
'--safe-area-top': `${layoutState.layout.safeAreaInsets.top}px`,
|
|
395
|
+
'--safe-area-bottom': `${layoutState.layout.safeAreaInsets.bottom}px`,
|
|
396
|
+
'--safe-area-left': `${layoutState.layout.safeAreaInsets.left}px`,
|
|
397
|
+
'--safe-area-right': `${layoutState.layout.safeAreaInsets.right}px`
|
|
398
|
+
}
|
|
399
|
+
}, [layoutState, defaultConfig.touchTargetSize])
|
|
400
|
+
|
|
401
|
+
// Auto-focus first input when layout changes
|
|
402
|
+
const autoFocusFirstInput = useCallback(() => {
|
|
403
|
+
if (!defaultConfig.enableAutoFocus) return
|
|
404
|
+
|
|
405
|
+
const firstInput = document.querySelector('input, textarea, select') as HTMLElement
|
|
406
|
+
if (firstInput && layoutState.layout.isMobile) {
|
|
407
|
+
// Small delay to ensure layout is stable
|
|
408
|
+
setTimeout(() => {
|
|
409
|
+
firstInput.focus()
|
|
410
|
+
}, 100)
|
|
411
|
+
}
|
|
412
|
+
}, [defaultConfig.enableAutoFocus, layoutState.layout.isMobile])
|
|
413
|
+
|
|
414
|
+
// Clean up timers on unmount
|
|
415
|
+
useEffect(() => {
|
|
416
|
+
return () => {
|
|
417
|
+
if (layoutUpdateTimerRef.current) {
|
|
418
|
+
clearTimeout(layoutUpdateTimerRef.current)
|
|
419
|
+
}
|
|
420
|
+
if (keyboardTimerRef.current) {
|
|
421
|
+
clearTimeout(keyboardTimerRef.current)
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}, [])
|
|
425
|
+
|
|
426
|
+
return {
|
|
427
|
+
layoutState,
|
|
428
|
+
updateLayout,
|
|
429
|
+
getLayoutClasses,
|
|
430
|
+
getLayoutCSSVariables,
|
|
431
|
+
autoFocusFirstInput,
|
|
432
|
+
isMobile: layoutState.layout.isMobile,
|
|
433
|
+
isTablet: layoutState.layout.isTablet,
|
|
434
|
+
isDesktop: layoutState.layout.isDesktop,
|
|
435
|
+
orientation: layoutState.layout.orientation,
|
|
436
|
+
keyboardVisible: layoutState.layout.keyboardVisible,
|
|
437
|
+
currentSpacing: layoutState.currentSpacing,
|
|
438
|
+
currentFieldWidth: layoutState.currentFieldWidth,
|
|
439
|
+
isOptimized: layoutState.isOptimized,
|
|
440
|
+
performanceMetrics: layoutState.performanceMetrics
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// Convenience hook for basic mobile layout
|
|
445
|
+
export const useBasicMobileLayout = () => {
|
|
446
|
+
return useMobileFormLayout({}, {
|
|
447
|
+
enableTouchOptimization: true,
|
|
448
|
+
enableKeyboardOptimization: false,
|
|
449
|
+
enableResponsiveBehavior: true,
|
|
450
|
+
enablePerformanceOptimization: false,
|
|
451
|
+
enableAutoFocus: false
|
|
452
|
+
})
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// Convenience hook for enhanced mobile layout
|
|
456
|
+
export const useEnhancedMobileLayout = () => {
|
|
457
|
+
return useMobileFormLayout({}, {
|
|
458
|
+
enableTouchOptimization: true,
|
|
459
|
+
enableKeyboardOptimization: true,
|
|
460
|
+
enableResponsiveBehavior: true,
|
|
461
|
+
enablePerformanceOptimization: true,
|
|
462
|
+
enableAutoFocus: true
|
|
463
|
+
})
|
|
464
|
+
}
|