@umituz/web-design-system 1.5.1 → 1.7.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.
@@ -0,0 +1,110 @@
1
+ /**
2
+ * QuickActionCard Component (Organism)
3
+ * @description Card component for quick navigation actions with icon
4
+ */
5
+
6
+ import { forwardRef } from 'react';
7
+ import { cn } from '../../infrastructure/utils';
8
+ import type { BaseProps, SizeVariant } from '../../domain/types';
9
+
10
+ export interface QuickActionCardProps extends BaseProps {
11
+ label: string;
12
+ icon?: React.ComponentType<{ className?: string }>;
13
+ iconColor?: string;
14
+ iconBgColor?: string;
15
+ onClick?: () => void;
16
+ href?: string;
17
+ size?: Extract<SizeVariant, 'sm' | 'md' | 'lg'>;
18
+ target?: '_blank' | '_self' | '_parent' | '_top';
19
+ }
20
+
21
+ const sizeStyles = {
22
+ sm: 'p-3 gap-2',
23
+ md: 'p-4 gap-3',
24
+ lg: 'p-5 gap-4',
25
+ };
26
+
27
+ const iconSizeStyles = {
28
+ sm: 'w-8 h-8',
29
+ md: 'w-10 h-10',
30
+ lg: 'w-12 h-12',
31
+ };
32
+
33
+ const iconInnerSizeStyles = {
34
+ sm: 'h-4 w-4',
35
+ md: 'h-5 w-5',
36
+ lg: 'h-6 w-6',
37
+ };
38
+
39
+ const labelSizeStyles = {
40
+ sm: 'text-xs',
41
+ md: 'text-sm',
42
+ lg: 'text-base',
43
+ };
44
+
45
+ export const QuickActionCard = forwardRef<HTMLAnchorElement | HTMLDivElement, QuickActionCardProps>(
46
+ (
47
+ {
48
+ className,
49
+ label,
50
+ icon: Icon,
51
+ iconColor = 'text-white',
52
+ iconBgColor = 'bg-primary',
53
+ onClick,
54
+ href,
55
+ size = 'md',
56
+ target = '_self',
57
+ ...props
58
+ },
59
+ ref
60
+ ) => {
61
+ const content = (
62
+ <>
63
+ {Icon && (
64
+ <div className={cn(iconSizeStyles[size], 'rounded-lg flex items-center justify-center flex-shrink-0', iconBgColor)}>
65
+ <Icon className={cn(iconInnerSizeStyles[size], iconColor)} />
66
+ </div>
67
+ )}
68
+ <span className={cn('font-medium text-foreground group-hover:text-primary transition-colors', labelSizeStyles[size])}>
69
+ {label}
70
+ </span>
71
+ </>
72
+ );
73
+
74
+ const baseClasses = cn(
75
+ 'bg-card border border-border rounded-xl',
76
+ 'flex items-center',
77
+ 'hover:border-primary/50 transition-colors group',
78
+ sizeStyles[size],
79
+ className
80
+ );
81
+
82
+ if (href) {
83
+ return (
84
+ <a
85
+ ref={ref as React.Ref<HTMLAnchorElement>}
86
+ href={href}
87
+ target={target}
88
+ className={baseClasses}
89
+ onClick={onClick}
90
+ {...props}
91
+ >
92
+ {content}
93
+ </a>
94
+ );
95
+ }
96
+
97
+ return (
98
+ <div
99
+ ref={ref as React.Ref<HTMLDivElement>}
100
+ className={baseClasses}
101
+ onClick={onClick}
102
+ {...props}
103
+ >
104
+ {content}
105
+ </div>
106
+ );
107
+ }
108
+ );
109
+
110
+ QuickActionCard.displayName = 'QuickActionCard';
@@ -0,0 +1,135 @@
1
+ /**
2
+ * Sheet Component (Organism)
3
+ * @description Slide-over sheet dialog (Shadcn/ui compatible)
4
+ */
5
+
6
+ import * as SheetPrimitive from '@radix-ui/react-dialog';
7
+ import { X } from 'lucide-react';
8
+ import * as React from 'react';
9
+ import { cn } from '../../infrastructure/utils';
10
+
11
+ const Sheet = SheetPrimitive.Root;
12
+
13
+ const SheetTrigger = SheetPrimitive.Trigger;
14
+
15
+ const SheetClose = SheetPrimitive.Close;
16
+
17
+ const SheetPortal = SheetPrimitive.Portal;
18
+
19
+ const SheetOverlay = React.forwardRef<
20
+ React.ElementRef<typeof SheetPrimitive.Overlay>,
21
+ React.ComponentPropsWithoutRef<typeof SheetPrimitive.Overlay>
22
+ >(({ className, ...props }, ref) => (
23
+ <SheetPrimitive.Overlay
24
+ className={cn(
25
+ 'fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
26
+ className
27
+ )}
28
+ {...props}
29
+ ref={ref}
30
+ />
31
+ ));
32
+ SheetOverlay.displayName = SheetPrimitive.Overlay.displayName;
33
+
34
+ const getSheetVariants = (side: 'top' | 'bottom' | 'left' | 'right') => {
35
+ const base = 'fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500';
36
+
37
+ const sideVariants = {
38
+ top: 'inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top',
39
+ bottom: 'inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom',
40
+ left: 'inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm',
41
+ right: 'inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm',
42
+ };
43
+
44
+ return cn(base, sideVariants[side]);
45
+ };
46
+
47
+ interface SheetContentProps extends React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content> {
48
+ side?: 'top' | 'bottom' | 'left' | 'right';
49
+ }
50
+
51
+ const SheetContent = React.forwardRef<
52
+ React.ElementRef<typeof SheetPrimitive.Content>,
53
+ SheetContentProps
54
+ >(({ side = 'right', className, children, ...props }, ref) => (
55
+ <SheetPortal>
56
+ <SheetOverlay />
57
+ <SheetPrimitive.Content
58
+ ref={ref}
59
+ className={cn(getSheetVariants(side), className)}
60
+ {...props}
61
+ >
62
+ {children}
63
+ <SheetPrimitive.Close 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 disabled:pointer-events-none data-[state=open]:bg-secondary">
64
+ <X className="h-4 w-4" />
65
+ <span className="sr-only">Close</span>
66
+ </SheetPrimitive.Close>
67
+ </SheetPrimitive.Content>
68
+ </SheetPortal>
69
+ ));
70
+ SheetContent.displayName = SheetPrimitive.Content.displayName;
71
+
72
+ const SheetHeader = ({
73
+ className,
74
+ ...props
75
+ }: React.HTMLAttributes<HTMLDivElement>) => (
76
+ <div
77
+ className={cn(
78
+ 'flex flex-col space-y-2 text-center sm:text-left',
79
+ className
80
+ )}
81
+ {...props}
82
+ />
83
+ );
84
+ SheetHeader.displayName = 'SheetHeader';
85
+
86
+ const SheetFooter = ({
87
+ className,
88
+ ...props
89
+ }: React.HTMLAttributes<HTMLDivElement>) => (
90
+ <div
91
+ className={cn(
92
+ 'flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2',
93
+ className
94
+ )}
95
+ {...props}
96
+ />
97
+ );
98
+ SheetFooter.displayName = 'SheetFooter';
99
+
100
+ const SheetTitle = React.forwardRef<
101
+ React.ElementRef<typeof SheetPrimitive.Title>,
102
+ React.ComponentPropsWithoutRef<typeof SheetPrimitive.Title>
103
+ >(({ className, ...props }, ref) => (
104
+ <SheetPrimitive.Title
105
+ ref={ref}
106
+ className={cn('text-lg font-semibold text-foreground', className)}
107
+ {...props}
108
+ />
109
+ ));
110
+ SheetTitle.displayName = SheetPrimitive.Title.displayName;
111
+
112
+ const SheetDescription = React.forwardRef<
113
+ React.ElementRef<typeof SheetPrimitive.Description>,
114
+ React.ComponentPropsWithoutRef<typeof SheetPrimitive.Description>
115
+ >(({ className, ...props }, ref) => (
116
+ <SheetPrimitive.Description
117
+ ref={ref}
118
+ className={cn('text-sm text-muted-foreground', className)}
119
+ {...props}
120
+ />
121
+ ));
122
+ SheetDescription.displayName = SheetPrimitive.Description.displayName;
123
+
124
+ export {
125
+ Sheet,
126
+ SheetClose,
127
+ SheetContent,
128
+ SheetDescription,
129
+ SheetFooter,
130
+ SheetHeader,
131
+ SheetOverlay,
132
+ SheetPortal,
133
+ SheetTitle,
134
+ SheetTrigger,
135
+ };
@@ -0,0 +1,161 @@
1
+ /**
2
+ * StatCard Component (Organism)
3
+ * @description Enhanced metric card with progress bar and target tracking
4
+ * Combines functionality of KPICard and StatsCard from main app
5
+ */
6
+
7
+ import { forwardRef, type ComponentType } from 'react';
8
+ import { cn } from '../../infrastructure/utils';
9
+ import { Card, CardContent } from './Card';
10
+ import { Progress } from '../atoms';
11
+ import { ArrowUpRight, ArrowDownRight } from 'lucide-react';
12
+ import type { BaseProps, ColorVariant, SizeVariant } from '../../domain/types';
13
+
14
+ export interface StatCardProps extends BaseProps {
15
+ title: string;
16
+ value: string | number;
17
+ icon?: ComponentType<{ className?: string }>;
18
+ iconColor?: string;
19
+ trend?: {
20
+ value: number;
21
+ label?: string;
22
+ };
23
+ target?: {
24
+ value: string;
25
+ progress: number;
26
+ };
27
+ size?: Extract<SizeVariant, 'sm' | 'md' | 'lg'>;
28
+ variant?: 'default' | 'gradient' | 'elevated';
29
+ trendValueFormatter?: (value: number) => string;
30
+ onClick?: () => void;
31
+ changeLabel?: string; // For backward compatibility with StatsCard
32
+ }
33
+
34
+ const sizeStyles = {
35
+ sm: 'p-3',
36
+ md: 'p-4',
37
+ lg: 'p-6',
38
+ };
39
+
40
+ const valueSizeStyles = {
41
+ sm: 'text-lg',
42
+ md: 'text-2xl',
43
+ lg: 'text-3xl',
44
+ };
45
+
46
+ const iconSizeStyles = {
47
+ sm: 'h-4 w-4',
48
+ md: 'h-8 w-8',
49
+ lg: 'h-12 w-12',
50
+ };
51
+
52
+ const labelSizeStyles = {
53
+ sm: 'text-xs',
54
+ md: 'text-sm',
55
+ lg: 'text-base',
56
+ };
57
+
58
+ const iconBgSizeStyles = {
59
+ sm: 'w-8 h-8',
60
+ md: 'w-12 h-12',
61
+ lg: 'w-16 h-16',
62
+ };
63
+
64
+ export const StatCard = forwardRef<HTMLDivElement, StatCardProps>(
65
+ (
66
+ {
67
+ className,
68
+ title,
69
+ value,
70
+ icon: Icon,
71
+ iconColor = 'text-primary',
72
+ trend,
73
+ target,
74
+ size = 'md',
75
+ variant = 'default',
76
+ trendValueFormatter = (v) => `${v > 0 ? '+' : ''}${Math.abs(v).toFixed(1)}%`,
77
+ onClick,
78
+ changeLabel,
79
+ ...props
80
+ },
81
+ ref
82
+ ) => {
83
+ const isPositive = trend?.value !== undefined && trend.value >= 0;
84
+ const GrowthIcon = isPositive ? ArrowUpRight : ArrowDownRight;
85
+ const trendColor = isPositive ? 'text-green-600' : 'text-red-600';
86
+ const iconBgColor = isPositive ? 'text-green-500' : 'text-red-500';
87
+
88
+ const cardVariant = variant === 'elevated' ? 'elevated' : 'default';
89
+
90
+ return (
91
+ <Card
92
+ ref={ref}
93
+ variant={cardVariant}
94
+ className={cn(
95
+ 'transition-all duration-300',
96
+ variant === 'gradient' && 'bg-gradient-to-br from-white to-gray-50 hover:shadow-lg border-0 shadow-md',
97
+ onClick && 'cursor-pointer hover:shadow-lg hover:border-primary/50',
98
+ className
99
+ )}
100
+ onClick={onClick}
101
+ {...props}
102
+ >
103
+ <CardContent className={cn(sizeStyles[size])}>
104
+ <div className="flex items-center justify-between mb-4">
105
+ {Icon && (
106
+ <div
107
+ className={cn(
108
+ 'rounded-xl flex items-center justify-center',
109
+ variant === 'gradient' && 'bg-gradient-to-br from-teal-500 to-cyan-500',
110
+ variant !== 'gradient' && `bg-${iconColor.split('-')[1]}-500/10`,
111
+ iconBgSizeStyles[size]
112
+ )}
113
+ >
114
+ <Icon className={cn(iconSizeStyles[size], variant === 'gradient' ? 'text-white' : iconColor)} />
115
+ </div>
116
+ )}
117
+ {trend && (
118
+ <span
119
+ className={cn(
120
+ 'text-sm font-medium px-2 py-1 rounded-full',
121
+ isPositive ? 'text-green-700 bg-green-100' : 'text-red-700 bg-red-100'
122
+ )}
123
+ >
124
+ {changeLabel || trendValueFormatter(trend.value)}
125
+ </span>
126
+ )}
127
+ </div>
128
+
129
+ <div className="space-y-3">
130
+ <div>
131
+ <p className={cn('font-bold text-gray-900', valueSizeStyles[size])}>
132
+ {typeof value === 'number' ? value.toLocaleString() : value}
133
+ </p>
134
+ <p className={cn('text-muted-foreground', labelSizeStyles[size])}>{title}</p>
135
+ </div>
136
+
137
+ {target && (
138
+ <div className="space-y-1">
139
+ <div className="flex justify-between text-xs">
140
+ <span className="text-muted-foreground">Target: {target.value}</span>
141
+ <span className="text-teal-600">{target.progress}%</span>
142
+ </div>
143
+ <Progress value={target.progress} className="h-2" />
144
+ </div>
145
+ )}
146
+
147
+ {trend && variant !== 'gradient' && (
148
+ <div className="flex items-center mt-1">
149
+ <GrowthIcon className={cn('h-3 w-3 mr-1', iconBgColor)} />
150
+ <p className={cn('text-xs', trendColor)}>{trendValueFormatter(trend.value)}</p>
151
+ {trend.label && <p className="text-xs text-muted-foreground ml-1">{trend.label}</p>}
152
+ </div>
153
+ )}
154
+ </div>
155
+ </CardContent>
156
+ </Card>
157
+ );
158
+ }
159
+ );
160
+
161
+ StatCard.displayName = 'StatCard';
@@ -33,8 +33,62 @@ export type { TabsProps, Tab } from './Tabs';
33
33
 
34
34
  export { Accordion, AccordionItem, AccordionTrigger, AccordionContent } from './Accordion';
35
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';
48
+
36
49
  export { Breadcrumbs } from './Breadcrumb';
37
50
  export type { BreadcrumbsProps, BreadcrumbItem } from './Breadcrumb';
38
51
 
52
+ export { Popover, PopoverTrigger, PopoverContent } from './Popover';
53
+
54
+ export { Collapsible, CollapsibleTrigger, CollapsibleContent } from './Collapsible';
55
+
56
+ export {
57
+ Sheet,
58
+ SheetClose,
59
+ SheetContent,
60
+ SheetDescription,
61
+ SheetFooter,
62
+ SheetHeader,
63
+ SheetOverlay,
64
+ SheetPortal,
65
+ SheetTitle,
66
+ SheetTrigger,
67
+ } from './Sheet';
68
+
39
69
  export { Footer } from './Footer';
40
70
  export type { FooterProps } from './Footer';
71
+
72
+ export { MetricCard } from './MetricCard';
73
+ export type { MetricCardProps } from './MetricCard';
74
+
75
+ export { QuickActionCard } from './QuickActionCard';
76
+ export type { QuickActionCardProps } from './QuickActionCard';
77
+
78
+ export { EmptyState } from './EmptyState';
79
+ export type { EmptyStateProps } from './EmptyState';
80
+
81
+ export { LoadingState } from './LoadingState';
82
+ export type { LoadingStateProps } from './LoadingState';
83
+
84
+ export { DataTable } from './DataTable';
85
+ export type { DataTableProps, Column } from './DataTable';
86
+
87
+ export { StatCard } from './StatCard';
88
+ export type { StatCardProps } from './StatCard';
89
+
90
+ export { FormModal } from './FormModal';
91
+ export type { FormModalProps } from './FormModal';
92
+
93
+ export { ConfirmDialog } from './ConfirmDialog';
94
+ export type { ConfirmDialogProps } from './ConfirmDialog';