@umituz/web-design-system 3.1.22 → 4.0.2

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 (84) hide show
  1. package/README.md +137 -469
  2. package/dist/styles.css +2 -0
  3. package/package.json +31 -31
  4. package/src/__tests__/cn.test.ts +24 -0
  5. package/src/__tests__/components.smoke.test.tsx +78 -0
  6. package/src/__tests__/styles.contract.test.ts +60 -0
  7. package/src/domain/tokens/index.ts +8 -49
  8. package/src/index.ts +8 -10
  9. package/src/infrastructure/constants/component.constants.ts +3 -17
  10. package/src/infrastructure/constants/index.ts +4 -2
  11. package/src/infrastructure/constants/timing.constants.ts +46 -0
  12. package/src/infrastructure/utils/calculation.utils.ts +74 -0
  13. package/src/infrastructure/utils/cn.util.ts +1 -1
  14. package/src/infrastructure/utils/index.ts +12 -0
  15. package/src/presentation/atoms/IconButton.tsx +55 -0
  16. package/src/presentation/atoms/Progress.tsx +2 -1
  17. package/src/presentation/atoms/Spinner.tsx +1 -1
  18. package/src/presentation/atoms/Switch.tsx +2 -2
  19. package/src/presentation/atoms/Text.tsx +15 -13
  20. package/src/presentation/atoms/Tooltip.tsx +2 -1
  21. package/src/presentation/atoms/index.ts +3 -0
  22. package/src/presentation/hooks/index.ts +0 -3
  23. package/src/presentation/hooks/useClickOutside.ts +16 -10
  24. package/src/presentation/hooks/useClipboard.ts +30 -12
  25. package/src/presentation/hooks/useDebounce.ts +9 -11
  26. package/src/presentation/hooks/useLocalStorage.ts +32 -20
  27. package/src/presentation/hooks/useMediaQuery.ts +20 -15
  28. package/src/presentation/hooks/useScrollLock.ts +40 -12
  29. package/src/presentation/hooks/useToggle.ts +8 -6
  30. package/src/presentation/molecules/ActiveFilterTags.tsx +19 -18
  31. package/src/presentation/molecules/Avatar.tsx +1 -1
  32. package/src/presentation/molecules/CodeBlock.tsx +50 -48
  33. package/src/presentation/molecules/InputGroup.tsx +1 -1
  34. package/src/presentation/molecules/ListItem.tsx +40 -29
  35. package/src/presentation/molecules/Select.tsx +1 -1
  36. package/src/presentation/molecules/Toggle.tsx +4 -4
  37. package/src/presentation/molecules/index.ts +0 -3
  38. package/src/presentation/organisms/Alert.tsx +1 -1
  39. package/src/presentation/organisms/DataTable.tsx +26 -15
  40. package/src/presentation/organisms/Dialog.tsx +1 -1
  41. package/src/presentation/organisms/DropdownMenu.tsx +2 -2
  42. package/src/presentation/organisms/HoverCard.tsx +1 -1
  43. package/src/presentation/organisms/LoadingState.tsx +1 -5
  44. package/src/presentation/organisms/Popover.tsx +1 -1
  45. package/src/presentation/organisms/StatCard.tsx +2 -2
  46. package/src/presentation/organisms/Table.tsx +2 -2
  47. package/src/presentation/organisms/index.ts +0 -60
  48. package/src/styles/compile.css +18 -0
  49. package/src/styles/theme.css +131 -0
  50. package/src/domain/tokens/animation.tokens.ts +0 -87
  51. package/src/domain/tokens/color.tokens.ts +0 -95
  52. package/src/domain/tokens/radius.tokens.ts +0 -27
  53. package/src/domain/tokens/shadow.tokens.ts +0 -25
  54. package/src/domain/tokens/spacing.tokens.ts +0 -65
  55. package/src/domain/tokens/typography.tokens.ts +0 -74
  56. package/src/infrastructure/error/ErrorBoundary.tsx +0 -258
  57. package/src/infrastructure/error/ErrorDisplay.tsx +0 -346
  58. package/src/infrastructure/error/SuspenseWrapper.tsx +0 -133
  59. package/src/infrastructure/error/index.ts +0 -5
  60. package/src/infrastructure/performance/index.ts +0 -92
  61. package/src/infrastructure/performance/useLazyLoading.ts +0 -342
  62. package/src/infrastructure/performance/useMemoryOptimization.ts +0 -293
  63. package/src/infrastructure/performance/usePerformanceMonitor.ts +0 -158
  64. package/src/infrastructure/security/index.ts +0 -10
  65. package/src/infrastructure/security/security-config.ts +0 -171
  66. package/src/infrastructure/security/useFormValidation.ts +0 -216
  67. package/src/infrastructure/security/validation.ts +0 -246
  68. package/src/presentation/hooks/useLanguage.ts +0 -109
  69. package/src/presentation/molecules/Breadcrumb.tsx +0 -54
  70. package/src/presentation/organisms/AlertDialog.tsx +0 -181
  71. package/src/presentation/organisms/Calendar.tsx +0 -79
  72. package/src/presentation/organisms/Comments.tsx +0 -103
  73. package/src/presentation/organisms/ConfirmDialog.tsx +0 -97
  74. package/src/presentation/organisms/FilterBar.tsx +0 -244
  75. package/src/presentation/organisms/FilterSidebar.tsx +0 -172
  76. package/src/presentation/organisms/Footer.tsx +0 -86
  77. package/src/presentation/organisms/FormModal.tsx +0 -126
  78. package/src/presentation/organisms/Grid.tsx +0 -128
  79. package/src/presentation/organisms/ImageLightbox.tsx +0 -252
  80. package/src/presentation/organisms/MainNavbar.tsx +0 -320
  81. package/src/presentation/organisms/Modal.tsx +0 -173
  82. package/src/presentation/organisms/Navbar.tsx +0 -75
  83. package/src/presentation/organisms/NewsletterSignup.tsx +0 -165
  84. package/src/presentation/organisms/QuickActionCard.tsx +0 -93
@@ -1,320 +0,0 @@
1
- /**
2
- * MainNavbar Component (Organism)
3
- * @description Full-featured navigation bar with logo, links, theme toggle, language selector, and mobile menu
4
- */
5
-
6
- import { useState, useEffect, useRef, useMemo } from 'react';
7
- // @ts-ignore - react-router-dom is a peer dependency, may not be available during package build
8
- import { Link, useLocation } from 'react-router-dom';
9
- import React from 'react';
10
- import { Show, Hide } from '../atoms';
11
- import type { BaseProps } from '../../domain/types';
12
-
13
- export interface NavItem {
14
- name: string;
15
- path: string;
16
- }
17
-
18
- export interface MainNavbarLanguage {
19
- code: string;
20
- name: string;
21
- flag: string;
22
- }
23
-
24
- export interface MainNavbarProps extends BaseProps {
25
- logo?: React.ReactNode;
26
- appName: string;
27
- navItems: NavItem[];
28
- supportedLanguages: Record<string, MainNavbarLanguage>;
29
- currentLanguage: string;
30
- onLanguageChange: (code: string) => void;
31
- theme: 'light' | 'dark';
32
- onThemeToggle: () => void;
33
- githubUrl?: string;
34
- githubLabel?: string;
35
- translations?: {
36
- language: string;
37
- switchToMode: (mode: string) => string;
38
- lightMode: string;
39
- darkMode: string;
40
- github: string;
41
- };
42
- }
43
-
44
- export const MainNavbar = ({
45
- logo,
46
- appName,
47
- navItems,
48
- supportedLanguages,
49
- currentLanguage,
50
- onLanguageChange,
51
- theme,
52
- onThemeToggle,
53
- githubUrl,
54
- githubLabel = 'GitHub',
55
- translations = {
56
- language: 'Language',
57
- switchToMode: (mode: string) => `Switch to ${mode} mode`,
58
- lightMode: 'Light Mode',
59
- darkMode: 'Dark Mode',
60
- github: 'GitHub',
61
- },
62
- className,
63
- }: MainNavbarProps) => {
64
- const [isOpen, setIsOpen] = useState(false);
65
- const [isLangOpen, setIsLangOpen] = useState(false);
66
- const location = useLocation();
67
- const langDropdownRef = useRef<HTMLDivElement>(null);
68
-
69
- const navItemsMemo = useMemo(() => navItems, [JSON.stringify(navItems)]);
70
-
71
- useEffect(() => {
72
- const handleClickOutside = (event: MouseEvent) => {
73
- if (langDropdownRef.current && !langDropdownRef.current.contains(event.target as Node)) {
74
- setIsLangOpen(false);
75
- }
76
- };
77
-
78
- if (isLangOpen) {
79
- document.addEventListener('mousedown', handleClickOutside);
80
- return () => {
81
- document.removeEventListener('mousedown', handleClickOutside);
82
- };
83
- }
84
- }, [isLangOpen]);
85
-
86
- return (
87
- <nav className={`bg-bg-primary sticky top-0 z-50 border-b border-border transition-theme ${className || ''}`}>
88
- <div className="max-w-7xl mx-auto px-4">
89
- <div className="flex justify-between h-16 items-center">
90
- {/* Logo + App Name */}
91
- <Link to="/" className="flex items-center gap-2 sm:gap-3">
92
- {logo}
93
- <span className="text-xl font-bold text-text-primary">{appName}</span>
94
- </Link>
95
-
96
- {/* Desktop Menu */}
97
- <Show above="lg">
98
- <div className="flex items-center gap-6 lg:gap-8">
99
- {navItemsMemo.map((item) => {
100
- const isActive = location.pathname === item.path;
101
- return (
102
- <Link
103
- key={item.path}
104
- to={item.path}
105
- className={`font-medium transition-colors transition-theme whitespace-nowrap ${
106
- isActive ? 'text-primary-light' : 'text-text-secondary hover:text-primary-light'
107
- }`}
108
- >
109
- {item.name}
110
- </Link>
111
- );
112
- })}
113
- </div>
114
- </Show>
115
-
116
- {/* Actions */}
117
- <div className="flex items-center gap-2 md:gap-3">
118
- {/* Language Selector */}
119
- <Show above="lg">
120
- <div className="relative" ref={langDropdownRef}>
121
- <button
122
- onClick={() => setIsLangOpen(!isLangOpen)}
123
- className="p-2 rounded-lg bg-bg-secondary text-text-secondary hover:text-primary-light border border-border hover:border-primary-light transition-all transition-theme"
124
- title={translations.language}
125
- type="button"
126
- >
127
- <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
128
- <path d="m5 8 6 6" />
129
- <path d="m4 14 6-6 2-3" />
130
- <path d="M2 5h12" />
131
- <path d="M7 2h1" />
132
- <path d="m22 22-5-10-5 10" />
133
- <path d="M14 18h6" />
134
- </svg>
135
- </button>
136
-
137
- {isLangOpen && (
138
- <div className="absolute right-0 top-full mt-2 w-48 bg-bg-card rounded-lg border border-border shadow-xl z-50">
139
- {Object.entries(supportedLanguages).map(([code, { name, flag }]) => (
140
- <button
141
- key={code}
142
- onClick={() => {
143
- onLanguageChange(code);
144
- setIsLangOpen(false);
145
- }}
146
- className={`w-full flex items-center gap-3 px-4 py-3 text-left hover:bg-bg-tertiary transition-colors transition-theme ${
147
- currentLanguage === code ? 'bg-bg-tertiary text-primary-light' : 'text-text-secondary'
148
- }`}
149
- type="button"
150
- >
151
- <span className="text-xl">{flag}</span>
152
- <span className="text-sm">{name}</span>
153
- {currentLanguage === code && (
154
- <span className="ml-auto text-primary-light">✓</span>
155
- )}
156
- </button>
157
- ))}
158
- </div>
159
- )}
160
- </div>
161
- </Show>
162
-
163
- {/* Theme Toggle */}
164
- <button
165
- onClick={onThemeToggle}
166
- className="p-2 rounded-lg bg-bg-secondary text-text-secondary hover:text-primary-light border border-border hover:border-primary-light transition-all transition-theme"
167
- title={translations.switchToMode(theme === 'dark' ? 'light' : 'dark')}
168
- aria-label={`Switch to ${theme === 'dark' ? 'light' : 'dark'} mode`}
169
- type="button"
170
- >
171
- {theme === 'dark' ? (
172
- <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
173
- <circle cx="12" cy="12" r="4" />
174
- <path d="M12 2v2" />
175
- <path d="M12 20v2" />
176
- <path d="m4.93 4.93 1.41 1.41" />
177
- <path d="m17.66 17.66 1.41 1.41" />
178
- <path d="M2 12h2" />
179
- <path d="M20 12h2" />
180
- <path d="m6.34 17.66-1.41 1.41" />
181
- <path d="m19.07 4.93-1.41 1.41" />
182
- </svg>
183
- ) : (
184
- <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
185
- <path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z" />
186
- </svg>
187
- )}
188
- </button>
189
-
190
- {/* GitHub */}
191
- <Show above="lg">
192
- {githubUrl && (
193
- <a
194
- href={githubUrl}
195
- target="_blank"
196
- rel="noopener noreferrer"
197
- className="flex items-center gap-2 px-4 py-2 bg-bg-secondary text-text-secondary rounded-lg border border-border hover:border-primary-light hover:text-text-primary transition-all transition-theme"
198
- >
199
- <svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor">
200
- <path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/>
201
- </svg>
202
- <span className="font-medium">{githubLabel}</span>
203
- </a>
204
- )}
205
- </Show>
206
-
207
- {/* Mobile Button */}
208
- <Hide above="lg">
209
- <button
210
- onClick={() => setIsOpen(!isOpen)}
211
- className="text-text-secondary flex"
212
- type="button"
213
- aria-label="Toggle menu"
214
- >
215
- {isOpen ? (
216
- <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
217
- <path d="M18 6L6 18M6 6l12 12" />
218
- </svg>
219
- ) : (
220
- <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
221
- <line x1="4" x2="20" y1="12" y2="12" />
222
- <line x1="4" x2="20" y1="6" y2="6" />
223
- <line x1="4" x2="20" y1="18" y2="18" />
224
- </svg>
225
- )}
226
- </button>
227
- </Hide>
228
- </div>
229
- </div>
230
- </div>
231
-
232
- {/* Mobile Menu */}
233
- <Hide above="lg">
234
- {isOpen && (
235
- <div className="bg-bg-secondary border-t border-border transition-theme">
236
- <div className="px-4 py-4 space-y-2">
237
- {/* Theme Toggle Mobile */}
238
- <button
239
- onClick={onThemeToggle}
240
- className="w-full flex items-center gap-3 px-4 py-3 rounded-lg text-text-secondary hover:bg-bg-tertiary transition-all transition-theme"
241
- type="button"
242
- >
243
- {theme === 'dark' ? (
244
- <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
245
- <circle cx="12" cy="12" r="4" />
246
- <path d="M12 2v2" />
247
- <path d="M12 20v2" />
248
- <path d="m4.93 4.93 1.41 1.41" />
249
- <path d="m17.66 17.66 1.41 1.41" />
250
- <path d="M2 12h2" />
251
- <path d="M20 12h2" />
252
- <path d="m6.34 17.66-1.41 1.41" />
253
- <path d="m19.07 4.93-1.41 1.41" />
254
- </svg>
255
- ) : (
256
- <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
257
- <path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z" />
258
- </svg>
259
- )}
260
- <span>{theme === 'dark' ? translations.lightMode : translations.darkMode}</span>
261
- </button>
262
-
263
- {/* Language Selector Mobile */}
264
- <div className="px-4 py-2">
265
- <div className="text-text-secondary text-sm mb-2">{translations.language}</div>
266
- <div className="grid grid-cols-2 gap-2">
267
- {Object.entries(supportedLanguages).map(([code, { name, flag }]) => (
268
- <button
269
- key={code}
270
- onClick={() => onLanguageChange(code)}
271
- className={`flex items-center gap-2 px-3 py-2 rounded-lg text-sm transition-all transition-theme ${
272
- currentLanguage === code
273
- ? 'bg-primary-light text-text-primary'
274
- : 'bg-bg-primary text-text-secondary hover:bg-bg-tertiary'
275
- }`}
276
- type="button"
277
- >
278
- <span>{flag}</span>
279
- <span>{name}</span>
280
- </button>
281
- ))}
282
- </div>
283
- </div>
284
-
285
- {navItemsMemo.map((item) => {
286
- const isActive = location.pathname === item.path;
287
- return (
288
- <Link
289
- key={item.path}
290
- to={item.path}
291
- className={`block px-4 py-2 rounded-lg font-medium transition-theme ${
292
- isActive ? 'text-primary-light bg-bg-tertiary' : 'text-text-secondary hover:bg-bg-tertiary'
293
- }`}
294
- onClick={() => setIsOpen(false)}
295
- >
296
- {item.name}
297
- </Link>
298
- );
299
- })}
300
-
301
- {githubUrl && (
302
- <a
303
- href={githubUrl}
304
- target="_blank"
305
- rel="noopener noreferrer"
306
- className="flex items-center gap-2 px-4 py-2 text-text-secondary hover:text-text-primary rounded-lg hover:bg-bg-tertiary transition-all transition-theme"
307
- >
308
- <svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor">
309
- <path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/>
310
- </svg>
311
- <span>{githubLabel}</span>
312
- </a>
313
- )}
314
- </div>
315
- </div>
316
- )}
317
- </Hide>
318
- </nav>
319
- );
320
- };
@@ -1,173 +0,0 @@
1
- /**
2
- * Modal Component (Organism)
3
- * @description Dialog/overlay container with optimized transitions
4
- */
5
-
6
- import { forwardRef, type HTMLAttributes, useEffect, useState, useRef } from 'react';
7
- import React from 'react';
8
- import { cn } from '../../infrastructure/utils';
9
- import type { BaseProps } from '../../domain/types';
10
-
11
- export interface ModalProps extends HTMLAttributes<HTMLDivElement>, BaseProps {
12
- open?: boolean;
13
- onClose?: () => void;
14
- showCloseButton?: boolean;
15
- size?: 'sm' | 'md' | 'lg' | 'xl' | 'full';
16
- }
17
-
18
- const sizeStyles: Record<'sm' | 'md' | 'lg' | 'xl' | 'full', string> = {
19
- sm: 'max-w-sm',
20
- md: 'max-w-md',
21
- lg: 'max-w-lg',
22
- xl: 'max-w-xl',
23
- full: 'max-w-full mx-4',
24
- };
25
-
26
- export const Modal = React.memo(forwardRef<HTMLDivElement, ModalProps>(
27
- ({ open = false, onClose, showCloseButton = true, size = 'md', className, children, ...props }, ref) => {
28
- const [shouldRender, setShouldRender] = useState(open);
29
- const [isAnimating, setIsAnimating] = useState(false);
30
- const rafRef = useRef<number | null>(null);
31
- const timerRef = useRef<number | null>(null);
32
-
33
- useEffect(() => {
34
- if (open) {
35
- setShouldRender(true);
36
- // FIX: Store rafId and cleanup properly
37
- rafRef.current = requestAnimationFrame(() => {
38
- setIsAnimating(true);
39
- });
40
- } else {
41
- setIsAnimating(false);
42
- // Wait for animation to complete before unmounting
43
- timerRef.current = window.setTimeout(() => {
44
- setShouldRender(false);
45
- }, 200); // Match animation duration
46
- }
47
-
48
- // FIX: Proper cleanup for both animation frame and timeout
49
- return () => {
50
- if (rafRef.current) {
51
- cancelAnimationFrame(rafRef.current);
52
- }
53
- if (timerRef.current) {
54
- clearTimeout(timerRef.current);
55
- }
56
- };
57
- }, [open]);
58
-
59
- if (!shouldRender) return null;
60
-
61
- const handleBackdropClick = (e: React.MouseEvent) => {
62
- if (e.target === e.currentTarget && onClose) {
63
- onClose();
64
- }
65
- };
66
-
67
- return (
68
- <div
69
- className="fixed inset-0 z-50 flex items-center justify-center"
70
- {...props}
71
- >
72
- {/* Backdrop */}
73
- <div
74
- className={`fixed inset-0 bg-background/80 backdrop-blur-sm transition-opacity duration-200 ${
75
- isAnimating ? 'opacity-100' : 'opacity-0'
76
- }`}
77
- onClick={handleBackdropClick}
78
- aria-hidden="true"
79
- />
80
-
81
- {/* Modal */}
82
- <div
83
- ref={ref}
84
- className={cn(
85
- 'relative z-50 w-full rounded-lg border bg-card p-6 shadow-lg',
86
- 'transition-all duration-200',
87
- isAnimating ? 'opacity-100 scale-100' : 'opacity-0 scale-95',
88
- sizeStyles[size],
89
- className
90
- )}
91
- role="dialog"
92
- aria-modal="true"
93
- >
94
- {showCloseButton && (
95
- <button
96
- onClick={onClose}
97
- className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2"
98
- aria-label="Close modal"
99
- >
100
- <svg className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
101
- <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
102
- </svg>
103
- <span className="sr-only">Close</span>
104
- </button>
105
- )}
106
-
107
- {children}
108
- </div>
109
- </div>
110
- );
111
- }
112
- ));
113
-
114
- Modal.displayName = 'Modal';
115
-
116
- export const ModalHeader = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
117
- ({ className, ...props }, ref) => (
118
- <div
119
- ref={ref}
120
- className={cn('flex flex-col space-y-1.5 text-center sm:text-left', className)}
121
- {...props}
122
- />
123
- )
124
- );
125
-
126
- ModalHeader.displayName = 'ModalHeader';
127
-
128
- export const ModalTitle = forwardRef<HTMLHeadingElement, HTMLAttributes<HTMLHeadingElement>>(
129
- ({ className, ...props }, ref) => (
130
- <h2
131
- ref={ref}
132
- className={cn('text-lg font-semibold leading-none tracking-tight', className)}
133
- {...props}
134
- />
135
- )
136
- );
137
-
138
- ModalTitle.displayName = 'ModalTitle';
139
-
140
- export const ModalDescription = forwardRef<HTMLParagraphElement, HTMLAttributes<HTMLParagraphElement>>(
141
- ({ className, ...props }, ref) => (
142
- <p
143
- ref={ref}
144
- className={cn('text-sm text-muted-foreground', className)}
145
- {...props}
146
- />
147
- )
148
- );
149
-
150
- ModalDescription.displayName = 'ModalDescription';
151
-
152
- export const ModalContent = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
153
- ({ className, ...props }, ref) => (
154
- <div ref={ref} className={cn('mt-4', className)} {...props} />
155
- )
156
- );
157
-
158
- ModalContent.displayName = 'ModalContent';
159
-
160
- export const ModalFooter = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
161
- ({ className, ...props }, ref) => (
162
- <div
163
- ref={ref}
164
- className={cn(
165
- 'mt-4 flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2',
166
- className
167
- )}
168
- {...props}
169
- />
170
- )
171
- );
172
-
173
- ModalFooter.displayName = 'ModalFooter';
@@ -1,75 +0,0 @@
1
- /**
2
- * Navbar Component (Organism)
3
- * @description Navigation bar with logo and links - optimized for performance
4
- */
5
-
6
- import { forwardRef, type HTMLAttributes } from 'react';
7
- import React from 'react';
8
- import { cn } from '../../infrastructure/utils';
9
- import type { BaseProps } from '../../domain/types';
10
-
11
- export interface NavbarProps extends HTMLAttributes<HTMLElement>, BaseProps {
12
- variant?: 'default' | 'sticky' | 'fixed';
13
- }
14
-
15
- const variantStyles: Record<'default' | 'sticky' | 'fixed', string> = {
16
- default: 'relative',
17
- sticky: 'sticky top-0 z-50',
18
- fixed: 'fixed top-0 left-0 right-0 z-50',
19
- };
20
-
21
- export const Navbar = React.memo(forwardRef<HTMLElement, NavbarProps>(
22
- ({ className, variant = 'default', children, ...props }, ref) => {
23
- return (
24
- <nav
25
- ref={ref}
26
- className={cn(
27
- 'flex h-16 items-center gap-4 border-b border-border/40 bg-background/95 px-4 backdrop-blur supports-[backdrop-filter]:bg-background/60',
28
- variantStyles[variant],
29
- className
30
- )}
31
- {...props}
32
- >
33
- {children}
34
- </nav>
35
- );
36
- }
37
- ));
38
-
39
- Navbar.displayName = 'Navbar';
40
-
41
- export const NavbarBrand = React.memo(forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
42
- ({ className, ...props }, ref) => (
43
- <div
44
- ref={ref}
45
- className={cn('flex items-center gap-2 font-bold text-lg', className)}
46
- {...props}
47
- />
48
- )
49
- ));
50
-
51
- NavbarBrand.displayName = 'NavbarBrand';
52
-
53
- export const NavbarLinks = React.memo(forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
54
- ({ className, ...props }, ref) => (
55
- <div
56
- ref={ref}
57
- className={cn('hidden items-center gap-6 md:flex', className)}
58
- {...props}
59
- />
60
- )
61
- ));
62
-
63
- NavbarLinks.displayName = 'NavbarLinks';
64
-
65
- export const NavbarActions = React.memo(forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
66
- ({ className, ...props }, ref) => (
67
- <div
68
- ref={ref}
69
- className={cn('ml-auto flex items-center gap-2', className)}
70
- {...props}
71
- />
72
- )
73
- ));
74
-
75
- NavbarActions.displayName = 'NavbarActions';