@moontra/moonui 2.3.9 → 2.4.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/dist/index.d.mts +84 -7
- package/dist/index.d.ts +84 -7
- package/dist/index.global.js +30 -30
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +399 -29
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +395 -31
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/components/ui/index.ts +8 -0
- package/src/components/ui/input.tsx +92 -19
- package/src/components/ui/pagination.tsx +1 -1
- package/src/components/ui/scroll-reveal.tsx +245 -0
- package/src/components/ui/table.tsx +40 -14
- package/src/components/ui/textarea.tsx +194 -20
|
@@ -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
|
)
|