@moontra/moonui 2.3.10 → 2.4.1
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/dist/index.d.mts +73 -39
- package/dist/index.d.ts +73 -39
- package/dist/index.global.js +540 -38
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +1211 -574
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1210 -574
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/components/ui/index.ts +77 -0
- package/src/components/ui/input.tsx +92 -19
- package/src/components/ui/moon-logo.tsx +13 -16
- package/src/components/ui/pagination.tsx +1 -1
- package/src/components/ui/textarea.tsx +194 -20
package/package.json
CHANGED
|
@@ -17,6 +17,10 @@ export {
|
|
|
17
17
|
AlertDescription as MoonUIAlertDescription,
|
|
18
18
|
} from "./alert";
|
|
19
19
|
|
|
20
|
+
export type {
|
|
21
|
+
AlertProps as MoonUIAlertProps,
|
|
22
|
+
} from "./alert";
|
|
23
|
+
|
|
20
24
|
// AspectRatio
|
|
21
25
|
export { AspectRatio as MoonUIAspectRatio } from "./aspect-ratio";
|
|
22
26
|
|
|
@@ -28,12 +32,20 @@ export {
|
|
|
28
32
|
AvatarGroup as MoonUIAvatarGroup,
|
|
29
33
|
} from "./avatar";
|
|
30
34
|
|
|
35
|
+
export type {
|
|
36
|
+
AvatarProps as MoonUIAvatarProps,
|
|
37
|
+
} from "./avatar";
|
|
38
|
+
|
|
31
39
|
// Badge
|
|
32
40
|
export {
|
|
33
41
|
Badge as MoonUIBadge,
|
|
34
42
|
badgeVariants as moonUIBadgeVariants,
|
|
35
43
|
} from "./badge";
|
|
36
44
|
|
|
45
|
+
export type {
|
|
46
|
+
BadgeProps as MoonUIBadgeProps,
|
|
47
|
+
} from "./badge";
|
|
48
|
+
|
|
37
49
|
// Breadcrumb
|
|
38
50
|
export {
|
|
39
51
|
Breadcrumb as MoonUIBreadcrumb,
|
|
@@ -51,6 +63,10 @@ export {
|
|
|
51
63
|
buttonVariants as moonUIButtonVariants,
|
|
52
64
|
} from "./button";
|
|
53
65
|
|
|
66
|
+
export type {
|
|
67
|
+
ButtonProps as MoonUIButtonProps,
|
|
68
|
+
} from "./button";
|
|
69
|
+
|
|
54
70
|
// Card
|
|
55
71
|
export {
|
|
56
72
|
Card as MoonUICard,
|
|
@@ -61,6 +77,19 @@ export {
|
|
|
61
77
|
CardContent as MoonUICardContent,
|
|
62
78
|
} from "./card";
|
|
63
79
|
|
|
80
|
+
export type {
|
|
81
|
+
CardProps as MoonUICardProps,
|
|
82
|
+
} from "./card";
|
|
83
|
+
|
|
84
|
+
// Calendar
|
|
85
|
+
export {
|
|
86
|
+
Calendar as MoonUICalendar,
|
|
87
|
+
} from "./calendar";
|
|
88
|
+
|
|
89
|
+
export type {
|
|
90
|
+
CalendarProps as MoonUICalendarProps,
|
|
91
|
+
} from "./calendar";
|
|
92
|
+
|
|
64
93
|
// Card Input
|
|
65
94
|
export {
|
|
66
95
|
CardNumberInput as MoonUICardNumberInput,
|
|
@@ -77,6 +106,10 @@ export {
|
|
|
77
106
|
CheckboxLabel as MoonUICheckboxLabel,
|
|
78
107
|
} from "./checkbox";
|
|
79
108
|
|
|
109
|
+
export type {
|
|
110
|
+
CheckboxProps as MoonUICheckboxProps,
|
|
111
|
+
} from "./checkbox";
|
|
112
|
+
|
|
80
113
|
// Collapsible
|
|
81
114
|
export {
|
|
82
115
|
Collapsible as MoonUICollapsible,
|
|
@@ -91,6 +124,10 @@ export {
|
|
|
91
124
|
GradientPicker as MoonUIGradientPicker,
|
|
92
125
|
} from "./color-picker";
|
|
93
126
|
|
|
127
|
+
export type {
|
|
128
|
+
ColorPickerProps as MoonUIColorPickerProps,
|
|
129
|
+
} from "./color-picker";
|
|
130
|
+
|
|
94
131
|
// Command
|
|
95
132
|
export {
|
|
96
133
|
Command as MoonUICommand,
|
|
@@ -117,6 +154,10 @@ export {
|
|
|
117
154
|
MonthPicker as MoonUIMonthPicker,
|
|
118
155
|
} from "./date-picker";
|
|
119
156
|
|
|
157
|
+
export type {
|
|
158
|
+
DatePickerProps as MoonUIDatePickerProps,
|
|
159
|
+
} from "./date-picker";
|
|
160
|
+
|
|
120
161
|
// Dialog
|
|
121
162
|
export {
|
|
122
163
|
Dialog as MoonUIDialog,
|
|
@@ -132,6 +173,10 @@ export {
|
|
|
132
173
|
// DraggableList
|
|
133
174
|
export { DraggableList as MoonUIDraggableList } from "./draggable-list";
|
|
134
175
|
|
|
176
|
+
export type {
|
|
177
|
+
DraggableListProps as MoonUIDraggableListProps,
|
|
178
|
+
} from "./draggable-list";
|
|
179
|
+
|
|
135
180
|
// DropdownMenu
|
|
136
181
|
export {
|
|
137
182
|
DropdownMenu as MoonUIDropdownMenu,
|
|
@@ -154,15 +199,27 @@ export {
|
|
|
154
199
|
// FileUpload
|
|
155
200
|
export { FileUpload as MoonUIFileUpload } from "./file-upload";
|
|
156
201
|
|
|
202
|
+
export type {
|
|
203
|
+
FileUploadProps as MoonUIFileUploadProps,
|
|
204
|
+
} from "./file-upload";
|
|
205
|
+
|
|
157
206
|
// GestureDrawer
|
|
158
207
|
export { GestureDrawer as MoonUIGestureDrawer } from "./gesture-drawer";
|
|
159
208
|
|
|
209
|
+
export type {
|
|
210
|
+
GestureDrawerProps as MoonUIGestureDrawerProps,
|
|
211
|
+
} from "./gesture-drawer";
|
|
212
|
+
|
|
160
213
|
// GitHubStars
|
|
161
214
|
export { GitHubStars as MoonUIGitHubStars } from "./github-stars";
|
|
162
215
|
|
|
163
216
|
// Input
|
|
164
217
|
export { Input as MoonUIInput } from "./input";
|
|
165
218
|
|
|
219
|
+
export type {
|
|
220
|
+
InputProps as MoonUIInputProps,
|
|
221
|
+
} from "./input";
|
|
222
|
+
|
|
166
223
|
// Label
|
|
167
224
|
export { Label as MoonUILabel } from "./label";
|
|
168
225
|
|
|
@@ -201,6 +258,10 @@ export {
|
|
|
201
258
|
// Progress
|
|
202
259
|
export { Progress as MoonUIProgress } from "./progress";
|
|
203
260
|
|
|
261
|
+
export type {
|
|
262
|
+
ProgressProps as MoonUIProgressProps,
|
|
263
|
+
} from "./progress";
|
|
264
|
+
|
|
204
265
|
// RadioGroup
|
|
205
266
|
export {
|
|
206
267
|
RadioGroup as MoonUIRadioGroup,
|
|
@@ -209,6 +270,10 @@ export {
|
|
|
209
270
|
RadioItemWithLabel as MoonUIRadioItemWithLabel,
|
|
210
271
|
} from "./radio-group";
|
|
211
272
|
|
|
273
|
+
export type {
|
|
274
|
+
RadioGroupProps as MoonUIRadioGroupProps,
|
|
275
|
+
} from "./radio-group";
|
|
276
|
+
|
|
212
277
|
// RichTextEditor
|
|
213
278
|
export { RichTextEditor as MoonUIRichTextEditor } from "./rich-text-editor";
|
|
214
279
|
|
|
@@ -254,6 +319,10 @@ export {
|
|
|
254
319
|
skeletonVariants as moonUISkeletonVariants,
|
|
255
320
|
} from "./skeleton";
|
|
256
321
|
|
|
322
|
+
export type {
|
|
323
|
+
SkeletonProps as MoonUISkeletonProps,
|
|
324
|
+
} from "./skeleton";
|
|
325
|
+
|
|
257
326
|
// Slider
|
|
258
327
|
export { Slider as MoonUISlider } from "./slider";
|
|
259
328
|
|
|
@@ -263,6 +332,10 @@ export { SwipeableCard as MoonUISwipeableCard } from "./swipeable-card";
|
|
|
263
332
|
// Switch
|
|
264
333
|
export { Switch as MoonUISwitch } from "./switch";
|
|
265
334
|
|
|
335
|
+
export type {
|
|
336
|
+
SwitchProps as MoonUISwitchProps,
|
|
337
|
+
} from "./switch";
|
|
338
|
+
|
|
266
339
|
// Table
|
|
267
340
|
export {
|
|
268
341
|
Table as MoonUITable,
|
|
@@ -289,6 +362,10 @@ export { TagsInput as MoonUITagsInput } from "./tags-input";
|
|
|
289
362
|
// Textarea
|
|
290
363
|
export { Textarea as MoonUITextarea } from "./textarea";
|
|
291
364
|
|
|
365
|
+
export type {
|
|
366
|
+
TextareaProps as MoonUITextareaProps,
|
|
367
|
+
} from "./textarea";
|
|
368
|
+
|
|
292
369
|
// Toast
|
|
293
370
|
export {
|
|
294
371
|
Toast as MoonUIToast,
|
|
@@ -39,10 +39,10 @@ const inputVariants = cva(
|
|
|
39
39
|
{
|
|
40
40
|
variants: {
|
|
41
41
|
variant: {
|
|
42
|
-
standard: "border border-gray-300 dark:border-gray-700 rounded-md px-3 py-2 hover:border-gray-400 dark:hover:border-gray-600 focus-visible:ring-2 focus-visible:ring-primary/30 dark:focus-visible:ring-primary/20 focus-visible:border-primary dark:focus-visible:border-primary/80 dark:bg-gray-
|
|
43
|
-
filled: "border border-transparent bg-gray-100 dark:bg-gray-800
|
|
42
|
+
standard: "border border-gray-300 dark:border-gray-700 rounded-md px-3 py-2 hover:border-gray-400 dark:hover:border-gray-600 focus-visible:ring-2 focus-visible:ring-primary/30 dark:focus-visible:ring-primary/20 focus-visible:border-primary dark:focus-visible:border-primary/80 dark:bg-gray-800/80 dark:shadow-inner dark:shadow-gray-950/10",
|
|
43
|
+
filled: "border border-transparent bg-gray-100 dark:bg-gray-800 rounded-md px-3 py-2 hover:bg-gray-200 dark:hover:bg-gray-700 focus-visible:ring-2 focus-visible:ring-primary/30 dark:focus-visible:ring-primary/20 dark:shadow-inner dark:shadow-gray-950/10",
|
|
44
44
|
ghost: "border-none bg-transparent shadow-none px-1 dark:text-gray-300 dark:placeholder:text-gray-500 hover:bg-gray-100/50 dark:hover:bg-gray-800/30 focus-visible:bg-transparent",
|
|
45
|
-
underline: "border-t-0 border-l-0 border-r-0 border-b border-gray-300 dark:border-gray-600 rounded-none px-0 py-2 hover:border-gray-400 dark:hover:border-gray-500 focus-visible:ring-0 focus-visible:border-b-2 focus-visible:border-primary dark:focus-visible:border-primary/80 dark:text-gray-300",
|
|
45
|
+
underline: "border-t-0 border-l-0 border-r-0 border-b border-gray-300 dark:border-gray-600 rounded-none px-0 py-2 hover:border-gray-400 dark:hover:border-gray-500 focus-visible:ring-0 focus-visible:border-b-2 focus-visible:border-primary dark:focus-visible:border-primary/80 dark:text-gray-300 dark:bg-transparent",
|
|
46
46
|
},
|
|
47
47
|
size: {
|
|
48
48
|
sm: "h-8 text-xs",
|
|
@@ -90,6 +90,12 @@ const inputVariants = cva(
|
|
|
90
90
|
export interface InputProps
|
|
91
91
|
extends Omit<React.InputHTMLAttributes<HTMLInputElement>, "size">,
|
|
92
92
|
Omit<VariantProps<typeof inputVariants>, "isDisabled" | "hasLeftIcon" | "hasRightIcon" | "hasRightButton"> {
|
|
93
|
+
/** Label metni */
|
|
94
|
+
label?: string;
|
|
95
|
+
/** Floating label animasyonu (Material Design pattern) */
|
|
96
|
+
floatingLabel?: boolean;
|
|
97
|
+
/** Floating label için ek sınıflar */
|
|
98
|
+
floatingLabelClassName?: string;
|
|
93
99
|
/** Hata mesajı */
|
|
94
100
|
error?: string;
|
|
95
101
|
/** Başarı mesajı */
|
|
@@ -125,14 +131,14 @@ export interface InputProps
|
|
|
125
131
|
* @param props.alwaysShowMessage - Mesajın her zaman görünür olması
|
|
126
132
|
*/
|
|
127
133
|
const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
|
128
|
-
({
|
|
129
|
-
className,
|
|
134
|
+
({
|
|
135
|
+
className,
|
|
130
136
|
wrapperClassName,
|
|
131
137
|
messageClassName,
|
|
132
|
-
variant,
|
|
133
|
-
size,
|
|
138
|
+
variant,
|
|
139
|
+
size,
|
|
134
140
|
isError,
|
|
135
|
-
disabled,
|
|
141
|
+
disabled,
|
|
136
142
|
error,
|
|
137
143
|
success,
|
|
138
144
|
loading,
|
|
@@ -140,8 +146,48 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
|
|
140
146
|
rightIcon,
|
|
141
147
|
rightButton,
|
|
142
148
|
alwaysShowMessage = false,
|
|
143
|
-
|
|
149
|
+
label,
|
|
150
|
+
floatingLabel = false,
|
|
151
|
+
floatingLabelClassName,
|
|
152
|
+
value,
|
|
153
|
+
defaultValue,
|
|
154
|
+
onChange,
|
|
155
|
+
onFocus,
|
|
156
|
+
onBlur,
|
|
157
|
+
placeholder,
|
|
158
|
+
...props
|
|
144
159
|
}, ref) => {
|
|
160
|
+
// Floating label state management
|
|
161
|
+
const [isFocused, setIsFocused] = React.useState(false);
|
|
162
|
+
const [internalValue, setInternalValue] = React.useState(value || defaultValue || "");
|
|
163
|
+
|
|
164
|
+
// Floating label aktif mi kontrolü
|
|
165
|
+
const hasValue = internalValue !== "" && internalValue !== null && internalValue !== undefined;
|
|
166
|
+
const isLabelActive = isFocused || hasValue;
|
|
167
|
+
|
|
168
|
+
// Event handler'lar
|
|
169
|
+
const handleFocus = (e: React.FocusEvent<HTMLInputElement>) => {
|
|
170
|
+
setIsFocused(true);
|
|
171
|
+
onFocus?.(e);
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
const handleBlur = (e: React.FocusEvent<HTMLInputElement>) => {
|
|
175
|
+
setIsFocused(false);
|
|
176
|
+
onBlur?.(e);
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
180
|
+
setInternalValue(e.target.value);
|
|
181
|
+
onChange?.(e);
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
// Value değişikliklerini takip et
|
|
185
|
+
React.useEffect(() => {
|
|
186
|
+
if (value !== undefined) {
|
|
187
|
+
setInternalValue(value);
|
|
188
|
+
}
|
|
189
|
+
}, [value]);
|
|
190
|
+
|
|
145
191
|
// Mesajı göster/gizle
|
|
146
192
|
const showMessage = alwaysShowMessage || error || success;
|
|
147
193
|
const messageType = error ? "error" : success ? "success" : "normal";
|
|
@@ -149,18 +195,39 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
|
|
149
195
|
return (
|
|
150
196
|
<div className="space-y-1.5 w-full">
|
|
151
197
|
<div className={cn("moonui-theme", inputWrapperVariants({ size }), wrapperClassName)}>
|
|
198
|
+
{/* Floating Label */}
|
|
199
|
+
{floatingLabel && label && (
|
|
200
|
+
<label
|
|
201
|
+
htmlFor={props.id}
|
|
202
|
+
className={cn(
|
|
203
|
+
"absolute transition-all duration-200 pointer-events-none",
|
|
204
|
+
leftIcon || loading ? "left-10" : "left-3",
|
|
205
|
+
"text-gray-500 dark:text-gray-400",
|
|
206
|
+
isLabelActive
|
|
207
|
+
? "top-0 -translate-y-1/2 text-xs bg-background dark:bg-gray-800 px-1"
|
|
208
|
+
: "top-1/2 -translate-y-1/2 text-sm",
|
|
209
|
+
isFocused && "text-primary dark:text-primary",
|
|
210
|
+
!!error && "text-error",
|
|
211
|
+
disabled && "opacity-50",
|
|
212
|
+
floatingLabelClassName
|
|
213
|
+
)}
|
|
214
|
+
>
|
|
215
|
+
{label}
|
|
216
|
+
</label>
|
|
217
|
+
)}
|
|
218
|
+
|
|
152
219
|
{leftIcon && (
|
|
153
220
|
<div className="absolute left-3 text-gray-500 flex items-center justify-center pointer-events-none">
|
|
154
221
|
{loading ? <Loader2 className="h-4 w-4 animate-spin" /> : leftIcon}
|
|
155
222
|
</div>
|
|
156
223
|
)}
|
|
157
|
-
|
|
224
|
+
|
|
158
225
|
<input
|
|
159
226
|
className={cn(
|
|
160
|
-
inputVariants({
|
|
161
|
-
variant,
|
|
162
|
-
size,
|
|
163
|
-
isError: !!error || isError,
|
|
227
|
+
inputVariants({
|
|
228
|
+
variant,
|
|
229
|
+
size,
|
|
230
|
+
isError: !!error || isError,
|
|
164
231
|
isSuccess: !!success,
|
|
165
232
|
isDisabled: disabled || loading,
|
|
166
233
|
hasLeftIcon: !!leftIcon || loading,
|
|
@@ -176,30 +243,36 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
|
|
176
243
|
data-success={!!success ? "" : undefined}
|
|
177
244
|
aria-invalid={!!error || !!isError || undefined}
|
|
178
245
|
aria-describedby={error ? `${props.id || ''}-error` : success ? `${props.id || ''}-success` : undefined}
|
|
246
|
+
value={value}
|
|
247
|
+
defaultValue={defaultValue}
|
|
248
|
+
onChange={handleChange}
|
|
249
|
+
onFocus={handleFocus}
|
|
250
|
+
onBlur={handleBlur}
|
|
251
|
+
placeholder={floatingLabel ? (isLabelActive ? placeholder : "") : placeholder}
|
|
179
252
|
{...props}
|
|
180
253
|
/>
|
|
181
|
-
|
|
254
|
+
|
|
182
255
|
{rightIcon && !loading && (
|
|
183
256
|
<div className="absolute right-3 text-gray-500 flex items-center justify-center pointer-events-none">
|
|
184
257
|
{rightIcon}
|
|
185
258
|
</div>
|
|
186
259
|
)}
|
|
187
|
-
|
|
260
|
+
|
|
188
261
|
{rightButton && (
|
|
189
262
|
<div className="absolute right-3">
|
|
190
263
|
{rightButton}
|
|
191
264
|
</div>
|
|
192
265
|
)}
|
|
193
|
-
|
|
266
|
+
|
|
194
267
|
{loading && !leftIcon && (
|
|
195
268
|
<div className="absolute left-3 text-gray-500 flex items-center justify-center pointer-events-none">
|
|
196
269
|
<Loader2 className="h-4 w-4 animate-spin" aria-hidden="true" />
|
|
197
270
|
</div>
|
|
198
271
|
)}
|
|
199
272
|
</div>
|
|
200
|
-
|
|
273
|
+
|
|
201
274
|
{showMessage && (
|
|
202
|
-
<p
|
|
275
|
+
<p
|
|
203
276
|
className={cn(
|
|
204
277
|
"text-xs transition-all",
|
|
205
278
|
messageType === "error" && "text-error",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client"
|
|
2
2
|
|
|
3
3
|
import * as React from "react"
|
|
4
|
-
import { cn } from "
|
|
4
|
+
import { cn } from "@/lib/utils"
|
|
5
5
|
|
|
6
6
|
interface MoonLogoProps extends React.SVGProps<SVGSVGElement> {
|
|
7
7
|
variant?: "default" | "monochrome" | "gradient"
|
|
@@ -16,9 +16,10 @@ export function MoonLogo({
|
|
|
16
16
|
}: MoonLogoProps) {
|
|
17
17
|
const logoId = React.useId()
|
|
18
18
|
const gradientId = `moon-gradient-${logoId}`
|
|
19
|
+
const maskId = `moon-mask-${logoId}`
|
|
19
20
|
|
|
20
21
|
return (
|
|
21
|
-
<div className={cn("
|
|
22
|
+
<div className={cn("flex items-center gap-2", className)}>
|
|
22
23
|
{/* Moon Icon */}
|
|
23
24
|
<svg
|
|
24
25
|
width="32"
|
|
@@ -29,32 +30,28 @@ export function MoonLogo({
|
|
|
29
30
|
className="flex-shrink-0"
|
|
30
31
|
{...props}
|
|
31
32
|
>
|
|
32
|
-
|
|
33
|
-
|
|
33
|
+
<defs>
|
|
34
|
+
{variant === "gradient" && (
|
|
34
35
|
<linearGradient id={gradientId} x1="0%" y1="0%" x2="100%" y2="100%">
|
|
35
36
|
<stop offset="0%" stopColor="#3B82F6" />
|
|
36
37
|
<stop offset="50%" stopColor="#8B5CF6" />
|
|
37
38
|
<stop offset="100%" stopColor="#EC4899" />
|
|
38
39
|
</linearGradient>
|
|
39
|
-
|
|
40
|
-
|
|
40
|
+
)}
|
|
41
|
+
<mask id={maskId}>
|
|
42
|
+
<rect width="32" height="32" fill="white"/>
|
|
43
|
+
<circle cx="22" cy="10" r="10" fill="black"/>
|
|
44
|
+
</mask>
|
|
45
|
+
</defs>
|
|
41
46
|
|
|
42
|
-
{/* Moon Circle */}
|
|
47
|
+
{/* Moon Circle with transparent crescent */}
|
|
43
48
|
<circle
|
|
44
49
|
cx="16"
|
|
45
50
|
cy="16"
|
|
46
51
|
r="14"
|
|
47
52
|
fill={variant === "gradient" ? `url(#${gradientId})` : "currentColor"}
|
|
48
53
|
className={variant === "default" ? "fill-primary" : ""}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
{/* Crescent cutout */}
|
|
52
|
-
<circle
|
|
53
|
-
cx="22"
|
|
54
|
-
cy="10"
|
|
55
|
-
r="10"
|
|
56
|
-
fill="white"
|
|
57
|
-
className="dark:fill-background"
|
|
54
|
+
mask={`url(#${maskId})`}
|
|
58
55
|
/>
|
|
59
56
|
</svg>
|
|
60
57
|
|
|
@@ -1,45 +1,219 @@
|
|
|
1
1
|
"use client"
|
|
2
2
|
|
|
3
3
|
import * as React from "react"
|
|
4
|
-
|
|
4
|
+
import { cva, type VariantProps } from "class-variance-authority"
|
|
5
5
|
import { cn } from "../../lib/utils"
|
|
6
|
+
import { Loader2 } from "lucide-react"
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Textarea Variant Styles
|
|
10
|
+
*/
|
|
11
|
+
const textareaVariants = cva(
|
|
12
|
+
[
|
|
13
|
+
"flex w-full rounded-md px-3 py-2 text-sm transition-all duration-200",
|
|
14
|
+
"text-foreground placeholder:text-muted-foreground dark:placeholder:text-gray-500",
|
|
15
|
+
"disabled:cursor-not-allowed disabled:opacity-50",
|
|
16
|
+
"focus-visible:outline-none dark:text-gray-200",
|
|
17
|
+
"resize-none" // Always disable manual resize, we control it
|
|
18
|
+
],
|
|
19
|
+
{
|
|
20
|
+
variants: {
|
|
21
|
+
variant: {
|
|
22
|
+
default: "border border-gray-300 dark:border-gray-700 bg-background dark:bg-gray-800/80 hover:border-gray-400 dark:hover:border-gray-600 focus-visible:ring-2 focus-visible:ring-primary/30 dark:focus-visible:ring-primary/20 focus-visible:border-primary dark:focus-visible:border-primary/80 dark:shadow-inner dark:shadow-gray-950/10",
|
|
23
|
+
outline: "border-2 border-gray-300 dark:border-gray-700 bg-transparent hover:border-gray-400 dark:hover:border-gray-600 focus-visible:border-primary dark:focus-visible:border-primary/80",
|
|
24
|
+
ghost: "border-none bg-transparent hover:bg-gray-100/50 dark:hover:bg-gray-800/30 focus-visible:bg-transparent",
|
|
25
|
+
underline: "border-t-0 border-l-0 border-r-0 border-b-2 border-gray-300 dark:border-gray-600 rounded-none px-0 hover:border-gray-400 dark:hover:border-gray-500 focus-visible:ring-0 focus-visible:border-primary dark:focus-visible:border-primary/80 bg-transparent dark:bg-transparent"
|
|
26
|
+
},
|
|
27
|
+
size: {
|
|
28
|
+
sm: "min-h-[60px] text-xs",
|
|
29
|
+
md: "min-h-[80px] text-sm",
|
|
30
|
+
lg: "min-h-[120px] text-base"
|
|
31
|
+
},
|
|
32
|
+
isError: {
|
|
33
|
+
true: "border-error focus-visible:ring-error/30 focus-visible:border-error hover:border-error/80 dark:hover:border-error/80",
|
|
34
|
+
false: ""
|
|
35
|
+
},
|
|
36
|
+
isSuccess: {
|
|
37
|
+
true: "border-success focus-visible:ring-success/30 focus-visible:border-success hover:border-success/80 dark:hover:border-success/80",
|
|
38
|
+
false: ""
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
defaultVariants: {
|
|
42
|
+
variant: "default",
|
|
43
|
+
size: "md",
|
|
44
|
+
isError: false,
|
|
45
|
+
isSuccess: false
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
)
|
|
6
49
|
|
|
7
50
|
export interface TextareaProps
|
|
8
|
-
extends React.TextareaHTMLAttributes<HTMLTextAreaElement
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
size?: "sm" | "md" | "lg"
|
|
51
|
+
extends Omit<React.TextareaHTMLAttributes<HTMLTextAreaElement>, "size">,
|
|
52
|
+
Omit<VariantProps<typeof textareaVariants>, "isError" | "isSuccess"> {
|
|
53
|
+
/** Hata mesajı */
|
|
12
54
|
error?: boolean | string
|
|
13
|
-
|
|
55
|
+
/** Başarı mesajı */
|
|
56
|
+
success?: boolean | string
|
|
57
|
+
/** Yükleniyor durumu */
|
|
14
58
|
loading?: boolean
|
|
59
|
+
/** Otomatik yükseklik ayarlama */
|
|
15
60
|
autoResize?: boolean
|
|
61
|
+
/** Maksimum yükseklik (px) */
|
|
16
62
|
maxHeight?: number
|
|
63
|
+
/** Karakter sayacı göster */
|
|
17
64
|
characterCount?: boolean
|
|
65
|
+
/** Wrapper için ek sınıflar */
|
|
66
|
+
wrapperClassName?: string
|
|
67
|
+
/** Mesaj için ek sınıflar */
|
|
68
|
+
messageClassName?: string
|
|
18
69
|
}
|
|
19
70
|
|
|
71
|
+
/**
|
|
72
|
+
* Advanced Textarea Component
|
|
73
|
+
*
|
|
74
|
+
* Features:
|
|
75
|
+
* - Auto-resize based on content
|
|
76
|
+
* - Character count display
|
|
77
|
+
* - Max height constraint
|
|
78
|
+
* - Multiple variants
|
|
79
|
+
* - Error/Success states
|
|
80
|
+
* - Loading state
|
|
81
|
+
*/
|
|
20
82
|
const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
|
|
21
|
-
({
|
|
22
|
-
className,
|
|
23
|
-
|
|
83
|
+
({
|
|
84
|
+
className,
|
|
85
|
+
wrapperClassName,
|
|
86
|
+
messageClassName,
|
|
24
87
|
variant,
|
|
25
88
|
size,
|
|
26
89
|
error,
|
|
27
90
|
success,
|
|
28
91
|
loading,
|
|
29
|
-
autoResize,
|
|
92
|
+
autoResize = false,
|
|
30
93
|
maxHeight,
|
|
31
|
-
characterCount,
|
|
32
|
-
|
|
94
|
+
characterCount = false,
|
|
95
|
+
disabled,
|
|
96
|
+
maxLength,
|
|
97
|
+
value,
|
|
98
|
+
defaultValue,
|
|
99
|
+
onChange,
|
|
100
|
+
...props
|
|
33
101
|
}, ref) => {
|
|
102
|
+
const textareaRef = React.useRef<HTMLTextAreaElement>(null)
|
|
103
|
+
const [internalValue, setInternalValue] = React.useState(value || defaultValue || "")
|
|
104
|
+
|
|
105
|
+
// Merge refs
|
|
106
|
+
React.useImperativeHandle(ref, () => textareaRef.current!)
|
|
107
|
+
|
|
108
|
+
// Auto-resize logic
|
|
109
|
+
const adjustHeight = React.useCallback(() => {
|
|
110
|
+
const textarea = textareaRef.current
|
|
111
|
+
if (!textarea || !autoResize) return
|
|
112
|
+
|
|
113
|
+
// Reset height to get correct scrollHeight
|
|
114
|
+
textarea.style.height = 'auto'
|
|
115
|
+
|
|
116
|
+
// Set new height
|
|
117
|
+
const newHeight = textarea.scrollHeight
|
|
118
|
+
if (maxHeight && newHeight > maxHeight) {
|
|
119
|
+
textarea.style.height = `${maxHeight}px`
|
|
120
|
+
textarea.style.overflowY = 'auto'
|
|
121
|
+
} else {
|
|
122
|
+
textarea.style.height = `${newHeight}px`
|
|
123
|
+
textarea.style.overflowY = 'hidden'
|
|
124
|
+
}
|
|
125
|
+
}, [autoResize, maxHeight])
|
|
126
|
+
|
|
127
|
+
// Adjust height on value change
|
|
128
|
+
React.useEffect(() => {
|
|
129
|
+
adjustHeight()
|
|
130
|
+
}, [internalValue, adjustHeight])
|
|
131
|
+
|
|
132
|
+
// Handle value changes
|
|
133
|
+
React.useEffect(() => {
|
|
134
|
+
if (value !== undefined) {
|
|
135
|
+
setInternalValue(value)
|
|
136
|
+
}
|
|
137
|
+
}, [value])
|
|
138
|
+
|
|
139
|
+
const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
|
140
|
+
setInternalValue(e.target.value)
|
|
141
|
+
onChange?.(e)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Character count
|
|
145
|
+
const currentLength = String(internalValue).length
|
|
146
|
+
const showCharCount = characterCount && (maxLength !== undefined || currentLength > 0)
|
|
147
|
+
|
|
148
|
+
// Messages
|
|
149
|
+
const errorMessage = typeof error === "string" ? error : undefined
|
|
150
|
+
const successMessage = typeof success === "string" ? success : undefined
|
|
151
|
+
const showMessage = errorMessage || successMessage
|
|
152
|
+
|
|
34
153
|
return (
|
|
35
|
-
<
|
|
36
|
-
className=
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
154
|
+
<div className={cn("moonui-theme", "space-y-1.5 w-full", wrapperClassName)}>
|
|
155
|
+
<div className="relative">
|
|
156
|
+
<textarea
|
|
157
|
+
ref={textareaRef}
|
|
158
|
+
className={cn(
|
|
159
|
+
textareaVariants({
|
|
160
|
+
variant,
|
|
161
|
+
size,
|
|
162
|
+
isError: !!error,
|
|
163
|
+
isSuccess: !!success
|
|
164
|
+
}),
|
|
165
|
+
loading && "pr-10",
|
|
166
|
+
className
|
|
167
|
+
)}
|
|
168
|
+
disabled={disabled || loading}
|
|
169
|
+
value={value}
|
|
170
|
+
defaultValue={defaultValue}
|
|
171
|
+
onChange={handleChange}
|
|
172
|
+
maxLength={maxLength}
|
|
173
|
+
aria-invalid={!!error || undefined}
|
|
174
|
+
aria-describedby={
|
|
175
|
+
errorMessage ? `${props.id || ''}-error` :
|
|
176
|
+
successMessage ? `${props.id || ''}-success` :
|
|
177
|
+
undefined
|
|
178
|
+
}
|
|
179
|
+
{...props}
|
|
180
|
+
/>
|
|
181
|
+
|
|
182
|
+
{loading && (
|
|
183
|
+
<div className="absolute top-3 right-3 text-gray-500">
|
|
184
|
+
<Loader2 className="h-4 w-4 animate-spin" aria-hidden="true" />
|
|
185
|
+
</div>
|
|
186
|
+
)}
|
|
187
|
+
</div>
|
|
188
|
+
|
|
189
|
+
<div className="flex items-center justify-between">
|
|
190
|
+
{/* Error/Success Message */}
|
|
191
|
+
{showMessage && (
|
|
192
|
+
<p
|
|
193
|
+
className={cn(
|
|
194
|
+
"text-xs transition-all",
|
|
195
|
+
errorMessage && "text-error",
|
|
196
|
+
successMessage && "text-success",
|
|
197
|
+
messageClassName
|
|
198
|
+
)}
|
|
199
|
+
id={
|
|
200
|
+
errorMessage ? `${props.id || ''}-error` :
|
|
201
|
+
successMessage ? `${props.id || ''}-success` :
|
|
202
|
+
undefined
|
|
203
|
+
}
|
|
204
|
+
>
|
|
205
|
+
{errorMessage || successMessage}
|
|
206
|
+
</p>
|
|
207
|
+
)}
|
|
208
|
+
|
|
209
|
+
{/* Character Count */}
|
|
210
|
+
{showCharCount && (
|
|
211
|
+
<p className="text-xs text-muted-foreground dark:text-gray-500 ml-auto">
|
|
212
|
+
{currentLength}{maxLength !== undefined && ` / ${maxLength}`}
|
|
213
|
+
</p>
|
|
214
|
+
)}
|
|
215
|
+
</div>
|
|
216
|
+
</div>
|
|
43
217
|
)
|
|
44
218
|
}
|
|
45
219
|
)
|