@moontra/moonui 2.0.8 → 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 +9 -2
  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,262 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import { ChevronLeft, ChevronRight } from "lucide-react";
5
+ import { cn } from "../../lib/utils";
6
+ import { buttonVariants } from "./button";
7
+ import {
8
+ format,
9
+ startOfMonth,
10
+ endOfMonth,
11
+ eachDayOfInterval,
12
+ getDay,
13
+ isSameMonth,
14
+ isSameDay,
15
+ isToday,
16
+ addMonths,
17
+ subMonths,
18
+ startOfWeek,
19
+ endOfWeek
20
+ } from "date-fns";
21
+
22
+ export interface CalendarProps {
23
+ mode?: "single" | "range" | "multiple";
24
+ selected?: Date | Date[] | { from?: Date; to?: Date } | undefined;
25
+ onSelect?: (date: Date | Date[] | { from?: Date; to?: Date } | undefined) => void;
26
+ disabled?: (date: Date) => boolean;
27
+ showOutsideDays?: boolean;
28
+ className?: string;
29
+ classNames?: Record<string, string>;
30
+ numberOfMonths?: number;
31
+ defaultMonth?: Date;
32
+ initialFocus?: boolean;
33
+ }
34
+
35
+ function Calendar({
36
+ mode = "single",
37
+ selected,
38
+ onSelect,
39
+ disabled,
40
+ showOutsideDays = false,
41
+ className,
42
+ classNames,
43
+ numberOfMonths = 1,
44
+ defaultMonth,
45
+ ...props
46
+ }: CalendarProps) {
47
+ const [currentMonth, setCurrentMonth] = React.useState(
48
+ defaultMonth || (selected && (selected as Date)) || new Date()
49
+ );
50
+
51
+ const weekDays = [
52
+ { short: "S", full: "Sunday" },
53
+ { short: "M", full: "Monday" },
54
+ { short: "T", full: "Tuesday" },
55
+ { short: "W", full: "Wednesday" },
56
+ { short: "T", full: "Thursday" },
57
+ { short: "F", full: "Friday" },
58
+ { short: "S", full: "Saturday" }
59
+ ];
60
+
61
+ const handlePreviousMonth = () => {
62
+ setCurrentMonth(subMonths(currentMonth, 1));
63
+ };
64
+
65
+ const handleNextMonth = () => {
66
+ setCurrentMonth(addMonths(currentMonth, 1));
67
+ };
68
+
69
+ const handleDateClick = (date: Date) => {
70
+ if (disabled?.(date)) return;
71
+
72
+ if (mode === "single") {
73
+ onSelect?.(date);
74
+ } else if (mode === "range") {
75
+ const currentSelection = selected as { from?: Date; to?: Date } | undefined;
76
+ if (!currentSelection?.from || (currentSelection.from && currentSelection.to)) {
77
+ onSelect?.({ from: date, to: undefined });
78
+ } else {
79
+ if (date < currentSelection.from) {
80
+ onSelect?.({ from: date, to: currentSelection.from });
81
+ } else {
82
+ onSelect?.({ from: currentSelection.from, to: date });
83
+ }
84
+ }
85
+ }
86
+ };
87
+
88
+ const isDateSelected = (date: Date) => {
89
+ if (!selected) return false;
90
+
91
+ if (mode === "single") {
92
+ return isSameDay(date, selected as Date);
93
+ } else if (mode === "range") {
94
+ const range = selected as { from?: Date; to?: Date };
95
+ if (range.from && range.to) {
96
+ return date >= range.from && date <= range.to;
97
+ }
98
+ return range.from ? isSameDay(date, range.from) : false;
99
+ }
100
+ return false;
101
+ };
102
+
103
+ const isRangeStart = (date: Date) => {
104
+ if (mode !== "range" || !selected) return false;
105
+ const range = selected as { from?: Date; to?: Date };
106
+ return range.from ? isSameDay(date, range.from) : false;
107
+ };
108
+
109
+ const isRangeEnd = (date: Date) => {
110
+ if (mode !== "range" || !selected) return false;
111
+ const range = selected as { from?: Date; to?: Date };
112
+ return range.to ? isSameDay(date, range.to) : false;
113
+ };
114
+
115
+ const isRangeMiddle = (date: Date) => {
116
+ if (mode !== "range" || !selected) return false;
117
+ const range = selected as { from?: Date; to?: Date };
118
+ if (!range.from || !range.to) return false;
119
+ return date > range.from && date < range.to;
120
+ };
121
+
122
+ const getDaysInMonth = () => {
123
+ const start = startOfMonth(currentMonth);
124
+ const end = endOfMonth(currentMonth);
125
+ const days = eachDayOfInterval({ start, end });
126
+
127
+ // Get days from previous month to fill the first week
128
+ const firstDayOfWeek = getDay(start);
129
+ const previousMonthDays = [];
130
+ if (firstDayOfWeek > 0 && showOutsideDays) {
131
+ const previousMonthStart = startOfWeek(start);
132
+ const previousMonthEnd = new Date(start);
133
+ previousMonthEnd.setDate(previousMonthEnd.getDate() - 1);
134
+ previousMonthDays.push(...eachDayOfInterval({
135
+ start: previousMonthStart,
136
+ end: previousMonthEnd
137
+ }));
138
+ }
139
+
140
+ // Get days from next month to fill the last week
141
+ const lastDayOfWeek = getDay(end);
142
+ const nextMonthDays = [];
143
+ if (lastDayOfWeek < 6 && showOutsideDays) {
144
+ const nextMonthStart = new Date(end);
145
+ nextMonthStart.setDate(nextMonthStart.getDate() + 1);
146
+ const nextMonthEnd = endOfWeek(end);
147
+ nextMonthDays.push(...eachDayOfInterval({
148
+ start: nextMonthStart,
149
+ end: nextMonthEnd
150
+ }));
151
+ }
152
+
153
+ // Add empty cells for the first week if not showing outside days
154
+ const emptyCells = [];
155
+ if (!showOutsideDays && firstDayOfWeek > 0) {
156
+ for (let i = 0; i < firstDayOfWeek; i++) {
157
+ emptyCells.push(null);
158
+ }
159
+ }
160
+
161
+ return [...previousMonthDays, ...emptyCells, ...days, ...nextMonthDays];
162
+ };
163
+
164
+ const days = getDaysInMonth();
165
+
166
+ return (
167
+ <div className={cn("p-0", className)}>
168
+ {/* Header */}
169
+ <div className="flex items-center justify-between px-6 py-4">
170
+ <button
171
+ onClick={handlePreviousMonth}
172
+ className={cn(
173
+ "h-10 w-10 bg-transparent p-0 rounded-lg",
174
+ "hover:bg-muted transition-colors",
175
+ "inline-flex items-center justify-center"
176
+ )}
177
+ type="button"
178
+ >
179
+ <ChevronLeft className="h-5 w-5" />
180
+ </button>
181
+
182
+ <h2 className="text-base font-medium">
183
+ {format(currentMonth, "MMMM yyyy")}
184
+ </h2>
185
+
186
+ <button
187
+ onClick={handleNextMonth}
188
+ className={cn(
189
+ "h-10 w-10 bg-transparent p-0 rounded-lg",
190
+ "hover:bg-muted transition-colors",
191
+ "inline-flex items-center justify-center"
192
+ )}
193
+ type="button"
194
+ >
195
+ <ChevronRight className="h-5 w-5" />
196
+ </button>
197
+ </div>
198
+
199
+ {/* Calendar Grid */}
200
+ <div className="px-6 pb-4">
201
+ {/* Week days header */}
202
+ <div className="grid grid-cols-7 mb-2">
203
+ {weekDays.map((day, index) => (
204
+ <div
205
+ key={`weekday-${index}`}
206
+ className="text-muted-foreground text-xs font-medium h-10 flex items-center justify-center"
207
+ title={day.full}
208
+ >
209
+ {day.short}
210
+ </div>
211
+ ))}
212
+ </div>
213
+
214
+ {/* Days grid */}
215
+ <div className="grid grid-cols-7 gap-1">
216
+ {days.map((date, index) => {
217
+ if (!date) {
218
+ return <div key={`empty-${index}`} className="h-10 w-10" />;
219
+ }
220
+
221
+ const isOutsideMonth = !isSameMonth(date, currentMonth);
222
+ const isDisabled = disabled?.(date) || false;
223
+ const isSelected = isDateSelected(date);
224
+ const isTodayDate = isToday(date);
225
+ const rangeStart = isRangeStart(date);
226
+ const rangeEnd = isRangeEnd(date);
227
+ const rangeMiddle = isRangeMiddle(date);
228
+
229
+ return (
230
+ <button
231
+ key={date.toISOString()}
232
+ onClick={() => handleDateClick(date)}
233
+ disabled={isDisabled}
234
+ className={cn(
235
+ "h-10 w-10 p-0 font-normal",
236
+ "inline-flex items-center justify-center rounded-lg",
237
+ "hover:bg-muted transition-colors text-sm",
238
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
239
+ isOutsideMonth && "text-muted-foreground/50",
240
+ isDisabled && "text-muted-foreground/30 cursor-not-allowed hover:bg-transparent",
241
+ isSelected && !rangeMiddle && "bg-primary text-primary-foreground font-medium hover:bg-primary hover:text-primary-foreground",
242
+ isTodayDate && "font-semibold relative after:absolute after:bottom-1 after:left-1/2 after:-translate-x-1/2 after:h-1 after:w-1 after:rounded-full after:bg-primary",
243
+ rangeMiddle && "bg-accent/30 text-accent-foreground rounded-none",
244
+ rangeStart && "rounded-r-none",
245
+ rangeEnd && "rounded-l-none"
246
+ )}
247
+ type="button"
248
+ >
249
+ {format(date, "d")}
250
+ </button>
251
+ );
252
+ })}
253
+ </div>
254
+ </div>
255
+
256
+ </div>
257
+ );
258
+ }
259
+
260
+ Calendar.displayName = "Calendar";
261
+
262
+ export { Calendar };
@@ -0,0 +1,208 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from './card';
3
+ import { Button } from './button';
4
+ import { Badge } from './badge';
5
+ import { Bell, Check, X } from 'lucide-react';
6
+
7
+ const meta: Meta<typeof Card> = {
8
+ title: 'Components/Card',
9
+ component: Card,
10
+ parameters: {
11
+ layout: 'centered',
12
+ docs: {
13
+ description: {
14
+ component: 'Displays a card with header, content, and footer.',
15
+ },
16
+ },
17
+ },
18
+ tags: ['autodocs'],
19
+ argTypes: {
20
+ className: {
21
+ control: 'text',
22
+ description: 'Additional CSS classes',
23
+ },
24
+ },
25
+ };
26
+
27
+ export default meta;
28
+ type Story = StoryObj<typeof meta>;
29
+
30
+ export const Default: Story = {
31
+ render: () => (
32
+ <Card className="w-[350px]">
33
+ <CardHeader>
34
+ <CardTitle>Card Title</CardTitle>
35
+ <CardDescription>Card Description</CardDescription>
36
+ </CardHeader>
37
+ <CardContent>
38
+ <p>Card Content</p>
39
+ </CardContent>
40
+ <CardFooter className="flex justify-between">
41
+ <Button variant="outline">Cancel</Button>
42
+ <Button>Deploy</Button>
43
+ </CardFooter>
44
+ </Card>
45
+ ),
46
+ };
47
+
48
+ export const WithNotification: Story = {
49
+ render: () => (
50
+ <Card className="w-[380px]">
51
+ <CardHeader>
52
+ <div className="flex items-center space-x-2">
53
+ <Bell className="h-5 w-5" />
54
+ <CardTitle>Notifications</CardTitle>
55
+ </div>
56
+ <CardDescription>
57
+ You have 3 unread messages.
58
+ </CardDescription>
59
+ </CardHeader>
60
+ <CardContent>
61
+ <div className="space-y-3">
62
+ <div className="flex items-center space-x-2">
63
+ <div className="w-2 h-2 bg-blue-500 rounded-full"></div>
64
+ <span className="text-sm">New comment on your post</span>
65
+ </div>
66
+ <div className="flex items-center space-x-2">
67
+ <div className="w-2 h-2 bg-green-500 rounded-full"></div>
68
+ <span className="text-sm">Your order has been shipped</span>
69
+ </div>
70
+ <div className="flex items-center space-x-2">
71
+ <div className="w-2 h-2 bg-yellow-500 rounded-full"></div>
72
+ <span className="text-sm">Payment reminder</span>
73
+ </div>
74
+ </div>
75
+ </CardContent>
76
+ <CardFooter>
77
+ <Button className="w-full">Mark all as read</Button>
78
+ </CardFooter>
79
+ </Card>
80
+ ),
81
+ };
82
+
83
+ export const WithBadge: Story = {
84
+ render: () => (
85
+ <Card className="w-[350px]">
86
+ <CardHeader>
87
+ <div className="flex items-center justify-between">
88
+ <CardTitle>Feature Request</CardTitle>
89
+ <Badge variant="secondary">Open</Badge>
90
+ </div>
91
+ <CardDescription>
92
+ Add dark mode support to the dashboard
93
+ </CardDescription>
94
+ </CardHeader>
95
+ <CardContent>
96
+ <p className="text-sm text-muted-foreground">
97
+ Users have been requesting a dark mode option for better accessibility and reduced eye strain during night usage.
98
+ </p>
99
+ <div className="mt-4 flex items-center space-x-2">
100
+ <div className="flex -space-x-2">
101
+ <div className="w-8 h-8 bg-blue-500 rounded-full border-2 border-background"></div>
102
+ <div className="w-8 h-8 bg-green-500 rounded-full border-2 border-background"></div>
103
+ <div className="w-8 h-8 bg-yellow-500 rounded-full border-2 border-background"></div>
104
+ </div>
105
+ <span className="text-sm text-muted-foreground">+12 others</span>
106
+ </div>
107
+ </CardContent>
108
+ <CardFooter className="flex justify-between">
109
+ <Button variant="outline">
110
+ <X className="mr-2 h-4 w-4" />
111
+ Close
112
+ </Button>
113
+ <Button>
114
+ <Check className="mr-2 h-4 w-4" />
115
+ Approve
116
+ </Button>
117
+ </CardFooter>
118
+ </Card>
119
+ ),
120
+ };
121
+
122
+ export const ProductCard: Story = {
123
+ render: () => (
124
+ <Card className="w-[300px]">
125
+ <CardHeader className="pb-3">
126
+ <div className="aspect-square bg-muted rounded-lg flex items-center justify-center">
127
+ <span className="text-muted-foreground text-sm">Product Image</span>
128
+ </div>
129
+ </CardHeader>
130
+ <CardContent>
131
+ <CardTitle className="text-lg">Wireless Headphones</CardTitle>
132
+ <CardDescription className="mt-2">
133
+ High-quality wireless headphones with noise cancellation
134
+ </CardDescription>
135
+ <div className="mt-4 flex items-center justify-between">
136
+ <span className="text-2xl font-bold">$299</span>
137
+ <Badge>In Stock</Badge>
138
+ </div>
139
+ </CardContent>
140
+ <CardFooter>
141
+ <Button className="w-full">Add to Cart</Button>
142
+ </CardFooter>
143
+ </Card>
144
+ ),
145
+ };
146
+
147
+ export const SimpleCard: Story = {
148
+ render: () => (
149
+ <Card className="w-[300px]">
150
+ <CardContent className="pt-6">
151
+ <div className="text-center space-y-2">
152
+ <h3 className="text-lg font-semibold">Total Revenue</h3>
153
+ <p className="text-3xl font-bold">$15,231.89</p>
154
+ <p className="text-sm text-muted-foreground">
155
+ +20.1% from last month
156
+ </p>
157
+ </div>
158
+ </CardContent>
159
+ </Card>
160
+ ),
161
+ };
162
+
163
+ export const InteractiveCard: Story = {
164
+ render: () => (
165
+ <Card className="w-[350px] cursor-pointer transition-all hover:shadow-lg">
166
+ <CardHeader>
167
+ <CardTitle>Interactive Card</CardTitle>
168
+ <CardDescription>
169
+ This card responds to hover and click events
170
+ </CardDescription>
171
+ </CardHeader>
172
+ <CardContent>
173
+ <p className="text-sm text-muted-foreground">
174
+ Hover over this card to see the elevation effect, or click to interact with it.
175
+ </p>
176
+ </CardContent>
177
+ </Card>
178
+ ),
179
+ };
180
+
181
+ export const CardGrid: Story = {
182
+ render: () => (
183
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
184
+ {Array.from({ length: 6 }).map((_, index) => (
185
+ <Card key={index} className="w-[250px]">
186
+ <CardHeader>
187
+ <CardTitle>Card {index + 1}</CardTitle>
188
+ <CardDescription>
189
+ This is card number {index + 1}
190
+ </CardDescription>
191
+ </CardHeader>
192
+ <CardContent>
193
+ <p className="text-sm text-muted-foreground">
194
+ Some content for card {index + 1}
195
+ </p>
196
+ </CardContent>
197
+ </Card>
198
+ ))}
199
+ </div>
200
+ ),
201
+ parameters: {
202
+ docs: {
203
+ description: {
204
+ story: 'Multiple cards arranged in a responsive grid layout.',
205
+ },
206
+ },
207
+ },
208
+ };
@@ -0,0 +1,141 @@
1
+ import * as React from "react";
2
+ import { cva, type VariantProps } from "class-variance-authority";
3
+ import { motion, type HTMLMotionProps } from "framer-motion";
4
+ import { cn } from "../../lib/utils";
5
+ import { microInteractionVariants, hoverAnimations, tapAnimations } from "@/lib/micro-interactions";
6
+
7
+ const cardVariants = cva(
8
+ "rounded-lg border bg-card text-card-foreground shadow-sm dark:shadow-gray-900/20 dark:border-gray-800 dark:bg-gray-900 dark:text-gray-100 transition-all duration-200",
9
+ {
10
+ variants: {
11
+ variant: {
12
+ default: "",
13
+ outline: "border border-border shadow-none bg-transparent dark:border-gray-700 dark:bg-transparent dark:text-gray-200",
14
+ filled: "border-none bg-secondary dark:bg-gray-800 dark:text-gray-100 dark:shadow-inner dark:shadow-gray-950/10",
15
+ elevated: "border-none shadow-md dark:shadow-lg dark:shadow-gray-900/30 dark:bg-gray-850 dark:text-gray-100",
16
+ gradient: "border-none bg-gradient-to-br from-primary/5 to-primary/10 dark:from-primary/10 dark:to-primary/20",
17
+ glass: "border border-white/20 bg-white/10 backdrop-blur-md dark:border-gray-700/30 dark:bg-gray-800/10",
18
+ },
19
+ size: {
20
+ default: "p-6",
21
+ sm: "p-4",
22
+ lg: "p-8",
23
+ xl: "p-10",
24
+ },
25
+ radius: {
26
+ default: "rounded-lg",
27
+ sm: "rounded-md",
28
+ lg: "rounded-xl",
29
+ full: "rounded-3xl",
30
+ none: "rounded-none",
31
+ },
32
+ interactive: {
33
+ true: "cursor-pointer hover:shadow-lg hover:border-primary/20 dark:hover:border-primary/30 hover:-translate-y-0.5 active:translate-y-0 active:shadow-sm",
34
+ false: "",
35
+ },
36
+ },
37
+ defaultVariants: {
38
+ variant: "default",
39
+ size: "default",
40
+ radius: "default",
41
+ },
42
+ }
43
+ );
44
+
45
+ export interface CardProps
46
+ extends React.HTMLAttributes<HTMLDivElement>,
47
+ VariantProps<typeof cardVariants> {
48
+ asChild?: boolean;
49
+ microInteraction?: "lift" | "glow" | "scale" | "none";
50
+ }
51
+
52
+ const Card = React.forwardRef<HTMLDivElement, CardProps>(
53
+ ({ className, variant, size, radius, interactive, microInteraction = "lift", ...props }, ref) => {
54
+ // Interactive özelliği varsa motion.div, yoksa normal div kullan
55
+ if (interactive && microInteraction !== "none") {
56
+ const interactionProps = {
57
+ whileHover: microInteraction === "scale" ? hoverAnimations.scale :
58
+ microInteraction === "glow" ? hoverAnimations.glow :
59
+ hoverAnimations.lift,
60
+ whileTap: tapAnimations.scale,
61
+ };
62
+
63
+ return (
64
+ <motion.div
65
+ ref={ref}
66
+ className={cn(cardVariants({ variant, size, radius, interactive, className }))}
67
+ {...interactionProps}
68
+ {...props}
69
+ />
70
+ );
71
+ }
72
+
73
+ // Non-interactive version
74
+ return (
75
+ <div
76
+ ref={ref}
77
+ className={cn(cardVariants({ variant, size, radius, interactive, className }))}
78
+ {...props}
79
+ />
80
+ );
81
+ }
82
+ );
83
+ Card.displayName = "Card";
84
+
85
+ const CardHeader = React.forwardRef<
86
+ HTMLDivElement,
87
+ React.HTMLAttributes<HTMLDivElement>
88
+ >(({ className, ...props }, ref) => (
89
+ <div
90
+ ref={ref}
91
+ className={cn("flex flex-col space-y-1.5", className)}
92
+ {...props}
93
+ />
94
+ ));
95
+ CardHeader.displayName = "CardHeader";
96
+
97
+ const CardTitle = React.forwardRef<
98
+ HTMLHeadingElement,
99
+ React.HTMLAttributes<HTMLHeadingElement>
100
+ >(({ className, ...props }, ref) => (
101
+ <h3
102
+ ref={ref}
103
+ className={cn("text-lg font-semibold leading-none tracking-tight", className)}
104
+ {...props}
105
+ />
106
+ ));
107
+ CardTitle.displayName = "CardTitle";
108
+
109
+ const CardDescription = React.forwardRef<
110
+ HTMLParagraphElement,
111
+ React.HTMLAttributes<HTMLParagraphElement>
112
+ >(({ className, ...props }, ref) => (
113
+ <p
114
+ ref={ref}
115
+ className={cn("text-sm text-muted-foreground", className)}
116
+ {...props}
117
+ />
118
+ ));
119
+ CardDescription.displayName = "CardDescription";
120
+
121
+ const CardContent = React.forwardRef<
122
+ HTMLDivElement,
123
+ React.HTMLAttributes<HTMLDivElement>
124
+ >(({ className, ...props }, ref) => (
125
+ <div ref={ref} className={cn("pt-0", className)} {...props} />
126
+ ));
127
+ CardContent.displayName = "CardContent";
128
+
129
+ const CardFooter = React.forwardRef<
130
+ HTMLDivElement,
131
+ React.HTMLAttributes<HTMLDivElement>
132
+ >(({ className, ...props }, ref) => (
133
+ <div
134
+ ref={ref}
135
+ className={cn("flex items-center pt-4", className)}
136
+ {...props}
137
+ />
138
+ ));
139
+ CardFooter.displayName = "CardFooter";
140
+
141
+ export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent };