@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.
- package/package.json +26 -19
- package/src/__tests__/use-intersection-observer.test.tsx +216 -0
- package/src/__tests__/use-local-storage.test.tsx +174 -0
- package/src/__tests__/use-pro-access.test.tsx +183 -0
- package/src/components/ui/__tests__/accordion.test.tsx +571 -0
- package/src/components/ui/__tests__/alert.test.tsx +484 -0
- package/src/components/ui/__tests__/aspect-ratio.test.tsx +492 -0
- package/src/components/ui/__tests__/avatar.test.tsx +382 -0
- package/src/components/ui/__tests__/badge.test.tsx +364 -0
- package/src/components/ui/__tests__/breadcrumb.test.tsx +687 -0
- package/src/components/ui/__tests__/button.test.tsx +91 -0
- package/src/components/ui/__tests__/card.test.tsx +104 -0
- package/src/components/ui/__tests__/checkbox.test.tsx +591 -0
- package/src/components/ui/__tests__/collapsible.test.tsx +592 -0
- package/src/components/ui/__tests__/color-picker.test.tsx +365 -0
- package/src/components/ui/__tests__/command.test.tsx +677 -0
- package/src/components/ui/__tests__/data-table.test.tsx +256 -0
- package/src/components/ui/__tests__/date-picker.test.tsx +320 -0
- package/src/components/ui/__tests__/dialog.test.tsx +764 -0
- package/src/components/ui/__tests__/input.test.tsx +318 -0
- package/src/components/ui/__tests__/label.test.tsx +103 -0
- package/src/components/ui/__tests__/popover.test.tsx +637 -0
- package/src/components/ui/__tests__/progress.test.tsx +382 -0
- package/src/components/ui/__tests__/radio-group.test.tsx +555 -0
- package/src/components/ui/__tests__/select.test.tsx +705 -0
- package/src/components/ui/__tests__/skeleton.test.tsx +253 -0
- package/src/components/ui/__tests__/slider.test.tsx +493 -0
- package/src/components/ui/__tests__/switch.test.tsx +372 -0
- package/src/components/ui/__tests__/table.test.tsx +824 -0
- package/src/components/ui/__tests__/tabs.test.tsx +887 -0
- package/src/components/ui/__tests__/toast.test.tsx +458 -0
- package/src/components/ui/__tests__/tooltip.test.tsx +583 -0
- package/src/components/ui/accordion.tsx +63 -0
- package/src/components/ui/alert.tsx +130 -0
- package/src/components/ui/aspect-ratio.tsx +72 -0
- package/src/components/ui/avatar.tsx +135 -0
- package/src/components/ui/badge.tsx +225 -0
- package/src/components/ui/breadcrumb.tsx +207 -0
- package/src/components/ui/button.stories.tsx +162 -0
- package/src/components/ui/button.tsx +221 -0
- package/src/components/ui/calendar.tsx +262 -0
- package/src/components/ui/card.stories.tsx +208 -0
- package/src/components/ui/card.tsx +141 -0
- package/src/components/ui/checkbox.tsx +256 -0
- package/src/components/ui/collapsible.tsx +129 -0
- package/src/components/ui/color-picker.tsx +664 -0
- package/src/components/ui/command.tsx +218 -0
- package/src/components/ui/data-table.stories.tsx +509 -0
- package/src/components/ui/data-table.tsx +623 -0
- package/src/components/ui/date-picker.stories.tsx +321 -0
- package/src/components/ui/date-picker.tsx +603 -0
- package/src/components/ui/dialog.tsx +332 -0
- package/src/components/ui/dropdown-menu.tsx +200 -0
- package/src/components/ui/file-upload.tsx +400 -0
- package/src/components/ui/github-stars.tsx +201 -0
- package/src/components/ui/index.ts +12 -0
- package/src/components/ui/input.stories.tsx +255 -0
- package/src/components/ui/input.tsx +219 -0
- package/src/components/ui/label.tsx +26 -0
- package/src/components/ui/locked-component.tsx +215 -0
- package/src/components/ui/moon-logo.tsx +97 -0
- package/src/components/ui/pagination.tsx +116 -0
- package/src/components/ui/popover.tsx +183 -0
- package/src/components/ui/progress.tsx +182 -0
- package/src/components/ui/radio-group.tsx +243 -0
- package/src/components/ui/select.tsx +273 -0
- package/src/components/ui/separator.tsx +140 -0
- package/src/components/ui/simple-editor.tsx +652 -0
- package/src/components/ui/skeleton.tsx +351 -0
- package/src/components/ui/slider.tsx +351 -0
- package/src/components/ui/switch.tsx +83 -0
- package/src/components/ui/table.tsx +322 -0
- package/src/components/ui/tabs.tsx +195 -0
- package/src/components/ui/textarea.tsx +46 -0
- package/src/components/ui/toast.tsx +313 -0
- package/src/components/ui/toggle.tsx +47 -0
- package/src/components/ui/tooltip.tsx +152 -0
- package/src/docs/theme-system.md +1194 -0
- package/src/index.tsx +39 -0
- package/src/lib/micro-interactions.ts +255 -0
- package/src/lib/theme.tsx +398 -0
- package/src/lib/utils.ts +69 -0
- package/src/styles/design-system.css +365 -0
- package/src/styles/index.css +4 -0
- package/src/styles/tailwind.css +6 -0
- package/src/styles/tokens.css +453 -0
- package/src/use-intersection-observer.tsx +154 -0
- package/src/use-local-storage.tsx +71 -0
- package/src/use-paddle.ts +138 -0
- package/src/use-performance-optimizer.ts +379 -0
- package/src/use-pro-access.ts +141 -0
- package/src/use-scroll-animation.ts +221 -0
- package/src/use-subscription.ts +37 -0
- package/src/use-toast.ts +32 -0
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { Search } from "lucide-react"
|
|
5
|
+
import { Command as CommandPrimitive } from "cmdk"
|
|
6
|
+
import { cva, type VariantProps } from "class-variance-authority"
|
|
7
|
+
|
|
8
|
+
import { cn } from "../../lib/utils"
|
|
9
|
+
import { Dialog, DialogContent, DialogTitle } from "./dialog"
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Command (Komut Paleti) Bileşeni
|
|
13
|
+
*
|
|
14
|
+
* Gerçek command palette işlevselliği ile klavye navigasyonu, arama ve filtreleme.
|
|
15
|
+
* cmdk kütüphanesi üzerine inşa edilmiş, tam özellikli command interface.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const commandVariants = cva(
|
|
19
|
+
"flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground",
|
|
20
|
+
{
|
|
21
|
+
variants: {
|
|
22
|
+
variant: {
|
|
23
|
+
default: "bg-popover text-popover-foreground",
|
|
24
|
+
glass: "bg-background/80 text-foreground backdrop-blur-sm",
|
|
25
|
+
bordered: "bg-popover text-popover-foreground border border-border",
|
|
26
|
+
},
|
|
27
|
+
size: {
|
|
28
|
+
sm: "min-h-[200px]",
|
|
29
|
+
default: "min-h-[300px]",
|
|
30
|
+
lg: "min-h-[400px]",
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
defaultVariants: {
|
|
34
|
+
variant: "default",
|
|
35
|
+
size: "default",
|
|
36
|
+
},
|
|
37
|
+
}
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
// Command bileşeni - cmdk primitive wrapper
|
|
41
|
+
interface CommandProps
|
|
42
|
+
extends React.ComponentProps<typeof CommandPrimitive>,
|
|
43
|
+
VariantProps<typeof commandVariants> {}
|
|
44
|
+
|
|
45
|
+
const Command = React.forwardRef<
|
|
46
|
+
React.ElementRef<typeof CommandPrimitive>,
|
|
47
|
+
CommandProps
|
|
48
|
+
>(({ className, variant, size, ...props }, ref) => (
|
|
49
|
+
<CommandPrimitive
|
|
50
|
+
ref={ref}
|
|
51
|
+
className={cn(commandVariants({ variant, size }), className)}
|
|
52
|
+
{...props}
|
|
53
|
+
/>
|
|
54
|
+
))
|
|
55
|
+
Command.displayName = CommandPrimitive.displayName
|
|
56
|
+
|
|
57
|
+
// CommandDialog bileşeni
|
|
58
|
+
interface CommandDialogProps extends React.ComponentProps<typeof Dialog> {
|
|
59
|
+
commandClassName?: string
|
|
60
|
+
children?: React.ReactNode
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const CommandDialog = ({
|
|
64
|
+
children,
|
|
65
|
+
commandClassName,
|
|
66
|
+
...props
|
|
67
|
+
}: CommandDialogProps) => {
|
|
68
|
+
return (
|
|
69
|
+
<Dialog {...props}>
|
|
70
|
+
<DialogContent className="overflow-hidden p-0 shadow-lg" hideCloseButton>
|
|
71
|
+
<DialogTitle className="sr-only">Command Menu</DialogTitle>
|
|
72
|
+
<Command className={cn(
|
|
73
|
+
"[&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]]:px-2 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5",
|
|
74
|
+
commandClassName
|
|
75
|
+
)}>
|
|
76
|
+
{children}
|
|
77
|
+
</Command>
|
|
78
|
+
</DialogContent>
|
|
79
|
+
</Dialog>
|
|
80
|
+
)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// CommandInput bileşeni
|
|
84
|
+
interface CommandInputProps
|
|
85
|
+
extends React.ComponentProps<typeof CommandPrimitive.Input> {}
|
|
86
|
+
|
|
87
|
+
const CommandInput = React.forwardRef<
|
|
88
|
+
React.ElementRef<typeof CommandPrimitive.Input>,
|
|
89
|
+
CommandInputProps
|
|
90
|
+
>(({ className, ...props }, ref) => (
|
|
91
|
+
<div className="flex items-center border-b px-3" cmdk-input-wrapper="">
|
|
92
|
+
<Search className="mr-2 h-4 w-4 shrink-0 opacity-50" />
|
|
93
|
+
<CommandPrimitive.Input
|
|
94
|
+
ref={ref}
|
|
95
|
+
className={cn(
|
|
96
|
+
"flex h-11 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50",
|
|
97
|
+
className
|
|
98
|
+
)}
|
|
99
|
+
{...props}
|
|
100
|
+
/>
|
|
101
|
+
</div>
|
|
102
|
+
))
|
|
103
|
+
CommandInput.displayName = CommandPrimitive.Input.displayName
|
|
104
|
+
|
|
105
|
+
// CommandList bileşeni
|
|
106
|
+
interface CommandListProps
|
|
107
|
+
extends React.ComponentProps<typeof CommandPrimitive.List> {}
|
|
108
|
+
|
|
109
|
+
const CommandList = React.forwardRef<
|
|
110
|
+
React.ElementRef<typeof CommandPrimitive.List>,
|
|
111
|
+
CommandListProps
|
|
112
|
+
>(({ className, ...props }, ref) => (
|
|
113
|
+
<CommandPrimitive.List
|
|
114
|
+
ref={ref}
|
|
115
|
+
className={cn("max-h-[300px] overflow-y-auto overflow-x-hidden", className)}
|
|
116
|
+
{...props}
|
|
117
|
+
/>
|
|
118
|
+
))
|
|
119
|
+
CommandList.displayName = CommandPrimitive.List.displayName
|
|
120
|
+
|
|
121
|
+
// CommandEmpty bileşeni
|
|
122
|
+
interface CommandEmptyProps
|
|
123
|
+
extends React.ComponentProps<typeof CommandPrimitive.Empty> {}
|
|
124
|
+
|
|
125
|
+
const CommandEmpty = React.forwardRef<
|
|
126
|
+
React.ElementRef<typeof CommandPrimitive.Empty>,
|
|
127
|
+
CommandEmptyProps
|
|
128
|
+
>(({ className, ...props }, ref) => (
|
|
129
|
+
<CommandPrimitive.Empty
|
|
130
|
+
ref={ref}
|
|
131
|
+
className={cn("py-6 text-center text-sm text-muted-foreground", className)}
|
|
132
|
+
{...props}
|
|
133
|
+
/>
|
|
134
|
+
))
|
|
135
|
+
CommandEmpty.displayName = CommandPrimitive.Empty.displayName
|
|
136
|
+
|
|
137
|
+
// CommandGroup bileşeni
|
|
138
|
+
interface CommandGroupProps
|
|
139
|
+
extends React.ComponentProps<typeof CommandPrimitive.Group> {}
|
|
140
|
+
|
|
141
|
+
const CommandGroup = React.forwardRef<
|
|
142
|
+
React.ElementRef<typeof CommandPrimitive.Group>,
|
|
143
|
+
CommandGroupProps
|
|
144
|
+
>(({ className, ...props }, ref) => (
|
|
145
|
+
<CommandPrimitive.Group
|
|
146
|
+
ref={ref}
|
|
147
|
+
className={cn(
|
|
148
|
+
"overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground",
|
|
149
|
+
className
|
|
150
|
+
)}
|
|
151
|
+
{...props}
|
|
152
|
+
/>
|
|
153
|
+
))
|
|
154
|
+
CommandGroup.displayName = CommandPrimitive.Group.displayName
|
|
155
|
+
|
|
156
|
+
// CommandSeparator bileşeni
|
|
157
|
+
interface CommandSeparatorProps
|
|
158
|
+
extends React.ComponentProps<typeof CommandPrimitive.Separator> {}
|
|
159
|
+
|
|
160
|
+
const CommandSeparator = React.forwardRef<
|
|
161
|
+
React.ElementRef<typeof CommandPrimitive.Separator>,
|
|
162
|
+
CommandSeparatorProps
|
|
163
|
+
>(({ className, ...props }, ref) => (
|
|
164
|
+
<CommandPrimitive.Separator
|
|
165
|
+
ref={ref}
|
|
166
|
+
className={cn("-mx-1 h-px bg-border", className)}
|
|
167
|
+
{...props}
|
|
168
|
+
/>
|
|
169
|
+
))
|
|
170
|
+
CommandSeparator.displayName = CommandPrimitive.Separator.displayName
|
|
171
|
+
|
|
172
|
+
// CommandItem bileşeni
|
|
173
|
+
interface CommandItemProps
|
|
174
|
+
extends React.ComponentProps<typeof CommandPrimitive.Item> {}
|
|
175
|
+
|
|
176
|
+
const CommandItem = React.forwardRef<
|
|
177
|
+
React.ElementRef<typeof CommandPrimitive.Item>,
|
|
178
|
+
CommandItemProps
|
|
179
|
+
>(({ className, ...props }, ref) => (
|
|
180
|
+
<CommandPrimitive.Item
|
|
181
|
+
ref={ref}
|
|
182
|
+
className={cn(
|
|
183
|
+
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none aria-selected:bg-accent aria-selected:text-accent-foreground data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50",
|
|
184
|
+
className
|
|
185
|
+
)}
|
|
186
|
+
{...props}
|
|
187
|
+
/>
|
|
188
|
+
))
|
|
189
|
+
CommandItem.displayName = CommandPrimitive.Item.displayName
|
|
190
|
+
|
|
191
|
+
// CommandShortcut bileşeni - utility component
|
|
192
|
+
const CommandShortcut = ({
|
|
193
|
+
className,
|
|
194
|
+
...props
|
|
195
|
+
}: React.HTMLAttributes<HTMLSpanElement>) => {
|
|
196
|
+
return (
|
|
197
|
+
<span
|
|
198
|
+
className={cn(
|
|
199
|
+
"ml-auto text-xs tracking-widest text-muted-foreground",
|
|
200
|
+
className
|
|
201
|
+
)}
|
|
202
|
+
{...props}
|
|
203
|
+
/>
|
|
204
|
+
)
|
|
205
|
+
}
|
|
206
|
+
CommandShortcut.displayName = "CommandShortcut"
|
|
207
|
+
|
|
208
|
+
export {
|
|
209
|
+
Command,
|
|
210
|
+
CommandDialog,
|
|
211
|
+
CommandInput,
|
|
212
|
+
CommandList,
|
|
213
|
+
CommandEmpty,
|
|
214
|
+
CommandGroup,
|
|
215
|
+
CommandItem,
|
|
216
|
+
CommandShortcut,
|
|
217
|
+
CommandSeparator,
|
|
218
|
+
}
|
|
@@ -0,0 +1,509 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import { DataTable } from './data-table';
|
|
3
|
+
import { Badge } from './badge';
|
|
4
|
+
import { Button } from './button';
|
|
5
|
+
import { MoreHorizontal, ArrowUpDown, Edit, Trash2 } from 'lucide-react';
|
|
6
|
+
import { ColumnDef } from '@tanstack/react-table';
|
|
7
|
+
|
|
8
|
+
// Sample data types
|
|
9
|
+
type User = {
|
|
10
|
+
id: string;
|
|
11
|
+
name: string;
|
|
12
|
+
email: string;
|
|
13
|
+
role: 'admin' | 'user' | 'moderator';
|
|
14
|
+
status: 'active' | 'inactive' | 'pending';
|
|
15
|
+
createdAt: string;
|
|
16
|
+
lastLogin: string;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
type Product = {
|
|
20
|
+
id: string;
|
|
21
|
+
name: string;
|
|
22
|
+
category: string;
|
|
23
|
+
price: number;
|
|
24
|
+
stock: number;
|
|
25
|
+
status: 'available' | 'out_of_stock' | 'discontinued';
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
// Sample data
|
|
29
|
+
const userData: User[] = [
|
|
30
|
+
{
|
|
31
|
+
id: '1',
|
|
32
|
+
name: 'John Doe',
|
|
33
|
+
email: 'john@example.com',
|
|
34
|
+
role: 'admin',
|
|
35
|
+
status: 'active',
|
|
36
|
+
createdAt: '2023-01-15',
|
|
37
|
+
lastLogin: '2024-01-15',
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
id: '2',
|
|
41
|
+
name: 'Jane Smith',
|
|
42
|
+
email: 'jane@example.com',
|
|
43
|
+
role: 'user',
|
|
44
|
+
status: 'active',
|
|
45
|
+
createdAt: '2023-02-20',
|
|
46
|
+
lastLogin: '2024-01-14',
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
id: '3',
|
|
50
|
+
name: 'Bob Johnson',
|
|
51
|
+
email: 'bob@example.com',
|
|
52
|
+
role: 'moderator',
|
|
53
|
+
status: 'inactive',
|
|
54
|
+
createdAt: '2023-03-10',
|
|
55
|
+
lastLogin: '2024-01-10',
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
id: '4',
|
|
59
|
+
name: 'Alice Brown',
|
|
60
|
+
email: 'alice@example.com',
|
|
61
|
+
role: 'user',
|
|
62
|
+
status: 'pending',
|
|
63
|
+
createdAt: '2023-04-05',
|
|
64
|
+
lastLogin: '2024-01-12',
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
id: '5',
|
|
68
|
+
name: 'Charlie Wilson',
|
|
69
|
+
email: 'charlie@example.com',
|
|
70
|
+
role: 'user',
|
|
71
|
+
status: 'active',
|
|
72
|
+
createdAt: '2023-05-01',
|
|
73
|
+
lastLogin: '2024-01-11',
|
|
74
|
+
},
|
|
75
|
+
];
|
|
76
|
+
|
|
77
|
+
const productData: Product[] = [
|
|
78
|
+
{
|
|
79
|
+
id: '1',
|
|
80
|
+
name: 'Wireless Headphones',
|
|
81
|
+
category: 'Electronics',
|
|
82
|
+
price: 299.99,
|
|
83
|
+
stock: 45,
|
|
84
|
+
status: 'available',
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
id: '2',
|
|
88
|
+
name: 'Gaming Mouse',
|
|
89
|
+
category: 'Electronics',
|
|
90
|
+
price: 89.99,
|
|
91
|
+
stock: 0,
|
|
92
|
+
status: 'out_of_stock',
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
id: '3',
|
|
96
|
+
name: 'Mechanical Keyboard',
|
|
97
|
+
category: 'Electronics',
|
|
98
|
+
price: 159.99,
|
|
99
|
+
stock: 23,
|
|
100
|
+
status: 'available',
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
id: '4',
|
|
104
|
+
name: 'USB-C Cable',
|
|
105
|
+
category: 'Accessories',
|
|
106
|
+
price: 19.99,
|
|
107
|
+
stock: 100,
|
|
108
|
+
status: 'available',
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
id: '5',
|
|
112
|
+
name: 'Laptop Stand',
|
|
113
|
+
category: 'Accessories',
|
|
114
|
+
price: 49.99,
|
|
115
|
+
stock: 0,
|
|
116
|
+
status: 'discontinued',
|
|
117
|
+
},
|
|
118
|
+
];
|
|
119
|
+
|
|
120
|
+
// User columns
|
|
121
|
+
const userColumns: ColumnDef<User>[] = [
|
|
122
|
+
{
|
|
123
|
+
accessorKey: 'name',
|
|
124
|
+
header: ({ column }) => (
|
|
125
|
+
<Button
|
|
126
|
+
variant="ghost"
|
|
127
|
+
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
|
|
128
|
+
>
|
|
129
|
+
Name
|
|
130
|
+
<ArrowUpDown className="ml-2 h-4 w-4" />
|
|
131
|
+
</Button>
|
|
132
|
+
),
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
accessorKey: 'email',
|
|
136
|
+
header: 'Email',
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
accessorKey: 'role',
|
|
140
|
+
header: 'Role',
|
|
141
|
+
cell: ({ row }) => {
|
|
142
|
+
const role = row.getValue('role') as string;
|
|
143
|
+
return (
|
|
144
|
+
<Badge variant={role === 'admin' ? 'default' : 'secondary'}>
|
|
145
|
+
{role}
|
|
146
|
+
</Badge>
|
|
147
|
+
);
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
accessorKey: 'status',
|
|
152
|
+
header: 'Status',
|
|
153
|
+
cell: ({ row }) => {
|
|
154
|
+
const status = row.getValue('status') as string;
|
|
155
|
+
return (
|
|
156
|
+
<Badge
|
|
157
|
+
variant={
|
|
158
|
+
status === 'active'
|
|
159
|
+
? 'default'
|
|
160
|
+
: status === 'inactive'
|
|
161
|
+
? 'secondary'
|
|
162
|
+
: 'outline'
|
|
163
|
+
}
|
|
164
|
+
>
|
|
165
|
+
{status}
|
|
166
|
+
</Badge>
|
|
167
|
+
);
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
accessorKey: 'createdAt',
|
|
172
|
+
header: 'Created',
|
|
173
|
+
cell: ({ row }) => {
|
|
174
|
+
const date = row.getValue('createdAt') as string;
|
|
175
|
+
return new Date(date).toLocaleDateString();
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
id: 'actions',
|
|
180
|
+
cell: ({ row }) => {
|
|
181
|
+
const user = row.original;
|
|
182
|
+
return (
|
|
183
|
+
<div className="flex items-center gap-2">
|
|
184
|
+
<Button variant="ghost" size="sm">
|
|
185
|
+
<Edit className="h-4 w-4" />
|
|
186
|
+
</Button>
|
|
187
|
+
<Button variant="ghost" size="sm">
|
|
188
|
+
<Trash2 className="h-4 w-4" />
|
|
189
|
+
</Button>
|
|
190
|
+
<Button variant="ghost" size="sm">
|
|
191
|
+
<MoreHorizontal className="h-4 w-4" />
|
|
192
|
+
</Button>
|
|
193
|
+
</div>
|
|
194
|
+
);
|
|
195
|
+
},
|
|
196
|
+
},
|
|
197
|
+
];
|
|
198
|
+
|
|
199
|
+
// Product columns
|
|
200
|
+
const productColumns: ColumnDef<Product>[] = [
|
|
201
|
+
{
|
|
202
|
+
accessorKey: 'name',
|
|
203
|
+
header: ({ column }) => (
|
|
204
|
+
<Button
|
|
205
|
+
variant="ghost"
|
|
206
|
+
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
|
|
207
|
+
>
|
|
208
|
+
Product
|
|
209
|
+
<ArrowUpDown className="ml-2 h-4 w-4" />
|
|
210
|
+
</Button>
|
|
211
|
+
),
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
accessorKey: 'category',
|
|
215
|
+
header: 'Category',
|
|
216
|
+
cell: ({ row }) => {
|
|
217
|
+
const category = row.getValue('category') as string;
|
|
218
|
+
return <Badge variant="outline">{category}</Badge>;
|
|
219
|
+
},
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
accessorKey: 'price',
|
|
223
|
+
header: ({ column }) => (
|
|
224
|
+
<Button
|
|
225
|
+
variant="ghost"
|
|
226
|
+
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
|
|
227
|
+
>
|
|
228
|
+
Price
|
|
229
|
+
<ArrowUpDown className="ml-2 h-4 w-4" />
|
|
230
|
+
</Button>
|
|
231
|
+
),
|
|
232
|
+
cell: ({ row }) => {
|
|
233
|
+
const price = parseFloat(row.getValue('price'));
|
|
234
|
+
return new Intl.NumberFormat('en-US', {
|
|
235
|
+
style: 'currency',
|
|
236
|
+
currency: 'USD',
|
|
237
|
+
}).format(price);
|
|
238
|
+
},
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
accessorKey: 'stock',
|
|
242
|
+
header: ({ column }) => (
|
|
243
|
+
<Button
|
|
244
|
+
variant="ghost"
|
|
245
|
+
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
|
|
246
|
+
>
|
|
247
|
+
Stock
|
|
248
|
+
<ArrowUpDown className="ml-2 h-4 w-4" />
|
|
249
|
+
</Button>
|
|
250
|
+
),
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
accessorKey: 'status',
|
|
254
|
+
header: 'Status',
|
|
255
|
+
cell: ({ row }) => {
|
|
256
|
+
const status = row.getValue('status') as string;
|
|
257
|
+
return (
|
|
258
|
+
<Badge
|
|
259
|
+
variant={
|
|
260
|
+
status === 'available'
|
|
261
|
+
? 'default'
|
|
262
|
+
: status === 'out_of_stock'
|
|
263
|
+
? 'secondary'
|
|
264
|
+
: 'outline'
|
|
265
|
+
}
|
|
266
|
+
>
|
|
267
|
+
{status.replace('_', ' ')}
|
|
268
|
+
</Badge>
|
|
269
|
+
);
|
|
270
|
+
},
|
|
271
|
+
},
|
|
272
|
+
];
|
|
273
|
+
|
|
274
|
+
const meta: Meta<typeof DataTable> = {
|
|
275
|
+
title: 'Components/DataTable',
|
|
276
|
+
component: DataTable,
|
|
277
|
+
parameters: {
|
|
278
|
+
layout: 'fullscreen',
|
|
279
|
+
docs: {
|
|
280
|
+
description: {
|
|
281
|
+
component: 'A powerful data table component with sorting, filtering, and pagination.',
|
|
282
|
+
},
|
|
283
|
+
},
|
|
284
|
+
},
|
|
285
|
+
tags: ['autodocs'],
|
|
286
|
+
argTypes: {
|
|
287
|
+
columns: {
|
|
288
|
+
description: 'Column definitions for the table',
|
|
289
|
+
},
|
|
290
|
+
data: {
|
|
291
|
+
description: 'Data to display in the table',
|
|
292
|
+
},
|
|
293
|
+
searchable: {
|
|
294
|
+
control: 'boolean',
|
|
295
|
+
description: 'Enable search functionality',
|
|
296
|
+
},
|
|
297
|
+
filterable: {
|
|
298
|
+
control: 'boolean',
|
|
299
|
+
description: 'Enable column filtering',
|
|
300
|
+
},
|
|
301
|
+
exportable: {
|
|
302
|
+
control: 'boolean',
|
|
303
|
+
description: 'Enable data export',
|
|
304
|
+
},
|
|
305
|
+
},
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
export default meta;
|
|
309
|
+
type Story = StoryObj<typeof meta>;
|
|
310
|
+
|
|
311
|
+
export const Default: Story = {
|
|
312
|
+
args: {
|
|
313
|
+
columns: userColumns,
|
|
314
|
+
data: userData,
|
|
315
|
+
},
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
export const WithSearch: Story = {
|
|
319
|
+
args: {
|
|
320
|
+
columns: userColumns,
|
|
321
|
+
data: userData,
|
|
322
|
+
searchable: true,
|
|
323
|
+
},
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
export const WithFilters: Story = {
|
|
327
|
+
args: {
|
|
328
|
+
columns: userColumns,
|
|
329
|
+
data: userData,
|
|
330
|
+
searchable: true,
|
|
331
|
+
filterable: true,
|
|
332
|
+
},
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
export const WithExport: Story = {
|
|
336
|
+
args: {
|
|
337
|
+
columns: userColumns,
|
|
338
|
+
data: userData,
|
|
339
|
+
searchable: true,
|
|
340
|
+
filterable: true,
|
|
341
|
+
exportable: true,
|
|
342
|
+
},
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
export const ProductTable: Story = {
|
|
346
|
+
args: {
|
|
347
|
+
columns: productColumns,
|
|
348
|
+
data: productData,
|
|
349
|
+
searchable: true,
|
|
350
|
+
filterable: true,
|
|
351
|
+
},
|
|
352
|
+
};
|
|
353
|
+
|
|
354
|
+
export const LargeDataset: Story = {
|
|
355
|
+
args: {
|
|
356
|
+
columns: userColumns,
|
|
357
|
+
data: Array.from({ length: 50 }, (_, i) => ({
|
|
358
|
+
id: `${i + 1}`,
|
|
359
|
+
name: `User ${i + 1}`,
|
|
360
|
+
email: `user${i + 1}@example.com`,
|
|
361
|
+
role: ['admin', 'user', 'moderator'][i % 3] as 'admin' | 'user' | 'moderator',
|
|
362
|
+
status: ['active', 'inactive', 'pending'][i % 3] as 'active' | 'inactive' | 'pending',
|
|
363
|
+
createdAt: new Date(2023, Math.floor(i / 12), (i % 30) + 1).toISOString().split('T')[0],
|
|
364
|
+
lastLogin: new Date(2024, 0, (i % 15) + 1).toISOString().split('T')[0],
|
|
365
|
+
})),
|
|
366
|
+
searchable: true,
|
|
367
|
+
filterable: true,
|
|
368
|
+
exportable: true,
|
|
369
|
+
},
|
|
370
|
+
};
|
|
371
|
+
|
|
372
|
+
export const EmptyState: Story = {
|
|
373
|
+
args: {
|
|
374
|
+
columns: userColumns,
|
|
375
|
+
data: [],
|
|
376
|
+
searchable: true,
|
|
377
|
+
filterable: true,
|
|
378
|
+
},
|
|
379
|
+
};
|
|
380
|
+
|
|
381
|
+
export const SingleRow: Story = {
|
|
382
|
+
args: {
|
|
383
|
+
columns: userColumns,
|
|
384
|
+
data: [userData[0]],
|
|
385
|
+
searchable: true,
|
|
386
|
+
},
|
|
387
|
+
};
|
|
388
|
+
|
|
389
|
+
export const CustomPageSize: Story = {
|
|
390
|
+
args: {
|
|
391
|
+
columns: productColumns,
|
|
392
|
+
data: productData,
|
|
393
|
+
searchable: true,
|
|
394
|
+
filterable: true,
|
|
395
|
+
// Custom page size would be handled by the DataTable component
|
|
396
|
+
},
|
|
397
|
+
};
|
|
398
|
+
|
|
399
|
+
export const CompactTable: Story = {
|
|
400
|
+
args: {
|
|
401
|
+
columns: productColumns.map(col => ({
|
|
402
|
+
...col,
|
|
403
|
+
// Add compact styling
|
|
404
|
+
})),
|
|
405
|
+
data: productData,
|
|
406
|
+
searchable: true,
|
|
407
|
+
// Compact prop would be handled by the DataTable component
|
|
408
|
+
},
|
|
409
|
+
};
|
|
410
|
+
|
|
411
|
+
export const InteractiveExample: Story = {
|
|
412
|
+
render: () => {
|
|
413
|
+
const columns: ColumnDef<User>[] = [
|
|
414
|
+
{
|
|
415
|
+
accessorKey: 'name',
|
|
416
|
+
header: ({ column }) => (
|
|
417
|
+
<Button
|
|
418
|
+
variant="ghost"
|
|
419
|
+
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
|
|
420
|
+
>
|
|
421
|
+
Name
|
|
422
|
+
<ArrowUpDown className="ml-2 h-4 w-4" />
|
|
423
|
+
</Button>
|
|
424
|
+
),
|
|
425
|
+
},
|
|
426
|
+
{
|
|
427
|
+
accessorKey: 'email',
|
|
428
|
+
header: 'Email',
|
|
429
|
+
},
|
|
430
|
+
{
|
|
431
|
+
accessorKey: 'role',
|
|
432
|
+
header: 'Role',
|
|
433
|
+
cell: ({ row }) => {
|
|
434
|
+
const role = row.getValue('role') as string;
|
|
435
|
+
return (
|
|
436
|
+
<Badge
|
|
437
|
+
variant={role === 'admin' ? 'default' : 'secondary'}
|
|
438
|
+
className="cursor-pointer"
|
|
439
|
+
onClick={() => alert(`Role: ${role}`)}
|
|
440
|
+
>
|
|
441
|
+
{role}
|
|
442
|
+
</Badge>
|
|
443
|
+
);
|
|
444
|
+
},
|
|
445
|
+
},
|
|
446
|
+
{
|
|
447
|
+
accessorKey: 'status',
|
|
448
|
+
header: 'Status',
|
|
449
|
+
cell: ({ row }) => {
|
|
450
|
+
const status = row.getValue('status') as string;
|
|
451
|
+
return (
|
|
452
|
+
<Badge
|
|
453
|
+
variant={
|
|
454
|
+
status === 'active'
|
|
455
|
+
? 'default'
|
|
456
|
+
: status === 'inactive'
|
|
457
|
+
? 'secondary'
|
|
458
|
+
: 'outline'
|
|
459
|
+
}
|
|
460
|
+
className="cursor-pointer"
|
|
461
|
+
onClick={() => alert(`Status: ${status}`)}
|
|
462
|
+
>
|
|
463
|
+
{status}
|
|
464
|
+
</Badge>
|
|
465
|
+
);
|
|
466
|
+
},
|
|
467
|
+
},
|
|
468
|
+
{
|
|
469
|
+
id: 'actions',
|
|
470
|
+
cell: ({ row }) => {
|
|
471
|
+
const user = row.original;
|
|
472
|
+
return (
|
|
473
|
+
<div className="flex items-center gap-2">
|
|
474
|
+
<Button
|
|
475
|
+
variant="ghost"
|
|
476
|
+
size="sm"
|
|
477
|
+
onClick={() => alert(`Edit user: ${user.name}`)}
|
|
478
|
+
>
|
|
479
|
+
<Edit className="h-4 w-4" />
|
|
480
|
+
</Button>
|
|
481
|
+
<Button
|
|
482
|
+
variant="ghost"
|
|
483
|
+
size="sm"
|
|
484
|
+
onClick={() => alert(`Delete user: ${user.name}`)}
|
|
485
|
+
>
|
|
486
|
+
<Trash2 className="h-4 w-4" />
|
|
487
|
+
</Button>
|
|
488
|
+
</div>
|
|
489
|
+
);
|
|
490
|
+
},
|
|
491
|
+
},
|
|
492
|
+
];
|
|
493
|
+
|
|
494
|
+
return (
|
|
495
|
+
<div className="space-y-4">
|
|
496
|
+
<div className="text-sm text-muted-foreground">
|
|
497
|
+
Click on badges or action buttons to see interactions
|
|
498
|
+
</div>
|
|
499
|
+
<DataTable
|
|
500
|
+
columns={columns}
|
|
501
|
+
data={userData}
|
|
502
|
+
searchable={true}
|
|
503
|
+
filterable={true}
|
|
504
|
+
exportable={true}
|
|
505
|
+
/>
|
|
506
|
+
</div>
|
|
507
|
+
);
|
|
508
|
+
},
|
|
509
|
+
};
|