@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
Maintainers
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.
🚀 Getting Started
Installation
npm install @txtony/gitboard-tableOr with yarn:
yarn add @txtony/gitboard-tablePeer Dependencies
Make sure you have the required peer dependencies installed:
npm install react react-dom tailwindcssBasic 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:
Enterto commit changesEscapeto cancel- Arrow keys for navigation
Ctrl+Click/Cmd+Clickfor multi-selectShift+Clickfor 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 -
onBulkUpdatecallback 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 textequals/=- Exact matchnot-equals/!=- Does not matchis-empty- Field is emptyis-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(orCmdon Mac) and click to add rows to selection - Range selection - Hold
Shiftand 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 -
onRowSelectcallback 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 -
onRowsReordercallback 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 rowtoIndex- New position of moved rowrows- Complete reordered rows arraymovedRow- 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:
- Right-click any row to open the context menu
- Built-in actions (Show, Delete) appear at the top
- A divider separates built-in from custom actions
- Custom actions appear below with optional icons
- Clicking a custom action dispatches
onContextMenuClickevent - Event includes the action name and full row data
- 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 devStarts Vite dev server at http://localhost:5173
Building
# Build production bundle
npm run buildOutputs to dist/ directory with ESM, CJS, and type definitions
# Preview production build
npm run previewTesting
# Run tests in watch mode
npm test# Run tests with UI
npm run test:uiOpens Vitest UI at http://localhost:51204
# Generate coverage report
npm run test:coverageOutputs coverage to coverage/ directory
Code Quality
# Run ESLint
npm run lintChecks TypeScript and React code for errors and style issues
Storybook
# Start Storybook dev server
npm run storybookOpens Storybook at http://localhost:6006 with interactive component demos
# Build static Storybook
npm run build-storybookOutputs 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)
onChangefires with updated rows (just like single cell edits)onBulkUpdatefires 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 stylesImport 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:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Make your changes
- Add tests for new functionality
- Ensure all tests pass (
npm test) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
📄 License
MIT © TxTony
🙏 Acknowledgments
- Inspired by GitHub Projects table interface
- Built with React
- Styled with TailwindCSS
- Tested with Vitest
- Documented with Storybook
Made with ❤️ by TxTony
