@rakeyshgidwani/roger-ui-bank-theme-stan-design 0.1.4 → 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,462 @@
1
+ import React, { useState, useRef, useEffect, useCallback, cloneElement } from 'react';
2
+ import { useTheme } from '../../../themes/useTheme';
3
+ import { Portal } from './portal';
4
+ import { FocusManager } from './focus-manager';
5
+ import {
6
+ PopoverProps,
7
+ PopoverContentProps,
8
+ PopoverHeaderProps,
9
+ PopoverBodyProps,
10
+ PopoverFooterProps
11
+ } from './types';
12
+
13
+ // SVG Icons
14
+ const XMarkIcon: React.FC<{ className?: string }> = ({ className = 'w-5 h-5' }) => (
15
+ <svg className={className} fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor">
16
+ <path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
17
+ </svg>
18
+ );
19
+
20
+ // Default color fallbacks
21
+ const getDefaultColors = () => ({
22
+ surface: { background: '#ffffff', surface: '#f9fafb', border: '#e5e7eb', divider: '#e5e7eb' },
23
+ text: { primary: '#111827', secondary: '#6b7280', muted: '#9ca3af', inverse: '#ffffff' },
24
+ interactive: { hover: '#f3f4f6', active: '#e5e7eb', focus: '#3b82f6', disabled: '#d1d5db' },
25
+ primary: { 500: '#3b82f6', 600: '#2563eb' },
26
+ semantic: { success: '#10b981', warning: '#f59e0b', error: '#ef4444', info: '#3b82f6' }
27
+ });
28
+
29
+ // Position calculations utility (similar to tooltip but adjusted for larger content)
30
+ const calculatePosition = (
31
+ triggerRect: DOMRect,
32
+ popoverRect: DOMRect,
33
+ position: PopoverProps['position'] = 'bottom',
34
+ offset: number = 8
35
+ ) => {
36
+ const scrollX = window.scrollX || document.documentElement.scrollLeft;
37
+ const scrollY = window.scrollY || document.documentElement.scrollTop;
38
+
39
+ let top = 0;
40
+ let left = 0;
41
+
42
+ // Base calculations
43
+ const triggerCenterX = triggerRect.left + triggerRect.width / 2;
44
+ const triggerCenterY = triggerRect.top + triggerRect.height / 2;
45
+
46
+ switch (position) {
47
+ case 'top':
48
+ top = triggerRect.top - popoverRect.height - offset;
49
+ left = triggerCenterX - popoverRect.width / 2;
50
+ break;
51
+ case 'top-start':
52
+ top = triggerRect.top - popoverRect.height - offset;
53
+ left = triggerRect.left;
54
+ break;
55
+ case 'top-end':
56
+ top = triggerRect.top - popoverRect.height - offset;
57
+ left = triggerRect.right - popoverRect.width;
58
+ break;
59
+ case 'bottom':
60
+ top = triggerRect.bottom + offset;
61
+ left = triggerCenterX - popoverRect.width / 2;
62
+ break;
63
+ case 'bottom-start':
64
+ top = triggerRect.bottom + offset;
65
+ left = triggerRect.left;
66
+ break;
67
+ case 'bottom-end':
68
+ top = triggerRect.bottom + offset;
69
+ left = triggerRect.right - popoverRect.width;
70
+ break;
71
+ case 'left':
72
+ top = triggerCenterY - popoverRect.height / 2;
73
+ left = triggerRect.left - popoverRect.width - offset;
74
+ break;
75
+ case 'left-start':
76
+ top = triggerRect.top;
77
+ left = triggerRect.left - popoverRect.width - offset;
78
+ break;
79
+ case 'left-end':
80
+ top = triggerRect.bottom - popoverRect.height;
81
+ left = triggerRect.left - popoverRect.width - offset;
82
+ break;
83
+ case 'right':
84
+ top = triggerCenterY - popoverRect.height / 2;
85
+ left = triggerRect.right + offset;
86
+ break;
87
+ case 'right-start':
88
+ top = triggerRect.top;
89
+ left = triggerRect.right + offset;
90
+ break;
91
+ case 'right-end':
92
+ top = triggerRect.bottom - popoverRect.height;
93
+ left = triggerRect.right + offset;
94
+ break;
95
+ }
96
+
97
+ // Adjust for viewport boundaries
98
+ const viewportWidth = window.innerWidth;
99
+ const viewportHeight = window.innerHeight;
100
+
101
+ // Horizontal boundary checks
102
+ if (left < offset) {
103
+ left = offset;
104
+ } else if (left + popoverRect.width > viewportWidth - offset) {
105
+ left = viewportWidth - popoverRect.width - offset;
106
+ }
107
+
108
+ // Vertical boundary checks
109
+ if (top < offset) {
110
+ top = offset;
111
+ } else if (top + popoverRect.height > viewportHeight - offset) {
112
+ top = viewportHeight - popoverRect.height - offset;
113
+ }
114
+
115
+ return {
116
+ top: top + scrollY,
117
+ left: left + scrollX
118
+ };
119
+ };
120
+
121
+ // Arrow component for popovers
122
+ const PopoverArrow: React.FC<{
123
+ position: PopoverProps['position'];
124
+ colors: any;
125
+ className?: string;
126
+ }> = ({ className = '' }) => {
127
+ return (
128
+ <div className={`popover__arrow ${className}`} />
129
+ );
130
+ };
131
+
132
+ // Popover Content Component
133
+ export const PopoverContent: React.FC<PopoverContentProps> = ({
134
+ children,
135
+ className = '',
136
+ style = {}
137
+ }) => {
138
+ return (
139
+ <div className={`popover__content ${className}`} style={style}>
140
+ {children}
141
+ </div>
142
+ );
143
+ };
144
+
145
+ // Popover Header Component
146
+ export const PopoverHeader: React.FC<PopoverHeaderProps> = ({
147
+ title,
148
+ subtitle,
149
+ onClose,
150
+ showCloseButton = true,
151
+ closeButtonLabel = 'Close',
152
+ className = '',
153
+ borderless = false,
154
+ noPadding = false,
155
+ children
156
+ }) => {
157
+ const headerClasses = [
158
+ 'popover__header',
159
+ borderless ? 'popover__header--borderless' : '',
160
+ noPadding ? 'popover__header--no-padding' : '',
161
+ className
162
+ ].filter(Boolean).join(' ');
163
+
164
+ return (
165
+ <div className={headerClasses}>
166
+ <div>
167
+ {title && (
168
+ <h3 className="popover__title">
169
+ {title}
170
+ </h3>
171
+ )}
172
+ {subtitle && (
173
+ <p className="popover__subtitle">
174
+ {subtitle}
175
+ </p>
176
+ )}
177
+ {children}
178
+ </div>
179
+
180
+ {showCloseButton && onClose && (
181
+ <button
182
+ type="button"
183
+ className="popover__close"
184
+ onClick={onClose}
185
+ aria-label={closeButtonLabel}
186
+ >
187
+ <XMarkIcon />
188
+ </button>
189
+ )}
190
+ </div>
191
+ );
192
+ };
193
+
194
+ // Popover Body Component
195
+ export const PopoverBody: React.FC<PopoverBodyProps> = ({
196
+ children,
197
+ className = '',
198
+ padding = 'md',
199
+ scrollable = true,
200
+ maxHeight = '60vh'
201
+ }) => {
202
+ const bodyClasses = [
203
+ 'popover__body',
204
+ padding === 'none' ? 'popover__body--no-padding' : '',
205
+ padding === 'sm' ? 'popover__body--compact' : '',
206
+ padding === 'lg' ? 'popover__body--spacious' : '',
207
+ scrollable ? 'popover__body--scrollable' : '',
208
+ className
209
+ ].filter(Boolean).join(' ');
210
+
211
+ return (
212
+ <div
213
+ className={bodyClasses}
214
+ style={{ maxHeight: scrollable ? maxHeight : undefined }}
215
+ >
216
+ {children}
217
+ </div>
218
+ );
219
+ };
220
+
221
+ // Popover Footer Component
222
+ export const PopoverFooter: React.FC<PopoverFooterProps> = ({
223
+ children,
224
+ className = '',
225
+ justify = 'end',
226
+ padding = 'md',
227
+ borderTop = true
228
+ }) => {
229
+ const footerClasses = [
230
+ 'popover__footer',
231
+ !borderTop ? 'popover__footer--borderless' : '',
232
+ padding === 'none' ? 'popover__footer--no-padding' : '',
233
+ justify === 'start' ? 'popover__footer--start' : '',
234
+ justify === 'center' ? 'popover__footer--center' : '',
235
+ justify === 'between' ? 'popover__footer--space-between' : '',
236
+ className
237
+ ].filter(Boolean).join(' ');
238
+
239
+ return (
240
+ <div className={footerClasses}>
241
+ {children}
242
+ </div>
243
+ );
244
+ };
245
+
246
+ // Main Popover Component
247
+ export const Popover: React.FC<PopoverProps> = ({
248
+ trigger,
249
+ children,
250
+ isOpen: controlledIsOpen,
251
+ onOpenChange,
252
+ position = 'bottom',
253
+ triggerType = 'click',
254
+ delay = 0,
255
+ offset = 8,
256
+ size = 'md',
257
+ variant = 'default',
258
+ showArrow = true,
259
+ maxWidth = 400,
260
+ zIndex = 1000,
261
+ animation = 'fade',
262
+ closeOnBlur = true,
263
+ closeOnEscape = true,
264
+ className = '',
265
+ theme = 'stan-design'
266
+ }) => {
267
+ const { getTheme } = useTheme();
268
+ const themeConfig = getTheme(theme);
269
+ const colors = themeConfig?.colors || getDefaultColors();
270
+
271
+ const [internalIsOpen, setInternalIsOpen] = useState(false);
272
+ const [popoverPosition, setPopoverPosition] = useState({ top: 0, left: 0 });
273
+
274
+ const triggerRef = useRef<HTMLElement>(null);
275
+ const popoverRef = useRef<HTMLDivElement>(null);
276
+ const timeoutRef = useRef<NodeJS.Timeout | null>(null);
277
+
278
+ const isOpen = controlledIsOpen ?? internalIsOpen;
279
+
280
+ // Handle open/close state changes
281
+ const handleOpenChange = useCallback((open: boolean) => {
282
+ if (controlledIsOpen === undefined) {
283
+ setInternalIsOpen(open);
284
+ }
285
+ onOpenChange?.(open);
286
+ }, [controlledIsOpen, onOpenChange]);
287
+
288
+ // Show popover with delay
289
+ const showPopover = useCallback(() => {
290
+ if (timeoutRef.current) {
291
+ clearTimeout(timeoutRef.current);
292
+ }
293
+
294
+ if (delay > 0) {
295
+ timeoutRef.current = setTimeout(() => {
296
+ handleOpenChange(true);
297
+ }, delay);
298
+ } else {
299
+ handleOpenChange(true);
300
+ }
301
+ }, [delay, handleOpenChange]);
302
+
303
+ // Hide popover
304
+ const hidePopover = useCallback(() => {
305
+ if (timeoutRef.current) {
306
+ clearTimeout(timeoutRef.current);
307
+ }
308
+ handleOpenChange(false);
309
+ }, [handleOpenChange]);
310
+
311
+ // Position calculation
312
+ const updatePosition = useCallback(() => {
313
+ if (!triggerRef.current || !popoverRef.current || !isOpen) return;
314
+
315
+ const triggerRect = triggerRef.current.getBoundingClientRect();
316
+ const popoverRect = popoverRef.current.getBoundingClientRect();
317
+
318
+ const newPosition = calculatePosition(triggerRect, popoverRect, position, offset);
319
+ setPopoverPosition(newPosition);
320
+ }, [isOpen, position, offset]);
321
+
322
+ // Handle escape key
323
+ useEffect(() => {
324
+ if (!isOpen || !closeOnEscape) return;
325
+
326
+ const handleEscape = (event: KeyboardEvent) => {
327
+ if (event.key === 'Escape') {
328
+ hidePopover();
329
+ }
330
+ };
331
+
332
+ document.addEventListener('keydown', handleEscape);
333
+ return () => document.removeEventListener('keydown', handleEscape);
334
+ }, [isOpen, closeOnEscape, hidePopover]);
335
+
336
+ // Handle click outside
337
+ useEffect(() => {
338
+ if (!isOpen || !closeOnBlur) return;
339
+
340
+ const handleClickOutside = (event: MouseEvent) => {
341
+ const target = event.target as Node;
342
+
343
+ if (
344
+ triggerRef.current && !triggerRef.current.contains(target) &&
345
+ popoverRef.current && !popoverRef.current.contains(target)
346
+ ) {
347
+ hidePopover();
348
+ }
349
+ };
350
+
351
+ document.addEventListener('mousedown', handleClickOutside);
352
+ return () => document.removeEventListener('mousedown', handleClickOutside);
353
+ }, [isOpen, closeOnBlur, hidePopover]);
354
+
355
+ // Update position when popover opens or on scroll/resize
356
+ useEffect(() => {
357
+ if (isOpen) {
358
+ // Small delay to ensure DOM is updated
359
+ setTimeout(updatePosition, 0);
360
+
361
+ const handleResize = () => updatePosition();
362
+ const handleScroll = () => updatePosition();
363
+
364
+ window.addEventListener('resize', handleResize);
365
+ window.addEventListener('scroll', handleScroll, true);
366
+
367
+ return () => {
368
+ window.removeEventListener('resize', handleResize);
369
+ window.removeEventListener('scroll', handleScroll, true);
370
+ };
371
+ }
372
+ }, [isOpen, updatePosition]);
373
+
374
+ // Cleanup timeout on unmount
375
+ useEffect(() => {
376
+ return () => {
377
+ if (timeoutRef.current) {
378
+ clearTimeout(timeoutRef.current);
379
+ }
380
+ };
381
+ }, []);
382
+
383
+ // Get popover content classes
384
+ const getContentClasses = () => {
385
+ const classes = [
386
+ 'popover__content',
387
+ size ? `popover__content--size-${size}` : '',
388
+ variant ? `popover__content--variant-${variant}` : '',
389
+ position ? `popover__content--position-${position}` : '',
390
+ className
391
+ ];
392
+
393
+ // Add animation class
394
+ if (animation !== 'none' && isOpen) {
395
+ classes.push(`popover__content--${animation}-enter`);
396
+ }
397
+
398
+ return classes.filter(Boolean).join(' ');
399
+ };
400
+
401
+ // Create trigger event handlers
402
+ const getTriggerProps = () => {
403
+ const props: any = {};
404
+
405
+ if (triggerType === 'hover') {
406
+ props.onMouseEnter = showPopover;
407
+ props.onMouseLeave = hidePopover;
408
+ } else if (triggerType === 'click') {
409
+ props.onClick = () => handleOpenChange(!isOpen);
410
+ } else if (triggerType === 'focus') {
411
+ props.onFocus = showPopover;
412
+ props.onBlur = hidePopover;
413
+ }
414
+
415
+ return props;
416
+ };
417
+
418
+ // Clone trigger element with event handlers
419
+ const triggerElement = cloneElement(trigger, {
420
+ ...getTriggerProps(),
421
+ ref: triggerRef
422
+ });
423
+
424
+ return (
425
+ <>
426
+ {triggerElement}
427
+
428
+ {isOpen && (
429
+ <Portal>
430
+ <FocusManager
431
+ isActive={isOpen}
432
+ trapFocus={false} // Don't trap focus for popovers by default
433
+ restoreFocus={true}
434
+ >
435
+ <div
436
+ ref={popoverRef}
437
+ role="dialog"
438
+ aria-modal="false"
439
+ className={getContentClasses()}
440
+ style={{
441
+ ...popoverPosition,
442
+ maxWidth: typeof maxWidth === 'number' ? `${maxWidth}px` : maxWidth,
443
+ zIndex,
444
+ backgroundColor: colors.surface.background,
445
+ borderColor: colors.surface.border,
446
+ color: colors.text.primary
447
+ }}
448
+ >
449
+ {children}
450
+
451
+ {showArrow && (
452
+ <PopoverArrow position={position} colors={colors} />
453
+ )}
454
+ </div>
455
+ </FocusManager>
456
+ </Portal>
457
+ )}
458
+ </>
459
+ );
460
+ };
461
+
462
+ export default Popover;
@@ -0,0 +1,79 @@
1
+ import React, { useEffect, useState } from 'react';
2
+ import { createPortal } from 'react-dom';
3
+ import { PortalProps } from './types';
4
+
5
+ export const Portal: React.FC<PortalProps> = ({
6
+ children,
7
+ container,
8
+ className = '',
9
+ style = {},
10
+ onMount,
11
+ onUnmount,
12
+ variant = 'default',
13
+ position = 'fixed',
14
+ zIndex = 'modal',
15
+ debug = false
16
+ }) => {
17
+ const [mounted, setMounted] = useState(false);
18
+
19
+ useEffect(() => {
20
+ setMounted(true);
21
+
22
+ if (onMount) {
23
+ onMount();
24
+ }
25
+
26
+ return () => {
27
+ if (onUnmount) {
28
+ onUnmount();
29
+ }
30
+ };
31
+ }, [onMount, onUnmount]);
32
+
33
+ if (!mounted) {
34
+ return null;
35
+ }
36
+
37
+ // Determine the container to render into
38
+ let targetContainer: Element | null = null;
39
+
40
+ if (container) {
41
+ if (typeof container === 'function') {
42
+ targetContainer = container();
43
+ } else {
44
+ targetContainer = container;
45
+ }
46
+ }
47
+
48
+ // Fallback to document.body if no container specified
49
+ if (!targetContainer) {
50
+ targetContainer = document.body;
51
+ }
52
+
53
+ // Ensure the container exists
54
+ if (!targetContainer) {
55
+ console.warn('Portal: Target container not found');
56
+ return null;
57
+ }
58
+
59
+ // Build container classes
60
+ const containerClasses = [
61
+ 'portal__container',
62
+ variant !== 'default' ? `portal__container--${variant}` : '',
63
+ position ? `portal__container--${position}` : '',
64
+ zIndex ? `portal__container--${zIndex}` : '',
65
+ debug ? 'portal__container--debug' : '',
66
+ className
67
+ ].filter(Boolean).join(' ');
68
+
69
+ return createPortal(
70
+ <div className={containerClasses} style={style}>
71
+ <div className="portal__content">
72
+ {children}
73
+ </div>
74
+ </div>,
75
+ targetContainer
76
+ );
77
+ };
78
+
79
+ export default Portal;