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

@ggwan/mdata-table

v0.1.6

Published

A reusable React DataTable component built with TanStack Table

Readme

@ggwan/MData Table

A reusable React DataTable component built with TanStack Table, TypeScript, and Tailwind CSS.

Features

  • Infinite Scrolling - Automatically load more data as you scroll
  • Search & Filtering - Built-in search with customizable additional filters
  • Sorting - Click column headers to sort data
  • Row Selection - Select single or multiple rows with checkboxes
  • Column Visibility - Toggle column visibility with dropdown menu
  • Export - Export all data or selected rows to CSV
  • Bulk Actions - Perform actions on selected rows
  • Customizable - Flexible styling and configuration
  • TypeScript - Full TypeScript support with proper types
  • Responsive - Works on mobile and desktop
  • Local Storage - Remembers table state and filters
  • Client-side & Server-side - Supports both pagination modes

Installation

npm install @ggwan/mdata-table
# or
pnpm add @ggwan/mdata-table
# or
yarn add @ggwan/mdata-table

Peer Dependencies

This package requires the following peer dependencies:

npm install react react-dom @tanstack/react-table lucide-react

Usage

Basic Example

import { DataTable } from '@ggwan/mdata-table'
import type { ColumnDef } from '@tanstack/react-table'

interface User {
  id: number
  name: string
  email: string
  username: string
  phone: string
  website: string
}
}

const columns: ColumnDef<User>[] = [
  {
    id: 'select',
    header: ({ table }) => (
      <input
        type="checkbox"
        checked={table.getIsAllPageRowsSelected()}
        onChange={(e) => table.toggleAllPageRowsSelected(e.target.checked)}
        aria-label="Select all"
      />
    ),
    cell: ({ row }) => (
      <input
        type="checkbox"
        checked={row.getIsSelected()}
        onChange={(e) => row.toggleSelected(e.target.checked)}
        aria-label="Select row"
      />
    ),
  },
  {
    accessorKey: 'name',
    header: 'Name',
  },
  {
    accessorKey: 'email',
    header: 'Email',
  },
  {
    accessorKey: 'username',
    header: 'Username',
  },
  {
    accessorKey: 'phone',
    header: 'Phone',
  },
  {
    accessorKey: 'website',
    header: 'Website',
    cell: ({ getValue }) => {
      const website = getValue() as string;
      return (
        <a 
          href={`https://${website}`} 
          target="_blank" 
          rel="noopener noreferrer"
          className="text-blue-600 hover:underline"
        >
          {website}
        </a>
      );
    },
  },
]

function MyComponent() {
  return (
    <DataTable
      columns={columns}
      endpoint="https://jsonplaceholder.typicode.com/users"
      tableId="users-table"
      pageSize={5}
      enableExport={true}
      additionalFilters={[
        {
          key: 'username',
          label: 'Username',
          type: 'text',
          placeholder: 'Filter by username...',
        },
        {
          key: 'email',
          label: 'Email',
          type: 'text',
          placeholder: 'Filter by email...',
        },
      ]}
    />
  )
}

Using the Hook Directly

import { useDataTable } from '@ggwan/mdata-table'

function CustomTable() {
  const {
    data,
    isLoading,
    searchQuery,
    setSearchQuery,
    filters,
    handleFilterChange,
    sorting,
    setSorting,
    totalRecords,
    exportData,
    resetAllFilters,
  } = useDataTable({
    endpoint: 'https://jsonplaceholder.typicode.com/users',
    pageSize: 10,
    tableId: 'my-table',
  })

  // Your custom table implementation
  return (
    <div>
      <input 
        value={searchQuery}
        onChange={(e) => setSearchQuery(e.target.value)}
        placeholder="Search..."
      />
      {/* Your custom table JSX */}
      <div>Total: {totalRecords} records</div>
    </div>
  )
}
## API Reference

### DataTable Props

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `columns` | `ColumnDef<TData>[]` | **required** | Column definitions using TanStack Table format |
| `endpoint` | `string` | **required** | API endpoint URL for data fetching |
| `tableId` | `string` | **required** | Unique identifier for the table (used for localStorage) |
| `pageSize` | `number` | `20` | Number of items per page |
| `enableExport` | `boolean` | `false` | Enable export functionality |
| `additionalFilters` | `AdditionalFilter[]` | `[]` | Additional filter controls |
| `onBulkDelete` | `() => void` | `undefined` | Callback for bulk delete action |
| `onBulkExportSelected` | `() => void` | `undefined` | Callback for exporting selected rows |

### AdditionalFilter

| Property | Type | Description |
|----------|------|-------------|
| `key` | `string` | Filter field key |
| `label` | `string` | Display label for the filter |
| `type` | `'text' \| 'select' \| 'number'` | Filter input type |
| `placeholder` | `string` | Placeholder text (for text/number inputs) |
| `options` | `Array<{value: string, label: string}>` | Options for select filters |

### useDataTable Hook

#### Parameters
```typescript
interface UseDataTableOptions {
  endpoint: string
  pageSize: number
  tableId: string
}

Returns

interface UseDataTableReturn<TData> {
  data: TData[]
  searchQuery: string
  setSearchQuery: (query: string) => void
  isLoading: boolean
  isFetchingNextPage: boolean
  sorting: SortingState | null
  setSorting: (sorting: SortingState | null) => void
  hasMore: boolean
  filters: FilterState
  handleFilterChange: (key: string, value: string | number | boolean) => void
  showAdditionalFilters: boolean
  setShowAdditionalFilters: (show: boolean) => void
  loadMoreRef: React.RefObject<HTMLDivElement>
  totalRecords: number
  resetAllFilters: () => void
  exportData: () => Promise<TData[]>
  isExporting: boolean
  rowSelection: Record<string, boolean>
  setRowSelection: (selection: Record<string, boolean>) => void
}

API Response Format

The DataTable supports two response formats:

1. Array Response (like JSONPlaceholder)

[
  { "id": 1, "name": "John", "email": "[email protected]" },
  { "id": 2, "name": "Jane", "email": "[email protected]" }
]

2. Object Response (with pagination)

{
  "data": [
    { "id": 1, "name": "John", "email": "[email protected]" }
  ],
  "total": 100,
  "hasMore": true,
  "next_page_url": "https://api.example.com/users?page=2"
}

Styling

The component uses Tailwind CSS classes. You can customize the appearance by:

  1. Using CSS Variables: Override the built-in CSS variables
  2. Custom Classes: Pass custom className props
  3. Tailwind Config: Extend your Tailwind configuration

CSS Variables

:root {
  --background: 0 0% 100%;
  --foreground: 222.2 84% 4.9%;
  --primary: 221.2 83.2% 53.3%;
  --muted: 210 40% 96%;
  --border: 214.3 31.8% 91.4%;

TypeScript Support

This package is written in TypeScript and provides full type safety. All types are exported:

import type { 
  AdditionalFilter, 
  FilterState, 
  SortingState, 
  UseDataTableOptions,
  UseDataTableReturn 
} from '@ggwan/mdata-table'

Features in Detail

🔍 Search & Filtering

  • Global search: Search across all visible columns
  • Additional filters: Add custom filter controls (text, select, number)
  • Real-time filtering: Results update as you type
  • Filter persistence: Filters are remembered in localStorage

📊 Sorting

  • Column sorting: Click any column header to sort
  • Multi-directional: Ascending, descending, and no sort
  • Type-aware: Proper sorting for strings, numbers, and dates
  • Sort persistence: Last sort order is remembered

🎯 Row Selection

  • Individual selection: Click checkbox on any row
  • Select all: Master checkbox to select all visible rows
  • Bulk actions: Perform actions on multiple selected rows
  • Selection persistence: Selections maintained during pagination

📱 Responsive Design

  • Mobile-friendly: Optimized for small screens
  • Touch interactions: Proper touch targets for mobile
  • Adaptive layout: Adjusts to different screen sizes

⚡ Performance

  • Infinite scrolling: Smooth pagination without page reloads
  • Client-side optimization: Smart filtering and sorting
  • Lazy loading: Only render visible rows
  • Memory efficient: Proper cleanup and state management

Troubleshooting

Common Issues

  1. Styles not loading: Make sure to import the CSS
  2. TypeScript errors: Ensure all peer dependencies are installed
  3. Data not showing: Check your API response format
  4. Filtering not working: Verify filter key matches your data structure

Debug Mode

Enable debug logging by setting localStorage:

localStorage.setItem('mdata-table-debug', 'true')

Contributing

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add some amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

License

This project is licensed under the MIT License - see the LICENSE file for details.

Changelog

v1.0.0

  • Initial release
  • DataTable component with infinite scrolling
  • Search and filtering capabilities
  • Row selection and bulk actions
  • Export functionality
  • Column visibility toggle
  • TypeScript support
  • Responsive design accessorKey: 'name', header: 'Name', }, { accessorKey: 'email', header: 'Email', }, { accessorKey: 'role', header: 'Role', }, ]

function MyComponent() { return ( <DataTable columns={columns} endpoint="/api/users" tableId="users-table" pageSize={20} enableExport={true} additionalFilters={[ { key: 'role', label: 'Role', type: 'select', options: [ { value: 'admin', label: 'Admin' }, { value: 'user', label: 'User' }, ], }, { key: 'department', label: 'Department', type: 'text', placeholder: 'Search by department...', }, ]} renderSubComponent={(row) => ( User Details ID: {row.original.id} Email: {row.original.email} )} /> ) }


## API Reference

### DataTable Props

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `columns` | `ColumnDef<TData, TValue>[]` | - | **Required.** Column definitions for the table |
| `endpoint` | `string` | - | **Required.** API endpoint for fetching data |
| `tableId` | `string` | - | **Required.** Unique identifier for the table (used for localStorage) |
| `pageSize` | `number` | `5` | Number of items per page |
| `additionalFilters` | `AdditionalFilter[]` | `[]` | Additional filter options |
| `enableExport` | `boolean` | `false` | Enable CSV export functionality |
| `renderSubComponent` | `(row: Row<TData>) => ReactNode` | - | Render expandable row content |

### API Response Format

Your API endpoint should return data in this format:

```json
{
  "data": [...], // Array of data items
  "total": 100,  // Total number of records
  "hasMore": true // Whether there are more pages
}

API Parameters

The component will send these query parameters to your endpoint:

  • page - Current page number
  • limit - Items per page
  • search - Search query
  • sortBy - Column to sort by
  • sortOrder - Sort direction ('asc' or 'desc')
  • Additional filter parameters

Styling

This component uses Tailwind CSS. Make sure to include the styles in your project:

import 'mdata-table/styles'

Or if you want to customize the styles, you can import the base CSS and override with your own classes.

TypeScript

The package includes full TypeScript definitions. Key types:

import type {
  AdditionalFilter,
  FilterState,
  SortingState,
  UseDataTableReturn,
  UseDataTableOptions
} from 'mdata-table'

License

MIT License - see LICENSE file for details.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:

export default tseslint.config([
  globalIgnores(['dist']),
  {
    files: ['**/*.{ts,tsx}'],
    extends: [
      // Other configs...

      // Remove tseslint.configs.recommended and replace with this
      ...tseslint.configs.recommendedTypeChecked,
      // Alternatively, use this for stricter rules
      ...tseslint.configs.strictTypeChecked,
      // Optionally, add this for stylistic rules
      ...tseslint.configs.stylisticTypeChecked,

      // Other configs...
    ],
    languageOptions: {
      parserOptions: {
        project: ['./tsconfig.node.json', './tsconfig.app.json'],
        tsconfigRootDir: import.meta.dirname,
      },
      // other options...
    },
  },
])

You can also install eslint-plugin-react-x and eslint-plugin-react-dom for React-specific lint rules:

// eslint.config.js
import reactX from 'eslint-plugin-react-x'
import reactDom from 'eslint-plugin-react-dom'

export default tseslint.config([
  globalIgnores(['dist']),
  {
    files: ['**/*.{ts,tsx}'],
    extends: [
      // Other configs...
      // Enable lint rules for React
      reactX.configs['recommended-typescript'],
      // Enable lint rules for React DOM
      reactDom.configs.recommended,
    ],
    languageOptions: {
      parserOptions: {
        project: ['./tsconfig.node.json', './tsconfig.app.json'],
        tsconfigRootDir: import.meta.dirname,
      },
      // other options...
    },
  },
])