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

@@ -9,9 +9,10 @@ import { EditableCell } from './EditableCell'
interface TableCellProps<T> {
cell: Cell<T, unknown>
showGrouping?: boolean
}
export function TableCell<T>({ cell }: TableCellProps<T>) {
export function TableCell<T>({ cell, showGrouping }: TableCellProps<T>) {
const isSelectionCol = cell.column.id === SELECTION_COLUMN_ID
const isEditing = useGriddyStore((s) => s.isEditing)
const focusedRowIndex = useGriddyStore((s) => s.focusedRowIndex)
@@ -50,13 +51,45 @@ export function TableCell<T>({ cell }: TableCellProps<T>) {
}
}
const isPinned = cell.column.getIsPinned()
const leftOffset = isPinned === 'left' ? cell.column.getStart('left') : undefined
const rightOffset = isPinned === 'right' ? cell.column.getAfter('right') : undefined
const isGrouped = cell.getIsGrouped()
const isAggregated = cell.getIsAggregated()
const isPlaceholder = cell.getIsPlaceholder()
return (
<div
className={styles[CSS.cell]}
className={[
styles[CSS.cell],
isPinned === 'left' ? styles['griddy-cell--pinned-left'] : '',
isPinned === 'right' ? styles['griddy-cell--pinned-right'] : '',
].filter(Boolean).join(' ')}
onDoubleClick={handleDoubleClick}
role="gridcell"
style={{ width: cell.column.getSize() }}
style={{
left: leftOffset !== undefined ? `${leftOffset}px` : undefined,
position: isPinned ? 'sticky' : 'relative',
right: rightOffset !== undefined ? `${rightOffset}px` : undefined,
width: cell.column.getSize(),
zIndex: isPinned ? 1 : 0,
}}
>
{showGrouping && isGrouped && (
<button
onClick={() => cell.row.toggleExpanded()}
style={{
border: 'none',
background: 'none',
cursor: 'pointer',
marginRight: 4,
padding: 0,
}}
>
{cell.row.getIsExpanded() ? '\u25BC' : '\u25B6'}
</button>
)}
{isFocusedCell && isEditable ? (
<EditableCell
cell={cell}
@@ -64,7 +97,13 @@ export function TableCell<T>({ cell }: TableCellProps<T>) {
onCancelEdit={handleCancel}
onCommitEdit={handleCommit}
/>
) : (
) : isGrouped ? (
<>
{flexRender(cell.column.columnDef.cell, cell.getContext())} ({cell.row.subRows.length})
</>
) : isAggregated ? (
flexRender(cell.column.columnDef.aggregatedCell ?? cell.column.columnDef.cell, cell.getContext())
) : isPlaceholder ? null : (
flexRender(cell.column.columnDef.cell, cell.getContext())
)}
</div>
@@ -73,12 +112,25 @@ export function TableCell<T>({ cell }: TableCellProps<T>) {
function RowCheckbox<T>({ cell }: TableCellProps<T>) {
const row = cell.row
const isPinned = cell.column.getIsPinned()
const leftOffset = isPinned === 'left' ? cell.column.getStart('left') : undefined
const rightOffset = isPinned === 'right' ? cell.column.getAfter('right') : undefined
return (
<div
className={styles[CSS.cell]}
className={[
styles[CSS.cell],
isPinned === 'left' ? styles['griddy-cell--pinned-left'] : '',
isPinned === 'right' ? styles['griddy-cell--pinned-right'] : '',
].filter(Boolean).join(' ')}
role="gridcell"
style={{ width: cell.column.getSize() }}
style={{
left: leftOffset !== undefined ? `${leftOffset}px` : undefined,
position: isPinned ? 'sticky' : 'relative',
right: rightOffset !== undefined ? `${rightOffset}px` : undefined,
width: cell.column.getSize(),
zIndex: isPinned ? 1 : 0,
}}
>
<Checkbox
aria-label={`Select row ${row.index + 1}`}