@startsimpli/ui 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +537 -0
- package/package.json +80 -0
- package/src/components/index.ts +50 -0
- package/src/components/navigation/sidebar.tsx +178 -0
- package/src/components/ui/accordion.tsx +58 -0
- package/src/components/ui/alert.tsx +59 -0
- package/src/components/ui/badge.tsx +36 -0
- package/src/components/ui/button.tsx +57 -0
- package/src/components/ui/calendar.tsx +70 -0
- package/src/components/ui/card.tsx +68 -0
- package/src/components/ui/checkbox.tsx +30 -0
- package/src/components/ui/collapsible.tsx +12 -0
- package/src/components/ui/dialog.tsx +122 -0
- package/src/components/ui/dropdown-menu.tsx +200 -0
- package/src/components/ui/index.ts +24 -0
- package/src/components/ui/input.tsx +25 -0
- package/src/components/ui/label.tsx +26 -0
- package/src/components/ui/popover.tsx +31 -0
- package/src/components/ui/progress.tsx +28 -0
- package/src/components/ui/scroll-area.tsx +48 -0
- package/src/components/ui/select.tsx +160 -0
- package/src/components/ui/separator.tsx +31 -0
- package/src/components/ui/skeleton.tsx +15 -0
- package/src/components/ui/table.tsx +117 -0
- package/src/components/ui/tabs.tsx +55 -0
- package/src/components/ui/textarea.tsx +24 -0
- package/src/components/ui/tooltip.tsx +30 -0
- package/src/components/unified-table/UnifiedTable.tsx +553 -0
- package/src/components/unified-table/__tests__/components/BulkActionBar.test.tsx +477 -0
- package/src/components/unified-table/__tests__/components/ExportButton.test.tsx +467 -0
- package/src/components/unified-table/__tests__/components/InlineEditCell.test.tsx +159 -0
- package/src/components/unified-table/__tests__/components/SavedViewsDropdown.test.tsx +128 -0
- package/src/components/unified-table/__tests__/components/TablePagination.test.tsx +374 -0
- package/src/components/unified-table/__tests__/hooks/useColumnReorder.test.ts +191 -0
- package/src/components/unified-table/__tests__/hooks/useColumnResize.test.ts +122 -0
- package/src/components/unified-table/__tests__/hooks/useColumnVisibility.test.ts +594 -0
- package/src/components/unified-table/__tests__/hooks/useFilters.test.ts +460 -0
- package/src/components/unified-table/__tests__/hooks/usePagination.test.ts +439 -0
- package/src/components/unified-table/__tests__/hooks/useResponsive.test.ts +421 -0
- package/src/components/unified-table/__tests__/hooks/useSelection.test.ts +367 -0
- package/src/components/unified-table/__tests__/hooks/useTableKeyboard.test.ts +803 -0
- package/src/components/unified-table/__tests__/hooks/useTableState.test.ts +210 -0
- package/src/components/unified-table/__tests__/integration/table-with-selection.test.tsx +624 -0
- package/src/components/unified-table/__tests__/utils/export.test.ts +427 -0
- package/src/components/unified-table/components/BulkActionBar/index.tsx +119 -0
- package/src/components/unified-table/components/DataTableCore/index.tsx +473 -0
- package/src/components/unified-table/components/InlineEditCell/index.tsx +159 -0
- package/src/components/unified-table/components/MobileView/Card.tsx +218 -0
- package/src/components/unified-table/components/MobileView/CardActions.tsx +126 -0
- package/src/components/unified-table/components/MobileView/README.md +411 -0
- package/src/components/unified-table/components/MobileView/index.tsx +77 -0
- package/src/components/unified-table/components/MobileView/types.ts +77 -0
- package/src/components/unified-table/components/TableFilters/index.tsx +298 -0
- package/src/components/unified-table/components/TablePagination/index.tsx +157 -0
- package/src/components/unified-table/components/Toolbar/ExportButton.tsx +229 -0
- package/src/components/unified-table/components/Toolbar/SavedViewsDropdown.tsx +251 -0
- package/src/components/unified-table/components/Toolbar/StandardTableToolbar.tsx +146 -0
- package/src/components/unified-table/components/Toolbar/index.tsx +3 -0
- package/src/components/unified-table/hooks/index.ts +21 -0
- package/src/components/unified-table/hooks/useColumnReorder.ts +90 -0
- package/src/components/unified-table/hooks/useColumnResize.ts +123 -0
- package/src/components/unified-table/hooks/useColumnVisibility.ts +92 -0
- package/src/components/unified-table/hooks/useFilters.ts +53 -0
- package/src/components/unified-table/hooks/usePagination.ts +120 -0
- package/src/components/unified-table/hooks/useResponsive.ts +50 -0
- package/src/components/unified-table/hooks/useSelection.ts +152 -0
- package/src/components/unified-table/hooks/useTableKeyboard.ts +206 -0
- package/src/components/unified-table/hooks/useTablePreferences.ts +198 -0
- package/src/components/unified-table/hooks/useTableState.ts +103 -0
- package/src/components/unified-table/hooks/useTableURL.test.tsx +921 -0
- package/src/components/unified-table/hooks/useTableURL.ts +301 -0
- package/src/components/unified-table/index.ts +16 -0
- package/src/components/unified-table/types.ts +393 -0
- package/src/components/unified-table/utils/export.ts +236 -0
- package/src/components/unified-table/utils/index.ts +4 -0
- package/src/components/unified-table/utils/renderers.ts +105 -0
- package/src/components/unified-table/utils/themes.ts +87 -0
- package/src/components/unified-table/utils/validation.ts +122 -0
- package/src/index.ts +6 -0
- package/src/lib/utils.ts +1 -0
- package/src/theme/contract.ts +46 -0
- package/src/theme/index.ts +9 -0
- package/src/theme/tailwind.config.js +70 -0
- package/src/theme/tailwind.preset.ts +93 -0
- package/src/utils/cn.ts +6 -0
- package/src/utils/index.ts +91 -0
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { useState, useEffect, useCallback, useRef } from 'react'
|
|
2
|
+
import { ColumnVisibilityState, SortState } from '../types'
|
|
3
|
+
|
|
4
|
+
interface TablePreferences {
|
|
5
|
+
columnVisibility: ColumnVisibilityState
|
|
6
|
+
columnOrder: string[] | null
|
|
7
|
+
defaultSortColumn: string | null
|
|
8
|
+
defaultSortDirection: 'asc' | 'desc' | null
|
|
9
|
+
pageSize: number
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface UseTablePreferencesOptions {
|
|
13
|
+
tableId: string
|
|
14
|
+
enabled?: boolean
|
|
15
|
+
defaultColumnVisibility?: ColumnVisibilityState
|
|
16
|
+
defaultPageSize?: number
|
|
17
|
+
debounceMs?: number
|
|
18
|
+
// Custom fetch function for API calls
|
|
19
|
+
// Applications should provide their own apiFetch implementation
|
|
20
|
+
apiFetch?: typeof fetch
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface UseTablePreferencesReturn {
|
|
24
|
+
preferences: TablePreferences | null
|
|
25
|
+
isLoading: boolean
|
|
26
|
+
error: Error | null
|
|
27
|
+
updateColumnVisibility: (visibility: ColumnVisibilityState) => void
|
|
28
|
+
updateColumnOrder: (order: string[]) => void
|
|
29
|
+
updateSortPreference: (sort: SortState) => void
|
|
30
|
+
updatePageSize: (size: number) => void
|
|
31
|
+
resetPreferences: () => Promise<void>
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function useTablePreferences({
|
|
35
|
+
tableId,
|
|
36
|
+
enabled = true,
|
|
37
|
+
defaultColumnVisibility = {},
|
|
38
|
+
defaultPageSize = 25,
|
|
39
|
+
debounceMs = 500,
|
|
40
|
+
apiFetch = fetch // Default to native fetch if not provided
|
|
41
|
+
}: UseTablePreferencesOptions): UseTablePreferencesReturn {
|
|
42
|
+
const [preferences, setPreferences] = useState<TablePreferences | null>(null)
|
|
43
|
+
const [isLoading, setIsLoading] = useState(true)
|
|
44
|
+
const [error, setError] = useState<Error | null>(null)
|
|
45
|
+
const saveTimeoutRef = useRef<NodeJS.Timeout | null>(null)
|
|
46
|
+
const pendingUpdatesRef = useRef<Partial<TablePreferences>>({})
|
|
47
|
+
|
|
48
|
+
// Load preferences on mount
|
|
49
|
+
useEffect(() => {
|
|
50
|
+
if (!enabled) {
|
|
51
|
+
setIsLoading(false)
|
|
52
|
+
return
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const loadPreferences = async () => {
|
|
56
|
+
try {
|
|
57
|
+
const response = await apiFetch(`/api/v1/table-preferences/?tableId=${encodeURIComponent(tableId)}`)
|
|
58
|
+
if (!response.ok) {
|
|
59
|
+
throw new Error('Failed to load table preferences')
|
|
60
|
+
}
|
|
61
|
+
const data = await response.json()
|
|
62
|
+
|
|
63
|
+
if (data) {
|
|
64
|
+
setPreferences(data)
|
|
65
|
+
} else {
|
|
66
|
+
// Use defaults if no saved preferences
|
|
67
|
+
setPreferences({
|
|
68
|
+
columnVisibility: defaultColumnVisibility,
|
|
69
|
+
columnOrder: null,
|
|
70
|
+
defaultSortColumn: null,
|
|
71
|
+
defaultSortDirection: null,
|
|
72
|
+
pageSize: defaultPageSize
|
|
73
|
+
})
|
|
74
|
+
}
|
|
75
|
+
} catch (err) {
|
|
76
|
+
console.error('Error loading table preferences:', err)
|
|
77
|
+
setError(err instanceof Error ? err : new Error('Unknown error'))
|
|
78
|
+
// Fall back to defaults on error
|
|
79
|
+
setPreferences({
|
|
80
|
+
columnVisibility: defaultColumnVisibility,
|
|
81
|
+
columnOrder: null,
|
|
82
|
+
defaultSortColumn: null,
|
|
83
|
+
defaultSortDirection: null,
|
|
84
|
+
pageSize: defaultPageSize
|
|
85
|
+
})
|
|
86
|
+
} finally {
|
|
87
|
+
setIsLoading(false)
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
loadPreferences()
|
|
92
|
+
}, [tableId, enabled, defaultPageSize])
|
|
93
|
+
|
|
94
|
+
// Debounced save function
|
|
95
|
+
const savePreferences = useCallback(async (updates: Partial<TablePreferences>) => {
|
|
96
|
+
// Merge with pending updates
|
|
97
|
+
pendingUpdatesRef.current = { ...pendingUpdatesRef.current, ...updates }
|
|
98
|
+
|
|
99
|
+
// Clear existing timeout
|
|
100
|
+
if (saveTimeoutRef.current) {
|
|
101
|
+
clearTimeout(saveTimeoutRef.current)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Set new debounced timeout
|
|
105
|
+
saveTimeoutRef.current = setTimeout(async () => {
|
|
106
|
+
const updatesToSave = { ...pendingUpdatesRef.current }
|
|
107
|
+
pendingUpdatesRef.current = {}
|
|
108
|
+
|
|
109
|
+
try {
|
|
110
|
+
const response = await apiFetch('/api/v1/table-preferences/', {
|
|
111
|
+
method: 'POST',
|
|
112
|
+
headers: { 'Content-Type': 'application/json' },
|
|
113
|
+
body: JSON.stringify({
|
|
114
|
+
tableId,
|
|
115
|
+
...updatesToSave
|
|
116
|
+
})
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
if (!response.ok) {
|
|
120
|
+
throw new Error('Failed to save table preferences')
|
|
121
|
+
}
|
|
122
|
+
} catch (err) {
|
|
123
|
+
console.error('Error saving table preferences:', err)
|
|
124
|
+
// Don't update error state for save failures - user can continue working
|
|
125
|
+
}
|
|
126
|
+
}, debounceMs)
|
|
127
|
+
}, [tableId, debounceMs])
|
|
128
|
+
|
|
129
|
+
// Cleanup timeout on unmount
|
|
130
|
+
useEffect(() => {
|
|
131
|
+
return () => {
|
|
132
|
+
if (saveTimeoutRef.current) {
|
|
133
|
+
clearTimeout(saveTimeoutRef.current)
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}, [])
|
|
137
|
+
|
|
138
|
+
const updateColumnVisibility = useCallback((visibility: ColumnVisibilityState) => {
|
|
139
|
+
setPreferences(prev => prev ? { ...prev, columnVisibility: visibility } : null)
|
|
140
|
+
savePreferences({ columnVisibility: visibility })
|
|
141
|
+
}, [savePreferences])
|
|
142
|
+
|
|
143
|
+
const updateColumnOrder = useCallback((order: string[]) => {
|
|
144
|
+
setPreferences(prev => prev ? { ...prev, columnOrder: order } : null)
|
|
145
|
+
savePreferences({ columnOrder: order })
|
|
146
|
+
}, [savePreferences])
|
|
147
|
+
|
|
148
|
+
const updateSortPreference = useCallback((sort: SortState) => {
|
|
149
|
+
setPreferences(prev => prev ? {
|
|
150
|
+
...prev,
|
|
151
|
+
defaultSortColumn: sort.sortBy,
|
|
152
|
+
defaultSortDirection: sort.sortDirection
|
|
153
|
+
} : null)
|
|
154
|
+
savePreferences({
|
|
155
|
+
defaultSortColumn: sort.sortBy,
|
|
156
|
+
defaultSortDirection: sort.sortDirection
|
|
157
|
+
})
|
|
158
|
+
}, [savePreferences])
|
|
159
|
+
|
|
160
|
+
const updatePageSize = useCallback((size: number) => {
|
|
161
|
+
setPreferences(prev => prev ? { ...prev, pageSize: size } : null)
|
|
162
|
+
savePreferences({ pageSize: size })
|
|
163
|
+
}, [savePreferences])
|
|
164
|
+
|
|
165
|
+
const resetPreferences = useCallback(async () => {
|
|
166
|
+
try {
|
|
167
|
+
const response = await apiFetch(`/api/v1/table-preferences/?tableId=${encodeURIComponent(tableId)}`, {
|
|
168
|
+
method: 'DELETE'
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
if (!response.ok) {
|
|
172
|
+
throw new Error('Failed to reset table preferences')
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
setPreferences({
|
|
176
|
+
columnVisibility: defaultColumnVisibility,
|
|
177
|
+
columnOrder: null,
|
|
178
|
+
defaultSortColumn: null,
|
|
179
|
+
defaultSortDirection: null,
|
|
180
|
+
pageSize: defaultPageSize
|
|
181
|
+
})
|
|
182
|
+
} catch (err) {
|
|
183
|
+
console.error('Error resetting table preferences:', err)
|
|
184
|
+
throw err
|
|
185
|
+
}
|
|
186
|
+
}, [tableId, defaultColumnVisibility, defaultPageSize])
|
|
187
|
+
|
|
188
|
+
return {
|
|
189
|
+
preferences,
|
|
190
|
+
isLoading,
|
|
191
|
+
error,
|
|
192
|
+
updateColumnVisibility,
|
|
193
|
+
updateColumnOrder,
|
|
194
|
+
updateSortPreference,
|
|
195
|
+
updatePageSize,
|
|
196
|
+
resetPreferences
|
|
197
|
+
}
|
|
198
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { useState, useCallback, useMemo } from 'react'
|
|
2
|
+
import { UseTableStateReturn, TableState } from '../types'
|
|
3
|
+
|
|
4
|
+
interface UseTableStateProps<TData> {
|
|
5
|
+
initialData?: TData[]
|
|
6
|
+
initialPageSize?: number
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function useTableState<TData>({
|
|
10
|
+
initialData = [],
|
|
11
|
+
initialPageSize = 25,
|
|
12
|
+
}: UseTableStateProps<TData>): UseTableStateReturn<TData> {
|
|
13
|
+
const [data, setDataState] = useState<TData[]>(initialData)
|
|
14
|
+
const [currentPage, setCurrentPage] = useState(1)
|
|
15
|
+
const [pageSize, setPageSizeState] = useState(initialPageSize)
|
|
16
|
+
const [searchTerm, setSearchTerm] = useState('')
|
|
17
|
+
const [debouncedSearchTerm, setDebouncedSearchTerm] = useState('')
|
|
18
|
+
const [sortBy, setSortBy] = useState<string | null>(null)
|
|
19
|
+
const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('asc')
|
|
20
|
+
const [loading, setLoadingState] = useState(false)
|
|
21
|
+
const [loadingRows, setLoadingRowsState] = useState<Set<string>>(new Set())
|
|
22
|
+
const [error, setErrorState] = useState<Error | null>(null)
|
|
23
|
+
const [viewMode, setViewModeState] = useState<'table' | 'card'>('table')
|
|
24
|
+
|
|
25
|
+
// Computed values
|
|
26
|
+
const totalCount = data.length
|
|
27
|
+
const totalPages = Math.max(1, Math.ceil(totalCount / pageSize))
|
|
28
|
+
|
|
29
|
+
const state: TableState<TData> = useMemo(() => ({
|
|
30
|
+
data,
|
|
31
|
+
filteredData: data, // Will be computed by filters hook
|
|
32
|
+
displayedData: data, // Will be computed by pagination
|
|
33
|
+
currentPage,
|
|
34
|
+
pageSize,
|
|
35
|
+
totalPages,
|
|
36
|
+
totalCount,
|
|
37
|
+
selectedIds: new Set(),
|
|
38
|
+
selectAllPages: false,
|
|
39
|
+
activeFilters: {},
|
|
40
|
+
sortBy,
|
|
41
|
+
sortDirection,
|
|
42
|
+
searchTerm,
|
|
43
|
+
debouncedSearchTerm,
|
|
44
|
+
loading,
|
|
45
|
+
loadingRows,
|
|
46
|
+
error,
|
|
47
|
+
viewMode,
|
|
48
|
+
}), [
|
|
49
|
+
data,
|
|
50
|
+
currentPage,
|
|
51
|
+
pageSize,
|
|
52
|
+
totalPages,
|
|
53
|
+
totalCount,
|
|
54
|
+
sortBy,
|
|
55
|
+
sortDirection,
|
|
56
|
+
searchTerm,
|
|
57
|
+
debouncedSearchTerm,
|
|
58
|
+
loading,
|
|
59
|
+
loadingRows,
|
|
60
|
+
error,
|
|
61
|
+
viewMode,
|
|
62
|
+
])
|
|
63
|
+
|
|
64
|
+
const setData = useCallback((newData: TData[]) => {
|
|
65
|
+
setDataState(newData)
|
|
66
|
+
}, [])
|
|
67
|
+
|
|
68
|
+
const setPage = useCallback((page: number) => {
|
|
69
|
+
setCurrentPage(page)
|
|
70
|
+
}, [])
|
|
71
|
+
|
|
72
|
+
const setPageSize = useCallback((size: number) => {
|
|
73
|
+
setPageSizeState(size)
|
|
74
|
+
setCurrentPage(1) // Reset to first page when changing page size
|
|
75
|
+
}, [])
|
|
76
|
+
|
|
77
|
+
const setLoading = useCallback((isLoading: boolean) => {
|
|
78
|
+
setLoadingState(isLoading)
|
|
79
|
+
}, [])
|
|
80
|
+
|
|
81
|
+
const setLoadingRows = useCallback((ids: Set<string>) => {
|
|
82
|
+
setLoadingRowsState(ids)
|
|
83
|
+
}, [])
|
|
84
|
+
|
|
85
|
+
const setError = useCallback((err: Error | null) => {
|
|
86
|
+
setErrorState(err)
|
|
87
|
+
}, [])
|
|
88
|
+
|
|
89
|
+
const setViewMode = useCallback((mode: 'table' | 'card') => {
|
|
90
|
+
setViewModeState(mode)
|
|
91
|
+
}, [])
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
state,
|
|
95
|
+
setData,
|
|
96
|
+
setPage,
|
|
97
|
+
setPageSize,
|
|
98
|
+
setLoading,
|
|
99
|
+
setLoadingRows,
|
|
100
|
+
setError,
|
|
101
|
+
setViewMode,
|
|
102
|
+
}
|
|
103
|
+
}
|