UNPKG

nextjs-reusable-table

Version:

A highly customizable and reusable table component for Next.js applications, built with TypeScript and the latest technologies.

397 lines (327 loc) 13.3 kB
![Maintenance](https://img.shields.io/badge/status-legacy%20only-lightgrey) > ## ⚠️ This package is no longer maintained. > > The new and improved version is now [`@flowers-nextjs-table`](https://www.npmjs.com/package/flowers-nextjs-table). > > - No new features will be added here. > - For all new projects and updates, migrate to [`@flowers-nextjs-table`](https://www.npmjs.com/package/flowers-nextjs-table). > - See our [migration guide](https://github.com/ninsau/flowers-nextjs-table/blob/main/MIGRATION.md) for help upgrading. # nextjs-reusable-table A highly customizable and reusable table component for Next.js applications, built with TypeScript and TailwindCSS. ## Installation ```bash npm install nextjs-reusable-table@latest # or yarn add nextjs-reusable-table@latest # or pnpm add nextjs-reusable-table@latest ``` ## Prerequisites - Next.js 12 or later - React 16 or later - React DOM 16 or later - Tailwind CSS 3.0 or later - TypeScript (recommended) `Note: This is a Client Component ("use client"). Using it in purely SSR contexts may require additional handling to avoid hydration mismatches.` ## Basic Usage ```tsx "use client"; import React from "react"; import { TableComponent } from "nextjs-reusable-table"; import "nextjs-reusable-table/dist/index.css"; interface User { id: number; name: string; } export default function BasicTable() { const data: User[] = [ { id: 1, name: "Alice" }, { id: 2, name: "Bob" }, ]; return ( <TableComponent<User> columns={["ID", "Name"]} data={data} props={["id", "name"]} /> ); } ``` ## Introduction Next.js Reusable Table is designed for easy integration into Next.js apps. It supports sorting, pagination, row actions, and flexible data rendering via custom formatters. Built-in helpers handle arrays, dates, and URLs gracefully. ## Features ### Column Management - Hide/Show columns: Each column header has a dropdown (⋮) to remove or unhide columns. - Sorting: Specify which columns can be sorted. ```tsx <TableComponent columns={["Name", "Email"]} data={yourData} props={["name", "email"]} sortableProps={["name"]} /> ``` ### Smart Row Interactions The table provides intelligent click handling: - Click anywhere on a row to trigger row action - Click on cell content to expand/interact without triggering row action - Expandable content with "show more" functionality ### Built-In Data Handling - Dates automatically formatted. - Arrays displayed as chips with “+X more” for large arrays. - URLs automatically detected and rendered as links. ### Action Dropdowns - Easily attach row actions via a dropdown button: ```tsx <TableComponent actions actionTexts={["Edit", "Delete"]} actionFunctions={[(item) => editItem(item), (item) => deleteItem(item)]} /> ``` ### Search and Pagination - searchValue filters rows against all columns. - Built-in pagination. Provide page, setPage, and itemsPerPage. ```tsx <TableComponent searchValue={searchTerm} enablePagination page={page} setPage={setPage} itemsPerPage={10} /> ``` ### Custom Styling - Styling with Tailwind. Override default classes or disable them entirely: ```tsx const customClassNames = { table: "my-custom-table-styles", thead: "bg-gray-200 text-gray-700", tbody: "divide-y divide-gray-200", pagination: { container: "flex justify-center mt-4", button: "px-2 py-1 border", pageInfo: "mx-2", }, }; <TableComponent customClassNames={customClassNames} disableDefaultStyles={false} />; ``` ### Dark Mode - Automatically respects system preference if enableDarkMode is true. ### Loading Skeleton - Show a skeleton loader while data is loading. ```tsx <TableComponent loading /> ``` ### Empty State Pass noContentProps to customize text and icon: ```tsx <TableComponent noContentProps={{ text: "No data found", icon: <MyCustomIcon />, }} /> ``` ## Advanced Example ```tsx "use client"; import React, { useState, useMemo } from "react"; import { TableComponent } from "nextjs-reusable-table"; import "nextjs-reusable-table/dist/index.css"; interface Project { id: number; title: string; tags: string[]; deadline: string; active: boolean; link: string; } export default function AdvancedProjectTable() { const initialData: Project[] = [ { id: 1, title: "Website Redesign", tags: ["UI", "UX", "Frontend"], deadline: "2025-03-15T10:30:00Z", active: true, link: "https://example.com/project/1", }, { id: 2, title: "Mobile App Development", tags: ["iOS", "Android", "Backend"], deadline: "2025-04-01T14:00:00Z", active: false, link: "https://example.com/project/2", }, { id: 3, title: "Marketing Campaign", tags: ["SEO", "Social Media"], deadline: "2025-02-20T09:00:00Z", active: true, link: "https://example.com/project/3", }, { id: 4, title: "E-commerce Platform", tags: ["Frontend", "Backend", "API", "Payments", "Analytics"], deadline: "2025-05-05T11:00:00Z", active: true, link: "https://example.com/project/4", }, { id: 5, title: "Data Analysis", tags: ["Python", "ML", "Data Science"], deadline: "2025-03-01T08:00:00Z", active: false, link: "https://example.com/project/5", }, ]; const [projects, setProjects] = useState<Project[]>(initialData); const [page, setPage] = useState<number>(1); const [searchTerm, setSearchTerm] = useState<string>(""); const [sortConfig, setSortConfig] = useState<{ prop: keyof Project; order: "asc" | "desc"; } | null>(null); const handleSort = (prop: keyof Project) => { let order: "asc" | "desc" = "asc"; if (sortConfig && sortConfig.prop === prop) { order = sortConfig.order === "asc" ? "desc" : "asc"; } setSortConfig({ prop, order }); }; const editProject = (project: Project) => { alert(`Edit project: ${project.title}`); }; const deleteProject = (project: Project) => { alert(`Delete project: ${project.title}`); }; const handleRowClick = (project: Project) => { console.log("Row clicked:", project); }; const formatValue = (value: string, prop: string, project: Project) => { if (prop === "active") { return project.active ? "Active" : "Archived"; } return value; }; const formatHeader = (header: string, prop: string, index: number) => ( <div> <span className="font-semibold uppercase tracking-wide">{header}</span> </div> ); const sortedFilteredProjects = useMemo(() => { const filtered = projects.filter((project) => { const searchLower = searchTerm.toLowerCase(); return ( String(project.id).includes(searchLower) || project.title.toLowerCase().includes(searchLower) || project.tags.join(" ").toLowerCase().includes(searchLower) || project.deadline.toLowerCase().includes(searchLower) || (project.active ? "active" : "archived").includes(searchLower) || project.link.toLowerCase().includes(searchLower) ); }); if (sortConfig) { filtered.sort((a, b) => { const aValue = String(a[sortConfig.prop]).toLowerCase(); const bValue = String(b[sortConfig.prop]).toLowerCase(); if (aValue < bValue) return sortConfig.order === "asc" ? -1 : 1; if (aValue > bValue) return sortConfig.order === "asc" ? 1 : -1; return 0; }); } return filtered; }, [projects, searchTerm, sortConfig]); const customClassNames = { table: "border border-gray-300 rounded-md shadow-sm", thead: "bg-blue-50 text-blue-700", tbody: "", th: "px-4 py-2", tr: "", td: "px-4 py-2", pagination: { container: "mt-4", button: "bg-blue-500 text-white rounded px-3 py-1", buttonDisabled: "bg-gray-300 text-gray-700 rounded px-3 py-1", pageInfo: "text-blue-700", }, }; return ( <div className="p-4"> <h1 className="text-2xl font-bold mb-4">Advanced Project Table</h1> <div className="mb-4"> <input type="text" placeholder="Search projects..." value={searchTerm} onChange={(e) => setSearchTerm(e.target.value)} className="border border-gray-300 rounded px-2 py-1 w-full" /> </div> <TableComponent<Project> columns={["ID", "Title", "Tags", "Deadline", "Status", "Link"]} data={sortedFilteredProjects} props={["id", "title", "tags", "deadline", "active", "link"]} sortableProps={["title", "deadline"]} onSort={handleSort} actionTexts={["Edit", "Delete"]} actionFunctions={[editProject, deleteProject]} rowOnClick={handleRowClick} formatValue={formatValue} formatHeader={formatHeader} enablePagination page={page} setPage={setPage} itemsPerPage={2} noContentProps={{ text: "No projects found", icon: null }} customClassNames={customClassNames} /> </div> ); } ``` ## Prop Reference | Prop | Type | Default | Description | | -------------------- | ------------------------------------ | ------- | ------------------------------------------------------------ | | columns | string[] | – | Column headers | | data | T[] | – | Array of data objects | | props | ReadonlyArray<keyof T> | – | Object keys to display | | actions | boolean | false | Enable action dropdown | | actionTexts | string[] | – | Labels for dropdown actions | | loading | boolean | false | Show loading skeleton | | actionFunctions | Array<Function> | – | Handlers for dropdown items | | searchValue | string | – | Filter rows by substring | | rowOnClick | (item: T) => void | – | Callback for row clicks | | enablePagination | boolean | false | Enable pagination | | page | number | 1 | Current page index | | setPage | (page: number) => void | – | Page setter callback | | itemsPerPage | number | 10 | Rows per page | | totalPages | number | – | Override total pages | | sortableProps | Array<keyof T> | [] | Columns that can be sorted | | formatValue | (val, prop, item) => React.ReactNode | – | Custom cell formatter | | enableDarkMode | boolean | true | Respect system dark mode | | disableDefaultStyles | boolean | false | Disable built-in styling | | customClassNames | object | {} | Tailwind class overrides | | noContentProps | object | {} | Custom empty state | | onSort | (prop: keyof T) => void | – | Callback triggered when a sortable column header is clicked. | | formatHeader | (header: string) => React.ReactNode | – | Custom header formatter | ## Contributing Contributions are welcome! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for details on how to get started. ## Versioning We use [Semantic Versioning](https://semver.org/) for versioning. For the versions available, see the [tags on this repository](https://github.com/ninsau/nextjs-reusable-table/tags). To bump the version, update the `version` field in `package.json` and follow the guidelines in the [CONTRIBUTING.md](CONTRIBUTING.md) file. ## License This project is licensed under the ISC License - see the [LICENSE](LICENSE) file for details. ## Code of Conduct This project adheres to the [Contributor Covenant Code of Conduct](CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code. ## Acknowledgments - Inspired by common data table patterns in React and Next.js applications. - Thanks to all contributors and users for their support.