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

@@ -12,49 +12,87 @@ export function getGriddyColumn<T>(column: { columnDef: ColumnDef<T> }): GriddyC
return (column.columnDef.meta as { griddy?: GriddyColumn<T> })?.griddy
}
/**
* Converts a single GriddyColumn to a TanStack ColumnDef
*/
function mapSingleColumn<T>(col: GriddyColumn<T>): ColumnDef<T> {
const isStringAccessor = typeof col.accessor !== 'function'
const def: ColumnDef<T> = {
id: col.id,
// Use accessorKey for string keys (enables TanStack auto-detection of sort/filter),
// accessorFn for function accessors
...(isStringAccessor
? { accessorKey: col.accessor as string }
: { accessorFn: col.accessor as (row: T) => unknown }),
aggregationFn: col.aggregationFn,
enableColumnFilter: col.filterable ?? false,
enableGrouping: col.groupable ?? false,
enableHiding: true,
enablePinning: true,
enableResizing: true,
enableSorting: col.sortable ?? true,
header: () => col.header,
maxSize: col.maxWidth ?? DEFAULTS.maxColumnWidth,
meta: { griddy: col },
minSize: col.minWidth ?? DEFAULTS.minColumnWidth,
size: col.width,
}
// For function accessors, TanStack can't auto-detect the sort type, so provide a default
if (col.sortFn) {
def.sortingFn = col.sortFn
} else if (!isStringAccessor && col.sortable !== false) {
// Use alphanumeric sorting for function accessors
def.sortingFn = 'alphanumeric'
}
if (col.filterFn) {
def.filterFn = col.filterFn
} else if (col.filterable) {
def.filterFn = createOperatorFilter()
}
return def
}
/**
* Maps Griddy's user-facing GriddyColumn<T> definitions to TanStack Table ColumnDef<T>[].
* Optionally prepends a selection checkbox column.
* Supports header grouping and optionally prepends a selection checkbox column.
*/
export function mapColumns<T>(
columns: GriddyColumn<T>[],
selection?: SelectionConfig,
): ColumnDef<T>[] {
const mapped: ColumnDef<T>[] = columns.map((col) => {
const isStringAccessor = typeof col.accessor !== 'function'
// Group columns by headerGroup
const grouped = new Map<string, GriddyColumn<T>[]>()
const ungrouped: GriddyColumn<T>[] = []
const def: ColumnDef<T> = {
id: col.id,
// Use accessorKey for string keys (enables TanStack auto-detection of sort/filter),
// accessorFn for function accessors
...(isStringAccessor
? { accessorKey: col.accessor as string }
: { accessorFn: col.accessor as (row: T) => unknown }),
enableColumnFilter: col.filterable ?? false,
enableHiding: true,
enableResizing: true,
enableSorting: col.sortable ?? true,
header: () => col.header,
maxSize: col.maxWidth ?? DEFAULTS.maxColumnWidth,
meta: { griddy: col },
minSize: col.minWidth ?? DEFAULTS.minColumnWidth,
size: col.width,
columns.forEach(col => {
if (col.headerGroup) {
const existing = grouped.get(col.headerGroup) || []
existing.push(col)
grouped.set(col.headerGroup, existing)
} else {
ungrouped.push(col)
}
})
// For function accessors, TanStack can't auto-detect the sort type, so provide a default
if (col.sortFn) {
def.sortingFn = col.sortFn
} else if (!isStringAccessor && col.sortable !== false) {
// Use alphanumeric sorting for function accessors
def.sortingFn = 'alphanumeric'
}
// Build column definitions
const mapped: ColumnDef<T>[] = []
if (col.filterFn) {
def.filterFn = col.filterFn
} else if (col.filterable) {
def.filterFn = createOperatorFilter()
// Add ungrouped columns first
ungrouped.forEach(col => {
mapped.push(mapSingleColumn(col))
})
// Add grouped columns
grouped.forEach((groupColumns, groupName) => {
const groupDef: ColumnDef<T> = {
header: groupName,
id: `group-${groupName}`,
columns: groupColumns.map(col => mapSingleColumn(col)),
}
return def
mapped.push(groupDef)
})
// Prepend checkbox column if selection is enabled