luxtable 0.2.3 → 0.3.1
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
CHANGED
|
@@ -23,7 +23,39 @@ LuxTable is a high-performance, feature-rich data grid library designed specific
|
|
|
23
23
|
|
|
24
24
|
## 🚀 Quick Start
|
|
25
25
|
|
|
26
|
-
### Installation
|
|
26
|
+
### Installation (Recommended: shadcn CLI)
|
|
27
|
+
|
|
28
|
+
LuxTable uses the **shadcn registry** approach - components are copied directly into your project, giving you full control over the code.
|
|
29
|
+
|
|
30
|
+
#### Prerequisites
|
|
31
|
+
|
|
32
|
+
Make sure you have shadcn/ui set up in your project:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
pnpm dlx shadcn@latest init
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
#### Install LuxTable
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
# Install the main LuxTable component
|
|
42
|
+
pnpm dlx shadcn@latest add "https://luxtable.dev/r/lux-table.json"
|
|
43
|
+
|
|
44
|
+
# Optional: Install pre-built cell renderers
|
|
45
|
+
pnpm dlx shadcn@latest add "https://luxtable.dev/r/lux-table-cell-renderers.json"
|
|
46
|
+
|
|
47
|
+
# Optional: Install column helper utilities
|
|
48
|
+
pnpm dlx shadcn@latest add "https://luxtable.dev/r/lux-table-column-helper.json"
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
This will:
|
|
52
|
+
- Copy LuxTable components to your `components/lux-table` folder
|
|
53
|
+
- Install required dependencies (`@tanstack/react-table`, `lucide-react`)
|
|
54
|
+
- Install required shadcn/ui components (button, checkbox, dropdown-menu, input, select)
|
|
55
|
+
|
|
56
|
+
### Alternative: npm Package
|
|
57
|
+
|
|
58
|
+
If you prefer using LuxTable as an npm package:
|
|
27
59
|
|
|
28
60
|
```bash
|
|
29
61
|
npm install luxtable
|
|
@@ -33,10 +65,21 @@ pnpm add luxtable
|
|
|
33
65
|
yarn add luxtable
|
|
34
66
|
```
|
|
35
67
|
|
|
68
|
+
> **Note:** When using the npm package, you need to configure your `tailwind.config.js` to include LuxTable:
|
|
69
|
+
> ```js
|
|
70
|
+
> module.exports = {
|
|
71
|
+
> content: [
|
|
72
|
+
> "./src/**/*.{js,ts,jsx,tsx}",
|
|
73
|
+
> "./node_modules/luxtable/dist/**/*.{js,mjs}",
|
|
74
|
+
> ],
|
|
75
|
+
> };
|
|
76
|
+
> ```
|
|
77
|
+
|
|
36
78
|
### Basic Usage
|
|
37
79
|
|
|
38
80
|
```tsx
|
|
39
|
-
import { LuxTable
|
|
81
|
+
import { LuxTable } from "@/components/lux-table/lux-table";
|
|
82
|
+
import { ColumnDef } from "@tanstack/react-table";
|
|
40
83
|
|
|
41
84
|
type User = {
|
|
42
85
|
id: number;
|
|
@@ -45,21 +88,23 @@ type User = {
|
|
|
45
88
|
status: "active" | "inactive";
|
|
46
89
|
};
|
|
47
90
|
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
columnHelper.accessor("id", {
|
|
91
|
+
const columns: ColumnDef<User>[] = [
|
|
92
|
+
{
|
|
93
|
+
accessorKey: "id",
|
|
52
94
|
header: "ID",
|
|
53
|
-
}
|
|
54
|
-
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
accessorKey: "name",
|
|
55
98
|
header: "Name",
|
|
56
|
-
}
|
|
57
|
-
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
accessorKey: "email",
|
|
58
102
|
header: "Email",
|
|
59
|
-
}
|
|
60
|
-
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
accessorKey: "status",
|
|
61
106
|
header: "Status",
|
|
62
|
-
}
|
|
107
|
+
},
|
|
63
108
|
];
|
|
64
109
|
|
|
65
110
|
const data: User[] = [
|
|
@@ -73,10 +118,10 @@ export default function App() {
|
|
|
73
118
|
data={data}
|
|
74
119
|
columns={columns}
|
|
75
120
|
options={{
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
121
|
+
pagination: true,
|
|
122
|
+
filtering: true,
|
|
123
|
+
selection: "multiple",
|
|
124
|
+
showToolbar: true,
|
|
80
125
|
}}
|
|
81
126
|
/>
|
|
82
127
|
);
|
|
@@ -88,49 +133,48 @@ export default function App() {
|
|
|
88
133
|
LuxTable comes with built-in cell renderers for common use cases:
|
|
89
134
|
|
|
90
135
|
```tsx
|
|
91
|
-
import {
|
|
92
|
-
StatusCell,
|
|
93
|
-
ProgressCell,
|
|
94
|
-
DateCell,
|
|
95
|
-
CopyableCell
|
|
96
|
-
} from "luxtable";
|
|
136
|
+
import { StatusCell, ProgressCell, DateCell, CopyableCell } from "@/components/lux-table/cell-renderers";
|
|
97
137
|
|
|
98
138
|
// Status badges
|
|
99
|
-
|
|
139
|
+
{
|
|
140
|
+
accessorKey: "status",
|
|
100
141
|
header: "Status",
|
|
101
|
-
cell: (
|
|
102
|
-
}
|
|
142
|
+
cell: ({ row }) => <StatusCell value={row.getValue("status")} />,
|
|
143
|
+
}
|
|
103
144
|
|
|
104
145
|
// Progress bars
|
|
105
|
-
|
|
146
|
+
{
|
|
147
|
+
accessorKey: "progress",
|
|
106
148
|
header: "Progress",
|
|
107
|
-
cell: (
|
|
108
|
-
}
|
|
149
|
+
cell: ({ row }) => <ProgressCell value={row.getValue("progress")} />,
|
|
150
|
+
}
|
|
109
151
|
|
|
110
152
|
// Formatted dates
|
|
111
|
-
|
|
153
|
+
{
|
|
154
|
+
accessorKey: "createdAt",
|
|
112
155
|
header: "Created",
|
|
113
|
-
cell: (
|
|
114
|
-
}
|
|
156
|
+
cell: ({ row }) => <DateCell value={row.getValue("createdAt")} />,
|
|
157
|
+
}
|
|
115
158
|
|
|
116
159
|
// Copy to clipboard
|
|
117
|
-
|
|
160
|
+
{
|
|
161
|
+
accessorKey: "id",
|
|
118
162
|
header: "ID",
|
|
119
|
-
cell: (
|
|
120
|
-
}
|
|
163
|
+
cell: ({ row }) => <CopyableCell value={row.getValue("id")} />,
|
|
164
|
+
}
|
|
121
165
|
```
|
|
122
166
|
|
|
123
167
|
## 🎯 Options
|
|
124
168
|
|
|
125
169
|
| Option | Type | Default | Description |
|
|
126
170
|
|--------|------|---------|-------------|
|
|
127
|
-
| `
|
|
128
|
-
| `enableColumnFilters` | `boolean` | `false` | Enable column-level filtering |
|
|
129
|
-
| `enableGlobalFilter` | `boolean` | `false` | Enable global search |
|
|
130
|
-
| `enableRowSelection` | `boolean` | `false` | Enable row selection with checkboxes |
|
|
131
|
-
| `enableSorting` | `boolean` | `true` | Enable column sorting |
|
|
132
|
-
| `showToolbar` | `boolean` | `false` | Show toolbar with search and column visibility |
|
|
171
|
+
| `pagination` | `boolean` | `false` | Enable/disable pagination |
|
|
133
172
|
| `pageSize` | `number` | `10` | Default rows per page |
|
|
173
|
+
| `filtering` | `boolean` | `false` | Enable column-level filtering |
|
|
174
|
+
| `selection` | `"single" \| "multiple" \| "none"` | `"none"` | Row selection mode |
|
|
175
|
+
| `showToolbar` | `boolean` | `false` | Show toolbar with search and column visibility |
|
|
176
|
+
| `showGlobalSearch` | `boolean` | `true` | Show global search in toolbar |
|
|
177
|
+
| `showColumnVisibility` | `boolean` | `true` | Show column visibility controls |
|
|
134
178
|
|
|
135
179
|
## 🛠 Tech Stack
|
|
136
180
|
|
|
@@ -147,6 +191,16 @@ Visit our [documentation](https://luxtable.dev/docs) for detailed guides and exa
|
|
|
147
191
|
|
|
148
192
|
Most data grids are either too lightweight for complex tasks or too bloated with legacy styles. LuxTable is built from the ground up to be **headless-first but styled-ready**. It provides the engine you need for heavy data work, wrapped in the minimalist aesthetic that modern users expect.
|
|
149
193
|
|
|
194
|
+
### Why shadcn Registry?
|
|
195
|
+
|
|
196
|
+
By using the shadcn registry approach instead of a traditional npm package:
|
|
197
|
+
|
|
198
|
+
- ✅ **Full Control** - Components are in your codebase, customize freely
|
|
199
|
+
- ✅ **No CSS Conflicts** - Uses your project's Tailwind configuration
|
|
200
|
+
- ✅ **Smaller Bundle** - Only include what you need
|
|
201
|
+
- ✅ **Easy Updates** - Re-run the add command to update
|
|
202
|
+
- ✅ **Matches Your Stack** - Works seamlessly with your existing shadcn/ui components
|
|
203
|
+
|
|
150
204
|
## 📄 License
|
|
151
205
|
|
|
152
206
|
MIT © [LuxTable](https://github.com/luxtable/luxtable)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "luxtable",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Enterprise-Grade Data Management. Minimalist Aesthetics.",
|
|
6
6
|
"keywords": [
|
|
@@ -9,7 +9,10 @@
|
|
|
9
9
|
"datagrid",
|
|
10
10
|
"tanstack",
|
|
11
11
|
"headless",
|
|
12
|
-
"ui"
|
|
12
|
+
"ui",
|
|
13
|
+
"shadcn",
|
|
14
|
+
"shadcn-ui",
|
|
15
|
+
"registry"
|
|
13
16
|
],
|
|
14
17
|
"author": "LuxTable",
|
|
15
18
|
"license": "MIT",
|
|
@@ -25,10 +28,12 @@
|
|
|
25
28
|
"types": "./dist/index.d.ts",
|
|
26
29
|
"import": "./dist/index.mjs",
|
|
27
30
|
"require": "./dist/index.js"
|
|
28
|
-
}
|
|
31
|
+
},
|
|
32
|
+
"./registry/*": "./registry/*"
|
|
29
33
|
},
|
|
30
34
|
"files": [
|
|
31
35
|
"dist",
|
|
36
|
+
"registry",
|
|
32
37
|
"README.md"
|
|
33
38
|
],
|
|
34
39
|
"publishConfig": {
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
|
3
|
+
"name": "lux-table-cell-renderers",
|
|
4
|
+
"type": "registry:component",
|
|
5
|
+
"title": "LuxTable Cell Renderers",
|
|
6
|
+
"author": "LuxTable (https://luxtable.dev)",
|
|
7
|
+
"description": "Pre-built cell renderers for common data types: status badges, progress bars, dates, copyable text, currency, and booleans.",
|
|
8
|
+
"dependencies": [
|
|
9
|
+
"lucide-react"
|
|
10
|
+
],
|
|
11
|
+
"registryDependencies": [],
|
|
12
|
+
"files": [
|
|
13
|
+
{
|
|
14
|
+
"path": "registry/new-york/blocks/lux-table-cell-renderers/status-cell.tsx",
|
|
15
|
+
"content": "\"use client\";\n\n// ============================================================================\n// STATUS CELL - Status Display\n// ============================================================================\n// This component is used to visually display status values with colored badges\n// in tables.\n//\n// Features:\n// - 5 predefined status colors (Active, Inactive, Pending, Completed, Cancelled)\n// - Dark mode support\n// - Customizable colors\n// - Default gray badge for undefined statuses\n// ============================================================================\n\n/**\n * Default colors for Status Cell\n * \n * 4 values are defined for each status:\n * - bg: Light theme background color\n * - text: Light theme text color\n * - darkBg: Dark theme background color\n * - darkText: Dark theme text color\n */\nexport const defaultStatusColors: Record<string, { bg: string; text: string; darkBg: string; darkText: string }> = {\n Active: {\n bg: \"bg-green-100\",\n text: \"text-green-800\",\n darkBg: \"dark:bg-green-900\",\n darkText: \"dark:text-green-300\",\n },\n Inactive: {\n bg: \"bg-red-100\",\n text: \"text-red-800\",\n darkBg: \"dark:bg-red-900\",\n darkText: \"dark:text-red-300\",\n },\n Pending: {\n bg: \"bg-yellow-100\",\n text: \"text-yellow-800\",\n darkBg: \"dark:bg-yellow-900\",\n darkText: \"dark:text-yellow-300\",\n },\n Completed: {\n bg: \"bg-blue-100\",\n text: \"text-blue-800\",\n darkBg: \"dark:bg-blue-900\",\n darkText: \"dark:text-blue-300\",\n },\n Cancelled: {\n bg: \"bg-gray-100\",\n text: \"text-gray-800\",\n darkBg: \"dark:bg-gray-900\",\n darkText: \"dark:text-gray-300\",\n },\n};\n\nexport interface StatusCellProps {\n /** Status value to display (e.g., \"Active\", \"Pending\") */\n value: string;\n /** \n * Custom color definitions\n * Used to override default colors or add new colors\n * \n * @example\n * ```tsx\n * colors={{\n * Custom: { bg: \"bg-purple-100\", text: \"text-purple-800\" }\n * }}\n * ```\n */\n colors?: Record<string, { bg: string; text: string; darkBg?: string; darkText?: string }>;\n /** Additional CSS classes */\n className?: string;\n}\n\n/**\n * Ready-to-use component for status cells\n * \n * Supports the following statuses by default:\n * - **Active** → Green badge\n * - **Inactive** → Red badge \n * - **Pending** → Yellow badge\n * - **Completed** → Blue badge\n * - **Cancelled** → Gray badge\n * \n * Undefined statuses are automatically shown with a gray badge.\n * \n * @example\n * // Basit kullanım\n * ```tsx\n * <StatusCell value=\"Active\" />\n * ```\n * \n * @example\n * // Özel renklerle kullanım\n * ```tsx\n * <StatusCell \n * value=\"OnHold\" \n * colors={{\n * OnHold: { \n * bg: \"bg-orange-100\", \n * text: \"text-orange-800\",\n * darkBg: \"dark:bg-orange-900\",\n * darkText: \"dark:text-orange-300\"\n * }\n * }}\n * />\n * ```\n * \n * @example\n * // TanStack Table column içinde kullanım\n * ```tsx\n * columnHelper.accessor(\"status\", {\n * header: \"Durum\",\n * cell: (info) => <StatusCell value={info.getValue()} />,\n * })\n * ```\n */\nexport function StatusCell({ value, colors, className }: StatusCellProps) {\n const mergedColors = { ...defaultStatusColors, ...colors };\n const colorConfig = mergedColors[value];\n\n if (!colorConfig) {\n return (\n <span className={`px-2 py-1 rounded-full text-xs font-medium bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-300 ${className || \"\"}`}>\n {value}\n </span>\n );\n }\n\n const { bg, text, darkBg, darkText } = colorConfig;\n\n return (\n <span className={`px-2 py-1 rounded-full text-xs font-medium ${bg} ${text} ${darkBg || \"\"} ${darkText || \"\"} ${className || \"\"}`}>\n {value}\n </span>\n );\n}\n",
|
|
16
|
+
"type": "registry:component",
|
|
17
|
+
"target": "components/lux-table/cell-renderers/status-cell.tsx"
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"path": "registry/new-york/blocks/lux-table-cell-renderers/progress-cell.tsx",
|
|
21
|
+
"content": "\"use client\";\n\n// ============================================================================\n// PROGRESS CELL - Progress Bar\n// ============================================================================\n// This component is used to display percentage values with a visual progress bar\n// in tables.\n//\n// Features:\n// - Customizable bar and background colors\n// - Optional percentage label\n// - Smooth animations\n// - Value safety between 0-100\n// ============================================================================\n\nexport interface ProgressCellProps {\n /** Progress value (between 0-100) */\n value: number;\n /** \n * Progress bar color \n * @default \"bg-blue-600\"\n */\n barColor?: string;\n /** \n * Background color \n * @default \"bg-gray-200 dark:bg-gray-700\"\n */\n bgColor?: string;\n /** \n * Show percentage label \n * @default false\n */\n showLabel?: boolean;\n /** Additional CSS classes */\n className?: string;\n}\n\n/**\n * Ready-to-use component for progress bar cells\n * \n * Displays percentage values as a visual progress bar.\n * Value is automatically clamped between 0-100.\n * \n * @example\n * // Basit kullanım\n * ```tsx\n * <ProgressCell value={75} />\n * ```\n * \n * @example\n * // Etiket ile\n * ```tsx\n * <ProgressCell value={42} showLabel />\n * ```\n * \n * @example\n * // Özel renklerle\n * ```tsx\n * <ProgressCell \n * value={60} \n * barColor=\"bg-green-500\"\n * bgColor=\"bg-green-100\"\n * showLabel\n * />\n * ```\n * \n * @example\n * // TanStack Table column içinde kullanım\n * ```tsx\n * columnHelper.accessor(\"progress\", {\n * header: \"İlerleme\",\n * cell: (info) => <ProgressCell value={info.getValue()} showLabel />,\n * })\n * ```\n */\nexport function ProgressCell({\n value,\n barColor = \"bg-blue-600\",\n bgColor = \"bg-gray-200 dark:bg-gray-700\",\n showLabel = false,\n className,\n}: ProgressCellProps) {\n // Clamp value between 0-100\n const clampedValue = Math.min(100, Math.max(0, value));\n\n return (\n <div className={`flex items-center gap-2 ${className || \"\"}`}>\n <div className={`w-full rounded-full h-2.5 ${bgColor}`}>\n <div\n className={`${barColor} h-2.5 rounded-full transition-all`}\n style={{ width: `${clampedValue}%` }}\n />\n </div>\n {showLabel && (\n <span className=\"text-xs text-gray-500 dark:text-gray-400 w-8\">\n {value}%\n </span>\n )}\n </div>\n );\n}\n",
|
|
22
|
+
"type": "registry:component",
|
|
23
|
+
"target": "components/lux-table/cell-renderers/progress-cell.tsx"
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"path": "registry/new-york/blocks/lux-table-cell-renderers/date-cell.tsx",
|
|
27
|
+
"content": "\"use client\";\n\n// ============================================================================\n// DATE CELL - Date Display\n// ============================================================================\n// This component is used to display date values in tables in different formats.\n//\n// Features:\n// - 3 different formats: short, long, relative\n// - Locale support (language and region formatting)\n// - Accepts String or Date object\n// - Safe fallback for invalid dates\n// ============================================================================\n\nexport interface DateCellProps {\n /** Date value (ISO string or Date object) */\n value: string | Date;\n /** \n * Date format\n * - \"short\": Short format (e.g., 12/28/2024)\n * - \"long\": Long format (e.g., December 28, 2024)\n * - \"relative\": Relative format (e.g., \"Today\", \"Yesterday\", \"3 days ago\")\n * @default \"short\"\n */\n format?: \"short\" | \"long\" | \"relative\";\n /** \n * Locale (language and region)\n * @default \"en-US\"\n */\n locale?: string;\n}\n\n/**\n * Ready-to-use component for date values\n * \n * Supported formats:\n * - **short**: Standard short date format (e.g., 12/28/2024)\n * - **long**: Long format with month name (e.g., December 28, 2024)\n * - **relative**: Relative time (Today, Yesterday, X days ago)\n * \n * Invalid dates are automatically displayed as \"-\".\n * \n * @example\n * // Simple usage (short format)\n * ```tsx\n * <DateCell value=\"2024-12-28\" />\n * // Output: \"12/28/2024\"\n * ```\n * \n * @example\n * // Long format\n * ```tsx\n * <DateCell value={new Date()} format=\"long\" />\n * // Output: \"December 28, 2024\"\n * ```\n * \n * @example\n * // Relative format\n * ```tsx\n * <DateCell value={new Date()} format=\"relative\" />\n * // Output: \"Today\"\n * ```\n * \n * @example\n * // Different locale\n * ```tsx\n * <DateCell value=\"2024-12-28\" format=\"long\" locale=\"fr-FR\" />\n * // Output: \"28 décembre 2024\"\n * ```\n * \n * @example\n * // Usage within TanStack Table column\n * ```tsx\n * columnHelper.accessor(\"createdAt\", {\n * header: \"Created At\",\n * cell: (info) => <DateCell value={info.getValue()} format=\"long\" />,\n * })\n * ```\n */\nexport function DateCell({ value, format = \"short\", locale = \"en-US\" }: DateCellProps) {\n const date = typeof value === \"string\" ? new Date(value) : value;\n\n // Invalid date check\n if (isNaN(date.getTime())) {\n return <span className=\"text-gray-400\">-</span>;\n }\n\n let formatted: string;\n\n switch (format) {\n case \"long\":\n formatted = date.toLocaleDateString(locale, {\n year: \"numeric\",\n month: \"long\",\n day: \"numeric\",\n });\n break;\n case \"relative\": {\n const now = new Date();\n const diffMs = now.getTime() - date.getTime();\n const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));\n\n if (diffDays === 0) formatted = \"Today\";\n else if (diffDays === 1) formatted = \"Yesterday\";\n else if (diffDays < 7) formatted = `${diffDays} days ago`;\n else formatted = date.toLocaleDateString(locale);\n break;\n }\n default:\n formatted = date.toLocaleDateString(locale);\n }\n\n return <span>{formatted}</span>;\n}\n",
|
|
28
|
+
"type": "registry:component",
|
|
29
|
+
"target": "components/lux-table/cell-renderers/date-cell.tsx"
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"path": "registry/new-york/blocks/lux-table-cell-renderers/copyable-cell.tsx",
|
|
33
|
+
"content": "\"use client\";\n\nimport * as React from \"react\";\nimport { Copy, Check } from \"lucide-react\";\n\n// ============================================================================\n// COPYABLE CELL - Copyable Cell\n// ============================================================================\n// This component is used for content that can be copied by clicking on it in tables.\n// Ideal for values like Email, ID, codes that users frequently copy.\n//\n// Features:\n// - Copy to clipboard with a single click\n// - Visual feedback (✓ icon)\n// - Hover effects\n// - Optional callback\n// ============================================================================\n\nexport interface CopyableCellProps {\n /** Value to display and copy */\n value: string | number;\n /** \n * Duration of feedback shown after copying (ms) \n * @default 2000\n */\n feedbackDuration?: number;\n /** Callback called after copying */\n onCopy?: (value: string) => void;\n /** Additional CSS classes */\n className?: string;\n /** \n * Tooltip text \n * @default \"Click to copy\"\n */\n tooltip?: string;\n /** \n * Always show icon? \n * If false, only visible on hover\n * @default false\n */\n alwaysShowIcon?: boolean;\n}\n\n/**\n * Cell component that copies value to clipboard when clicked\n * \n * Ideal for values users frequently need to copy such as\n * Email addresses, reference codes, IDs.\n * \n * Features:\n * - Single click copy\n * - Shows ✓ icon when copied\n * - Option to show icon on hover or always\n * - Capable of capturing copy event with optional callback\n * \n * @example\n * // Simple usage\n * ```tsx\n * <CopyableCell value=\"user@example.com\" />\n * ```\n * \n * @example\n * // With Callback\n * ```tsx\n * <CopyableCell \n * value=\"ABC123\" \n * onCopy={(val) => console.log('Copied:', val)}\n * tooltip=\"Copy reference code\"\n * />\n * ```\n * \n * @example\n * // Icon always visible\n * ```tsx\n * <CopyableCell \n * value=\"order-12345\" \n * alwaysShowIcon \n * />\n * ```\n * \n * @example\n * // Usage within TanStack Table column\n * ```tsx\n * columnHelper.accessor(\"email\", {\n * header: \"Email\",\n * cell: (info) => <CopyableCell value={info.getValue()} />,\n * })\n * ```\n */\nexport function CopyableCell({\n value,\n feedbackDuration = 2000,\n onCopy,\n className,\n tooltip = \"Click to copy\",\n alwaysShowIcon = false,\n}: CopyableCellProps) {\n const [copied, setCopied] = React.useState(false);\n const [isHovered, setIsHovered] = React.useState(false);\n\n const handleCopy = async (e: React.MouseEvent) => {\n e.stopPropagation();\n\n const textToCopy = String(value);\n\n try {\n await navigator.clipboard.writeText(textToCopy);\n setCopied(true);\n onCopy?.(textToCopy);\n\n setTimeout(() => {\n setCopied(false);\n }, feedbackDuration);\n } catch (err) {\n console.error(\"Copy error:\", err);\n }\n };\n\n return (\n <button\n type=\"button\"\n onClick={handleCopy}\n onMouseEnter={() => setIsHovered(true)}\n onMouseLeave={() => setIsHovered(false)}\n title={tooltip}\n className={`\n group inline-flex items-center gap-2 \n px-2 py-1 -mx-2 -my-1\n rounded-md\n transition-all duration-200\n hover:bg-slate-100 dark:hover:bg-slate-800\n focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-1\n cursor-pointer\n ${className || \"\"}\n `}\n >\n <span className=\"select-none\">{value}</span>\n\n {/* Copy/Check icon */}\n <span\n className={`\n inline-flex items-center justify-center\n transition-all duration-200\n ${alwaysShowIcon || isHovered || copied ? \"opacity-100\" : \"opacity-0\"}\n `}\n >\n {copied ? (\n <Check className=\"h-3.5 w-3.5 text-green-600 dark:text-green-400\" />\n ) : (\n <Copy className=\"h-3.5 w-3.5 text-slate-400 dark:text-slate-500 group-hover:text-slate-600 dark:group-hover:text-slate-300\" />\n )}\n </span>\n </button>\n );\n}\n\n/**\n * Factory function for CopyableCell\n * \n * Designed for easy use with column helper.\n * Creates CopyableCell by passing the value directly.\n * \n * @example\n * ```tsx\n * const columnHelper = createColumnHelper<User>();\n * \n * const columns = [\n * columnHelper.accessor(\"email\", {\n * header: \"Email\",\n * cell: (info) => createCopyableCell(info.getValue()),\n * }),\n * columnHelper.accessor(\"orderId\", {\n * header: \"Order No\",\n * cell: (info) => createCopyableCell(info.getValue(), { \n * tooltip: \"Copy order number\" \n * }),\n * }),\n * ];\n * ```\n */\nexport function createCopyableCell(\n value: string | number,\n options?: Omit<CopyableCellProps, \"value\">\n) {\n return <CopyableCell value={value} {...options} />;\n}\n",
|
|
34
|
+
"type": "registry:component",
|
|
35
|
+
"target": "components/lux-table/cell-renderers/copyable-cell.tsx"
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"path": "registry/new-york/blocks/lux-table-cell-renderers/currency-cell.tsx",
|
|
39
|
+
"content": "\"use client\";\n\n// ============================================================================\n// CURRENCY CELL - Para Birimi Gösterimi\n// ============================================================================\n// Bu bileşen, tablolarda sayısal değerleri para birimi formatında\n// göstermek için kullanılır.\n//\n// Özellikler:\n// - Otomatik para birimi sembolü\n// - Binlik ayracı ve ondalık formatı\n// - Locale desteği\n// - Tüm dünya para birimleri desteği (TRY, USD, EUR, GBP vb.)\n// ============================================================================\n\nexport interface CurrencyCellProps {\n /** Sayısal değer */\n value: number;\n /** \n * Para birimi kodu (ISO 4217)\n * @default \"TRY\"\n * @example \"USD\", \"EUR\", \"GBP\", \"JPY\"\n */\n currency?: string;\n /** \n * Locale (dil ve bölge formatı)\n * @default \"tr-TR\"\n */\n locale?: string;\n}\n\n/**\n * Para birimi için hazır bileşen\n * \n * Intl.NumberFormat kullanarak sayısal değerleri yerel formatta gösterir.\n * Para birimi sembolü, binlik ayracı ve ondalık formatı otomatik ayarlanır.\n * \n * @example\n * // Türk Lirası (varsayılan)\n * ```tsx\n * <CurrencyCell value={1234.56} />\n * // Çıktı: \"₺1.234,56\"\n * ```\n * \n * @example\n * // Amerikan Doları\n * ```tsx\n * <CurrencyCell value={1234.56} currency=\"USD\" locale=\"en-US\" />\n * // Çıktı: \"$1,234.56\"\n * ```\n * \n * @example\n * // Euro\n * ```tsx\n * <CurrencyCell value={1234.56} currency=\"EUR\" locale=\"de-DE\" />\n * // Çıktı: \"1.234,56 €\"\n * ```\n * \n * @example\n * // TanStack Table column içinde kullanım\n * ```tsx\n * columnHelper.accessor(\"price\", {\n * header: \"Fiyat\",\n * cell: (info) => <CurrencyCell value={info.getValue()} />,\n * })\n * ```\n * \n * @example\n * // Çoklu para birimi desteği\n * ```tsx\n * columnHelper.accessor(\"amount\", {\n * header: \"Tutar\",\n * cell: (info) => (\n * <CurrencyCell \n * value={info.getValue()} \n * currency={info.row.original.currency}\n * />\n * ),\n * })\n * ```\n */\nexport function CurrencyCell({ value, currency = \"TRY\", locale = \"tr-TR\" }: CurrencyCellProps) {\n const formatted = new Intl.NumberFormat(locale, {\n style: \"currency\",\n currency,\n }).format(value);\n\n return <span className=\"font-medium\">{formatted}</span>;\n}\n",
|
|
40
|
+
"type": "registry:component",
|
|
41
|
+
"target": "components/lux-table/cell-renderers/currency-cell.tsx"
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"path": "registry/new-york/blocks/lux-table-cell-renderers/boolean-cell.tsx",
|
|
45
|
+
"content": "\"use client\";\n\n// ============================================================================\n// BOOLEAN CELL - Evet/Hayır Gösterimi\n// ============================================================================\n// Bu bileşen, tablolarda boolean (true/false) değerlerini\n// okunabilir ve renkli metin olarak göstermek için kullanılır.\n//\n// Özellikler:\n// - Özelleştirilebilir etiketler (Yes/No, Evet/Hayır, ✓/✗ vb.)\n// - Özelleştirilebilir renkler\n// - Dark mode desteği\n// ============================================================================\n\nexport interface BooleanCellProps {\n /** Boolean değer */\n value: boolean;\n /** \n * True değeri için metin \n * @default \"Yes\"\n */\n trueLabel?: string;\n /** \n * False değeri için metin \n * @default \"No\"\n */\n falseLabel?: string;\n /** \n * True değeri için renk sınıfları \n * @default \"text-green-600 dark:text-green-400\"\n */\n trueColor?: string;\n /** \n * False değeri için renk sınıfları \n * @default \"text-red-600 dark:text-red-400\"\n */\n falseColor?: string;\n}\n\n/**\n * Boolean değerler için hazır bileşen\n * \n * True değerleri yeşil, false değerleri kırmızı renkte gösterir.\n * Etiketler ve renkler tamamen özelleştirilebilir.\n * \n * @example\n * // Basit kullanım\n * ```tsx\n * <BooleanCell value={true} />\n * // Çıktı: \"Yes\" (yeşil)\n * ```\n * \n * @example\n * // Türkçe etiketlerle\n * ```tsx\n * <BooleanCell value={false} trueLabel=\"Evet\" falseLabel=\"Hayır\" />\n * // Çıktı: \"Hayır\" (kırmızı)\n * ```\n * \n * @example\n * // İkon kullanımı\n * ```tsx\n * <BooleanCell value={true} trueLabel=\"✓\" falseLabel=\"✗\" />\n * // Çıktı: \"✓\" (yeşil)\n * ```\n * \n * @example\n * // Özel renklerle\n * ```tsx\n * <BooleanCell \n * value={true} \n * trueLabel=\"Onaylandı\"\n * trueColor=\"text-blue-600 dark:text-blue-400\"\n * />\n * ```\n * \n * @example\n * // TanStack Table column içinde kullanım\n * ```tsx\n * columnHelper.accessor(\"isActive\", {\n * header: \"Aktif\",\n * cell: (info) => (\n * <BooleanCell \n * value={info.getValue()} \n * trueLabel=\"Aktif\" \n * falseLabel=\"Pasif\" \n * />\n * ),\n * })\n * ```\n */\nexport function BooleanCell({\n value,\n trueLabel = \"Yes\",\n falseLabel = \"No\",\n trueColor = \"text-green-600 dark:text-green-400\",\n falseColor = \"text-red-600 dark:text-red-400\",\n}: BooleanCellProps) {\n return (\n <span className={`font-medium ${value ? trueColor : falseColor}`}>\n {value ? trueLabel : falseLabel}\n </span>\n );\n}\n",
|
|
46
|
+
"type": "registry:component",
|
|
47
|
+
"target": "components/lux-table/cell-renderers/boolean-cell.tsx"
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"path": "registry/new-york/blocks/lux-table-cell-renderers/index.ts",
|
|
51
|
+
"content": "// ============================================================================\n// CELL RENDERERS - Hazır Hücre Bileşenleri\n// ============================================================================\n// LuxTable için önceden hazırlanmış hücre render bileşenleri.\n// Bu bileşenler, tablolardaki yaygın veri tiplerini görsel olarak\n// zengin bir şekilde göstermek için kullanılır.\n//\n// Kullanım:\n// ```tsx\n// import { \n// StatusCell, \n// ProgressCell, \n// BooleanCell,\n// DateCell,\n// CurrencyCell,\n// CopyableCell \n// } from \"@luxtable/core\";\n// ```\n//\n// Mevcut Bileşenler:\n// - StatusCell → Durum badge'leri (Active, Pending, vb.)\n// - ProgressCell → İlerleme çubuğu\n// - BooleanCell → Evet/Hayır gösterimi\n// - DateCell → Tarih formatlaması\n// - CurrencyCell → Para birimi formatlaması\n// - CopyableCell → Kopyalanabilir içerik\n// ============================================================================\n\n// Status Cell - Durum Gösterimi\nexport { StatusCell, defaultStatusColors } from \"@/components/lux-table/cell-renderers/status-cell\";\nexport type { StatusCellProps } from \"@/components/lux-table/cell-renderers/status-cell\";\n\n// Progress Cell - İlerleme Çubuğu\nexport { ProgressCell } from \"@/components/lux-table/cell-renderers/progress-cell\";\nexport type { ProgressCellProps } from \"@/components/lux-table/cell-renderers/progress-cell\";\n\n// Boolean Cell - Evet/Hayır Gösterimi\nexport { BooleanCell } from \"@/components/lux-table/cell-renderers/boolean-cell\";\nexport type { BooleanCellProps } from \"@/components/lux-table/cell-renderers/boolean-cell\";\n\n// Date Cell - Tarih Gösterimi\nexport { DateCell } from \"@/components/lux-table/cell-renderers/date-cell\";\nexport type { DateCellProps } from \"@/components/lux-table/cell-renderers/date-cell\";\n\n// Currency Cell - Para Birimi\nexport { CurrencyCell } from \"@/components/lux-table/cell-renderers/currency-cell\";\nexport type { CurrencyCellProps } from \"@/components/lux-table/cell-renderers/currency-cell\";\n\n// Copyable Cell - Kopyalanabilir Hücre\nexport { CopyableCell, createCopyableCell } from \"@/components/lux-table/cell-renderers/copyable-cell\";\nexport type { CopyableCellProps } from \"@/components/lux-table/cell-renderers/copyable-cell\";\n",
|
|
52
|
+
"type": "registry:component",
|
|
53
|
+
"target": "components/lux-table/cell-renderers/index.ts"
|
|
54
|
+
}
|
|
55
|
+
]
|
|
56
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
|
3
|
+
"name": "lux-table-column-helper",
|
|
4
|
+
"type": "registry:lib",
|
|
5
|
+
"title": "LuxTable Column Helper",
|
|
6
|
+
"author": "LuxTable (https://luxtable.dev)",
|
|
7
|
+
"description": "Type-safe column definition helper with built-in support for common patterns like accessor, display, and action columns.",
|
|
8
|
+
"dependencies": [
|
|
9
|
+
"@tanstack/react-table"
|
|
10
|
+
],
|
|
11
|
+
"registryDependencies": [],
|
|
12
|
+
"files": [
|
|
13
|
+
{
|
|
14
|
+
"path": "registry/new-york/blocks/lux-table-column-helper/column-helper.tsx",
|
|
15
|
+
"content": "import { createColumnHelper as tanstackCreateColumnHelper, ColumnDef, CellContext, HeaderContext } from \"@tanstack/react-table\";\nimport * as React from \"react\";\nimport { LuxDataTableColumnHeader } from \"../components/lux-table/column-header\";\n\n// camelCase -> Title Case conversion\nconst toTitleCase = (str: string) => {\n return str\n .replace(/([A-Z])/g, \" $1\")\n .replace(/^./, (s) => s.toUpperCase())\n .trim();\n};\n\nexport type { ColumnDef };\n\nexport interface ColumnOptions<TData, TValue> {\n id?: string;\n header?: string | ((context: HeaderContext<TData, TValue>) => React.ReactNode);\n cell?: (info: CellContext<TData, TValue>) => React.ReactNode;\n\n enableSorting?: boolean;\n /** Column meta information (e.g. filterVariant) */\n meta?: {\n /** Filter type: \"text\" (default) or \"select\" (dropdown) */\n filterVariant?: \"text\" | \"select\";\n };\n}\n\n// Interface for special column types\nexport type ColumnType = \"text\" | \"status\" | \"progress\" | \"boolean\" | \"date\" | \"currency\" | \"custom\";\n\nexport interface SmartColumnOptions<TData, TValue> extends ColumnOptions<TData, TValue> {\n /** \n * Column type - used for automatic rendering\n * - text: Displays value as plain text (default)\n * - status: Displays with StatusCell component\n * - progress: Displays with ProgressCell component\n * - boolean: Displays with BooleanCell component\n * - date: Displays with DateCell component\n * - currency: Displays with CurrencyCell component\n * - custom: Uses your provided cell function\n */\n type?: ColumnType;\n /** Custom colors for Status type */\n statusColors?: Record<string, { bg: string; text: string; darkBg?: string; darkText?: string }>;\n /** Bar color for Progress type */\n progressBarColor?: string;\n /** Whether to show label for Progress type */\n showProgressLabel?: boolean;\n /** Labels for Boolean type */\n booleanLabels?: { true: string; false: string };\n /** Format for Date type */\n dateFormat?: \"short\" | \"long\" | \"relative\";\n /** Currency code for Currency type */\n currency?: string;\n /** Locale */\n locale?: string;\n}\n\nexport function createColumnHelper<TData>() {\n const helper = tanstackCreateColumnHelper<TData>();\n\n return {\n /**\n * Simple accessor - if cell is not provided, value is rendered automatically\n */\n accessor: <TValue,>(\n accessor: keyof TData & string,\n column?: ColumnOptions<TData, TValue>\n ): ColumnDef<TData, TValue> => {\n const headerContent = column?.header;\n\n const finalColumn = {\n ...column,\n // enableSorting is true by default\n enableSorting: column?.enableSorting !== false,\n // Pass meta information (filterVariant etc.)\n meta: column?.meta,\n // Use LuxDataTableColumnHeader if header is string or undefined\n header: typeof headerContent === 'function'\n ? headerContent\n : ({ column: colParam }: HeaderContext<TData, TValue>) => (\n <LuxDataTableColumnHeader\n column={colParam}\n title={typeof headerContent === 'string' ? headerContent : toTitleCase(accessor)}\n />\n ),\n // If cell is not defined, show value directly\n cell: column?.cell || ((info: CellContext<TData, TValue>) => {\n const value = info.getValue();\n // if null or undefined, show -\n if (value === null || value === undefined) {\n return \"-\";\n }\n // If string or number, show directly\n return String(value);\n }),\n };\n return helper.accessor(accessor as any, finalColumn as any);\n },\n\n /**\n * Display column (for actions etc.)\n */\n display: (column: {\n id: string;\n header?: string | (() => React.ReactNode);\n cell?: (info: CellContext<TData, unknown>) => React.ReactNode;\n enableSorting?: boolean;\n enableHiding?: boolean;\n }): ColumnDef<TData, unknown> => {\n return helper.display(column as any);\n },\n\n /**\n * Action column - specialized display column for row actions\n * Disables sorting by default\n */\n action: (column: {\n cell: (info: CellContext<TData, unknown>) => React.ReactNode;\n id?: string;\n header?: string | (() => React.ReactNode);\n }): ColumnDef<TData, unknown> => {\n return helper.display({\n id: \"actions\",\n header: \"\",\n enableSorting: false,\n enableHiding: false,\n ...column,\n } as any);\n },\n\n /**\n * Create all columns automatically - Table directly from JSON\n * Just specifying headers is enough, cell is rendered automatically\n */\n auto: (\n columns: Array<{\n accessor: keyof TData & string;\n header: string;\n cell?: (info: CellContext<TData, unknown>) => React.ReactNode;\n }>\n ): ColumnDef<TData, unknown>[] => {\n return columns.map((col) => {\n return helper.accessor(col.accessor as any, {\n header: ({ column }: HeaderContext<TData, unknown>) => (\n <LuxDataTableColumnHeader column={column} title={col.header} />\n ),\n cell: col.cell || ((info: CellContext<TData, unknown>) => {\n const value = info.getValue();\n if (value === null || value === undefined) return \"-\";\n return String(value);\n }),\n } as any);\n });\n },\n };\n}\n\n/**\n * Creates automatic columns from JSON data\n * Uses column names as header (camelCase -> Title Case)\n */\nexport function createColumnsFromData<TData extends Record<string, unknown>>(\n data: TData[],\n options?: {\n exclude?: (keyof TData)[];\n include?: (keyof TData)[];\n headers?: Partial<Record<keyof TData, string>>;\n cells?: Partial<Record<keyof TData, (info: CellContext<TData, unknown>) => React.ReactNode>>;\n }\n): ColumnDef<TData, unknown>[] {\n if (!data || data.length === 0) return [];\n\n const helper = tanstackCreateColumnHelper<TData>();\n const firstRow = data[0];\n let keys = Object.keys(firstRow) as (keyof TData & string)[];\n\n // Filter include/exclude\n if (options?.include) {\n keys = keys.filter((k) => options.include?.includes(k));\n }\n if (options?.exclude) {\n keys = keys.filter((k) => !options.exclude?.includes(k));\n }\n\n\n\n return keys.map((key) => {\n const headerText = options?.headers?.[key] || toTitleCase(key as string);\n const cellRenderer = options?.cells?.[key];\n\n return helper.accessor(key as any, {\n header: ({ column }: HeaderContext<TData, unknown>) => <LuxDataTableColumnHeader column={column} title={headerText} />,\n cell: cellRenderer || ((info) => {\n const value = info.getValue();\n if (value === null || value === undefined) return \"-\";\n return String(value);\n }),\n } as any);\n });\n}\n",
|
|
16
|
+
"type": "registry:lib",
|
|
17
|
+
"target": "lib/column-helper.tsx"
|
|
18
|
+
}
|
|
19
|
+
]
|
|
20
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
|
3
|
+
"name": "lux-table",
|
|
4
|
+
"type": "registry:component",
|
|
5
|
+
"title": "LuxTable",
|
|
6
|
+
"author": "LuxTable (https://luxtable.dev)",
|
|
7
|
+
"description": "Enterprise-Grade Data Table component with sorting, filtering, pagination, and row selection. Built on TanStack Table.",
|
|
8
|
+
"dependencies": [
|
|
9
|
+
"@tanstack/react-table",
|
|
10
|
+
"lucide-react",
|
|
11
|
+
"clsx",
|
|
12
|
+
"tailwind-merge"
|
|
13
|
+
],
|
|
14
|
+
"registryDependencies": [
|
|
15
|
+
"button",
|
|
16
|
+
"checkbox",
|
|
17
|
+
"dropdown-menu",
|
|
18
|
+
"input",
|
|
19
|
+
"select"
|
|
20
|
+
],
|
|
21
|
+
"files": [
|
|
22
|
+
{
|
|
23
|
+
"path": "registry/new-york/blocks/lux-table/lux-table.tsx",
|
|
24
|
+
"content": "\"use client\";\n\nimport * as React from \"react\";\nimport {\n flexRender,\n getCoreRowModel,\n useReactTable,\n getPaginationRowModel,\n getSortedRowModel,\n getFilteredRowModel,\n getFacetedUniqueValues,\n SortingState,\n ColumnFiltersState,\n RowSelectionState,\n ColumnDef,\n Row,\n} from \"@tanstack/react-table\";\nimport { CheckCircle2 } from \"lucide-react\";\nimport { cn } from \"@/lib/utils\";\nimport {\n Table,\n TableHeader,\n TableBody,\n TableRow,\n TableHead,\n TableCell,\n} from \"@/components/table\";\nimport { ColumnFilter } from \"@/components/lux-table/column-filter\";\nimport { TablePagination } from \"@/components/lux-table/pagination\";\nimport { TableToolbar } from \"@/components/lux-table/table-toolbar\";\nimport { Checkbox } from \"@/components/ui/checkbox\";\nimport type { LuxTableProps } from \"@/components/lux-table/types\";\n\n// ============================================================================\n// Selection Checkbox Column Helper\n// ============================================================================\n\n/**\n * Creates column definition for selection checkbox\n */\nfunction createSelectionColumn<TData>(): ColumnDef<TData, unknown> {\n return {\n id: \"__selection__\",\n header: ({ table }) => (\n <Checkbox\n checked={\n table.getIsAllPageRowsSelected() ||\n (table.getIsSomePageRowsSelected() && \"indeterminate\")\n }\n onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}\n aria-label=\"Select all rows\"\n />\n ),\n cell: ({ row }) => (\n <Checkbox\n checked={row.getIsSelected()}\n disabled={!row.getCanSelect()}\n onCheckedChange={(value) => row.toggleSelected(!!value)}\n aria-label=\"Select row\"\n />\n ),\n size: 40,\n enableSorting: false,\n enableHiding: false,\n };\n}\n\n// ============================================================================\n// LuxTable Component\n// ============================================================================\n\n/**\n * LuxTable - Advanced React table component\n * \n * Modern table built on top of TanStack Table, comes with ready-to-use features.\n * \n * @example\n * ```tsx\n * // Simple usage\n * <LuxTable\n * columns={columns}\n * data={data}\n * options={{\n * pagination: true,\n * pageSize: 20,\n * filtering: true,\n * sorting: true\n * }}\n * />\n * \n * // With row selection\n * <LuxTable\n * columns={columns}\n * data={data}\n * options={{\n * selection: \"multiple\", // or \"single\"\n * }}\n * onSelectedRowsChange={(selectedRows) => {\n * console.log(\"Selected rows:\", selectedRows);\n * }}\n * />\n * ```\n */\nexport function LuxTable<TData>({\n columns,\n data,\n className,\n options,\n sorting: controlledSorting,\n onSortingChange,\n rowSelection: controlledRowSelection,\n onRowSelectionChange,\n onSelectedRowsChange,\n getRowId,\n}: LuxTableProps<TData>) {\n // Internal sorting state (used when not controlled)\n const [internalSorting, setInternalSorting] = React.useState<SortingState>([]);\n\n // Column filters state\n const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>([]);\n\n // Global filter state\n const [globalFilter, setGlobalFilter] = React.useState(\"\");\n\n // Column filtering visibility (controlled by toolbar)\n const [filteringVisible, setFilteringVisible] = React.useState(options?.filtering ?? false);\n\n // Row selection state\n const [internalRowSelection, setInternalRowSelection] = React.useState<RowSelectionState>({});\n\n // Determine if we're in controlled mode\n const isControlledSorting = controlledSorting !== undefined;\n const sorting = isControlledSorting ? controlledSorting : internalSorting;\n\n const isControlledRowSelection = controlledRowSelection !== undefined;\n const rowSelection = isControlledRowSelection ? controlledRowSelection : internalRowSelection;\n\n // Selection mode configuration\n const selectionMode = options?.selection ?? \"none\";\n const showCheckbox = options?.showSelectionCheckbox ?? (selectionMode !== \"none\");\n const enableRowSelection = selectionMode !== \"none\";\n const enableMultiRowSelection = selectionMode === \"multiple\";\n\n // Build columns with selection column if needed\n const tableColumns = React.useMemo<ColumnDef<TData, unknown>[]>(() => {\n if (showCheckbox && enableRowSelection) {\n return [createSelectionColumn<TData>(), ...columns] as ColumnDef<TData, unknown>[];\n }\n return columns as ColumnDef<TData, unknown>[];\n }, [columns, showCheckbox, enableRowSelection]);\n\n // Handle row selection change\n const handleRowSelectionChange = React.useCallback(\n (updater: RowSelectionState | ((old: RowSelectionState) => RowSelectionState)) => {\n const newSelection = typeof updater === \"function\" ? updater(rowSelection) : updater;\n\n if (isControlledRowSelection && onRowSelectionChange) {\n onRowSelectionChange(newSelection);\n } else {\n setInternalRowSelection(newSelection);\n }\n },\n [isControlledRowSelection, onRowSelectionChange, rowSelection]\n );\n\n const table = useReactTable({\n data,\n columns: tableColumns,\n state: {\n sorting,\n columnFilters,\n rowSelection,\n globalFilter,\n },\n onGlobalFilterChange: setGlobalFilter,\n onSortingChange: (updater) => {\n const newSorting = typeof updater === \"function\" ? updater(sorting) : updater;\n\n if (isControlledSorting && onSortingChange) {\n onSortingChange(newSorting);\n } else {\n setInternalSorting(newSorting);\n }\n },\n onColumnFiltersChange: setColumnFilters,\n onRowSelectionChange: handleRowSelectionChange,\n enableRowSelection,\n enableMultiRowSelection,\n getRowId: getRowId ?? ((row: TData, index: number) => {\n // Try to use \"id\" field if exists, otherwise use index\n if (typeof row === \"object\" && row !== null && \"id\" in row) {\n return String((row as { id: unknown }).id);\n }\n return String(index);\n }),\n getCoreRowModel: getCoreRowModel(),\n getPaginationRowModel: options?.pagination ? getPaginationRowModel() : undefined,\n getSortedRowModel: getSortedRowModel(),\n getFilteredRowModel: getFilteredRowModel(),\n getFacetedUniqueValues: getFacetedUniqueValues(),\n initialState: {\n pagination: {\n pageSize: options?.pageSize ?? 10,\n },\n },\n });\n\n // Call onSelectedRowsChange when selection changes\n React.useEffect(() => {\n if (onSelectedRowsChange) {\n const selectedRows = table.getSelectedRowModel().rows.map((row: Row<TData>) => row.original);\n onSelectedRowsChange(selectedRows);\n }\n }, [rowSelection, onSelectedRowsChange, table]);\n\n // Calculate visible column count (for empty state colspan)\n const visibleColumnCount = tableColumns.length;\n\n // Toolbar visibility\n const showToolbar = options?.showToolbar ?? false;\n const showGlobalSearch = options?.showGlobalSearch ?? true;\n const showColumnVisibility = options?.showColumnVisibility ?? true;\n\n return (\n <div className={cn(\"w-full space-y-4\", className)}>\n {/* Toolbar */}\n {showToolbar && (\n <TableToolbar\n table={table}\n showFiltering={options?.filtering !== undefined}\n filteringEnabled={filteringVisible}\n onFilteringToggle={setFilteringVisible}\n showGlobalSearch={showGlobalSearch}\n showColumnVisibility={showColumnVisibility}\n />\n )}\n\n {/* Selection info bar */}\n {enableRowSelection && Object.keys(rowSelection).length > 0 && (\n <div className=\"flex items-center gap-2 px-4 py-2 text-sm bg-blue-50 dark:bg-blue-950/50 text-blue-700 dark:text-blue-300 rounded-lg border border-blue-200 dark:border-blue-800\">\n <CheckCircle2 className=\"w-4 h-4\" />\n <span>\n <strong>{Object.keys(rowSelection).length}</strong> rows selected\n {table.getFilteredRowModel().rows.length > 0 && (\n <span className=\"text-blue-500 dark:text-blue-400\">\n {\" / \"}{table.getFilteredRowModel().rows.length} total\n </span>\n )}\n </span>\n <button\n type=\"button\"\n onClick={() => handleRowSelectionChange({})}\n className=\"ml-auto text-xs hover:text-blue-900 dark:hover:text-blue-100 underline underline-offset-2\"\n >\n Clear selection\n </button>\n </div>\n )}\n\n <div className=\"rounded-md border border-slate-200 dark:border-slate-800 bg-white dark:bg-slate-950 overflow-hidden\">\n <Table>\n <TableHeader>\n {table.getHeaderGroups().map((headerGroup) => (\n <TableRow key={headerGroup.id}>\n {headerGroup.headers.map((header) => {\n const isSelectionColumn = header.id === \"__selection__\";\n\n return (\n <TableHead\n key={header.id}\n style={isSelectionColumn ? { width: 40, padding: \"0 12px\" } : undefined}\n >\n {header.isPlaceholder\n ? null\n : (flexRender(\n header.column.columnDef.header,\n header.getContext()\n ) as React.ReactNode)}\n </TableHead>\n );\n })}\n\n </TableRow>\n ))}\n\n {/* Filter Row */}\n {filteringVisible && (\n <TableRow className=\"bg-slate-50/50 dark:bg-slate-900/50\">\n {table.getHeaderGroups()[0]?.headers.map((header) => {\n const isSelectionColumn = header.id === \"__selection__\";\n return (\n <TableHead key={`filter-${header.id}`} className=\"py-2\">\n {!isSelectionColumn && header.column.getCanFilter() ? (\n <ColumnFilter column={header.column} />\n ) : null}\n </TableHead>\n );\n })}\n </TableRow>\n )}\n </TableHeader>\n <TableBody>\n {table.getRowModel().rows?.length ? (\n table.getRowModel().rows.map((row) => (\n <TableRow\n key={row.id}\n data-state={row.getIsSelected() && \"selected\"}\n className={cn(\n enableRowSelection && \"cursor-pointer\",\n row.getIsSelected() && \"bg-blue-50/50 dark:bg-blue-950/30\"\n )}\n onClick={\n enableRowSelection && !showCheckbox\n ? () => {\n if (selectionMode === \"single\") {\n handleRowSelectionChange({ [row.id]: true });\n } else {\n row.toggleSelected();\n }\n }\n : undefined\n }\n >\n {row.getVisibleCells().map((cell) => {\n const isSelectionColumn = cell.column.id === \"__selection__\";\n return (\n <TableCell\n key={cell.id}\n style={isSelectionColumn ? { width: 40, padding: \"0 12px\" } : undefined}\n onClick={isSelectionColumn ? (e) => e.stopPropagation() : undefined}\n >\n {flexRender(cell.column.columnDef.cell, cell.getContext()) as React.ReactNode}\n </TableCell>\n );\n })}\n </TableRow>\n ))\n ) : (\n <TableRow>\n <TableCell colSpan={visibleColumnCount} className=\"h-24 text-center\">\n No results.\n </TableCell>\n </TableRow>\n )}\n </TableBody>\n </Table>\n </div>\n\n {/* Pagination Controls */}\n {options?.pagination && <TablePagination table={table} />}\n </div>\n );\n}\n\n",
|
|
25
|
+
"type": "registry:component",
|
|
26
|
+
"target": "components/lux-table/lux-table.tsx"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"path": "registry/new-york/blocks/lux-table/types.ts",
|
|
30
|
+
"content": "\"use client\";\n\nimport { ColumnDef, SortingState, RowSelectionState } from \"@tanstack/react-table\";\n\n// ============================================================================\n// LuxTable Types\n// ============================================================================\n\n/**\n * All type definitions for LuxTable\n */\n\nexport interface LuxTableOptions {\n /** Pagination feature (default: false) */\n pagination?: boolean;\n /** Rows per page (default: 10) */\n pageSize?: number;\n /** Sorting feature (default: true) */\n sorting?: boolean;\n /** Column filtering feature (default: false) */\n filtering?: boolean;\n /** Row selection mode - \"single\": single select, \"multiple\": multi-select, \"none\": disabled */\n selection?: \"single\" | \"multiple\" | \"none\";\n /** Show selection checkbox (default: true if selection !== \"none\") */\n showSelectionCheckbox?: boolean;\n \n // Toolbar Options\n /** Show toolbar with search and controls (default: false) */\n showToolbar?: boolean;\n /** Show global search in toolbar (default: true when toolbar is shown) */\n showGlobalSearch?: boolean;\n /** Show column visibility controls in toolbar (default: true when toolbar is shown) */\n showColumnVisibility?: boolean;\n}\n\nexport interface LuxTableProps<TData> {\n /** Column definitions */\n columns: ColumnDef<TData, any>[];\n /** Table data */\n data: TData[];\n /** Additional CSS classes */\n className?: string;\n /** Table options */\n options?: LuxTableOptions;\n /** Controlled sorting state */\n sorting?: SortingState;\n /** Called when sorting changes */\n onSortingChange?: (sorting: SortingState) => void;\n /** Controlled row selection state */\n rowSelection?: RowSelectionState;\n /** Called when row selection changes */\n onRowSelectionChange?: (rowSelection: RowSelectionState) => void;\n /** Called when selected rows change (with row data) */\n onSelectedRowsChange?: (rows: TData[]) => void;\n /** Unique ID field for each row (default: \"id\") */\n getRowId?: (row: TData, index: number) => string;\n}\n\n/**\n * Extended type for Column meta\n * Can be used in the meta field in column definitions\n */\nexport interface ColumnMeta {\n /** Filter type: text or select */\n filterVariant?: \"text\" | \"select\";\n}\n\n/**\n * Pagination information\n */\nexport interface PaginationInfo {\n pageIndex: number;\n pageSize: number;\n totalRows: number;\n totalPages: number;\n}\n",
|
|
31
|
+
"type": "registry:component",
|
|
32
|
+
"target": "components/lux-table/types.ts"
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"path": "registry/new-york/blocks/lux-table/column-filter.tsx",
|
|
36
|
+
"content": "\"use client\";\n\nimport * as React from \"react\";\nimport { Column } from \"@tanstack/react-table\";\nimport { Input } from \"@/components/ui/input\";\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from \"@/components/ui/select\";\nimport { cn } from \"@/lib/utils\";\n\n// ============================================================================\n// Column Filter Component\n// ============================================================================\n\ninterface ColumnFilterProps<TData, TValue> {\n column: Column<TData, TValue>;\n}\n\n/**\n * Column filter component\n * Rendered as text input or select dropdown using shadcn/ui components\n * \n * @example\n * ```tsx\n * <ColumnFilter column={column} />\n * ```\n */\nexport function ColumnFilter<TData, TValue>({ column }: ColumnFilterProps<TData, TValue>) {\n const columnFilterValue = column.getFilterValue();\n\n // Get filter type from column meta (default: text)\n const filterVariant = (column.columnDef.meta as { filterVariant?: \"text\" | \"select\" })?.filterVariant ?? \"text\";\n\n // For select filter, get unique values from the column\n const sortedUniqueValues = React.useMemo(() => {\n if (filterVariant !== \"select\") return [];\n\n const values = new Set<string>();\n column.getFacetedUniqueValues().forEach((_, key) => {\n if (key !== null && key !== undefined) {\n values.add(String(key));\n }\n });\n return Array.from(values).sort();\n }, [column, filterVariant]);\n\n // Select (dropdown) filter using shadcn/ui Select\n if (filterVariant === \"select\") {\n return (\n <Select\n value={(columnFilterValue ?? \"\") as string}\n onValueChange={(value) => column.setFilterValue(value === \"__all__\" ? undefined : value)}\n >\n <SelectTrigger\n className={cn(\n \"h-8 text-xs\",\n \"border-slate-200 dark:border-slate-700\",\n \"bg-white dark:bg-slate-900\",\n \"text-slate-900 dark:text-slate-100\"\n )}\n onClick={(e) => e.stopPropagation()}\n >\n <SelectValue placeholder=\"All\" />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"__all__\">All</SelectItem>\n {sortedUniqueValues.map((value) => (\n <SelectItem key={value} value={value}>\n {value}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n );\n }\n\n // Text input filter using shadcn/ui Input\n return (\n <Input\n type=\"text\"\n value={(columnFilterValue ?? \"\") as string}\n onChange={(e) => column.setFilterValue(e.target.value || undefined)}\n placeholder=\"Filter...\"\n className={cn(\n \"h-8 text-xs\",\n \"border-slate-200 dark:border-slate-700\",\n \"bg-white dark:bg-slate-900\",\n \"text-slate-900 dark:text-slate-100\",\n \"placeholder:text-slate-400 dark:placeholder:text-slate-500\"\n )}\n onClick={(e) => e.stopPropagation()}\n />\n );\n}\n\n",
|
|
37
|
+
"type": "registry:component",
|
|
38
|
+
"target": "components/lux-table/column-filter.tsx"
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"path": "registry/new-york/blocks/lux-table/column-header.tsx",
|
|
42
|
+
"content": "import {\n ArrowDown,\n ArrowUp,\n ChevronsUpDown,\n EyeOff,\n MoreVertical,\n X,\n} from \"lucide-react\"\n\nimport { Column } from \"@tanstack/react-table\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/components/ui/button\"\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n} from \"@/components/ui/dropdown-menu\"\n\ninterface LuxDataTableColumnHeaderProps<TData, TValue>\n extends React.HTMLAttributes<HTMLDivElement> {\n column: Column<TData, TValue>\n title: string\n}\n\nexport function LuxDataTableColumnHeader<TData, TValue>({\n column,\n title,\n className,\n}: LuxDataTableColumnHeaderProps<TData, TValue>) {\n const isSorted = column.getIsSorted()\n\n // If sorting is not enabled, just show the title\n if (!column.getCanSort()) {\n return (\n <div className={cn(\"flex items-center justify-between\", className)}>\n <span className=\"text-sm font-medium text-muted-foreground\">{title}</span>\n </div>\n )\n }\n\n // Sortable column - clickable title for quick sort + actions menu\n return (\n <div className={cn(\"flex items-center justify-between gap-1\", className)}>\n {/* Clickable title + sort icon for quick sorting */}\n <Button\n variant=\"ghost\"\n size=\"sm\"\n className=\"-ml-3 h-8 hover:bg-accent\"\n onClick={() => column.toggleSorting(isSorted === \"asc\")}\n >\n <span>{title}</span>\n {isSorted === \"desc\" ? (\n <ArrowDown className=\"ml-1.5 h-4 w-4 text-primary\" />\n ) : isSorted === \"asc\" ? (\n <ArrowUp className=\"ml-1.5 h-4 w-4 text-primary\" />\n ) : (\n <ChevronsUpDown className=\"ml-1.5 h-4 w-4 text-muted-foreground/50\" />\n )}\n </Button>\n\n {/* Actions dropdown menu */}\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button\n variant=\"ghost\"\n size=\"icon\"\n className=\"h-6 w-6 opacity-0 group-hover:opacity-100 hover:opacity-100 focus:opacity-100 data-[state=open]:opacity-100 transition-opacity\"\n >\n <MoreVertical className=\"h-3.5 w-3.5\" />\n <span className=\"sr-only\">Column actions</span>\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\">\n <DropdownMenuItem onClick={() => column.toggleSorting(false)}>\n <ArrowUp className=\"mr-2 h-3.5 w-3.5 text-muted-foreground/70\" />\n Sort Ascending\n </DropdownMenuItem>\n <DropdownMenuItem onClick={() => column.toggleSorting(true)}>\n <ArrowDown className=\"mr-2 h-3.5 w-3.5 text-muted-foreground/70\" />\n Sort Descending\n </DropdownMenuItem>\n {isSorted && (\n <>\n <DropdownMenuSeparator />\n <DropdownMenuItem onClick={() => column.clearSorting()}>\n <X className=\"mr-2 h-3.5 w-3.5 text-muted-foreground/70\" />\n Clear sorting\n </DropdownMenuItem>\n </>\n )}\n <DropdownMenuSeparator />\n <DropdownMenuItem onClick={() => column.toggleVisibility(false)}>\n <EyeOff className=\"mr-2 h-3.5 w-3.5 text-muted-foreground/70\" />\n Hide column\n </DropdownMenuItem>\n </DropdownMenuContent>\n </DropdownMenu>\n </div>\n )\n}\n\n",
|
|
43
|
+
"type": "registry:component",
|
|
44
|
+
"target": "components/lux-table/column-header.tsx"
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"path": "registry/new-york/blocks/lux-table/pagination.tsx",
|
|
48
|
+
"content": "\"use client\";\n\nimport * as React from \"react\";\nimport { Table } from \"@tanstack/react-table\";\nimport {\n ChevronsLeft,\n ChevronLeft,\n ChevronRight,\n ChevronsRight,\n} from \"lucide-react\";\nimport { cn } from \"@/lib/utils\";\n\n// ============================================================================\n// Pagination Button\n// ============================================================================\n\ninterface PaginationButtonProps {\n onClick: () => void;\n disabled: boolean;\n title: string;\n children: React.ReactNode;\n}\n\nfunction PaginationButton({ onClick, disabled, title, children }: PaginationButtonProps) {\n return (\n <button\n className={cn(\n \"inline-flex items-center justify-center rounded-md text-sm font-medium\",\n \"h-9 w-9\",\n \"border border-slate-200 dark:border-slate-800\",\n \"bg-white dark:bg-slate-950\",\n \"hover:bg-slate-100 dark:hover:bg-slate-800\",\n \"disabled:pointer-events-none disabled:opacity-50\",\n \"transition-colors\"\n )}\n onClick={onClick}\n disabled={disabled}\n title={title}\n >\n {children}\n </button>\n );\n}\n\n// ============================================================================\n// Page Number Button\n// ============================================================================\n\ninterface PageNumberButtonProps {\n pageNum: number;\n isActive: boolean;\n onClick: () => void;\n}\n\nfunction PageNumberButton({ pageNum, isActive, onClick }: PageNumberButtonProps) {\n return (\n <button\n onClick={onClick}\n className={cn(\n \"inline-flex items-center justify-center rounded-md text-sm font-medium\",\n \"h-9 w-9\",\n \"transition-colors\",\n isActive\n ? \"bg-blue-600 text-white border border-blue-600\"\n : \"border border-slate-200 dark:border-slate-800 bg-white dark:bg-slate-950 hover:bg-slate-100 dark:hover:bg-slate-800\"\n )}\n >\n {pageNum + 1}\n </button>\n );\n}\n\n// ============================================================================\n// Pagination Component\n// ============================================================================\n\ninterface TablePaginationProps<TData> {\n table: Table<TData>;\n}\n\n/**\n * Table pagination component\n * Provides page navigation, record info and page size selection\n */\nexport function TablePagination<TData>({ table }: TablePaginationProps<TData>) {\n const currentPage = table.getState().pagination.pageIndex;\n const totalPages = table.getPageCount();\n const pageSize = table.getState().pagination.pageSize;\n const totalRows = table.getFilteredRowModel().rows.length;\n\n // Calculate visible range\n const startRow = currentPage * pageSize + 1;\n const endRow = Math.min((currentPage + 1) * pageSize, totalRows);\n\n // Generate page numbers to display\n const getPageNumbers = (): (number | string)[] => {\n const pages: (number | string)[] = [];\n\n if (totalPages <= 7) {\n // Show all pages if 7 or less\n for (let i = 0; i < totalPages; i++) {\n pages.push(i);\n }\n } else {\n // Always show first page\n pages.push(0);\n\n if (currentPage > 3) {\n pages.push(\"...\");\n }\n\n // Show pages around current\n for (let i = Math.max(1, currentPage - 1); i <= Math.min(totalPages - 2, currentPage + 1); i++) {\n pages.push(i);\n }\n\n if (currentPage < totalPages - 4) {\n pages.push(\"...\");\n }\n\n // Always show last page\n pages.push(totalPages - 1);\n }\n\n return pages;\n };\n\n return (\n <div className=\"flex flex-col sm:flex-row items-center justify-between gap-4 px-2 py-3\">\n {/* Left side: Records info + Page size selector */}\n <div className=\"flex items-center gap-4\">\n {/* Records info */}\n <div className=\"text-sm text-slate-500 dark:text-slate-400\">\n <span className=\"font-medium text-slate-700 dark:text-slate-300\">\n {startRow}\n </span>\n -\n <span className=\"font-medium text-slate-700 dark:text-slate-300\">\n {endRow}\n </span>{\" \"}\n of{\" \"}\n <span className=\"font-medium text-slate-700 dark:text-slate-300\">\n {totalRows}\n </span>{\" \"}\n records shown\n </div>\n\n {/* Page size selector */}\n <div className=\"flex items-center gap-2\">\n <span className=\"text-sm text-slate-500 dark:text-slate-400\">Rows per page:</span>\n <select\n value={pageSize}\n onChange={(e) => {\n table.setPageSize(Number(e.target.value));\n }}\n className={cn(\n \"h-9 rounded-md border border-slate-200 dark:border-slate-800\",\n \"bg-white dark:bg-slate-950\",\n \"px-3 py-1 text-sm\",\n \"focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-1\",\n \"cursor-pointer\"\n )}\n >\n {[10, 20, 30, 50, 100].map((size) => (\n <option key={size} value={size}>\n {size}\n </option>\n ))}\n </select>\n </div>\n </div>\n\n {/* Right side: Navigation */}\n <div className=\"flex items-center gap-2\">\n {/* First page */}\n <PaginationButton\n onClick={() => table.setPageIndex(0)}\n disabled={!table.getCanPreviousPage()}\n title=\"First page\"\n >\n <ChevronsLeft className=\"h-4 w-4\" />\n </PaginationButton>\n\n {/* Previous page */}\n <PaginationButton\n onClick={() => table.previousPage()}\n disabled={!table.getCanPreviousPage()}\n title=\"Previous page\"\n >\n <ChevronLeft className=\"h-4 w-4\" />\n </PaginationButton>\n\n {/* Page numbers */}\n <div className=\"flex items-center gap-1\">\n {getPageNumbers().map((page, idx) => {\n if (page === \"...\") {\n return (\n <span key={`ellipsis-${idx}`} className=\"px-2 text-slate-400\">\n ...\n </span>\n );\n }\n\n const pageNum = page as number;\n return (\n <PageNumberButton\n key={pageNum}\n pageNum={pageNum}\n isActive={pageNum === currentPage}\n onClick={() => table.setPageIndex(pageNum)}\n />\n );\n })}\n </div>\n\n {/* Next page */}\n <PaginationButton\n onClick={() => table.nextPage()}\n disabled={!table.getCanNextPage()}\n title=\"Next page\"\n >\n <ChevronRight className=\"h-4 w-4\" />\n </PaginationButton>\n\n {/* Last page */}\n <PaginationButton\n onClick={() => table.setPageIndex(table.getPageCount() - 1)}\n disabled={!table.getCanNextPage()}\n title=\"Last page\"\n >\n <ChevronsRight className=\"h-4 w-4\" />\n </PaginationButton>\n </div>\n </div>\n );\n}\n",
|
|
49
|
+
"type": "registry:component",
|
|
50
|
+
"target": "components/lux-table/pagination.tsx"
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"path": "registry/new-york/blocks/lux-table/table-toolbar.tsx",
|
|
54
|
+
"content": "\"use client\";\n\nimport * as React from \"react\";\nimport { Table } from \"@tanstack/react-table\";\nimport {\n Search,\n Filter,\n FilterX,\n Columns3,\n Eye,\n EyeOff,\n RotateCcw,\n ChevronDown,\n} from \"lucide-react\";\nimport { cn } from \"@/lib/utils\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuLabel,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n DropdownMenuCheckboxItem,\n} from \"@/components/ui/dropdown-menu\";\n\n// ============================================================================\n// TableToolbar Component\n// ============================================================================\n\ninterface TableToolbarProps<TData> {\n /** TanStack Table instance */\n table: Table<TData>;\n /** Whether column filtering is enabled */\n showFiltering?: boolean;\n /** Callback to toggle column filtering UI */\n onFilteringToggle?: (enabled: boolean) => void;\n /** Current filtering state */\n filteringEnabled?: boolean;\n /** Show global search input */\n showGlobalSearch?: boolean;\n /** Show column visibility controls */\n showColumnVisibility?: boolean;\n /** Additional CSS classes */\n className?: string;\n}\n\n/**\n * TableToolbar - A toolbar component for LuxTable\n * \n * Provides controls for:\n * - Global search across all columns\n * - Column visibility toggle (show/hide columns)\n * - Column filtering toggle (on/off)\n */\nexport function TableToolbar<TData>({\n table,\n showFiltering = true,\n onFilteringToggle,\n filteringEnabled = false,\n showGlobalSearch = true,\n showColumnVisibility = true,\n className,\n}: TableToolbarProps<TData>) {\n const [globalFilter, setGlobalFilter] = React.useState(\"\");\n\n // Get all columns that can be hidden\n const hidableColumns = table\n .getAllColumns()\n .filter((column) => column.getCanHide() && column.id !== \"__selection__\");\n\n // Get hidden columns\n const hiddenColumns = hidableColumns.filter((column) => !column.getIsVisible());\n\n // Handle global search\n const handleGlobalSearch = React.useCallback(\n (value: string) => {\n setGlobalFilter(value);\n table.setGlobalFilter(value);\n },\n [table]\n );\n\n // Reset all column visibility\n const handleResetVisibility = React.useCallback(() => {\n table.resetColumnVisibility();\n }, [table]);\n\n // Show all hidden columns\n const handleShowAllColumns = React.useCallback(() => {\n hidableColumns.forEach((column) => {\n column.toggleVisibility(true);\n });\n }, [hidableColumns]);\n\n return (\n <div\n className={cn(\n \"flex flex-wrap items-center gap-2 p-3 rounded-lg border border-slate-200 dark:border-slate-800 bg-slate-50/50 dark:bg-slate-900/50\",\n className\n )}\n >\n {/* Global Search */}\n {showGlobalSearch && (\n <div className=\"relative flex-1 min-w-[200px] max-w-sm\">\n <Search className=\"absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-slate-400\" />\n <input\n type=\"text\"\n placeholder=\"Search in all columns...\"\n value={globalFilter}\n onChange={(e) => handleGlobalSearch(e.target.value)}\n className={cn(\n \"w-full h-9 pl-9 pr-3 rounded-md border border-slate-200 dark:border-slate-700\",\n \"bg-white dark:bg-slate-950 text-sm text-slate-900 dark:text-slate-100\",\n \"placeholder:text-slate-400 dark:placeholder:text-slate-500\",\n \"focus:outline-none focus:ring-2 focus:ring-blue-500/50 focus:border-blue-500\",\n \"transition-colors\"\n )}\n />\n {globalFilter && (\n <button\n onClick={() => handleGlobalSearch(\"\")}\n className=\"absolute right-2 top-1/2 -translate-y-1/2 p-1 rounded-full hover:bg-slate-200 dark:hover:bg-slate-700 transition-colors\"\n >\n <FilterX className=\"h-3 w-3 text-slate-400\" />\n </button>\n )}\n </div>\n )}\n\n <div className=\"flex items-center gap-2 ml-auto\">\n {/* Toggle Column Filtering */}\n {showFiltering && onFilteringToggle && (\n <Button\n variant={filteringEnabled ? \"default\" : \"outline\"}\n size=\"sm\"\n onClick={() => onFilteringToggle(!filteringEnabled)}\n className=\"gap-2\"\n >\n {filteringEnabled ? (\n <>\n <FilterX className=\"h-4 w-4\" />\n <span className=\"hidden sm:inline\">Hide Filters</span>\n </>\n ) : (\n <>\n <Filter className=\"h-4 w-4\" />\n <span className=\"hidden sm:inline\">Show Filters</span>\n </>\n )}\n </Button>\n )}\n\n {/* Column Visibility Dropdown */}\n {showColumnVisibility && (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button variant=\"outline\" size=\"sm\" className=\"gap-2\">\n <Columns3 className=\"h-4 w-4\" />\n <span className=\"hidden sm:inline\">Columns</span>\n {hiddenColumns.length > 0 && (\n <span className=\"ml-1 flex h-5 w-5 items-center justify-center rounded-full bg-blue-500 text-[10px] font-medium text-white\">\n {hiddenColumns.length}\n </span>\n )}\n <ChevronDown className=\"h-3 w-3 opacity-50\" />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\" className=\"w-56\">\n <DropdownMenuLabel className=\"flex items-center gap-2\">\n <Columns3 className=\"h-4 w-4\" />\n Toggle Columns\n </DropdownMenuLabel>\n <DropdownMenuSeparator />\n\n {/* Quick actions */}\n <div className=\"flex items-center gap-1 px-2 py-1.5\">\n <Button\n variant=\"ghost\"\n size=\"sm\"\n onClick={handleShowAllColumns}\n disabled={hiddenColumns.length === 0}\n className=\"h-7 flex-1 text-xs\"\n >\n <Eye className=\"mr-1 h-3 w-3\" />\n Show All\n </Button>\n <Button\n variant=\"ghost\"\n size=\"sm\"\n onClick={handleResetVisibility}\n className=\"h-7 flex-1 text-xs\"\n >\n <RotateCcw className=\"mr-1 h-3 w-3\" />\n Reset\n </Button>\n </div>\n\n <DropdownMenuSeparator />\n\n {/* Column list */}\n <div className=\"max-h-[300px] overflow-y-auto\">\n {hidableColumns.map((column) => {\n const columnDef = column.columnDef;\n const headerText =\n typeof columnDef.header === \"string\"\n ? columnDef.header\n : column.id;\n\n return (\n <DropdownMenuCheckboxItem\n key={column.id}\n checked={column.getIsVisible()}\n onCheckedChange={(value) => column.toggleVisibility(!!value)}\n className=\"capitalize\"\n >\n <span className=\"flex items-center gap-2\">\n {column.getIsVisible() ? (\n <Eye className=\"h-3.5 w-3.5 text-green-500\" />\n ) : (\n <EyeOff className=\"h-3.5 w-3.5 text-slate-400\" />\n )}\n {headerText}\n </span>\n </DropdownMenuCheckboxItem>\n );\n })}\n </div>\n\n {hiddenColumns.length > 0 && (\n <>\n <DropdownMenuSeparator />\n <div className=\"px-2 py-1.5 text-xs text-slate-500 dark:text-slate-400\">\n {hiddenColumns.length} column(s) hidden\n </div>\n </>\n )}\n </DropdownMenuContent>\n </DropdownMenu>\n )}\n </div>\n </div>\n );\n}\n",
|
|
55
|
+
"type": "registry:component",
|
|
56
|
+
"target": "components/lux-table/table-toolbar.tsx"
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"path": "registry/new-york/blocks/lux-table/table.tsx",
|
|
60
|
+
"content": "\"use client\";\n\nimport * as React from \"react\";\nimport { cn } from \"@/lib/utils\";\n\n\n// ============================================================================\n// Table Root\n// ============================================================================\n\nconst Table = React.forwardRef<\n HTMLTableElement,\n React.HTMLAttributes<HTMLTableElement>\n>(({ className, ...props }, ref) => (\n <div className=\"relative w-full overflow-auto\">\n <table\n ref={ref}\n className={cn(\"w-full caption-bottom text-sm\", className)}\n {...props}\n />\n </div>\n));\nTable.displayName = \"Table\";\n\n// ============================================================================\n// Table Header\n// ============================================================================\n\nconst TableHeader = React.forwardRef<\n HTMLTableSectionElement,\n React.HTMLAttributes<HTMLTableSectionElement>\n>(({ className, ...props }, ref) => (\n <thead\n ref={ref}\n className={cn(\"[&_tr]:border-b [&_tr]:border-slate-200 dark:[&_tr]:border-slate-800\", className)}\n {...props}\n />\n));\nTableHeader.displayName = \"TableHeader\";\n\n// ============================================================================\n// Table Body\n// ============================================================================\n\nconst TableBody = React.forwardRef<\n HTMLTableSectionElement,\n React.HTMLAttributes<HTMLTableSectionElement>\n>(({ className, ...props }, ref) => (\n <tbody\n ref={ref}\n className={cn(\"[&_tr:last-child]:border-0\", className)}\n {...props}\n />\n));\nTableBody.displayName = \"TableBody\";\n\n// ============================================================================\n// Table Footer\n// ============================================================================\n\nconst TableFooter = React.forwardRef<\n HTMLTableSectionElement,\n React.HTMLAttributes<HTMLTableSectionElement>\n>(({ className, ...props }, ref) => (\n <tfoot\n ref={ref}\n className={cn(\n \"border-t border-slate-200 dark:border-slate-800 bg-slate-100/50 dark:bg-slate-800/50 font-medium [&>tr]:last:border-b-0\",\n className\n )}\n {...props}\n />\n));\nTableFooter.displayName = \"TableFooter\";\n\n// ============================================================================\n// Table Row\n// ============================================================================\n\nconst TableRow = React.forwardRef<\n HTMLTableRowElement,\n React.HTMLAttributes<HTMLTableRowElement>\n>(({ className, ...props }, ref) => (\n <tr\n ref={ref}\n className={cn(\n \"border-b border-slate-200 dark:border-slate-800 transition-colors\",\n \"hover:bg-slate-100/50 dark:hover:bg-slate-800/50\",\n \"data-[state=selected]:bg-slate-100 dark:data-[state=selected]:bg-slate-800\",\n className\n )}\n {...props}\n />\n));\nTableRow.displayName = \"TableRow\";\n\n// ============================================================================\n// Table Head (th)\n// ============================================================================\n\nconst TableHead = React.forwardRef<\n HTMLTableCellElement,\n React.ThHTMLAttributes<HTMLTableCellElement>\n>(({ className, children, ...props }, ref) => {\n return (\n <th\n ref={ref}\n className={cn(\n \"h-10 px-4 text-left align-middle font-medium text-slate-500 dark:text-slate-400\",\n \"[&:has([role=checkbox])]:pr-0\",\n \"group\", // Enable group-hover for action buttons\n className\n )}\n {...props}\n >\n {children}\n </th>\n );\n});\nTableHead.displayName = \"TableHead\";\n\n\n\n// ============================================================================\n// Table Cell (td)\n// ============================================================================\n\nconst TableCell = React.forwardRef<\n HTMLTableCellElement,\n React.TdHTMLAttributes<HTMLTableCellElement>\n>(({ className, ...props }, ref) => (\n <td\n ref={ref}\n className={cn(\n \"p-4 align-middle [&:has([role=checkbox])]:pr-0\",\n className\n )}\n {...props}\n />\n));\nTableCell.displayName = \"TableCell\";\n\n// ============================================================================\n// Table Caption\n// ============================================================================\n\nconst TableCaption = React.forwardRef<\n HTMLTableCaptionElement,\n React.HTMLAttributes<HTMLTableCaptionElement>\n>(({ className, ...props }, ref) => (\n <caption\n ref={ref}\n className={cn(\"mt-4 text-sm text-slate-500 dark:text-slate-400\", className)}\n {...props}\n />\n));\nTableCaption.displayName = \"TableCaption\";\n\nexport {\n Table,\n TableHeader,\n TableBody,\n TableFooter,\n TableHead,\n TableRow,\n TableCell,\n TableCaption,\n};\n",
|
|
61
|
+
"type": "registry:component",
|
|
62
|
+
"target": "components/table/table.tsx"
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
"path": "registry/new-york/blocks/lux-table/utils.ts",
|
|
66
|
+
"content": "import { type ClassValue, clsx } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n",
|
|
67
|
+
"type": "registry:lib",
|
|
68
|
+
"target": "lib/utils.ts"
|
|
69
|
+
}
|
|
70
|
+
]
|
|
71
|
+
}
|