JSPM

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

Advanced React table component with search functionality and backend filter integration

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
  • โœ… 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}`) },
];

export default function App() {
  return (
    <I18nextProvider i18n={i18n}>
      <NcTable<User>
        data={data}
        columns={columns}
        actions={actions}
        idField="id"
      />
    </I18nextProvider>
  );
}

๐Ÿ“Š 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",
  },
  {
    label: "Delete",
    onClick: (emp) => console.log("Delete", emp),
    variant: "destructive",
    icon: "trash",
  },
];

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"
      // 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")

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;
};

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