JSPM

  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 36
  • Score
    100M100P100Q54619F
  • License MIT

Advanced React table component with search functionality, backend filter integration, and customizable response formats

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-toast

Requirements:

  • 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.2

See 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 data array 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 handler function 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 data OR handler, 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 firstName but search on first_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

Built with โค๏ธ for modern React applications


๐Ÿ“ Changelog

v0.3.6 (Latest)

โœจ New Features

  • Unique Table Instance IDs: Added id prop 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 showSerialNumber prop to control display of row numbers
    • Defaults to true
    • Shows pagination-aware serial numbers: (currentPage - 1) ร— pageSize + index + 1
    • Header displays "S/No"
  • Conditional Table Actions: Added show property to TableAction interface
    • Static visibility: show: true | false
    • Dynamic visibility: show: (item) => boolean
    • Smart action column display - only shows when there are visible actions

๐Ÿ”ง 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 ReactCurrentDispatcher errors

v0.3.3

  • Initial stable release
  • Core table functionality
  • Advanced search and filtering
  • Server-side data support