Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions examples/react/column-resizing-full-width/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
node_modules
.DS_Store
dist
dist-ssr
*.local
13 changes: 13 additions & 0 deletions examples/react/column-resizing-full-width/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Full-Width Column Resizing Example

This example demonstrates how to make a TanStack Table fill its container width, with the last column automatically stretching to fill remaining space. Columns can still be individually resized while maintaining the full-width behavior.

This pattern is useful when you want the table to always fill its container (like a spreadsheet) rather than having a fixed width based on column sizes.

## Key Features

- Table always fills container width
- Last column stretches to fill remaining space
- Individual columns remain resizable
- Responsive to container size changes via ResizeObserver
- Double-click a column border to reset that column's size
13 changes: 13 additions & 0 deletions examples/react/column-resizing-full-width/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite App</title>
<script type="module" src="https://cdn.skypack.dev/twind/shim"></script>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
27 changes: 27 additions & 0 deletions examples/react/column-resizing-full-width/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"name": "tanstack-table-example-column-resizing-full-width",
"version": "0.0.0",
"private": true,
"scripts": {
"dev": "vite",
"build": "vite build",
"serve": "vite preview",
"start": "vite",
"lint": "eslint ./src",
"test:types": "tsc"
},
"dependencies": {
"@faker-js/faker": "^10.4.0",
"@tanstack/react-table": "^9.0.0-alpha.33",
"react": "^19.2.5",
"react-dom": "^19.2.5"
},
"devDependencies": {
"@rollup/plugin-replace": "^6.0.3",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^6.0.1",
"typescript": "6.0.3",
"vite": "^8.0.8"
}
}
78 changes: 78 additions & 0 deletions examples/react/column-resizing-full-width/src/index.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
* {
box-sizing: border-box;
}

html {
font-family: sans-serif;
font-size: 14px;
}

.table-container {
overflow-x: auto;
border: 1px solid lightgray;
}

.divTable {
min-width: 100%;
}

.tr {
display: flex;
}

tr,
.tr {
min-width: 100%;
}

th,
.th,
td,
.td {
box-shadow: inset 0 0 0 1px lightgray;
padding: 0.25rem;
}

th,
.th {
padding: 2px 4px;
position: relative;
font-weight: bold;
text-align: center;
height: 30px;
}

td,
.td {
height: 30px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}

.resizer {
position: absolute;
top: 0;
height: 100%;
right: 0;
width: 5px;
background: rgba(0, 0, 0, 0.5);
cursor: col-resize;
user-select: none;
touch-action: none;
}

.resizer.isResizing {
background: blue;
opacity: 1;
}

@media (hover: hover) {
.resizer {
opacity: 0;
}

*:hover > .resizer {
opacity: 1;
}
}
211 changes: 211 additions & 0 deletions examples/react/column-resizing-full-width/src/main.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import {
columnResizingFeature,
columnSizingFeature,
createColumnHelper,
tableFeatures,
useTable,
} from '@tanstack/react-table'
import { makeData } from './makeData'
import type { Person } from './makeData'
import './index.css'

const _features = tableFeatures({ columnSizingFeature, columnResizingFeature })

const columnHelper = createColumnHelper<typeof _features, Person>()

const columns = columnHelper.columns([
columnHelper.accessor('firstName', {
cell: (info) => info.getValue(),
header: 'First Name',
size: 150,
}),
columnHelper.accessor('lastName', {
cell: (info) => info.getValue(),
header: 'Last Name',
size: 150,
}),
columnHelper.accessor('age', {
header: 'Age',
size: 80,
}),
columnHelper.accessor('visits', {
header: 'Visits',
size: 100,
}),
columnHelper.accessor('status', {
header: 'Status',
size: 150,
}),
columnHelper.accessor('progress', {
header: 'Profile Progress',
size: 180,
}),
])

function App() {
const [data] = React.useState(() => makeData(20))
const tableContainerRef = React.useRef<HTMLDivElement>(null)
const [containerWidth, setContainerWidth] = React.useState(0)

// Track container width with ResizeObserver
React.useEffect(() => {
const container = tableContainerRef.current
if (!container) return

const observer = new ResizeObserver((entries) => {
for (const entry of entries) {
setContainerWidth(entry.contentRect.width)
}
})
observer.observe(container)
return () => observer.disconnect()
}, [])

const table = useTable(
{
_features,
_rowModels: {},
columns,
data,
defaultColumn: {
minSize: 50,
maxSize: 800,
},
columnResizeMode: 'onChange',
},
(state) => ({
columnSizing: state.columnSizing,
columnResizing: state.columnResizing,
}),
)

const visibleColumns = table.getVisibleLeafColumns()
const totalColumnsWidth = table.getTotalSize()

// Determine if the last column should stretch to fill remaining space
const shouldExtendLastColumn = totalColumnsWidth < containerWidth

// Compute the width for each column, stretching the last one if needed
const getColumnWidth = React.useCallback(
(columnId: string, index: number, baseWidth: number) => {
if (shouldExtendLastColumn && index === visibleColumns.length - 1) {
const otherColumnsWidth = visibleColumns
.slice(0, -1)
.reduce((sum, col) => sum + col.getSize(), 0)
return Math.max(baseWidth, containerWidth - otherColumnsWidth)
}
return baseWidth
},
[shouldExtendLastColumn, visibleColumns, containerWidth],
)

// Pre-compute column sizes as CSS variables for performant resizing
const columnSizeVars = React.useMemo(() => {
const headers = table.getFlatHeaders()
const colSizes: Record<string, number> = {}
for (let i = 0; i < headers.length; i++) {
const header = headers[i]
const width = getColumnWidth(
header.column.id,
i,
header.column.getSize(),
)
colSizes[`--header-${header.id}-size`] = width
colSizes[`--col-${header.column.id}-size`] = width
}
return colSizes
}, [table.state.columnResizing, table.state.columnSizing, getColumnWidth])

// Table width: always at least the container width
const tableWidth = Math.max(totalColumnsWidth, containerWidth)

return (
<div className="p-2">
<h3>Full-Width Column Resizing</h3>
<p>
The table fills its container. The last column stretches to fill any
remaining space. Try resizing individual columns — the last column
adjusts automatically. Resize the browser window to see the table adapt.
</p>
<div className="h-4" />
<div ref={tableContainerRef} className="table-container">
<div
className="divTable"
style={{
...columnSizeVars,
width: tableWidth,
}}
>
<div className="thead" style={{ position: 'sticky', top: 0 }}>
{table.getHeaderGroups().map((headerGroup) => (
<div key={headerGroup.id} className="tr">
{headerGroup.headers.map((header) => (
<div
key={header.id}
className="th"
style={{
width: `calc(var(--header-${header.id}-size) * 1px)`,
}}
>
{header.isPlaceholder ? null : (
<table.FlexRender header={header} />
)}
<div
onDoubleClick={() => header.column.resetSize()}
onMouseDown={header.getResizeHandler()}
onTouchStart={header.getResizeHandler()}
className={`resizer ${
header.column.getIsResizing() ? 'isResizing' : ''
}`}
/>
</div>
))}
</div>
))}
</div>
<div className="tbody">
{table.getRowModel().rows.map((row) => (
<div key={row.id} className="tr">
{row.getAllCells().map((cell) => (
<div
key={cell.id}
className="td"
style={{
width: `calc(var(--col-${cell.column.id}-size) * 1px)`,
}}
>
{cell.renderValue<string>()}
</div>
))}
</div>
))}
</div>
</div>
</div>
<div className="h-4" />
<pre style={{ fontSize: '12px' }}>
{JSON.stringify(
{
containerWidth,
totalColumnsWidth,
shouldExtendLastColumn,
columnSizing: table.state.columnSizing,
},
null,
2,
)}
</pre>
</div>
)
}

const rootElement = document.getElementById('root')
if (!rootElement) throw new Error('Failed to find the root element')

ReactDOM.createRoot(rootElement).render(
<React.StrictMode>
<App />
</React.StrictMode>,
)
37 changes: 37 additions & 0 deletions examples/react/column-resizing-full-width/src/makeData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { faker } from '@faker-js/faker'

export type Person = {
firstName: string
lastName: string
age: number
visits: number
progress: number
status: 'relationship' | 'complicated' | 'single'
}

const range = (len: number) => {
const arr: Array<number> = []
for (let i = 0; i < len; i++) {
arr.push(i)
}
return arr
}

const newPerson = (): Person => {
return {
firstName: faker.person.firstName(),
lastName: faker.person.lastName(),
age: faker.number.int(40),
visits: faker.number.int(1000),
progress: faker.number.int(100),
status: faker.helpers.shuffle<Person['status']>([
'relationship',
'complicated',
'single',
])[0],
}
}

export function makeData(len: number): Array<Person> {
return range(len).map(() => newPerson())
}
Loading