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

@alsocoder/apna-table

v0.1.2

Published

A flexible React data table with client/server modes, sorting, filters, pagination, row selection, and bulk actions.

Readme

@alsocoder/apna-table

A flexible React data table with client and server modes, multi-column sorting, filters (ApnaInput / ApnaSelect / ApnaDatePicker), pagination, row selection, bulk actions, skeleton loading, and declarative row actions.

Install

npm install @alsocoder/apna-table

Filter/pagination UI internally uses ApnaInput, ApnaSelect, and ApnaDatePicker — you do not need to install or import them separately.

CSS import

import "@alsocoder/apna-table/styles.css"

This single stylesheet includes table styles plus the bundled filter/pagination component styles.

Quick start

import { ApnaTable, type ApnaTableColumn } from "@alsocoder/apna-table"
import "@alsocoder/apna-table/styles.css"

type User = { id: string; name: string; email: string }

const columns: ApnaTableColumn<User>[] = [
  { id: "name", header: "Name", accessorKey: "name", sortable: true },
  { id: "email", header: "Email", accessorKey: "email", sortable: true },
]

<ApnaTable
  mode="client"
  title="Users"
  description="Manage users"
  columns={columns}
  rowKey="id"
  data={users}
  showSerialNumber
  selectable
  filters={[
    {
      key: "search",
      type: "input",
      placeholder: "Search…",
      value: search,
      onValueChange: setSearch,
    },
  ]}
  actions={{
    variant: "dropdown",
    position: "end",
    items: [
      { key: "view", label: "View", icon: <EyeIcon />, onAction: (row) => view(row) },
    ],
  }}
  headerActions={<button>Add user</button>}
/>

Modes

Client mode

Pass data once. The table handles local pagination and sorting.

<ApnaTable mode="client" data={items} columns={columns} rowKey="id" />

Server mode

Pass fetchData. The table sends page, pageSize, sort, and debounced filters.

<ApnaTable
  mode="server"
  fetchData={async ({ page, pageSize, sort, filters, signal }) => {
    const res = await listUsers({ page, pageSize, sort, ...filters }, { signal })
    return { items: res.items, total: res.total }
  }}
  columns={columns}
  rowKey="id"
/>

Columns

type ApnaTableColumn<T> = {
  id: string
  header: ReactNode
  accessorKey?: keyof T
  cell?: (ctx: { row: T; index: number; serial: number }) => ReactNode
  sortable?: boolean
  sortKey?: string
  hidden?: boolean | "sm" | "md" | "lg"
  align?: "left" | "center" | "right"
}

Column order:

| Feature | Position | |---------|----------| | S.No | First (optional) | | Checkbox | After S.No (optional) | | Actions (position: "start") | After checkbox | | User columns | Middle | | Actions (position: "end") | Last (default) |

Filters

Supported filter types:

| type | Component | |------|-----------| | input | ApnaInput | | select | ApnaSelect | | date | ApnaDatePicker | | dateRange | ApnaDateRangePicker |

First filter stays visible. Extra filters open via Show filters.

Row actions

Dropdown (ellipsis menu)

actions={{
  variant: "dropdown",
  position: "end",
  items: [
    { key: "edit", label: "Edit", icon: <EditIcon />, onAction: editRow },
    { key: "delete", label: "Delete", icon: <TrashIcon />, destructive: true, onAction: deleteRow },
  ],
}}

Inline (side-by-side buttons)

actions={{
  variant: "inline",
  position: "start",
  items: [
    { key: "edit", label: "Edit", icon: <EditIcon />, iconOnly: true, onAction: editRow },
  ],
}}
  • show: false hides the actions column
  • Each action supports label + optional icon (consumer-provided ReactNode)

Bulk actions

Shown in the toolbar when rows are selected:

bulkActions={[
  {
    key: "archive",
    label: "Archive selected",
    icon: <ArchiveIcon />,
    onAction: (rows, keys) => archiveMany(rows),
  },
]}

Sorting

type ApnaTableSort = { field: string; direction: "asc" | "desc" }
  • Click a sortable header to cycle asc → desc → none
  • multiSort enables multiple sort columns
  • Server mode sends sort=createdAt:desc,title:asc

Skeleton loading

  • Server mode: skeleton shows while fetchData is in flight (after filter debounce)
  • Client mode: brief skeleton while filters/sort/page apply
  • skeletonRows controls placeholder row count (defaults to pageSize)
  • Pagination is disabled while loading is true

Backend API contract

Request query params

| Param | Example | |-------|---------| | page | 1 | | pageSize | 25 | | sort | createdAt:desc,title:asc | | filter keys | search=foo&status=active&dateFrom=2026-01-01&dateTo=2026-01-31 |

Date range filters are sent as {key}From and {key}To.

Response shape

{
  "success": true,
  "data": [],
  "meta": { "page": 1, "pageSize": 25, "total": 142, "totalPages": 6 }
}

Node/Express + MongoDB example

function parseSort(sort?: string): Record<string, 1 | -1> {
  if (!sort) return { createdAt: -1 }
  return Object.fromEntries(
    sort.split(",").map((part) => {
      const [field, dir] = part.split(":")
      return [field, dir === "asc" ? 1 : -1]
    })
  )
}

export async function listUsers({ page, pageSize, search, status, sort }) {
  const filter: Record<string, unknown> = {}
  if (search) filter.$or = [{ name: new RegExp(search, "i") }, { email: new RegExp(search, "i") }]
  if (status && status !== "all") filter.status = status

  const skip = (page - 1) * pageSize
  const [total, items] = await Promise.all([
    User.countDocuments(filter),
    User.find(filter).sort(parseSort(sort)).skip(skip).limit(pageSize),
  ])

  return { items, total, page, pageSize }
}

Hooks

import { useApnaTable, useApnaTablePagination, buildSortQuery } from "@alsocoder/apna-table"

const table = useApnaTable({ mode: "server", fetchData, columns, rowKey: "id" })
// rows, loading, pagination, sort, selection, refetch

Customization

  • Vanilla CSS with --apna-table-* variables
  • Falls back to shadcn/Tailwind tokens (--border, --foreground, --primary, etc.)
  • classNames object + className / tableClassName / headerClassName shortcuts
  • icons prop to override built-in SVG icons

Playground

cd playground
npm install
npm run dev

Mahima migration

Before:

<DataTable title="Services" filters={filters} pagination={pagination}>
  <Table>...</Table>
</DataTable>

After:

<ApnaTable
  mode="server"
  title="Services"
  columns={columns}
  fetchData={listServices}
  filters={filters}
  showSerialNumber
  actions={{ variant: "dropdown", items: [...] }}
  headerActions={<Button>Add service</Button>}
/>

License

MIT