feat(core): add column pinning and grouping features to Griddy table

- Implement column pinning functionality allowing users to pin columns to the left or right.
- Introduce data grouping capabilities for better data organization.
- Enhance the theming guide with new styles for pinned columns and loading indicators.
- Add infinite scroll support with loading indicators for improved user experience.
- Update CSS styles to accommodate new features and improve visual feedback.
This commit is contained in:
2026-02-14 21:18:04 +02:00
parent ad325d94a9
commit e776844588
17 changed files with 1161 additions and 124 deletions

View File

@@ -1,4 +1,4 @@
import { useEffect } from 'react'
import { useEffect, useRef } from 'react'
import { CSS } from '../core/constants'
import { useGriddyStore } from '../core/GriddyStore'
@@ -9,11 +9,15 @@ export function VirtualBody() {
const table = useGriddyStore((s) => s._table)
const virtualizer = useGriddyStore((s) => s._virtualizer)
const setTotalRows = useGriddyStore((s) => s.setTotalRows)
const infiniteScroll = useGriddyStore((s) => s.infiniteScroll)
const rows = table?.getRowModel().rows
const virtualRows = virtualizer?.getVirtualItems()
const totalSize = virtualizer?.getTotalSize() ?? 0
// Track if we're currently loading to prevent multiple simultaneous calls
const isLoadingRef = useRef(false)
// Sync row count to store for keyboard navigation bounds
useEffect(() => {
if (rows) {
@@ -21,8 +25,45 @@ export function VirtualBody() {
}
}, [rows?.length, setTotalRows])
// Infinite scroll: detect when approaching the end
useEffect(() => {
if (!infiniteScroll?.enabled || !infiniteScroll.onLoadMore || !virtualRows || !rows) {
return
}
const { threshold = 10, hasMore = true, isLoading = false } = infiniteScroll
// Don't trigger if already loading or no more data
if (isLoading || !hasMore || isLoadingRef.current) {
return
}
// Check if the last rendered virtual row is within threshold of the end
const lastVirtualRow = virtualRows[virtualRows.length - 1]
if (!lastVirtualRow) return
const lastVirtualIndex = lastVirtualRow.index
const totalRows = rows.length
const distanceFromEnd = totalRows - lastVirtualIndex - 1
if (distanceFromEnd <= threshold) {
isLoadingRef.current = true
const loadPromise = infiniteScroll.onLoadMore()
if (loadPromise instanceof Promise) {
loadPromise.finally(() => {
isLoadingRef.current = false
})
} else {
isLoadingRef.current = false
}
}
}, [virtualRows, rows, infiniteScroll])
if (!table || !virtualizer || !rows || !virtualRows) return null
const showLoadingIndicator = infiniteScroll?.enabled && infiniteScroll.isLoading
return (
<div
className={styles[CSS.tbody]}
@@ -46,6 +87,19 @@ export function VirtualBody() {
/>
)
})}
{showLoadingIndicator && (
<div
className={styles['griddy-loading-indicator']}
style={{
bottom: 0,
left: 0,
position: 'absolute',
right: 0,
}}
>
<div className={styles['griddy-loading-spinner']}>Loading more...</div>
</div>
)}
</div>
)
}