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

@startsimpli/ui

v0.1.0

Published

Shared UI components package for StartSimpli applications

Readme

@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

npm install @startsimpli/ui

Peer Dependencies

This package requires the following peer dependencies:

npm install react react-dom next

Usage

UnifiedTable

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

Basic Example

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.

// ✅ 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
}}
// ✅ 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
}}
// ✅ 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

<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

<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

<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

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

Saved Views

<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

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

UI Components

Import shadcn/ui components:

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

Utilities

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:

// 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

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.

# 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:

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