react-data-table-tailwind
Version:
A simple data table component for React with TailwindCSS
86 lines (85 loc) • 6.15 kB
JavaScript
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
import classNames from "classnames";
import { useEffect, useMemo, useRef, useState } from "react";
import Spinner from "./components/Spinner";
import { debounce } from "loadsh";
export default function DataTable({ columns, data, loading, usePagination, totalPages, onPageChange, dark = false, containerClassName, }) {
const [currentPage, setCurrentPage] = useState(1);
const [filteredData, setFilteredData] = useState(data);
const [searchValues, setSearchValues] = useState({});
const inputRefs = useRef({});
const focusedInput = useRef(null);
useEffect(() => {
setFilteredData(data);
}, [data]);
useEffect(() => {
if (focusedInput.current && inputRefs.current[focusedInput.current]) {
inputRefs.current[focusedInput.current]?.focus();
}
});
const debouncedSearch = useMemo(() => debounce((accessor, value) => {
const column = columns.find((col) => col.accessor === accessor);
if (column && column.onSearch) {
column.onSearch(accessor, value);
}
else {
const filteredData = data.filter((item) => String(item[accessor])
.toLowerCase()
.includes(value.toLowerCase()));
if (onPageChange) {
onPageChange(1); // Reset to first page on search
}
setFilteredData(filteredData);
}
}, 300), // 300ms debounce delay
[columns, data, onPageChange]);
const handleSearchChange = (accessor, value) => {
focusedInput.current = accessor;
setSearchValues((prev) => ({ ...prev, [accessor]: value }));
debouncedSearch(accessor, value);
};
const handlePageChange = (page) => {
setCurrentPage(page);
if (onPageChange) {
onPageChange(page);
}
};
return (_jsx("div", { className: classNames(containerClassName, "mt-8 p-4 rounded-lg shadow-md", {
"bg-gray-800": dark,
"bg-white": !dark,
}), children: _jsx("div", { className: "w-full", style: { overflowX: "auto" }, children: loading ? (_jsx(Spinner, {})) : (_jsxs(_Fragment, { children: [_jsxs("table", { className: classNames("w-full divide-y", {
"divide-gray-700": dark,
"divide-gray-200": !dark,
}), children: [_jsx("thead", { className: classNames("rounded-lg", {
"bg-gray-700": dark,
"bg-gray-200": !dark,
}), children: _jsx("tr", { children: columns.map((column) => (_jsx("th", { className: classNames("px-6 py-3 text-left text-xs font-medium uppercase tracking-wider", {
"text-gray-300": dark,
"text-gray-700": !dark,
}), children: _jsxs("div", { className: "flex flex-col", children: [_jsx("span", { children: column.header }), column.searchable && (_jsx("input", { ref: (el) => {
inputRefs.current[column.accessor] = el;
}, type: "text", placeholder: `Search ${column.header}`, value: searchValues[String(column.accessor)] || "", onChange: (e) => handleSearchChange(column.accessor, e.target.value), className: classNames("mt-2 px-2 py-1 text-sm rounded border", {
"bg-gray-900 text-gray-300 border-gray-600": dark,
"bg-gray-100 text-gray-700 border-gray-300": !dark,
}) }))] }) }, String(column.accessor)))) }) }), _jsx("tbody", { className: classNames({
"bg-gray-800 divide-y divide-gray-700": dark,
"bg-white divide-y divide-gray-200": !dark,
}), children: filteredData.length === 0 ? (_jsx("tr", { children: _jsx("td", { colSpan: columns.length, className: classNames("px-6 py-4 whitespace-nowrap text-center text-sm", {
"text-gray-400": dark,
"text-gray-500": !dark,
}), children: "No data found" }) })) : (filteredData.map((item, index) => (_jsx("tr", { children: columns.map((column) => (_jsx("td", { className: classNames("px-6 py-4 whitespace-nowrap text-sm", {
"text-gray-300": dark,
"text-gray-700": !dark,
}), children: column.renderRow
? column.renderRow(item, index)
: String(item[column.accessor]) }, column.accessor))) }, index)))) })] }), usePagination && (_jsxs("div", { className: "flex justify-between items-center mt-4", children: [_jsx("button", { onClick: () => handlePageChange(currentPage - 1), disabled: currentPage === 1, className: classNames("px-4 py-2 rounded disabled:opacity-50", {
"bg-gray-600 text-white": dark,
"bg-gray-200 text-gray-700": !dark,
}), children: "Previous" }), _jsxs("span", { className: classNames({
"text-white": dark,
"text-gray-700": !dark,
}), children: ["Page ", currentPage, " of ", totalPages] }), _jsx("button", { onClick: () => handlePageChange(currentPage + 1), disabled: currentPage === totalPages, className: classNames("px-4 py-2 rounded disabled:opacity-50", {
"bg-gray-600 text-white": dark,
"bg-gray-200 text-gray-700": !dark,
}), children: "Next" })] }))] })) }) }));
}