Package Exports
- nc-table-react
Readme
nc-table-react
Flexible React table component with advanced search, actions, settings, selection, and pagination.
A production-ready, feature-rich table component that supports both static and server-side data with powerful filtering capabilities.
๐ Features
- โ Static & Server-side Data - Works with local arrays or async data handlers
- โ Advanced Search - 5 data types with 12 comparison operators
- โ
Structured Filter Format - Backend-friendly
${column}${operator}${value};And$...format - โ Backend Field Mapping - Map display column names to different backend field names
- โ Row Selection - Single and multi-row selection with bulk actions
- โ Sorting & Pagination - Built-in sorting and configurable pagination
- โ CRUD Operations - Built-in delete functionality with confirmation dialogs
- โ Export Features - CSV, Excel, PDF export options
- โ Serial Number Column - Optional row numbering with pagination-aware calculation
- โ Conditional Actions - Show/hide table actions based on item data or static conditions
- โ Responsive Design - Mobile-optimized interface
- โ TypeScript - Full type safety and IntelliSense support
- โ Accessibility - ARIA labels and keyboard navigation
- โ Internationalization - i18n ready with react-i18next
๐ฆ Installation
npm install nc-table-react
# Install peer dependencies:
npm install react-i18next i18next lucide-react clsx class-variance-authority tailwind-merge date-fns
# Install Radix UI dependencies:
npm install @radix-ui/react-alert-dialog @radix-ui/react-checkbox @radix-ui/react-dialog @radix-ui/react-dropdown-menu @radix-ui/react-label @radix-ui/react-popover @radix-ui/react-select @radix-ui/react-slot @radix-ui/react-toastRequirements:
- React 18+
- TypeScript (recommended)
๐ง Troubleshooting Installation
If you encounter a createSlot import error, install the correct radix-ui version:
npm install @radix-ui/react-slot@^1.0.2See full installation troubleshooting guide
๐ฏ Quick Start
import React from "react";
import { NcTable, type Column, type TableAction } from "nc-table-react";
import i18n from "i18next";
import { I18nextProvider, initReactI18next } from "react-i18next";
// Minimal i18n setup
if (!i18n.isInitialized) {
i18n
.use(initReactI18next)
.init({ lng: "en", resources: { en: { translation: {} } } });
}
type User = {
id: number;
name: string;
email: string;
status: "active" | "inactive";
};
const data: User[] = [
{ id: 1, name: "Ada Lovelace", email: "ada@example.com", status: "active" },
{ id: 2, name: "Alan Turing", email: "alan@example.com", status: "inactive" },
];
const columns: Column<User>[] = [
{ key: "name", header: "Name" },
{ key: "email", header: "Email" },
{ key: "status", header: "Status" },
];
const actions: TableAction<User>[] = [
{
label: "View",
onClick: (u) => alert(`Viewing ${u.name}`),
show: true, // Always show
},
{
label: "Activate",
onClick: (u) => alert(`Activating ${u.name}`),
show: (u) => u.status === "inactive", // Only show for inactive users
},
];
export default function App() {
return (
<I18nextProvider i18n={i18n}>
<NcTable<User>
id="my-table"
data={data}
columns={columns}
actions={actions}
idField="id"
showSerialNumber={true}
/>
</I18nextProvider>
);
}๐ข Serial Number Column
The table includes an optional serial number column that shows row numbers based on the current page and position.
Basic Usage
<NcTable
data={data}
columns={columns}
showSerialNumber={true} // Default: true
idField="id"
/>Hide Serial Numbers
<NcTable
data={data}
columns={columns}
showSerialNumber={false} // Hide serial number column
idField="id"
/>Serial Number Calculation
Serial numbers are calculated to be continuous across pages:
- Page 1 (pageSize=5): 1, 2, 3, 4, 5
- Page 2 (pageSize=5): 6, 7, 8, 9, 10
- Page 3 (pageSize=5): 11, 12, 13, 14, 15
Formula: (currentPage - 1) ร pageSize + index + 1
๐ Unique Table Instance IDs
Each table instance can have a unique identifier for better isolation, testing, and debugging when using multiple tables.
Basic Usage
<NcTable
id="users-table" // Unique ID for this table instance
data={data}
columns={columns}
idField="id"
/>Benefits of Using IDs
- DOM Targeting: Direct access via
document.getElementById('users-table') - Testing: Easy targeting with
getByTestId('users-table') - Analytics: Track specific table interactions
- Accessibility: Screen readers can identify specific tables
- CSS Targeting: More specific styling with
#users-table .table-header
๐ฏ Conditional Table Actions
Table actions can be shown or hidden based on item data or static conditions using the show property.
Static Visibility
const actions: TableAction<User>[] = [
{
label: "Edit",
icon: "Edit",
onClick: (user) => console.log("Edit", user),
show: true, // Always show
},
{
label: "Archive",
icon: "Archive",
onClick: (user) => console.log("Archive", user),
show: false, // Never show
},
];Dynamic Visibility Based on Item Data
const actions: TableAction<User>[] = [
{
label: "Activate",
icon: "User",
onClick: (user) => console.log("Activate", user),
show: (user) => user.status === "inactive", // Only show for inactive users
},
{
label: "Deactivate",
icon: "User",
onClick: (user) => console.log("Deactivate", user),
show: (user) => user.status === "active", // Only show for active users
},
{
label: "Delete",
icon: "Trash",
onClick: (user) => console.log("Delete", user),
variant: "destructive",
show: (user) => user.role !== "Admin", // Hide delete for admins
},
{
label: "Admin Actions",
icon: "Settings",
onClick: (user) => console.log("Admin Actions", user),
show: (user) => user.role === "Admin", // Only show for admins
},
];Show Property Options
| Value | Description |
|---|---|
undefined |
Default behavior - always show the action |
true |
Always show the action |
false |
Never show the action |
(item: T) => boolean |
Show based on item data evaluation |
Smart Action Column Display
The actions column (dropdown menu) will only appear for rows that have at least one visible action. If all actions for a row are hidden, the actions column won't be rendered for that row.
๐ Usage Modes: Client-side vs Server-side
nc-table-react automatically detects the usage mode based on the props you provide. No manual configuration needed!
๐ฅ๏ธ Client-side Mode (Static Data)
Triggered when: You provide the data prop
<NcTable
data={users} // โ
Static array provided
columns={columns}
actions={actions}
idField="id"
// No handler needed
/>How it works:
- โ
Table uses your static
dataarray directly - โ Filtering/sorting happens in memory on the frontend
- โ No API calls are made
- โ Perfect for small datasets or pre-loaded data
- โ Instant search and sorting
๐ Server-side Mode (Dynamic Data)
Triggered when: You provide the handler prop
<NcTable
columns={columns}
handler={fetchTableData} // โ
Async function provided
actions={actions}
idField="id"
// No data prop needed
/>How it works:
- โ
Table calls your
handlerfunction for data - โ Filtering/sorting happens on your backend
- โ API calls made for pagination, search, sorting
- โ Perfect for large datasets or real-time data
- โ Advanced search filters sent to your backend
๐ Mode Comparison
| Feature | Client-side (data prop) |
Server-side (handler prop) |
|---|---|---|
| Data Source | Static array | API function |
| Filtering | Frontend (JavaScript) | Backend (your API) |
| Sorting | Frontend (JavaScript) | Backend (your API) |
| Pagination | Frontend pagination | Server-side pagination |
| API Calls | โ None | โ Yes (automatic) |
| Best For | Small datasets, demos | Large datasets, production |
| Search Performance | Instant | Depends on backend |
๐ฏ Important Notes
- Mutually Exclusive: Provide either
dataORhandler, not both - Automatic Detection: The table knows what to do based on your props
- Same API: Identical component interface for both modes
- Filter Format: In server-side mode, advanced search filters are sent as structured strings to your backend
๐ Server-side Data (Detailed Example)
import type { NcTableProps } from "nc-table-react";
type Item = { id: number; name: string };
const handler: NcTableProps<Item>["handler"] = async (params) => {
// params: { PageNumber, PageSize, Filter?, Order? }
console.log("Received params:", params);
// Example server request
const response = await fetch("/api/items", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(params),
});
return response.json(); // Returns IApiResponse<Item[]>
};
// Usage
<NcTable<Item> columns={[{ key: "name", header: "Name" }]} handler={handler} />;Backend Filter Format
The component generates structured filter strings for your backend:
// Single condition
"name~=John;"
// Multiple conditions
"name~=John;And$department==Engineering;And$salary>50000;"
// Backend receives:
{
"PageNumber": 1,
"PageSize": 10,
"Filter": "name~=John;And$department==Engineering;And$salary>50000;",
"Order": "name;Asc"
}๐ Advanced Search & Data Types
Supported Data Types
1. Text (Default)
{
key: "name",
header: "Name",
searchable: true,
// No searchOverride needed - text is default
}2. Date
{
key: "startDate",
header: "Start Date",
searchable: true,
searchOverride: {
dataType: "date"
},
render: (item) => new Date(item.startDate).toLocaleDateString(),
}3. Select (Dropdown)
{
key: "department",
header: "Department",
searchable: true,
searchOverride: {
dataType: "select",
selectOptions: [
{ text: "Engineering", value: "Engineering" },
{ text: "Marketing", value: "Marketing" },
{ text: "Sales", value: "Sales" },
]
}
}4. Boolean (Yes/No with true/false)
{
key: "isActive",
header: "Is Active",
searchable: true,
searchOverride: {
dataType: "boolean"
},
render: (item) => item.isActive ? "Yes" : "No",
}5. YesOrNo (Yes/No with 1/0)
{
key: "hasAccess",
header: "Has Access",
searchable: true,
searchOverride: {
dataType: "YesOrNo"
},
render: (item) => item.hasAccess === 1 ? "Yes" : "No",
}Search Operators
The component provides 12 powerful comparison operators:
- Equals (
==) - Exact match - Not Equals (
!=) - Does not match - Greater Than (
>) - Numeric/date comparison - Greater Than or Equals (
>=) - Numeric/date comparison - Less Than (
<) - Numeric/date comparison - Less Than or Equals (
<=) - Numeric/date comparison - Contains (
~=) - Substring match - Not Contains (
!~=) - Does not contain substring - Starts With (
_=) - Begins with pattern - Not Starts With (
!_=) - Does not begin with pattern - Ends With (
|=) - Ends with pattern - Not Ends With (
!|=) - Does not end with pattern
๐ Backend Field Mapping (searchOverride.key)
When your display column names differ from backend field names, use the key property in searchOverride:
const columns: Column<Employee>[] = [
{
key: "statusDescription", // Display field: "Active Employee"
header: "Status Description",
searchable: true,
searchOverride: {
key: "Status", // ๐ฏ Backend field: sends "Status==1;"
dataType: "select",
selectOptions: [
{ text: "Active Employee", value: "1" },
{ text: "Inactive Employee", value: "0" },
{ text: "Pending Approval", value: "2" },
],
},
},
{
key: "departmentName", // Display field: "Engineering Dept"
header: "Department Name",
searchable: true,
searchOverride: {
key: "DeptId", // ๐ฏ Backend field: sends "DeptId==ENG;"
dataType: "select",
selectOptions: [
{ text: "Engineering Dept", value: "ENG" },
{ text: "Marketing Dept", value: "MKT" },
],
},
},
{
key: "name", // Same for display and backend
header: "Name",
searchable: true,
// No key override - uses "name" for both display and backend
},
];Generated Filter Examples
// User searches: "Status Description" = "Active Employee"
// Generated: "Status==1;"
// User searches: "Department Name" = "Engineering Dept" AND "Status Description" = "Active Employee"
// Generated: "DeptId==ENG;And$Status==1;"
// User searches: "Name" contains "John"
// Generated: "name~=John;"Use Cases
- Database Fields: Display
firstNamebut search onfirst_name - Enum Values: Display "Active Employee" but search on numeric status codes
- Normalized IDs: Display department names but search on department IDs
- Legacy APIs: Map modern UI field names to legacy backend fields
๐๏ธ Complete Feature Example
import React, { useState } from "react";
import { NcTable, type Column, type TableAction } from "nc-table-react";
type Employee = {
id: number;
name: string;
email: string;
department: string;
startDate: string;
isActive: boolean;
hasAccess: number;
salary: number;
status: "active" | "inactive" | "pending";
};
const columns: Column<Employee>[] = [
{
key: "name",
header: "Name",
searchable: true,
},
{
key: "department",
header: "Department",
searchable: true,
searchOverride: {
dataType: "select",
selectOptions: [
{ text: "Engineering", value: "Engineering" },
{ text: "Marketing", value: "Marketing" },
{ text: "Sales", value: "Sales" },
],
},
},
{
key: "startDate",
header: "Start Date",
searchable: true,
searchOverride: { dataType: "date" },
render: (emp) => new Date(emp.startDate).toLocaleDateString(),
},
{
key: "isActive",
header: "Is Active",
searchable: true,
searchOverride: { dataType: "boolean" },
render: (emp) => (emp.isActive ? "Active" : "Inactive"),
},
{
key: "salary",
header: "Salary",
searchable: true,
render: (emp) => `$${emp.salary.toLocaleString()}`,
},
];
const actions: TableAction<Employee>[] = [
{
label: "Edit",
onClick: (emp) => console.log("Edit", emp),
icon: "edit",
show: true, // Always show
},
{
label: "Activate",
onClick: (emp) => console.log("Activate", emp),
icon: "user",
show: (emp) => emp.status === "inactive", // Only show for inactive employees
},
{
label: "Deactivate",
onClick: (emp) => console.log("Deactivate", emp),
icon: "user",
show: (emp) => emp.status === "active", // Only show for active employees
},
{
label: "Delete",
onClick: (emp) => console.log("Delete", emp),
variant: "destructive",
icon: "trash",
show: (emp) => emp.role !== "Admin", // Hide delete for admins
},
];
export default function AdvancedExample() {
const [employees, setEmployees] = useState<Employee[]>([
// ... your data
]);
const handleAdvancedSearch = (filters) => {
console.log("Search filters:", filters);
// Handle filtering logic
};
const handleBulkAction = (
action: string,
selectedIds: (string | number)[]
) => {
console.log(`Bulk ${action} on:`, selectedIds);
};
const handleExport = (format: string, selectedIds?: (string | number)[]) => {
console.log(`Export as ${format}:`, selectedIds || "all");
};
const handleDelete = async (ids: (string | number)[]) => {
// Delete implementation
console.log("Deleting:", ids);
return { Succeeded: true, Message: "Deleted successfully" };
};
return (
<NcTable<Employee>
// Data
data={employees}
columns={columns}
actions={actions}
idField="id"
// Serial Numbers
showSerialNumber={true}
// Search & Filtering
showAdvancedSearch={true}
onAdvancedSearch={handleAdvancedSearch}
enableInternalSearch={true}
// Selection & Bulk Actions
selectable={true}
bulkActions={[
{ value: "export", label: "Export Selected" },
{ value: "archive", label: "Archive Selected" },
{ value: "delete", label: "Delete Selected", variant: "destructive" },
]}
onBulkAction={handleBulkAction}
// Export
exportOptions={[
{ format: "csv", label: "Export as CSV" },
{ format: "excel", label: "Export as Excel" },
{ format: "pdf", label: "Export as PDF" },
]}
onExport={handleExport}
// CRUD Operations
canDelete={true}
removeItemHandler={handleDelete}
// Pagination
enableInternalPagination={true}
pageSize={10}
paginationProps={{
showFirstLast: true,
showEllipsis: true,
maxVisiblePages: 5,
}}
// Settings
enableInternalSettings={true}
defaultSettings={{
pageSize: 10,
sortBy: "name",
sortDirection: "Asc",
}}
// Customization
searchPlaceholder="Search employees..."
emptyStateMessage="No employees found"
className="my-custom-table"
/>
);
}๐ API Reference
Core Props
| Prop | Type | Description |
|---|---|---|
data? |
T[] |
Static data array |
columns |
Column<T>[] |
Table column definitions |
handler? |
DataHandler<T> |
Async data function for server-side data |
actions? |
TableAction<T>[] |
Row action menu items |
idField? |
keyof T |
Unique identifier field (default: "Id") |
showSerialNumber? |
boolean |
Show serial number column (default: true) |
id? |
string |
Unique identifier for this table instance |
Search & Filtering
| Prop | Type | Description |
|---|---|---|
showAdvancedSearch? |
boolean |
Enable advanced search UI |
onAdvancedSearch? |
(filters: SearchFilter[]) => void |
Advanced search callback |
enableInternalSearch? |
boolean |
Enable internal search state management |
searchPlaceholder? |
string |
Search input placeholder |
Selection & Bulk Actions
| Prop | Type | Description |
|---|---|---|
selectable? |
boolean |
Enable row selection (default: true) |
selectedIds? |
(string | number)[] |
Controlled selected IDs |
onSelectionChange? |
(ids: (string | number)[]) => void |
Selection change callback |
bulkActions? |
BulkAction[] |
Bulk action definitions |
onBulkAction? |
(action: string, ids: (string | number)[]) => void |
Bulk action callback |
Export Features
| Prop | Type | Description |
|---|---|---|
exportOptions? |
ExportOption[] |
Export format options |
onExport? |
(format: string, ids?: (string | number)[]) => void |
Export callback |
CRUD Operations
| Prop | Type | Description |
|---|---|---|
canDelete? |
boolean |
Enable delete functionality |
removeItemHandler? |
(ids: (string | number)[]) => Promise<IApiResponse<unknown>> |
Delete handler |
Pagination
| Prop | Type | Description |
|---|---|---|
enableInternalPagination? |
boolean |
Enable internal pagination |
showPagination? |
boolean |
Show pagination controls |
pageSize? |
number |
Items per page |
currentPage? |
number |
Current page (controlled) |
totalItems? |
number |
Total item count (for external pagination) |
paginationProps? |
PaginationProps |
Pagination customization |
Settings & Customization
| Prop | Type | Description |
|---|---|---|
enableInternalSettings? |
boolean |
Enable settings management |
settings? |
TableSettings |
Controlled settings |
onSettingsChange? |
(settings: TableSettings) => void |
Settings change callback |
defaultSettings? |
Partial<TableSettings> |
Default settings |
className? |
string |
Custom CSS class |
loading? |
boolean |
External loading state |
Core Types
type Column<T> = {
key: string;
header: string;
render?: (item: T) => React.ReactNode;
width?: string;
visible?: boolean;
searchable?: boolean;
searchOverride?: NcTableSearchOverride;
};
type TableAction<T> = {
label: string;
icon?: React.ReactNode | string;
onClick: (item: T) => void;
variant?: "default" | "destructive";
className?: string;
separator?: boolean;
show?: boolean | ((item: T) => boolean);
};
type DataHandler<T> = (params: {
PageNumber: number;
PageSize: number;
Filter?: string;
Order?: string;
}) => Promise<IApiResponse<T[]>>;
interface IApiResponse<T> {
Succeeded: boolean;
Data: T;
Count: number;
Message?: string;
}
type NcTableSearchDataType = "text" | "date" | "select" | "boolean" | "YesOrNo";
interface NcTableSearchOverride {
key?: string; // Backend field name to use instead of column name
dataType: NcTableSearchDataType;
selectOptions?: Array<{ text: string; value: unknown }>;
}๐จ Styling
The component uses Tailwind CSS utility classes and is designed to work seamlessly in Tailwind projects. It also works without Tailwind, using sensible defaults.
Custom Styling
<NcTable
className="my-custom-table"
// ... other props
/>Responsive Design
The table is fully responsive and adapts to different screen sizes:
- Mobile: Stacked layout with horizontal scrolling
- Tablet: Optimized column widths
- Desktop: Full feature display
๐ Internationalization
The component is i18n ready and uses react-i18next for translations:
// Add translations to your i18n resources
const resources = {
en: {
translation: {
"Search...": "Search...",
"No items found": "No items found",
// ... other translations
},
},
};๐งช Testing
The package includes comprehensive test coverage. To run tests:
npm test๐ License
MIT License - see LICENSE file for details.
๐ค Contributing
Contributions are welcome! Please read our contributing guidelines and submit pull requests.
๐ Support
- ๐ Documentation: This README
- ๐ Issues: GitHub Issues
- ๐ฌ Discussions: GitHub Discussions
๐ Repository
- GitHub: NextCounsel/NCTable
- npm: nc-table-react
Built with โค๏ธ for modern React applications
๐ Changelog
v0.3.6 (Latest)
โจ New Features
- Unique Table Instance IDs: Added
idprop for unique table identification- Enables better isolation when using multiple table instances
- Supports DOM targeting, testing, analytics, and accessibility
- Optional prop with no breaking changes
๐ Documentation
- Updated README with ID prop usage and benefits
- Enhanced Multiple Instances Guide with ID best practices
- Added comprehensive examples and testing strategies
v0.3.5
โจ New Features
- Serial Number Column: Added
showSerialNumberprop to control display of row numbers- Defaults to
true - Shows pagination-aware serial numbers:
(currentPage - 1) ร pageSize + index + 1 - Header displays "S/No"
- Defaults to
- Conditional Table Actions: Added
showproperty toTableActioninterface- Static visibility:
show: true | false - Dynamic visibility:
show: (item) => boolean - Smart action column display - only shows when there are visible actions
- Static visibility:
๐ง Improvements
- Enhanced action rendering logic with conditional visibility
- Improved table layout with optional serial number column
- Better TypeScript support for new properties
๐ Documentation
- Added comprehensive examples for new features
- Updated API reference with new props
- Enhanced usage examples with conditional actions
v0.3.4
- Fixed React JSX runtime bundling issues
- Improved React 18.3.1 compatibility
- Resolved
ReactCurrentDispatchererrors
v0.3.3
- Initial stable release
- Core table functionality
- Advanced search and filtering
- Server-side data support