@lp-labs/ui-components 0.0.0-stage → 1.0.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/CHANGELOG.md +41 -0
- package/LICENSE +21 -0
- package/README.md +256 -2
- package/dist/cjs/components/Accordion.d.ts +78 -0
- package/dist/cjs/components/Accordion.js +78 -0
- package/dist/cjs/components/AdvancedComponents.d.ts +353 -0
- package/dist/cjs/components/AdvancedComponents.js +353 -0
- package/dist/cjs/components/Alert.d.ts +102 -0
- package/dist/cjs/components/Alert.js +102 -0
- package/dist/cjs/components/Avatar.d.ts +89 -0
- package/dist/cjs/components/Avatar.js +89 -0
- package/dist/cjs/components/Badge.d.ts +57 -0
- package/dist/cjs/components/Badge.js +57 -0
- package/dist/cjs/components/Breadcrumb.d.ts +50 -0
- package/dist/cjs/components/Breadcrumb.js +50 -0
- package/dist/cjs/components/Button.d.ts +77 -0
- package/dist/cjs/components/Button.js +77 -0
- package/dist/cjs/components/ButtonVariants.d.ts +261 -0
- package/dist/cjs/components/ButtonVariants.js +261 -0
- package/dist/cjs/components/Card.d.ts +56 -0
- package/dist/cjs/components/Card.js +56 -0
- package/dist/cjs/components/CardVariants.d.ts +267 -0
- package/dist/cjs/components/CardVariants.js +267 -0
- package/dist/cjs/components/Checkbox.d.ts +50 -0
- package/dist/cjs/components/Checkbox.js +50 -0
- package/dist/cjs/components/Chip.d.ts +104 -0
- package/dist/cjs/components/Chip.js +104 -0
- package/dist/cjs/components/DataDisplayComponents.d.ts +275 -0
- package/dist/cjs/components/DataDisplayComponents.js +275 -0
- package/dist/cjs/components/Divider.d.ts +77 -0
- package/dist/cjs/components/Divider.js +77 -0
- package/dist/cjs/components/Drawer.d.ts +142 -0
- package/dist/cjs/components/Drawer.js +142 -0
- package/dist/cjs/components/Dropdown.d.ts +139 -0
- package/dist/cjs/components/Dropdown.js +139 -0
- package/dist/cjs/components/Form.d.ts +41 -0
- package/dist/cjs/components/Form.js +41 -0
- package/dist/cjs/components/FormGroup.d.ts +58 -0
- package/dist/cjs/components/FormGroup.js +58 -0
- package/dist/cjs/components/FormInputVariants.d.ts +298 -0
- package/dist/cjs/components/FormInputVariants.js +298 -0
- package/dist/cjs/components/Image.d.ts +106 -0
- package/dist/cjs/components/Image.js +106 -0
- package/dist/cjs/components/Input.d.ts +65 -0
- package/dist/cjs/components/Input.js +65 -0
- package/dist/cjs/components/LayoutComponents.d.ts +258 -0
- package/dist/cjs/components/LayoutComponents.js +258 -0
- package/dist/cjs/components/Link.d.ts +71 -0
- package/dist/cjs/components/Link.js +71 -0
- package/dist/cjs/components/LoadingFeedbackComponents.d.ts +230 -0
- package/dist/cjs/components/LoadingFeedbackComponents.js +230 -0
- package/dist/cjs/components/Menu.d.ts +167 -0
- package/dist/cjs/components/Menu.js +167 -0
- package/dist/cjs/components/Modal.d.ts +145 -0
- package/dist/cjs/components/Modal.js +145 -0
- package/dist/cjs/components/ModalTypes.d.ts +288 -0
- package/dist/cjs/components/ModalTypes.js +288 -0
- package/dist/cjs/components/Navbar.d.ts +181 -0
- package/dist/cjs/components/Navbar.js +181 -0
- package/dist/cjs/components/NavigationComponents.d.ts +276 -0
- package/dist/cjs/components/NavigationComponents.js +276 -0
- package/dist/cjs/components/Pagination.d.ts +94 -0
- package/dist/cjs/components/Pagination.js +94 -0
- package/dist/cjs/components/Popover.d.ts +108 -0
- package/dist/cjs/components/Popover.js +108 -0
- package/dist/cjs/components/Progress.d.ts +68 -0
- package/dist/cjs/components/Progress.js +68 -0
- package/dist/cjs/components/Radio.d.ts +93 -0
- package/dist/cjs/components/Radio.js +93 -0
- package/dist/cjs/components/Rating.d.ts +82 -0
- package/dist/cjs/components/Rating.js +82 -0
- package/dist/cjs/components/Select.d.ts +74 -0
- package/dist/cjs/components/Select.js +74 -0
- package/dist/cjs/components/Sidebar.d.ts +174 -0
- package/dist/cjs/components/Sidebar.js +174 -0
- package/dist/cjs/components/Skeleton.d.ts +57 -0
- package/dist/cjs/components/Skeleton.js +57 -0
- package/dist/cjs/components/Slider.d.ts +64 -0
- package/dist/cjs/components/Slider.js +64 -0
- package/dist/cjs/components/Spinner.d.ts +70 -0
- package/dist/cjs/components/Spinner.js +70 -0
- package/dist/cjs/components/Stack.d.ts +75 -0
- package/dist/cjs/components/Stack.js +75 -0
- package/dist/cjs/components/Stepper.d.ts +150 -0
- package/dist/cjs/components/Stepper.js +150 -0
- package/dist/cjs/components/Table.d.ts +173 -0
- package/dist/cjs/components/Table.js +173 -0
- package/dist/cjs/components/Tabs.d.ts +104 -0
- package/dist/cjs/components/Tabs.js +104 -0
- package/dist/cjs/components/Tag.d.ts +74 -0
- package/dist/cjs/components/Tag.js +74 -0
- package/dist/cjs/components/Textarea.d.ts +75 -0
- package/dist/cjs/components/Textarea.js +75 -0
- package/dist/cjs/components/Timeline.d.ts +124 -0
- package/dist/cjs/components/Timeline.js +124 -0
- package/dist/cjs/components/Toast.d.ts +155 -0
- package/dist/cjs/components/Toast.js +155 -0
- package/dist/cjs/components/Toggle.d.ts +75 -0
- package/dist/cjs/components/Toggle.js +75 -0
- package/dist/cjs/components/Tooltip.d.ts +80 -0
- package/dist/cjs/components/Tooltip.js +80 -0
- package/dist/cjs/components/TypographyComponents.d.ts +265 -0
- package/dist/cjs/components/TypographyComponents.js +265 -0
- package/dist/cjs/components/UtilityComponents.d.ts +345 -0
- package/dist/cjs/components/UtilityComponents.js +345 -0
- package/dist/cjs/components/Video.d.ts +82 -0
- package/dist/cjs/components/Video.js +82 -0
- package/dist/cjs/components/index.d.ts +311 -0
- package/dist/cjs/components/index.js +311 -0
- package/dist/cjs/index.d.ts +5 -0
- package/dist/cjs/index.js +5 -0
- package/dist/cjs/styles/main.css +493 -0
- package/dist/cjs/theme/ThemeProvider.d.ts +185 -0
- package/dist/cjs/theme/ThemeProvider.js +185 -0
- package/dist/cjs/theme/index.d.ts +2 -0
- package/dist/cjs/theme/index.js +2 -0
- package/dist/cjs/utilities/classNames.d.ts +200 -0
- package/dist/cjs/utilities/classNames.js +200 -0
- package/dist/cjs/utilities/index.d.ts +3 -0
- package/dist/cjs/utilities/index.js +3 -0
- package/dist/cjs/utilities/styles.d.ts +94 -0
- package/dist/cjs/utilities/styles.js +94 -0
- package/dist/esm/components/Accordion.d.ts +78 -0
- package/dist/esm/components/Accordion.js +78 -0
- package/dist/esm/components/AdvancedComponents.d.ts +353 -0
- package/dist/esm/components/AdvancedComponents.js +353 -0
- package/dist/esm/components/Alert.d.ts +102 -0
- package/dist/esm/components/Alert.js +102 -0
- package/dist/esm/components/Avatar.d.ts +89 -0
- package/dist/esm/components/Avatar.js +89 -0
- package/dist/esm/components/Badge.d.ts +57 -0
- package/dist/esm/components/Badge.js +57 -0
- package/dist/esm/components/Breadcrumb.d.ts +50 -0
- package/dist/esm/components/Breadcrumb.js +50 -0
- package/dist/esm/components/Button.d.ts +77 -0
- package/dist/esm/components/Button.js +77 -0
- package/dist/esm/components/ButtonVariants.d.ts +261 -0
- package/dist/esm/components/ButtonVariants.js +261 -0
- package/dist/esm/components/Card.d.ts +56 -0
- package/dist/esm/components/Card.js +56 -0
- package/dist/esm/components/CardVariants.d.ts +267 -0
- package/dist/esm/components/CardVariants.js +267 -0
- package/dist/esm/components/Checkbox.d.ts +50 -0
- package/dist/esm/components/Checkbox.js +50 -0
- package/dist/esm/components/Chip.d.ts +104 -0
- package/dist/esm/components/Chip.js +104 -0
- package/dist/esm/components/DataDisplayComponents.d.ts +275 -0
- package/dist/esm/components/DataDisplayComponents.js +275 -0
- package/dist/esm/components/Divider.d.ts +77 -0
- package/dist/esm/components/Divider.js +77 -0
- package/dist/esm/components/Drawer.d.ts +142 -0
- package/dist/esm/components/Drawer.js +142 -0
- package/dist/esm/components/Dropdown.d.ts +139 -0
- package/dist/esm/components/Dropdown.js +139 -0
- package/dist/esm/components/Form.d.ts +41 -0
- package/dist/esm/components/Form.js +41 -0
- package/dist/esm/components/FormGroup.d.ts +58 -0
- package/dist/esm/components/FormGroup.js +58 -0
- package/dist/esm/components/FormInputVariants.d.ts +298 -0
- package/dist/esm/components/FormInputVariants.js +298 -0
- package/dist/esm/components/Image.d.ts +106 -0
- package/dist/esm/components/Image.js +106 -0
- package/dist/esm/components/Input.d.ts +65 -0
- package/dist/esm/components/Input.js +65 -0
- package/dist/esm/components/LayoutComponents.d.ts +258 -0
- package/dist/esm/components/LayoutComponents.js +258 -0
- package/dist/esm/components/Link.d.ts +71 -0
- package/dist/esm/components/Link.js +71 -0
- package/dist/esm/components/LoadingFeedbackComponents.d.ts +230 -0
- package/dist/esm/components/LoadingFeedbackComponents.js +230 -0
- package/dist/esm/components/Menu.d.ts +167 -0
- package/dist/esm/components/Menu.js +167 -0
- package/dist/esm/components/Modal.d.ts +145 -0
- package/dist/esm/components/Modal.js +145 -0
- package/dist/esm/components/ModalTypes.d.ts +288 -0
- package/dist/esm/components/ModalTypes.js +288 -0
- package/dist/esm/components/Navbar.d.ts +181 -0
- package/dist/esm/components/Navbar.js +181 -0
- package/dist/esm/components/NavigationComponents.d.ts +276 -0
- package/dist/esm/components/NavigationComponents.js +276 -0
- package/dist/esm/components/Pagination.d.ts +94 -0
- package/dist/esm/components/Pagination.js +94 -0
- package/dist/esm/components/Popover.d.ts +108 -0
- package/dist/esm/components/Popover.js +108 -0
- package/dist/esm/components/Progress.d.ts +68 -0
- package/dist/esm/components/Progress.js +68 -0
- package/dist/esm/components/Radio.d.ts +93 -0
- package/dist/esm/components/Radio.js +93 -0
- package/dist/esm/components/Rating.d.ts +82 -0
- package/dist/esm/components/Rating.js +82 -0
- package/dist/esm/components/Select.d.ts +74 -0
- package/dist/esm/components/Select.js +74 -0
- package/dist/esm/components/Sidebar.d.ts +174 -0
- package/dist/esm/components/Sidebar.js +174 -0
- package/dist/esm/components/Skeleton.d.ts +57 -0
- package/dist/esm/components/Skeleton.js +57 -0
- package/dist/esm/components/Slider.d.ts +64 -0
- package/dist/esm/components/Slider.js +64 -0
- package/dist/esm/components/Spinner.d.ts +70 -0
- package/dist/esm/components/Spinner.js +70 -0
- package/dist/esm/components/Stack.d.ts +75 -0
- package/dist/esm/components/Stack.js +75 -0
- package/dist/esm/components/Stepper.d.ts +150 -0
- package/dist/esm/components/Stepper.js +150 -0
- package/dist/esm/components/Table.d.ts +173 -0
- package/dist/esm/components/Table.js +173 -0
- package/dist/esm/components/Tabs.d.ts +104 -0
- package/dist/esm/components/Tabs.js +104 -0
- package/dist/esm/components/Tag.d.ts +74 -0
- package/dist/esm/components/Tag.js +74 -0
- package/dist/esm/components/Textarea.d.ts +75 -0
- package/dist/esm/components/Textarea.js +75 -0
- package/dist/esm/components/Timeline.d.ts +124 -0
- package/dist/esm/components/Timeline.js +124 -0
- package/dist/esm/components/Toast.d.ts +155 -0
- package/dist/esm/components/Toast.js +155 -0
- package/dist/esm/components/Toggle.d.ts +75 -0
- package/dist/esm/components/Toggle.js +75 -0
- package/dist/esm/components/Tooltip.d.ts +80 -0
- package/dist/esm/components/Tooltip.js +80 -0
- package/dist/esm/components/TypographyComponents.d.ts +265 -0
- package/dist/esm/components/TypographyComponents.js +265 -0
- package/dist/esm/components/UtilityComponents.d.ts +345 -0
- package/dist/esm/components/UtilityComponents.js +345 -0
- package/dist/esm/components/Video.d.ts +82 -0
- package/dist/esm/components/Video.js +82 -0
- package/dist/esm/components/index.d.ts +311 -0
- package/dist/esm/components/index.js +311 -0
- package/dist/esm/index.d.ts +5 -0
- package/dist/esm/index.js +5 -0
- package/dist/esm/styles/main.css +493 -0
- package/dist/esm/theme/ThemeProvider.d.ts +185 -0
- package/dist/esm/theme/ThemeProvider.js +185 -0
- package/dist/esm/theme/index.d.ts +2 -0
- package/dist/esm/theme/index.js +2 -0
- package/dist/esm/utilities/classNames.d.ts +200 -0
- package/dist/esm/utilities/classNames.js +200 -0
- package/dist/esm/utilities/index.d.ts +3 -0
- package/dist/esm/utilities/index.js +3 -0
- package/dist/esm/utilities/styles.d.ts +94 -0
- package/dist/esm/utilities/styles.js +94 -0
- package/dist/styles/main.css +493 -0
- package/package.json +124 -6
- package/src/styles/main.css +493 -0
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
export interface FormInputProps extends React.InputHTMLAttributes<HTMLInputElement> {
|
|
4
|
+
size?: 'sm' | 'md' | 'lg';
|
|
5
|
+
variant?: 'default' | 'outline' | 'filled' | 'flushed';
|
|
6
|
+
state?: 'default' | 'focus' | 'error' | 'success' | 'disabled';
|
|
7
|
+
label?: string;
|
|
8
|
+
error?: string;
|
|
9
|
+
helpText?: string;
|
|
10
|
+
prefix?: React.ReactNode;
|
|
11
|
+
suffix?: React.ReactNode;
|
|
12
|
+
icon?: React.ReactNode;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const baseSizeClass = {
|
|
16
|
+
sm: 'px-3 py-1.5 text-sm',
|
|
17
|
+
md: 'px-4 py-2 text-base',
|
|
18
|
+
lg: 'px-5 py-3 text-lg',
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const baseVariantClass = {
|
|
22
|
+
default: 'border border-gray-300 bg-white',
|
|
23
|
+
outline: 'border-2 border-gray-300 bg-white',
|
|
24
|
+
filled: 'bg-gray-100 border-b-2 border-gray-300',
|
|
25
|
+
flushed: 'bg-transparent border-b-2 border-gray-300',
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const baseStateClass = {
|
|
29
|
+
default: 'text-gray-900',
|
|
30
|
+
focus: 'border-blue-500 outline-none',
|
|
31
|
+
error: 'border-red-500 text-red-600',
|
|
32
|
+
success: 'border-green-500 text-green-600',
|
|
33
|
+
disabled: 'bg-gray-50 text-gray-400 cursor-not-allowed',
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export const InputText: React.FC<FormInputProps> = ({
|
|
37
|
+
size = 'md',
|
|
38
|
+
variant = 'default',
|
|
39
|
+
state = 'default',
|
|
40
|
+
label,
|
|
41
|
+
error,
|
|
42
|
+
helpText,
|
|
43
|
+
prefix,
|
|
44
|
+
suffix,
|
|
45
|
+
className = '',
|
|
46
|
+
...props
|
|
47
|
+
}) => {
|
|
48
|
+
return (
|
|
49
|
+
<div className="w-full">
|
|
50
|
+
{label && <label className="block text-sm font-medium text-gray-700 mb-1">{label}</label>}
|
|
51
|
+
<div className="relative flex items-center">
|
|
52
|
+
{prefix && <span className="absolute left-3 text-gray-500">{prefix}</span>}
|
|
53
|
+
<input
|
|
54
|
+
type="text"
|
|
55
|
+
className={`w-full rounded-lg transition-colors ${baseSizeClass[size]} ${baseVariantClass[variant]} ${baseStateClass[state]} ${prefix ? 'pl-10' : ''} ${suffix ? 'pr-10' : ''} ${className}`}
|
|
56
|
+
{...props}
|
|
57
|
+
/>
|
|
58
|
+
{suffix && <span className="absolute right-3 text-gray-500">{suffix}</span>}
|
|
59
|
+
</div>
|
|
60
|
+
{error && <p className="text-red-500 text-sm mt-1">{error}</p>}
|
|
61
|
+
{helpText && !error && <p className="text-gray-500 text-sm mt-1">{helpText}</p>}
|
|
62
|
+
</div>
|
|
63
|
+
);
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export const InputEmail: React.FC<FormInputProps> = ({
|
|
67
|
+
size = 'md',
|
|
68
|
+
variant = 'default',
|
|
69
|
+
state = 'default',
|
|
70
|
+
label,
|
|
71
|
+
error,
|
|
72
|
+
helpText,
|
|
73
|
+
prefix,
|
|
74
|
+
suffix,
|
|
75
|
+
className = '',
|
|
76
|
+
...props
|
|
77
|
+
}) => {
|
|
78
|
+
return (
|
|
79
|
+
<div className="w-full">
|
|
80
|
+
{label && <label className="block text-sm font-medium text-gray-700 mb-1">{label}</label>}
|
|
81
|
+
<div className="relative flex items-center">
|
|
82
|
+
{prefix && <span className="absolute left-3 text-gray-500">{prefix}</span>}
|
|
83
|
+
<input
|
|
84
|
+
type="email"
|
|
85
|
+
className={`w-full rounded-lg transition-colors ${baseSizeClass[size]} ${baseVariantClass[variant]} ${baseStateClass[state]} ${prefix ? 'pl-10' : ''} ${suffix ? 'pr-10' : ''} ${className}`}
|
|
86
|
+
{...props}
|
|
87
|
+
/>
|
|
88
|
+
{suffix && <span className="absolute right-3 text-gray-500">{suffix}</span>}
|
|
89
|
+
</div>
|
|
90
|
+
{error && <p className="text-red-500 text-sm mt-1">{error}</p>}
|
|
91
|
+
{helpText && !error && <p className="text-gray-500 text-sm mt-1">{helpText}</p>}
|
|
92
|
+
</div>
|
|
93
|
+
);
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
export const InputPassword: React.FC<FormInputProps & { showPassword?: boolean; onTogglePassword?: () => void }> = ({
|
|
97
|
+
size = 'md',
|
|
98
|
+
variant = 'default',
|
|
99
|
+
state = 'default',
|
|
100
|
+
label,
|
|
101
|
+
error,
|
|
102
|
+
helpText,
|
|
103
|
+
prefix,
|
|
104
|
+
showPassword = false,
|
|
105
|
+
onTogglePassword,
|
|
106
|
+
className = '',
|
|
107
|
+
...props
|
|
108
|
+
}) => {
|
|
109
|
+
return (
|
|
110
|
+
<div className="w-full">
|
|
111
|
+
{label && <label className="block text-sm font-medium text-gray-700 mb-1">{label}</label>}
|
|
112
|
+
<div className="relative flex items-center">
|
|
113
|
+
{prefix && <span className="absolute left-3 text-gray-500">{prefix}</span>}
|
|
114
|
+
<input
|
|
115
|
+
type={showPassword ? 'text' : 'password'}
|
|
116
|
+
className={`w-full rounded-lg transition-colors ${baseSizeClass[size]} ${baseVariantClass[variant]} ${baseStateClass[state]} ${prefix ? 'pl-10' : ''} pr-10 ${className}`}
|
|
117
|
+
{...props}
|
|
118
|
+
/>
|
|
119
|
+
<button
|
|
120
|
+
type="button"
|
|
121
|
+
onClick={onTogglePassword}
|
|
122
|
+
className="absolute right-3 text-gray-500 hover:text-gray-700"
|
|
123
|
+
>
|
|
124
|
+
{showPassword ? '👁️' : '👁️🗨️'}
|
|
125
|
+
</button>
|
|
126
|
+
</div>
|
|
127
|
+
{error && <p className="text-red-500 text-sm mt-1">{error}</p>}
|
|
128
|
+
{helpText && !error && <p className="text-gray-500 text-sm mt-1">{helpText}</p>}
|
|
129
|
+
</div>
|
|
130
|
+
);
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
export const InputNumber: React.FC<FormInputProps> = ({
|
|
134
|
+
size = 'md',
|
|
135
|
+
variant = 'default',
|
|
136
|
+
state = 'default',
|
|
137
|
+
label,
|
|
138
|
+
error,
|
|
139
|
+
helpText,
|
|
140
|
+
prefix,
|
|
141
|
+
suffix,
|
|
142
|
+
className = '',
|
|
143
|
+
...props
|
|
144
|
+
}) => {
|
|
145
|
+
return (
|
|
146
|
+
<div className="w-full">
|
|
147
|
+
{label && <label className="block text-sm font-medium text-gray-700 mb-1">{label}</label>}
|
|
148
|
+
<div className="relative flex items-center">
|
|
149
|
+
{prefix && <span className="absolute left-3 text-gray-500">{prefix}</span>}
|
|
150
|
+
<input
|
|
151
|
+
type="number"
|
|
152
|
+
className={`w-full rounded-lg transition-colors ${baseSizeClass[size]} ${baseVariantClass[variant]} ${baseStateClass[state]} ${prefix ? 'pl-10' : ''} ${suffix ? 'pr-10' : ''} ${className}`}
|
|
153
|
+
{...props}
|
|
154
|
+
/>
|
|
155
|
+
{suffix && <span className="absolute right-3 text-gray-500">{suffix}</span>}
|
|
156
|
+
</div>
|
|
157
|
+
{error && <p className="text-red-500 text-sm mt-1">{error}</p>}
|
|
158
|
+
{helpText && !error && <p className="text-gray-500 text-sm mt-1">{helpText}</p>}
|
|
159
|
+
</div>
|
|
160
|
+
);
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
export const InputDate: React.FC<FormInputProps> = ({
|
|
164
|
+
size = 'md',
|
|
165
|
+
variant = 'default',
|
|
166
|
+
state = 'default',
|
|
167
|
+
label,
|
|
168
|
+
error,
|
|
169
|
+
helpText,
|
|
170
|
+
className = '',
|
|
171
|
+
...props
|
|
172
|
+
}) => {
|
|
173
|
+
return (
|
|
174
|
+
<div className="w-full">
|
|
175
|
+
{label && <label className="block text-sm font-medium text-gray-700 mb-1">{label}</label>}
|
|
176
|
+
<input
|
|
177
|
+
type="date"
|
|
178
|
+
className={`w-full rounded-lg transition-colors ${baseSizeClass[size]} ${baseVariantClass[variant]} ${baseStateClass[state]} ${className}`}
|
|
179
|
+
{...props}
|
|
180
|
+
/>
|
|
181
|
+
{error && <p className="text-red-500 text-sm mt-1">{error}</p>}
|
|
182
|
+
{helpText && !error && <p className="text-gray-500 text-sm mt-1">{helpText}</p>}
|
|
183
|
+
</div>
|
|
184
|
+
);
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
export const InputTime: React.FC<FormInputProps> = ({
|
|
188
|
+
size = 'md',
|
|
189
|
+
variant = 'default',
|
|
190
|
+
state = 'default',
|
|
191
|
+
label,
|
|
192
|
+
error,
|
|
193
|
+
helpText,
|
|
194
|
+
className = '',
|
|
195
|
+
...props
|
|
196
|
+
}) => {
|
|
197
|
+
return (
|
|
198
|
+
<div className="w-full">
|
|
199
|
+
{label && <label className="block text-sm font-medium text-gray-700 mb-1">{label}</label>}
|
|
200
|
+
<input
|
|
201
|
+
type="time"
|
|
202
|
+
className={`w-full rounded-lg transition-colors ${baseSizeClass[size]} ${baseVariantClass[variant]} ${baseStateClass[state]} ${className}`}
|
|
203
|
+
{...props}
|
|
204
|
+
/>
|
|
205
|
+
{error && <p className="text-red-500 text-sm mt-1">{error}</p>}
|
|
206
|
+
{helpText && !error && <p className="text-gray-500 text-sm mt-1">{helpText}</p>}
|
|
207
|
+
</div>
|
|
208
|
+
);
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
export const InputTel: React.FC<FormInputProps> = ({
|
|
212
|
+
size = 'md',
|
|
213
|
+
variant = 'default',
|
|
214
|
+
state = 'default',
|
|
215
|
+
label,
|
|
216
|
+
error,
|
|
217
|
+
helpText,
|
|
218
|
+
prefix,
|
|
219
|
+
suffix,
|
|
220
|
+
className = '',
|
|
221
|
+
...props
|
|
222
|
+
}) => {
|
|
223
|
+
return (
|
|
224
|
+
<div className="w-full">
|
|
225
|
+
{label && <label className="block text-sm font-medium text-gray-700 mb-1">{label}</label>}
|
|
226
|
+
<div className="relative flex items-center">
|
|
227
|
+
{prefix && <span className="absolute left-3 text-gray-500">{prefix}</span>}
|
|
228
|
+
<input
|
|
229
|
+
type="tel"
|
|
230
|
+
className={`w-full rounded-lg transition-colors ${baseSizeClass[size]} ${baseVariantClass[variant]} ${baseStateClass[state]} ${prefix ? 'pl-10' : ''} ${suffix ? 'pr-10' : ''} ${className}`}
|
|
231
|
+
{...props}
|
|
232
|
+
/>
|
|
233
|
+
{suffix && <span className="absolute right-3 text-gray-500">{suffix}</span>}
|
|
234
|
+
</div>
|
|
235
|
+
{error && <p className="text-red-500 text-sm mt-1">{error}</p>}
|
|
236
|
+
{helpText && !error && <p className="text-gray-500 text-sm mt-1">{helpText}</p>}
|
|
237
|
+
</div>
|
|
238
|
+
);
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
export const InputUrl: React.FC<FormInputProps> = ({
|
|
242
|
+
size = 'md',
|
|
243
|
+
variant = 'default',
|
|
244
|
+
state = 'default',
|
|
245
|
+
label,
|
|
246
|
+
error,
|
|
247
|
+
helpText,
|
|
248
|
+
prefix,
|
|
249
|
+
suffix,
|
|
250
|
+
className = '',
|
|
251
|
+
...props
|
|
252
|
+
}) => {
|
|
253
|
+
return (
|
|
254
|
+
<div className="w-full">
|
|
255
|
+
{label && <label className="block text-sm font-medium text-gray-700 mb-1">{label}</label>}
|
|
256
|
+
<div className="relative flex items-center">
|
|
257
|
+
{prefix && <span className="absolute left-3 text-gray-500">{prefix}</span>}
|
|
258
|
+
<input
|
|
259
|
+
type="url"
|
|
260
|
+
className={`w-full rounded-lg transition-colors ${baseSizeClass[size]} ${baseVariantClass[variant]} ${baseStateClass[state]} ${prefix ? 'pl-10' : ''} ${suffix ? 'pr-10' : ''} ${className}`}
|
|
261
|
+
{...props}
|
|
262
|
+
/>
|
|
263
|
+
{suffix && <span className="absolute right-3 text-gray-500">{suffix}</span>}
|
|
264
|
+
</div>
|
|
265
|
+
{error && <p className="text-red-500 text-sm mt-1">{error}</p>}
|
|
266
|
+
{helpText && !error && <p className="text-gray-500 text-sm mt-1">{helpText}</p>}
|
|
267
|
+
</div>
|
|
268
|
+
);
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
export const InputSearch: React.FC<FormInputProps & { onSearch?: (value: string) => void }> = ({
|
|
272
|
+
size = 'md',
|
|
273
|
+
variant = 'default',
|
|
274
|
+
state = 'default',
|
|
275
|
+
label,
|
|
276
|
+
error,
|
|
277
|
+
helpText,
|
|
278
|
+
onSearch,
|
|
279
|
+
className = '',
|
|
280
|
+
...props
|
|
281
|
+
}) => {
|
|
282
|
+
return (
|
|
283
|
+
<div className="w-full">
|
|
284
|
+
{label && <label className="block text-sm font-medium text-gray-700 mb-1">{label}</label>}
|
|
285
|
+
<div className="relative flex items-center">
|
|
286
|
+
<span className="absolute left-3 text-gray-500">🔍</span>
|
|
287
|
+
<input
|
|
288
|
+
type="search"
|
|
289
|
+
className={`w-full rounded-lg transition-colors ${baseSizeClass[size]} ${baseVariantClass[variant]} ${baseStateClass[state]} pl-10 ${className}`}
|
|
290
|
+
onChange={(e) => onSearch?.(e.target.value)}
|
|
291
|
+
{...props}
|
|
292
|
+
/>
|
|
293
|
+
</div>
|
|
294
|
+
{error && <p className="text-red-500 text-sm mt-1">{error}</p>}
|
|
295
|
+
{helpText && !error && <p className="text-gray-500 text-sm mt-1">{helpText}</p>}
|
|
296
|
+
</div>
|
|
297
|
+
);
|
|
298
|
+
};
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
export interface FormInputProps extends React.InputHTMLAttributes<HTMLInputElement> {
|
|
4
|
+
size?: 'sm' | 'md' | 'lg';
|
|
5
|
+
variant?: 'default' | 'outline' | 'filled' | 'flushed';
|
|
6
|
+
state?: 'default' | 'focus' | 'error' | 'success' | 'disabled';
|
|
7
|
+
label?: string;
|
|
8
|
+
error?: string;
|
|
9
|
+
helpText?: string;
|
|
10
|
+
prefix?: React.ReactNode;
|
|
11
|
+
suffix?: React.ReactNode;
|
|
12
|
+
icon?: React.ReactNode;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const baseSizeClass = {
|
|
16
|
+
sm: 'px-3 py-1.5 text-sm',
|
|
17
|
+
md: 'px-4 py-2 text-base',
|
|
18
|
+
lg: 'px-5 py-3 text-lg',
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const baseVariantClass = {
|
|
22
|
+
default: 'border border-gray-300 bg-white',
|
|
23
|
+
outline: 'border-2 border-gray-300 bg-white',
|
|
24
|
+
filled: 'bg-gray-100 border-b-2 border-gray-300',
|
|
25
|
+
flushed: 'bg-transparent border-b-2 border-gray-300',
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const baseStateClass = {
|
|
29
|
+
default: 'text-gray-900',
|
|
30
|
+
focus: 'border-blue-500 outline-none',
|
|
31
|
+
error: 'border-red-500 text-red-600',
|
|
32
|
+
success: 'border-green-500 text-green-600',
|
|
33
|
+
disabled: 'bg-gray-50 text-gray-400 cursor-not-allowed',
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export const InputText: React.FC<FormInputProps> = ({
|
|
37
|
+
size = 'md',
|
|
38
|
+
variant = 'default',
|
|
39
|
+
state = 'default',
|
|
40
|
+
label,
|
|
41
|
+
error,
|
|
42
|
+
helpText,
|
|
43
|
+
prefix,
|
|
44
|
+
suffix,
|
|
45
|
+
className = '',
|
|
46
|
+
...props
|
|
47
|
+
}) => {
|
|
48
|
+
return (
|
|
49
|
+
<div className="w-full">
|
|
50
|
+
{label && <label className="block text-sm font-medium text-gray-700 mb-1">{label}</label>}
|
|
51
|
+
<div className="relative flex items-center">
|
|
52
|
+
{prefix && <span className="absolute left-3 text-gray-500">{prefix}</span>}
|
|
53
|
+
<input
|
|
54
|
+
type="text"
|
|
55
|
+
className={`w-full rounded-lg transition-colors ${baseSizeClass[size]} ${baseVariantClass[variant]} ${baseStateClass[state]} ${prefix ? 'pl-10' : ''} ${suffix ? 'pr-10' : ''} ${className}`}
|
|
56
|
+
{...props}
|
|
57
|
+
/>
|
|
58
|
+
{suffix && <span className="absolute right-3 text-gray-500">{suffix}</span>}
|
|
59
|
+
</div>
|
|
60
|
+
{error && <p className="text-red-500 text-sm mt-1">{error}</p>}
|
|
61
|
+
{helpText && !error && <p className="text-gray-500 text-sm mt-1">{helpText}</p>}
|
|
62
|
+
</div>
|
|
63
|
+
);
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export const InputEmail: React.FC<FormInputProps> = ({
|
|
67
|
+
size = 'md',
|
|
68
|
+
variant = 'default',
|
|
69
|
+
state = 'default',
|
|
70
|
+
label,
|
|
71
|
+
error,
|
|
72
|
+
helpText,
|
|
73
|
+
prefix,
|
|
74
|
+
suffix,
|
|
75
|
+
className = '',
|
|
76
|
+
...props
|
|
77
|
+
}) => {
|
|
78
|
+
return (
|
|
79
|
+
<div className="w-full">
|
|
80
|
+
{label && <label className="block text-sm font-medium text-gray-700 mb-1">{label}</label>}
|
|
81
|
+
<div className="relative flex items-center">
|
|
82
|
+
{prefix && <span className="absolute left-3 text-gray-500">{prefix}</span>}
|
|
83
|
+
<input
|
|
84
|
+
type="email"
|
|
85
|
+
className={`w-full rounded-lg transition-colors ${baseSizeClass[size]} ${baseVariantClass[variant]} ${baseStateClass[state]} ${prefix ? 'pl-10' : ''} ${suffix ? 'pr-10' : ''} ${className}`}
|
|
86
|
+
{...props}
|
|
87
|
+
/>
|
|
88
|
+
{suffix && <span className="absolute right-3 text-gray-500">{suffix}</span>}
|
|
89
|
+
</div>
|
|
90
|
+
{error && <p className="text-red-500 text-sm mt-1">{error}</p>}
|
|
91
|
+
{helpText && !error && <p className="text-gray-500 text-sm mt-1">{helpText}</p>}
|
|
92
|
+
</div>
|
|
93
|
+
);
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
export const InputPassword: React.FC<FormInputProps & { showPassword?: boolean; onTogglePassword?: () => void }> = ({
|
|
97
|
+
size = 'md',
|
|
98
|
+
variant = 'default',
|
|
99
|
+
state = 'default',
|
|
100
|
+
label,
|
|
101
|
+
error,
|
|
102
|
+
helpText,
|
|
103
|
+
prefix,
|
|
104
|
+
showPassword = false,
|
|
105
|
+
onTogglePassword,
|
|
106
|
+
className = '',
|
|
107
|
+
...props
|
|
108
|
+
}) => {
|
|
109
|
+
return (
|
|
110
|
+
<div className="w-full">
|
|
111
|
+
{label && <label className="block text-sm font-medium text-gray-700 mb-1">{label}</label>}
|
|
112
|
+
<div className="relative flex items-center">
|
|
113
|
+
{prefix && <span className="absolute left-3 text-gray-500">{prefix}</span>}
|
|
114
|
+
<input
|
|
115
|
+
type={showPassword ? 'text' : 'password'}
|
|
116
|
+
className={`w-full rounded-lg transition-colors ${baseSizeClass[size]} ${baseVariantClass[variant]} ${baseStateClass[state]} ${prefix ? 'pl-10' : ''} pr-10 ${className}`}
|
|
117
|
+
{...props}
|
|
118
|
+
/>
|
|
119
|
+
<button
|
|
120
|
+
type="button"
|
|
121
|
+
onClick={onTogglePassword}
|
|
122
|
+
className="absolute right-3 text-gray-500 hover:text-gray-700"
|
|
123
|
+
>
|
|
124
|
+
{showPassword ? '👁️' : '👁️🗨️'}
|
|
125
|
+
</button>
|
|
126
|
+
</div>
|
|
127
|
+
{error && <p className="text-red-500 text-sm mt-1">{error}</p>}
|
|
128
|
+
{helpText && !error && <p className="text-gray-500 text-sm mt-1">{helpText}</p>}
|
|
129
|
+
</div>
|
|
130
|
+
);
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
export const InputNumber: React.FC<FormInputProps> = ({
|
|
134
|
+
size = 'md',
|
|
135
|
+
variant = 'default',
|
|
136
|
+
state = 'default',
|
|
137
|
+
label,
|
|
138
|
+
error,
|
|
139
|
+
helpText,
|
|
140
|
+
prefix,
|
|
141
|
+
suffix,
|
|
142
|
+
className = '',
|
|
143
|
+
...props
|
|
144
|
+
}) => {
|
|
145
|
+
return (
|
|
146
|
+
<div className="w-full">
|
|
147
|
+
{label && <label className="block text-sm font-medium text-gray-700 mb-1">{label}</label>}
|
|
148
|
+
<div className="relative flex items-center">
|
|
149
|
+
{prefix && <span className="absolute left-3 text-gray-500">{prefix}</span>}
|
|
150
|
+
<input
|
|
151
|
+
type="number"
|
|
152
|
+
className={`w-full rounded-lg transition-colors ${baseSizeClass[size]} ${baseVariantClass[variant]} ${baseStateClass[state]} ${prefix ? 'pl-10' : ''} ${suffix ? 'pr-10' : ''} ${className}`}
|
|
153
|
+
{...props}
|
|
154
|
+
/>
|
|
155
|
+
{suffix && <span className="absolute right-3 text-gray-500">{suffix}</span>}
|
|
156
|
+
</div>
|
|
157
|
+
{error && <p className="text-red-500 text-sm mt-1">{error}</p>}
|
|
158
|
+
{helpText && !error && <p className="text-gray-500 text-sm mt-1">{helpText}</p>}
|
|
159
|
+
</div>
|
|
160
|
+
);
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
export const InputDate: React.FC<FormInputProps> = ({
|
|
164
|
+
size = 'md',
|
|
165
|
+
variant = 'default',
|
|
166
|
+
state = 'default',
|
|
167
|
+
label,
|
|
168
|
+
error,
|
|
169
|
+
helpText,
|
|
170
|
+
className = '',
|
|
171
|
+
...props
|
|
172
|
+
}) => {
|
|
173
|
+
return (
|
|
174
|
+
<div className="w-full">
|
|
175
|
+
{label && <label className="block text-sm font-medium text-gray-700 mb-1">{label}</label>}
|
|
176
|
+
<input
|
|
177
|
+
type="date"
|
|
178
|
+
className={`w-full rounded-lg transition-colors ${baseSizeClass[size]} ${baseVariantClass[variant]} ${baseStateClass[state]} ${className}`}
|
|
179
|
+
{...props}
|
|
180
|
+
/>
|
|
181
|
+
{error && <p className="text-red-500 text-sm mt-1">{error}</p>}
|
|
182
|
+
{helpText && !error && <p className="text-gray-500 text-sm mt-1">{helpText}</p>}
|
|
183
|
+
</div>
|
|
184
|
+
);
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
export const InputTime: React.FC<FormInputProps> = ({
|
|
188
|
+
size = 'md',
|
|
189
|
+
variant = 'default',
|
|
190
|
+
state = 'default',
|
|
191
|
+
label,
|
|
192
|
+
error,
|
|
193
|
+
helpText,
|
|
194
|
+
className = '',
|
|
195
|
+
...props
|
|
196
|
+
}) => {
|
|
197
|
+
return (
|
|
198
|
+
<div className="w-full">
|
|
199
|
+
{label && <label className="block text-sm font-medium text-gray-700 mb-1">{label}</label>}
|
|
200
|
+
<input
|
|
201
|
+
type="time"
|
|
202
|
+
className={`w-full rounded-lg transition-colors ${baseSizeClass[size]} ${baseVariantClass[variant]} ${baseStateClass[state]} ${className}`}
|
|
203
|
+
{...props}
|
|
204
|
+
/>
|
|
205
|
+
{error && <p className="text-red-500 text-sm mt-1">{error}</p>}
|
|
206
|
+
{helpText && !error && <p className="text-gray-500 text-sm mt-1">{helpText}</p>}
|
|
207
|
+
</div>
|
|
208
|
+
);
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
export const InputTel: React.FC<FormInputProps> = ({
|
|
212
|
+
size = 'md',
|
|
213
|
+
variant = 'default',
|
|
214
|
+
state = 'default',
|
|
215
|
+
label,
|
|
216
|
+
error,
|
|
217
|
+
helpText,
|
|
218
|
+
prefix,
|
|
219
|
+
suffix,
|
|
220
|
+
className = '',
|
|
221
|
+
...props
|
|
222
|
+
}) => {
|
|
223
|
+
return (
|
|
224
|
+
<div className="w-full">
|
|
225
|
+
{label && <label className="block text-sm font-medium text-gray-700 mb-1">{label}</label>}
|
|
226
|
+
<div className="relative flex items-center">
|
|
227
|
+
{prefix && <span className="absolute left-3 text-gray-500">{prefix}</span>}
|
|
228
|
+
<input
|
|
229
|
+
type="tel"
|
|
230
|
+
className={`w-full rounded-lg transition-colors ${baseSizeClass[size]} ${baseVariantClass[variant]} ${baseStateClass[state]} ${prefix ? 'pl-10' : ''} ${suffix ? 'pr-10' : ''} ${className}`}
|
|
231
|
+
{...props}
|
|
232
|
+
/>
|
|
233
|
+
{suffix && <span className="absolute right-3 text-gray-500">{suffix}</span>}
|
|
234
|
+
</div>
|
|
235
|
+
{error && <p className="text-red-500 text-sm mt-1">{error}</p>}
|
|
236
|
+
{helpText && !error && <p className="text-gray-500 text-sm mt-1">{helpText}</p>}
|
|
237
|
+
</div>
|
|
238
|
+
);
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
export const InputUrl: React.FC<FormInputProps> = ({
|
|
242
|
+
size = 'md',
|
|
243
|
+
variant = 'default',
|
|
244
|
+
state = 'default',
|
|
245
|
+
label,
|
|
246
|
+
error,
|
|
247
|
+
helpText,
|
|
248
|
+
prefix,
|
|
249
|
+
suffix,
|
|
250
|
+
className = '',
|
|
251
|
+
...props
|
|
252
|
+
}) => {
|
|
253
|
+
return (
|
|
254
|
+
<div className="w-full">
|
|
255
|
+
{label && <label className="block text-sm font-medium text-gray-700 mb-1">{label}</label>}
|
|
256
|
+
<div className="relative flex items-center">
|
|
257
|
+
{prefix && <span className="absolute left-3 text-gray-500">{prefix}</span>}
|
|
258
|
+
<input
|
|
259
|
+
type="url"
|
|
260
|
+
className={`w-full rounded-lg transition-colors ${baseSizeClass[size]} ${baseVariantClass[variant]} ${baseStateClass[state]} ${prefix ? 'pl-10' : ''} ${suffix ? 'pr-10' : ''} ${className}`}
|
|
261
|
+
{...props}
|
|
262
|
+
/>
|
|
263
|
+
{suffix && <span className="absolute right-3 text-gray-500">{suffix}</span>}
|
|
264
|
+
</div>
|
|
265
|
+
{error && <p className="text-red-500 text-sm mt-1">{error}</p>}
|
|
266
|
+
{helpText && !error && <p className="text-gray-500 text-sm mt-1">{helpText}</p>}
|
|
267
|
+
</div>
|
|
268
|
+
);
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
export const InputSearch: React.FC<FormInputProps & { onSearch?: (value: string) => void }> = ({
|
|
272
|
+
size = 'md',
|
|
273
|
+
variant = 'default',
|
|
274
|
+
state = 'default',
|
|
275
|
+
label,
|
|
276
|
+
error,
|
|
277
|
+
helpText,
|
|
278
|
+
onSearch,
|
|
279
|
+
className = '',
|
|
280
|
+
...props
|
|
281
|
+
}) => {
|
|
282
|
+
return (
|
|
283
|
+
<div className="w-full">
|
|
284
|
+
{label && <label className="block text-sm font-medium text-gray-700 mb-1">{label}</label>}
|
|
285
|
+
<div className="relative flex items-center">
|
|
286
|
+
<span className="absolute left-3 text-gray-500">🔍</span>
|
|
287
|
+
<input
|
|
288
|
+
type="search"
|
|
289
|
+
className={`w-full rounded-lg transition-colors ${baseSizeClass[size]} ${baseVariantClass[variant]} ${baseStateClass[state]} pl-10 ${className}`}
|
|
290
|
+
onChange={(e) => onSearch?.(e.target.value)}
|
|
291
|
+
{...props}
|
|
292
|
+
/>
|
|
293
|
+
</div>
|
|
294
|
+
{error && <p className="text-red-500 text-sm mt-1">{error}</p>}
|
|
295
|
+
{helpText && !error && <p className="text-gray-500 text-sm mt-1">{helpText}</p>}
|
|
296
|
+
</div>
|
|
297
|
+
);
|
|
298
|
+
};
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { cn } from '../utilities/classNames';
|
|
3
|
+
|
|
4
|
+
export interface ImageProps extends React.ImgHTMLAttributes<HTMLImageElement> {
|
|
5
|
+
src: string;
|
|
6
|
+
alt: string;
|
|
7
|
+
width?: number | string;
|
|
8
|
+
height?: number | string;
|
|
9
|
+
fallback?: React.ReactNode;
|
|
10
|
+
rounded?: boolean;
|
|
11
|
+
border?: boolean;
|
|
12
|
+
shadow?: boolean;
|
|
13
|
+
aspect?: 'square' | 'video' | 'auto';
|
|
14
|
+
objectFit?: 'cover' | 'contain' | 'fill' | 'scale-down';
|
|
15
|
+
onLoadingStateChange?: (state: 'loading' | 'loaded' | 'error') => void;
|
|
16
|
+
className?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const Image = React.forwardRef<HTMLImageElement, ImageProps>(
|
|
20
|
+
(
|
|
21
|
+
{
|
|
22
|
+
src,
|
|
23
|
+
alt,
|
|
24
|
+
width,
|
|
25
|
+
height,
|
|
26
|
+
fallback,
|
|
27
|
+
rounded = false,
|
|
28
|
+
border = false,
|
|
29
|
+
shadow = false,
|
|
30
|
+
aspect = 'auto',
|
|
31
|
+
objectFit = 'cover',
|
|
32
|
+
onLoadingStateChange,
|
|
33
|
+
className,
|
|
34
|
+
...props
|
|
35
|
+
},
|
|
36
|
+
ref
|
|
37
|
+
) => {
|
|
38
|
+
const [error, setError] = React.useState(false);
|
|
39
|
+
const [loading, setLoading] = React.useState(true);
|
|
40
|
+
|
|
41
|
+
const aspectClasses = {
|
|
42
|
+
square: 'aspect-square',
|
|
43
|
+
video: 'aspect-video',
|
|
44
|
+
auto: 'auto',
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const handleLoad = () => {
|
|
48
|
+
setLoading(false);
|
|
49
|
+
onLoadingStateChange?.('loaded');
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const handleError = () => {
|
|
53
|
+
setError(true);
|
|
54
|
+
setLoading(false);
|
|
55
|
+
onLoadingStateChange?.('error');
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
if (error && fallback) {
|
|
59
|
+
return <>{fallback}</>;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<div
|
|
64
|
+
className={cn(
|
|
65
|
+
'relative bg-gray-100',
|
|
66
|
+
aspectClasses[aspect] !== 'auto' && aspectClasses[aspect],
|
|
67
|
+
{
|
|
68
|
+
'rounded-lg': rounded,
|
|
69
|
+
'border border-gray-200': border,
|
|
70
|
+
'shadow-md': shadow,
|
|
71
|
+
},
|
|
72
|
+
aspect === 'auto' && !height && 'w-full'
|
|
73
|
+
)}
|
|
74
|
+
style={{
|
|
75
|
+
width: typeof width === 'number' ? `${width}px` : width,
|
|
76
|
+
height: typeof height === 'number' ? `${height}px` : height,
|
|
77
|
+
}}
|
|
78
|
+
>
|
|
79
|
+
<img
|
|
80
|
+
ref={ref}
|
|
81
|
+
src={src}
|
|
82
|
+
alt={alt}
|
|
83
|
+
onLoad={handleLoad}
|
|
84
|
+
onError={handleError}
|
|
85
|
+
className={cn(
|
|
86
|
+
'w-full h-full',
|
|
87
|
+
{
|
|
88
|
+
rounded: rounded,
|
|
89
|
+
'animate-pulse': loading,
|
|
90
|
+
},
|
|
91
|
+
className
|
|
92
|
+
)}
|
|
93
|
+
style={{
|
|
94
|
+
objectFit: objectFit,
|
|
95
|
+
}}
|
|
96
|
+
{...props}
|
|
97
|
+
/>
|
|
98
|
+
{loading && (
|
|
99
|
+
<div className="absolute inset-0 bg-gray-200 animate-pulse rounded-lg" />
|
|
100
|
+
)}
|
|
101
|
+
</div>
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
Image.displayName = 'Image';
|