@moontra/moonui 2.0.7 → 2.0.9

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.
Files changed (94) hide show
  1. package/package.json +26 -19
  2. package/src/__tests__/use-intersection-observer.test.tsx +216 -0
  3. package/src/__tests__/use-local-storage.test.tsx +174 -0
  4. package/src/__tests__/use-pro-access.test.tsx +183 -0
  5. package/src/components/ui/__tests__/accordion.test.tsx +571 -0
  6. package/src/components/ui/__tests__/alert.test.tsx +484 -0
  7. package/src/components/ui/__tests__/aspect-ratio.test.tsx +492 -0
  8. package/src/components/ui/__tests__/avatar.test.tsx +382 -0
  9. package/src/components/ui/__tests__/badge.test.tsx +364 -0
  10. package/src/components/ui/__tests__/breadcrumb.test.tsx +687 -0
  11. package/src/components/ui/__tests__/button.test.tsx +91 -0
  12. package/src/components/ui/__tests__/card.test.tsx +104 -0
  13. package/src/components/ui/__tests__/checkbox.test.tsx +591 -0
  14. package/src/components/ui/__tests__/collapsible.test.tsx +592 -0
  15. package/src/components/ui/__tests__/color-picker.test.tsx +365 -0
  16. package/src/components/ui/__tests__/command.test.tsx +677 -0
  17. package/src/components/ui/__tests__/data-table.test.tsx +256 -0
  18. package/src/components/ui/__tests__/date-picker.test.tsx +320 -0
  19. package/src/components/ui/__tests__/dialog.test.tsx +764 -0
  20. package/src/components/ui/__tests__/input.test.tsx +318 -0
  21. package/src/components/ui/__tests__/label.test.tsx +103 -0
  22. package/src/components/ui/__tests__/popover.test.tsx +637 -0
  23. package/src/components/ui/__tests__/progress.test.tsx +382 -0
  24. package/src/components/ui/__tests__/radio-group.test.tsx +555 -0
  25. package/src/components/ui/__tests__/select.test.tsx +705 -0
  26. package/src/components/ui/__tests__/skeleton.test.tsx +253 -0
  27. package/src/components/ui/__tests__/slider.test.tsx +493 -0
  28. package/src/components/ui/__tests__/switch.test.tsx +372 -0
  29. package/src/components/ui/__tests__/table.test.tsx +824 -0
  30. package/src/components/ui/__tests__/tabs.test.tsx +887 -0
  31. package/src/components/ui/__tests__/toast.test.tsx +458 -0
  32. package/src/components/ui/__tests__/tooltip.test.tsx +583 -0
  33. package/src/components/ui/accordion.tsx +63 -0
  34. package/src/components/ui/alert.tsx +130 -0
  35. package/src/components/ui/aspect-ratio.tsx +72 -0
  36. package/src/components/ui/avatar.tsx +135 -0
  37. package/src/components/ui/badge.tsx +225 -0
  38. package/src/components/ui/breadcrumb.tsx +207 -0
  39. package/src/components/ui/button.stories.tsx +162 -0
  40. package/src/components/ui/button.tsx +221 -0
  41. package/src/components/ui/calendar.tsx +262 -0
  42. package/src/components/ui/card.stories.tsx +208 -0
  43. package/src/components/ui/card.tsx +141 -0
  44. package/src/components/ui/checkbox.tsx +256 -0
  45. package/src/components/ui/collapsible.tsx +129 -0
  46. package/src/components/ui/color-picker.tsx +664 -0
  47. package/src/components/ui/command.tsx +218 -0
  48. package/src/components/ui/data-table.stories.tsx +509 -0
  49. package/src/components/ui/data-table.tsx +623 -0
  50. package/src/components/ui/date-picker.stories.tsx +321 -0
  51. package/src/components/ui/date-picker.tsx +603 -0
  52. package/src/components/ui/dialog.tsx +332 -0
  53. package/src/components/ui/dropdown-menu.tsx +200 -0
  54. package/src/components/ui/file-upload.tsx +400 -0
  55. package/src/components/ui/github-stars.tsx +201 -0
  56. package/src/components/ui/index.ts +12 -0
  57. package/src/components/ui/input.stories.tsx +255 -0
  58. package/src/components/ui/input.tsx +219 -0
  59. package/src/components/ui/label.tsx +26 -0
  60. package/src/components/ui/locked-component.tsx +215 -0
  61. package/src/components/ui/moon-logo.tsx +97 -0
  62. package/src/components/ui/pagination.tsx +116 -0
  63. package/src/components/ui/popover.tsx +183 -0
  64. package/src/components/ui/progress.tsx +182 -0
  65. package/src/components/ui/radio-group.tsx +243 -0
  66. package/src/components/ui/select.tsx +273 -0
  67. package/src/components/ui/separator.tsx +140 -0
  68. package/src/components/ui/simple-editor.tsx +652 -0
  69. package/src/components/ui/skeleton.tsx +351 -0
  70. package/src/components/ui/slider.tsx +351 -0
  71. package/src/components/ui/switch.tsx +83 -0
  72. package/src/components/ui/table.tsx +322 -0
  73. package/src/components/ui/tabs.tsx +195 -0
  74. package/src/components/ui/textarea.tsx +46 -0
  75. package/src/components/ui/toast.tsx +313 -0
  76. package/src/components/ui/toggle.tsx +47 -0
  77. package/src/components/ui/tooltip.tsx +152 -0
  78. package/src/docs/theme-system.md +1194 -0
  79. package/src/index.tsx +39 -0
  80. package/src/lib/micro-interactions.ts +255 -0
  81. package/src/lib/theme.tsx +398 -0
  82. package/src/lib/utils.ts +69 -0
  83. package/src/styles/design-system.css +365 -0
  84. package/src/styles/index.css +4 -0
  85. package/src/styles/tailwind.css +6 -0
  86. package/src/styles/tokens.css +453 -0
  87. package/src/use-intersection-observer.tsx +154 -0
  88. package/src/use-local-storage.tsx +71 -0
  89. package/src/use-paddle.ts +138 -0
  90. package/src/use-performance-optimizer.ts +379 -0
  91. package/src/use-pro-access.ts +141 -0
  92. package/src/use-scroll-animation.ts +221 -0
  93. package/src/use-subscription.ts +37 -0
  94. package/src/use-toast.ts +32 -0
@@ -0,0 +1,255 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { Input } from './input';
3
+ import { Label } from './label';
4
+ import { Button } from './button';
5
+ import { Search, Mail, Lock, Eye, EyeOff } from 'lucide-react';
6
+ import { useState } from 'react';
7
+
8
+ const meta: Meta<typeof Input> = {
9
+ title: 'Components/Input',
10
+ component: Input,
11
+ parameters: {
12
+ layout: 'centered',
13
+ docs: {
14
+ description: {
15
+ component: 'Displays a form input field or a component that looks like an input field.',
16
+ },
17
+ },
18
+ },
19
+ tags: ['autodocs'],
20
+ argTypes: {
21
+ type: {
22
+ control: 'select',
23
+ options: ['text', 'email', 'password', 'number', 'search', 'tel', 'url'],
24
+ description: 'The type of input',
25
+ },
26
+ placeholder: {
27
+ control: 'text',
28
+ description: 'Placeholder text',
29
+ },
30
+ disabled: {
31
+ control: 'boolean',
32
+ description: 'Whether the input is disabled',
33
+ },
34
+ className: {
35
+ control: 'text',
36
+ description: 'Additional CSS classes',
37
+ },
38
+ },
39
+ };
40
+
41
+ export default meta;
42
+ type Story = StoryObj<typeof meta>;
43
+
44
+ export const Default: Story = {
45
+ args: {
46
+ placeholder: 'Enter text...',
47
+ },
48
+ };
49
+
50
+ export const WithLabel: Story = {
51
+ render: () => (
52
+ <div className="grid w-full max-w-sm items-center gap-1.5">
53
+ <Label htmlFor="email">Email</Label>
54
+ <Input type="email" id="email" placeholder="Email" />
55
+ </div>
56
+ ),
57
+ };
58
+
59
+ export const Email: Story = {
60
+ args: {
61
+ type: 'email',
62
+ placeholder: 'Enter your email',
63
+ },
64
+ };
65
+
66
+ export const Password: Story = {
67
+ args: {
68
+ type: 'password',
69
+ placeholder: 'Enter your password',
70
+ },
71
+ };
72
+
73
+ export const Search: Story = {
74
+ args: {
75
+ type: 'search',
76
+ placeholder: 'Search...',
77
+ },
78
+ };
79
+
80
+ export const Number: Story = {
81
+ args: {
82
+ type: 'number',
83
+ placeholder: 'Enter a number',
84
+ },
85
+ };
86
+
87
+ export const Disabled: Story = {
88
+ args: {
89
+ disabled: true,
90
+ placeholder: 'Disabled input',
91
+ },
92
+ };
93
+
94
+ export const WithIcon: Story = {
95
+ render: () => (
96
+ <div className="relative">
97
+ <Search className="absolute left-3 top-3 h-4 w-4 text-muted-foreground" />
98
+ <Input
99
+ type="search"
100
+ placeholder="Search..."
101
+ className="pl-10"
102
+ />
103
+ </div>
104
+ ),
105
+ };
106
+
107
+ export const WithButton: Story = {
108
+ render: () => (
109
+ <div className="flex w-full max-w-sm items-center space-x-2">
110
+ <Input type="email" placeholder="Email" />
111
+ <Button type="submit">Subscribe</Button>
112
+ </div>
113
+ ),
114
+ };
115
+
116
+ export const FormExample: Story = {
117
+ render: () => (
118
+ <div className="w-full max-w-sm space-y-4">
119
+ <div className="space-y-2">
120
+ <Label htmlFor="name">Name</Label>
121
+ <Input id="name" placeholder="Your name" />
122
+ </div>
123
+ <div className="space-y-2">
124
+ <Label htmlFor="email-form">Email</Label>
125
+ <div className="relative">
126
+ <Mail className="absolute left-3 top-3 h-4 w-4 text-muted-foreground" />
127
+ <Input
128
+ id="email-form"
129
+ type="email"
130
+ placeholder="your@email.com"
131
+ className="pl-10"
132
+ />
133
+ </div>
134
+ </div>
135
+ <div className="space-y-2">
136
+ <Label htmlFor="password-form">Password</Label>
137
+ <div className="relative">
138
+ <Lock className="absolute left-3 top-3 h-4 w-4 text-muted-foreground" />
139
+ <Input
140
+ id="password-form"
141
+ type="password"
142
+ placeholder="••••••••"
143
+ className="pl-10"
144
+ />
145
+ </div>
146
+ </div>
147
+ <Button className="w-full">Sign Up</Button>
148
+ </div>
149
+ ),
150
+ };
151
+
152
+ export const PasswordWithToggle: Story = {
153
+ render: () => {
154
+ const [showPassword, setShowPassword] = useState(false);
155
+
156
+ return (
157
+ <div className="relative">
158
+ <Input
159
+ type={showPassword ? 'text' : 'password'}
160
+ placeholder="Enter your password"
161
+ className="pr-10"
162
+ />
163
+ <button
164
+ type="button"
165
+ className="absolute right-3 top-3 h-4 w-4 text-muted-foreground hover:text-foreground"
166
+ onClick={() => setShowPassword(!showPassword)}
167
+ >
168
+ {showPassword ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />}
169
+ </button>
170
+ </div>
171
+ );
172
+ },
173
+ };
174
+
175
+ export const Sizes: Story = {
176
+ render: () => (
177
+ <div className="w-full max-w-sm space-y-4">
178
+ <div className="space-y-2">
179
+ <Label>Small</Label>
180
+ <Input placeholder="Small input" className="h-8 text-sm" />
181
+ </div>
182
+ <div className="space-y-2">
183
+ <Label>Default</Label>
184
+ <Input placeholder="Default input" />
185
+ </div>
186
+ <div className="space-y-2">
187
+ <Label>Large</Label>
188
+ <Input placeholder="Large input" className="h-12 text-lg" />
189
+ </div>
190
+ </div>
191
+ ),
192
+ };
193
+
194
+ export const States: Story = {
195
+ render: () => (
196
+ <div className="w-full max-w-sm space-y-4">
197
+ <div className="space-y-2">
198
+ <Label>Default</Label>
199
+ <Input placeholder="Default state" />
200
+ </div>
201
+ <div className="space-y-2">
202
+ <Label>Focused</Label>
203
+ <Input placeholder="Focused state" autoFocus />
204
+ </div>
205
+ <div className="space-y-2">
206
+ <Label>Disabled</Label>
207
+ <Input placeholder="Disabled state" disabled />
208
+ </div>
209
+ <div className="space-y-2">
210
+ <Label>Error</Label>
211
+ <Input
212
+ placeholder="Error state"
213
+ className="border-red-500 focus:border-red-500 focus:ring-red-500"
214
+ />
215
+ </div>
216
+ </div>
217
+ ),
218
+ };
219
+
220
+ export const WithValidation: Story = {
221
+ render: () => {
222
+ const [email, setEmail] = useState('');
223
+ const [error, setError] = useState('');
224
+
225
+ const validateEmail = (value: string) => {
226
+ if (!value) {
227
+ setError('Email is required');
228
+ } else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) {
229
+ setError('Please enter a valid email address');
230
+ } else {
231
+ setError('');
232
+ }
233
+ };
234
+
235
+ return (
236
+ <div className="w-full max-w-sm space-y-2">
237
+ <Label htmlFor="email-validation">Email</Label>
238
+ <Input
239
+ id="email-validation"
240
+ type="email"
241
+ placeholder="Enter your email"
242
+ value={email}
243
+ onChange={(e) => {
244
+ setEmail(e.target.value);
245
+ validateEmail(e.target.value);
246
+ }}
247
+ className={error ? 'border-red-500' : ''}
248
+ />
249
+ {error && (
250
+ <p className="text-sm text-red-500">{error}</p>
251
+ )}
252
+ </div>
253
+ );
254
+ },
255
+ };
@@ -0,0 +1,219 @@
1
+ import * as React from "react";
2
+ import { cva, type VariantProps } from "class-variance-authority";
3
+ import { cn } from "../../lib/utils";
4
+ import { Loader2 } from "lucide-react";
5
+
6
+ /**
7
+ * Premium Input Component
8
+ *
9
+ * Erişilebilir, estetik ve işlevsel input bileşeni.
10
+ * İkon desteği, hata gösterimi ve çeşitli varyantlar sunar.
11
+ */
12
+
13
+ const inputWrapperVariants = cva(
14
+ "group relative flex items-center w-full transition-colors",
15
+ {
16
+ variants: {
17
+ size: {
18
+ sm: "",
19
+ md: "",
20
+ lg: "",
21
+ },
22
+ },
23
+ defaultVariants: {
24
+ size: "md",
25
+ },
26
+ }
27
+ );
28
+
29
+ const inputVariants = cva(
30
+ [
31
+ "w-full bg-background transition-all duration-200",
32
+ "text-foreground placeholder:text-muted-foreground dark:placeholder:text-gray-500",
33
+ "disabled:cursor-not-allowed disabled:opacity-50",
34
+ "file:border-0 file:bg-transparent file:font-medium",
35
+ "focus-visible:outline-none dark:text-gray-200"
36
+ ],
37
+ {
38
+ variants: {
39
+ variant: {
40
+ 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",
41
+ 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
+ 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",
43
+ 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",
44
+ },
45
+ size: {
46
+ sm: "h-8 text-xs",
47
+ md: "h-10 text-sm",
48
+ lg: "h-12 text-base",
49
+ },
50
+ hasLeftIcon: {
51
+ true: "pl-10",
52
+ false: "",
53
+ },
54
+ hasRightIcon: {
55
+ true: "pr-10",
56
+ false: "",
57
+ },
58
+ hasRightButton: {
59
+ true: "pr-10",
60
+ false: "",
61
+ },
62
+ isError: {
63
+ true: "border-error focus-visible:ring-error/30 focus-visible:border-error hover:border-error/80 dark:hover:border-error/80",
64
+ false: "",
65
+ },
66
+ isSuccess: {
67
+ true: "border-success focus-visible:ring-success/30 focus-visible:border-success hover:border-success/80 dark:hover:border-success/80",
68
+ false: "",
69
+ },
70
+ isDisabled: {
71
+ true: "opacity-50 cursor-not-allowed bg-gray-50 dark:bg-gray-800/50 pointer-events-none",
72
+ false: "",
73
+ }
74
+ },
75
+ defaultVariants: {
76
+ variant: "standard",
77
+ size: "md",
78
+ isError: false,
79
+ isSuccess: false,
80
+ isDisabled: false,
81
+ hasLeftIcon: false,
82
+ hasRightIcon: false,
83
+ hasRightButton: false,
84
+ },
85
+ }
86
+ );
87
+
88
+ export interface InputProps
89
+ extends Omit<React.InputHTMLAttributes<HTMLInputElement>, "size">,
90
+ Omit<VariantProps<typeof inputVariants>, "isDisabled" | "hasLeftIcon" | "hasRightIcon" | "hasRightButton"> {
91
+ /** Hata mesajı */
92
+ error?: string;
93
+ /** Başarı mesajı */
94
+ success?: string;
95
+ /** Yükleniyor durumu */
96
+ loading?: boolean;
97
+ /** Sol tarafta gösterilecek ikon */
98
+ leftIcon?: React.ReactNode;
99
+ /** Sağ tarafta gösterilecek ikon */
100
+ rightIcon?: React.ReactNode;
101
+ /** Sağ tarafta gösterilecek buton (password show/hide vb. için) */
102
+ rightButton?: React.ReactNode;
103
+ /** Mesajın görünürlüğü (true: her zaman görünür, false: sadece hata/başarı durumunda) */
104
+ alwaysShowMessage?: boolean;
105
+ /** Input wrapper için ek sınıflar */
106
+ wrapperClassName?: string;
107
+ /** Mesaj için ek sınıflar */
108
+ messageClassName?: string;
109
+ }
110
+
111
+ /**
112
+ * Premium Input Component
113
+ *
114
+ * @param props - Input bileşeni özellikleri
115
+ * @param props.variant - Görsel varyant (standard, filled, ghost, underline)
116
+ * @param props.size - Boyut (sm, md, lg)
117
+ * @param props.error - Hata mesajı
118
+ * @param props.success - Başarı mesajı
119
+ * @param props.loading - Yükleniyor durumu
120
+ * @param props.leftIcon - Sol tarafta gösterilecek ikon
121
+ * @param props.rightIcon - Sağ tarafta gösterilecek ikon
122
+ * @param props.rightButton - Sağ tarafta gösterilecek buton
123
+ * @param props.alwaysShowMessage - Mesajın her zaman görünür olması
124
+ */
125
+ const Input = React.forwardRef<HTMLInputElement, InputProps>(
126
+ ({
127
+ className,
128
+ wrapperClassName,
129
+ messageClassName,
130
+ variant,
131
+ size,
132
+ isError,
133
+ disabled,
134
+ error,
135
+ success,
136
+ loading,
137
+ leftIcon,
138
+ rightIcon,
139
+ rightButton,
140
+ alwaysShowMessage = false,
141
+ ...props
142
+ }, ref) => {
143
+ // Mesajı göster/gizle
144
+ const showMessage = alwaysShowMessage || error || success;
145
+ const messageType = error ? "error" : success ? "success" : "normal";
146
+
147
+ return (
148
+ <div className="space-y-1.5 w-full">
149
+ <div className={cn(inputWrapperVariants({ size }), wrapperClassName)}>
150
+ {leftIcon && (
151
+ <div className="absolute left-3 text-gray-500 flex items-center justify-center pointer-events-none">
152
+ {loading ? <Loader2 className="h-4 w-4 animate-spin" /> : leftIcon}
153
+ </div>
154
+ )}
155
+
156
+ <input
157
+ className={cn(
158
+ inputVariants({
159
+ variant,
160
+ size,
161
+ isError: !!error || isError,
162
+ isSuccess: !!success,
163
+ isDisabled: disabled || loading,
164
+ hasLeftIcon: !!leftIcon || loading,
165
+ hasRightIcon: !!rightIcon,
166
+ hasRightButton: !!rightButton,
167
+ }),
168
+ className
169
+ )}
170
+ ref={ref}
171
+ disabled={disabled || loading}
172
+ data-loading={loading ? "" : undefined}
173
+ data-error={!!error ? "" : undefined}
174
+ data-success={!!success ? "" : undefined}
175
+ aria-invalid={!!error || !!isError || undefined}
176
+ aria-describedby={error ? `${props.id || ''}-error` : success ? `${props.id || ''}-success` : undefined}
177
+ {...props}
178
+ />
179
+
180
+ {rightIcon && !loading && (
181
+ <div className="absolute right-3 text-gray-500 flex items-center justify-center pointer-events-none">
182
+ {rightIcon}
183
+ </div>
184
+ )}
185
+
186
+ {rightButton && (
187
+ <div className="absolute right-3">
188
+ {rightButton}
189
+ </div>
190
+ )}
191
+
192
+ {loading && !leftIcon && (
193
+ <div className="absolute left-3 text-gray-500 flex items-center justify-center pointer-events-none">
194
+ <Loader2 className="h-4 w-4 animate-spin" aria-hidden="true" />
195
+ </div>
196
+ )}
197
+ </div>
198
+
199
+ {showMessage && (
200
+ <p
201
+ className={cn(
202
+ "text-xs transition-all",
203
+ messageType === "error" && "text-error",
204
+ messageType === "success" && "text-success",
205
+ messageType === "normal" && "text-muted-foreground",
206
+ messageClassName
207
+ )}
208
+ id={error ? `${props.id || ''}-error` : success ? `${props.id || ''}-success` : undefined}
209
+ >
210
+ {error || success || ""}
211
+ </p>
212
+ )}
213
+ </div>
214
+ );
215
+ }
216
+ );
217
+ Input.displayName = "Input";
218
+
219
+ export { Input };
@@ -0,0 +1,26 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as LabelPrimitive from "@radix-ui/react-label"
5
+ import { cva, type VariantProps } from "class-variance-authority"
6
+
7
+ import { cn } from "../../lib/utils"
8
+
9
+ const labelVariants = cva(
10
+ "text-sm font-medium leading-none text-gray-900 dark:text-gray-200 peer-disabled:cursor-not-allowed peer-disabled:opacity-70 dark:peer-disabled:opacity-60 transition-colors duration-200"
11
+ )
12
+
13
+ const Label = React.forwardRef<
14
+ React.ElementRef<typeof LabelPrimitive.Root>,
15
+ React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
16
+ VariantProps<typeof labelVariants>
17
+ >(({ className, ...props }, ref) => (
18
+ <LabelPrimitive.Root
19
+ ref={ref}
20
+ className={cn(labelVariants(), className)}
21
+ {...props}
22
+ />
23
+ ))
24
+ Label.displayName = LabelPrimitive.Root.displayName
25
+
26
+ export { Label }