@mostrom/app-shell 0.1.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/.claude/ralph-loop.local.md +9 -0
- package/README.md +172 -0
- package/bin/init.js +269 -0
- package/bun.lock +401 -0
- package/components.json +28 -0
- package/package.json +74 -0
- package/scripts/publish-npm.sh +202 -0
- package/src/AppShell.tsx +847 -0
- package/src/components/PageHeader.tsx +160 -0
- package/src/components/data-table/README.md +447 -0
- package/src/components/data-table/data-table-preferences.tsx +184 -0
- package/src/components/data-table/data-table-toolbar.tsx +118 -0
- package/src/components/data-table/data-table.tsx +37 -0
- package/src/components/data-table/index.ts +32 -0
- package/src/components/global-header/AllServicesButton.tsx +127 -0
- package/src/components/global-header/CategoriesButton.tsx +120 -0
- package/src/components/global-header/GlobalHeader.tsx +59 -0
- package/src/components/global-header/GlobalHeaderSearch.tsx +57 -0
- package/src/components/global-header/HeaderUtilities.tsx +243 -0
- package/src/components/global-header/ServicesMenu.tsx +246 -0
- package/src/components/layout/AppBreadcrumb.tsx +70 -0
- package/src/components/layout/AppFlashbar.tsx +95 -0
- package/src/components/layout/AppLayout.tsx +271 -0
- package/src/components/layout/AppNavigation.tsx +313 -0
- package/src/components/layout/AppSidebar.tsx +229 -0
- package/src/components/patterns/index.ts +14 -0
- package/src/components/patterns/p-alert-5.tsx +19 -0
- package/src/components/patterns/p-autocomplete-5.tsx +89 -0
- package/src/components/patterns/p-breadcrumb-1.tsx +28 -0
- package/src/components/patterns/p-button-42.tsx +37 -0
- package/src/components/patterns/p-button-51.tsx +14 -0
- package/src/components/patterns/p-button-6.tsx +5 -0
- package/src/components/patterns/p-calendar-1.tsx +18 -0
- package/src/components/patterns/p-card-1.tsx +33 -0
- package/src/components/patterns/p-card-2.tsx +26 -0
- package/src/components/patterns/p-card-5.tsx +31 -0
- package/src/components/patterns/p-collapsible-7.tsx +121 -0
- package/src/components/patterns/p-command-6.tsx +113 -0
- package/src/components/patterns/p-dialog-1.tsx +56 -0
- package/src/components/patterns/p-dropdown-menu-1.tsx +38 -0
- package/src/components/patterns/p-dropdown-menu-11.tsx +122 -0
- package/src/components/patterns/p-dropdown-menu-14.tsx +165 -0
- package/src/components/patterns/p-dropdown-menu-9.tsx +108 -0
- package/src/components/patterns/p-empty-2.tsx +34 -0
- package/src/components/patterns/p-file-upload-1.tsx +72 -0
- package/src/components/patterns/p-filters-1.tsx +666 -0
- package/src/components/patterns/p-frame-2.tsx +26 -0
- package/src/components/patterns/p-tabs-2.tsx +129 -0
- package/src/components/reui/alert.tsx +92 -0
- package/src/components/reui/autocomplete.tsx +343 -0
- package/src/components/reui/badge.tsx +87 -0
- package/src/components/reui/data-grid/data-grid-column-filter.tsx +165 -0
- package/src/components/reui/data-grid/data-grid-column-header.tsx +339 -0
- package/src/components/reui/data-grid/data-grid-column-visibility.tsx +55 -0
- package/src/components/reui/data-grid/data-grid-pagination.tsx +224 -0
- package/src/components/reui/data-grid/data-grid-table-dnd-rows.tsx +260 -0
- package/src/components/reui/data-grid/data-grid-table-dnd.tsx +253 -0
- package/src/components/reui/data-grid/data-grid-table.tsx +639 -0
- package/src/components/reui/data-grid/data-grid.tsx +209 -0
- package/src/components/reui/date-selector.tsx +1330 -0
- package/src/components/reui/filters.tsx +1869 -0
- package/src/components/reui/frame.tsx +134 -0
- package/src/components/reui/index.ts +17 -0
- package/src/components/reui/timeline.tsx +219 -0
- package/src/components/search/Autocomplete.tsx +183 -0
- package/src/components/search/AutocompleteClient.tsx +293 -0
- package/src/components/search/GlobalSearch.tsx +187 -0
- package/src/components/section-drawer/deal-drawer-content.tsx +891 -0
- package/src/components/section-drawer/index.ts +19 -0
- package/src/components/section-drawer/section-drawer.css +665 -0
- package/src/components/section-drawer/section-drawer.tsx +467 -0
- package/src/components/sectioned-list-board/README.md +78 -0
- package/src/components/sectioned-list-board/board-card-content.tsx +340 -0
- package/src/components/sectioned-list-board/date-range-filter.tsx +249 -0
- package/src/components/sectioned-list-board/index.ts +19 -0
- package/src/components/sectioned-list-board/sectioned-list-board.css +564 -0
- package/src/components/sectioned-list-board/sectioned-list-board.tsx +731 -0
- package/src/components/sectioned-list-board/sortable-card.tsx +314 -0
- package/src/components/sectioned-list-board/sortable-section.tsx +319 -0
- package/src/components/sectioned-list-board/types.ts +216 -0
- package/src/components/sectioned-list-table/README.md +80 -0
- package/src/components/sectioned-list-table/index.ts +14 -0
- package/src/components/sectioned-list-table/sectioned-list-table.css +534 -0
- package/src/components/sectioned-list-table/sectioned-list-table.tsx +740 -0
- package/src/components/sectioned-list-table/sortable-column-header.tsx +120 -0
- package/src/components/sectioned-list-table/sortable-row.tsx +420 -0
- package/src/components/sectioned-list-table/sortable-section.tsx +251 -0
- package/src/components/sectioned-list-table/table-cell-content.tsx +129 -0
- package/src/components/sectioned-list-table/types.ts +120 -0
- package/src/components/sectioned-list-table/use-column-preferences.ts +103 -0
- package/src/components/ui/actions-dropdown.tsx +109 -0
- package/src/components/ui/assignee-selector.tsx +209 -0
- package/src/components/ui/avatar.tsx +107 -0
- package/src/components/ui/breadcrumb.tsx +109 -0
- package/src/components/ui/button-group.tsx +83 -0
- package/src/components/ui/button.tsx +64 -0
- package/src/components/ui/calendar.tsx +220 -0
- package/src/components/ui/card.tsx +92 -0
- package/src/components/ui/chart.tsx +376 -0
- package/src/components/ui/checkbox.tsx +30 -0
- package/src/components/ui/collapsible.tsx +33 -0
- package/src/components/ui/command.tsx +182 -0
- package/src/components/ui/context-menu.tsx +250 -0
- package/src/components/ui/create-button-group.tsx +128 -0
- package/src/components/ui/dialog.tsx +156 -0
- package/src/components/ui/drawer.tsx +133 -0
- package/src/components/ui/dropdown-menu.tsx +255 -0
- package/src/components/ui/empty.tsx +104 -0
- package/src/components/ui/field.tsx +248 -0
- package/src/components/ui/form.tsx +165 -0
- package/src/components/ui/index.ts +37 -0
- package/src/components/ui/input-group.tsx +168 -0
- package/src/components/ui/input.tsx +21 -0
- package/src/components/ui/kbd.tsx +28 -0
- package/src/components/ui/label.tsx +22 -0
- package/src/components/ui/navigation-menu.tsx +168 -0
- package/src/components/ui/page-header.tsx +80 -0
- package/src/components/ui/popover.tsx +87 -0
- package/src/components/ui/scroll-area.tsx +56 -0
- package/src/components/ui/select.tsx +190 -0
- package/src/components/ui/separator.tsx +26 -0
- package/src/components/ui/sheet.tsx +141 -0
- package/src/components/ui/sidebar.tsx +726 -0
- package/src/components/ui/skeleton.tsx +13 -0
- package/src/components/ui/sonner.tsx +38 -0
- package/src/components/ui/switch.tsx +33 -0
- package/src/components/ui/tabs.tsx +91 -0
- package/src/components/ui/textarea.tsx +18 -0
- package/src/components/ui/toggle-group.tsx +83 -0
- package/src/components/ui/toggle.tsx +45 -0
- package/src/components/ui/tooltip.tsx +57 -0
- package/src/hooks/use-copy-to-clipboard.ts +37 -0
- package/src/hooks/use-file-upload.ts +415 -0
- package/src/hooks/use-mobile.ts +19 -0
- package/src/index.ts +95 -0
- package/src/lib/utils.ts +6 -0
- package/src/styles.css +1859 -0
- package/src/urls.ts +83 -0
- package/src/vite.d.ts +22 -0
- package/src/vite.js +241 -0
- package/tsconfig.base.json +18 -0
- package/tsconfig.json +24 -0
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { cn } from "@/lib/utils";
|
|
3
|
+
|
|
4
|
+
export interface PageHeaderProps {
|
|
5
|
+
/** The title text for the header */
|
|
6
|
+
title: React.ReactNode;
|
|
7
|
+
/** Optional description text below the title */
|
|
8
|
+
description?: React.ReactNode;
|
|
9
|
+
/** Optional counter to display next to the title (e.g., "(42)") */
|
|
10
|
+
counter?: string | number;
|
|
11
|
+
/** Optional info link/icon to display next to the title */
|
|
12
|
+
info?: React.ReactNode;
|
|
13
|
+
/** Action buttons/elements to display on the right side */
|
|
14
|
+
actions?: React.ReactNode;
|
|
15
|
+
/** Header variant controlling the heading level and styling */
|
|
16
|
+
variant?: "h1" | "h2" | "h3";
|
|
17
|
+
/** Whether the header should be sticky at the top */
|
|
18
|
+
sticky?: boolean;
|
|
19
|
+
/** Additional class name for the header container */
|
|
20
|
+
className?: string;
|
|
21
|
+
/** Content to render below the header (e.g., breadcrumbs) */
|
|
22
|
+
children?: React.ReactNode;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const variantStyles = {
|
|
26
|
+
h1: "text-2xl font-semibold",
|
|
27
|
+
h2: "text-xl font-semibold",
|
|
28
|
+
h3: "text-lg font-medium",
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* PageHeader - A flexible page header component with title, description, and actions
|
|
33
|
+
*
|
|
34
|
+
* Features:
|
|
35
|
+
* - Multiple heading variants (h1, h2, h3)
|
|
36
|
+
* - Optional description below title
|
|
37
|
+
* - Counter display next to title
|
|
38
|
+
* - Info link/icon slot
|
|
39
|
+
* - Actions slot for buttons
|
|
40
|
+
* - Optional sticky positioning
|
|
41
|
+
*/
|
|
42
|
+
export function PageHeader({
|
|
43
|
+
title,
|
|
44
|
+
description,
|
|
45
|
+
counter,
|
|
46
|
+
info,
|
|
47
|
+
actions,
|
|
48
|
+
variant = "h1",
|
|
49
|
+
sticky = false,
|
|
50
|
+
className,
|
|
51
|
+
children,
|
|
52
|
+
}: PageHeaderProps) {
|
|
53
|
+
const titleContent = (
|
|
54
|
+
<>
|
|
55
|
+
{title}
|
|
56
|
+
{counter !== undefined && (
|
|
57
|
+
<span className="text-muted-foreground font-normal ml-2">
|
|
58
|
+
({counter})
|
|
59
|
+
</span>
|
|
60
|
+
)}
|
|
61
|
+
</>
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<div
|
|
66
|
+
className={cn(
|
|
67
|
+
"flex flex-col gap-2",
|
|
68
|
+
sticky && "sticky top-0 z-10 bg-background py-4 border-b border-border",
|
|
69
|
+
className
|
|
70
|
+
)}
|
|
71
|
+
>
|
|
72
|
+
{children}
|
|
73
|
+
<div className="flex items-start justify-between gap-4">
|
|
74
|
+
<div className="flex flex-col gap-1 min-w-0">
|
|
75
|
+
<div className="flex items-center gap-2">
|
|
76
|
+
{variant === "h1" && (
|
|
77
|
+
<h1 className={cn("text-foreground", variantStyles.h1)}>
|
|
78
|
+
{titleContent}
|
|
79
|
+
</h1>
|
|
80
|
+
)}
|
|
81
|
+
{variant === "h2" && (
|
|
82
|
+
<h2 className={cn("text-foreground", variantStyles.h2)}>
|
|
83
|
+
{titleContent}
|
|
84
|
+
</h2>
|
|
85
|
+
)}
|
|
86
|
+
{variant === "h3" && (
|
|
87
|
+
<h3 className={cn("text-foreground", variantStyles.h3)}>
|
|
88
|
+
{titleContent}
|
|
89
|
+
</h3>
|
|
90
|
+
)}
|
|
91
|
+
{info && (
|
|
92
|
+
<span className="text-muted-foreground">{info}</span>
|
|
93
|
+
)}
|
|
94
|
+
</div>
|
|
95
|
+
{description && (
|
|
96
|
+
<p className="text-sm text-muted-foreground">{description}</p>
|
|
97
|
+
)}
|
|
98
|
+
</div>
|
|
99
|
+
{actions && (
|
|
100
|
+
<div className="flex items-center gap-2 shrink-0">
|
|
101
|
+
{actions}
|
|
102
|
+
</div>
|
|
103
|
+
)}
|
|
104
|
+
</div>
|
|
105
|
+
</div>
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* PageHeaderTitle - Standalone title component for more custom layouts
|
|
111
|
+
*/
|
|
112
|
+
export function PageHeaderTitle({
|
|
113
|
+
children,
|
|
114
|
+
className,
|
|
115
|
+
variant = "h1",
|
|
116
|
+
}: {
|
|
117
|
+
children: React.ReactNode;
|
|
118
|
+
className?: string;
|
|
119
|
+
variant?: "h1" | "h2" | "h3";
|
|
120
|
+
}) {
|
|
121
|
+
const classes = cn("text-foreground", variantStyles[variant], className);
|
|
122
|
+
|
|
123
|
+
if (variant === "h1") return <h1 className={classes}>{children}</h1>;
|
|
124
|
+
if (variant === "h2") return <h2 className={classes}>{children}</h2>;
|
|
125
|
+
return <h3 className={classes}>{children}</h3>;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* PageHeaderDescription - Standalone description component
|
|
130
|
+
*/
|
|
131
|
+
export function PageHeaderDescription({
|
|
132
|
+
children,
|
|
133
|
+
className,
|
|
134
|
+
}: {
|
|
135
|
+
children: React.ReactNode;
|
|
136
|
+
className?: string;
|
|
137
|
+
}) {
|
|
138
|
+
return (
|
|
139
|
+
<p className={cn("text-sm text-muted-foreground", className)}>
|
|
140
|
+
{children}
|
|
141
|
+
</p>
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* PageHeaderActions - Container for action buttons
|
|
147
|
+
*/
|
|
148
|
+
export function PageHeaderActions({
|
|
149
|
+
children,
|
|
150
|
+
className,
|
|
151
|
+
}: {
|
|
152
|
+
children: React.ReactNode;
|
|
153
|
+
className?: string;
|
|
154
|
+
}) {
|
|
155
|
+
return (
|
|
156
|
+
<div className={cn("flex items-center gap-2", className)}>
|
|
157
|
+
{children}
|
|
158
|
+
</div>
|
|
159
|
+
);
|
|
160
|
+
}
|
|
@@ -0,0 +1,447 @@
|
|
|
1
|
+
# Data Table
|
|
2
|
+
|
|
3
|
+
A feature-rich data table component built on TanStack Table with support for sorting, pagination, row selection, column resizing, column reordering (drag & drop), and column pinning.
|
|
4
|
+
|
|
5
|
+
## Important: Button Placement
|
|
6
|
+
|
|
7
|
+
**Create/Import/Export buttons should be placed OUTSIDE the table, not in the toolbar.**
|
|
8
|
+
|
|
9
|
+
The table toolbar should only contain:
|
|
10
|
+
- Search/filter input
|
|
11
|
+
- Column visibility toggle
|
|
12
|
+
- Selection-based bulk actions (e.g., "Delete Selected", "Archive Selected")
|
|
13
|
+
|
|
14
|
+
Use `CreateButtonGroup` above the table for create/import/export actions:
|
|
15
|
+
|
|
16
|
+
```tsx
|
|
17
|
+
import {
|
|
18
|
+
DataGrid,
|
|
19
|
+
DataTableToolbar,
|
|
20
|
+
CreateButtonGroup,
|
|
21
|
+
} from "@platform/app-shell/components/data-table"
|
|
22
|
+
|
|
23
|
+
function MyPage() {
|
|
24
|
+
return (
|
|
25
|
+
<div className="space-y-4">
|
|
26
|
+
{/* Header with Create button OUTSIDE the table */}
|
|
27
|
+
<div className="flex items-center justify-between">
|
|
28
|
+
<h1 className="text-xl font-semibold">Items ({data.length})</h1>
|
|
29
|
+
<CreateButtonGroup
|
|
30
|
+
createLabel="Create Item"
|
|
31
|
+
onCreate={() => openCreateModal()}
|
|
32
|
+
onImport={() => openImportDialog()}
|
|
33
|
+
onExport={() => exportData()}
|
|
34
|
+
/>
|
|
35
|
+
</div>
|
|
36
|
+
|
|
37
|
+
{/* Table with only table-specific controls */}
|
|
38
|
+
<DataGrid table={table}>
|
|
39
|
+
<DataTableToolbar
|
|
40
|
+
table={table}
|
|
41
|
+
filterValue={filterText}
|
|
42
|
+
onFilterChange={setFilterText}
|
|
43
|
+
bulkActions={
|
|
44
|
+
selectedCount > 0 && (
|
|
45
|
+
<Button variant="destructive" size="sm">
|
|
46
|
+
Delete Selected ({selectedCount})
|
|
47
|
+
</Button>
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
/>
|
|
51
|
+
...
|
|
52
|
+
</DataGrid>
|
|
53
|
+
</div>
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Installation
|
|
59
|
+
|
|
60
|
+
The data table components are part of `@platform/app-shell`. Import from the data-table module:
|
|
61
|
+
|
|
62
|
+
```tsx
|
|
63
|
+
import {
|
|
64
|
+
DataGrid,
|
|
65
|
+
DataGridTableDnd,
|
|
66
|
+
DataGridPagination,
|
|
67
|
+
DataGridColumnHeader,
|
|
68
|
+
DataGridTableRowSelect,
|
|
69
|
+
DataGridTableRowSelectAll,
|
|
70
|
+
useReactTable,
|
|
71
|
+
getCoreRowModel,
|
|
72
|
+
getFilteredRowModel,
|
|
73
|
+
getPaginationRowModel,
|
|
74
|
+
getSortedRowModel,
|
|
75
|
+
} from "@platform/app-shell/components/data-table"
|
|
76
|
+
|
|
77
|
+
import type {
|
|
78
|
+
ColumnDef,
|
|
79
|
+
ColumnOrderState,
|
|
80
|
+
PaginationState,
|
|
81
|
+
SortingState,
|
|
82
|
+
RowSelectionState,
|
|
83
|
+
} from "@platform/app-shell/components/data-table"
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
For drag & drop functionality, also install:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
bun add @dnd-kit/core @dnd-kit/sortable
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Basic Usage
|
|
93
|
+
|
|
94
|
+
```tsx
|
|
95
|
+
import * as React from "react"
|
|
96
|
+
import {
|
|
97
|
+
DataGrid,
|
|
98
|
+
DataGridTableDnd,
|
|
99
|
+
DataGridPagination,
|
|
100
|
+
DataGridColumnHeader,
|
|
101
|
+
DataGridTableRowSelect,
|
|
102
|
+
DataGridTableRowSelectAll,
|
|
103
|
+
useReactTable,
|
|
104
|
+
getCoreRowModel,
|
|
105
|
+
getFilteredRowModel,
|
|
106
|
+
getPaginationRowModel,
|
|
107
|
+
getSortedRowModel,
|
|
108
|
+
} from "@platform/app-shell/components/data-table"
|
|
109
|
+
import type {
|
|
110
|
+
ColumnDef,
|
|
111
|
+
ColumnOrderState,
|
|
112
|
+
PaginationState,
|
|
113
|
+
SortingState,
|
|
114
|
+
RowSelectionState,
|
|
115
|
+
} from "@platform/app-shell/components/data-table"
|
|
116
|
+
import type { DragEndEvent } from "@dnd-kit/core"
|
|
117
|
+
import { arrayMove } from "@dnd-kit/sortable"
|
|
118
|
+
import { Card, CardFooter } from "@platform/app-shell/components/ui/card"
|
|
119
|
+
import { ScrollArea, ScrollBar } from "@platform/app-shell/components/ui/scroll-area"
|
|
120
|
+
|
|
121
|
+
interface DataRow {
|
|
122
|
+
id: string
|
|
123
|
+
name: string
|
|
124
|
+
email: string
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function MyTable({ data }: { data: DataRow[] }) {
|
|
128
|
+
// Table state
|
|
129
|
+
const [pagination, setPagination] = React.useState<PaginationState>({
|
|
130
|
+
pageIndex: 0,
|
|
131
|
+
pageSize: 10,
|
|
132
|
+
})
|
|
133
|
+
const [sorting, setSorting] = React.useState<SortingState>([])
|
|
134
|
+
const [rowSelection, setRowSelection] = React.useState<RowSelectionState>({})
|
|
135
|
+
const [columnOrder, setColumnOrder] = React.useState<ColumnOrderState>([
|
|
136
|
+
"select",
|
|
137
|
+
"name",
|
|
138
|
+
"email",
|
|
139
|
+
])
|
|
140
|
+
|
|
141
|
+
// Handle column drag and drop
|
|
142
|
+
const handleDragEnd = (event: DragEndEvent) => {
|
|
143
|
+
const { active, over } = event
|
|
144
|
+
if (active && over && active.id !== over.id) {
|
|
145
|
+
setColumnOrder((columnOrder) => {
|
|
146
|
+
const oldIndex = columnOrder.indexOf(active.id as string)
|
|
147
|
+
const newIndex = columnOrder.indexOf(over.id as string)
|
|
148
|
+
return arrayMove(columnOrder, oldIndex, newIndex)
|
|
149
|
+
})
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Define columns
|
|
154
|
+
const columns = React.useMemo<ColumnDef<DataRow>[]>(
|
|
155
|
+
() => [
|
|
156
|
+
{
|
|
157
|
+
id: "select",
|
|
158
|
+
header: () => <DataGridTableRowSelectAll />,
|
|
159
|
+
cell: ({ row }) => <DataGridTableRowSelect row={row} />,
|
|
160
|
+
size: 40,
|
|
161
|
+
enableSorting: false,
|
|
162
|
+
enableHiding: false,
|
|
163
|
+
enableResizing: false,
|
|
164
|
+
enableColumnOrdering: false, // Disable dragging for this column
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
accessorKey: "name",
|
|
168
|
+
id: "name",
|
|
169
|
+
header: ({ column }) => (
|
|
170
|
+
<DataGridColumnHeader title="Name" column={column} />
|
|
171
|
+
),
|
|
172
|
+
cell: ({ row }) => row.original.name,
|
|
173
|
+
size: 200,
|
|
174
|
+
enableSorting: true,
|
|
175
|
+
enableHiding: false,
|
|
176
|
+
enableResizing: true,
|
|
177
|
+
meta: {
|
|
178
|
+
headerTitle: "Name",
|
|
179
|
+
},
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
accessorKey: "email",
|
|
183
|
+
id: "email",
|
|
184
|
+
header: ({ column }) => (
|
|
185
|
+
<DataGridColumnHeader title="Email" column={column} />
|
|
186
|
+
),
|
|
187
|
+
cell: ({ row }) => row.original.email,
|
|
188
|
+
size: 250,
|
|
189
|
+
enableSorting: true,
|
|
190
|
+
enableHiding: true,
|
|
191
|
+
enableResizing: true,
|
|
192
|
+
meta: {
|
|
193
|
+
headerTitle: "Email",
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
],
|
|
197
|
+
[]
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
// Create table instance
|
|
201
|
+
const table = useReactTable({
|
|
202
|
+
columns,
|
|
203
|
+
data,
|
|
204
|
+
pageCount: Math.ceil(data.length / pagination.pageSize),
|
|
205
|
+
getRowId: (row) => row.id,
|
|
206
|
+
state: {
|
|
207
|
+
pagination,
|
|
208
|
+
sorting,
|
|
209
|
+
rowSelection,
|
|
210
|
+
columnOrder,
|
|
211
|
+
},
|
|
212
|
+
enableRowSelection: true,
|
|
213
|
+
columnResizeMode: "onChange",
|
|
214
|
+
onPaginationChange: setPagination,
|
|
215
|
+
onSortingChange: setSorting,
|
|
216
|
+
onRowSelectionChange: setRowSelection,
|
|
217
|
+
onColumnOrderChange: setColumnOrder,
|
|
218
|
+
getCoreRowModel: getCoreRowModel(),
|
|
219
|
+
getFilteredRowModel: getFilteredRowModel(),
|
|
220
|
+
getPaginationRowModel: getPaginationRowModel(),
|
|
221
|
+
getSortedRowModel: getSortedRowModel(),
|
|
222
|
+
})
|
|
223
|
+
|
|
224
|
+
return (
|
|
225
|
+
<DataGrid
|
|
226
|
+
table={table}
|
|
227
|
+
recordCount={data.length}
|
|
228
|
+
tableLayout={{
|
|
229
|
+
columnsResizable: true,
|
|
230
|
+
columnsMovable: true,
|
|
231
|
+
columnsDraggable: true,
|
|
232
|
+
columnsPinnable: true,
|
|
233
|
+
columnsVisibility: true,
|
|
234
|
+
}}
|
|
235
|
+
>
|
|
236
|
+
<Card className="w-full gap-3 py-3.5">
|
|
237
|
+
{/* Table */}
|
|
238
|
+
<div className="w-full border-y">
|
|
239
|
+
<ScrollArea>
|
|
240
|
+
<DataGridTableDnd handleDragEnd={handleDragEnd} />
|
|
241
|
+
<ScrollBar orientation="horizontal" />
|
|
242
|
+
</ScrollArea>
|
|
243
|
+
</div>
|
|
244
|
+
|
|
245
|
+
{/* Pagination */}
|
|
246
|
+
<CardFooter className="border-none bg-transparent px-3.5 py-0">
|
|
247
|
+
<DataGridPagination sizes={[10, 20, 50]} />
|
|
248
|
+
</CardFooter>
|
|
249
|
+
</Card>
|
|
250
|
+
</DataGrid>
|
|
251
|
+
)
|
|
252
|
+
}
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
## Components
|
|
256
|
+
|
|
257
|
+
### DataGrid
|
|
258
|
+
|
|
259
|
+
The main wrapper component that provides context to all child components.
|
|
260
|
+
|
|
261
|
+
```tsx
|
|
262
|
+
<DataGrid
|
|
263
|
+
table={table} // TanStack table instance
|
|
264
|
+
recordCount={data.length} // Total number of records
|
|
265
|
+
tableLayout={{
|
|
266
|
+
dense: false, // Compact row height
|
|
267
|
+
columnsResizable: true, // Enable column resizing
|
|
268
|
+
columnsMovable: true, // Enable column reordering via menu
|
|
269
|
+
columnsDraggable: true, // Enable drag & drop column reordering
|
|
270
|
+
columnsPinnable: true, // Enable column pinning
|
|
271
|
+
columnsVisibility: true, // Enable column visibility toggle
|
|
272
|
+
}}
|
|
273
|
+
>
|
|
274
|
+
{children}
|
|
275
|
+
</DataGrid>
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
### DataGridTableDnd
|
|
279
|
+
|
|
280
|
+
The table component with drag & drop support for column reordering.
|
|
281
|
+
|
|
282
|
+
```tsx
|
|
283
|
+
<DataGridTableDnd handleDragEnd={handleDragEnd} />
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
### DataGridColumnHeader
|
|
287
|
+
|
|
288
|
+
Column header with sorting controls and dropdown menu for column actions.
|
|
289
|
+
|
|
290
|
+
```tsx
|
|
291
|
+
<DataGridColumnHeader
|
|
292
|
+
title="Column Name"
|
|
293
|
+
column={column}
|
|
294
|
+
icon={<IconComponent />} // Optional icon
|
|
295
|
+
pinnable={true} // Enable pinning option in menu
|
|
296
|
+
visibility={true} // Show column visibility in menu
|
|
297
|
+
filter={<FilterComponent />} // Optional filter component
|
|
298
|
+
/>
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
### DataGridTableRowSelect / DataGridTableRowSelectAll
|
|
302
|
+
|
|
303
|
+
Row selection checkboxes.
|
|
304
|
+
|
|
305
|
+
```tsx
|
|
306
|
+
// In header
|
|
307
|
+
header: () => <DataGridTableRowSelectAll />
|
|
308
|
+
|
|
309
|
+
// In cell
|
|
310
|
+
cell: ({ row }) => <DataGridTableRowSelect row={row} />
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
### DataGridPagination
|
|
314
|
+
|
|
315
|
+
Pagination controls with page size selector.
|
|
316
|
+
|
|
317
|
+
```tsx
|
|
318
|
+
<DataGridPagination sizes={[10, 20, 50]} />
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
### CreateButtonGroup
|
|
322
|
+
|
|
323
|
+
Button group for create/import/export actions. **Place this ABOVE the table, not in the toolbar.**
|
|
324
|
+
|
|
325
|
+
```tsx
|
|
326
|
+
<CreateButtonGroup
|
|
327
|
+
createLabel="Create Item" // Primary button label
|
|
328
|
+
onCreate={() => {}} // Primary action handler
|
|
329
|
+
importLabel="Import" // Optional, defaults to "Import"
|
|
330
|
+
onImport={() => {}} // Import action handler
|
|
331
|
+
exportLabel="Export" // Optional, defaults to "Export"
|
|
332
|
+
onExport={() => {}} // Export action handler
|
|
333
|
+
showDropdown={true} // Show dropdown, defaults to true
|
|
334
|
+
additionalItems={[ // Optional extra dropdown items
|
|
335
|
+
{
|
|
336
|
+
id: "bulk-create",
|
|
337
|
+
label: "Bulk Create",
|
|
338
|
+
onClick: () => {},
|
|
339
|
+
},
|
|
340
|
+
]}
|
|
341
|
+
/>
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
### DataTableToolbar
|
|
345
|
+
|
|
346
|
+
Toolbar for table-specific controls. The `bulkActions` prop is for selection-based actions only.
|
|
347
|
+
|
|
348
|
+
```tsx
|
|
349
|
+
<DataTableToolbar
|
|
350
|
+
table={table}
|
|
351
|
+
filterValue={filterText}
|
|
352
|
+
onFilterChange={setFilterText}
|
|
353
|
+
filterPlaceholder="Search items..."
|
|
354
|
+
showColumnVisibility={true}
|
|
355
|
+
selectedCount={selectedCount}
|
|
356
|
+
totalCount={data.length}
|
|
357
|
+
bulkActions={
|
|
358
|
+
selectedCount > 0 && (
|
|
359
|
+
<Button variant="destructive" size="sm">
|
|
360
|
+
Delete Selected ({selectedCount})
|
|
361
|
+
</Button>
|
|
362
|
+
)
|
|
363
|
+
}
|
|
364
|
+
/>
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
## Column Definition Options
|
|
368
|
+
|
|
369
|
+
```tsx
|
|
370
|
+
{
|
|
371
|
+
id: "columnId",
|
|
372
|
+
accessorKey: "dataField",
|
|
373
|
+
header: ({ column }) => <DataGridColumnHeader title="Title" column={column} />,
|
|
374
|
+
cell: ({ row }) => row.original.field,
|
|
375
|
+
size: 200, // Column width in pixels
|
|
376
|
+
enableSorting: true, // Allow sorting
|
|
377
|
+
enableHiding: true, // Allow hiding via visibility toggle
|
|
378
|
+
enableResizing: true, // Allow resizing
|
|
379
|
+
enableColumnOrdering: true, // Allow drag reordering (default: true)
|
|
380
|
+
meta: {
|
|
381
|
+
headerTitle: "Display Name", // Used in column visibility menu
|
|
382
|
+
cellClassName: "custom-class",
|
|
383
|
+
headerClassName: "custom-class",
|
|
384
|
+
},
|
|
385
|
+
}
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
## Table Layout Options
|
|
389
|
+
|
|
390
|
+
| Option | Type | Default | Description |
|
|
391
|
+
|--------|------|---------|-------------|
|
|
392
|
+
| `dense` | boolean | false | Use compact row height |
|
|
393
|
+
| `columnsResizable` | boolean | false | Enable column resizing |
|
|
394
|
+
| `columnsMovable` | boolean | false | Enable move left/right in column menu |
|
|
395
|
+
| `columnsDraggable` | boolean | false | Enable drag & drop column reordering |
|
|
396
|
+
| `columnsPinnable` | boolean | false | Enable column pinning (left/right) |
|
|
397
|
+
| `columnsVisibility` | boolean | false | Enable column visibility toggle |
|
|
398
|
+
| `rowBorder` | boolean | true | Show row borders |
|
|
399
|
+
| `cellBorder` | boolean | false | Show cell borders |
|
|
400
|
+
| `stripped` | boolean | false | Alternating row colors |
|
|
401
|
+
| `headerSticky` | boolean | false | Sticky header on scroll |
|
|
402
|
+
|
|
403
|
+
## Disabling Drag on Specific Columns
|
|
404
|
+
|
|
405
|
+
To prevent a column from being draggable (e.g., selection checkbox column):
|
|
406
|
+
|
|
407
|
+
```tsx
|
|
408
|
+
{
|
|
409
|
+
id: "select",
|
|
410
|
+
enableColumnOrdering: false, // This column cannot be dragged
|
|
411
|
+
// ...
|
|
412
|
+
}
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
## Selection Counter in Header
|
|
416
|
+
|
|
417
|
+
Display selection count in the table header instead of a separate line:
|
|
418
|
+
|
|
419
|
+
```tsx
|
|
420
|
+
const selectedCount = Object.keys(rowSelection).length
|
|
421
|
+
|
|
422
|
+
<div className="text-xl font-semibold">
|
|
423
|
+
Items
|
|
424
|
+
<span className="text-muted-foreground font-normal">
|
|
425
|
+
{" "}({selectedCount > 0 ? `${selectedCount}/${data.length}` : data.length})
|
|
426
|
+
</span>
|
|
427
|
+
</div>
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
This shows:
|
|
431
|
+
- `Items (10)` when no rows selected
|
|
432
|
+
- `Items (3/10)` when 3 of 10 rows selected
|
|
433
|
+
|
|
434
|
+
## Styling
|
|
435
|
+
|
|
436
|
+
The table inherits from the app's theme. Common customizations:
|
|
437
|
+
|
|
438
|
+
```tsx
|
|
439
|
+
// Italic placeholder in search input
|
|
440
|
+
<Input
|
|
441
|
+
placeholder="Search..."
|
|
442
|
+
className="placeholder:italic"
|
|
443
|
+
/>
|
|
444
|
+
|
|
445
|
+
// Custom badge sizes in cells
|
|
446
|
+
<Badge variant="outline">{tag}</Badge> // Uses default size (text-xs)
|
|
447
|
+
```
|