npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@alisdev/fe-kit-table

v2.0.1

Published

A comprehensive, highly scalable table management library for React. It seamlessly switches between fully client-side and fully server-side operations while providing a rich, responsive UI component out of the box.

Readme

@alisdev/fe-kit-table (V2)

A comprehensive, highly scalable table management library for React. It seamlessly switches between fully client-side and fully server-side operations while providing a rich, responsive UI component out of the box.

Features

  • 🔄 Hybrid Data Model: Pass an array to handle everything in memory, or pass a fetchFn to delegate pagination, sorting, and filtering to your API.
  • 📌 Frozen Columns: Pin columns to the left or right side of the table with auto-calculated sticky CSS offsets.
  • 📏 Sticky Header: Keep column headers visible while scrolling through hundreds of rows.
  • 📱 Mobile Card View: Automatically detects viewports. Under 768px (configurable), rows transform into vertical cards with expandable hidden fields and dropdown actions.
  • Selection System: Built-in tracking for single row and bulk selections.
  • 👁️ Column Visibility: Programmatically toggle columns, with an out-of-the-box dropdown UI provided by Table.ColumnToggle.
  • Expandable Rows: Support nested content below rows. Includes renderAsync for fetching details only when a row is expanded.
  • 📥 Export: Built-in CSV and Excel export. Automatically respects column visibility and ignores custom React render functions in favor of raw data.

Installation

pnpm add @alisdev/fe-kit-table xlsx

Basic Usage (<Table /> Component)

The compound Table component wraps the useTable hook and provides a styled, ready-to-use interface.

import { Table, ColumnDef } from "@alisdev/fe-kit-table";

interface User {
  id: string;
  name: string;
  email: string;
  role: string;
}

const columns: ColumnDef<User>[] = [
  { 
    key: "name", 
    label: "Name", 
    sortable: true, 
    pinned: "left", // Stays visible when scrolling right
    width: "200px", 
    mobilePriority: 1 // Acts as the title in Mobile Card view
  },
  { 
    key: "email", 
    label: "Email", 
    filterable: true,
    mobilePriority: 2 // Always visible in Mobile Card body
  },
  { 
    key: "role", 
    label: "Role",
    render: (val) => <span className="badge">{val}</span>
  },
  { 
    key: "actions", 
    label: "Actions", 
    pinned: "right",
    actions: (row) => <button onClick={() => editUser(row.id)}>Edit</button>
  }
];

export function UsersPage() {
  return (
    <Table<User>
      // Automatically triggers server-side mode
      fetchFn={async (params) => {
        // params = { page: 1, size: 10, search: "", sort: [], filters: [] }
        const res = await fetch(`/api/users?${new URLSearchParams(params as any)}`);
        const json = await res.json();
        // Return PageResult shape
        return {
          content: json.data,
          total: json.totalElements,
          page: json.number,
          size: json.size,
          totalPages: json.totalPages,
          hasNext: !json.last,
          hasPrev: !json.first
        };
      }}
      columns={columns}
      pagination={{ size: 20 }}
      selection={true} // Enables checkbox column
      showPagination={true}
      showColumnToggle={true}
      showExport={true}
      exportFormats={["csv", "excel"]}
    />
  );
}

Advanced Customization (useTable Hook)

If you need a completely bespoke UI layout, bypass the <Table /> component and use the useTable hook directly.

import { useTable, Table } from "@alisdev/fe-kit-table";

export function CustomDashboardTable({ localData }) {
  const table = useTable({
    data: localData, // Triggers Client-Side mode
    columns: myColumns,
  });

  return (
    <div className="custom-wrapper">
      {/* Custom Toolbar */}
      <div className="toolbar">
        <input 
          placeholder="Global Search..." 
          value={table.search} 
          onChange={e => table.setSearch(e.target.value)} 
        />
        <span>{table.selectedRows.length} items selected</span>
        
        {/* You can still use the sub-components manually! */}
        <Table.ColumnToggle table={table} />
        <Table.ExportButton table={table} />
      </div>

      {/* Manual Table Render */}
      <div className="scroll-area">
        <table>
          <thead>
            <tr>
              {table.visibleColumns.map(col => (
                <th 
                   key={col.key as string}
                   onClick={() => col.sortable && table.addSort(col.key as string, "asc")}
                >
                  {col.label} 
                  {table.sorts.find(s => s.key === col.key)?.order === 'asc' ? '↑' : ''}
                </th>
              ))}
            </tr>
          </thead>
          <tbody>
             {/* Render logic... */}
          </tbody>
        </table>
      </div>
      
      <Table.Pagination table={table} />
    </div>
  );
}

Core Data Structures

ColumnDef<T>

Defines how a specific column behaves in desktop, mobile, and export scenarios.

| Property | Type | Description | | :--- | :--- | :--- | | key | keyof T \| string | The property name in your data object. | | label | string | The text rendered in the header <th> and export CSV headers. | | sortable | boolean | Enables click-to-sort logic. | | filterable | boolean | Flags column for filter UI usage. | | width | string | e.g. "150px". Strongly recommended for pinned columns. | | pinned | "left" \| "right" | Freezes the column during horizontal scroll. | | visible | boolean | If false, column starts hidden. | | render | (val, row) => ReactNode | Custom JSX to render inside the cell. | | actions | (row) => ReactNode | Special render for action buttons (ignored in CSV exports, renders as dropdown in mobile). | | mobilePriority| number | 1 = Card Title, 2 = Visible in body, 3+ = Hidden inside "Show More" accordion. |

ExpandableConfig<T>

Configures the behavior for nested rows.

| Property | Type | Description | | :--- | :--- | :--- | | multiple | boolean | If true, expanding row B does not collapse row A. | | render | (row) => ReactNode | Synchronous render for the expanded area. | | renderAsync| (row) => Promise<ReactNode> | Fetches data when expanded. Displays loadingComponent while resolving. | | cacheResult| boolean | If true, renderAsync is only fired the first time a row is expanded. |

Server-Side Params (TableParams)

When fetchFn is triggered, it receives this object. You map this to your backend's API format.

interface TableParams {
  page: number; // 1-indexed
  size: number;
  search?: string;
  sort?: { key: string; order: "asc" | "desc"; priority: number }[];
  filters?: { key: string; value: unknown; operator?: "eq" | "like" | "gte" | "lte" | "in" }[];
}