@pattern-stack/frontend-patterns 0.0.3 → 0.0.4

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 (154) hide show
  1. package/dist/index.es.js +1 -1
  2. package/dist/index.js +1 -0
  3. package/package.json +5 -3
  4. package/src/App.css +42 -0
  5. package/src/App.tsx +54 -0
  6. package/src/__tests__/README.md +221 -0
  7. package/src/__tests__/atoms/hooks/simple-hooks.test.ts +44 -0
  8. package/src/__tests__/atoms/ui/button.test.tsx +68 -0
  9. package/src/__tests__/atoms/utils/simple.test.ts +18 -0
  10. package/src/__tests__/atoms/utils/utils.test.ts +77 -0
  11. package/src/__tests__/features/auth/simple-auth.test.tsx +40 -0
  12. package/src/__tests__/molecules/layout/simple-layout.test.tsx +81 -0
  13. package/src/__tests__/organisms/showcase/simple-showcase.test.tsx +167 -0
  14. package/src/__tests__/setup.ts +51 -0
  15. package/src/__tests__/utils.tsx +123 -0
  16. package/src/atoms/composed/Accordion/Accordion.tsx +271 -0
  17. package/src/atoms/composed/Accordion/index.ts +1 -0
  18. package/src/atoms/composed/Alert/Alert.tsx +132 -0
  19. package/src/atoms/composed/Alert/index.ts +1 -0
  20. package/src/atoms/composed/Breadcrumb/Breadcrumb.tsx +83 -0
  21. package/src/atoms/composed/Breadcrumb/index.ts +1 -0
  22. package/src/atoms/composed/Chart/Chart.tsx +425 -0
  23. package/src/atoms/composed/Chart/index.ts +2 -0
  24. package/src/atoms/composed/ColorSwatch/ColorSwatch.tsx +72 -0
  25. package/src/atoms/composed/ColorSwatch/index.ts +1 -0
  26. package/src/atoms/composed/DarkModeToggle.tsx +66 -0
  27. package/src/atoms/composed/DataBadge/DataBadge.tsx +81 -0
  28. package/src/atoms/composed/DataBadge/index.ts +1 -0
  29. package/src/atoms/composed/DataTable/DataTable.tsx +394 -0
  30. package/src/atoms/composed/DataTable/TableCellWithTooltip.tsx +41 -0
  31. package/src/atoms/composed/DataTable/index.ts +2 -0
  32. package/src/atoms/composed/DateTimePicker/DateTimePicker.tsx +611 -0
  33. package/src/atoms/composed/DateTimePicker/index.ts +2 -0
  34. package/src/atoms/composed/DetailedCard/DetailedCard.tsx +181 -0
  35. package/src/atoms/composed/DetailedCard/index.ts +2 -0
  36. package/src/atoms/composed/EmptyState/EmptyState.tsx +90 -0
  37. package/src/atoms/composed/EmptyState/index.ts +1 -0
  38. package/src/atoms/composed/FileUpload/FileUpload.tsx +477 -0
  39. package/src/atoms/composed/FileUpload/index.ts +2 -0
  40. package/src/atoms/composed/FormField/FormField.tsx +92 -0
  41. package/src/atoms/composed/FormField/index.ts +1 -0
  42. package/src/atoms/composed/GlobalSearch/GlobalSearch.tsx +37 -0
  43. package/src/atoms/composed/GlobalSearch/index.ts +1 -0
  44. package/src/atoms/composed/IconBadge/IconBadge.tsx +95 -0
  45. package/src/atoms/composed/IconBadge/index.ts +2 -0
  46. package/src/atoms/composed/Modal/Modal.tsx +223 -0
  47. package/src/atoms/composed/Modal/index.ts +2 -0
  48. package/src/atoms/composed/PaletteSwitcher.tsx +386 -0
  49. package/src/atoms/composed/ProgressBar/ProgressBar.tsx +116 -0
  50. package/src/atoms/composed/ProgressBar/index.ts +1 -0
  51. package/src/atoms/composed/StatCard/StatCard.tsx +219 -0
  52. package/src/atoms/composed/StatCard/index.ts +1 -0
  53. package/src/atoms/composed/StyleGuide.tsx +717 -0
  54. package/src/atoms/composed/Toast/Toast.tsx +219 -0
  55. package/src/atoms/composed/Toast/index.ts +1 -0
  56. package/src/atoms/composed/Tooltip/Tooltip.tsx +213 -0
  57. package/src/atoms/composed/Tooltip/index.ts +1 -0
  58. package/src/atoms/composed/UserAvatar/UserAvatar.tsx +139 -0
  59. package/src/atoms/composed/UserAvatar/index.ts +1 -0
  60. package/src/atoms/composed/UserMenu/UserMenu.tsx +16 -0
  61. package/src/atoms/composed/UserMenu/index.ts +1 -0
  62. package/src/atoms/composed/index.ts +29 -0
  63. package/src/atoms/hooks/useApi.ts +80 -0
  64. package/src/atoms/hooks/useHealth.ts +17 -0
  65. package/src/atoms/index.ts +13 -0
  66. package/src/atoms/services/api/client.ts +134 -0
  67. package/src/atoms/services/auth-service.ts +248 -0
  68. package/src/atoms/services/health.ts +15 -0
  69. package/src/atoms/services/index.ts +3 -0
  70. package/src/atoms/shared/config/constants.ts +17 -0
  71. package/src/atoms/shared/config/dashboard-sizes.ts +111 -0
  72. package/src/atoms/shared/config/environment.ts +10 -0
  73. package/src/atoms/shared/index.ts +4 -0
  74. package/src/atoms/shared/styles/color-palettes.css +566 -0
  75. package/src/atoms/types/auth.ts +62 -0
  76. package/src/atoms/types/generated.ts +1469 -0
  77. package/src/atoms/types/index.ts +4 -0
  78. package/src/atoms/types/loading.ts +28 -0
  79. package/src/atoms/ui/Badge.tsx +30 -0
  80. package/src/atoms/ui/ErrorBoundary.tsx +59 -0
  81. package/src/atoms/ui/Select.tsx +53 -0
  82. package/src/atoms/ui/Switch.tsx +42 -0
  83. package/src/atoms/ui/Tabs.tsx +118 -0
  84. package/src/atoms/ui/avatar.tsx +48 -0
  85. package/src/atoms/ui/button.tsx +70 -0
  86. package/src/atoms/ui/card.tsx +76 -0
  87. package/src/atoms/ui/dropdown-menu.tsx +199 -0
  88. package/src/atoms/ui/index.ts +39 -0
  89. package/src/atoms/ui/input.tsx +23 -0
  90. package/src/atoms/ui/label.tsx +23 -0
  91. package/src/atoms/ui/skeleton.tsx +13 -0
  92. package/src/atoms/ui/spinner.tsx +49 -0
  93. package/src/atoms/ui/table.tsx +116 -0
  94. package/src/atoms/utils/animations.ts +135 -0
  95. package/src/atoms/utils/tooltip-helpers.ts +140 -0
  96. package/src/atoms/utils/utils.ts +9 -0
  97. package/src/features/auth/components/LoginForm.tsx +168 -0
  98. package/src/features/auth/components/LogoutButton.tsx +19 -0
  99. package/src/features/auth/components/ProtectedRoute.tsx +60 -0
  100. package/src/features/auth/components/index.ts +4 -0
  101. package/src/features/auth/hooks/index.ts +2 -0
  102. package/src/features/auth/hooks/useAuth.tsx +205 -0
  103. package/src/features/auth/hooks/usePermissions.ts +35 -0
  104. package/src/features/auth/index.ts +2 -0
  105. package/src/features/index.ts +2 -0
  106. package/src/index.css +704 -0
  107. package/src/index.ts +13 -0
  108. package/src/main.tsx +48 -0
  109. package/src/molecules/.gitkeep +0 -0
  110. package/src/molecules/forms/FormGroup.tsx +75 -0
  111. package/src/molecules/forms/SearchInput.tsx +259 -0
  112. package/src/molecules/forms/index.ts +4 -0
  113. package/src/molecules/index.ts +4 -0
  114. package/src/molecules/layout/AppHeader/AppHeader.tsx +42 -0
  115. package/src/molecules/layout/AppHeader/index.ts +1 -0
  116. package/src/molecules/layout/AppLayout.tsx +29 -0
  117. package/src/molecules/layout/PageTemplate.tsx +87 -0
  118. package/src/molecules/layout/SectionHeader/SectionHeader.tsx +87 -0
  119. package/src/molecules/layout/SectionHeader/index.ts +1 -0
  120. package/src/molecules/layout/ShowcaseSection.tsx +57 -0
  121. package/src/molecules/layout/Sidebar.tsx +144 -0
  122. package/src/molecules/layout/SidebarButton/SidebarButton.tsx +99 -0
  123. package/src/molecules/layout/SidebarButton/index.ts +1 -0
  124. package/src/molecules/layout/SidebarContext.tsx +31 -0
  125. package/src/molecules/layout/index.ts +7 -0
  126. package/src/molecules/navigation/NavMenu.tsx +188 -0
  127. package/src/molecules/navigation/Pagination.tsx +172 -0
  128. package/src/molecules/navigation/index.ts +4 -0
  129. package/src/organisms/index.ts +5 -0
  130. package/src/organisms/showcase/ComponentShowcasePage.tsx +2496 -0
  131. package/src/organisms/showcase/index.ts +1 -0
  132. package/src/pages/AdminShowcase/AdminCRUDShowcase.tsx +242 -0
  133. package/src/pages/AdminShowcase/AdminDashboardShowcase.tsx +171 -0
  134. package/src/pages/AdminShowcase/AdminDetailShowcase.tsx +385 -0
  135. package/src/pages/AdminShowcase/index.tsx +3 -0
  136. package/src/pages/ComponentShowcase/BadgesShowcase.tsx +188 -0
  137. package/src/pages/ComponentShowcase/CardsShowcase.tsx +392 -0
  138. package/src/pages/ComponentShowcase/PalettesShowcase.tsx +207 -0
  139. package/src/pages/ComponentShowcase/StatesShowcase.tsx +485 -0
  140. package/src/pages/ComponentShowcase/TablesShowcase.tsx +134 -0
  141. package/src/pages/ComponentShowcase/TypographyShowcase.tsx +255 -0
  142. package/src/pages/ComponentShowcase/index.tsx +188 -0
  143. package/src/pages/index.ts +2 -0
  144. package/src/templates/AuthTemplate.tsx +216 -0
  145. package/src/templates/ComponentShowcaseTemplate.tsx +173 -0
  146. package/src/templates/DashboardTemplate.tsx +232 -0
  147. package/src/templates/DataTemplate.tsx +319 -0
  148. package/src/templates/admin/AdminCRUDTemplate.tsx +630 -0
  149. package/src/templates/admin/AdminDashboardTemplate.tsx +351 -0
  150. package/src/templates/admin/AdminDetailTemplate.tsx +563 -0
  151. package/src/templates/admin/index.ts +29 -0
  152. package/src/templates/factory.tsx +169 -0
  153. package/src/templates/index.ts +37 -0
  154. package/src/vite-env.d.ts +1 -0
@@ -0,0 +1,219 @@
1
+ import React, { useEffect, useState, useCallback } from 'react';
2
+ import { X, CheckCircle, AlertTriangle, AlertCircle, Info } from 'lucide-react';
3
+ import { cn } from '../../utils/utils';
4
+ import { Button } from '../../ui/button';
5
+ import { getAnimationClasses, animationPresets } from '../../utils/animations';
6
+
7
+ export interface ToastProps {
8
+ /** Toast content */
9
+ children: React.ReactNode;
10
+ /** Toast status type */
11
+ status?: 'success' | 'warning' | 'error' | 'info' | 'neutral';
12
+ /** Toast title */
13
+ title?: string;
14
+ /** Auto-dismiss duration in milliseconds */
15
+ duration?: number;
16
+ /** Whether toast can be dismissed */
17
+ dismissible?: boolean;
18
+ /** Dismiss handler */
19
+ onDismiss?: () => void;
20
+ /** Toast position */
21
+ position?: 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left' | 'top-center' | 'bottom-center';
22
+ /** Custom icon */
23
+ icon?: React.ReactNode;
24
+ /** Hide default icon */
25
+ hideIcon?: boolean;
26
+ /** Additional CSS classes */
27
+ className?: string;
28
+ /** Show progress bar for auto-dismiss */
29
+ showProgress?: boolean;
30
+ }
31
+
32
+ const statusIcons = {
33
+ success: CheckCircle,
34
+ warning: AlertTriangle,
35
+ error: AlertCircle,
36
+ info: Info,
37
+ neutral: Info
38
+ };
39
+
40
+ export const Toast: React.FC<ToastProps> = ({
41
+ children,
42
+ status = 'neutral',
43
+ title,
44
+ duration = 0, // 0 means no auto-dismiss
45
+ dismissible = true,
46
+ onDismiss,
47
+ position = 'top-right',
48
+ icon,
49
+ hideIcon = false,
50
+ className,
51
+ showProgress = false
52
+ }) => {
53
+ const [isVisible, setIsVisible] = useState(true);
54
+ const [progress, setProgress] = useState(100);
55
+
56
+ const IconComponent = statusIcons[status];
57
+ const displayIcon = icon || (!hideIcon && <IconComponent className="w-5 h-5" />);
58
+
59
+ const handleDismiss = useCallback(() => {
60
+ setIsVisible(false);
61
+ setTimeout(() => onDismiss?.(), 300); // Wait for exit animation
62
+ }, [onDismiss]);
63
+
64
+ useEffect(() => {
65
+ if (duration > 0) {
66
+ const dismissTimer = setTimeout(handleDismiss, duration);
67
+
68
+ if (showProgress) {
69
+ const progressInterval = setInterval(() => {
70
+ setProgress(prev => {
71
+ const decrement = 100 / (duration / 50); // Update every 50ms
72
+ return Math.max(prev - decrement, 0);
73
+ });
74
+ }, 50);
75
+
76
+ return () => {
77
+ clearTimeout(dismissTimer);
78
+ clearInterval(progressInterval);
79
+ };
80
+ }
81
+
82
+ return () => clearTimeout(dismissTimer);
83
+ }
84
+ }, [duration, showProgress, handleDismiss]);
85
+
86
+ const positionClasses = {
87
+ 'top-right': 'top-4 right-4',
88
+ 'top-left': 'top-4 left-4',
89
+ 'bottom-right': 'bottom-4 right-4',
90
+ 'bottom-left': 'bottom-4 left-4',
91
+ 'top-center': 'top-4 left-1/2 -translate-x-1/2',
92
+ 'bottom-center': 'bottom-4 left-1/2 -translate-x-1/2'
93
+ };
94
+
95
+ const statusClasses = {
96
+ success: 'bg-background text-status-success border-status-success/30 shadow-status-success/10',
97
+ warning: 'bg-background text-status-warning border-status-warning/30 shadow-status-warning/10',
98
+ error: 'bg-background text-status-error border-status-error/30 shadow-status-error/10',
99
+ info: 'bg-background text-status-info border-status-info/30 shadow-status-info/10',
100
+ neutral: 'bg-background text-foreground border-border shadow-muted/20'
101
+ };
102
+
103
+ if (!isVisible) {
104
+ return null;
105
+ }
106
+
107
+ return (
108
+ <div
109
+ className={cn(
110
+ 'fixed z-50 w-full max-w-sm',
111
+ positionClasses[position]
112
+ )}
113
+ >
114
+ <div
115
+ className={cn(
116
+ 'rounded-lg border p-4 shadow-lg backdrop-blur-sm',
117
+ 'flex items-start gap-3 relative overflow-hidden',
118
+ statusClasses[status],
119
+ getAnimationClasses(animationPresets.card),
120
+ className
121
+ )}
122
+ role="alert"
123
+ data-component-name="Toast"
124
+ >
125
+ {/* Icon */}
126
+ {displayIcon && (
127
+ <div className="flex-shrink-0 mt-0.5">
128
+ {displayIcon}
129
+ </div>
130
+ )}
131
+
132
+ {/* Content */}
133
+ <div className="flex-1 min-w-0">
134
+ {title && (
135
+ <h4 className="font-semibold mb-1 text-sm">
136
+ {title}
137
+ </h4>
138
+ )}
139
+ <div className="text-sm text-current">
140
+ {children}
141
+ </div>
142
+ </div>
143
+
144
+ {/* Dismiss button */}
145
+ {dismissible && (
146
+ <Button
147
+ variant="ghost"
148
+ size="sm"
149
+ onClick={handleDismiss}
150
+ className={cn(
151
+ 'flex-shrink-0 h-auto p-1 -mt-1 -mr-1',
152
+ 'hover:bg-current/10 text-current'
153
+ )}
154
+ aria-label="Dismiss notification"
155
+ >
156
+ <X className="w-4 h-4" />
157
+ </Button>
158
+ )}
159
+
160
+ {/* Progress bar */}
161
+ {showProgress && duration > 0 && (
162
+ <div className="absolute bottom-0 left-0 right-0 h-1 bg-current/10">
163
+ <div
164
+ className="h-full bg-current/30 transition-all duration-50 ease-linear"
165
+ style={{ width: `${progress}%` }}
166
+ />
167
+ </div>
168
+ )}
169
+ </div>
170
+ </div>
171
+ );
172
+ };
173
+
174
+ // Toast container component for managing multiple toasts
175
+ export interface ToastContainerProps {
176
+ /** Toast notifications */
177
+ toasts: Array<ToastProps & { id: string }>;
178
+ /** Container position */
179
+ position?: ToastProps['position'];
180
+ /** Maximum number of visible toasts */
181
+ maxToasts?: number;
182
+ /** Additional CSS classes */
183
+ className?: string;
184
+ }
185
+
186
+ export const ToastContainer: React.FC<ToastContainerProps> = ({
187
+ toasts,
188
+ position = 'top-right',
189
+ maxToasts = 5,
190
+ className
191
+ }) => {
192
+ const visibleToasts = toasts.slice(0, maxToasts);
193
+
194
+ const positionClasses = {
195
+ 'top-right': 'top-4 right-4 flex-col',
196
+ 'top-left': 'top-4 left-4 flex-col',
197
+ 'bottom-right': 'bottom-4 right-4 flex-col-reverse',
198
+ 'bottom-left': 'bottom-4 left-4 flex-col-reverse',
199
+ 'top-center': 'top-4 left-1/2 -translate-x-1/2 flex-col',
200
+ 'bottom-center': 'bottom-4 left-1/2 -translate-x-1/2 flex-col-reverse'
201
+ };
202
+
203
+ return (
204
+ <div
205
+ className={cn(
206
+ 'fixed z-50 flex gap-2 pointer-events-none',
207
+ positionClasses[position],
208
+ className
209
+ )}
210
+ data-component-name="ToastContainer"
211
+ >
212
+ {visibleToasts.map(({ id, ...toastProps }) => (
213
+ <div key={id} className="pointer-events-auto">
214
+ <Toast {...toastProps} position={position} />
215
+ </div>
216
+ ))}
217
+ </div>
218
+ );
219
+ };
@@ -0,0 +1 @@
1
+ export { Toast, ToastContainer, type ToastProps, type ToastContainerProps } from './Toast';
@@ -0,0 +1,213 @@
1
+ import React, { useState, useRef, useEffect } from 'react';
2
+ import { cn } from '../../utils/utils';
3
+ import { getAnimationClasses, animationPresets } from '../../utils/animations';
4
+
5
+ export interface TooltipProps {
6
+ children: React.ReactNode;
7
+ content: React.ReactNode;
8
+ position?: 'top' | 'bottom' | 'left' | 'right';
9
+ variant?: 'default' | 'info' | 'warning' | 'error';
10
+ size?: 'sm' | 'md' | 'lg';
11
+ disabled?: boolean;
12
+ delay?: number;
13
+ offset?: number;
14
+ trigger?: 'hover' | 'focus' | 'both';
15
+ className?: string;
16
+ contentClassName?: string;
17
+ }
18
+
19
+ export const Tooltip = ({
20
+ children,
21
+ content,
22
+ position = 'top',
23
+ variant = 'default',
24
+ size = 'md',
25
+ disabled = false,
26
+ delay = 300,
27
+ offset = 8,
28
+ trigger = 'both',
29
+ className,
30
+ contentClassName
31
+ }: TooltipProps) => {
32
+ const [isVisible, setIsVisible] = useState(false);
33
+ const [actualPosition, setActualPosition] = useState(position);
34
+ const triggerRef = useRef<HTMLDivElement>(null);
35
+ const tooltipRef = useRef<HTMLDivElement>(null);
36
+ const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
37
+
38
+ const sizeClasses: Record<'sm' | 'md' | 'lg', string> = {
39
+ sm: 'px-2 py-1 text-xs max-w-48',
40
+ md: 'px-3 py-2 text-sm max-w-64',
41
+ lg: 'px-4 py-3 text-base max-w-80',
42
+ };
43
+
44
+ const variantClasses: Record<'default' | 'info' | 'warning' | 'error', string> = {
45
+ default: 'bg-card/95 text-card-foreground border-border/30',
46
+ info: 'status-info text-card-foreground border-status-info/30',
47
+ warning: 'status-warning text-card-foreground border-status-warning/30',
48
+ error: 'status-error text-card-foreground border-status-error/30',
49
+ };
50
+
51
+ const positionClasses: Record<'top' | 'bottom' | 'left' | 'right', string> = {
52
+ top: 'bottom-full left-1/2 transform -translate-x-1/2 mb-2',
53
+ bottom: 'top-full left-1/2 transform -translate-x-1/2 mt-2',
54
+ left: 'right-full top-1/2 transform -translate-y-1/2 mr-2',
55
+ right: 'left-full top-1/2 transform -translate-y-1/2 ml-2',
56
+ };
57
+
58
+ const arrowClasses: Record<'top' | 'bottom' | 'left' | 'right', string> = {
59
+ top: 'top-full left-1/2 transform -translate-x-1/2 border-l-transparent border-r-transparent border-b-transparent',
60
+ bottom: 'bottom-full left-1/2 transform -translate-x-1/2 border-l-transparent border-r-transparent border-t-transparent',
61
+ left: 'left-full top-1/2 transform -translate-y-1/2 border-t-transparent border-b-transparent border-r-transparent',
62
+ right: 'right-full top-1/2 transform -translate-y-1/2 border-t-transparent border-b-transparent border-l-transparent',
63
+ };
64
+
65
+ const showTooltip = () => {
66
+ if (disabled) return;
67
+ if (timeoutRef.current) clearTimeout(timeoutRef.current);
68
+
69
+ timeoutRef.current = setTimeout(() => {
70
+ setIsVisible(true);
71
+ requestAnimationFrame(() => {
72
+ checkAndAdjustPosition();
73
+ });
74
+ }, delay);
75
+ };
76
+
77
+ const hideTooltip = () => {
78
+ if (timeoutRef.current) clearTimeout(timeoutRef.current);
79
+ setIsVisible(false);
80
+ };
81
+
82
+ const checkAndAdjustPosition = () => {
83
+ if (!triggerRef.current || !tooltipRef.current) return;
84
+
85
+ const triggerRect = triggerRef.current.getBoundingClientRect();
86
+ const tooltipRect = tooltipRef.current.getBoundingClientRect();
87
+ const viewport = {
88
+ width: window.innerWidth,
89
+ height: window.innerHeight,
90
+ };
91
+
92
+ let newPosition = position;
93
+
94
+ // Check if tooltip goes outside viewport and adjust position
95
+ switch (position) {
96
+ case 'top':
97
+ if (triggerRect.top - tooltipRect.height - offset < 0) {
98
+ newPosition = 'bottom';
99
+ }
100
+ break;
101
+ case 'bottom':
102
+ if (triggerRect.bottom + tooltipRect.height + offset > viewport.height) {
103
+ newPosition = 'top';
104
+ }
105
+ break;
106
+ case 'left':
107
+ if (triggerRect.left - tooltipRect.width - offset < 0) {
108
+ newPosition = 'right';
109
+ }
110
+ break;
111
+ case 'right':
112
+ if (triggerRect.right + tooltipRect.width + offset > viewport.width) {
113
+ newPosition = 'left';
114
+ }
115
+ break;
116
+ }
117
+
118
+ setActualPosition(newPosition);
119
+ };
120
+
121
+ useEffect(() => {
122
+ return () => {
123
+ if (timeoutRef.current) clearTimeout(timeoutRef.current);
124
+ };
125
+ }, []);
126
+
127
+ const triggerProps = {
128
+ ...(trigger === 'hover' || trigger === 'both' ? {
129
+ onMouseEnter: showTooltip,
130
+ onMouseLeave: hideTooltip,
131
+ } : {}),
132
+ ...(trigger === 'focus' || trigger === 'both' ? {
133
+ onFocus: showTooltip,
134
+ onBlur: hideTooltip,
135
+ } : {}),
136
+ };
137
+
138
+ const tooltipBaseClasses = cn(
139
+ 'absolute z-50 rounded-lg border backdrop-blur-sm shadow-lg pointer-events-none',
140
+ 'animate-fade-in transition-all duration-200 ease-out',
141
+ sizeClasses[size],
142
+ variantClasses[variant],
143
+ positionClasses[actualPosition],
144
+ getAnimationClasses({
145
+ ...animationPresets.subtle,
146
+ timing: 'fast'
147
+ }),
148
+ contentClassName
149
+ );
150
+
151
+ const arrowBaseClasses = cn(
152
+ 'absolute w-0 h-0 border-4',
153
+ arrowClasses[actualPosition]
154
+ );
155
+
156
+ const getArrowColor = () => {
157
+ switch (variant) {
158
+ case 'info':
159
+ return 'border-t-status-info';
160
+ case 'warning':
161
+ return 'border-t-status-warning';
162
+ case 'error':
163
+ return 'border-t-status-error';
164
+ default:
165
+ return 'border-t-card';
166
+ }
167
+ };
168
+
169
+ return (
170
+ <div className={cn("relative inline-block", className)}>
171
+ <div
172
+ ref={triggerRef}
173
+ {...triggerProps}
174
+ className="cursor-help"
175
+ aria-describedby={isVisible ? 'tooltip' : undefined}
176
+ data-component-name="TooltipTrigger"
177
+ >
178
+ {children}
179
+ </div>
180
+
181
+ {isVisible && !disabled && (
182
+ <div
183
+ ref={tooltipRef}
184
+ id="tooltip"
185
+ role="tooltip"
186
+ aria-hidden={!isVisible}
187
+ className={tooltipBaseClasses}
188
+ style={{
189
+ marginTop: actualPosition === 'bottom' ? `${offset}px` : actualPosition === 'top' ? `-${offset}px` : '0',
190
+ marginLeft: actualPosition === 'right' ? `${offset}px` : actualPosition === 'left' ? `-${offset}px` : '0',
191
+ }}
192
+ data-component-name="Tooltip"
193
+ >
194
+ {content}
195
+
196
+ {/* Arrow */}
197
+ <div
198
+ className={cn(
199
+ arrowBaseClasses,
200
+ getArrowColor()
201
+ )}
202
+ style={{
203
+ borderTopColor: actualPosition === 'bottom' ? 'inherit' : 'transparent',
204
+ borderBottomColor: actualPosition === 'top' ? 'inherit' : 'transparent',
205
+ borderLeftColor: actualPosition === 'right' ? 'inherit' : 'transparent',
206
+ borderRightColor: actualPosition === 'left' ? 'inherit' : 'transparent',
207
+ }}
208
+ />
209
+ </div>
210
+ )}
211
+ </div>
212
+ );
213
+ };
@@ -0,0 +1 @@
1
+ export * from './Tooltip';
@@ -0,0 +1,139 @@
1
+ import React from 'react';
2
+ import { User, Settings, LogOut, UserCircle, Moon, Sun } from 'lucide-react';
3
+ import {
4
+ Avatar,
5
+ AvatarFallback,
6
+ DropdownMenu,
7
+ DropdownMenuTrigger,
8
+ DropdownMenuContent,
9
+ DropdownMenuItem,
10
+ DropdownMenuSeparator,
11
+ Button
12
+ } from '../../ui';
13
+ import { useAuth } from '../../../features/auth';
14
+ import { cn } from '../../utils/utils';
15
+
16
+ interface UserAvatarProps {
17
+ className?: string;
18
+ category?: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8;
19
+ }
20
+
21
+ export const UserAvatar: React.FC<UserAvatarProps> = ({ className, category = 1 }) => {
22
+ const { user, logout } = useAuth();
23
+ const [isDark, setIsDark] = React.useState(() =>
24
+ document.documentElement.classList.contains('dark')
25
+ );
26
+
27
+ const toggleDarkMode = () => {
28
+ const newDarkMode = !isDark;
29
+ setIsDark(newDarkMode);
30
+
31
+ if (newDarkMode) {
32
+ document.documentElement.classList.add('dark');
33
+ } else {
34
+ document.documentElement.classList.remove('dark');
35
+ }
36
+ };
37
+
38
+ const userInitials = (user as any)?.name
39
+ ? (user as any).name.split(' ').map((n: string) => n[0]).join('').toUpperCase().slice(0, 2)
40
+ : user?.email?.[0]?.toUpperCase() || 'U';
41
+
42
+ const avatarStyles = {
43
+ background: `linear-gradient(135deg, hsl(var(--category-${category}) / 0.1), hsl(var(--category-${category}) / 0.2))`,
44
+ borderColor: `hsl(var(--category-${category}) / 0.3)`,
45
+ color: `hsl(var(--category-${category}))`
46
+ };
47
+
48
+ const handleLogout = () => {
49
+ logout();
50
+ };
51
+
52
+ return (
53
+ <div className={cn("flex items-center", className)}>
54
+ <DropdownMenu>
55
+ <DropdownMenuTrigger asChild>
56
+ <Button variant="ghost" className="relative h-9 w-9 rounded p-0 hover:bg-muted/30 transition-colors">
57
+ <Avatar className="h-9 w-9">
58
+ <AvatarFallback
59
+ className="border font-medium text-sm"
60
+ style={avatarStyles}
61
+ >
62
+ <User className="w-4 h-4" />
63
+ </AvatarFallback>
64
+ </Avatar>
65
+ </Button>
66
+ </DropdownMenuTrigger>
67
+ <DropdownMenuContent
68
+ className="w-56 bg-card/95 backdrop-blur-sm border-border/30"
69
+ align="end"
70
+ forceMount
71
+ >
72
+ {/* User Info */}
73
+ <div className="flex items-center space-x-3 p-3">
74
+ <Avatar className="h-8 w-8">
75
+ <AvatarFallback
76
+ className="border font-medium text-xs"
77
+ style={avatarStyles}
78
+ >
79
+ {userInitials}
80
+ </AvatarFallback>
81
+ </Avatar>
82
+ <div className="flex flex-col space-y-0.5">
83
+ <p className="text-sm font-medium leading-none text-foreground">
84
+ {(user as any)?.name || 'Design User'}
85
+ </p>
86
+ <p className="text-xs leading-none text-muted-foreground">
87
+ {user?.email || 'user@design.studio'}
88
+ </p>
89
+ </div>
90
+ </div>
91
+
92
+ <DropdownMenuSeparator />
93
+
94
+ {/* Dark Mode Toggle */}
95
+ <DropdownMenuItem
96
+ className="cursor-pointer focus:bg-muted/50"
97
+ onClick={toggleDarkMode}
98
+ >
99
+ {isDark ? (
100
+ <Sun className="mr-2 h-4 w-4" />
101
+ ) : (
102
+ <Moon className="mr-2 h-4 w-4" />
103
+ )}
104
+ <span>{isDark ? 'Light Mode' : 'Dark Mode'}</span>
105
+ </DropdownMenuItem>
106
+
107
+ <DropdownMenuSeparator />
108
+
109
+ {/* Menu Items */}
110
+ <DropdownMenuItem
111
+ className="cursor-pointer focus:bg-muted/50"
112
+ onClick={() => alert('Profile coming soon!')}
113
+ >
114
+ <UserCircle className="mr-2 h-4 w-4" />
115
+ <span>Profile</span>
116
+ </DropdownMenuItem>
117
+
118
+ <DropdownMenuItem
119
+ className="cursor-pointer focus:bg-muted/50"
120
+ onClick={() => alert('Settings coming soon!')}
121
+ >
122
+ <Settings className="mr-2 h-4 w-4" />
123
+ <span>Settings</span>
124
+ </DropdownMenuItem>
125
+
126
+ <DropdownMenuSeparator />
127
+
128
+ <DropdownMenuItem
129
+ className="cursor-pointer text-destructive focus:text-destructive focus:bg-destructive/10"
130
+ onClick={handleLogout}
131
+ >
132
+ <LogOut className="mr-2 h-4 w-4" />
133
+ <span>Logout</span>
134
+ </DropdownMenuItem>
135
+ </DropdownMenuContent>
136
+ </DropdownMenu>
137
+ </div>
138
+ );
139
+ };
@@ -0,0 +1 @@
1
+ export { UserAvatar } from './UserAvatar';
@@ -0,0 +1,16 @@
1
+ import React from 'react';
2
+ import { UserAvatar } from '../UserAvatar';
3
+ import { cn } from '../../utils/utils';
4
+
5
+ interface UserMenuProps {
6
+ className?: string;
7
+ category?: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8;
8
+ }
9
+
10
+ export const UserMenu: React.FC<UserMenuProps> = ({ className, category }) => {
11
+ return (
12
+ <div className={cn("flex items-center", className)}>
13
+ <UserAvatar category={category} />
14
+ </div>
15
+ );
16
+ };
@@ -0,0 +1 @@
1
+ export { UserMenu } from './UserMenu';
@@ -0,0 +1,29 @@
1
+ // Business-specific shared components
2
+ export { DataBadge } from './DataBadge';
3
+ export { StatCard } from './StatCard';
4
+ export { DetailedCard } from './DetailedCard';
5
+ export { EmptyState } from './EmptyState';
6
+ export { ColorSwatch } from './ColorSwatch';
7
+ export { IconBadge } from './IconBadge';
8
+ export { UserAvatar } from './UserAvatar';
9
+ export { UserMenu } from './UserMenu';
10
+ export { GlobalSearch } from './GlobalSearch';
11
+ export { DataTable } from './DataTable';
12
+ export { DateTimePicker } from './DateTimePicker';
13
+ export { Chart } from './Chart';
14
+ export { Accordion } from './Accordion';
15
+ export { Modal } from './Modal';
16
+ export { Tooltip } from './Tooltip';
17
+ export { FileUpload } from './FileUpload';
18
+
19
+ // Moved from molecules - business UI components
20
+ export { Alert } from './Alert';
21
+ export { Toast, ToastContainer } from './Toast';
22
+ export { ProgressBar } from './ProgressBar';
23
+ export { FormField } from './FormField';
24
+ export { Breadcrumb } from './Breadcrumb';
25
+
26
+ // Re-export any remaining components
27
+ export { DarkModeToggle } from './DarkModeToggle';
28
+ export { PaletteSwitcher } from './PaletteSwitcher';
29
+ export { StyleGuide } from './StyleGuide';