@kala-ui/react-app 0.1.0-beta.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/CHANGELOG.md +13 -0
- package/LICENSE +21 -0
- package/README.md +36 -0
- package/dist/components/app-shell/app-shell.d.ts +54 -0
- package/dist/components/app-shell/app-shell.js +91 -0
- package/dist/components/app-shell/index.d.ts +1 -0
- package/dist/components/app-shell/index.js +2 -0
- package/dist/components/charts/area-chart.d.ts +6 -0
- package/dist/components/charts/area-chart.js +172 -0
- package/dist/components/charts/bar-chart.d.ts +6 -0
- package/dist/components/charts/bar-chart.js +153 -0
- package/dist/components/charts/chart-skeleton.d.ts +36 -0
- package/dist/components/charts/chart-skeleton.js +13 -0
- package/dist/components/charts/chart.d.ts +25 -0
- package/dist/components/charts/chart.js +48 -0
- package/dist/components/charts/chart.types.d.ts +196 -0
- package/dist/components/charts/chart.types.js +6 -0
- package/dist/components/charts/donut-chart.d.ts +7 -0
- package/dist/components/charts/donut-chart.js +146 -0
- package/dist/components/charts/index.d.ts +14 -0
- package/dist/components/charts/index.js +15 -0
- package/dist/components/charts/line-chart.d.ts +6 -0
- package/dist/components/charts/line-chart.js +158 -0
- package/dist/components/charts/radial-bar-chart.d.ts +6 -0
- package/dist/components/charts/radial-bar-chart.js +115 -0
- package/dist/components/charts/theme-utils.d.ts +77 -0
- package/dist/components/charts/theme-utils.js +243 -0
- package/dist/components/charts/use-theme-aware-chart.d.ts +12 -0
- package/dist/components/charts/use-theme-aware-chart.js +68 -0
- package/dist/components/charts/utils.d.ts +56 -0
- package/dist/components/charts/utils.js +135 -0
- package/dist/components/data-table/column-filters.d.ts +14 -0
- package/dist/components/data-table/column-filters.js +51 -0
- package/dist/components/data-table/column-header-filter.d.ts +14 -0
- package/dist/components/data-table/column-header-filter.js +75 -0
- package/dist/components/data-table/data-table-body-rows.d.ts +23 -0
- package/dist/components/data-table/data-table-body-rows.js +43 -0
- package/dist/components/data-table/data-table-filter-chips.d.ts +14 -0
- package/dist/components/data-table/data-table-filter-chips.js +42 -0
- package/dist/components/data-table/data-table-header-row.d.ts +21 -0
- package/dist/components/data-table/data-table-header-row.js +35 -0
- package/dist/components/data-table/data-table-skeleton.d.ts +49 -0
- package/dist/components/data-table/data-table-skeleton.js +32 -0
- package/dist/components/data-table/data-table-toolbar.d.ts +18 -0
- package/dist/components/data-table/data-table-toolbar.js +25 -0
- package/dist/components/data-table/data-table.d.ts +23 -0
- package/dist/components/data-table/data-table.js +250 -0
- package/dist/components/data-table/data-table.types.d.ts +305 -0
- package/dist/components/data-table/data-table.types.js +6 -0
- package/dist/components/data-table/index.d.ts +6 -0
- package/dist/components/data-table/index.js +6 -0
- package/dist/components/data-table/pagination-nav.d.ts +15 -0
- package/dist/components/data-table/pagination-nav.js +23 -0
- package/dist/components/data-table/useTableState.d.ts +29 -0
- package/dist/components/data-table/useTableState.js +312 -0
- package/dist/components/dnd/dnd.d.ts +118 -0
- package/dist/components/dnd/dnd.js +128 -0
- package/dist/components/dnd/index.d.ts +1 -0
- package/dist/components/dnd/index.js +2 -0
- package/dist/components/footer/footer.d.ts +37 -0
- package/dist/components/footer/footer.js +8 -0
- package/dist/components/footer/index.d.ts +2 -0
- package/dist/components/footer/index.js +2 -0
- package/dist/components/header/header-skeleton.d.ts +54 -0
- package/dist/components/header/header-skeleton.js +23 -0
- package/dist/components/header/header.d.ts +40 -0
- package/dist/components/header/header.js +77 -0
- package/dist/components/header/header.types.d.ts +39 -0
- package/dist/components/header/header.types.js +2 -0
- package/dist/components/header/index.d.ts +4 -0
- package/dist/components/header/index.js +3 -0
- package/dist/components/metric-card/index.d.ts +2 -0
- package/dist/components/metric-card/index.js +3 -0
- package/dist/components/metric-card/metric-card-skeleton.d.ts +32 -0
- package/dist/components/metric-card/metric-card-skeleton.js +29 -0
- package/dist/components/metric-card/metric-card.d.ts +16 -0
- package/dist/components/metric-card/metric-card.js +55 -0
- package/dist/components/metric-card/metric-card.types.d.ts +14 -0
- package/dist/components/metric-card/metric-card.types.js +2 -0
- package/dist/components/nav-link/index.d.ts +1 -0
- package/dist/components/nav-link/index.js +2 -0
- package/dist/components/nav-link/nav-link.d.ts +26 -0
- package/dist/components/nav-link/nav-link.js +23 -0
- package/dist/components/navigation/index.d.ts +4 -0
- package/dist/components/navigation/index.js +3 -0
- package/dist/components/navigation/navigation-skeleton.d.ts +54 -0
- package/dist/components/navigation/navigation-skeleton.js +23 -0
- package/dist/components/navigation/navigation.d.ts +26 -0
- package/dist/components/navigation/navigation.js +44 -0
- package/dist/components/session-card/index.d.ts +2 -0
- package/dist/components/session-card/index.js +3 -0
- package/dist/components/session-card/session-card-skeleton.d.ts +31 -0
- package/dist/components/session-card/session-card-skeleton.js +28 -0
- package/dist/components/session-card/session-card.d.ts +27 -0
- package/dist/components/session-card/session-card.js +67 -0
- package/dist/components/sidebar/index.d.ts +3 -0
- package/dist/components/sidebar/index.js +3 -0
- package/dist/components/sidebar/sidebar-skeleton.d.ts +54 -0
- package/dist/components/sidebar/sidebar-skeleton.js +23 -0
- package/dist/components/sidebar/sidebar.d.ts +46 -0
- package/dist/components/sidebar/sidebar.js +69 -0
- package/dist/components/social-login-button/index.d.ts +1 -0
- package/dist/components/social-login-button/index.js +2 -0
- package/dist/components/social-login-button/social-login-button.d.ts +20 -0
- package/dist/components/social-login-button/social-login-button.js +57 -0
- package/dist/components/social-login-buttons/index.d.ts +1 -0
- package/dist/components/social-login-buttons/index.js +2 -0
- package/dist/components/social-login-buttons/social-login-buttons.d.ts +34 -0
- package/dist/components/social-login-buttons/social-login-buttons.js +18 -0
- package/dist/components/sparkline-chart/index.d.ts +2 -0
- package/dist/components/sparkline-chart/index.js +2 -0
- package/dist/components/sparkline-chart/sparkline-chart.d.ts +51 -0
- package/dist/components/sparkline-chart/sparkline-chart.js +134 -0
- package/dist/components/user-menu-dropdown/index.d.ts +1 -0
- package/dist/components/user-menu-dropdown/index.js +2 -0
- package/dist/components/user-menu-dropdown/user-menu-dropdown.d.ts +31 -0
- package/dist/components/user-menu-dropdown/user-menu-dropdown.js +27 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +19 -0
- package/dist/lib/active-path.d.ts +5 -0
- package/dist/lib/active-path.js +12 -0
- package/dist/styles/app.css +2125 -0
- package/package.json +164 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DataTable Component
|
|
3
|
+
* Fully-featured, accessible data table with sorting, filtering, search, and pagination
|
|
4
|
+
*
|
|
5
|
+
* Features:
|
|
6
|
+
* - Sorting (single column)
|
|
7
|
+
* - Filtering (multiple filters)
|
|
8
|
+
* - Global search
|
|
9
|
+
* - Pagination with configurable page sizes
|
|
10
|
+
* - Row selection (optional)
|
|
11
|
+
* - Bulk actions (optional)
|
|
12
|
+
* - Loading states
|
|
13
|
+
* - Empty states
|
|
14
|
+
* - Full keyboard navigation
|
|
15
|
+
* - WCAG 2.1 AA compliant
|
|
16
|
+
* - React 19 compatible
|
|
17
|
+
*/
|
|
18
|
+
import type { DataTableProps } from "./data-table.types";
|
|
19
|
+
/**
|
|
20
|
+
* DataTable Component
|
|
21
|
+
*/
|
|
22
|
+
export declare function DataTable<TData>({ data, columns, searchable, pagination, defaultSort, defaultFilters, filterableColumns, emptyState, isLoading, loadingConfig, skeletonConfig, skeleton, selection, bulkActions, onRowClick, getRowClassName, getRowAttributes, stickyHeader, compact, striped, bordered, hoverable, caption, footer, className, ariaLabel, stickyFooter, headersBelow, onSortChange, onFilterChange, toolbarContent, }: DataTableProps<TData>): import("react").JSX.Element;
|
|
23
|
+
export { DataTableSkeleton } from "./data-table-skeleton";
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
/**
|
|
3
|
+
* DataTable Component
|
|
4
|
+
* Fully-featured, accessible data table with sorting, filtering, search, and pagination
|
|
5
|
+
*
|
|
6
|
+
* Features:
|
|
7
|
+
* - Sorting (single column)
|
|
8
|
+
* - Filtering (multiple filters)
|
|
9
|
+
* - Global search
|
|
10
|
+
* - Pagination with configurable page sizes
|
|
11
|
+
* - Row selection (optional)
|
|
12
|
+
* - Bulk actions (optional)
|
|
13
|
+
* - Loading states
|
|
14
|
+
* - Empty states
|
|
15
|
+
* - Full keyboard navigation
|
|
16
|
+
* - WCAG 2.1 AA compliant
|
|
17
|
+
* - React 19 compatible
|
|
18
|
+
*/
|
|
19
|
+
"use client";
|
|
20
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
21
|
+
import { Box } from "@kala-ui/react/box";
|
|
22
|
+
import { Button } from "@kala-ui/react/button";
|
|
23
|
+
import { Checkbox } from "@kala-ui/react/checkbox";
|
|
24
|
+
import { EmptyState } from "@kala-ui/react/empty-state";
|
|
25
|
+
import { Flex } from "@kala-ui/react/flex";
|
|
26
|
+
import { cn } from "@kala-ui/react/lib/utils";
|
|
27
|
+
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@kala-ui/react/select";
|
|
28
|
+
import { Stack } from "@kala-ui/react/stack";
|
|
29
|
+
import { TableCell, TableFooter, TableHead, TableHeader, TableRow, } from "@kala-ui/react/table";
|
|
30
|
+
import { Text } from "@kala-ui/react/text";
|
|
31
|
+
import { useDisclosure } from "@kala-ui/react-hooks";
|
|
32
|
+
import { ArrowDown, ArrowUp } from "lucide-react";
|
|
33
|
+
import { useCallback, useMemo, useRef, useState } from "react";
|
|
34
|
+
import { DataTableBodyRows } from "./data-table-body-rows.js";
|
|
35
|
+
import { DataTableFilterChips } from "./data-table-filter-chips.js";
|
|
36
|
+
import { DataTableHeaderRow } from "./data-table-header-row.js";
|
|
37
|
+
import { DataTableSkeleton } from "./data-table-skeleton.js";
|
|
38
|
+
import { DataTableToolbar } from "./data-table-toolbar.js";
|
|
39
|
+
import { PaginationNav } from "./pagination-nav.js";
|
|
40
|
+
import { useTableState } from "./useTableState.js";
|
|
41
|
+
/**
|
|
42
|
+
* DataTable Component
|
|
43
|
+
*/
|
|
44
|
+
export function DataTable({ data, columns, searchable, pagination, defaultSort, defaultFilters, filterableColumns, emptyState, isLoading = false, loadingConfig, skeletonConfig, skeleton, selection, bulkActions, onRowClick, getRowClassName, getRowAttributes, stickyHeader = true, compact = false, striped = false, bordered = false, hoverable = true, caption, footer, className, ariaLabel, stickyFooter = false, headersBelow = false, onSortChange, onFilterChange, toolbarContent, }) {
|
|
45
|
+
// Determine page size
|
|
46
|
+
const pageSize = useMemo(() => {
|
|
47
|
+
if (!pagination)
|
|
48
|
+
return data.length;
|
|
49
|
+
if (typeof pagination === "boolean")
|
|
50
|
+
return 10;
|
|
51
|
+
return pagination.pageSize;
|
|
52
|
+
}, [pagination, data.length]);
|
|
53
|
+
// Extract callbacks from configs
|
|
54
|
+
const paginationCallback = useMemo(() => {
|
|
55
|
+
if (!pagination || typeof pagination === "boolean")
|
|
56
|
+
return undefined;
|
|
57
|
+
return pagination.onPageChange;
|
|
58
|
+
}, [pagination]);
|
|
59
|
+
const totalCount = useMemo(() => {
|
|
60
|
+
if (!pagination || typeof pagination === "boolean")
|
|
61
|
+
return undefined;
|
|
62
|
+
return pagination.total;
|
|
63
|
+
}, [pagination]);
|
|
64
|
+
const searchCallback = useMemo(() => {
|
|
65
|
+
if (!searchable || typeof searchable === "boolean")
|
|
66
|
+
return undefined;
|
|
67
|
+
return searchable.onChange;
|
|
68
|
+
}, [searchable]);
|
|
69
|
+
// Table state management
|
|
70
|
+
const controlledPage = useMemo(() => {
|
|
71
|
+
if (!pagination || typeof pagination === "boolean")
|
|
72
|
+
return undefined;
|
|
73
|
+
return pagination.page;
|
|
74
|
+
}, [pagination]);
|
|
75
|
+
const tableState = useTableState({
|
|
76
|
+
data,
|
|
77
|
+
columns,
|
|
78
|
+
searchConfig: searchable,
|
|
79
|
+
pageSize,
|
|
80
|
+
total: totalCount,
|
|
81
|
+
page: controlledPage,
|
|
82
|
+
defaultSort,
|
|
83
|
+
defaultFilters,
|
|
84
|
+
onSortChange,
|
|
85
|
+
onFilterChange,
|
|
86
|
+
onSearchChange: searchCallback,
|
|
87
|
+
onPaginationChange: paginationCallback,
|
|
88
|
+
});
|
|
89
|
+
const { pageData, sortConfig, toggleSort, searchQuery, setSearchQuery, currentPage, setCurrentPage, pageSize: currentPageSize, setPageSize, totalPages, hasNextPage, hasPreviousPage, nextPage, previousPage, processedData, filterConfigs, setFilter, removeFilter, clearFilters, } = tableState;
|
|
90
|
+
// Determine display data: use full data for server-side pagination, pageData for client-side
|
|
91
|
+
const displayData = paginationCallback ? data : pageData;
|
|
92
|
+
// Row selection state. When the caller controls selection via
|
|
93
|
+
// `selection.selectedIds`, mirror prop changes into state during render
|
|
94
|
+
// (React's "adjust state on prop change" pattern) — useState only seeds the
|
|
95
|
+
// initial value, so bulk-delete flows that update the prop would otherwise
|
|
96
|
+
// keep rendering a stale selection.
|
|
97
|
+
const [selectedIds, setSelectedIds] = useState(selection?.selectedIds ?? new Set());
|
|
98
|
+
const controlledSelectedIds = selection?.selectedIds;
|
|
99
|
+
const prevControlledSelectedIds = useRef(controlledSelectedIds);
|
|
100
|
+
if (controlledSelectedIds !== undefined &&
|
|
101
|
+
controlledSelectedIds !== prevControlledSelectedIds.current) {
|
|
102
|
+
prevControlledSelectedIds.current = controlledSelectedIds;
|
|
103
|
+
setSelectedIds(controlledSelectedIds);
|
|
104
|
+
}
|
|
105
|
+
// Scroll container ref for scroll-to-top/bottom buttons
|
|
106
|
+
const scrollContainerRef = useRef(null);
|
|
107
|
+
const [showScrollButtons, { set: setShowScrollButtons }] = useDisclosure(false);
|
|
108
|
+
const handleScroll = useCallback(() => {
|
|
109
|
+
if (scrollContainerRef.current) {
|
|
110
|
+
const { scrollTop, scrollHeight, clientHeight } = scrollContainerRef.current;
|
|
111
|
+
setShowScrollButtons(scrollHeight > clientHeight && scrollTop > 100);
|
|
112
|
+
}
|
|
113
|
+
}, [setShowScrollButtons]);
|
|
114
|
+
const scrollToTop = useCallback(() => {
|
|
115
|
+
scrollContainerRef.current?.scrollTo({ top: 0, behavior: "smooth" });
|
|
116
|
+
}, []);
|
|
117
|
+
const scrollToBottom = useCallback(() => {
|
|
118
|
+
if (scrollContainerRef.current) {
|
|
119
|
+
scrollContainerRef.current.scrollTo({
|
|
120
|
+
top: scrollContainerRef.current.scrollHeight,
|
|
121
|
+
behavior: "smooth",
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
}, []);
|
|
125
|
+
// Handle select all
|
|
126
|
+
const isAllSelected = useMemo(() => {
|
|
127
|
+
if (!selection || displayData.length === 0)
|
|
128
|
+
return false;
|
|
129
|
+
const selectableRows = displayData.filter((row) => !selection.isRowSelectable || selection.isRowSelectable(row));
|
|
130
|
+
return selectableRows.every((row) => selectedIds.has(selection.getRowId(row)));
|
|
131
|
+
}, [selection, displayData, selectedIds]);
|
|
132
|
+
const isSomeSelected = useMemo(() => {
|
|
133
|
+
if (!selection || displayData.length === 0)
|
|
134
|
+
return false;
|
|
135
|
+
const selectableRows = displayData.filter((row) => !selection.isRowSelectable || selection.isRowSelectable(row));
|
|
136
|
+
return (selectableRows.some((row) => selectedIds.has(selection.getRowId(row))) &&
|
|
137
|
+
!isAllSelected);
|
|
138
|
+
}, [selection, displayData, selectedIds, isAllSelected]);
|
|
139
|
+
const handleSelectAll = useCallback(() => {
|
|
140
|
+
if (!selection)
|
|
141
|
+
return;
|
|
142
|
+
const newSelectedIds = new Set(selectedIds);
|
|
143
|
+
const selectableRows = displayData.filter((row) => !selection.isRowSelectable || selection.isRowSelectable(row));
|
|
144
|
+
if (isAllSelected) {
|
|
145
|
+
selectableRows.forEach((row) => {
|
|
146
|
+
newSelectedIds.delete(selection.getRowId(row));
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
selectableRows.forEach((row) => {
|
|
151
|
+
newSelectedIds.add(selection.getRowId(row));
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
setSelectedIds(newSelectedIds);
|
|
155
|
+
selection.onSelectionChange(newSelectedIds);
|
|
156
|
+
}, [selection, displayData, selectedIds, isAllSelected]);
|
|
157
|
+
const handleSelectRow = useCallback((row) => {
|
|
158
|
+
if (!selection)
|
|
159
|
+
return;
|
|
160
|
+
const rowId = selection.getRowId(row);
|
|
161
|
+
const newSelectedIds = new Set(selectedIds);
|
|
162
|
+
if (newSelectedIds.has(rowId)) {
|
|
163
|
+
newSelectedIds.delete(rowId);
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
newSelectedIds.add(rowId);
|
|
167
|
+
}
|
|
168
|
+
setSelectedIds(newSelectedIds);
|
|
169
|
+
selection.onSelectionChange(newSelectedIds);
|
|
170
|
+
}, [selection, selectedIds]);
|
|
171
|
+
// Get selected rows for bulk actions
|
|
172
|
+
const selectedRows = useMemo(() => {
|
|
173
|
+
if (!selection)
|
|
174
|
+
return [];
|
|
175
|
+
return data.filter((row) => selectedIds.has(selection.getRowId(row)));
|
|
176
|
+
}, [data, selectedIds, selection]);
|
|
177
|
+
// Search config
|
|
178
|
+
const searchConfig = useMemo(() => {
|
|
179
|
+
if (!searchable)
|
|
180
|
+
return null;
|
|
181
|
+
if (typeof searchable === "boolean") {
|
|
182
|
+
return {
|
|
183
|
+
placeholder: "Search...",
|
|
184
|
+
ariaLabel: "Search table",
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
return {
|
|
188
|
+
placeholder: searchable.placeholder ?? "Search...",
|
|
189
|
+
ariaLabel: searchable.ariaLabel ?? "Search table",
|
|
190
|
+
...searchable,
|
|
191
|
+
};
|
|
192
|
+
}, [searchable]);
|
|
193
|
+
// Pagination config
|
|
194
|
+
const paginationConfig = useMemo(() => {
|
|
195
|
+
if (!pagination)
|
|
196
|
+
return null;
|
|
197
|
+
if (typeof pagination === "boolean") {
|
|
198
|
+
return {
|
|
199
|
+
page: currentPage,
|
|
200
|
+
pageSize: currentPageSize,
|
|
201
|
+
total: processedData.length,
|
|
202
|
+
pageSizeOptions: [10, 20, 50, 100],
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
return {
|
|
206
|
+
...pagination,
|
|
207
|
+
page: currentPage,
|
|
208
|
+
pageSize: currentPageSize,
|
|
209
|
+
total: pagination.total ?? processedData.length,
|
|
210
|
+
};
|
|
211
|
+
}, [pagination, currentPage, currentPageSize, processedData.length]);
|
|
212
|
+
// Render loading state
|
|
213
|
+
if (isLoading) {
|
|
214
|
+
if (skeleton) {
|
|
215
|
+
return (_jsx(Stack, { gap: 4, className: className, children: skeleton }));
|
|
216
|
+
}
|
|
217
|
+
const showSearch = !!searchConfig;
|
|
218
|
+
const showFilters = !!filterableColumns;
|
|
219
|
+
const showPagination = !!paginationConfig;
|
|
220
|
+
const showSelection = !!selection?.enabled;
|
|
221
|
+
const showBulkActions = !!bulkActions;
|
|
222
|
+
const skeletonRows = skeletonConfig?.rows ?? loadingConfig?.rows ?? 5;
|
|
223
|
+
return (_jsx(Stack, { gap: 4, className: className, children: _jsx(DataTableSkeleton, { rows: skeletonRows, columns: columns, showSearch: showSearch, showFilters: showFilters, showPagination: showPagination, showSelection: showSelection, showBulkActions: showBulkActions, stickyHeader: stickyHeader, stickyFooter: stickyFooter }) }));
|
|
224
|
+
}
|
|
225
|
+
// Compute empty state config
|
|
226
|
+
const emptyConfig = {
|
|
227
|
+
title: searchQuery ? "No results found" : "No data available",
|
|
228
|
+
description: searchQuery
|
|
229
|
+
? `No results found for "${searchQuery}". Try adjusting your search.`
|
|
230
|
+
: "There is no data to display at the moment.",
|
|
231
|
+
...emptyState,
|
|
232
|
+
};
|
|
233
|
+
return (_jsxs(Stack, { className: cn(stickyFooter ? "h-full min-h-0" : "", className), "aria-label": ariaLabel, role: ariaLabel ? "region" : undefined, gap: 0, children: [_jsx(DataTableToolbar, { searchConfig: searchConfig, searchQuery: searchQuery, onSearchChange: setSearchQuery, bulkActions: bulkActions, selectedRows: selectedRows, selectedIdsCount: selectedIds.size, toolbarContent: toolbarContent, hasData: data.length > 0, className: stickyFooter ? "shrink-0" : undefined }), filterableColumns && (_jsx("div", { className: stickyFooter ? "shrink-0" : undefined, children: _jsx(DataTableFilterChips, { filterConfigs: filterConfigs, filterableColumns: filterableColumns, onSetFilter: setFilter, onRemoveFilter: removeFilter, onClearFilters: clearFilters }) })), displayData.length === 0 && !filterConfigs.length && !searchQuery ? (_jsx(EmptyState, { title: emptyConfig.title, description: emptyConfig.description, ...(emptyState?.icon && { icon: emptyState.icon }), ...(emptyState?.action && { action: emptyState.action }) })) : (_jsxs(_Fragment, { children: [_jsxs(Box, { className: cn("border relative overflow-hidden kala-surface-card", stickyFooter && paginationConfig && displayData.length > 0
|
|
234
|
+
? "flex-1 min-h-0"
|
|
235
|
+
: "", bordered && "border-2", paginationConfig && displayData.length > 0
|
|
236
|
+
? "rounded-t-lg border-b-0"
|
|
237
|
+
: "rounded-lg"), children: [_jsx(Box, { ref: scrollContainerRef, onScroll: handleScroll, className: cn("overflow-auto relative", paginationConfig && displayData.length > 0
|
|
238
|
+
? "rounded-t-lg"
|
|
239
|
+
: "rounded-lg", stickyFooter ? "flex-1 min-h-0 max-h-[70vh]" : "max-h-[800px]"), children: _jsxs("table", { className: "w-full border-separate border-spacing-0 caption-bottom text-sm", children: [caption && _jsx("caption", { className: "sr-only", children: caption }), _jsx(DataTableHeaderRow, { columns: columns, selection: selection, sortConfig: sortConfig, onToggleSort: toggleSort, filterableColumns: filterableColumns, filterConfigs: filterConfigs, onSetFilter: setFilter, onRemoveFilter: removeFilter, isAllSelected: isAllSelected, isSomeSelected: isSomeSelected, onSelectAll: handleSelectAll, stickyHeader: stickyHeader }), displayData.length === 0 ? (_jsx("tbody", { children: _jsx("tr", { children: _jsx("td", { colSpan: columns.length + (selection?.enabled ? 1 : 0), className: "h-48 text-center", children: _jsxs("div", { className: "flex flex-col items-center gap-3", children: [_jsx(Text, { size: "sm", className: "text-muted-foreground", children: "No results match the current filters." }), clearFilters && (_jsx(Button, { variant: "outline", size: "sm", onClick: clearFilters, children: "Clear Filters" }))] }) }) }) })) : (_jsxs(_Fragment, { children: [_jsx(DataTableBodyRows, { displayData: displayData, columns: columns, selection: selection, selectedIds: selectedIds, onSelectRow: handleSelectRow, onRowClick: onRowClick, compact: compact, striped: striped, hoverable: hoverable, getRowClassName: getRowClassName, getRowAttributes: getRowAttributes, hasPagination: !!paginationConfig, hasFooter: !!footer }), headersBelow && displayData.length > 10 && (_jsx(TableHeader, { className: cn("bg-muted border-t", stickyFooter &&
|
|
240
|
+
"sticky bottom-0 z-40 shadow-[0_-1px_0_0_var(--border)] backdrop-blur-sm bg-muted/95"), children: _jsxs(TableRow, { children: [selection?.enabled && (_jsx(TableHead, { className: "w-12", children: _jsx(Checkbox, { checked: isAllSelected
|
|
241
|
+
? true
|
|
242
|
+
: isSomeSelected
|
|
243
|
+
? "indeterminate"
|
|
244
|
+
: false, onCheckedChange: handleSelectAll, "aria-label": "Select all rows (bottom)" }) })), columns.map((column) => (_jsx(TableHead, { className: cn(column.className, column.align === "center" && "text-center", column.align === "right" && "text-right"), children: _jsx(Flex, { align: "center", gap: 2, className: cn(column.align === "center" && "justify-center", column.align === "right" && "justify-end"), children: column.enableSorting !== false ? (_jsxs(Button, { variant: "ghost", size: "sm", className: "h-auto p-0 -ml-2 justify-start font-medium hover:bg-transparent", onClick: () => toggleSort(column.accessorKey ||
|
|
245
|
+
column.id), "aria-label": `Sort by ${column.header}`, children: [column.header, sortConfig?.key ===
|
|
246
|
+
(column.accessorKey || column.id) &&
|
|
247
|
+
(sortConfig.direction === "asc" ? (_jsx(ArrowUp, { className: "ml-2 h-4 w-4 text-muted-foreground", "aria-hidden": "true" })) : (_jsx(ArrowDown, { className: "ml-2 h-4 w-4 text-muted-foreground", "aria-hidden": "true" })))] })) : (_jsx(Text, { weight: "medium", children: column.header })) }) }, column.id)))] }) })), footer && (_jsx(TableFooter, { className: cn("border-t", !!paginationConfig && "border-b"), children: _jsx(TableRow, { className: "border-0", children: _jsx(TableCell, { colSpan: columns.length + (selection?.enabled ? 1 : 0), className: "p-4", children: footer }) }) }))] }))] }) }), showScrollButtons && (_jsxs(Flex, { gap: 2, className: "absolute bottom-4 right-4 z-10", children: [_jsx(Button, { color: "secondary", size: "icon", onClick: scrollToTop, className: "rounded-full shadow-lg h-9 w-9 bg-background/80 backdrop-blur-sm border-border-strong/20", "aria-label": "Scroll to top", children: _jsx(ArrowUp, { className: "h-4 w-4" }) }), _jsx(Button, { color: "secondary", size: "icon", onClick: scrollToBottom, className: "rounded-full shadow-lg h-9 w-9 bg-background/80 backdrop-blur-sm border-border-strong/20", "aria-label": "Scroll to bottom", children: _jsx(ArrowDown, { className: "h-4 w-4" }) })] }))] }), paginationConfig && displayData.length > 0 && (_jsxs(Flex, { align: "center", justify: "between", className: cn("flex-col gap-3 sm:flex-row sm:gap-0 px-4 py-3 border rounded-b-lg bg-muted/30", stickyFooter && "shrink-0"), children: [_jsxs(Flex, { align: "center", justify: "between", gap: 4, className: "w-full sm:w-auto", wrap: "wrap", children: [_jsxs(Text, { as: "span", className: "text-foreground whitespace-nowrap", children: [Math.min((paginationConfig.page - 1) * paginationConfig.pageSize + 1, paginationConfig.total), " - ", Math.min(paginationConfig.page * paginationConfig.pageSize, paginationConfig.total), " / ", _jsx(Text, { as: "span", weight: "medium", className: "text-foreground", children: paginationConfig.total })] }), paginationConfig.pageSizeOptions && (_jsxs(Flex, { align: "center", gap: 2, children: [_jsx(Text, { size: "sm", className: "text-muted-foreground whitespace-nowrap", children: "Rows per page" }), _jsxs(Select, { value: String(paginationConfig.pageSize), onValueChange: (value) => setPageSize(Number(value)), children: [_jsx(SelectTrigger, { className: "h-8 w-[70px]", children: _jsx(SelectValue, { placeholder: String(paginationConfig.pageSize) }) }), _jsx(SelectContent, { side: "top", children: paginationConfig.pageSizeOptions.map((option) => (_jsx(SelectItem, { value: String(option), children: option }, option))) })] })] }))] }), _jsx(PaginationNav, { currentPage: paginationConfig.page, totalPages: totalPages, hasPreviousPage: hasPreviousPage, hasNextPage: hasNextPage, onPageChange: setCurrentPage, onPreviousPage: previousPage, onNextPage: nextPage })] }))] }))] }));
|
|
248
|
+
}
|
|
249
|
+
// Export skeleton for direct use
|
|
250
|
+
export { DataTableSkeleton } from "./data-table-skeleton.js";
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DataTable Types
|
|
3
|
+
* Type definitions for the DataTable component with full TypeScript support
|
|
4
|
+
*/
|
|
5
|
+
import type { EmptyStateProps } from "@kala-ui/react/empty-state";
|
|
6
|
+
import type { HTMLAttributes, ReactNode } from "react";
|
|
7
|
+
/**
|
|
8
|
+
* Sort direction
|
|
9
|
+
*/
|
|
10
|
+
export type SortDirection = "asc" | "desc";
|
|
11
|
+
/**
|
|
12
|
+
* Sort configuration
|
|
13
|
+
*/
|
|
14
|
+
export interface SortConfig<TData> {
|
|
15
|
+
key: keyof TData;
|
|
16
|
+
direction: SortDirection;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Filter operator types
|
|
20
|
+
*/
|
|
21
|
+
export type FilterOperator = "equals" | "contains" | "startsWith" | "endsWith" | "in";
|
|
22
|
+
/**
|
|
23
|
+
* Filter configuration
|
|
24
|
+
*/
|
|
25
|
+
export interface FilterConfig<TData> {
|
|
26
|
+
key: keyof TData;
|
|
27
|
+
operator: FilterOperator;
|
|
28
|
+
value: unknown;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Filterable column configuration
|
|
32
|
+
*/
|
|
33
|
+
export interface FilterableColumn<TData> {
|
|
34
|
+
/** Key of the column to filter */
|
|
35
|
+
key: keyof TData;
|
|
36
|
+
/** Label to display for the filter */
|
|
37
|
+
label: string;
|
|
38
|
+
/** Type of filter input */
|
|
39
|
+
type: "select" | "text";
|
|
40
|
+
/** Options for select filter */
|
|
41
|
+
options?: Array<{
|
|
42
|
+
label: string;
|
|
43
|
+
value: string;
|
|
44
|
+
}>;
|
|
45
|
+
/** Placeholder for text filter */
|
|
46
|
+
placeholder?: string;
|
|
47
|
+
/** Filter operator to use (default: 'equals' for select, 'contains' for text) */
|
|
48
|
+
operator?: FilterOperator;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Column definition for DataTable
|
|
52
|
+
*/
|
|
53
|
+
export interface ColumnDef<TData> {
|
|
54
|
+
/** Unique identifier for the column */
|
|
55
|
+
id: string;
|
|
56
|
+
/** Column header label */
|
|
57
|
+
header: string;
|
|
58
|
+
/** Key to access data from row object */
|
|
59
|
+
accessorKey?: keyof TData;
|
|
60
|
+
/** Custom cell renderer */
|
|
61
|
+
cell?: (row: TData) => ReactNode;
|
|
62
|
+
/** Enable sorting for this column */
|
|
63
|
+
enableSorting?: boolean;
|
|
64
|
+
/** Custom CSS class for the column */
|
|
65
|
+
className?: string;
|
|
66
|
+
/** Enable filtering for this column */
|
|
67
|
+
enableFiltering?: boolean;
|
|
68
|
+
/** Custom sort function */
|
|
69
|
+
sortFn?: (a: TData, b: TData, direction: SortDirection) => number;
|
|
70
|
+
/** Column width (CSS value) */
|
|
71
|
+
width?: string;
|
|
72
|
+
/** Column alignment */
|
|
73
|
+
align?: "left" | "center" | "right";
|
|
74
|
+
/** Hide column on mobile */
|
|
75
|
+
hideOnMobile?: boolean;
|
|
76
|
+
/** ARIA label for column header */
|
|
77
|
+
ariaLabel?: string;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Pagination configuration
|
|
81
|
+
*/
|
|
82
|
+
export interface PaginationConfig {
|
|
83
|
+
/** Current page number (1-indexed) */
|
|
84
|
+
page: number;
|
|
85
|
+
/** Number of items per page */
|
|
86
|
+
pageSize: number;
|
|
87
|
+
/** Total number of items */
|
|
88
|
+
total: number;
|
|
89
|
+
/** Page size options */
|
|
90
|
+
pageSizeOptions?: number[];
|
|
91
|
+
/** Callback when pagination changes (for server-side pagination) */
|
|
92
|
+
onPageChange?: (page: number, pageSize: number) => void;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Search configuration
|
|
96
|
+
*/
|
|
97
|
+
export interface SearchConfig<TData> {
|
|
98
|
+
/** Placeholder text for search input */
|
|
99
|
+
placeholder?: string;
|
|
100
|
+
/** Keys to search in */
|
|
101
|
+
searchKeys?: (keyof TData)[];
|
|
102
|
+
/** Debounce delay in ms */
|
|
103
|
+
debounce?: number;
|
|
104
|
+
/** ARIA label for search input */
|
|
105
|
+
ariaLabel?: string;
|
|
106
|
+
/** Callback when search query changes (for server-side search) */
|
|
107
|
+
onChange?: (query: string) => void;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Empty state configuration
|
|
111
|
+
*/
|
|
112
|
+
export interface EmptyStateConfig {
|
|
113
|
+
/** Title text */
|
|
114
|
+
title?: string;
|
|
115
|
+
/** Description text */
|
|
116
|
+
description?: string;
|
|
117
|
+
/** Icon component */
|
|
118
|
+
icon?: EmptyStateProps["icon"];
|
|
119
|
+
/** Action button */
|
|
120
|
+
action?: {
|
|
121
|
+
label: string;
|
|
122
|
+
onClick: () => void;
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Loading state configuration - DEPRECATED: Use DataTableSkeletonConfig
|
|
127
|
+
*/
|
|
128
|
+
export interface LoadingConfig {
|
|
129
|
+
/** Number of skeleton rows to show */
|
|
130
|
+
rows?: number;
|
|
131
|
+
/** Show loading overlay */
|
|
132
|
+
overlay?: boolean;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* DataTable Skeleton Configuration
|
|
136
|
+
* Full-featured skeleton configuration for DataTable
|
|
137
|
+
*/
|
|
138
|
+
export interface DataTableSkeletonConfig<_TData = unknown> {
|
|
139
|
+
/** Number of skeleton rows to show (default: 5) */
|
|
140
|
+
rows?: number;
|
|
141
|
+
/** Show search bar skeleton */
|
|
142
|
+
showSearch?: boolean;
|
|
143
|
+
/** Show filter pills skeleton */
|
|
144
|
+
showFilters?: boolean;
|
|
145
|
+
/** Number of filter pills to show (default: 3) */
|
|
146
|
+
filterCount?: number;
|
|
147
|
+
/** Show pagination skeleton */
|
|
148
|
+
showPagination?: boolean;
|
|
149
|
+
/** Show selection checkboxes */
|
|
150
|
+
showSelection?: boolean;
|
|
151
|
+
/** Show bulk actions bar */
|
|
152
|
+
showBulkActions?: boolean;
|
|
153
|
+
/** Enable sticky header */
|
|
154
|
+
stickyHeader?: boolean;
|
|
155
|
+
/** Enable sticky footer (for pagination) */
|
|
156
|
+
stickyFooter?: boolean;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Row selection configuration
|
|
160
|
+
*/
|
|
161
|
+
export interface SelectionConfig<TData> {
|
|
162
|
+
/** Enable row selection */
|
|
163
|
+
enabled: boolean;
|
|
164
|
+
/** Selected row IDs */
|
|
165
|
+
selectedIds: Set<string>;
|
|
166
|
+
/** Get unique ID from row */
|
|
167
|
+
getRowId: (row: TData) => string;
|
|
168
|
+
/** Selection change handler */
|
|
169
|
+
onSelectionChange: (selectedIds: Set<string>) => void;
|
|
170
|
+
/** Disable selection for specific rows */
|
|
171
|
+
isRowSelectable?: (row: TData) => boolean;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Bulk action definition
|
|
175
|
+
*/
|
|
176
|
+
export interface BulkAction<TData> {
|
|
177
|
+
/** Action identifier */
|
|
178
|
+
id: string;
|
|
179
|
+
/** Action label */
|
|
180
|
+
label: string;
|
|
181
|
+
/** Action icon */
|
|
182
|
+
icon?: ReactNode;
|
|
183
|
+
/** Action handler */
|
|
184
|
+
onClick: (selectedRows: TData[]) => void | Promise<void>;
|
|
185
|
+
/** Disabled state */
|
|
186
|
+
disabled?: boolean;
|
|
187
|
+
/** Variant (default, destructive, etc.) */
|
|
188
|
+
variant?: "solid" | "ghost" | "outline" | "subtle" | "link" | "destructive";
|
|
189
|
+
/** Requires confirmation */
|
|
190
|
+
requireConfirmation?: boolean;
|
|
191
|
+
/** Confirmation message */
|
|
192
|
+
confirmationMessage?: string;
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* DataTable Props
|
|
196
|
+
*/
|
|
197
|
+
export interface DataTableProps<TData> {
|
|
198
|
+
/** Table data */
|
|
199
|
+
data: TData[];
|
|
200
|
+
/** Column definitions */
|
|
201
|
+
columns: ColumnDef<TData>[];
|
|
202
|
+
/** Enable global search */
|
|
203
|
+
searchable?: SearchConfig<TData> | boolean;
|
|
204
|
+
/** Enable pagination */
|
|
205
|
+
pagination?: PaginationConfig | boolean;
|
|
206
|
+
/** Initial sort configuration */
|
|
207
|
+
defaultSort?: SortConfig<TData>;
|
|
208
|
+
/** Initial filter configuration */
|
|
209
|
+
defaultFilters?: FilterConfig<TData>[];
|
|
210
|
+
/** Filterable columns configuration */
|
|
211
|
+
filterableColumns?: FilterableColumn<TData>[];
|
|
212
|
+
/** Callback when sort changes (for server-side sorting) */
|
|
213
|
+
onSortChange?: (sort: SortConfig<TData> | null) => void;
|
|
214
|
+
/** Callback when filters change (for server-side filtering) */
|
|
215
|
+
onFilterChange?: (filters: FilterConfig<TData>[]) => void;
|
|
216
|
+
/** Empty state configuration */
|
|
217
|
+
emptyState?: EmptyStateConfig;
|
|
218
|
+
/** Loading state */
|
|
219
|
+
isLoading?: boolean;
|
|
220
|
+
/** Loading configuration - DEPRECATED: Use loadingConfig or skeletonConfig */
|
|
221
|
+
loadingConfig?: LoadingConfig;
|
|
222
|
+
/** Skeleton configuration for advanced control */
|
|
223
|
+
skeletonConfig?: DataTableSkeletonConfig;
|
|
224
|
+
/** Custom skeleton override */
|
|
225
|
+
skeleton?: React.ReactNode;
|
|
226
|
+
/** Row selection */
|
|
227
|
+
selection?: SelectionConfig<TData>;
|
|
228
|
+
/** Bulk actions */
|
|
229
|
+
bulkActions?: BulkAction<TData>[];
|
|
230
|
+
/** Row click handler */
|
|
231
|
+
onRowClick?: (row: TData) => void;
|
|
232
|
+
/** Custom row class name function */
|
|
233
|
+
getRowClassName?: (row: TData) => string;
|
|
234
|
+
/** Custom row attributes function */
|
|
235
|
+
getRowAttributes?: (row: TData) => HTMLAttributes<HTMLTableRowElement>;
|
|
236
|
+
/** Sticky header */
|
|
237
|
+
stickyHeader?: boolean;
|
|
238
|
+
/** Compact mode (reduced padding) */
|
|
239
|
+
compact?: boolean;
|
|
240
|
+
/** Striped rows */
|
|
241
|
+
striped?: boolean;
|
|
242
|
+
/** Bordered */
|
|
243
|
+
bordered?: boolean;
|
|
244
|
+
/** Hoverable rows */
|
|
245
|
+
hoverable?: boolean;
|
|
246
|
+
/** Custom table caption for a11y */
|
|
247
|
+
caption?: string;
|
|
248
|
+
/** Table footer content */
|
|
249
|
+
footer?: ReactNode;
|
|
250
|
+
/** Extra content rendered in the toolbar row, next to the search input */
|
|
251
|
+
toolbarContent?: ReactNode;
|
|
252
|
+
/** Custom CSS class for table container */
|
|
253
|
+
className?: string;
|
|
254
|
+
/** ARIA label for the table */
|
|
255
|
+
ariaLabel?: string;
|
|
256
|
+
/** Sticky footer (keeps pagination visible when scrolling) */
|
|
257
|
+
stickyFooter?: boolean;
|
|
258
|
+
/** Show duplicate column headers below data rows */
|
|
259
|
+
headersBelow?: boolean;
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Table state hook return type
|
|
263
|
+
*/
|
|
264
|
+
export interface TableState<TData> {
|
|
265
|
+
/** Current filtered and sorted data */
|
|
266
|
+
processedData: TData[];
|
|
267
|
+
/** Current page data (if pagination enabled) */
|
|
268
|
+
pageData: TData[];
|
|
269
|
+
/** Current sort configuration */
|
|
270
|
+
sortConfig: SortConfig<TData> | null;
|
|
271
|
+
/** Set sort configuration */
|
|
272
|
+
setSortConfig: (config: SortConfig<TData> | null) => void;
|
|
273
|
+
/** Toggle sort for a column */
|
|
274
|
+
toggleSort: (key: keyof TData) => void;
|
|
275
|
+
/** Current filter configurations */
|
|
276
|
+
filterConfigs: FilterConfig<TData>[];
|
|
277
|
+
/** Add or update filter */
|
|
278
|
+
setFilter: (filter: FilterConfig<TData>) => void;
|
|
279
|
+
/** Remove filter */
|
|
280
|
+
removeFilter: (key: keyof TData) => void;
|
|
281
|
+
/** Clear all filters */
|
|
282
|
+
clearFilters: () => void;
|
|
283
|
+
/** Search query */
|
|
284
|
+
searchQuery: string;
|
|
285
|
+
/** Set search query */
|
|
286
|
+
setSearchQuery: (query: string) => void;
|
|
287
|
+
/** Current page (1-indexed) */
|
|
288
|
+
currentPage: number;
|
|
289
|
+
/** Set current page */
|
|
290
|
+
setCurrentPage: (page: number) => void;
|
|
291
|
+
/** Page size */
|
|
292
|
+
pageSize: number;
|
|
293
|
+
/** Set page size */
|
|
294
|
+
setPageSize: (size: number) => void;
|
|
295
|
+
/** Total pages */
|
|
296
|
+
totalPages: number;
|
|
297
|
+
/** Has next page */
|
|
298
|
+
hasNextPage: boolean;
|
|
299
|
+
/** Has previous page */
|
|
300
|
+
hasPreviousPage: boolean;
|
|
301
|
+
/** Go to next page */
|
|
302
|
+
nextPage: () => void;
|
|
303
|
+
/** Go to previous page */
|
|
304
|
+
previousPage: () => void;
|
|
305
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DataTable Component Exports
|
|
3
|
+
*/
|
|
4
|
+
export { DataTable, DataTableSkeleton } from "./data-table";
|
|
5
|
+
export type { BulkAction, ColumnDef, DataTableProps, DataTableSkeletonConfig, EmptyStateConfig, FilterableColumn, FilterConfig, FilterOperator, LoadingConfig, PaginationConfig, SearchConfig, SelectionConfig, SortConfig, SortDirection, TableState, } from "./data-table.types";
|
|
6
|
+
export { useTableState } from "./useTableState";
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PaginationNav Component
|
|
3
|
+
* Numbered page buttons with ellipsis for DataTable pagination
|
|
4
|
+
*/
|
|
5
|
+
interface PaginationNavProps {
|
|
6
|
+
currentPage: number;
|
|
7
|
+
totalPages: number;
|
|
8
|
+
hasPreviousPage: boolean;
|
|
9
|
+
hasNextPage: boolean;
|
|
10
|
+
onPageChange: (page: number) => void;
|
|
11
|
+
onPreviousPage: () => void;
|
|
12
|
+
onNextPage: () => void;
|
|
13
|
+
}
|
|
14
|
+
export declare function PaginationNav({ currentPage, totalPages, hasPreviousPage, hasNextPage, onPageChange, onPreviousPage, onNextPage, }: PaginationNavProps): import("react").JSX.Element;
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
/**
|
|
3
|
+
* PaginationNav Component
|
|
4
|
+
* Numbered page buttons with ellipsis for DataTable pagination
|
|
5
|
+
*/
|
|
6
|
+
"use client";
|
|
7
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
8
|
+
import { Box } from "@kala-ui/react/box";
|
|
9
|
+
import { Button } from "@kala-ui/react/button";
|
|
10
|
+
import { Flex } from "@kala-ui/react/flex";
|
|
11
|
+
import { DOTS, usePagination } from "@kala-ui/react-hooks";
|
|
12
|
+
import { ChevronLeft, ChevronRight, MoreHorizontal } from "lucide-react";
|
|
13
|
+
export function PaginationNav({ currentPage, totalPages, hasPreviousPage, hasNextPage, onPageChange, onPreviousPage, onNextPage, }) {
|
|
14
|
+
// Use fewer siblings on small screens to reduce button count
|
|
15
|
+
const { range } = usePagination({
|
|
16
|
+
total: totalPages,
|
|
17
|
+
page: currentPage,
|
|
18
|
+
siblings: 0,
|
|
19
|
+
boundaries: 1,
|
|
20
|
+
onPageChange,
|
|
21
|
+
});
|
|
22
|
+
return (_jsxs(Flex, { align: "center", justify: "center", wrap: "wrap", gap: 1, children: [_jsxs(Button, { variant: "outline", size: "sm", onClick: onPreviousPage, disabled: !hasPreviousPage, className: "h-8 w-8 p-0", children: [_jsx(ChevronLeft, { className: "h-4 w-4" }), _jsx(Box, { as: "span", className: "sr-only", children: "Previous page" })] }), range.map((item, index) => item === DOTS ? (_jsx(Flex, { align: "center", justify: "center", className: "h-8 w-8", "aria-hidden": true, children: _jsx(MoreHorizontal, { className: "h-4 w-4 text-muted-foreground" }) }, `dots-${index}`)) : (_jsx(Button, { variant: item === currentPage ? "solid" : "outline", size: "sm", onClick: () => onPageChange(item), className: "h-8 w-8 p-0 text-xs", "aria-label": `Page ${item}`, "aria-current": item === currentPage ? "page" : undefined, children: item }, item))), _jsxs(Button, { variant: "outline", size: "sm", onClick: onNextPage, disabled: !hasNextPage, className: "h-8 w-8 p-0", children: [_jsx(ChevronRight, { className: "h-4 w-4" }), _jsx(Box, { as: "span", className: "sr-only", children: "Next page" })] })] }));
|
|
23
|
+
}
|