@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.
Files changed (123) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/LICENSE +21 -0
  3. package/README.md +36 -0
  4. package/dist/components/app-shell/app-shell.d.ts +54 -0
  5. package/dist/components/app-shell/app-shell.js +91 -0
  6. package/dist/components/app-shell/index.d.ts +1 -0
  7. package/dist/components/app-shell/index.js +2 -0
  8. package/dist/components/charts/area-chart.d.ts +6 -0
  9. package/dist/components/charts/area-chart.js +172 -0
  10. package/dist/components/charts/bar-chart.d.ts +6 -0
  11. package/dist/components/charts/bar-chart.js +153 -0
  12. package/dist/components/charts/chart-skeleton.d.ts +36 -0
  13. package/dist/components/charts/chart-skeleton.js +13 -0
  14. package/dist/components/charts/chart.d.ts +25 -0
  15. package/dist/components/charts/chart.js +48 -0
  16. package/dist/components/charts/chart.types.d.ts +196 -0
  17. package/dist/components/charts/chart.types.js +6 -0
  18. package/dist/components/charts/donut-chart.d.ts +7 -0
  19. package/dist/components/charts/donut-chart.js +146 -0
  20. package/dist/components/charts/index.d.ts +14 -0
  21. package/dist/components/charts/index.js +15 -0
  22. package/dist/components/charts/line-chart.d.ts +6 -0
  23. package/dist/components/charts/line-chart.js +158 -0
  24. package/dist/components/charts/radial-bar-chart.d.ts +6 -0
  25. package/dist/components/charts/radial-bar-chart.js +115 -0
  26. package/dist/components/charts/theme-utils.d.ts +77 -0
  27. package/dist/components/charts/theme-utils.js +243 -0
  28. package/dist/components/charts/use-theme-aware-chart.d.ts +12 -0
  29. package/dist/components/charts/use-theme-aware-chart.js +68 -0
  30. package/dist/components/charts/utils.d.ts +56 -0
  31. package/dist/components/charts/utils.js +135 -0
  32. package/dist/components/data-table/column-filters.d.ts +14 -0
  33. package/dist/components/data-table/column-filters.js +51 -0
  34. package/dist/components/data-table/column-header-filter.d.ts +14 -0
  35. package/dist/components/data-table/column-header-filter.js +75 -0
  36. package/dist/components/data-table/data-table-body-rows.d.ts +23 -0
  37. package/dist/components/data-table/data-table-body-rows.js +43 -0
  38. package/dist/components/data-table/data-table-filter-chips.d.ts +14 -0
  39. package/dist/components/data-table/data-table-filter-chips.js +42 -0
  40. package/dist/components/data-table/data-table-header-row.d.ts +21 -0
  41. package/dist/components/data-table/data-table-header-row.js +35 -0
  42. package/dist/components/data-table/data-table-skeleton.d.ts +49 -0
  43. package/dist/components/data-table/data-table-skeleton.js +32 -0
  44. package/dist/components/data-table/data-table-toolbar.d.ts +18 -0
  45. package/dist/components/data-table/data-table-toolbar.js +25 -0
  46. package/dist/components/data-table/data-table.d.ts +23 -0
  47. package/dist/components/data-table/data-table.js +250 -0
  48. package/dist/components/data-table/data-table.types.d.ts +305 -0
  49. package/dist/components/data-table/data-table.types.js +6 -0
  50. package/dist/components/data-table/index.d.ts +6 -0
  51. package/dist/components/data-table/index.js +6 -0
  52. package/dist/components/data-table/pagination-nav.d.ts +15 -0
  53. package/dist/components/data-table/pagination-nav.js +23 -0
  54. package/dist/components/data-table/useTableState.d.ts +29 -0
  55. package/dist/components/data-table/useTableState.js +312 -0
  56. package/dist/components/dnd/dnd.d.ts +118 -0
  57. package/dist/components/dnd/dnd.js +128 -0
  58. package/dist/components/dnd/index.d.ts +1 -0
  59. package/dist/components/dnd/index.js +2 -0
  60. package/dist/components/footer/footer.d.ts +37 -0
  61. package/dist/components/footer/footer.js +8 -0
  62. package/dist/components/footer/index.d.ts +2 -0
  63. package/dist/components/footer/index.js +2 -0
  64. package/dist/components/header/header-skeleton.d.ts +54 -0
  65. package/dist/components/header/header-skeleton.js +23 -0
  66. package/dist/components/header/header.d.ts +40 -0
  67. package/dist/components/header/header.js +77 -0
  68. package/dist/components/header/header.types.d.ts +39 -0
  69. package/dist/components/header/header.types.js +2 -0
  70. package/dist/components/header/index.d.ts +4 -0
  71. package/dist/components/header/index.js +3 -0
  72. package/dist/components/metric-card/index.d.ts +2 -0
  73. package/dist/components/metric-card/index.js +3 -0
  74. package/dist/components/metric-card/metric-card-skeleton.d.ts +32 -0
  75. package/dist/components/metric-card/metric-card-skeleton.js +29 -0
  76. package/dist/components/metric-card/metric-card.d.ts +16 -0
  77. package/dist/components/metric-card/metric-card.js +55 -0
  78. package/dist/components/metric-card/metric-card.types.d.ts +14 -0
  79. package/dist/components/metric-card/metric-card.types.js +2 -0
  80. package/dist/components/nav-link/index.d.ts +1 -0
  81. package/dist/components/nav-link/index.js +2 -0
  82. package/dist/components/nav-link/nav-link.d.ts +26 -0
  83. package/dist/components/nav-link/nav-link.js +23 -0
  84. package/dist/components/navigation/index.d.ts +4 -0
  85. package/dist/components/navigation/index.js +3 -0
  86. package/dist/components/navigation/navigation-skeleton.d.ts +54 -0
  87. package/dist/components/navigation/navigation-skeleton.js +23 -0
  88. package/dist/components/navigation/navigation.d.ts +26 -0
  89. package/dist/components/navigation/navigation.js +44 -0
  90. package/dist/components/session-card/index.d.ts +2 -0
  91. package/dist/components/session-card/index.js +3 -0
  92. package/dist/components/session-card/session-card-skeleton.d.ts +31 -0
  93. package/dist/components/session-card/session-card-skeleton.js +28 -0
  94. package/dist/components/session-card/session-card.d.ts +27 -0
  95. package/dist/components/session-card/session-card.js +67 -0
  96. package/dist/components/sidebar/index.d.ts +3 -0
  97. package/dist/components/sidebar/index.js +3 -0
  98. package/dist/components/sidebar/sidebar-skeleton.d.ts +54 -0
  99. package/dist/components/sidebar/sidebar-skeleton.js +23 -0
  100. package/dist/components/sidebar/sidebar.d.ts +46 -0
  101. package/dist/components/sidebar/sidebar.js +69 -0
  102. package/dist/components/social-login-button/index.d.ts +1 -0
  103. package/dist/components/social-login-button/index.js +2 -0
  104. package/dist/components/social-login-button/social-login-button.d.ts +20 -0
  105. package/dist/components/social-login-button/social-login-button.js +57 -0
  106. package/dist/components/social-login-buttons/index.d.ts +1 -0
  107. package/dist/components/social-login-buttons/index.js +2 -0
  108. package/dist/components/social-login-buttons/social-login-buttons.d.ts +34 -0
  109. package/dist/components/social-login-buttons/social-login-buttons.js +18 -0
  110. package/dist/components/sparkline-chart/index.d.ts +2 -0
  111. package/dist/components/sparkline-chart/index.js +2 -0
  112. package/dist/components/sparkline-chart/sparkline-chart.d.ts +51 -0
  113. package/dist/components/sparkline-chart/sparkline-chart.js +134 -0
  114. package/dist/components/user-menu-dropdown/index.d.ts +1 -0
  115. package/dist/components/user-menu-dropdown/index.js +2 -0
  116. package/dist/components/user-menu-dropdown/user-menu-dropdown.d.ts +31 -0
  117. package/dist/components/user-menu-dropdown/user-menu-dropdown.js +27 -0
  118. package/dist/index.d.ts +16 -0
  119. package/dist/index.js +19 -0
  120. package/dist/lib/active-path.d.ts +5 -0
  121. package/dist/lib/active-path.js +12 -0
  122. package/dist/styles/app.css +2125 -0
  123. 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
+ "use client";
2
+ /**
3
+ * DataTable Types
4
+ * Type definitions for the DataTable component with full TypeScript support
5
+ */
6
+ export {};
@@ -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,6 @@
1
+ "use client";
2
+ /**
3
+ * DataTable Component Exports
4
+ */
5
+ export { DataTable, DataTableSkeleton } from "./data-table.js";
6
+ export { useTableState } from "./useTableState.js";
@@ -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
+ }