@umituz/web-design-system 2.0.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +4 -1
- package/src/index.ts +63 -3
- package/src/infrastructure/error/SuspenseWrapper.tsx +2 -3
- package/src/presentation/atoms/Text.tsx +2 -0
- package/src/presentation/atoms/Tooltip.tsx +1 -1
- package/src/presentation/atoms/index.ts +0 -2
- package/src/presentation/molecules/ListItem.tsx +8 -4
- package/src/presentation/molecules/index.ts +0 -1
- package/src/presentation/organisms/Calendar.tsx +6 -2
- package/src/presentation/organisms/DataTable.tsx +2 -1
- package/src/presentation/organisms/ImageLightbox.tsx +253 -0
- package/src/presentation/organisms/Table.tsx +1 -1
- package/src/presentation/organisms/index.ts +70 -100
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/web-design-system",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Web Design System - Atomic Design components (Atoms, Molecules, Organisms, Templates) for React applications",
|
|
6
6
|
"main": "./src/index.ts",
|
|
@@ -77,6 +77,7 @@
|
|
|
77
77
|
"react-day-picker": ">=9.0.0",
|
|
78
78
|
"react-dom": ">=19.0.0",
|
|
79
79
|
"react-i18next": ">=13.0.0",
|
|
80
|
+
"react-syntax-highlighter": ">=15.0.0",
|
|
80
81
|
"tailwind-merge": ">=2.0.0"
|
|
81
82
|
},
|
|
82
83
|
"devDependencies": {
|
|
@@ -99,6 +100,7 @@
|
|
|
99
100
|
"@radix-ui/react-tooltip": "^1.2.7",
|
|
100
101
|
"@types/react": "^19.0.0",
|
|
101
102
|
"@types/react-dom": "^19.0.0",
|
|
103
|
+
"@types/react-syntax-highlighter": "^15.5.13",
|
|
102
104
|
"class-variance-authority": "^0.7.1",
|
|
103
105
|
"clsx": "^2.1.1",
|
|
104
106
|
"lucide-react": "^0.577.0",
|
|
@@ -106,6 +108,7 @@
|
|
|
106
108
|
"react-day-picker": "^9.14.0",
|
|
107
109
|
"react-dom": "^19.0.0",
|
|
108
110
|
"react-i18next": "^13.0.0",
|
|
111
|
+
"react-syntax-highlighter": "^16.1.1",
|
|
109
112
|
"tailwind-merge": "^3.5.0",
|
|
110
113
|
"typescript": "~5.9.2"
|
|
111
114
|
},
|
package/src/index.ts
CHANGED
|
@@ -24,8 +24,68 @@ export * from './infrastructure/constants';
|
|
|
24
24
|
export * from './infrastructure/security';
|
|
25
25
|
export * from './infrastructure/performance';
|
|
26
26
|
export * from './infrastructure/error';
|
|
27
|
-
export * from './presentation/atoms';
|
|
28
|
-
export * from './presentation/molecules';
|
|
29
|
-
export * from './presentation/organisms';
|
|
30
27
|
export * from './presentation/templates';
|
|
31
28
|
export * from './presentation/hooks';
|
|
29
|
+
|
|
30
|
+
// Atoms
|
|
31
|
+
export type { ButtonProps } from './presentation/atoms/Button';
|
|
32
|
+
export { Button } from './presentation/atoms/Button';
|
|
33
|
+
export type { BadgeProps } from './presentation/atoms/Badge';
|
|
34
|
+
export { Badge } from './presentation/atoms/Badge';
|
|
35
|
+
export type { InputProps } from './presentation/atoms/Input';
|
|
36
|
+
export { Input } from './presentation/atoms/Input';
|
|
37
|
+
export type { TextProps, TextElement, TextVariant, TextSize } from './presentation/atoms/Text';
|
|
38
|
+
export { Text } from './presentation/atoms/Text';
|
|
39
|
+
export type { IconProps } from './presentation/atoms/Icon';
|
|
40
|
+
export { Icon } from './presentation/atoms/Icon';
|
|
41
|
+
export type { SpinnerProps } from './presentation/atoms/Spinner';
|
|
42
|
+
export { Spinner } from './presentation/atoms/Spinner';
|
|
43
|
+
export type { CheckboxProps } from './presentation/atoms/Checkbox';
|
|
44
|
+
export { Checkbox } from './presentation/atoms/Checkbox';
|
|
45
|
+
export type { RadioProps } from './presentation/atoms/Radio';
|
|
46
|
+
export { Radio } from './presentation/atoms/Radio';
|
|
47
|
+
export type { SliderProps } from './presentation/atoms/Slider';
|
|
48
|
+
export { Slider } from './presentation/atoms/Slider';
|
|
49
|
+
export type { DividerProps } from './presentation/atoms/Divider';
|
|
50
|
+
export { Divider } from './presentation/atoms/Divider';
|
|
51
|
+
export type { SkeletonProps } from './presentation/atoms/Skeleton';
|
|
52
|
+
export { Skeleton } from './presentation/atoms/Skeleton';
|
|
53
|
+
export type { LinkProps } from './presentation/atoms/Link';
|
|
54
|
+
export { Link } from './presentation/atoms/Link';
|
|
55
|
+
export type { TooltipProps } from './presentation/atoms/Tooltip';
|
|
56
|
+
export { Tooltip } from './presentation/atoms/Tooltip';
|
|
57
|
+
export type { ProgressProps } from './presentation/atoms/Progress';
|
|
58
|
+
export { Progress } from './presentation/atoms/Progress';
|
|
59
|
+
export { Label } from './presentation/atoms/Label';
|
|
60
|
+
export type { AspectRatioProps } from './presentation/atoms/AspectRatio';
|
|
61
|
+
export { AspectRatio } from './presentation/atoms/AspectRatio';
|
|
62
|
+
export type { SwitchProps } from './presentation/atoms/Switch';
|
|
63
|
+
export { Switch } from './presentation/atoms/Switch';
|
|
64
|
+
export { Separator } from './presentation/atoms/Separator';
|
|
65
|
+
export { Toggle as ToggleAtom, toggleVariants } from './presentation/atoms/Toggle';
|
|
66
|
+
|
|
67
|
+
// Molecules
|
|
68
|
+
export type { FormFieldProps } from './presentation/molecules/FormField';
|
|
69
|
+
export { FormField } from './presentation/molecules/FormField';
|
|
70
|
+
export type { SearchBoxProps } from './presentation/molecules/SearchBox';
|
|
71
|
+
export { SearchBox } from './presentation/molecules/SearchBox';
|
|
72
|
+
export type { AvatarProps, AvatarImageProps, AvatarFallbackProps } from './presentation/molecules/Avatar';
|
|
73
|
+
export { Avatar, AvatarImage, AvatarFallback } from './presentation/molecules/Avatar';
|
|
74
|
+
export type { ChipProps } from './presentation/molecules/Chip';
|
|
75
|
+
export { Chip } from './presentation/molecules/Chip';
|
|
76
|
+
export type { ToggleProps } from './presentation/molecules/Toggle';
|
|
77
|
+
export { Toggle } from './presentation/molecules/Toggle';
|
|
78
|
+
export { Select, SelectGroup, SelectValue, SelectTrigger, SelectContent, SelectLabel, SelectItem, SelectSeparator, SelectScrollUpButton, SelectScrollDownButton } from './presentation/molecules/Select';
|
|
79
|
+
export type { TextareaProps } from './presentation/molecules/Textarea';
|
|
80
|
+
export { Textarea } from './presentation/molecules/Textarea';
|
|
81
|
+
export type { RadioGroupProps, RadioOption } from './presentation/molecules/RadioGroup';
|
|
82
|
+
export { RadioGroup } from './presentation/molecules/RadioGroup';
|
|
83
|
+
export type { CheckboxGroupProps, CheckboxOption } from './presentation/molecules/CheckboxGroup';
|
|
84
|
+
export { CheckboxGroup } from './presentation/molecules/CheckboxGroup';
|
|
85
|
+
export type { InputGroupProps } from './presentation/molecules/InputGroup';
|
|
86
|
+
export { InputGroup, GroupedInput } from './presentation/molecules/InputGroup';
|
|
87
|
+
export { ScrollArea, ScrollBar } from './presentation/molecules/ScrollArea';
|
|
88
|
+
export type { ListItemProps } from './presentation/molecules/ListItem';
|
|
89
|
+
export { ListItem } from './presentation/molecules/ListItem';
|
|
90
|
+
|
|
91
|
+
export * from './presentation/organisms';
|
|
@@ -14,14 +14,13 @@ export interface SuspenseWrapperProps {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
const DefaultLoadingSpinner: React.FC<{ text?: string; className?: string }> = ({ text, className }) => (
|
|
17
|
-
<div style={{
|
|
17
|
+
<div className={className} style={{
|
|
18
18
|
display: 'flex',
|
|
19
19
|
flexDirection: 'column',
|
|
20
20
|
alignItems: 'center',
|
|
21
21
|
justifyContent: 'center',
|
|
22
22
|
gap: '16px',
|
|
23
|
-
padding: '32px'
|
|
24
|
-
...({ className } as any)
|
|
23
|
+
padding: '32px'
|
|
25
24
|
}}>
|
|
26
25
|
<div style={{
|
|
27
26
|
width: '40px',
|
|
@@ -43,6 +43,8 @@ const weightStyles: Record<'normal' | 'medium' | 'semibold' | 'bold', string> =
|
|
|
43
43
|
bold: 'font-bold',
|
|
44
44
|
};
|
|
45
45
|
|
|
46
|
+
// NOTE: "as any" is used here for the polymorphic ref, which is a necessary workaround
|
|
47
|
+
// for TypeScript's limitations in typing polymorphic components properly.
|
|
46
48
|
export const Text = forwardRef<HTMLElement, TextProps>(
|
|
47
49
|
({ className, as = 'p', variant = 'body', size = 'md', weight = 'normal', ...props }, ref) => {
|
|
48
50
|
const Tag = as as any;
|
|
@@ -23,7 +23,7 @@ const placementStyles: Record<'top' | 'bottom' | 'left' | 'right', string> = {
|
|
|
23
23
|
export const Tooltip = forwardRef<HTMLDivElement, TooltipProps>(
|
|
24
24
|
({ className, children, content, placement = 'top', delay = 200, ...props }, ref) => {
|
|
25
25
|
const [isOpen, setIsOpen] = useState(false);
|
|
26
|
-
const timeoutRef = useRef<
|
|
26
|
+
const timeoutRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);
|
|
27
27
|
|
|
28
28
|
const handleMouseEnter = () => {
|
|
29
29
|
timeoutRef.current = setTimeout(() => {
|
|
@@ -55,7 +55,5 @@ export { Switch } from './Switch';
|
|
|
55
55
|
export type { SwitchProps } from './Switch';
|
|
56
56
|
|
|
57
57
|
export { Separator } from './Separator';
|
|
58
|
-
export type { SeparatorProps } from './Separator';
|
|
59
58
|
|
|
60
59
|
export { Toggle, toggleVariants } from './Toggle';
|
|
61
|
-
export type { ToggleProps } from './Toggle';
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Reduces boilerplate in list components throughout the app
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { forwardRef, type ReactNode, type
|
|
7
|
+
import { forwardRef, type ReactNode, type HTMLAttributes, type AnchorHTMLAttributes } from 'react';
|
|
8
8
|
import { cn } from '../../infrastructure/utils';
|
|
9
9
|
import { Button } from '../atoms';
|
|
10
10
|
import type { BaseProps } from '../../domain/types';
|
|
@@ -22,6 +22,8 @@ export interface ListItemProps extends BaseProps {
|
|
|
22
22
|
selected?: boolean;
|
|
23
23
|
size?: 'sm' | 'md' | 'lg';
|
|
24
24
|
variant?: 'default' | 'bordered' | 'ghost';
|
|
25
|
+
className?: string;
|
|
26
|
+
id?: string;
|
|
25
27
|
}
|
|
26
28
|
|
|
27
29
|
const sizeStyles = {
|
|
@@ -64,7 +66,7 @@ export const ListItem = forwardRef<HTMLDivElement, ListItemProps>(
|
|
|
64
66
|
selected = false,
|
|
65
67
|
size = 'md',
|
|
66
68
|
variant = 'default',
|
|
67
|
-
|
|
69
|
+
id,
|
|
68
70
|
},
|
|
69
71
|
ref
|
|
70
72
|
) => {
|
|
@@ -104,9 +106,11 @@ export const ListItem = forwardRef<HTMLDivElement, ListItemProps>(
|
|
|
104
106
|
</>
|
|
105
107
|
);
|
|
106
108
|
|
|
109
|
+
// NOTE: Polymorphic component - renders as anchor when href is provided
|
|
110
|
+
// "ref as any" is necessary due to TypeScript limitations with polymorphic refs
|
|
107
111
|
if (href && !disabled) {
|
|
108
112
|
return (
|
|
109
|
-
<a href={href}
|
|
113
|
+
<a href={href} className={baseClasses} id={id} ref={ref as any}>
|
|
110
114
|
{content}
|
|
111
115
|
</a>
|
|
112
116
|
);
|
|
@@ -117,7 +121,7 @@ export const ListItem = forwardRef<HTMLDivElement, ListItemProps>(
|
|
|
117
121
|
ref={ref}
|
|
118
122
|
className={baseClasses}
|
|
119
123
|
onClick={disabled ? undefined : onClick}
|
|
120
|
-
{
|
|
124
|
+
id={id}
|
|
121
125
|
>
|
|
122
126
|
{content}
|
|
123
127
|
</div>
|
|
@@ -20,7 +20,6 @@ export { Toggle } from './Toggle';
|
|
|
20
20
|
export type { ToggleProps } from './Toggle';
|
|
21
21
|
|
|
22
22
|
export { Select, SelectGroup, SelectValue, SelectTrigger, SelectContent, SelectLabel, SelectItem, SelectSeparator, SelectScrollUpButton, SelectScrollDownButton } from './Select';
|
|
23
|
-
export type { SelectProps } from './Select';
|
|
24
23
|
|
|
25
24
|
export { Textarea } from './Textarea';
|
|
26
25
|
export type { TextareaProps } from './Textarea';
|
|
@@ -63,8 +63,12 @@ function Calendar({
|
|
|
63
63
|
...classNames,
|
|
64
64
|
}}
|
|
65
65
|
components={{
|
|
66
|
-
|
|
67
|
-
|
|
66
|
+
Chevron: ({ orientation, ...props }) =>
|
|
67
|
+
orientation === 'left' ? (
|
|
68
|
+
<ChevronLeft className="h-4 w-4" {...props} />
|
|
69
|
+
) : (
|
|
70
|
+
<ChevronRight className="h-4 w-4" {...props} />
|
|
71
|
+
),
|
|
68
72
|
}}
|
|
69
73
|
{...props}
|
|
70
74
|
/>
|
|
@@ -84,8 +84,9 @@ export function DataTable<T extends Record<string, unknown>>({
|
|
|
84
84
|
const bValue = typeof column.accessor === 'function' ? column.accessor(b) : b[column.accessor];
|
|
85
85
|
|
|
86
86
|
if (aValue === bValue) return 0;
|
|
87
|
+
if (aValue == null || bValue == null) return 0;
|
|
87
88
|
|
|
88
|
-
const comparison = aValue < bValue ? -1 : 1;
|
|
89
|
+
const comparison = (aValue as string | number) < (bValue as string | number) ? -1 : 1;
|
|
89
90
|
return sortDirection === 'asc' ? comparison : -comparison;
|
|
90
91
|
});
|
|
91
92
|
}, [data, sortColumn, sortDirection, columns, sortable]);
|
|
@@ -0,0 +1,253 @@
|
|
|
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
|
+
className,
|
|
30
|
+
}: ImageLightboxProps) => {
|
|
31
|
+
// Early return if closed
|
|
32
|
+
if (!isOpen) return null;
|
|
33
|
+
|
|
34
|
+
// Early return if no images
|
|
35
|
+
if (!images || images.length === 0) {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const [currentIndex, setCurrentIndex] = useState(initialIndex);
|
|
40
|
+
const [isZoomed, setIsZoomed] = useState(false);
|
|
41
|
+
|
|
42
|
+
// Ensure initialIndex is within bounds
|
|
43
|
+
const safeIndex = Math.min(Math.max(initialIndex, 0), images.length - 1);
|
|
44
|
+
const currentImage = images[safeIndex];
|
|
45
|
+
|
|
46
|
+
// Use useCallback to memoize navigation functions
|
|
47
|
+
const goToNext = useCallback(() => {
|
|
48
|
+
setCurrentIndex((prev) => (prev + 1) % images.length);
|
|
49
|
+
}, [images.length]);
|
|
50
|
+
|
|
51
|
+
const goToPrevious = useCallback(() => {
|
|
52
|
+
setCurrentIndex((prev) => (prev - 1 + images.length) % images.length);
|
|
53
|
+
}, [images.length]);
|
|
54
|
+
|
|
55
|
+
const handleImageClick = useCallback(() => {
|
|
56
|
+
setIsZoomed(!isZoomed);
|
|
57
|
+
}, [isZoomed]);
|
|
58
|
+
|
|
59
|
+
// Handle keyboard navigation
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
const handleKeyDown = (e: KeyboardEvent) => {
|
|
62
|
+
switch (e.key) {
|
|
63
|
+
case 'Escape':
|
|
64
|
+
onClose();
|
|
65
|
+
break;
|
|
66
|
+
case 'ArrowLeft':
|
|
67
|
+
goToPrevious();
|
|
68
|
+
break;
|
|
69
|
+
case 'ArrowRight':
|
|
70
|
+
goToNext();
|
|
71
|
+
break;
|
|
72
|
+
case '+':
|
|
73
|
+
case '=':
|
|
74
|
+
setIsZoomed(!isZoomed);
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
window.addEventListener('keydown', handleKeyDown);
|
|
80
|
+
return () => window.removeEventListener('keydown', handleKeyDown);
|
|
81
|
+
}, [onClose, goToPrevious, goToNext, isZoomed]);
|
|
82
|
+
|
|
83
|
+
// Prevent body scroll when lightbox is open
|
|
84
|
+
useEffect(() => {
|
|
85
|
+
const originalOverflow = document.body.style.overflow;
|
|
86
|
+
document.body.style.overflow = 'hidden';
|
|
87
|
+
|
|
88
|
+
return () => {
|
|
89
|
+
document.body.style.overflow = originalOverflow;
|
|
90
|
+
};
|
|
91
|
+
}, []);
|
|
92
|
+
|
|
93
|
+
if (!currentImage) return null;
|
|
94
|
+
|
|
95
|
+
return (
|
|
96
|
+
<div
|
|
97
|
+
className="fixed inset-0 z-50 bg-black/90 flex items-center justify-center p-4"
|
|
98
|
+
onClick={onClose}
|
|
99
|
+
>
|
|
100
|
+
{/* Close button */}
|
|
101
|
+
<button
|
|
102
|
+
onClick={onClose}
|
|
103
|
+
className="absolute top-4 right-4 p-2 bg-black/50 hover:bg-black/70 rounded-full text-white transition-all"
|
|
104
|
+
aria-label="Close lightbox"
|
|
105
|
+
>
|
|
106
|
+
<Icon className="text-white">
|
|
107
|
+
<path
|
|
108
|
+
strokeLinecap="round"
|
|
109
|
+
strokeLinejoin="round"
|
|
110
|
+
strokeWidth={2}
|
|
111
|
+
d="M6 18L18 6M6 6l12 12"
|
|
112
|
+
/>
|
|
113
|
+
</Icon>
|
|
114
|
+
</button>
|
|
115
|
+
|
|
116
|
+
{/* Navigation - Previous */}
|
|
117
|
+
{images.length > 1 && (
|
|
118
|
+
<button
|
|
119
|
+
onClick={(e) => {
|
|
120
|
+
e.stopPropagation();
|
|
121
|
+
goToPrevious();
|
|
122
|
+
}}
|
|
123
|
+
className="absolute left-4 p-2 bg-black/50 hover:bg-black/70 rounded-full text-white transition-all"
|
|
124
|
+
aria-label="Previous image"
|
|
125
|
+
>
|
|
126
|
+
<Icon className="text-white" size="lg">
|
|
127
|
+
<path
|
|
128
|
+
strokeLinecap="round"
|
|
129
|
+
strokeLinejoin="round"
|
|
130
|
+
strokeWidth={2}
|
|
131
|
+
d="M15 19l-7-7 7-7"
|
|
132
|
+
/>
|
|
133
|
+
</Icon>
|
|
134
|
+
</button>
|
|
135
|
+
)}
|
|
136
|
+
|
|
137
|
+
{/* Navigation - Next */}
|
|
138
|
+
{images.length > 1 && (
|
|
139
|
+
<button
|
|
140
|
+
onClick={(e) => {
|
|
141
|
+
e.stopPropagation();
|
|
142
|
+
goToNext();
|
|
143
|
+
}}
|
|
144
|
+
className="absolute right-4 p-2 bg-black/50 hover:bg-black/70 rounded-full text-white transition-all"
|
|
145
|
+
aria-label="Next image"
|
|
146
|
+
>
|
|
147
|
+
<Icon className="text-white" size="lg">
|
|
148
|
+
<path
|
|
149
|
+
strokeLinecap="round"
|
|
150
|
+
strokeLinejoin="round"
|
|
151
|
+
strokeWidth={2}
|
|
152
|
+
d="M9 5l7 7-7 7"
|
|
153
|
+
/>
|
|
154
|
+
</Icon>
|
|
155
|
+
</button>
|
|
156
|
+
)}
|
|
157
|
+
|
|
158
|
+
{/* Main Image */}
|
|
159
|
+
<div
|
|
160
|
+
className="relative max-w-5xl max-h-[90vh] w-full"
|
|
161
|
+
onClick={handleImageClick}
|
|
162
|
+
>
|
|
163
|
+
<img
|
|
164
|
+
src={currentImage.src}
|
|
165
|
+
alt={currentImage.alt}
|
|
166
|
+
className={cn(
|
|
167
|
+
'w-full h-full object-contain',
|
|
168
|
+
isZoomed ? 'cursor-zoom-out' : 'cursor-zoom-in'
|
|
169
|
+
)}
|
|
170
|
+
style={{
|
|
171
|
+
maxHeight: '90vh',
|
|
172
|
+
objectFit: 'contain',
|
|
173
|
+
}}
|
|
174
|
+
/>
|
|
175
|
+
|
|
176
|
+
{/* Zoom indicator */}
|
|
177
|
+
<div className="absolute bottom-4 right-4 flex items-center gap-2 bg-black/50 px-3 py-2 rounded-full">
|
|
178
|
+
<button
|
|
179
|
+
onClick={(e) => {
|
|
180
|
+
e.stopPropagation();
|
|
181
|
+
setIsZoomed(!isZoomed);
|
|
182
|
+
}}
|
|
183
|
+
className="p-1 hover:bg-black/70 rounded-full transition-all text-white"
|
|
184
|
+
aria-label={isZoomed ? 'Zoom out' : 'Zoom in'}
|
|
185
|
+
>
|
|
186
|
+
{isZoomed ? (
|
|
187
|
+
<Icon className="text-white" size="sm">
|
|
188
|
+
<path
|
|
189
|
+
strokeLinecap="round"
|
|
190
|
+
strokeLinejoin="round"
|
|
191
|
+
strokeWidth={2}
|
|
192
|
+
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0zM13 10H7"
|
|
193
|
+
/>
|
|
194
|
+
</Icon>
|
|
195
|
+
) : (
|
|
196
|
+
<Icon className="text-white" size="sm">
|
|
197
|
+
<path
|
|
198
|
+
strokeLinecap="round"
|
|
199
|
+
strokeLinejoin="round"
|
|
200
|
+
strokeWidth={2}
|
|
201
|
+
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0zM10 7v6m3-3H7"
|
|
202
|
+
/>
|
|
203
|
+
</Icon>
|
|
204
|
+
)}
|
|
205
|
+
</button>
|
|
206
|
+
</div>
|
|
207
|
+
</div>
|
|
208
|
+
|
|
209
|
+
{/* Image Counter */}
|
|
210
|
+
{images.length > 1 && (
|
|
211
|
+
<div className="absolute bottom-4 left-4 bg-black/50 px-4 py-2 rounded-full text-white text-sm">
|
|
212
|
+
{currentIndex + 1} / {images.length}
|
|
213
|
+
</div>
|
|
214
|
+
)}
|
|
215
|
+
|
|
216
|
+
{/* Image Title */}
|
|
217
|
+
{currentImage.title && (
|
|
218
|
+
<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">
|
|
219
|
+
{currentImage.title}
|
|
220
|
+
</div>
|
|
221
|
+
)}
|
|
222
|
+
|
|
223
|
+
{/* Thumbnails */}
|
|
224
|
+
{images.length > 1 && (
|
|
225
|
+
<div className="absolute bottom-20 left-1/2 -translate-x-1/2 flex gap-2 max-w-xl overflow-x-auto">
|
|
226
|
+
{images.map((image, index) => (
|
|
227
|
+
<button
|
|
228
|
+
key={index}
|
|
229
|
+
onClick={(e) => {
|
|
230
|
+
e.stopPropagation();
|
|
231
|
+
setCurrentIndex(index);
|
|
232
|
+
}}
|
|
233
|
+
className={cn(
|
|
234
|
+
'flex-shrink-0 w-16 h-16 rounded-lg overflow-hidden border-2 transition-all',
|
|
235
|
+
index === currentIndex
|
|
236
|
+
? 'border-white scale-110'
|
|
237
|
+
: 'border-transparent opacity-50 hover:opacity-100'
|
|
238
|
+
)}
|
|
239
|
+
>
|
|
240
|
+
<img
|
|
241
|
+
src={image.src}
|
|
242
|
+
alt={image.alt}
|
|
243
|
+
className="w-full h-full object-cover"
|
|
244
|
+
/>
|
|
245
|
+
</button>
|
|
246
|
+
))}
|
|
247
|
+
</div>
|
|
248
|
+
)}
|
|
249
|
+
</div>
|
|
250
|
+
);
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
ImageLightbox.displayName = 'ImageLightbox';
|
|
@@ -92,7 +92,7 @@ export const TableHead = forwardRef<HTMLTableCellElement, HTMLAttributes<HTMLTab
|
|
|
92
92
|
|
|
93
93
|
TableHead.displayName = 'TableHead';
|
|
94
94
|
|
|
95
|
-
export const TableCell = forwardRef<HTMLTableCellElement, HTMLAttributes<HTMLTableCellElement
|
|
95
|
+
export const TableCell = forwardRef<HTMLTableCellElement, HTMLAttributes<HTMLTableCellElement> & { colSpan?: number }>(
|
|
96
96
|
({ className, ...props }, ref) => (
|
|
97
97
|
<td
|
|
98
98
|
ref={ref}
|
|
@@ -1,75 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Organisms Export
|
|
3
|
-
* @description
|
|
3
|
+
* @description Complex UI components composed of molecules and atoms
|
|
4
4
|
* Subpath: @umituz/web-design-system/organisms
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
export type { CardProps } from './Card';
|
|
9
|
-
|
|
10
|
-
export { Alert, AlertTitle, AlertDescription } from './Alert';
|
|
11
|
-
export type { AlertProps } from './Alert';
|
|
12
|
-
|
|
13
|
-
export { Modal, ModalHeader, ModalTitle, ModalDescription, ModalContent, ModalFooter } from './Modal';
|
|
14
|
-
export type { ModalProps } from './Modal';
|
|
15
|
-
|
|
7
|
+
// Layout & Structure
|
|
16
8
|
export { Navbar, NavbarBrand, NavbarLinks, NavbarActions } from './Navbar';
|
|
17
9
|
export type { NavbarProps } from './Navbar';
|
|
18
10
|
|
|
19
|
-
export {
|
|
20
|
-
Table,
|
|
21
|
-
TableHeader,
|
|
22
|
-
TableBody,
|
|
23
|
-
TableFooter,
|
|
24
|
-
TableRow,
|
|
25
|
-
TableHead,
|
|
26
|
-
TableCell,
|
|
27
|
-
TableCaption
|
|
28
|
-
} from './Table';
|
|
29
|
-
export type { TableProps } from './Table';
|
|
30
|
-
|
|
31
|
-
export { Tabs } from './Tabs';
|
|
32
|
-
export type { TabsProps, Tab } from './Tabs';
|
|
33
|
-
|
|
34
|
-
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent } from './Accordion';
|
|
35
|
-
|
|
36
|
-
export {
|
|
37
|
-
Dialog,
|
|
38
|
-
DialogPortal,
|
|
39
|
-
DialogOverlay,
|
|
40
|
-
DialogClose,
|
|
41
|
-
DialogTrigger,
|
|
42
|
-
DialogContent,
|
|
43
|
-
DialogHeader,
|
|
44
|
-
DialogFooter,
|
|
45
|
-
DialogTitle,
|
|
46
|
-
DialogDescription,
|
|
47
|
-
} from './Dialog';
|
|
11
|
+
export { Footer } from './Footer';
|
|
48
12
|
|
|
49
13
|
export { Breadcrumbs } from './Breadcrumb';
|
|
50
|
-
export type {
|
|
14
|
+
export type { BreadcrumbItem, BreadcrumbsProps } from './Breadcrumb';
|
|
51
15
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
export {
|
|
55
|
-
|
|
56
|
-
export { Collapsible, CollapsibleTrigger, CollapsibleContent } from './Collapsible';
|
|
57
|
-
|
|
58
|
-
export {
|
|
59
|
-
Sheet,
|
|
60
|
-
SheetClose,
|
|
61
|
-
SheetContent,
|
|
62
|
-
SheetDescription,
|
|
63
|
-
SheetFooter,
|
|
64
|
-
SheetHeader,
|
|
65
|
-
SheetOverlay,
|
|
66
|
-
SheetPortal,
|
|
67
|
-
SheetTitle,
|
|
68
|
-
SheetTrigger,
|
|
69
|
-
} from './Sheet';
|
|
16
|
+
// Data Display
|
|
17
|
+
export { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter } from './Card';
|
|
18
|
+
export type { CardProps, CardHeaderProps, CardTitleProps, CardDescriptionProps, CardContentProps, CardFooterProps } from './Card';
|
|
70
19
|
|
|
71
|
-
export {
|
|
72
|
-
export type {
|
|
20
|
+
export { StatCard } from './StatCard';
|
|
21
|
+
export type { StatCardProps } from './StatCard';
|
|
73
22
|
|
|
74
23
|
export { MetricCard } from './MetricCard';
|
|
75
24
|
export type { MetricCardProps } from './MetricCard';
|
|
@@ -77,59 +26,80 @@ export type { MetricCardProps } from './MetricCard';
|
|
|
77
26
|
export { QuickActionCard } from './QuickActionCard';
|
|
78
27
|
export type { QuickActionCardProps } from './QuickActionCard';
|
|
79
28
|
|
|
29
|
+
// Lists & Tables
|
|
30
|
+
export { Table, TableHeader, TableBody, TableFooter, TableHead, TableRow, TableCell } from './Table';
|
|
31
|
+
export type { TableProps, TableHeaderProps, TableBodyProps, TableFooterProps, TableHeadProps, TableRowProps, TableCellProps } from './Table';
|
|
32
|
+
|
|
33
|
+
export { DataTable } from './DataTable';
|
|
34
|
+
export type { DataTableProps, ColumnDef } from './DataTable';
|
|
35
|
+
|
|
36
|
+
export { List, ListItem, ListHeader } from './List';
|
|
37
|
+
export type { ListProps, ListItemProps, ListHeaderProps } from './List';
|
|
38
|
+
|
|
39
|
+
// Feedback & Empty States
|
|
40
|
+
export { Alert, AlertTitle, AlertDescription } from './Alert';
|
|
41
|
+
export type { AlertProps, AlertTitleProps, AlertDescriptionProps } from './Alert';
|
|
42
|
+
|
|
80
43
|
export { EmptyState } from './EmptyState';
|
|
81
44
|
export type { EmptyStateProps } from './EmptyState';
|
|
82
45
|
|
|
83
46
|
export { LoadingState } from './LoadingState';
|
|
84
47
|
export type { LoadingStateProps } from './LoadingState';
|
|
85
48
|
|
|
86
|
-
|
|
87
|
-
export
|
|
49
|
+
// Overlays & Modals
|
|
50
|
+
export { Dialog, DialogHeader, DialogTitle, DialogDescription, DialogContent, DialogFooter } from './Dialog';
|
|
51
|
+
export type { DialogProps } from './Dialog';
|
|
88
52
|
|
|
89
|
-
export {
|
|
90
|
-
export type {
|
|
53
|
+
export { Modal } from './Modal';
|
|
54
|
+
export type { ModalProps } from './Modal';
|
|
91
55
|
|
|
92
56
|
export { FormModal } from './FormModal';
|
|
93
57
|
export type { FormModalProps } from './FormModal';
|
|
94
58
|
|
|
95
|
-
export {
|
|
96
|
-
export type {
|
|
59
|
+
export { AlertDialog, AlertDialogTrigger, AlertDialogContent, AlertDialogHeader, AlertDialogTitle, AlertDialogDescription, AlertDialogFooter, AlertDialogCancel, AlertDialogAction } from './AlertDialog';
|
|
60
|
+
export type { AlertDialogProps } from './AlertDialog';
|
|
97
61
|
|
|
98
|
-
export {
|
|
99
|
-
export type {
|
|
62
|
+
export { Popover, PopoverTrigger, PopoverContent } from './Popover';
|
|
63
|
+
export type { PopoverProps } from './Popover';
|
|
64
|
+
|
|
65
|
+
export { Sheet, SheetTrigger, SheetContent, SheetHeader, SheetTitle, SheetDescription, SheetFooter } from './Sheet';
|
|
66
|
+
export type { SheetProps, SheetSide } from './Sheet';
|
|
67
|
+
|
|
68
|
+
export { HoverCard, HoverCardTrigger, HoverCardContent } from './HoverCard';
|
|
69
|
+
export type { HoverCardProps } from './HoverCard';
|
|
70
|
+
|
|
71
|
+
export { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuLabel } from './DropdownMenu';
|
|
72
|
+
export type { DropdownMenuProps } from './DropdownMenu';
|
|
100
73
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
AlertDialogDescription,
|
|
111
|
-
AlertDialogAction,
|
|
112
|
-
AlertDialogCancel,
|
|
113
|
-
} from './AlertDialog';
|
|
114
|
-
|
|
115
|
-
export {
|
|
116
|
-
DropdownMenu,
|
|
117
|
-
DropdownMenuTrigger,
|
|
118
|
-
DropdownMenuContent,
|
|
119
|
-
DropdownMenuItem,
|
|
120
|
-
DropdownMenuCheckboxItem,
|
|
121
|
-
DropdownMenuRadioItem,
|
|
122
|
-
DropdownMenuLabel,
|
|
123
|
-
DropdownMenuSeparator,
|
|
124
|
-
DropdownMenuShortcut,
|
|
125
|
-
DropdownMenuGroup,
|
|
126
|
-
DropdownMenuPortal,
|
|
127
|
-
DropdownMenuSub,
|
|
128
|
-
DropdownMenuSubContent,
|
|
129
|
-
DropdownMenuSubTrigger,
|
|
130
|
-
DropdownMenuRadioGroup,
|
|
131
|
-
} from './DropdownMenu';
|
|
132
|
-
export type { DropdownMenuProps, DropdownMenuContentProps } from './DropdownMenu';
|
|
74
|
+
// Interactive Components
|
|
75
|
+
export { Tabs, TabsList, TabsTrigger, TabsContent } from './Tabs';
|
|
76
|
+
export type { TabsProps } from './Tabs';
|
|
77
|
+
|
|
78
|
+
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent } from './Accordion';
|
|
79
|
+
export type { AccordionProps } from './Accordion';
|
|
80
|
+
|
|
81
|
+
export { Toggle } from './Toggle';
|
|
82
|
+
export type { ToggleProps } from './Toggle';
|
|
133
83
|
|
|
134
84
|
export { ToggleGroup, ToggleGroupItem } from './ToggleGroup';
|
|
135
85
|
export type { ToggleGroupProps, ToggleGroupItemProps } from './ToggleGroup';
|
|
86
|
+
|
|
87
|
+
export { Collapsible, CollapsibleTrigger, CollapsibleContent } from './Collapsible';
|
|
88
|
+
export type { CollapsibleProps } from './Collapsible';
|
|
89
|
+
|
|
90
|
+
// Form Components
|
|
91
|
+
export { CheckboxGroup } from './CheckboxGroup';
|
|
92
|
+
export type { CheckboxGroupProps } from './CheckboxGroup';
|
|
93
|
+
|
|
94
|
+
export { RadioGroup, RadioGroupItem } from './RadioGroup';
|
|
95
|
+
export type { RadioGroupProps, RadioGroupItemProps } from './RadioGroup';
|
|
96
|
+
|
|
97
|
+
export { Select, SelectTrigger, SelectValue, SelectContent, SelectItem, SelectGroup, SelectLabel, SelectSeparator } from './Select';
|
|
98
|
+
export type { SelectProps } from './Select';
|
|
99
|
+
|
|
100
|
+
export { Calendar } from './Calendar';
|
|
101
|
+
export type { CalendarProps } from './Calendar';
|
|
102
|
+
|
|
103
|
+
// NEW: Media & Content Components
|
|
104
|
+
export { ImageLightbox } from './ImageLightbox';
|
|
105
|
+
export type { ImageLightboxProps, ImageLightboxImage } from './ImageLightbox';
|