@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,135 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
/**
|
|
3
|
+
* Chart Utilities
|
|
4
|
+
* Helper functions and default configurations for charts
|
|
5
|
+
*/
|
|
6
|
+
import { getChartColors, getThemeAwareChartOptions } from "./theme-utils.js";
|
|
7
|
+
/**
|
|
8
|
+
* Get default chart options that match our design system
|
|
9
|
+
* @param theme - Optional theme override, otherwise detects from DOM
|
|
10
|
+
*/
|
|
11
|
+
export function getDefaultChartOptions(theme) {
|
|
12
|
+
const themeColors = getChartColors(theme);
|
|
13
|
+
const themeOptions = getThemeAwareChartOptions(theme);
|
|
14
|
+
return {
|
|
15
|
+
chart: {
|
|
16
|
+
fontFamily: "inherit",
|
|
17
|
+
foreColor: "var(--muted-foreground)",
|
|
18
|
+
background: "transparent",
|
|
19
|
+
toolbar: {
|
|
20
|
+
show: false,
|
|
21
|
+
},
|
|
22
|
+
zoom: {
|
|
23
|
+
enabled: false,
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
grid: {
|
|
27
|
+
borderColor: themeColors.grid,
|
|
28
|
+
strokeDashArray: 3,
|
|
29
|
+
xaxis: {
|
|
30
|
+
lines: {
|
|
31
|
+
show: false,
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
dataLabels: {
|
|
36
|
+
enabled: false,
|
|
37
|
+
},
|
|
38
|
+
tooltip: {
|
|
39
|
+
theme: themeOptions.tooltip.theme,
|
|
40
|
+
},
|
|
41
|
+
legend: {
|
|
42
|
+
labels: {
|
|
43
|
+
colors: themeOptions.legend.labels.colors,
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Default color palette matching design tokens
|
|
50
|
+
* @param theme - Optional theme override, otherwise detects from DOM
|
|
51
|
+
* @deprecated Use getChartColors() instead for theme-aware colors
|
|
52
|
+
*/
|
|
53
|
+
export function defaultColors(theme) {
|
|
54
|
+
const colors = getChartColors(theme);
|
|
55
|
+
return {
|
|
56
|
+
primary: colors.primary,
|
|
57
|
+
success: colors.success,
|
|
58
|
+
warning: colors.warning,
|
|
59
|
+
destructive: colors.destructive,
|
|
60
|
+
info: colors.info,
|
|
61
|
+
mixed: colors.mixed,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Format number with appropriate units
|
|
66
|
+
*/
|
|
67
|
+
export function formatNumber(value) {
|
|
68
|
+
if (value >= 1000000) {
|
|
69
|
+
return `${(value / 1000000).toFixed(1)}M`;
|
|
70
|
+
}
|
|
71
|
+
if (value >= 1000) {
|
|
72
|
+
return `${(value / 1000).toFixed(1)}K`;
|
|
73
|
+
}
|
|
74
|
+
return value.toFixed(0);
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Format currency values
|
|
78
|
+
*/
|
|
79
|
+
export function formatCurrency(value, currency = "USD") {
|
|
80
|
+
return new Intl.NumberFormat("en-US", {
|
|
81
|
+
style: "currency",
|
|
82
|
+
currency,
|
|
83
|
+
minimumFractionDigits: 0,
|
|
84
|
+
maximumFractionDigits: 2,
|
|
85
|
+
}).format(value);
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Format percentage values
|
|
89
|
+
*/
|
|
90
|
+
export function formatPercentage(value, decimals = 1) {
|
|
91
|
+
return `${value.toFixed(decimals)}%`;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Generate gradient colors for area charts
|
|
95
|
+
*/
|
|
96
|
+
export function createGradient(_color, opacity = 0.7) {
|
|
97
|
+
return {
|
|
98
|
+
type: "gradient",
|
|
99
|
+
gradient: {
|
|
100
|
+
shadeIntensity: 1,
|
|
101
|
+
opacityFrom: opacity,
|
|
102
|
+
opacityTo: opacity * 0.3,
|
|
103
|
+
stops: [0, 90, 100],
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Get responsive breakpoints for charts
|
|
109
|
+
*/
|
|
110
|
+
export function getResponsiveOptions() {
|
|
111
|
+
return [
|
|
112
|
+
{
|
|
113
|
+
breakpoint: 768,
|
|
114
|
+
options: {
|
|
115
|
+
chart: {
|
|
116
|
+
height: 300,
|
|
117
|
+
},
|
|
118
|
+
legend: {
|
|
119
|
+
position: "bottom",
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
breakpoint: 480,
|
|
125
|
+
options: {
|
|
126
|
+
chart: {
|
|
127
|
+
height: 250,
|
|
128
|
+
},
|
|
129
|
+
legend: {
|
|
130
|
+
show: false,
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
];
|
|
135
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ColumnFilters Component
|
|
3
|
+
* Renders filter controls for filterable columns in DataTable
|
|
4
|
+
*/
|
|
5
|
+
import type { FilterableColumn, FilterConfig } from "./data-table.types";
|
|
6
|
+
interface ColumnFiltersProps<TData> {
|
|
7
|
+
filterableColumns: FilterableColumn<TData>[];
|
|
8
|
+
activeFilters: FilterConfig<TData>[];
|
|
9
|
+
onFilterChange: (filter: FilterConfig<TData>) => void;
|
|
10
|
+
onFilterRemove: (key: keyof TData) => void;
|
|
11
|
+
onClearAll: () => void;
|
|
12
|
+
}
|
|
13
|
+
export declare function ColumnFilters<TData>({ filterableColumns, activeFilters, onFilterChange, onFilterRemove, onClearAll, }: ColumnFiltersProps<TData>): import("react").JSX.Element | null;
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
/**
|
|
3
|
+
* ColumnFilters Component
|
|
4
|
+
* Renders filter controls for filterable columns in DataTable
|
|
5
|
+
*/
|
|
6
|
+
"use client";
|
|
7
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
8
|
+
import { Button } from "@kala-ui/react/button";
|
|
9
|
+
import { Input } from "@kala-ui/react/input";
|
|
10
|
+
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@kala-ui/react/select";
|
|
11
|
+
import { X } from "lucide-react";
|
|
12
|
+
export function ColumnFilters({ filterableColumns, activeFilters, onFilterChange, onFilterRemove, onClearAll, }) {
|
|
13
|
+
if (filterableColumns.length === 0) {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
const hasActiveFilters = activeFilters.length > 0;
|
|
17
|
+
return (_jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [filterableColumns.map((column) => {
|
|
18
|
+
const activeFilter = activeFilters.find((f) => f.key === column.key);
|
|
19
|
+
const filterValue = activeFilter?.value;
|
|
20
|
+
if (column.type === "select" && column.options) {
|
|
21
|
+
return (_jsxs("div", { className: "flex items-center gap-2", children: [_jsxs("span", { className: "text-sm font-medium text-foreground", children: [column.label, ":"] }), _jsxs(Select, { ...(filterValue ? { value: String(filterValue) } : {}), onValueChange: (value) => {
|
|
22
|
+
if (value === "__clear__") {
|
|
23
|
+
onFilterRemove(column.key);
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
onFilterChange({
|
|
27
|
+
key: column.key,
|
|
28
|
+
operator: column.operator || "equals",
|
|
29
|
+
value,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
}, children: [_jsx(SelectTrigger, { className: "w-[180px] h-9", children: _jsx(SelectValue, { placeholder: `All ${column.label}` }) }), _jsxs(SelectContent, { children: [_jsxs(SelectItem, { value: "__clear__", children: ["All ", column.label] }), column.options.map((option) => (_jsx(SelectItem, { value: option.value, children: option.label }, option.value)))] })] })] }, String(column.key)));
|
|
33
|
+
}
|
|
34
|
+
if (column.type === "text") {
|
|
35
|
+
return (_jsxs("div", { className: "flex items-center gap-2", children: [_jsxs("span", { className: "text-sm font-medium text-foreground", children: [column.label, ":"] }), _jsxs("div", { className: "relative", children: [_jsx(Input, { type: "text", placeholder: column.placeholder || `Filter by ${column.label}`, value: filterValue ? String(filterValue) : "", onChange: (e) => {
|
|
36
|
+
const value = e.target.value;
|
|
37
|
+
if (value) {
|
|
38
|
+
onFilterChange({
|
|
39
|
+
key: column.key,
|
|
40
|
+
operator: column.operator || "contains",
|
|
41
|
+
value,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
onFilterRemove(column.key);
|
|
46
|
+
}
|
|
47
|
+
}, className: "w-[200px] h-9 pr-8" }), filterValue ? (_jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => onFilterRemove(column.key), className: "absolute right-0 top-1/2 -translate-y-1/2 h-7 w-7 p-0", "aria-label": `Clear ${String(column.label)} filter`, children: _jsx(X, { className: "h-3 w-3" }) })) : null] })] }, String(column.key)));
|
|
48
|
+
}
|
|
49
|
+
return null;
|
|
50
|
+
}), hasActiveFilters && (_jsxs(Button, { variant: "ghost", size: "sm", onClick: onClearAll, className: "h-9 text-muted-foreground hover:text-foreground", children: [_jsx(X, { className: "h-4 w-4 mr-1.5" }), "Clear filters"] }))] }));
|
|
51
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Column Header Filter Component
|
|
3
|
+
* Inline filter for DataTable columns with funnel icon and multi-select popover
|
|
4
|
+
*/
|
|
5
|
+
import * as React from "react";
|
|
6
|
+
import type { FilterableColumn, FilterConfig } from "./data-table.types";
|
|
7
|
+
interface ColumnHeaderFilterProps<TData> {
|
|
8
|
+
column: FilterableColumn<TData>;
|
|
9
|
+
activeFilters: FilterConfig<TData>[];
|
|
10
|
+
onFilterChange: (filter: FilterConfig<TData>) => void;
|
|
11
|
+
onFilterRemove: (key: keyof TData) => void;
|
|
12
|
+
}
|
|
13
|
+
export declare function ColumnHeaderFilter<TData>({ column, activeFilters, onFilterChange, onFilterRemove, }: ColumnHeaderFilterProps<TData>): React.JSX.Element;
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
/**
|
|
3
|
+
* Column Header Filter Component
|
|
4
|
+
* Inline filter for DataTable columns with funnel icon and multi-select popover
|
|
5
|
+
*/
|
|
6
|
+
"use client";
|
|
7
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
8
|
+
import { Badge } from "@kala-ui/react/badge";
|
|
9
|
+
import { Button } from "@kala-ui/react/button";
|
|
10
|
+
import { Checkbox } from "@kala-ui/react/checkbox";
|
|
11
|
+
import { Flex } from "@kala-ui/react/flex";
|
|
12
|
+
import { Heading } from "@kala-ui/react/heading";
|
|
13
|
+
import { Input } from "@kala-ui/react/input";
|
|
14
|
+
import { Label } from "@kala-ui/react/label";
|
|
15
|
+
import { Popover, PopoverContent, PopoverTrigger, } from "@kala-ui/react/popover";
|
|
16
|
+
import { Stack } from "@kala-ui/react/stack";
|
|
17
|
+
import { Text } from "@kala-ui/react/text";
|
|
18
|
+
import { useDisclosure } from "@kala-ui/react-hooks";
|
|
19
|
+
import { Filter, X } from "lucide-react";
|
|
20
|
+
import * as React from "react";
|
|
21
|
+
export function ColumnHeaderFilter({ column, activeFilters, onFilterChange, onFilterRemove, }) {
|
|
22
|
+
const [open, { set: setOpen }] = useDisclosure(false);
|
|
23
|
+
const [textValue, setTextValue] = React.useState("");
|
|
24
|
+
const activeFilter = activeFilters.find((f) => f.key === column.key);
|
|
25
|
+
const filterValue = activeFilter?.value;
|
|
26
|
+
// For multi-select, track selected values
|
|
27
|
+
const selectedValues = filterValue
|
|
28
|
+
? Array.isArray(filterValue)
|
|
29
|
+
? filterValue
|
|
30
|
+
: [filterValue]
|
|
31
|
+
: [];
|
|
32
|
+
const activeCount = selectedValues.length;
|
|
33
|
+
const hasActiveFilter = activeCount > 0 || (column.type === "text" && filterValue);
|
|
34
|
+
const handleCheckboxChange = (optionValue, checked) => {
|
|
35
|
+
let newValues;
|
|
36
|
+
if (checked) {
|
|
37
|
+
newValues = [...selectedValues, optionValue];
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
newValues = selectedValues.filter((v) => v !== optionValue);
|
|
41
|
+
}
|
|
42
|
+
if (newValues.length === 0) {
|
|
43
|
+
onFilterRemove(column.key);
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
onFilterChange({
|
|
47
|
+
key: column.key,
|
|
48
|
+
operator: "in",
|
|
49
|
+
value: newValues,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
const handleTextApply = () => {
|
|
54
|
+
if (textValue.trim()) {
|
|
55
|
+
onFilterChange({
|
|
56
|
+
key: column.key,
|
|
57
|
+
operator: column.operator || "contains",
|
|
58
|
+
value: textValue.trim(),
|
|
59
|
+
});
|
|
60
|
+
setOpen(false);
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
const handleClear = () => {
|
|
64
|
+
onFilterRemove(column.key);
|
|
65
|
+
setTextValue("");
|
|
66
|
+
};
|
|
67
|
+
return (_jsxs(Popover, { open: open, onOpenChange: setOpen, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs(Button, { variant: "ghost", size: "sm", className: "h-8 w-8 p-0 hover:bg-muted relative", "aria-label": `Filter by ${column.label}`, children: [_jsx(Filter, { className: `h-4 w-4 ${hasActiveFilter ? "text-primary" : "text-muted-foreground"}` }), hasActiveFilter ? (_jsx(Badge, { className: "absolute -top-1 -right-1 h-4 w-4 p-0 flex items-center justify-center text-[10px]", children: _jsx(Text, { size: "xs", weight: "bold", className: "text-primary-foreground", children: column.type === "text" ? "1" : activeCount }) })) : null] }) }), _jsx(PopoverContent, { className: "w-64 p-3", align: "start", children: _jsxs(Stack, { gap: 3, children: [_jsxs(Flex, { align: "center", justify: "between", children: [_jsxs(Heading, { size: "h6", weight: "medium", className: "text-sm", children: ["Filter by ", column.label] }), hasActiveFilter ? (_jsxs(Button, { variant: "ghost", size: "sm", onClick: handleClear, className: "h-7 px-2 text-xs text-muted-foreground", children: [_jsx(X, { className: "h-3 w-3 mr-1" }), "Clear"] })) : null] }), column.type === "select" && column.options ? (_jsx(Stack, { gap: 2, className: "max-h-[300px] overflow-y-auto", children: column.options.map((option) => {
|
|
68
|
+
const isChecked = selectedValues.includes(option.value);
|
|
69
|
+
return (_jsxs(Flex, { align: "center", gap: 2, children: [_jsx(Checkbox, { id: `${String(column.key)}-${option.value}`, checked: isChecked, onCheckedChange: (checked) => handleCheckboxChange(option.value, checked === true) }), _jsx(Label, { htmlFor: `${String(column.key)}-${option.value}`, className: "text-sm font-normal cursor-pointer flex-1 text-foreground", children: option.label })] }, option.value));
|
|
70
|
+
}) })) : (_jsxs(Stack, { gap: 2, children: [_jsx(Input, { type: "text", placeholder: column.placeholder || `Filter by ${column.label}`, value: textValue, onChange: (e) => setTextValue(e.target.value), onKeyDown: (e) => {
|
|
71
|
+
if (e.key === "Enter") {
|
|
72
|
+
handleTextApply();
|
|
73
|
+
}
|
|
74
|
+
}, className: "h-9" }), _jsx(Button, { onClick: handleTextApply, className: "w-full", size: "sm", children: "Apply" })] }))] }) })] }));
|
|
75
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DataTableBodyRows Component
|
|
3
|
+
* Table body rows with selection, cell rendering, and row interaction for DataTable
|
|
4
|
+
*/
|
|
5
|
+
import type { HTMLAttributes } from "react";
|
|
6
|
+
import type { ColumnDef, SelectionConfig } from "./data-table.types";
|
|
7
|
+
interface DataTableBodyRowsProps<TData> {
|
|
8
|
+
displayData: TData[];
|
|
9
|
+
columns: ColumnDef<TData>[];
|
|
10
|
+
selection?: SelectionConfig<TData>;
|
|
11
|
+
selectedIds: Set<string>;
|
|
12
|
+
onSelectRow: (row: TData) => void;
|
|
13
|
+
onRowClick?: (row: TData) => void;
|
|
14
|
+
compact: boolean;
|
|
15
|
+
striped: boolean;
|
|
16
|
+
hoverable: boolean;
|
|
17
|
+
getRowClassName?: (row: TData) => string;
|
|
18
|
+
getRowAttributes?: (row: TData) => HTMLAttributes<HTMLTableRowElement>;
|
|
19
|
+
hasPagination: boolean;
|
|
20
|
+
hasFooter: boolean;
|
|
21
|
+
}
|
|
22
|
+
export declare function DataTableBodyRows<TData>({ displayData, columns, selection, selectedIds, onSelectRow, onRowClick, compact, striped, hoverable, getRowClassName, getRowAttributes, hasPagination, hasFooter, }: DataTableBodyRowsProps<TData>): import("react").JSX.Element;
|
|
23
|
+
export {};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
/**
|
|
3
|
+
* DataTableBodyRows Component
|
|
4
|
+
* Table body rows with selection, cell rendering, and row interaction for DataTable
|
|
5
|
+
*/
|
|
6
|
+
"use client";
|
|
7
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
8
|
+
import { Checkbox } from "@kala-ui/react/checkbox";
|
|
9
|
+
import { cn } from "@kala-ui/react/lib/utils";
|
|
10
|
+
import { TableBody, TableCell, TableRow } from "@kala-ui/react/table";
|
|
11
|
+
export function DataTableBodyRows({ displayData, columns, selection, selectedIds, onSelectRow, onRowClick, compact, striped, hoverable, getRowClassName, getRowAttributes, hasPagination, hasFooter, }) {
|
|
12
|
+
return (_jsx(TableBody, { children: displayData.map((row, rowIndex) => {
|
|
13
|
+
const isSelectable = !selection?.isRowSelectable || selection.isRowSelectable(row);
|
|
14
|
+
const rowId = selection?.getRowId(row);
|
|
15
|
+
const isSelected = rowId ? selectedIds.has(rowId) : false;
|
|
16
|
+
const isClickable = !!onRowClick;
|
|
17
|
+
const isLastRow = rowIndex === displayData.length - 1;
|
|
18
|
+
const rowClassNames = cn(isSelected && "bg-accent/50", striped && rowIndex % 2 === 0 && "bg-muted/50", hoverable && "hover:bg-muted/50", isClickable &&
|
|
19
|
+
"cursor-pointer outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-inset", compact && "h-10", getRowClassName?.(row), "border-0");
|
|
20
|
+
return (_jsxs(TableRow, { ...getRowAttributes?.(row), "data-state": isSelected ? "selected" : undefined, onClick: () => isClickable && onRowClick(row), className: rowClassNames,
|
|
21
|
+
// clickable rows stay real <tr>s: focusable + Enter/Space
|
|
22
|
+
// activation keeps table semantics intact for screen readers
|
|
23
|
+
tabIndex: isClickable ? 0 : undefined, onKeyDown: (e) => {
|
|
24
|
+
if (isClickable && (e.key === "Enter" || e.key === " ")) {
|
|
25
|
+
e.preventDefault();
|
|
26
|
+
onRowClick(row);
|
|
27
|
+
}
|
|
28
|
+
}, children: [selection?.enabled && (_jsx(TableCell, { className: cn(!isLastRow || (hasPagination && !hasFooter)) && "border-b", children: _jsx(Checkbox, { checked: isSelected, disabled: !isSelectable, "aria-label": `Select row ${rowIndex + 1}`, onCheckedChange: () => {
|
|
29
|
+
if (isSelectable)
|
|
30
|
+
onSelectRow(row);
|
|
31
|
+
}, onClick: (e) => e.stopPropagation() }) })), columns.map((column) => {
|
|
32
|
+
const cellValue = column.accessorKey
|
|
33
|
+
? row[column.accessorKey]
|
|
34
|
+
: undefined;
|
|
35
|
+
const cellClassNames = cn(column.align === "center" && "text-center", column.align === "right" && "text-right", column.hideOnMobile && "hidden md:table-cell", compact && "py-2", (!isLastRow || (hasPagination && !hasFooter)) && "border-b");
|
|
36
|
+
return (_jsx(TableCell, { className: cellClassNames, children: column.cell
|
|
37
|
+
? column.cell(row)
|
|
38
|
+
: cellValue !== null && cellValue !== undefined
|
|
39
|
+
? String(cellValue)
|
|
40
|
+
: "—" }, column.id));
|
|
41
|
+
})] }, rowId ?? rowIndex));
|
|
42
|
+
}) }));
|
|
43
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DataTableFilterChips Component
|
|
3
|
+
* Active filter badges with remove/clear actions for DataTable
|
|
4
|
+
*/
|
|
5
|
+
import type { FilterableColumn, FilterConfig } from "./data-table.types";
|
|
6
|
+
interface DataTableFilterChipsProps<TData> {
|
|
7
|
+
filterConfigs: FilterConfig<TData>[];
|
|
8
|
+
filterableColumns: FilterableColumn<TData>[];
|
|
9
|
+
onSetFilter: (filter: FilterConfig<TData>) => void;
|
|
10
|
+
onRemoveFilter: (key: keyof TData) => void;
|
|
11
|
+
onClearFilters: () => void;
|
|
12
|
+
}
|
|
13
|
+
export declare function DataTableFilterChips<TData>({ filterConfigs, filterableColumns, onSetFilter, onRemoveFilter, onClearFilters, }: DataTableFilterChipsProps<TData>): import("react").JSX.Element | null;
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
/**
|
|
3
|
+
* DataTableFilterChips Component
|
|
4
|
+
* Active filter badges with remove/clear actions for DataTable
|
|
5
|
+
*/
|
|
6
|
+
"use client";
|
|
7
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
8
|
+
import { Badge } from "@kala-ui/react/badge";
|
|
9
|
+
import { Button } from "@kala-ui/react/button";
|
|
10
|
+
import { Flex } from "@kala-ui/react/flex";
|
|
11
|
+
import { Text } from "@kala-ui/react/text";
|
|
12
|
+
import { X } from "lucide-react";
|
|
13
|
+
export function DataTableFilterChips({ filterConfigs, filterableColumns, onSetFilter, onRemoveFilter, onClearFilters, }) {
|
|
14
|
+
if (filterConfigs.length === 0)
|
|
15
|
+
return null;
|
|
16
|
+
return (_jsxs(Flex, { align: "center", gap: 2, className: "mb-4 flex-wrap", children: [_jsx(Text, { size: "sm", className: "text-muted-foreground", children: "Active filters:" }), filterConfigs.map((filter) => {
|
|
17
|
+
const column = filterableColumns.find((fc) => fc.key === filter.key);
|
|
18
|
+
if (!column)
|
|
19
|
+
return null;
|
|
20
|
+
const filterValueArray = Array.isArray(filter.value)
|
|
21
|
+
? filter.value
|
|
22
|
+
: [filter.value];
|
|
23
|
+
const findLabel = (val) => {
|
|
24
|
+
const strVal = String(val);
|
|
25
|
+
const option = column.options?.find((o) => o.value === strVal);
|
|
26
|
+
return option?.label ?? strVal;
|
|
27
|
+
};
|
|
28
|
+
return filterValueArray.map((value, idx) => (_jsxs(Badge, { color: "secondary", className: "gap-1.5 pl-2 pr-1 py-1 bg-primary/10 text-primary border-primary/20", children: [_jsxs(Text, { size: "xs", weight: "medium", children: [column.label, ": ", findLabel(value)] }), _jsx(Button, { variant: "ghost", size: "sm", onClick: () => {
|
|
29
|
+
if (filterValueArray.length === 1) {
|
|
30
|
+
onRemoveFilter(filter.key);
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
const newValues = filterValueArray.filter((v) => v !== value);
|
|
34
|
+
onSetFilter({
|
|
35
|
+
key: filter.key,
|
|
36
|
+
operator: filter.operator,
|
|
37
|
+
value: newValues,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
}, className: "h-4 w-4 p-0 hover:bg-primary/20 rounded-sm", "aria-label": `Remove ${findLabel(value)}`, children: _jsx(X, { className: "h-3 w-3" }) })] }, `${String(filter.key)}-${value}-${idx}`)));
|
|
41
|
+
}), _jsx(Button, { variant: "ghost", size: "sm", onClick: onClearFilters, className: "h-7 px-2 text-xs text-muted-foreground hover:text-foreground", children: "Clear all" })] }));
|
|
42
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DataTableHeaderRow Component
|
|
3
|
+
* Column headers with sort indicators and filter popovers for DataTable
|
|
4
|
+
*/
|
|
5
|
+
import type { ColumnDef, FilterableColumn, FilterConfig, SelectionConfig, SortConfig } from "./data-table.types";
|
|
6
|
+
interface DataTableHeaderRowProps<TData> {
|
|
7
|
+
columns: ColumnDef<TData>[];
|
|
8
|
+
selection?: SelectionConfig<TData>;
|
|
9
|
+
sortConfig: SortConfig<TData> | null;
|
|
10
|
+
onToggleSort: (key: keyof TData) => void;
|
|
11
|
+
filterableColumns?: FilterableColumn<TData>[];
|
|
12
|
+
filterConfigs: FilterConfig<TData>[];
|
|
13
|
+
onSetFilter: (filter: FilterConfig<TData>) => void;
|
|
14
|
+
onRemoveFilter: (key: keyof TData) => void;
|
|
15
|
+
isAllSelected: boolean;
|
|
16
|
+
isSomeSelected: boolean;
|
|
17
|
+
onSelectAll: () => void;
|
|
18
|
+
stickyHeader: boolean;
|
|
19
|
+
}
|
|
20
|
+
export declare function DataTableHeaderRow<TData>({ columns, selection, sortConfig, onToggleSort, filterableColumns, filterConfigs, onSetFilter, onRemoveFilter, isAllSelected, isSomeSelected, onSelectAll, stickyHeader, }: DataTableHeaderRowProps<TData>): import("react").JSX.Element;
|
|
21
|
+
export {};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
/**
|
|
3
|
+
* DataTableHeaderRow Component
|
|
4
|
+
* Column headers with sort indicators and filter popovers for DataTable
|
|
5
|
+
*/
|
|
6
|
+
"use client";
|
|
7
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
8
|
+
import { Button } from "@kala-ui/react/button";
|
|
9
|
+
import { Checkbox } from "@kala-ui/react/checkbox";
|
|
10
|
+
import { Flex } from "@kala-ui/react/flex";
|
|
11
|
+
import { cn } from "@kala-ui/react/lib/utils";
|
|
12
|
+
import { TableHead, TableHeader, TableRow } from "@kala-ui/react/table";
|
|
13
|
+
import { ChevronDown, ChevronUp } from "lucide-react";
|
|
14
|
+
import { ColumnHeaderFilter } from "./column-header-filter.js";
|
|
15
|
+
export function DataTableHeaderRow({ columns, selection, sortConfig, onToggleSort, filterableColumns, filterConfigs, onSetFilter, onRemoveFilter, isAllSelected, isSomeSelected, onSelectAll, stickyHeader, }) {
|
|
16
|
+
return (_jsx(TableHeader, { className: stickyHeader
|
|
17
|
+
? "sticky top-0 z-1 bg-muted/95 backdrop-blur-sm [&_tr]:border-0"
|
|
18
|
+
: "[&_tr]:border-0", children: _jsxs(TableRow, { className: "border-0", children: [selection?.enabled && (_jsx(TableHead, { className: stickyHeader
|
|
19
|
+
? "w-12 rounded-tl-lg border-b border-border-strong/50"
|
|
20
|
+
: "w-12 rounded-tl-lg border-b", children: _jsx(Checkbox, { checked: isAllSelected ? true : isSomeSelected ? "indeterminate" : false, onCheckedChange: onSelectAll, "aria-label": "Select all rows" }) })), columns.map((column, index) => {
|
|
21
|
+
const isSorted = column.accessorKey && sortConfig?.key === column.accessorKey;
|
|
22
|
+
const sortDirection = isSorted ? sortConfig.direction : null;
|
|
23
|
+
const canSort = column.enableSorting !== false && column.accessorKey;
|
|
24
|
+
const filterColumn = filterableColumns?.find((fc) => fc.key === column.accessorKey);
|
|
25
|
+
const isFirst = !selection?.enabled && index === 0;
|
|
26
|
+
const isLast = index === columns.length - 1;
|
|
27
|
+
return (_jsx(TableHead, { style: { width: column.width }, className: cn(column.align === "center" && "text-center", column.align === "right" && "text-right", column.hideOnMobile && "hidden md:table-cell", stickyHeader ? "border-b border-border-strong/50" : "border-b", isFirst && "rounded-tl-lg", isLast && "rounded-tr-lg"), "aria-sort": isSorted && sortDirection
|
|
28
|
+
? sortDirection === "asc"
|
|
29
|
+
? "ascending"
|
|
30
|
+
: "descending"
|
|
31
|
+
: "none", children: _jsxs(Flex, { align: "center", gap: 1, className: cn(column.align === "center" && "justify-center", column.align === "right" && "justify-end"), children: [canSort && column.accessorKey ? (_jsxs(Button, { variant: "ghost", size: "sm", onClick: () => onToggleSort(column.accessorKey), className: "-ml-3 h-8 data-[state=open]:bg-accent hover:bg-accent text-foreground hover:text-accent-foreground", "aria-label": column.ariaLabel
|
|
32
|
+
? `Sort by ${column.ariaLabel}`
|
|
33
|
+
: `Sort by ${column.header}`, children: [column.header, isSorted ? (sortDirection === "asc" ? (_jsx(ChevronUp, { className: "ml-2 h-4 w-4", "aria-hidden": "true" })) : (_jsx(ChevronDown, { className: "ml-2 h-4 w-4", "aria-hidden": "true" }))) : (_jsx(ChevronDown, { className: "ml-2 h-4 w-4 opacity-40", "aria-hidden": "true" }))] })) : (_jsx("span", { className: "text-xs normal-case tracking-normal text-muted-foreground", children: column.header })), filterColumn && (_jsx(ColumnHeaderFilter, { column: filterColumn, activeFilters: filterConfigs, onFilterChange: onSetFilter, onFilterRemove: onRemoveFilter }))] }) }, column.id));
|
|
34
|
+
})] }) }));
|
|
35
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DataTable Skeleton Component
|
|
3
|
+
* Provides a full-featured loading placeholder that matches the DataTable component structure
|
|
4
|
+
* including search bar, filters, table with selections, and pagination
|
|
5
|
+
*/
|
|
6
|
+
import type { ColumnDef } from "./data-table.types";
|
|
7
|
+
export interface DataTableSkeletonConfig<TData = unknown> {
|
|
8
|
+
/**
|
|
9
|
+
* Number of skeleton rows to show (default: 5)
|
|
10
|
+
*/
|
|
11
|
+
rows?: number;
|
|
12
|
+
/**
|
|
13
|
+
* Column definitions for accurate layout matching
|
|
14
|
+
*/
|
|
15
|
+
columns: ColumnDef<TData>[];
|
|
16
|
+
/**
|
|
17
|
+
* Show search bar skeleton (default: true if searchable)
|
|
18
|
+
*/
|
|
19
|
+
showSearch?: boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Show filter pills skeleton (default: true if filterable)
|
|
22
|
+
*/
|
|
23
|
+
showFilters?: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Number of filter pills to show (default: 3)
|
|
26
|
+
*/
|
|
27
|
+
filterCount?: number;
|
|
28
|
+
/**
|
|
29
|
+
* Show pagination skeleton (default: true if pagination)
|
|
30
|
+
*/
|
|
31
|
+
showPagination?: boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Show selection checkboxes (default: false)
|
|
34
|
+
*/
|
|
35
|
+
showSelection?: boolean;
|
|
36
|
+
/**
|
|
37
|
+
* Show bulk actions bar (default: false)
|
|
38
|
+
*/
|
|
39
|
+
showBulkActions?: boolean;
|
|
40
|
+
/**
|
|
41
|
+
* Enable sticky header
|
|
42
|
+
*/
|
|
43
|
+
stickyHeader?: boolean;
|
|
44
|
+
/**
|
|
45
|
+
* Enable sticky footer (for pagination)
|
|
46
|
+
*/
|
|
47
|
+
stickyFooter?: boolean;
|
|
48
|
+
}
|
|
49
|
+
export declare function DataTableSkeleton<TData = unknown>({ rows, columns, showSearch, showFilters, filterCount, showPagination, showSelection, showBulkActions, stickyHeader, stickyFooter, }: DataTableSkeletonConfig<TData>): import("react").JSX.Element;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
/**
|
|
4
|
+
* DataTable Skeleton Component
|
|
5
|
+
* Provides a full-featured loading placeholder that matches the DataTable component structure
|
|
6
|
+
* including search bar, filters, table with selections, and pagination
|
|
7
|
+
*/
|
|
8
|
+
import { Badge } from "@kala-ui/react/badge";
|
|
9
|
+
import { Button } from "@kala-ui/react/button";
|
|
10
|
+
import { Checkbox } from "@kala-ui/react/checkbox";
|
|
11
|
+
import { FieldLabel } from "@kala-ui/react/field";
|
|
12
|
+
import { Input } from "@kala-ui/react/input";
|
|
13
|
+
import { cn } from "@kala-ui/react/lib/utils";
|
|
14
|
+
import { Select, SelectTrigger, SelectValue } from "@kala-ui/react/select";
|
|
15
|
+
import { Skeleton } from "@kala-ui/react/skeleton";
|
|
16
|
+
import { TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@kala-ui/react/table";
|
|
17
|
+
export function DataTableSkeleton({ rows = 5, columns, showSearch = false, showFilters = false, filterCount = 3, showPagination = false, showSelection = false, showBulkActions = false, stickyHeader = true, stickyFooter = false, }) {
|
|
18
|
+
return (_jsxs("div", { className: "flex flex-col", children: [showSearch && (_jsxs("div", { className: "flex flex-col sm:flex-row items-start sm:items-center justify-between gap-4 mb-4 shrink-0", children: [_jsx("div", { className: "flex-1 max-w-sm", children: _jsx(Input, { type: "search", placeholder: "Search...", disabled: true, className: "opacity-50" }) }), showBulkActions && (_jsxs("div", { className: "flex items-center gap-2 flex-wrap min-h-[36px]", children: [_jsx("span", { className: "text-sm text-muted-foreground", children: "0 selected" }), _jsx(Button, { variant: "outline", size: "sm", disabled: true, className: "opacity-50", children: "Edit" }), _jsx(Button, { variant: "outline", size: "sm", disabled: true, className: "opacity-50", children: "Delete" })] }))] })), showFilters && (_jsxs("div", { className: "flex flex-wrap items-center gap-2 mb-4 shrink-0", children: [_jsx("span", { className: "text-sm text-muted-foreground", children: "Active filters:" }), Array.from({ length: filterCount }).map((_, i) => (_jsxs(Badge, { color: "secondary", className: "gap-1.5 pl-2 pr-1 py-1 bg-primary/10 text-primary border-primary/20", children: [_jsx("span", { className: "text-xs font-medium", children: "Filter: Value" }), _jsx(Button, { variant: "ghost", size: "sm", disabled: true, className: "h-4 w-4 p-0 hover:bg-primary/20 rounded-sm", children: _jsx(Skeleton, { className: "h-3 w-3" }) })] }, `filter-${i}`))), _jsx(Button, { variant: "ghost", size: "sm", disabled: true, className: "h-7 px-2 text-xs text-muted-foreground hover:text-foreground", children: "Clear all" })] })), _jsx("div", { className: cn("border relative overflow-hidden kala-surface-card", showPagination ? "rounded-t-lg border-b-0" : "rounded-lg", stickyFooter ? "flex-1 min-h-0" : ""), children: _jsx("div", { className: cn("overflow-auto relative", showPagination ? "rounded-t-lg" : "rounded-lg", stickyFooter ? "flex-1 min-h-0 max-h-[70vh]" : "max-h-[600px]"), children: _jsxs("table", { className: "w-full border-separate border-spacing-0 caption-bottom text-sm", children: [_jsx(TableHeader, { className: cn(stickyHeader && "sticky top-0 z-1 bg-muted/95 backdrop-blur-sm", "[&_tr]:border-0"), children: _jsxs(TableRow, { className: "border-0", children: [showSelection && (_jsx(TableHead, { className: cn("w-12 rounded-tl-lg", stickyHeader
|
|
19
|
+
? "border-b border-border-strong/50"
|
|
20
|
+
: "border-b"), children: _jsx(Checkbox, { disabled: true }) })), columns.map((column, index) => {
|
|
21
|
+
const isFirst = !showSelection && index === 0;
|
|
22
|
+
const isLast = index === columns.length - 1;
|
|
23
|
+
return (_jsx(TableHead, { style: { width: column.width }, className: cn(column.align === "center" && "text-center", column.align === "right" && "text-right", column.hideOnMobile && "hidden md:table-cell", stickyHeader
|
|
24
|
+
? "border-b border-border-strong/50"
|
|
25
|
+
: "border-b", isFirst && "rounded-tl-lg", isLast && "rounded-tr-lg"), children: _jsx("div", { className: cn("flex items-center gap-1", column.align === "center" && "justify-center", column.align === "right" && "justify-end"), children: column.enableFiltering ? (_jsxs(_Fragment, { children: [_jsx(Skeleton, { className: "h-4 flex-1" }), _jsx(Skeleton, { className: "h-4 w-4" })] })) : (_jsx(Skeleton, { className: "h-4 w-24" })) }) }, column.id));
|
|
26
|
+
})] }) }), _jsx(TableBody, { children: Array.from({ length: rows }).map((_, rowIndex) => {
|
|
27
|
+
const isLastRow = rowIndex === rows - 1;
|
|
28
|
+
return (_jsxs(TableRow, { className: cn("border-0"), children: [showSelection && (_jsx(TableCell, { children: _jsx(Checkbox, { disabled: true, "aria-label": `Select row ${rowIndex + 1}` }) })), columns.map((column) => {
|
|
29
|
+
return (_jsx(TableCell, { className: cn(column.align === "center" && "text-center", column.align === "right" && "text-right", column.hideOnMobile && "hidden md:table-cell", !isLastRow && "border-b"), children: _jsx(Skeleton, { className: "h-4 w-32" }) }, column.id));
|
|
30
|
+
})] }, rowIndex));
|
|
31
|
+
}) })] }) }) }), showPagination && (_jsxs("div", { className: cn("flex flex-col gap-3 sm:flex-row sm:gap-0 items-center sm:justify-between bg-background px-4 py-3 rounded-b-lg kala-surface-card border-x border-b", stickyFooter && "sticky bottom-0 z-1 shadow-xl bg-background"), children: [_jsxs("div", { className: "flex items-center justify-between gap-4 w-full sm:w-auto", children: [_jsx("div", { className: "flex items-center gap-2 text-sm text-muted-foreground", children: _jsx(Skeleton, { className: "h-4 w-48" }) }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(FieldLabel, { htmlFor: "page-size", className: "text-sm text-muted-foreground mb-0", children: "Rows per page:" }), _jsx(Select, { disabled: true, children: _jsx(SelectTrigger, { className: "w-20 h-9 text-foreground opacity-50", children: _jsx(SelectValue, {}) }) })] })] }), _jsxs("div", { className: "flex items-center justify-center gap-1 flex-wrap", children: [_jsx(Button, { variant: "outline", size: "sm", disabled: true, className: "opacity-50", children: _jsx(Skeleton, { className: "h-4 w-4" }) }), _jsx("span", { className: "text-sm text-muted-foreground px-2", children: _jsx(Skeleton, { className: "h-4 w-16 inline-block" }) }), _jsx(Button, { variant: "outline", size: "sm", disabled: true, className: "opacity-50", children: _jsx(Skeleton, { className: "h-4 w-4" }) })] })] }))] }));
|
|
32
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DataTableToolbar Component
|
|
3
|
+
* Search input, bulk actions, and custom toolbar content for DataTable
|
|
4
|
+
*/
|
|
5
|
+
import type { BulkAction, SearchConfig } from "./data-table.types";
|
|
6
|
+
interface DataTableToolbarProps<TData> {
|
|
7
|
+
searchConfig: SearchConfig<TData> | null;
|
|
8
|
+
searchQuery: string;
|
|
9
|
+
onSearchChange: (query: string) => void;
|
|
10
|
+
bulkActions?: BulkAction<TData>[];
|
|
11
|
+
selectedRows: TData[];
|
|
12
|
+
selectedIdsCount: number;
|
|
13
|
+
toolbarContent?: React.ReactNode;
|
|
14
|
+
hasData: boolean;
|
|
15
|
+
className?: string;
|
|
16
|
+
}
|
|
17
|
+
export declare function DataTableToolbar<TData>({ searchConfig, searchQuery, onSearchChange, bulkActions, selectedRows, selectedIdsCount, toolbarContent, hasData, className, }: DataTableToolbarProps<TData>): import("react").JSX.Element | null;
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
/**
|
|
3
|
+
* DataTableToolbar Component
|
|
4
|
+
* Search input, bulk actions, and custom toolbar content for DataTable
|
|
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 { Input } from "@kala-ui/react/input";
|
|
12
|
+
import { cn } from "@kala-ui/react/lib/utils";
|
|
13
|
+
import { Text } from "@kala-ui/react/text";
|
|
14
|
+
import { Search, X } from "lucide-react";
|
|
15
|
+
export function DataTableToolbar({ searchConfig, searchQuery, onSearchChange, bulkActions, selectedRows, selectedIdsCount, toolbarContent, hasData, className, }) {
|
|
16
|
+
const showSearch = searchConfig && (hasData || searchQuery);
|
|
17
|
+
const showRight = toolbarContent || bulkActions;
|
|
18
|
+
if (!showSearch && !showRight)
|
|
19
|
+
return null;
|
|
20
|
+
return (_jsxs(Flex, { align: "center", justify: "between", gap: 4, className: cn("mb-4 flex-wrap", className), children: [showSearch && (_jsx(Box, { className: "flex-1 max-w-sm", children: _jsx(Input, { type: "text", placeholder: searchConfig?.placeholder, value: searchQuery, onChange: (e) => onSearchChange(e.target.value), prefixIcon: _jsx(Search, { className: "h-4 w-4" }), suffixIcon: searchQuery ? (_jsx(Button, { variant: "ghost", size: "sm", onClick: () => onSearchChange(""), className: "p-1 text-muted-foreground hover:text-foreground h-auto", "aria-label": "Clear search", children: _jsx(X, { className: "h-4 w-4" }) })) : null, "aria-label": searchConfig?.ariaLabel }) })), showRight && (_jsxs(Flex, { align: "center", gap: 2, className: "flex-wrap", children: [toolbarContent, bulkActions && (_jsxs(Flex, { align: "center", gap: 2, className: "flex-wrap min-h-[36px]", children: [_jsxs(Text, { size: "sm", className: "text-muted-foreground", children: [selectedIdsCount, " selected"] }), bulkActions.map((action) => (_jsxs(Button, { variant: action.variant === "destructive"
|
|
21
|
+
? "solid"
|
|
22
|
+
: (action.variant ?? "outline"), color: action.variant === "destructive" ? "destructive" : undefined, size: "sm", onClick: () => action.onClick(selectedRows), disabled: selectedIdsCount === 0 || action.disabled, className: selectedIdsCount === 0
|
|
23
|
+
? "opacity-50 cursor-not-allowed"
|
|
24
|
+
: undefined, children: [action.icon, action.label] }, action.id)))] }))] }))] }));
|
|
25
|
+
}
|