@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,126 +0,0 @@
1
- /**
2
- * FormModal Component (Template)
3
- * @description Pre-configured modal with form wrapper and loading state
4
- * Reduces boilerplate in modal components like CampaignModal, ProjectListModal
5
- */
6
-
7
- import { forwardRef, type ReactNode } from 'react';
8
- import { cn } from '../../infrastructure/utils';
9
- import {
10
- Dialog,
11
- DialogContent,
12
- DialogDescription,
13
- DialogFooter,
14
- DialogHeader,
15
- DialogTitle,
16
- } from './Dialog';
17
- import { Button } from '../atoms';
18
- import { Loader2 } from 'lucide-react';
19
- import type { BaseProps } from '../../domain/types';
20
-
21
- export interface FormModalProps extends BaseProps {
22
- open: boolean;
23
- onOpenChange: (open: boolean) => void;
24
- title: string;
25
- description?: string;
26
- children: ReactNode;
27
- onSubmit?: (e: React.FormEvent) => void | Promise<void>;
28
- loading?: boolean;
29
- submitText?: string;
30
- cancelText?: string;
31
- showFooter?: boolean;
32
- footer?: ReactNode;
33
- size?: 'sm' | 'md' | 'lg' | 'xl' | 'full';
34
- preventCloseOnSubmit?: boolean;
35
- }
36
-
37
- const sizeStyles = {
38
- sm: 'sm:max-w-[400px]',
39
- md: 'sm:max-w-[425px]',
40
- lg: 'sm:max-w-[600px]',
41
- xl: 'sm:max-w-[800px]',
42
- full: 'sm:max-w-[95vw]',
43
- };
44
-
45
- export const FormModal = forwardRef<HTMLDivElement, FormModalProps>(
46
- (
47
- {
48
- className,
49
- open,
50
- onOpenChange,
51
- title,
52
- description,
53
- children,
54
- onSubmit,
55
- loading = false,
56
- submitText = 'Submit',
57
- cancelText = 'Cancel',
58
- showFooter = true,
59
- footer,
60
- size = 'md',
61
- preventCloseOnSubmit = false,
62
- ...props
63
- },
64
- ref
65
- ) => {
66
- const handleSubmit = async (e: React.FormEvent) => {
67
- e.preventDefault();
68
- if (onSubmit && !loading) {
69
- await onSubmit(e);
70
- if (!preventCloseOnSubmit) {
71
- onOpenChange(false);
72
- }
73
- }
74
- };
75
-
76
- return (
77
- <Dialog open={open} onOpenChange={onOpenChange}>
78
- <DialogContent className={cn(sizeStyles[size], className)} ref={ref} {...props}>
79
- {onSubmit ? (
80
- <form onSubmit={handleSubmit}>
81
- <DialogHeader>
82
- <DialogTitle>{title}</DialogTitle>
83
- {description && <DialogDescription>{description}</DialogDescription>}
84
- </DialogHeader>
85
- <div className="grid gap-4 py-4">{children}</div>
86
- {showFooter && (
87
- <DialogFooter>
88
- {footer || (
89
- <>
90
- <Button type="button" variant="outline" onClick={() => onOpenChange(false)} disabled={loading}>
91
- {cancelText}
92
- </Button>
93
- <Button type="submit" disabled={loading}>
94
- {loading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
95
- {submitText}
96
- </Button>
97
- </>
98
- )}
99
- </DialogFooter>
100
- )}
101
- </form>
102
- ) : (
103
- <>
104
- <DialogHeader>
105
- <DialogTitle>{title}</DialogTitle>
106
- {description && <DialogDescription>{description}</DialogDescription>}
107
- </DialogHeader>
108
- <div className="grid gap-4 py-4">{children}</div>
109
- {showFooter && (
110
- <DialogFooter>
111
- {footer || (
112
- <Button variant="outline" onClick={() => onOpenChange(false)} disabled={loading}>
113
- {cancelText}
114
- </Button>
115
- )}
116
- </DialogFooter>
117
- )}
118
- </>
119
- )}
120
- </DialogContent>
121
- </Dialog>
122
- );
123
- }
124
- );
125
-
126
- FormModal.displayName = 'FormModal';
@@ -1,128 +0,0 @@
1
- /**
2
- * Grid Component (Organism)
3
- * @description Responsive grid system with configurable columns
4
- */
5
-
6
- import { cn } from '../../infrastructure/utils';
7
- import type { BaseProps } from '../../domain/types';
8
-
9
- export interface GridProps extends BaseProps {
10
- children: React.ReactNode;
11
- cols?: {
12
- xs?: number;
13
- sm?: number;
14
- md?: number;
15
- lg?: number;
16
- xl?: number;
17
- '2xl'?: number;
18
- };
19
- gap?: number | string;
20
- className?: string;
21
- }
22
-
23
- /**
24
- * Responsive grid system that adjusts columns based on breakpoint
25
- * @param cols - Number of columns per breakpoint
26
- * @param gap - Gap between items (spacing unit or custom value)
27
- * @param children - Grid items
28
- *
29
- * @example
30
- * ```tsx
31
- * // Auto responsive: 1 col mobile, 2 tablet, 3 desktop
32
- * <Grid cols={{ xs: 1, sm: 2, lg: 3 }}>
33
- * <div>Item 1</div>
34
- * <div>Item 2</div>
35
- * </Grid>
36
- *
37
- * // Fixed 4 columns
38
- * <Grid cols={{ lg: 4 }}>
39
- * {items.map(item => <Card key={item.id}>{item}</Card>)}
40
- * </Grid>
41
- * ```
42
- */
43
- export const Grid = ({ children, cols = {}, gap = 4, className }: GridProps) => {
44
- const {
45
- xs = 1,
46
- sm = xs,
47
- md = sm,
48
- lg = md,
49
- xl = lg,
50
- '2xl': xl2 = xl
51
- } = cols;
52
-
53
- const gridCols = cn(
54
- 'grid',
55
- `grid-cols-${xs}`, // xs always applies (mobile-first)
56
- sm !== xs && `sm:grid-cols-${sm}`,
57
- md !== sm && `md:grid-cols-${md}`,
58
- lg !== md && `lg:grid-cols-${lg}`,
59
- xl !== lg && `xl:grid-cols-${xl}`,
60
- xl2 !== xl && `2xl:grid-cols-${xl2}`
61
- );
62
-
63
- const gapClass = typeof gap === 'number' ? `gap-${gap}` : gap;
64
-
65
- return (
66
- <div className={cn(gridCols, gapClass, className)}>
67
- {children}
68
- </div>
69
- );
70
- };
71
-
72
- Grid.displayName = 'Grid';
73
-
74
- /**
75
- * Grid Item component for custom column spans
76
- */
77
- export interface GridItemProps extends BaseProps {
78
- children: React.ReactNode;
79
- span?: {
80
- xs?: number;
81
- sm?: number;
82
- md?: number;
83
- lg?: number;
84
- xl?: number;
85
- '2xl'?: number;
86
- };
87
- className?: string;
88
- }
89
-
90
- /**
91
- * Grid item with custom column span per breakpoint
92
- * @param span - Number of columns to span (1-12)
93
- *
94
- * @example
95
- * ```tsx
96
- * <Grid cols={{ lg: 4 }}>
97
- * <GridItem span={{ lg: 2 }}>Spans 2 columns</GridItem>
98
- * <GridItem>Regular 1 column</GridItem>
99
- * </Grid>
100
- * ```
101
- */
102
- export const GridItem = ({ children, span = {}, className }: GridItemProps) => {
103
- const {
104
- xs = 1,
105
- sm = xs,
106
- md = sm,
107
- lg = md,
108
- xl = lg,
109
- '2xl': xl2 = xl
110
- } = span;
111
-
112
- const colSpan = cn(
113
- 'col-span-1',
114
- `sm:col-span-${sm}`,
115
- `md:col-span-${md}`,
116
- `lg:col-span-${lg}`,
117
- `xl:col-span-${xl}`,
118
- `2xl:col-span-${xl2}`
119
- );
120
-
121
- return (
122
- <div className={cn(colSpan, className)}>
123
- {children}
124
- </div>
125
- );
126
- };
127
-
128
- GridItem.displayName = 'GridItem';
@@ -1,252 +0,0 @@
1
- /**
2
- * ImageLightbox Component (Organism)
3
- * @description Full-screen image gallery with zoom, navigation, and thumbnails
4
- */
5
-
6
- import { useState, useCallback, useEffect } from 'react';
7
- import { cn } from '../../infrastructure/utils';
8
- import type { BaseProps } from '../../domain/types';
9
- import { Icon } from '../atoms/Icon';
10
-
11
- export interface ImageLightboxImage {
12
- src: string;
13
- alt: string;
14
- title?: string;
15
- }
16
-
17
- export interface ImageLightboxProps extends BaseProps {
18
- images: ImageLightboxImage[];
19
- initialIndex?: number;
20
- isOpen: boolean;
21
- onClose: () => void;
22
- }
23
-
24
- export const ImageLightbox = ({
25
- images,
26
- initialIndex = 0,
27
- isOpen,
28
- onClose,
29
- }: ImageLightboxProps) => {
30
- // Early return if closed
31
- if (!isOpen) return null;
32
-
33
- // Early return if no images
34
- if (!images || images.length === 0) {
35
- return null;
36
- }
37
-
38
- const [currentIndex, setCurrentIndex] = useState(initialIndex);
39
- const [isZoomed, setIsZoomed] = useState(false);
40
-
41
- // Ensure initialIndex is within bounds
42
- const safeIndex = Math.min(Math.max(initialIndex, 0), images.length - 1);
43
- const currentImage = images[safeIndex];
44
-
45
- // Use useCallback to memoize navigation functions
46
- const goToNext = useCallback(() => {
47
- setCurrentIndex((prev) => (prev + 1) % images.length);
48
- }, [images.length]);
49
-
50
- const goToPrevious = useCallback(() => {
51
- setCurrentIndex((prev) => (prev - 1 + images.length) % images.length);
52
- }, [images.length]);
53
-
54
- const handleImageClick = useCallback(() => {
55
- setIsZoomed(!isZoomed);
56
- }, [isZoomed]);
57
-
58
- // Handle keyboard navigation
59
- useEffect(() => {
60
- const handleKeyDown = (e: KeyboardEvent) => {
61
- switch (e.key) {
62
- case 'Escape':
63
- onClose();
64
- break;
65
- case 'ArrowLeft':
66
- goToPrevious();
67
- break;
68
- case 'ArrowRight':
69
- goToNext();
70
- break;
71
- case '+':
72
- case '=':
73
- setIsZoomed(!isZoomed);
74
- break;
75
- }
76
- };
77
-
78
- window.addEventListener('keydown', handleKeyDown);
79
- return () => window.removeEventListener('keydown', handleKeyDown);
80
- }, [onClose, goToPrevious, goToNext, isZoomed]);
81
-
82
- // Prevent body scroll when lightbox is open
83
- useEffect(() => {
84
- const originalOverflow = document.body.style.overflow;
85
- document.body.style.overflow = 'hidden';
86
-
87
- return () => {
88
- document.body.style.overflow = originalOverflow;
89
- };
90
- }, []);
91
-
92
- if (!currentImage) return null;
93
-
94
- return (
95
- <div
96
- className="fixed inset-0 z-50 bg-black/90 flex items-center justify-center p-4"
97
- onClick={onClose}
98
- >
99
- {/* Close button */}
100
- <button
101
- onClick={onClose}
102
- className="absolute top-4 right-4 p-2 bg-black/50 hover:bg-black/70 rounded-full text-white transition-all"
103
- aria-label="Close lightbox"
104
- >
105
- <Icon className="text-white">
106
- <path
107
- strokeLinecap="round"
108
- strokeLinejoin="round"
109
- strokeWidth={2}
110
- d="M6 18L18 6M6 6l12 12"
111
- />
112
- </Icon>
113
- </button>
114
-
115
- {/* Navigation - Previous */}
116
- {images.length > 1 && (
117
- <button
118
- onClick={(e) => {
119
- e.stopPropagation();
120
- goToPrevious();
121
- }}
122
- className="absolute left-4 p-2 bg-black/50 hover:bg-black/70 rounded-full text-white transition-all"
123
- aria-label="Previous image"
124
- >
125
- <Icon className="text-white" size="lg">
126
- <path
127
- strokeLinecap="round"
128
- strokeLinejoin="round"
129
- strokeWidth={2}
130
- d="M15 19l-7-7 7-7"
131
- />
132
- </Icon>
133
- </button>
134
- )}
135
-
136
- {/* Navigation - Next */}
137
- {images.length > 1 && (
138
- <button
139
- onClick={(e) => {
140
- e.stopPropagation();
141
- goToNext();
142
- }}
143
- className="absolute right-4 p-2 bg-black/50 hover:bg-black/70 rounded-full text-white transition-all"
144
- aria-label="Next image"
145
- >
146
- <Icon className="text-white" size="lg">
147
- <path
148
- strokeLinecap="round"
149
- strokeLinejoin="round"
150
- strokeWidth={2}
151
- d="M9 5l7 7-7 7"
152
- />
153
- </Icon>
154
- </button>
155
- )}
156
-
157
- {/* Main Image */}
158
- <div
159
- className="relative max-w-5xl max-h-[90vh] w-full"
160
- onClick={handleImageClick}
161
- >
162
- <img
163
- src={currentImage.src}
164
- alt={currentImage.alt}
165
- className={cn(
166
- 'w-full h-full object-contain',
167
- isZoomed ? 'cursor-zoom-out' : 'cursor-zoom-in'
168
- )}
169
- style={{
170
- maxHeight: '90vh',
171
- objectFit: 'contain',
172
- }}
173
- />
174
-
175
- {/* Zoom indicator */}
176
- <div className="absolute bottom-4 right-4 flex items-center gap-2 bg-black/50 px-3 py-2 rounded-full">
177
- <button
178
- onClick={(e) => {
179
- e.stopPropagation();
180
- setIsZoomed(!isZoomed);
181
- }}
182
- className="p-1 hover:bg-black/70 rounded-full transition-all text-white"
183
- aria-label={isZoomed ? 'Zoom out' : 'Zoom in'}
184
- >
185
- {isZoomed ? (
186
- <Icon className="text-white" size="sm">
187
- <path
188
- strokeLinecap="round"
189
- strokeLinejoin="round"
190
- strokeWidth={2}
191
- d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0zM13 10H7"
192
- />
193
- </Icon>
194
- ) : (
195
- <Icon className="text-white" size="sm">
196
- <path
197
- strokeLinecap="round"
198
- strokeLinejoin="round"
199
- strokeWidth={2}
200
- d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0zM10 7v6m3-3H7"
201
- />
202
- </Icon>
203
- )}
204
- </button>
205
- </div>
206
- </div>
207
-
208
- {/* Image Counter */}
209
- {images.length > 1 && (
210
- <div className="absolute bottom-4 left-4 bg-black/50 px-4 py-2 rounded-full text-white text-sm">
211
- {currentIndex + 1} / {images.length}
212
- </div>
213
- )}
214
-
215
- {/* Image Title */}
216
- {currentImage.title && (
217
- <div className="absolute bottom-4 left-1/2 -translate-x-1/2 bg-black/50 px-4 py-2 rounded-full text-white text-sm max-w-md text-center">
218
- {currentImage.title}
219
- </div>
220
- )}
221
-
222
- {/* Thumbnails */}
223
- {images.length > 1 && (
224
- <div className="absolute bottom-20 left-1/2 -translate-x-1/2 flex gap-2 max-w-xl overflow-x-auto">
225
- {images.map((image, index) => (
226
- <button
227
- key={index}
228
- onClick={(e) => {
229
- e.stopPropagation();
230
- setCurrentIndex(index);
231
- }}
232
- className={cn(
233
- 'flex-shrink-0 w-16 h-16 rounded-lg overflow-hidden border-2 transition-all',
234
- index === currentIndex
235
- ? 'border-white scale-110'
236
- : 'border-transparent opacity-50 hover:opacity-100'
237
- )}
238
- >
239
- <img
240
- src={image.src}
241
- alt={image.alt}
242
- className="w-full h-full object-cover"
243
- />
244
- </button>
245
- ))}
246
- </div>
247
- )}
248
- </div>
249
- );
250
- };
251
-
252
- ImageLightbox.displayName = 'ImageLightbox';