@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moontra/moonui",
3
- "version": "2.3.9",
3
+ "version": "2.4.0",
4
4
  "description": "Premium React component library with modern design and customization",
5
5
  "author": "MoonUI",
6
6
  "license": "MIT",
@@ -218,6 +218,13 @@ export {
218
218
  ScrollBar as MoonUIScrollBar,
219
219
  } from "./scroll-area";
220
220
 
221
+ // ScrollReveal
222
+ export {
223
+ ScrollReveal as MoonUIScrollReveal,
224
+ ScrollRevealContainer as MoonUIScrollRevealContainer,
225
+ ScrollRevealItem as MoonUIScrollRevealItem,
226
+ } from "./scroll-reveal";
227
+
221
228
  // Select
222
229
  export {
223
230
  Select as MoonUISelect,
@@ -342,6 +349,7 @@ export * from "./progress";
342
349
  export * from "./radio-group";
343
350
  export * from "./rich-text-editor";
344
351
  export * from "./scroll-area";
352
+ export * from "./scroll-reveal";
345
353
  export * from "./select";
346
354
  export * from "./separator";
347
355
  export * from "./simple-editor";
@@ -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-900/60 dark:shadow-inner dark:shadow-gray-950/10",
43
- filled: "border border-transparent bg-gray-100 dark:bg-gray-800/90 rounded-md px-3 py-2 hover:bg-gray-200 dark:hover:bg-gray-700/90 focus-visible:ring-2 focus-visible:ring-primary/30 dark:focus-visible:ring-primary/20 dark:shadow-inner dark:shadow-gray-950/10",
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
- ...props
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",
@@ -37,7 +37,7 @@ PaginationItem.displayName = "PaginationItem"
37
37
 
38
38
  type PaginationLinkProps = {
39
39
  isActive?: boolean
40
- } & Pick<ButtonProps, "size"> &
40
+ } & Partial<Pick<ButtonProps, "size">> &
41
41
  React.ComponentProps<"a">
42
42
 
43
43
  const PaginationLink = ({
@@ -0,0 +1,245 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { motion, useInView, useAnimation, Variants } from "framer-motion"
5
+ import { cn } from "../../lib/utils"
6
+
7
+ interface ScrollRevealProps {
8
+ children: React.ReactNode
9
+ direction?: "up" | "down" | "left" | "right"
10
+ delay?: number
11
+ duration?: number
12
+ distance?: number
13
+ threshold?: number
14
+ triggerOnce?: boolean
15
+ stagger?: number
16
+ className?: string
17
+ variants?: Variants
18
+ animate?: boolean
19
+ style?: React.CSSProperties
20
+ id?: string
21
+ }
22
+
23
+ const defaultVariants: Record<string, Variants> = {
24
+ up: {
25
+ hidden: { opacity: 0, y: 50 },
26
+ visible: { opacity: 1, y: 0 }
27
+ },
28
+ down: {
29
+ hidden: { opacity: 0, y: -50 },
30
+ visible: { opacity: 1, y: 0 }
31
+ },
32
+ left: {
33
+ hidden: { opacity: 0, x: 50 },
34
+ visible: { opacity: 1, x: 0 }
35
+ },
36
+ right: {
37
+ hidden: { opacity: 0, x: -50 },
38
+ visible: { opacity: 1, x: 0 }
39
+ },
40
+ fade: {
41
+ hidden: { opacity: 0 },
42
+ visible: { opacity: 1 }
43
+ },
44
+ scale: {
45
+ hidden: { opacity: 0, scale: 0.8 },
46
+ visible: { opacity: 1, scale: 1 }
47
+ },
48
+ blur: {
49
+ hidden: { opacity: 0, filter: "blur(10px)" },
50
+ visible: { opacity: 1, filter: "blur(0px)" }
51
+ }
52
+ }
53
+
54
+ const ScrollReveal = React.forwardRef<HTMLDivElement, ScrollRevealProps>(
55
+ ({
56
+ children,
57
+ direction = "up",
58
+ delay = 0,
59
+ duration = 0.6,
60
+ distance = 50,
61
+ threshold = 0.1,
62
+ triggerOnce = true,
63
+ stagger = 0,
64
+ className,
65
+ variants,
66
+ animate = true,
67
+ style,
68
+ id
69
+ }, ref) => {
70
+ const controls = useAnimation()
71
+ const elementRef = React.useRef<HTMLDivElement>(null)
72
+ const isInView = useInView(elementRef, {
73
+ amount: threshold,
74
+ once: triggerOnce
75
+ })
76
+
77
+ // Custom variants with distance
78
+ const customVariants = React.useMemo(() => {
79
+ if (variants) return variants
80
+
81
+ const baseVariants = defaultVariants[direction] || defaultVariants.up
82
+
83
+ return {
84
+ hidden: {
85
+ ...baseVariants.hidden,
86
+ ...(direction === "up" && { y: distance }),
87
+ ...(direction === "down" && { y: -distance }),
88
+ ...(direction === "left" && { x: distance }),
89
+ ...(direction === "right" && { x: -distance })
90
+ },
91
+ visible: baseVariants.visible
92
+ }
93
+ }, [direction, distance, variants])
94
+
95
+ React.useEffect(() => {
96
+ if (!animate) return
97
+
98
+ if (isInView) {
99
+ controls.start("visible")
100
+ } else if (!triggerOnce) {
101
+ controls.start("hidden")
102
+ }
103
+ }, [isInView, controls, triggerOnce, animate])
104
+
105
+ React.useImperativeHandle(ref, () => elementRef.current!)
106
+
107
+ if (!animate) {
108
+ return (
109
+ <div
110
+ ref={elementRef}
111
+ className={className}
112
+ style={style}
113
+ id={id}
114
+ >
115
+ {children}
116
+ </div>
117
+ )
118
+ }
119
+
120
+ return (
121
+ <motion.div
122
+ ref={elementRef}
123
+ variants={customVariants}
124
+ initial="hidden"
125
+ animate={controls}
126
+ transition={{
127
+ duration,
128
+ delay: delay + stagger,
129
+ ease: "easeOut"
130
+ }}
131
+ className={cn(className)}
132
+ style={style}
133
+ id={id}
134
+ >
135
+ {children}
136
+ </motion.div>
137
+ )
138
+ }
139
+ )
140
+
141
+ ScrollReveal.displayName = "ScrollReveal"
142
+
143
+ // ScrollReveal container for staggered animations
144
+ interface ScrollRevealContainerProps {
145
+ children: React.ReactNode
146
+ stagger?: number
147
+ className?: string
148
+ style?: React.CSSProperties
149
+ id?: string
150
+ }
151
+
152
+ const ScrollRevealContainer = React.forwardRef<HTMLDivElement, ScrollRevealContainerProps>(
153
+ ({ children, stagger = 0.1, className, style, id }, ref) => {
154
+ const containerRef = React.useRef<HTMLDivElement>(null)
155
+ const isInView = useInView(containerRef, { amount: 0.1, once: true })
156
+
157
+ React.useImperativeHandle(ref, () => containerRef.current!)
158
+
159
+ return (
160
+ <motion.div
161
+ ref={containerRef}
162
+ initial="hidden"
163
+ animate={isInView ? "visible" : "hidden"}
164
+ variants={{
165
+ hidden: {},
166
+ visible: {
167
+ transition: {
168
+ staggerChildren: stagger
169
+ }
170
+ }
171
+ }}
172
+ className={cn(className)}
173
+ style={style}
174
+ id={id}
175
+ >
176
+ {children}
177
+ </motion.div>
178
+ )
179
+ }
180
+ )
181
+
182
+ ScrollRevealContainer.displayName = "ScrollRevealContainer"
183
+
184
+ // ScrollReveal item for use within containers
185
+ interface ScrollRevealItemProps {
186
+ children: React.ReactNode
187
+ direction?: "up" | "down" | "left" | "right"
188
+ duration?: number
189
+ distance?: number
190
+ className?: string
191
+ variants?: Variants
192
+ style?: React.CSSProperties
193
+ id?: string
194
+ }
195
+
196
+ const ScrollRevealItem = React.forwardRef<HTMLDivElement, ScrollRevealItemProps>(
197
+ ({
198
+ children,
199
+ direction = "up",
200
+ duration = 0.6,
201
+ distance = 50,
202
+ className,
203
+ variants,
204
+ style,
205
+ id
206
+ }, ref) => {
207
+ const customVariants = React.useMemo(() => {
208
+ if (variants) return variants
209
+
210
+ const baseVariants = defaultVariants[direction] || defaultVariants.up
211
+
212
+ return {
213
+ hidden: {
214
+ ...baseVariants.hidden,
215
+ ...(direction === "up" && { y: distance }),
216
+ ...(direction === "down" && { y: -distance }),
217
+ ...(direction === "left" && { x: distance }),
218
+ ...(direction === "right" && { x: -distance })
219
+ },
220
+ visible: baseVariants.visible
221
+ }
222
+ }, [direction, distance, variants])
223
+
224
+ return (
225
+ <motion.div
226
+ ref={ref}
227
+ variants={customVariants}
228
+ transition={{
229
+ duration,
230
+ ease: "easeOut"
231
+ }}
232
+ className={cn(className)}
233
+ style={style}
234
+ id={id}
235
+ >
236
+ {children}
237
+ </motion.div>
238
+ )
239
+ }
240
+ )
241
+
242
+ ScrollRevealItem.displayName = "ScrollRevealItem"
243
+
244
+ export { ScrollReveal, ScrollRevealContainer, ScrollRevealItem }
245
+ export type { ScrollRevealProps, ScrollRevealContainerProps, ScrollRevealItemProps }
@@ -207,10 +207,12 @@ interface TableHeadProps extends React.ThHTMLAttributes<HTMLTableCellElement> {
207
207
  sorted?: SortDirection;
208
208
  /** Sıralama değiştiğinde çağrılacak fonksiyon */
209
209
  onSort?: () => void;
210
+ /** Text alignment */
211
+ align?: 'left' | 'center' | 'right';
210
212
  }
211
213
 
212
214
  const TableHead = React.forwardRef<HTMLTableCellElement, TableHeadProps>(
213
- ({ className, sortable, sorted, onSort, children, ...props }, ref) => {
215
+ ({ className, sortable, sorted, onSort, align = 'left', children, ...props }, ref) => {
214
216
  // Sıralama için simgeler
215
217
  const renderSortIcon = () => {
216
218
  if (!sortable) return null;
@@ -265,37 +267,61 @@ const TableHead = React.forwardRef<HTMLTableCellElement, TableHeadProps>(
265
267
  );
266
268
  };
267
269
 
270
+ const alignmentClasses = {
271
+ left: 'text-left justify-start',
272
+ center: 'text-center justify-center',
273
+ right: 'text-right justify-end'
274
+ };
275
+
268
276
  return (
269
277
  <th
270
278
  ref={ref}
271
279
  className={cn(
272
- "h-10 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0",
280
+ "h-10 px-4 align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0",
281
+ alignmentClasses[align].split(' ')[0], // text alignment
273
282
  sortable && "cursor-pointer hover:text-foreground select-none",
274
283
  className
275
284
  )}
276
285
  onClick={sortable ? onSort : undefined}
277
286
  {...props}
278
287
  >
279
- <div className="flex items-center">
280
- {children}
281
- {sortable && renderSortIcon()}
282
- </div>
288
+ {sortable || align !== 'left' ? (
289
+ <div className={cn("flex items-center", alignmentClasses[align].split(' ')[1])}>
290
+ {children}
291
+ {sortable && renderSortIcon()}
292
+ </div>
293
+ ) : (
294
+ children
295
+ )}
283
296
  </th>
284
297
  );
285
298
  }
286
299
  );
287
300
  TableHead.displayName = "TableHead";
288
301
 
302
+ interface TableCellProps extends React.TdHTMLAttributes<HTMLTableCellElement> {
303
+ /** Text alignment */
304
+ align?: 'left' | 'center' | 'right';
305
+ }
306
+
289
307
  const TableCell = React.forwardRef<
290
308
  HTMLTableCellElement,
291
- React.TdHTMLAttributes<HTMLTableCellElement>
292
- >(({ className, ...props }, ref) => (
293
- <td
294
- ref={ref}
295
- className={cn("moonui-theme", "p-4 align-middle [&:has([role=checkbox])]:pr-0", className)}
296
- {...props}
297
- />
298
- ));
309
+ TableCellProps
310
+ >(({ className, align = 'left', ...props }, ref) => {
311
+ const alignmentClass = {
312
+ left: 'text-left',
313
+ center: 'text-center',
314
+ right: 'text-right'
315
+ }[align];
316
+
317
+ return (
318
+ <td
319
+ ref={ref}
320
+ className={cn("moonui-theme", "p-4 align-middle [&:has([role=checkbox])]:pr-0", alignmentClass, className)}
321
+ {...props}
322
+ />
323
+ );
324
+ });
299
325
  TableCell.displayName = "TableCell";
300
326
 
301
327
  const TableCaption = React.forwardRef<