@nccirtu/tablefy 0.2.0 → 0.5.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/README.md +20 -4
- package/cli/templates/tablefy/avatar-list.tsx +51 -0
- package/cli/templates/tablefy/data-table-empty.tsx +77 -0
- package/cli/templates/tablefy/data-table-header.tsx +208 -0
- package/cli/templates/tablefy/data-table-pagination.tsx +137 -0
- package/cli/templates/tablefy/data-table-schema.tsx +13 -0
- package/cli/templates/tablefy/data-table.tsx +222 -0
- package/dist/cli/index.js +475 -0
- package/dist/columns/avatar-group-column.d.ts +1 -1
- package/dist/columns/columns/avatar-group-column.d.ts +1 -1
- package/dist/columns/index.d.ts +2 -2
- package/dist/columns/index.esm.js +153 -7921
- package/dist/columns/index.esm.js.map +1 -1
- package/dist/columns/index.js +214 -8000
- package/dist/columns/index.js.map +1 -1
- package/dist/columns/tablefy/avatar-list.d.ts +15 -0
- package/dist/columns/{components/ui/data-table → tablefy}/data-table-header.d.ts +1 -1
- package/dist/columns/{components/ui/data-table → tablefy}/data-table-pagination.d.ts +1 -1
- package/dist/columns/tablefy/data-table-schema.d.ts +1 -0
- package/dist/columns/tablefy/index.d.ts +6 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.esm.js +23 -7825
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +55 -7875
- package/dist/index.js.map +1 -1
- package/dist/tablefy/avatar-list.d.ts +15 -0
- package/dist/{components/ui/data-table → tablefy}/data-table-header.d.ts +1 -1
- package/dist/{components/ui/data-table → tablefy}/data-table-pagination.d.ts +1 -1
- package/dist/tablefy/data-table-schema.d.ts +1 -0
- package/dist/tablefy/index.d.ts +6 -0
- package/package.json +46 -13
- package/dist/columns/components/animata/list/avatar-list.d.ts +0 -12
- package/dist/columns/components/ui/badge.d.ts +0 -9
- package/dist/columns/components/ui/button.d.ts +0 -10
- package/dist/columns/components/ui/checkbox.d.ts +0 -4
- package/dist/columns/components/ui/data-table/data-table-schema.d.ts +0 -5
- package/dist/columns/components/ui/dropdown-menu.d.ts +0 -25
- package/dist/columns/components/ui/input.d.ts +0 -3
- package/dist/columns/components/ui/progress.d.ts +0 -4
- package/dist/columns/components/ui/select.d.ts +0 -15
- package/dist/columns/components/ui/table.d.ts +0 -10
- package/dist/columns/components/ui/tooltip.d.ts +0 -7
- package/dist/components/animata/list/avatar-list.d.ts +0 -12
- package/dist/components/ui/badge.d.ts +0 -9
- package/dist/components/ui/button.d.ts +0 -10
- package/dist/components/ui/checkbox.d.ts +0 -4
- package/dist/components/ui/data-table/data-table-schema.d.ts +0 -5
- package/dist/components/ui/dropdown-menu.d.ts +0 -25
- package/dist/components/ui/input.d.ts +0 -3
- package/dist/components/ui/progress.d.ts +0 -4
- package/dist/components/ui/select.d.ts +0 -15
- package/dist/components/ui/table.d.ts +0 -10
- package/dist/components/ui/tooltip.d.ts +0 -7
- package/dist/lib/table/schema/empty-state.d.ts +0 -23
- package/dist/lib/table/types.d.ts +0 -76
- package/dist/schema/data-table-schema.d.ts +0 -75
- package/dist/schema/empty-state.d.ts +0 -45
- package/dist/schema/table-schema.d.ts +0 -44
- package/dist/schema.d.ts +0 -11
- package/dist/src/columns/actions-column.d.ts +0 -26
- package/dist/src/columns/avatar-group-column.d.ts +0 -38
- package/dist/src/columns/badge-column.d.ts +0 -22
- package/dist/src/columns/base-column.d.ts +0 -19
- package/dist/src/columns/button-column.d.ts +0 -14
- package/dist/src/columns/checkbox-column.d.ts +0 -5
- package/dist/src/columns/date-column.d.ts +0 -24
- package/dist/src/columns/dropdown-column.d.ts +0 -17
- package/dist/src/columns/icon-column.d.ts +0 -58
- package/dist/src/columns/image-column.d.ts +0 -21
- package/dist/src/columns/index.d.ts +0 -13
- package/dist/src/columns/input-column.d.ts +0 -14
- package/dist/src/columns/link-column.d.ts +0 -27
- package/dist/src/columns/number-column.d.ts +0 -23
- package/dist/src/columns/progress-column.d.ts +0 -30
- package/dist/src/columns/select-column.d.ts +0 -19
- package/dist/src/columns/text-column.d.ts +0 -14
- package/dist/src/columns/types.d.ts +0 -37
- package/dist/src/components/animata/list/avatar-list.d.ts +0 -12
- package/dist/src/components/ui/badge.d.ts +0 -9
- package/dist/src/components/ui/button.d.ts +0 -10
- package/dist/src/components/ui/checkbox.d.ts +0 -4
- package/dist/src/components/ui/data-table/data-table-empty.d.ts +0 -8
- package/dist/src/components/ui/data-table/data-table-header.d.ts +0 -15
- package/dist/src/components/ui/data-table/data-table-pagination.d.ts +0 -9
- package/dist/src/components/ui/data-table/data-table-schema.d.ts +0 -5
- package/dist/src/components/ui/data-table/data-table.d.ts +0 -13
- package/dist/src/components/ui/dropdown-menu.d.ts +0 -25
- package/dist/src/components/ui/input.d.ts +0 -3
- package/dist/src/components/ui/progress.d.ts +0 -4
- package/dist/src/components/ui/select.d.ts +0 -15
- package/dist/src/components/ui/table.d.ts +0 -10
- package/dist/src/components/ui/tooltip.d.ts +0 -7
- package/dist/src/index.d.ts +0 -15
- package/dist/src/lib/table/schema/empty-state.d.ts +0 -23
- package/dist/src/lib/table/types.d.ts +0 -76
- package/dist/src/lib/utils.d.ts +0 -1
- package/dist/src/schema/data-table-schema.d.ts +0 -75
- package/dist/src/schema/empty-state.d.ts +0 -45
- package/dist/src/schema/table-schema.d.ts +0 -44
- package/dist/src/schema.d.ts +0 -11
- package/dist/src/types.d.ts +0 -76
- package/dist/types.d.ts +0 -76
- /package/dist/columns/{components/ui/data-table → tablefy}/data-table-empty.d.ts +0 -0
- /package/dist/columns/{components/ui/data-table → tablefy}/data-table.d.ts +0 -0
- /package/dist/{components/ui/data-table → tablefy}/data-table-empty.d.ts +0 -0
- /package/dist/{components/ui/data-table → tablefy}/data-table.d.ts +0 -0
package/README.md
CHANGED
|
@@ -17,15 +17,31 @@ A powerful, type-safe React table package built with TanStack Table and shadcn/u
|
|
|
17
17
|
## Installation
|
|
18
18
|
|
|
19
19
|
```bash
|
|
20
|
-
npm install tablefy
|
|
20
|
+
npm install @nccirtu/tablefy
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
**⚠️ Important:** Tablefy requires additional setup steps to work correctly.
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
### What you need to install
|
|
26
|
+
|
|
27
|
+
1. **Peer dependencies** (required libraries)
|
|
28
|
+
2. **shadcn/ui components** (UI building blocks)
|
|
29
|
+
3. **Tailwind configuration** (for styling)
|
|
30
|
+
|
|
31
|
+
👉 **[Follow the complete installation guide →](./INSTALLATION.md)**
|
|
32
|
+
|
|
33
|
+
### Quick Overview
|
|
26
34
|
|
|
27
35
|
```bash
|
|
36
|
+
# 1. Install dependencies
|
|
28
37
|
npm install @tanstack/react-table lucide-react class-variance-authority clsx tailwind-merge
|
|
38
|
+
|
|
39
|
+
# 2. Install shadcn components
|
|
40
|
+
npx shadcn@latest add button table checkbox dropdown-menu input select badge progress tooltip
|
|
41
|
+
|
|
42
|
+
# 3. Configure Tailwind (see [INSTALLATION.md](./INSTALLATION.md) for instructions for React, Next.js, Vue and Svelte)
|
|
43
|
+
|
|
44
|
+
# 4. Restart dev server
|
|
29
45
|
```
|
|
30
46
|
|
|
31
47
|
## Quick Start
|
|
@@ -173,7 +189,7 @@ export const projectColumns = TableSchema.make<Project>()
|
|
|
173
189
|
|
|
174
190
|
## Documentation
|
|
175
191
|
|
|
176
|
-
- [Installation Guide](./
|
|
192
|
+
- [Installation Guide](./INSTALLATION.md) - Detailed installation instructions
|
|
177
193
|
- [Usage Guide](./docs/USAGE.md) - Complete API reference and examples
|
|
178
194
|
- [Column Types](./docs/USAGE.md#column-types) - All column types and their methods
|
|
179
195
|
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
export const AvatarList = ({
|
|
4
|
+
items,
|
|
5
|
+
maxVisible = 5,
|
|
6
|
+
size = 32,
|
|
7
|
+
...props
|
|
8
|
+
}: {
|
|
9
|
+
items: Array<{ id: string; src?: string; alt?: string; initials?: string }>;
|
|
10
|
+
maxVisible?: number;
|
|
11
|
+
size?: number | string;
|
|
12
|
+
[key: string]: any;
|
|
13
|
+
}) => {
|
|
14
|
+
const sizeValue = typeof size === "string" ? parseInt(size, 10) || 32 : size;
|
|
15
|
+
return (
|
|
16
|
+
<div
|
|
17
|
+
className="avatar-list"
|
|
18
|
+
style={{ "--avatar-size": `${sizeValue}px` } as React.CSSProperties}
|
|
19
|
+
{...props}
|
|
20
|
+
>
|
|
21
|
+
{items.slice(0, maxVisible).map((item) => (
|
|
22
|
+
<div key={item.id} className="avatar-item">
|
|
23
|
+
{item.src ? (
|
|
24
|
+
<img
|
|
25
|
+
src={item.src}
|
|
26
|
+
alt={item.alt || "Avatar"}
|
|
27
|
+
style={{ width: `${sizeValue}px`, height: `${sizeValue}px` }}
|
|
28
|
+
/>
|
|
29
|
+
) : (
|
|
30
|
+
<div
|
|
31
|
+
className="avatar-initials"
|
|
32
|
+
style={{ width: `${sizeValue}px`, height: `${sizeValue}px` }}
|
|
33
|
+
>
|
|
34
|
+
{item.initials || "?"}
|
|
35
|
+
</div>
|
|
36
|
+
)}
|
|
37
|
+
</div>
|
|
38
|
+
))}
|
|
39
|
+
{items.length > maxVisible && (
|
|
40
|
+
<div
|
|
41
|
+
className="avatar-count"
|
|
42
|
+
style={{ width: `${sizeValue}px`, height: `${sizeValue}px` }}
|
|
43
|
+
>
|
|
44
|
+
+{items.length - maxVisible}
|
|
45
|
+
</div>
|
|
46
|
+
)}
|
|
47
|
+
</div>
|
|
48
|
+
);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export default AvatarList;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// shadcn components - installed by user
|
|
4
|
+
import { Button } from "@/components/ui/button";
|
|
5
|
+
import { cn } from "@/lib/utils";
|
|
6
|
+
import { EmptyStateConfig } from "@/lib/types";
|
|
7
|
+
|
|
8
|
+
interface DataTableEmptyProps {
|
|
9
|
+
config: EmptyStateConfig;
|
|
10
|
+
colSpan: number;
|
|
11
|
+
className?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function DataTableEmpty({
|
|
15
|
+
config,
|
|
16
|
+
colSpan,
|
|
17
|
+
className,
|
|
18
|
+
}: DataTableEmptyProps) {
|
|
19
|
+
const { icon, imageUrl, title, description, action, variant } = config;
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<tr>
|
|
23
|
+
<td colSpan={colSpan} className={cn("h-[400px]", className)}>
|
|
24
|
+
<div className="flex h-full flex-col items-center justify-center gap-4 text-center p-8">
|
|
25
|
+
{imageUrl ? (
|
|
26
|
+
<img className="w-24 h-24" src={imageUrl} alt="Empty State" />
|
|
27
|
+
) : icon ? (
|
|
28
|
+
<div
|
|
29
|
+
className={cn(
|
|
30
|
+
"rounded-full p-4",
|
|
31
|
+
variant === "error" ? "bg-destructive/10" : "bg-muted",
|
|
32
|
+
)}
|
|
33
|
+
>
|
|
34
|
+
{icon}
|
|
35
|
+
</div>
|
|
36
|
+
) : null}
|
|
37
|
+
|
|
38
|
+
<div className="w-full flex flex-col items-center justify-center gap-2">
|
|
39
|
+
<h3 className="text-sm font-semibold text-foreground whitespace-normal break-words">
|
|
40
|
+
{title}
|
|
41
|
+
</h3>
|
|
42
|
+
{description && (
|
|
43
|
+
<p className="text-xs text-muted-foreground font-normal whitespace-normal break-words leading-relaxed">
|
|
44
|
+
{description}
|
|
45
|
+
</p>
|
|
46
|
+
)}
|
|
47
|
+
</div>
|
|
48
|
+
|
|
49
|
+
{action && (
|
|
50
|
+
<Button
|
|
51
|
+
variant={variant === "error" ? "outline" : "default"}
|
|
52
|
+
onClick={action.onClick}
|
|
53
|
+
asChild={!!action.href}
|
|
54
|
+
className="mt-2"
|
|
55
|
+
>
|
|
56
|
+
{action.href ? (
|
|
57
|
+
<a href={action.href}>
|
|
58
|
+
{action.icon}
|
|
59
|
+
<span className={cn(action.icon ? "ml-2" : "")}>
|
|
60
|
+
{action.label}
|
|
61
|
+
</span>
|
|
62
|
+
</a>
|
|
63
|
+
) : (
|
|
64
|
+
<>
|
|
65
|
+
{action.icon}
|
|
66
|
+
<span className={cn(action.icon ? "ml-2" : "")}>
|
|
67
|
+
{action.label}
|
|
68
|
+
</span>
|
|
69
|
+
</>
|
|
70
|
+
)}
|
|
71
|
+
</Button>
|
|
72
|
+
)}
|
|
73
|
+
</div>
|
|
74
|
+
</td>
|
|
75
|
+
</tr>
|
|
76
|
+
);
|
|
77
|
+
}
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Table as TanstackTable } from "@tanstack/react-table";
|
|
4
|
+
// shadcn components - installed by user
|
|
5
|
+
import { Button } from "@/components/ui/button";
|
|
6
|
+
import { Input } from "@/components/ui/input";
|
|
7
|
+
import {
|
|
8
|
+
DropdownMenu,
|
|
9
|
+
DropdownMenuContent,
|
|
10
|
+
DropdownMenuItem,
|
|
11
|
+
DropdownMenuTrigger,
|
|
12
|
+
} from "@/components/ui/dropdown-menu";
|
|
13
|
+
import { ChevronDown, Search, X } from "lucide-react";
|
|
14
|
+
import { cn } from "@/lib/utils";
|
|
15
|
+
import { HeaderAction, SearchConfig } from "@/lib/types";
|
|
16
|
+
|
|
17
|
+
interface DataTableHeaderProps<TData> {
|
|
18
|
+
title?: string;
|
|
19
|
+
description?: string;
|
|
20
|
+
actions?: HeaderAction<TData>[];
|
|
21
|
+
search?: SearchConfig;
|
|
22
|
+
searchValue?: string;
|
|
23
|
+
onSearchChange?: (value: string) => void;
|
|
24
|
+
table?: TanstackTable<TData>;
|
|
25
|
+
selectedCount?: number;
|
|
26
|
+
className?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function DataTableHeader<TData>({
|
|
30
|
+
title,
|
|
31
|
+
description,
|
|
32
|
+
actions = [],
|
|
33
|
+
search,
|
|
34
|
+
searchValue = "",
|
|
35
|
+
onSearchChange,
|
|
36
|
+
table,
|
|
37
|
+
selectedCount = 0,
|
|
38
|
+
className,
|
|
39
|
+
}: DataTableHeaderProps<TData>) {
|
|
40
|
+
const normalActions = actions.filter((a) => !a.bulk && !a.hidden);
|
|
41
|
+
const bulkActions = actions.filter((a) => a.bulk && !a.hidden);
|
|
42
|
+
const showBulkActions = selectedCount > 0 && bulkActions.length > 0;
|
|
43
|
+
|
|
44
|
+
const getSelectedRows = (): TData[] => {
|
|
45
|
+
if (!table) return [];
|
|
46
|
+
return table.getFilteredSelectedRowModel().rows.map((row) => row.original);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const renderAction = (action: HeaderAction<TData>, index: number) => {
|
|
50
|
+
if (action.children && action.children.length > 0) {
|
|
51
|
+
return (
|
|
52
|
+
<DropdownMenu key={action.id || index}>
|
|
53
|
+
<DropdownMenuTrigger asChild>
|
|
54
|
+
<Button
|
|
55
|
+
variant={action.variant || "outline"}
|
|
56
|
+
size={action.size || "default"}
|
|
57
|
+
disabled={action.disabled || action.loading}
|
|
58
|
+
>
|
|
59
|
+
{action.icon}
|
|
60
|
+
<span className={cn(action.icon ? "ml-2" : "")}>
|
|
61
|
+
{action.label}
|
|
62
|
+
</span>
|
|
63
|
+
<ChevronDown className="ml-2 h-4 w-4" />
|
|
64
|
+
</Button>
|
|
65
|
+
</DropdownMenuTrigger>
|
|
66
|
+
<DropdownMenuContent align="end">
|
|
67
|
+
{action.children.map((child, childIndex) => (
|
|
68
|
+
<DropdownMenuItem
|
|
69
|
+
key={child.id || childIndex}
|
|
70
|
+
onClick={child.onClick}
|
|
71
|
+
className={cn(
|
|
72
|
+
child.variant === "destructive" &&
|
|
73
|
+
"text-destructive focus:text-destructive",
|
|
74
|
+
)}
|
|
75
|
+
>
|
|
76
|
+
{child.icon && <span className="mr-2">{child.icon}</span>}
|
|
77
|
+
{child.label}
|
|
78
|
+
</DropdownMenuItem>
|
|
79
|
+
))}
|
|
80
|
+
</DropdownMenuContent>
|
|
81
|
+
</DropdownMenu>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (action.href) {
|
|
86
|
+
return (
|
|
87
|
+
<Button
|
|
88
|
+
key={action.id || index}
|
|
89
|
+
variant={action.variant || "outline"}
|
|
90
|
+
size={action.size || "default"}
|
|
91
|
+
disabled={action.disabled || action.loading}
|
|
92
|
+
asChild
|
|
93
|
+
>
|
|
94
|
+
<a href={action.href}>
|
|
95
|
+
{action.icon}
|
|
96
|
+
{action.size !== "icon" && (
|
|
97
|
+
<span className={cn(action.icon ? "ml-2" : "")}>
|
|
98
|
+
{action.label}
|
|
99
|
+
</span>
|
|
100
|
+
)}
|
|
101
|
+
</a>
|
|
102
|
+
</Button>
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return (
|
|
107
|
+
<Button
|
|
108
|
+
key={action.id || index}
|
|
109
|
+
variant={action.variant || "outline"}
|
|
110
|
+
size={action.size || "default"}
|
|
111
|
+
disabled={action.disabled || action.loading}
|
|
112
|
+
onClick={() => {
|
|
113
|
+
if (action.bulk && action.bulkOnClick) {
|
|
114
|
+
action.bulkOnClick(getSelectedRows());
|
|
115
|
+
} else if (action.onClick) {
|
|
116
|
+
action.onClick();
|
|
117
|
+
}
|
|
118
|
+
}}
|
|
119
|
+
>
|
|
120
|
+
{action.loading ? (
|
|
121
|
+
<svg className="animate-spin h-4 w-4" viewBox="0 0 24 24">
|
|
122
|
+
<circle
|
|
123
|
+
className="opacity-25"
|
|
124
|
+
cx="12"
|
|
125
|
+
cy="12"
|
|
126
|
+
r="10"
|
|
127
|
+
stroke="currentColor"
|
|
128
|
+
strokeWidth="4"
|
|
129
|
+
fill="none"
|
|
130
|
+
/>
|
|
131
|
+
<path
|
|
132
|
+
className="opacity-75"
|
|
133
|
+
fill="currentColor"
|
|
134
|
+
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"
|
|
135
|
+
/>
|
|
136
|
+
</svg>
|
|
137
|
+
) : (
|
|
138
|
+
action.icon
|
|
139
|
+
)}
|
|
140
|
+
{action.size !== "icon" && (
|
|
141
|
+
<span className={cn(action.icon || action.loading ? "ml-2" : "")}>
|
|
142
|
+
{action.label}
|
|
143
|
+
</span>
|
|
144
|
+
)}
|
|
145
|
+
</Button>
|
|
146
|
+
);
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
if (
|
|
150
|
+
!title &&
|
|
151
|
+
!description &&
|
|
152
|
+
normalActions.length === 0 &&
|
|
153
|
+
!search?.enabled
|
|
154
|
+
) {
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return (
|
|
159
|
+
<div className={cn("flex flex-col gap-4", className)}>
|
|
160
|
+
{(title || description) && (
|
|
161
|
+
<div className="space-y-1">
|
|
162
|
+
{title && (
|
|
163
|
+
<h2 className="text-xl font-semibold tracking-tight">{title}</h2>
|
|
164
|
+
)}
|
|
165
|
+
{description && (
|
|
166
|
+
<p className="text-sm text-muted-foreground">{description}</p>
|
|
167
|
+
)}
|
|
168
|
+
</div>
|
|
169
|
+
)}
|
|
170
|
+
|
|
171
|
+
{(search?.enabled || normalActions.length > 0 || showBulkActions) && (
|
|
172
|
+
<div className="flex items-center justify-between gap-4 mb-4">
|
|
173
|
+
{search?.enabled && (
|
|
174
|
+
<div className="relative max-w-sm flex-1">
|
|
175
|
+
<Search className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
|
|
176
|
+
<Input
|
|
177
|
+
placeholder={search.placeholder || "Suchen..."}
|
|
178
|
+
value={searchValue}
|
|
179
|
+
onChange={(e: React.ChangeEvent<HTMLInputElement>) => onSearchChange?.(e.target.value)}
|
|
180
|
+
className="pl-9 pr-9"
|
|
181
|
+
/>
|
|
182
|
+
{searchValue && (
|
|
183
|
+
<button
|
|
184
|
+
onClick={() => onSearchChange?.("")}
|
|
185
|
+
className="absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground"
|
|
186
|
+
>
|
|
187
|
+
<X className="h-4 w-4" />
|
|
188
|
+
</button>
|
|
189
|
+
)}
|
|
190
|
+
</div>
|
|
191
|
+
)}
|
|
192
|
+
|
|
193
|
+
<div className="flex items-center gap-2">
|
|
194
|
+
{showBulkActions && (
|
|
195
|
+
<span className="text-sm text-muted-foreground">
|
|
196
|
+
{selectedCount} ausgewählt
|
|
197
|
+
</span>
|
|
198
|
+
)}
|
|
199
|
+
{showBulkActions &&
|
|
200
|
+
bulkActions.map((action, index) => renderAction(action, index))}
|
|
201
|
+
{normalActions.length > 0 &&
|
|
202
|
+
normalActions.map((action, index) => renderAction(action, index))}
|
|
203
|
+
</div>
|
|
204
|
+
</div>
|
|
205
|
+
)}
|
|
206
|
+
</div>
|
|
207
|
+
);
|
|
208
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Table as TanstackTable } from "@tanstack/react-table";
|
|
4
|
+
// shadcn components - installed by user
|
|
5
|
+
import { Button } from "@/components/ui/button";
|
|
6
|
+
import {
|
|
7
|
+
Select,
|
|
8
|
+
SelectContent,
|
|
9
|
+
SelectItem,
|
|
10
|
+
SelectTrigger,
|
|
11
|
+
SelectValue,
|
|
12
|
+
} from "@/components/ui/select";
|
|
13
|
+
import {
|
|
14
|
+
ChevronLeft,
|
|
15
|
+
ChevronRight,
|
|
16
|
+
ChevronsLeft,
|
|
17
|
+
ChevronsRight,
|
|
18
|
+
} from "lucide-react";
|
|
19
|
+
import { cn } from "@/lib/utils";
|
|
20
|
+
import { PaginationConfig } from "@/lib/types";
|
|
21
|
+
|
|
22
|
+
interface DataTablePaginationProps<TData> {
|
|
23
|
+
table: TanstackTable<TData>;
|
|
24
|
+
config?: PaginationConfig;
|
|
25
|
+
className?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function DataTablePagination<TData>({
|
|
29
|
+
table,
|
|
30
|
+
config,
|
|
31
|
+
className,
|
|
32
|
+
}: DataTablePaginationProps<TData>) {
|
|
33
|
+
if (!config?.enabled) return null;
|
|
34
|
+
|
|
35
|
+
const {
|
|
36
|
+
showPageInfo = true,
|
|
37
|
+
showPageSizeSelector = true,
|
|
38
|
+
pageSizeOptions = [10, 20, 30, 50, 100],
|
|
39
|
+
} = config;
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<div
|
|
43
|
+
className={cn(
|
|
44
|
+
"flex flex-col gap-4 px-4 py-4 sm:flex-row sm:items-center sm:justify-between",
|
|
45
|
+
className,
|
|
46
|
+
)}
|
|
47
|
+
>
|
|
48
|
+
<div className="text-sm text-muted-foreground">
|
|
49
|
+
{table.getFilteredSelectedRowModel().rows.length > 0 ? (
|
|
50
|
+
<span>
|
|
51
|
+
{table.getFilteredSelectedRowModel().rows.length} von{" "}
|
|
52
|
+
{table.getFilteredRowModel().rows.length} Zeile(n) ausgewählt
|
|
53
|
+
</span>
|
|
54
|
+
) : showPageInfo ? (
|
|
55
|
+
<span>{table.getFilteredRowModel().rows.length} Einträge</span>
|
|
56
|
+
) : null}
|
|
57
|
+
</div>
|
|
58
|
+
|
|
59
|
+
<div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:gap-6">
|
|
60
|
+
{showPageSizeSelector && (
|
|
61
|
+
<div className="flex items-center gap-2">
|
|
62
|
+
<span className="text-sm text-muted-foreground whitespace-nowrap">
|
|
63
|
+
Zeilen pro Seite
|
|
64
|
+
</span>
|
|
65
|
+
<Select
|
|
66
|
+
value={`${table.getState().pagination.pageSize}`}
|
|
67
|
+
onValueChange={(value: string) => table.setPageSize(Number(value))}
|
|
68
|
+
>
|
|
69
|
+
<SelectTrigger className="h-8 w-[70px]">
|
|
70
|
+
<SelectValue
|
|
71
|
+
placeholder={table.getState().pagination.pageSize}
|
|
72
|
+
/>
|
|
73
|
+
</SelectTrigger>
|
|
74
|
+
<SelectContent side="top">
|
|
75
|
+
{pageSizeOptions.map((pageSize) => (
|
|
76
|
+
<SelectItem key={pageSize} value={`${pageSize}`}>
|
|
77
|
+
{pageSize}
|
|
78
|
+
</SelectItem>
|
|
79
|
+
))}
|
|
80
|
+
</SelectContent>
|
|
81
|
+
</Select>
|
|
82
|
+
</div>
|
|
83
|
+
)}
|
|
84
|
+
|
|
85
|
+
{showPageInfo && (
|
|
86
|
+
<div className="text-sm text-muted-foreground whitespace-nowrap">
|
|
87
|
+
Seite {table.getState().pagination.pageIndex + 1} von{" "}
|
|
88
|
+
{table.getPageCount()}
|
|
89
|
+
</div>
|
|
90
|
+
)}
|
|
91
|
+
|
|
92
|
+
<div className="flex items-center gap-1">
|
|
93
|
+
<Button
|
|
94
|
+
variant="outline"
|
|
95
|
+
size="icon"
|
|
96
|
+
className="h-8 w-8"
|
|
97
|
+
onClick={() => table.setPageIndex(0)}
|
|
98
|
+
disabled={!table.getCanPreviousPage()}
|
|
99
|
+
>
|
|
100
|
+
<ChevronsLeft className="h-4 w-4" />
|
|
101
|
+
<span className="sr-only">Erste Seite</span>
|
|
102
|
+
</Button>
|
|
103
|
+
<Button
|
|
104
|
+
variant="outline"
|
|
105
|
+
size="icon"
|
|
106
|
+
className="h-8 w-8"
|
|
107
|
+
onClick={() => table.previousPage()}
|
|
108
|
+
disabled={!table.getCanPreviousPage()}
|
|
109
|
+
>
|
|
110
|
+
<ChevronLeft className="h-4 w-4" />
|
|
111
|
+
<span className="sr-only">Vorherige Seite</span>
|
|
112
|
+
</Button>
|
|
113
|
+
<Button
|
|
114
|
+
variant="outline"
|
|
115
|
+
size="icon"
|
|
116
|
+
className="h-8 w-8"
|
|
117
|
+
onClick={() => table.nextPage()}
|
|
118
|
+
disabled={!table.getCanNextPage()}
|
|
119
|
+
>
|
|
120
|
+
<ChevronRight className="h-4 w-4" />
|
|
121
|
+
<span className="sr-only">Nächste Seite</span>
|
|
122
|
+
</Button>
|
|
123
|
+
<Button
|
|
124
|
+
variant="outline"
|
|
125
|
+
size="icon"
|
|
126
|
+
className="h-8 w-8"
|
|
127
|
+
onClick={() => table.setPageIndex(table.getPageCount() - 1)}
|
|
128
|
+
disabled={!table.getCanNextPage()}
|
|
129
|
+
>
|
|
130
|
+
<ChevronsRight className="h-4 w-4" />
|
|
131
|
+
<span className="sr-only">Letzte Seite</span>
|
|
132
|
+
</Button>
|
|
133
|
+
</div>
|
|
134
|
+
</div>
|
|
135
|
+
</div>
|
|
136
|
+
);
|
|
137
|
+
}
|