Row Pinning
Pin | First Name | Last Name | Age | Visits | Status | Profile Progress |
---|
Show
10
{"rowPinning": {"bottom": [],"top": []},"rowSelection": {}}
import {useMemo, useState} from "react"import {ArrowDown, ArrowUp, ChevronDown, ChevronRight, X} from "lucide-react"import {CodeHighlight} from "@qui/mdx-docs"import {QButton,QCheckbox,QIconButton,QPagination,QProgressCircle,QTextInput,} from "@qui/react"import {CellContext,Column,ColumnDef,ExpandedState,flexRender,getCoreRowModel,getExpandedRowModel,getFilteredRowModel,getPaginationRowModel,QTable,QTbody,QTd,QTh,QThead,QTr,Row,RowPinningState,Table,useReactTable,useTablePagination,} from "@qui/react-table"import {Person, usePersonData} from "~utils/data"export default function RowPinning() {// table statesconst [rowPinning, setRowPinning] = useState<RowPinningState>({bottom: [],top: [],})const [expanded, setExpanded] = useState<ExpandedState>({})// demo statesconst [keepPinnedRows, setKeepPinnedRows] = useState(false)const [includeLeafRows, setIncludeLeafRows] = useState(true)const [includeParentRows, setIncludeParentRows] = useState(true)const columns: ColumnDef<Person>[] = useMemo<ColumnDef<Person>[]>(() => [{cell: ({row}) =>row.getIsPinned() ? (<QIconButtondenseicon={X}onClick={() => row.pin(false, includeLeafRows, includeParentRows)}/>) : (<div className="flex gap-1"><QIconButtondenseicon={ArrowUp}onClick={() =>row.pin("top", includeLeafRows, includeParentRows)}/><QIconButtondenseicon={ArrowDown}onClick={() =>row.pin("bottom", includeLeafRows, includeParentRows)}/></div>),header: () => "Pin",id: "pin",},{accessorKey: "firstName",cell: ({getValue, row}: CellContext<Person, any>) => {const indeterminate = row.getIsSomeSelected()const checked = row.getIsSelected()return (<divclassName="inline-flex items-center gap-2"style={{// Since rows are flattened by default,// we can use the row.depth property// and paddingLeft to visually indicate the depth// of the rowpaddingLeft: `${row.depth * 2}rem`,}}><><QCheckboxchecked={checked}indeterminate={indeterminate}onChange={(event, checked) => {row.toggleSelected(checked)}}/>{row.getCanExpand() ? (<QIconButtonicon={row.getIsExpanded() ? ChevronDown : ChevronRight}onClick={row.getToggleExpandedHandler()}size="s"/>) : null}<span>{getValue()}</span></></div>)},footer: (props) => props.column.id,header: ({table}) => {return (<div className="flex items-center gap-2"><QCheckboxchecked={table.getIsAllRowsSelected()}indeterminate={table.getIsSomeRowsSelected()}onChange={table.getToggleAllRowsSelectedHandler()}/><QIconButtonicon={table.getIsAllRowsExpanded() ? ChevronDown : ChevronRight}onClick={table.getToggleAllRowsExpandedHandler()}size="s"/><span>First Name</span></div>)},},{accessorFn: (row) => row.lastName,cell: (info) => info.getValue(),header: "Last Name",id: "lastName",},{accessorKey: "age",header: "Age",size: 50,},{accessorKey: "visits",header: "Visits",size: 50,},{accessorKey: "status",header: "Status",},{accessorKey: "progress",header: "Profile Progress",size: 80,},],[includeLeafRows, includeParentRows],)const {data = [], isFetching, refetch} = usePersonData(1000, 2, 2)const refreshData = () => refetch()const table = useReactTable({columns,data,getCoreRowModel: getCoreRowModel(),getExpandedRowModel: getExpandedRowModel(),getFilteredRowModel: getFilteredRowModel(),getPaginationRowModel: getPaginationRowModel(),getSubRows: (row) => row.subRows,initialState: {pagination: {pageIndex: 0, pageSize: 10}},keepPinnedRows,onExpandedChange: setExpanded,onRowPinningChange: setRowPinning,state: {expanded,rowPinning,},})const paginationProps = useTablePagination(table)return (<div className="flex flex-col overflow-x-auto p-2"><div className="align-center vertical flex flex-col gap-2"><QCheckboxchecked={includeParentRows}label="Include Parent Rows When Pinning Child"onChange={(e, checked) => setIncludeParentRows(checked)}/><QCheckboxchecked={includeLeafRows}label="Include Leaf Rows When Pinning Parent"onChange={(e, checked) => setIncludeLeafRows(checked)}/><QCheckboxchecked={keepPinnedRows}label="Persist Pinned Rows across Pagination and Filtering"onChange={(e, checked) => setKeepPinnedRows(checked)}/><div className="mt-1 flex items-center gap-2"><QButton onClick={refreshData} variant="outline">Refresh Data</QButton>{isFetching ? <QProgressCircle size="xs" /> : null}</div></div><div className="mt-3 overflow-x-auto"><QTable><QThead>{table.getHeaderGroups().map((headerGroup) => (<QTr key={headerGroup.id}>{headerGroup.headers.map((header) => {return (<QTh key={header.id} colSpan={header.colSpan}>{header.isPlaceholder ? null : (<div className="inline-flex flex-col gap-1"><div className="inline-flex min-h-[28px] items-center justify-center">{flexRender(header.column.columnDef.header,header.getContext(),)}</div>{header.column.getCanFilter() ? (<Filter column={header.column} table={table} />) : null}</div>)}</QTh>)})}</QTr>))}</QThead><QTbody>{table.getTopRows().map((row) => (<PinnedRow key={row.id} row={row} table={table} />))}{table.getCenterRows().map((row) => {return (<QTr key={row.id} isSelected={row.getIsSelected()}>{row.getVisibleCells().map((cell) => {return (<QTd key={cell.id}>{flexRender(cell.column.columnDef.cell,cell.getContext(),)}</QTd>)})}</QTr>)})}{table.getBottomRows().map((row) => (<PinnedRow key={row.id} row={row} table={table} />))}</QTbody></QTable></div><div className="mt-4"><QPagination{...paginationProps}renderPageMeta={(context) =>`${context.currentPage} of ${context.totalPages}`}rowsPerPageLabel="Show"rowsPerPageOptions={[10, 20, 50]}/></div><CodeHighlightclassName="mt-4 w-fit"code={JSON.stringify({rowPinning: table.getState().rowPinning,rowSelection: table.getState().rowSelection,},null,2,)}disableCopylanguage="json"/></div>)}function PinnedRow({row, table}: {row: Row<Person>; table: Table<Person>}) {return (<QTrclassName="sticky"isSelectedstyle={{bottom:row.getIsPinned() === "bottom"? `${(table.getBottomRows().length - 1 - row.getPinnedIndex()) * 26}px`: undefined,top:row.getIsPinned() === "top"? `${row.getPinnedIndex() * 26 + 48}px`: undefined,}}>{row.getVisibleCells().map((cell) => {return (<QTd key={cell.id}>{flexRender(cell.column.columnDef.cell, cell.getContext())}</QTd>)})}</QTr>)}function Filter({column,table,}: {column: Column<Person>table: Table<Person>}) {const firstValue = table.getPreFilteredRowModel().flatRows[0]?.getValue(column.id)const columnFilterValue = column.getFilterValue()return typeof firstValue === "number" ? (<div className="flex gap-2"><QTextInputclassName="w-24"inputProps={{min: 0, type: "number"}}onChange={(e, value) =>column.setFilterValue((old: [number, number]) => [value, old?.[1]])}placeholder="Min"size="s"value={(columnFilterValue as [number, number])?.[0] ?? ""}/><QTextInputclassName="w-24"inputProps={{min: 0, type: "number"}}onChange={(e, value) =>column.setFilterValue((old: [number, number]) => [old?.[0], value])}placeholder="Max"size="s"value={(columnFilterValue as [number, number])?.[1] ?? ""}/></div>) : (<QTextInputclassName="w-36"onChange={(e, value) => column.setFilterValue(value)}placeholder="Search..."size="s"value={(columnFilterValue ?? "") as string}/>)}