@startsimpli/ui
v0.1.0
Published
Shared UI components package for StartSimpli applications
Maintainers
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/uiPeer Dependencies
This package requires the following peer dependencies:
npm install react react-dom nextUsage
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 displaycolumns: ColumnConfig<TData>[]- Column definitionstableId: string- Unique identifier for the tablegetRowId: (row: TData) => string- Function to get unique row ID
Pagination
pagination.enabled: boolean- Enable paginationpagination.pageSize: number- Rows per pagepagination.totalCount: number- Total number of rows (from API)pagination.currentPage: number- Current page number (1-indexed)pagination.serverSide: boolean- MUST be true for productionpagination.onPageChange: (page: number) => void- Page change callback
Sorting
sorting.enabled: boolean- Enable sortingsorting.serverSide: boolean- MUST be true for productionsorting.value: SortState- Current sort state (controlled)sorting.onChange: (sort: SortState) => void- Sort change callback
Search
search.enabled: boolean- Enable searchsearch.placeholder: string- Search input placeholdersearch.value: string- Current search value (controlled)search.onChange: (value: string) => void- Search change callback
Selection
selection.enabled: boolean- Enable row selectionselection.selectedIds: Set<string>- Selected row IDs (controlled)selection.onSelectionChange: (ids: Set<string>) => void- Selection change callbackselection.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 filtersfilters.config: FilterConfig- Filter configurationfilters.value: FilterState- Current filter values (controlled)filters.onChange: (filters: FilterState) => void- Filter change callback
Column Visibility
columnVisibility.enabled: boolean- Enable column visibility controlscolumnVisibility.defaultVisible: string[]- Initially visible columnscolumnVisibility.alwaysVisible: string[]- Columns that cannot be hiddencolumnVisibility.persistKey: string- localStorage key for persistence
Column Reordering
columnReorder.enabled: boolean- Enable drag-and-drop column reorderingcolumnReorder.initialOrder: string[]- Initial column ordercolumnReorder.onOrderChange: (order: string[]) => void- Order change callback
Column Resizing
columnResize.enabled: boolean- Enable column resizingcolumnResize.initialWidths: Record<string, number>- Initial column widthscolumnResize.minWidth: number- Minimum column width (default: 50)columnResize.onWidthChange: (widths: Record<string, number>) => void- Width change callback
Inline Editing
inlineEdit.enabled: boolean- Enable inline editinginlineEdit.onSave: (rowId, columnId, value, row) => Promise<void>- Save callbackinlineEdit.optimisticUpdate: boolean- Apply changes immediately (default: true)
Export
export.enabled: boolean- Enable export functionalityexport.baseFilename: string- Base filename for exportsexport.formats: ('csv' | 'excel')[]- Available export formatsexport.showProgress: boolean- Show progress during export
Saved Views
savedViews.enabled: boolean- Enable saved viewssavedViews.views: SavedView[]- Array of saved viewssavedViews.currentViewId: string- Currently active view IDsavedViews.onSaveView: (view) => Promise<SavedView>- Save view callbacksavedViews.onLoadView: (viewId) => void- Load view callback
URL Persistence
urlPersistence.enabled: boolean- Enable URL state persistenceurlPersistence.debounceMs: number- Debounce delay (default: 300ms)
Mobile
mobileConfig.titleKey: string- Key for card titlemobileConfig.subtitleKey: string- Key for card subtitlemobileConfig.primaryFields: string[]- Primary fields to displaymobileConfig.secondaryFields: string[]- Secondary fields to display
Other
loading: boolean- Show loading stateloadingRows: Set<string>- Row IDs that are loadingclassName: string- Additional CSS classesemptyState: ReactNode- Custom empty state componenterrorState: ReactNode- Custom error state componentonRowClick: (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:coverageType 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
- Always use server-side operations - Never load entire datasets into memory
- Debounce search inputs - Use the built-in debouncing (default: 300ms)
- Virtualization for large lists - Consider implementing virtual scrolling for 1000+ rows
- Optimize column renderers - Use
useMemofor complex cell renderers - 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
