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 🙏

© 2025 – Pkg Stats / Ryan Hefner

@txtony/gitboard-table

v0.1.21

Published

A reusable GitHub-Projects-style table component, built with React + TypeScript, TailwindCSS, and theme support

Downloads

2,192

Readme

GitBoard Table

A reusable GitHub-Projects-style table component, built with React + TypeScript, TailwindCSS, and theme support. Designed for internal tools, admin dashboards, project boards, and data-heavy applications.

TypeScript React Tests License

🚀 Getting Started

Installation

npm install @txtony/gitboard-table

Or with yarn:

yarn add @txtony/gitboard-table

Peer Dependencies

Make sure you have the required peer dependencies installed:

npm install react react-dom tailwindcss

Basic Usage

import { useState } from 'react';
import { GitBoardTable } from '@txtony/gitboard-table';
import type { FieldDefinition, Row } from '@txtony/gitboard-table';
import '@txtony/gitboard-table/styles.css';

const fields: FieldDefinition[] = [
  {
    id: 'fld_title_1',
    name: 'Title',
    type: 'title',
    visible: true,
  },
  {
    id: 'fld_status_1',
    name: 'Status',
    type: 'single-select',
    visible: true,
    options: [
      { id: 'opt_todo', label: 'To Do', color: '#6b7280' },
      { id: 'opt_inprog', label: 'In Progress', color: '#3b82f6' },
      { id: 'opt_done', label: 'Done', color: '#10b981' },
    ],
  },
  {
    id: 'fld_assignee_1',
    name: 'Assignee',
    type: 'assignee',
    visible: true,
    options: [
      { id: 'usr_1', label: 'Alice', color: '#8b5cf6' },
      { id: 'usr_2', label: 'Bob', color: '#f59e0b' },
    ],
  },
  {
    id: 'fld_docs_1',
    name: 'Documentation',
    type: 'link',
    visible: true,
  },
];

const initialRows: Row[] = [
  {
    id: 'row_1',
    values: {
      fld_title_1: 'Implement export system',
      fld_status_1: 'opt_inprog',
      fld_assignee_1: 'usr_1',
      fld_docs_1: 'https://docs.example.com/export',
    },
  },
];

export function App() {
  const [rows, setRows] = useState<Row[]>(initialRows);

  return (
    <GitBoardTable
      fields={fields}
      rows={rows}
      theme="light"
      tableId="my-project-board"
      onChange={setRows}
    />
  );
}

Tailwind Configuration

Add the component to your Tailwind config to enable proper styling:

// tailwind.config.js
module.exports = {
  content: [
    './src/**/*.{js,jsx,ts,tsx}',
    './node_modules/@txtony/gitboard-table/**/*.{js,jsx,ts,tsx}',
  ],
  // ... rest of your config
};

✨ Features

🎛 Configurable Field Types

  • Title - Primary text field for row identification
  • Text - General text input
  • Number - Numeric values with validation
  • Date - Date picker with calendar UI
  • Single-select - Dropdown with one choice
  • Multi-select - Dropdown with multiple choices
  • Assignee - User assignment with colored badges
  • Iteration - Sprint/milestone tracking
  • Link - Clickable URLs with automatic https:// prepending

✍️ Inline Cell Editing

  • Double-click to edit - Single click selects, double-click enters edit mode
  • Field-aware editors - Specialized input for each field type
  • Keyboard shortcuts:
    • Enter to commit changes
    • Escape to cancel
    • Arrow keys for navigation
    • Ctrl+Click / Cmd+Click for multi-select
    • Shift+Click for range selection
  • Visual feedback - Blue ring on selected cells

🎯 Cell Selection & Bulk Operations

  • Single-click selection - Click any cell to select it
  • Drag-fill support - Excel-style drag handle for bulk updates (works automatically!)
  • Visual indicators - Selected cells show blue ring, drag targets show green highlight
  • Automatic updates - Component handles bulk edits internally, consistent with single cell edits
  • Optional events - onBulkUpdate callback provides details for analytics/logging

🔍 Advanced Filtering

  • Inline filter syntax - Type filters directly: status:equals:done title:contains:login
  • Smart autocomplete - Context-aware suggestions for fields, operators, and values
  • Multiple filters - Combine multiple filters with spaces
  • Negative filters - Use - prefix to exclude: -status:equals:done
  • Comparison operators - For numbers: points:>:3, points:<=:5
  • Quoted values - Support spaces: title:"login page"
  • Supported operators:
    • contains / is - Contains text
    • equals / = - Exact match
    • not-equals / != - Does not match
    • is-empty - Field is empty
    • is-not-empty - Field has value
    • >, >=, <, <= - Numeric comparisons

📖 See FILTERS.md for complete filter system documentation including:

  • Detailed filter operators and syntax
  • Mock data examples for all filter scenarios
  • Complete implementation examples
  • Filter patterns and troubleshooting
  • Testing filters

📖 See FILTER_SYSTEM.md for filter implementation details and Firebase/Firestore adapter instructions.

👁️ View Management

Save and switch between different table configurations (filters, sorting, column visibility):

  • Multiple views - Create unlimited views with different configurations
  • Tab navigation - Click view tabs to switch between saved views
  • Quick create - Click "+ Add view" button to create new views instantly
  • Rename views - Double-click any view tab to edit its name
  • Auto-apply - Switching views automatically applies filters, sorting, and column visibility
  • Unsaved changes indicator - "Save" button appears when filters are modified
  • Save current state - Click "Save" to update view with current filters
  • View properties:
    • Name (editable)
    • Filters (can be saved and restored)
    • Sort configuration
    • Column visibility and order (auto-saved on change)
    • Filter count badge
  • Auto-save features:
    • Column reordering automatically updates the current view
    • Column visibility changes (show/hide) automatically update the current view
    • Filter changes require manual save (via "Save" button)

Example:

import { useState } from 'react';
import { GitBoardTable } from '@txtony/gitboard-table';
import type { ViewConfig } from '@txtony/gitboard-table';

const initialViews: ViewConfig[] = [
  {
    id: 'view_all',
    name: 'All Tasks',
    columns: ['fld_title', 'fld_status', 'fld_assignee'],
    sortBy: null,
    filters: [],
    groupBy: null,
  },
  {
    id: 'view_in_progress',
    name: 'In Progress',
    columns: ['fld_title', 'fld_assignee'],
    sortBy: { field: 'fld_title', direction: 'asc' },
    filters: [
      { field: 'fld_status', operator: 'equals', value: 'opt_inprog' }
    ],
    groupBy: null,
  },
];

function App() {
  const [views, setViews] = useState<ViewConfig[]>(initialViews);

  const handleCreateView = (view: ViewConfig) => {
    console.log('New view created:', view);
    setViews(prev => [...prev, view]);
  };

  const handleUpdateView = (view: ViewConfig) => {
    console.log('View updated:', view);
    setViews(prev => prev.map(v => v.id === view.id ? view : v));
  };

  const handleViewChange = (view: ViewConfig) => {
    console.log('Switched to view:', view.name);
  };

  return (
    <GitBoardTable
      fields={fields}
      rows={rows}
      views={views}
      onViewChange={handleViewChange}
      onCreateView={handleCreateView}
      onUpdateView={handleUpdateView}
    />
  );
}

📖 See VIEWS.md for complete view management documentation including:

  • Detailed view lifecycle and data structures
  • Mock data examples for all view scenarios
  • Complete event payloads and callback examples
  • Implementation patterns and troubleshooting
  • Auto-save vs manual save behavior

📊 Sorting & Organization

  • Click to sort - Click column headers to sort ascending/descending
  • Visual indicators - Arrow shows current sort direction
  • Multi-type support - Smart sorting for text, numbers, dates, and selects
  • Persistent state - Sort preferences saved to localStorage

📁 Grouping

  • Group by field - Organize rows into collapsible groups by any field (Status, Assignee, Priority, etc.)
  • Collapsible groups - Each group can be expanded/collapsed independently
  • Group headers - Display group value and row count
  • View-specific - Each view can have its own grouping configuration
  • Empty handling - Rows with empty values grouped as "No [FieldName]"
  • Smart sorting - Groups sorted alphabetically with empty group last
  • Supported fields - single-select, multi-select, text, title, assignee, iteration
  • See GROUPING.md for complete documentation

🔄 Column Management

  • Drag to reorder - Rearrange columns by dragging headers
  • Resize columns - Drag the right edge of any column header to resize
  • Show/Hide columns - Toggle column visibility via eye icon menu
  • Minimum width - Columns cannot be smaller than 80px
  • Persistent layout - Column order, widths, and visibility saved automatically
  • Visual feedback - Blue highlight on resize handle hover
  • Column counter - Shows visible/total count (e.g., "3/4")

✅ Row Selection

  • Single selection - Click row number to select a single row
  • Multi-select - Hold Ctrl (or Cmd on Mac) and click to add rows to selection
  • Range selection - Hold Shift and click to select all rows between anchor and target
  • Checkbox selection - Select individual or all rows via checkboxes
  • Keyboard shortcuts:
    • Click - Select single row
    • Ctrl+Click / Cmd+Click - Multi-select (add to selection)
    • Shift+Click - Range select (select all rows between)
    • Double-click row number - Open row detail panel
  • Visual feedback - Selected rows highlighted with blue background
  • Event emission - onRowSelect callback provides selection details with full row data
  • Toolbar actions - Context-sensitive toolbar for selected rows

Example:

import { GitBoardTable } from '@txtony/gitboard-table';
import type { RowSelectionEvent } from '@txtony/gitboard-table';

function App() {
  const handleRowSelect = (event: RowSelectionEvent) => {
    console.log('Selected rows:', event.selectedRows.length);
    console.log('Selection type:', event.lastAction); // 'select', 'multi', 'range', 'deselect', 'clear'

    // Access full row data
    event.selectedRows.forEach(row => {
      console.log('Row:', row.id, row.values);
    });

    // Track analytics
    if (event.lastAction === 'range') {
      trackEvent('range_selection', { count: event.selectedRows.length });
    }
  };

  return (
    <GitBoardTable
      fields={fields}
      rows={rows}
      onRowSelect={handleRowSelect}
    />
  );
}

📝 Add Rows

  • Quick add - Type title and press Enter to create new row
  • Empty row creation - Add blank rows via toolbar
  • Auto-scroll - Newly added rows scroll into view

🔄 Row Reordering

Drag and drop rows to reorder them manually:

  • Drag-and-drop interface - Click and drag any row to a new position
  • Visual feedback - Rows show dragging state and drop target indicators
  • Event emission - onRowsReorder callback provides complete reorder details
  • Controlled updates - Parent component controls the final row order
  • Works with filters - Reorder filtered/sorted rows seamlessly
  • Payload includes:
    • fromIndex - Original position of moved row
    • toIndex - New position of moved row
    • rows - Complete reordered rows array
    • movedRow - The specific row that was moved

Example:

import { GitBoardTable } from '@txtony/gitboard-table';
import type { RowReorderEvent } from '@txtony/gitboard-table';

function App() {
  const [rows, setRows] = useState<Row[]>(initialRows);

  const handleRowsReorder = async (event: RowReorderEvent) => {
    console.log('Row moved from', event.fromIndex, 'to', event.toIndex);

    // Update local state immediately
    setRows(event.rows);

    // Persist to backend
    await api.saveRowOrder({
      rowId: event.movedRow.id,
      newPosition: event.toIndex
    });
  };

  return (
    <GitBoardTable
      fields={fields}
      rows={rows}
      onChange={setRows}
      onRowsReorder={handleRowsReorder}
    />
  );
}

📖 See ROWS.md for complete row reordering documentation including:

  • Detailed event payload structure
  • Mock data examples for all reordering scenarios
  • Complete implementation patterns
  • Backend integration examples
  • Testing row reordering
  • Troubleshooting common issues

🎨 Theme Support

  • Light & Dark modes - GitHub-inspired color schemes
  • CSS variables - Easily customizable via CSS custom properties
  • Seamless switching - Change themes at runtime
  • System detection - Can follow system theme preference

💾 Automatic Persistence

  • localStorage integration - Table state automatically saved
  • Unique table IDs - Multiple tables with separate states
  • Saved state includes:
    • Column order
    • Column widths
    • Column visibility (show/hide)
    • Sort configuration
    • Active filters
  • Instant restore - State loaded on component mount

🎯 Advanced Features

  • Context menus - Right-click for row/column actions (see below for details)
  • Keyboard navigation - Full keyboard accessibility
  • Loading states - Graceful handling of async operations
  • Error boundaries - Resilient error handling
  • Type-safe - Complete TypeScript coverage
  • Accessible - ARIA labels and semantic HTML

🖱️ Row Context Menu

Right-click on any row to access context menu actions:

  • Built-in actions:
    • Show - Opens the row detail panel
    • Delete - Deletes the row (with confirmation dialog)
  • Custom actions - Add your own actions to the context menu
  • Event dispatching - Receive events when custom actions are clicked

Custom Actions Structure:

interface CustomAction {
  name: string;      // Unique identifier for the action
  label: string;     // Display text in the menu
  icon?: string;     // Optional icon (emoji or text)
}

Example - Adding Custom Actions:

import { GitBoardTable } from '@txtony/gitboard-table';
import type { CustomAction, ContextMenuClickEvent, Row } from '@txtony/gitboard-table';

function App() {
  const [rows, setRows] = useState<Row[]>(initialRows);

  // Define custom actions
  const customActions: CustomAction[] = [
    { name: 'archive', label: 'Archive', icon: '📦' },
    { name: 'duplicate', label: 'Duplicate', icon: '📋' },
    { name: 'export', label: 'Export as PDF', icon: '📄' },
    { name: 'share', label: 'Share' }, // No icon
  ];

  // Handle custom action clicks
  const handleContextMenuClick = (event: ContextMenuClickEvent) => {
    console.log('Action:', event.actionName);
    console.log('Row:', event.row);

    switch (event.actionName) {
      case 'archive':
        // Archive the row
        archiveRow(event.row.id);
        break;

      case 'duplicate':
        // Duplicate the row
        const duplicatedRow = {
          ...event.row,
          id: generateNewId(),
          values: { ...event.row.values }
        };
        setRows(prev => [...prev, duplicatedRow]);
        break;

      case 'export':
        // Export row to PDF
        exportRowToPDF(event.row);
        break;

      case 'share':
        // Open share dialog
        openShareDialog(event.row);
        break;
    }
  };

  return (
    <GitBoardTable
      fields={fields}
      rows={rows}
      onChange={setRows}
      customActions={customActions}
      onContextMenuClick={handleContextMenuClick}
    />
  );
}

How It Works:

  1. Right-click any row to open the context menu
  2. Built-in actions (Show, Delete) appear at the top
  3. A divider separates built-in from custom actions
  4. Custom actions appear below with optional icons
  5. Clicking a custom action dispatches onContextMenuClick event
  6. Event includes the action name and full row data
  7. Handle the action in your callback function

Event Payload:

interface ContextMenuClickEvent {
  type: 'context-menu-click';
  actionName: string;  // The name of the clicked custom action
  row: Row;            // Complete row data including id and values
}

Use Cases:

  • Archive completed tasks
  • Duplicate rows with similar data
  • Export individual rows
  • Share rows via email/link
  • Move rows to different projects
  • Trigger custom workflows
  • Open external tools with row data

📋 Available NPM Commands

Development

# Start development server with hot reload
npm run dev

Starts Vite dev server at http://localhost:5173

Building

# Build production bundle
npm run build

Outputs to dist/ directory with ESM, CJS, and type definitions

# Preview production build
npm run preview

Testing

# Run tests in watch mode
npm test
# Run tests with UI
npm run test:ui

Opens Vitest UI at http://localhost:51204

# Generate coverage report
npm run test:coverage

Outputs coverage to coverage/ directory

Code Quality

# Run ESLint
npm run lint

Checks TypeScript and React code for errors and style issues

Storybook

# Start Storybook dev server
npm run storybook

Opens Storybook at http://localhost:6006 with interactive component demos

# Build static Storybook
npm run build-storybook

Outputs to storybook-static/ directory

📚 API Reference

GitBoardTable Props

interface GitBoardTableProps {
  // Required
  fields: FieldDefinition[];      // Column definitions
  rows: Row[];                    // Table data

  // Optional
  theme?: 'light' | 'dark';       // Theme mode (default: 'light')
  tableId?: string;               // Unique ID for state persistence
  onChange?: (rows: Row[]) => void;           // Called when rows change
  onRowOpen?: (row: Row) => void;             // Called when row is clicked
  onFieldChange?: (fields: FieldDefinition[]) => void;  // Called when fields change
  onBulkUpdate?: (event: BulkUpdateEvent) => void;      // Called on drag-fill
  onRowsReorder?: (event: RowReorderEvent) => void;    // Called when rows are reordered
  onRowSelect?: (event: RowSelectionEvent) => void;    // Called when row selection changes
  onContentUpdate?: (rowId: UID, content: RowContent) => void;  // Called when row content updates
  contentResolver?: (id: UID) => Promise<ContentItem>;  // Async content loader
  users?: User[];                 // Available users for assignee field
  iterations?: Iteration[];       // Available iterations
  initialView?: ViewConfig;       // Initial sorting/filtering state
  views?: ViewConfig[];           // Array of available views
  onViewChange?: (view: ViewConfig) => void;   // Called when view is switched
  onCreateView?: (view: ViewConfig) => void;   // Called when new view is created
  onUpdateView?: (view: ViewConfig) => void;   // Called when view is updated
  onDeleteView?: (viewId: string) => void;     // Called when view is deleted
  customActions?: CustomAction[];              // Custom actions for row context menu
  onContextMenuClick?: (event: ContextMenuClickEvent) => void;  // Called when custom action clicked
}

FieldDefinition

interface FieldDefinition {
  id: string;                     // Unique field identifier
  name: string;                   // Display name
  type: FieldType;                // Field type (see below)
  visible: boolean;               // Show/hide in table
  width?: number;                 // Column width in pixels
  options?: FieldOption[];        // For select/assignee/iteration types
}

type FieldType =
  | 'title'
  | 'text'
  | 'number'
  | 'date'
  | 'single-select'
  | 'multi-select'
  | 'assignee'
  | 'iteration'
  | 'link';

Row

interface Row {
  id: string;                     // Unique row identifier
  values: Record<string, CellValue>;  // Field values keyed by field.id
  contentId?: string;             // Optional content panel ID
}

type CellValue = string | number | boolean | null | string[] | undefined;

BulkUpdateEvent

interface BulkUpdateEvent {
  sourceCell: {
    rowId: string;
    fieldId: string;
    value: CellValue;
  };
  targetCells: BulkUpdateTarget[];
  field: FieldDefinition;
}

interface BulkUpdateTarget {
  rowId: string;
  fieldId: string;
  currentValue: CellValue;
}

RowSelectionEvent

interface RowSelectionEvent {
  selectedRowIds: string[];        // Array of selected row IDs
  selectedRows: Row[];             // Full row objects with all data
  lastAction: 'select' | 'deselect' | 'range' | 'multi' | 'clear';  // Type of selection action
}

ViewConfig

interface ViewConfig {
  id: string;                     // Unique view identifier
  name: string;                   // Display name (editable)
  columns: string[];              // Visible field IDs in order
  sortBy: SortConfig | null;      // Sort configuration
  filters: FilterConfig[];        // Active filters
  groupBy: string | null;         // Field ID to group by (future)
}

interface SortConfig {
  field: string;                  // Field ID to sort by
  direction: 'asc' | 'desc';      // Sort direction
}

interface FilterConfig {
  field: string;                  // Field ID to filter
  operator: 'contains' | 'equals' | 'not-equals' | 'is-empty' | 'is-not-empty' | 'gt' | 'gte' | 'lt' | 'lte';
  value?: any;                    // Filter value (optional for is-empty/is-not-empty)
}

CustomAction

interface CustomAction {
  name: string;                   // Unique identifier for the action
  label: string;                  // Display text shown in the menu
  icon?: string;                  // Optional icon (emoji, text, or CSS class)
}

ContextMenuClickEvent

interface ContextMenuClickEvent {
  type: 'context-menu-click';     // Event type identifier
  actionName: string;             // Name of the custom action that was clicked
  row: Row;                       // Complete row data including id and values
}

📡 Events & Callbacks

The component emits several events for different user interactions.

📖 See EVENTS.md for complete event documentation including all payloads and examples.

onChange - Row Data Changes

Called whenever row data is modified (cell edits, add row, delete rows, bulk updates).

onChange?: (rows: Row[]) => void;

Example:

const handleChange = (updatedRows: Row[]) => {
  console.log('Rows updated:', updatedRows);
  await api.updateRows(updatedRows);  // Sync with backend
};

<GitBoardTable
  fields={fields}
  rows={rows}
  onChange={handleChange}
/>

onBulkUpdate - Drag-Fill Operations

NEW: Drag-fill works automatically! This callback is OPTIONAL and used for analytics/logging only.

Called when user performs Excel-style drag-fill operations.

onBulkUpdate?: (event: BulkUpdateEvent) => void;

Basic Usage - Drag-fill works automatically with just onChange:

const [rows, setRows] = useState<Row[]>(initialRows);

<GitBoardTable
  fields={fields}
  rows={rows}
  onChange={setRows}  // Drag-fill works automatically!
/>

Optional: With Analytics:

const handleBulkUpdate = (event: BulkUpdateEvent) => {
  // Log bulk update event
  console.log('Bulk update:', event.field.name, '→', event.targetCells.length, 'cells');

  // Track analytics
  trackEvent('bulk_update', {
    field: event.field.name,
    cellCount: event.targetCells.length,
  });
};

<GitBoardTable
  fields={fields}
  rows={rows}
  onChange={setRows}
  onBulkUpdate={handleBulkUpdate}  // Optional - for analytics only
/>

How It Works:

  • Component updates cells automatically (internal state management)
  • onChange fires with updated rows (just like single cell edits)
  • onBulkUpdate fires with event details (if provided) for analytics/logging

onRowOpen - Row Click (Planned)

🚧 Not yet implemented. Will be called when a row is clicked to open details.

onFieldChange - Field Config Changes (Planned)

🚧 Not yet implemented. Will be called when field definitions are modified.

onViewChange - View Switching

Called when the user switches between different views.

onViewChange?: (view: ViewConfig) => void;

Example:

const handleViewChange = (view: ViewConfig) => {
  console.log('Switched to:', view.name);
  console.log('Active filters:', view.filters.length);

  // Track analytics
  trackEvent('view_changed', {
    viewName: view.name,
    filterCount: view.filters.length,
  });
};

<GitBoardTable
  fields={fields}
  rows={rows}
  views={views}
  onViewChange={handleViewChange}
/>

onCreateView - New View Creation

Called when the user creates a new view by clicking the "+ Add view" button.

onCreateView?: (view: ViewConfig) => void;

Example:

const [views, setViews] = useState<ViewConfig[]>(initialViews);

const handleCreateView = (newView: ViewConfig) => {
  console.log('New view created:', newView.name);

  // Add to state
  setViews(prev => [...prev, newView]);

  // Optionally save to backend
  await api.saveView(newView);
};

<GitBoardTable
  fields={fields}
  rows={rows}
  views={views}
  onCreateView={handleCreateView}
/>

onUpdateView - View Updates

Called when the user modifies a view (renames it or saves filter changes).

onUpdateView?: (view: ViewConfig) => void;

Example:

const [views, setViews] = useState<ViewConfig[]>(initialViews);

const handleUpdateView = (updatedView: ViewConfig) => {
  console.log('View updated:', updatedView.name);

  // Update in state
  setViews(prev => prev.map(v => v.id === updatedView.id ? updatedView : v));

  // Optionally save to backend
  await api.updateView(updatedView);
};

<GitBoardTable
  fields={fields}
  rows={rows}
  views={views}
  onUpdateView={handleUpdateView}
/>

onRowSelect - Row Selection Changes

NEW: Called whenever row selection changes (single, multi-select, or range selection).

onRowSelect?: (event: RowSelectionEvent) => void;

Triggers on:

  • Single click on row number (select one)
  • Ctrl+Click / Cmd+Click (add to selection)
  • Shift+Click (select range)
  • Clicking selected row again (deselect)
  • Select all checkbox

Example:

const handleRowSelect = (event: RowSelectionEvent) => {
  console.log(`${event.selectedRows.length} rows selected`);
  console.log('Selection type:', event.lastAction);

  // Different actions for different selection types
  switch (event.lastAction) {
    case 'select':
      console.log('Single selection');
      break;
    case 'multi':
      console.log('Multi-select (Ctrl+Click or Select All)');
      break;
    case 'range':
      console.log('Range selection (Shift+Click)');
      break;
    case 'deselect':
      console.log('Row deselected');
      break;
    case 'clear':
      console.log('All rows cleared');
      break;
  }

  // Access full row data
  event.selectedRows.forEach(row => {
    console.log('Row ID:', row.id);
    console.log('Title:', row.values.title);
    console.log('Status:', row.values.status);
  });

  // Enable/disable bulk action buttons
  setBulkActionsEnabled(event.selectedRows.length > 0);

  // Track analytics
  if (event.lastAction === 'range') {
    trackEvent('range_selection_used', {
      count: event.selectedRows.length
    });
  }
};

<GitBoardTable
  fields={fields}
  rows={rows}
  onRowSelect={handleRowSelect}
/>

Use Cases:

  • Enable/disable bulk action buttons based on selection
  • Display selection count in toolbar
  • Track user selection patterns
  • Enable context menus for selected items
  • Sync selection state with external systems

onContextMenuClick - Custom Context Menu Actions

NEW: Called when a custom action is clicked in the row context menu.

onContextMenuClick?: (event: ContextMenuClickEvent) => void;

Triggers when:

  • User right-clicks a row to open context menu
  • User clicks a custom action from the menu

Example:

const customActions: CustomAction[] = [
  { name: 'archive', label: 'Archive', icon: '📦' },
  { name: 'duplicate', label: 'Duplicate', icon: '📋' },
  { name: 'export', label: 'Export as PDF', icon: '📄' },
];

const handleContextMenuClick = (event: ContextMenuClickEvent) => {
  console.log('Action clicked:', event.actionName);
  console.log('Row data:', event.row);

  switch (event.actionName) {
    case 'archive':
      archiveRow(event.row.id);
      showToast('Row archived successfully');
      break;

    case 'duplicate':
      const newRow = {
        ...event.row,
        id: generateId(),
        values: { ...event.row.values }
      };
      setRows(prev => [...prev, newRow]);
      break;

    case 'export':
      exportRowToPDF(event.row);
      trackEvent('row_exported', { rowId: event.row.id });
      break;
  }
};

<GitBoardTable
  fields={fields}
  rows={rows}
  customActions={customActions}
  onContextMenuClick={handleContextMenuClick}
/>

Use Cases:

  • Archive or soft-delete rows
  • Duplicate rows with pre-filled data
  • Export individual rows to PDF/Excel
  • Share rows via email or link
  • Move rows to different projects
  • Trigger custom workflows
  • Integration with external tools

📖 See EVENTS.md for complete event documentation including:

  • Detailed payload structures
  • When each event fires
  • Best practices (debouncing, optimistic updates, validation)
  • TypeScript types and examples

🎨 Theming

Using Built-in Themes

<GitBoardTable theme="dark" fields={fields} rows={rows} />

Custom CSS Variables

Override theme variables in your CSS:

:root {
  /* Background colors */
  --gb-bg-default: #ffffff;
  --gb-bg-muted: #f6f8fa;
  --gb-bg-subtle: #f0f3f6;
  --gb-bg-inset: #eff2f5;

  /* Border colors */
  --gb-border: #d0d7de;
  --gb-border-muted: #d8dee4;

  /* Text colors */
  --gb-text-primary: #1f2328;
  --gb-text-secondary: #656d76;
  --gb-text-tertiary: #8c959f;
  --gb-text-link: #0969da;

  /* Accent colors */
  --gb-accent-emphasis: #0969da;
  --gb-accent-muted: #ddf4ff;
  --gb-accent-subtle: #b6e3ff;

  /* Spacing */
  --gb-spacing-xs: 0.25rem;
  --gb-spacing-sm: 0.5rem;
  --gb-spacing-md: 0.75rem;
  --gb-spacing-lg: 1rem;
  --gb-spacing-xl: 1.5rem;

  /* Border radius */
  --gb-radius: 0.375rem;
}

🧪 Testing

The package includes comprehensive test coverage with:

  • 321 passing tests (264 + 57 new)
  • Vitest + React Testing Library
  • Unit tests for all components
  • Integration tests for complex interactions
  • Utility function tests
  • 14 bulk update tests - Verify drag-fill functionality
  • 58 view tabs tests - Verify view switching, filters, creation, editing, and saving
  • 5 view management tests - Verify column visibility and filter application from views
  • 5 FilterBar external filter tests - Verify filter display from view changes
  • 39 view management tests - Comprehensive coverage of:
    • Add view button functionality (7 tests)
    • Double-click to edit view name (11 tests)
    • Save button for filter changes (13 tests)
    • View creation and update callbacks (8 tests)
  • 36 content panel save button tests - Manual save functionality with keyboard shortcuts
  • 57 context menu tests - Row context menu with custom actions:
    • RowContextMenu unit tests (33 tests)
    • RowContextMenu integration tests (24 tests)

Example Test

import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { GitBoardTable } from '@txtony/gitboard-table';

test('edits a cell value', async () => {
  const user = userEvent.setup();
  const onChange = vi.fn();

  render(<GitBoardTable fields={fields} rows={rows} onChange={onChange} />);

  // Double-click to enter edit mode
  const cell = screen.getByText('Implement export system');
  await user.dblClick(cell);

  // Type new value
  const input = screen.getByRole('textbox');
  await user.clear(input);
  await user.type(input, 'New title{Enter}');

  // Verify onChange was called
  expect(onChange).toHaveBeenCalled();
});

🛠 Development Setup

# Clone the repository
git clone https://github.com/txtony/gitboard-table.git
cd gitboard-table

# Install dependencies
npm install

# Start development server
npm run dev

# Run tests in watch mode
npm test

# Start Storybook
npm run storybook

📦 Package Exports

The package exports both ESM and CJS formats:

@txtony/gitboard-table/
├── dist/
│   ├── index.esm.js      # ES Module
│   ├── index.cjs.js      # CommonJS
│   ├── types.d.ts        # TypeScript definitions
│   └── styles.css        # Component styles

Import Examples

// Component
import { GitBoardTable } from '@txtony/gitboard-table';

// Types
import type {
  FieldDefinition,
  Row,
  CellValue,
  RowSelectionEvent,
  RowReorderEvent,
  BulkUpdateEvent,
  CustomAction,
  ContextMenuClickEvent
} from '@txtony/gitboard-table';

// Styles
import '@txtony/gitboard-table/styles.css';

📚 Documentation

Comprehensive documentation is available for all major features in the ./DOC_GEN_BY_AI folder:

  • GROUPING.md - Complete guide to grouping rows by field values

    • How grouping integrates with views
    • Supported field types
    • Grouping utilities and components
    • Performance considerations
    • Examples and troubleshooting
  • VIEWS.md - Views system documentation

    • Creating and managing multiple table configurations
    • View lifecycle and state management
    • Event callbacks and persistence
    • Examples and common patterns
  • FILTERS.md - Advanced filtering system

    • Filter operators and syntax
    • Creating complex filter combinations
    • Programmatic filtering
  • FIRESTORE_INTEGRATION.md - Firebase/Firestore integration

    • Complete examples with infinite scroll
    • Batched data loading (200 items)
    • Real-time updates
    • Performance optimization
  • EVENTS.md - Event system reference

    • All available callbacks
    • Event payload structures
    • Usage examples
  • ROWS.md - Row management documentation

    • CRUD operations
    • Row reordering
    • Row selection

🤝 Contributing

Contributions are welcome! Please follow these steps:

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/amazing-feature)
  3. Make your changes
  4. Add tests for new functionality
  5. Ensure all tests pass (npm test)
  6. Commit your changes (git commit -m 'Add amazing feature')
  7. Push to the branch (git push origin feature/amazing-feature)
  8. Open a Pull Request

📄 License

MIT © TxTony

🙏 Acknowledgments


Made with ❤️ by TxTony