npm package: @startsimpli/ui
Description: Shared UI components package for StartSimpli applications
Latest version: 0.4.6

# @startsimpli/ui

Shared UI components package for StartSimpli applications. Provides a comprehensive set of React components including the powerful UnifiedTable component and shadcn/ui primitives.

## Features

- **UnifiedTable**: Production-ready data table with advanced features
  - Server-side pagination, sorting, and search
  - Column visibility, reordering, and resizing
  - Bulk actions and row actions
  - Inline editing
  - Filters with presets
  - Export to CSV/Excel
  - Saved views
  - Mobile-responsive card view
  - Keyboard navigation
  - URL persistence

- **shadcn/ui Components**: Complete set of accessible UI primitives
- **Tailwind CSS Integration**: Pre-configured theme and utilities
- **TypeScript**: Full type safety

## Installation

```bash
npm install @startsimpli/ui
```

### Peer Dependencies

This package requires the following peer dependencies:

```bash
npm install react react-dom next
```

## Usage

### UnifiedTable

The UnifiedTable component provides a complete data table solution with server-side operations.

#### Basic Example

```tsx
import { UnifiedTable } from '@startsimpli/ui/table'

function MyTable() {
  const [data, setData] = useState([])
  const [loading, setLoading] = useState(false)
  const [page, setPage] = useState(1)
  const [totalCount, setTotalCount] = useState(0)

  const columns = [
    {
      id: 'name',
      header: 'Name',
      accessorKey: 'name',
      sortable: true,
    },
    {
      id: 'email',
      header: 'Email',
      accessorKey: 'email',
      sortable: true,
    },
  ]

  return (
    <UnifiedTable
      data={data}
      columns={columns}
      tableId="users-table"
      getRowId={(row) => row.id}
      pagination={{
        enabled: true,
        pageSize: 25,
        totalCount,
        currentPage: page,
        serverSide: true, // CRITICAL: Always use server-side pagination
        onPageChange: setPage,
      }}
      sorting={{
        enabled: true,
        serverSide: true, // CRITICAL: Always use server-side sorting
        onChange: (sort) => {
          // Pass sort params to API
          fetchData({ page, sortBy: sort.sortBy, sortDirection: sort.sortDirection })
        },
      }}
      search={{
        enabled: true,
        placeholder: 'Search users...',
        value: searchTerm,
        onChange: (value) => {
          // Pass search query to API
          setSearchTerm(value)
          fetchData({ page: 1, search: value })
        },
      }}
      loading={loading}
    />
  )
}
```

#### Server-Side Operations (CRITICAL)

**ALL tables MUST use server-side operations. This is non-negotiable.**

```tsx
// ✅ CORRECT - Server-side pagination
pagination={{
  enabled: true,
  pageSize: 25,
  totalCount: 1000, // Total from API
  currentPage: page,
  serverSide: true, // Fetch pages from API
  onPageChange: (newPage) => {
    // Call API with ?page=newPage&pageSize=25
    fetchData({ page: newPage, pageSize: 25 })
  },
}}

// ❌ WRONG - Client-side pagination
pagination={{
  enabled: true,
  serverSide: false, // Loads all data into memory
}}
```

```tsx
// ✅ CORRECT - Server-side sorting
sorting={{
  enabled: true,
  serverSide: true, // Pass sort params to API
  onChange: (sort) => {
    // Call API with ?sortField=name&sortDirection=asc
    fetchData({ sortField: sort.sortBy, sortDirection: sort.sortDirection })
  },
}}

// ❌ WRONG - Client-side sorting
sorting={{
  enabled: true,
  serverSide: false, // Sorts in memory
}}
```

```tsx
// ✅ CORRECT - Server-side search
search={{
  enabled: true,
  value: searchTerm,
  onChange: (value) => {
    // Call API with ?search=value
    fetchData({ search: value })
  },
}}

// ❌ WRONG - Client-side search
// (No client-side search mode - always passes to API)
```

#### Selection and Bulk Actions

```tsx
<UnifiedTable
  selection={{
    enabled: true,
    selectedIds: selectedIds,
    onSelectionChange: setSelectedIds,
  }}
  bulkActions={[
    {
      id: 'delete',
      label: 'Delete',
      icon: Trash2,
      variant: 'gradient-purple',
      onClick: async (ids) => {
        await deleteUsers(Array.from(ids))
      },
      confirmMessage: 'Delete {count} users?',
    },
  ]}
/>
```

#### Filters

```tsx
<UnifiedTable
  filters={{
    enabled: true,
    position: 'top',
    collapsible: true,
    config: {
      sections: [
        {
          id: 'status',
          type: 'chips',
          label: 'Status',
          filters: [
            { id: 'status', label: 'Status', type: 'chips', options: ['active', 'inactive'] },
          ],
        },
      ],
    },
    value: filters,
    onChange: (newFilters) => {
      setFilters(newFilters)
      // Pass to API: ?status=active
    },
  }}
/>
```

#### Inline Editing

```tsx
<UnifiedTable
  columns={[
    {
      id: 'name',
      header: 'Name',
      accessorKey: 'name',
      editable: true,
      editType: 'text',
      validate: (value) => {
        if (!value) return 'Name is required'
        return null
      },
    },
  ]}
  inlineEdit={{
    enabled: true,
    onSave: async (rowId, columnId, value, row) => {
      await updateUser(rowId, { [columnId]: value })
    },
  }}
/>
```

#### Export

```tsx
<UnifiedTable
  export={{
    enabled: true,
    baseFilename: 'users',
    formats: ['csv', 'excel'],
    showProgress: true,
    onExportComplete: (format, scope, rowCount) => {
      console.log(`Exported ${rowCount} rows as ${format}`)
    },
  }}
/>
```

#### Saved Views

```tsx
<UnifiedTable
  savedViews={{
    enabled: true,
    views: savedViews,
    currentViewId: currentViewId,
    onSaveView: async (view) => {
      const newView = await saveView(view)
      return newView
    },
    onLoadView: (viewId) => {
      const view = savedViews.find(v => v.id === viewId)
      if (view) {
        // Apply view settings
        setFilters(view.filters || {})
        setColumnVisibility(view.columnVisibility || {})
      }
    },
  }}
/>
```

#### Mobile Support

```tsx
<UnifiedTable
  mobileConfig={{
    titleKey: 'name',
    subtitleKey: 'email',
    primaryFields: ['name', 'email'],
    secondaryFields: ['status', 'createdAt'],
  }}
/>
```

### UI Components

Import shadcn/ui components:

```tsx
import { Button } from '@startsimpli/ui/components'
import { Dialog, DialogContent, DialogHeader } from '@startsimpli/ui/components'
import { Input, Label } from '@startsimpli/ui/components'
```

### Utilities

```tsx
import { cn } from '@startsimpli/ui/utils'

// Merge Tailwind classes
const className = cn('base-class', condition && 'conditional-class')
```

### Tailwind Configuration

Extend your Tailwind config with the shared theme:

```js
// tailwind.config.js
const baseConfig = require('@startsimpli/ui/theme/tailwind.config')

module.exports = {
  ...baseConfig,
  content: [
    './src/**/*.{ts,tsx}',
    './node_modules/@startsimpli/ui/src/**/*.{ts,tsx}',
  ],
  theme: {
    ...baseConfig.theme,
    extend: {
      ...baseConfig.theme.extend,
      // Your custom extensions
    },
  },
}
```

## UnifiedTable API Reference

### Props

#### Core Props

- `data: TData[]` - Array of data to display
- `columns: ColumnConfig<TData>[]` - Column definitions
- `tableId: string` - Unique identifier for the table
- `getRowId: (row: TData) => string` - Function to get unique row ID

#### Pagination

- `pagination.enabled: boolean` - Enable pagination
- `pagination.pageSize: number` - Rows per page
- `pagination.totalCount: number` - Total number of rows (from API)
- `pagination.currentPage: number` - Current page number (1-indexed)
- `pagination.serverSide: boolean` - **MUST be true** for production
- `pagination.onPageChange: (page: number) => void` - Page change callback

#### Sorting

- `sorting.enabled: boolean` - Enable sorting
- `sorting.serverSide: boolean` - **MUST be true** for production
- `sorting.value: SortState` - Current sort state (controlled)
- `sorting.onChange: (sort: SortState) => void` - Sort change callback

#### Search

- `search.enabled: boolean` - Enable search
- `search.placeholder: string` - Search input placeholder
- `search.value: string` - Current search value (controlled)
- `search.onChange: (value: string) => void` - Search change callback

#### Selection

- `selection.enabled: boolean` - Enable row selection
- `selection.selectedIds: Set<string>` - Selected row IDs (controlled)
- `selection.onSelectionChange: (ids: Set<string>) => void` - Selection change callback
- `selection.selectAllPages: boolean` - Whether all pages are selected

#### Bulk Actions

- `bulkActions: BulkAction[]` - Array of bulk action definitions

#### Row Actions

- `rowActions: RowAction<TData>[]` - Array of row-level actions

#### Filters

- `filters.enabled: boolean` - Enable filters
- `filters.config: FilterConfig` - Filter configuration
- `filters.value: FilterState` - Current filter values (controlled)
- `filters.onChange: (filters: FilterState) => void` - Filter change callback

#### Column Visibility

- `columnVisibility.enabled: boolean` - Enable column visibility controls
- `columnVisibility.defaultVisible: string[]` - Initially visible columns
- `columnVisibility.alwaysVisible: string[]` - Columns that cannot be hidden
- `columnVisibility.persistKey: string` - localStorage key for persistence

#### Column Reordering

- `columnReorder.enabled: boolean` - Enable drag-and-drop column reordering
- `columnReorder.initialOrder: string[]` - Initial column order
- `columnReorder.onOrderChange: (order: string[]) => void` - Order change callback

#### Column Resizing

- `columnResize.enabled: boolean` - Enable column resizing
- `columnResize.initialWidths: Record<string, number>` - Initial column widths
- `columnResize.minWidth: number` - Minimum column width (default: 50)
- `columnResize.onWidthChange: (widths: Record<string, number>) => void` - Width change callback

#### Inline Editing

- `inlineEdit.enabled: boolean` - Enable inline editing
- `inlineEdit.onSave: (rowId, columnId, value, row) => Promise<void>` - Save callback
- `inlineEdit.optimisticUpdate: boolean` - Apply changes immediately (default: true)

#### Export

- `export.enabled: boolean` - Enable export functionality
- `export.baseFilename: string` - Base filename for exports
- `export.formats: ('csv' | 'excel')[]` - Available export formats
- `export.showProgress: boolean` - Show progress during export

#### Saved Views

- `savedViews.enabled: boolean` - Enable saved views
- `savedViews.views: SavedView[]` - Array of saved views
- `savedViews.currentViewId: string` - Currently active view ID
- `savedViews.onSaveView: (view) => Promise<SavedView>` - Save view callback
- `savedViews.onLoadView: (viewId) => void` - Load view callback

#### URL Persistence

- `urlPersistence.enabled: boolean` - Enable URL state persistence
- `urlPersistence.debounceMs: number` - Debounce delay (default: 300ms)

#### Mobile

- `mobileConfig.titleKey: string` - Key for card title
- `mobileConfig.subtitleKey: string` - Key for card subtitle
- `mobileConfig.primaryFields: string[]` - Primary fields to display
- `mobileConfig.secondaryFields: string[]` - Secondary fields to display

#### Other

- `loading: boolean` - Show loading state
- `loadingRows: Set<string>` - Row IDs that are loading
- `className: string` - Additional CSS classes
- `emptyState: ReactNode` - Custom empty state component
- `errorState: ReactNode` - Custom error state component
- `onRowClick: (row: TData) => void` - Row click handler

### Column Configuration

```tsx
interface ColumnConfig<TData> {
  id: string
  header: string | ((props: any) => ReactNode)
  accessorKey?: string // Dot notation supported: 'user.name'
  accessorFn?: (row: TData) => any
  cell?: (row: TData) => ReactNode
  sortable?: boolean
  sortingFn?: (a: TData, b: TData) => number
  width?: string | number
  minWidth?: string | number
  maxWidth?: string | number
  mobilePrimary?: boolean
  mobileSecondary?: boolean
  hideOnMobile?: boolean
  hideable?: boolean
  editable?: boolean
  editType?: 'text' | 'number' | 'select' | 'date'
  editOptions?: string[]
  validate?: (value: any, row: TData) => string | null
}
```

## Testing

The package includes comprehensive test coverage for all components.

```bash
# Run tests
npm test

# Watch mode
npm run test:watch

# Coverage
npm run test:coverage
```

## Type Safety

All components are fully typed with TypeScript. The UnifiedTable component uses generics for type-safe data handling:

```tsx
interface User {
  id: string
  name: string
  email: string
}

<UnifiedTable<User>
  data={users}
  columns={columns}
  getRowId={(user) => user.id} // Type-safe
/>
```

## Performance Considerations

1. **Always use server-side operations** - Never load entire datasets into memory
2. **Debounce search inputs** - Use the built-in debouncing (default: 300ms)
3. **Virtualization for large lists** - Consider implementing virtual scrolling for 1000+ rows
4. **Optimize column renderers** - Use `useMemo` for complex cell renderers
5. **Minimize re-renders** - Use controlled state carefully

## Browser Support

- Chrome (latest)
- Firefox (latest)
- Safari (latest)
- Edge (latest)

## Contributing

This package is part of the StartSimpli monorepo. Follow the monorepo contribution guidelines.

## License

Proprietary - StartSimpli