Column Sizing
Columns are a core concept for
Infinite Table and sizing columns is an important topic to master. Here is a summary of how columns can be sized:- fixed-sized columns can be specified via
columns.defaultWidth - flexible columns need
columns.defaultFlex columns.minWidthspecifies the minimum size for a columncolumns.maxWidthis for the maximum width a column can take- default values are available for all of the above:
columnDefaultWidthgives all columns (that are otherwise unconfigured) a default sizecolumnMinWidthspecifies the minimum width for all columns (that don't have one)columnMaxWidthspecifies the maximum width for all columns (that don't have one)
For fine-grained controlled-behavior on column sizing, use the controlled
columnSizing prop (for uncontrolled variant, see defaultColumnSizing). If you want to get updates to columns changing size as a result of user interaction, use onColumnSizingChange.Use
columnDefaultWidth to configure the default column width. If a column is not sized otherwise, this will be applied. The default value for columnDefaultWidth is 200 (pixels).For setting a minimum and maximum width for all columns, use
columnMinWidth (defaults to 30) and columnMaxWidth (defaults to 2000) respectively.Understanding default column sizing#
The easiest way to get started and specify a sizing behavior for columns is to use
column.defaultWidth, column.defaultFlex and/or columnDefaultWidth (including related pros for specifying limits, like column.minWidth, column.maxWidth and columnMinWidth / columnMaxWidth).Those properties have
default in their name because after the initial rendering of a column, you can't change its size by updating those values - more technically, column.defaultWidth and column.defaultFlex are uncontrolled props.We suggest you use those to get started and if you don't have care about responding to the user changing the widths of those columns via drag&drop. As long as you're not using
onColumnSizingChange to be notified of column size changes, you're probably good with those.Controlled column sizing#
However, once you start using
onColumnSizingChange and want to have full control of column sizing (maybe you want to restore it later to the state the user had it when the app was closed), you probably want to use controlled columnSizing.The
columnSizing prop is an object of column ids to column sizing objects. Those sizing objects can have the following properties:flex- use this for flexible columns. Behaves like the flex CSS property.width- use this for fixed sized columnsminWidth- specifies the minimum width of the column. Useful for flexible columns or for restricting users resizing both fixed and flexible columns.maxWidth- specifies the maximum width of the column. Useful for flexible columns or for restricting users resizing both fixed and flexible columns.
If a column is not specified in the
columnSizing prop (or its uncontrolled variant), or sized otherwise (eg: via the column type), it will have a fixed size, defaulting to columnDefaultWidth (which also defaults to 200 if no value is passed in). You can also specify a columnMinWidth and columnMaxWidth - those will be applied for all columns (namely for those that dont explicitly specify other min/max widths).const columnSizing: InfiniteTablePropColumnSizing = {
country: {
flex: 1,
// minWidth is optional
minWidth: 200,
},
city: {
width: 400,
// and so is maxWidth
maxWidth: 500,
},
salary: {
flex: 3,
},
};
// any column not specified in the columnSizing (or defaultColumnSizing) prop
// will have fixed width (defaulting to `columnDefaultWidth`, which in turn defaults to 200px) COPY
You might find specifying the column size outside the column object to be a bit verbose to start with, but it will be easier to manage in many cases and is much more flexible. For example, when the user resizes a column via drag & drop and you want to persist the new column sizes, you don't have to update the whole
columns object but instead update columnSizing alone.
The same principle is true for columnPinning and other column-level props.The
columnSizing prop also has an uncontrolled version, namely defaultColumnSizing.Using flexible column sizing#
The way flex sizing is implemented is similar to how CSS flexbox algorithm works. Explore this section to find out more details.
Imagine you have
1000px of space available to the viewport of InfiniteTable and you have 3 columns:- a fixed column
100pxwide - name it colA - a fixed column
300pxwide - name it colB - a flexible column with
flex: 1- name it colF1 - a flexible column with
flex: 2- name it colF2
The space remaining for the flexible columns is
1000px - 400px = 600px and the sum of all flex values is 3, that means each flex unit will be 600px / 3 = 200px.This means columns will have the following sizes:
- col
Awill be100px - col
Bwill be300px - col
F1will be200px( so a flex unit) - col
F2will be400px( so the equivalent of2flex units)
If the browser changes the layout of the component, so
InfiniteTable has only 700px available, then a flex unit would be (700px - 400px) / 3 = 100px.This means columns will have the following sizes:
- col
Awill be100px - col
Bwill be300px - col
F1will be100px( so a flex unit) - col
F2will be200px( so the equivalent of2flex units)
The flexbox algorithm also uses
viewportReservedWidth to determine the width of the viewport to use for sizing columns - you can use viewportReservedWidth=100 to always have a 100px reserved area that won't be used for flexing columns.This example has a
viewportReservedWidth of 50px.View Mode
Fork Forkimport { InfiniteTable, DataSource, InfiniteTablePropColumnSizing, InfiniteTableColumn, } from '@infinite-table/infinite-react'; import * as React from 'react'; import { useState } from 'react'; export const columns: Record<string, InfiniteTableColumn<Employee>> = { firstName: { field: 'firstName', header: 'First Name', }, country: { field: 'country', header: 'Country', }, city: { field: 'city', header: 'City', }, salary: { field: 'salary', type: 'number', header: 'Salary', }, }; const defaultColumnSizing: InfiniteTablePropColumnSizing = { country: { flex: 1 }, city: { flex: 1 }, salary: { flex: 2 }, }; export default function App() { const [viewportReservedWidth, setViewportReservedWidth] = useState(0); return ( <> <div style={{ color: 'var(--infinite-cell-color' }}> <p>Current viewport reserved width: {viewportReservedWidth}px.</p> <button style={{ padding: 5, margin: 5, border: '2px solid currentColor', }} onClick={() => { setViewportReservedWidth(0); }} > Click to reset viewportReservedWidth to 0 </button> </div> <DataSource<Employee> data={dataSource} primaryKey="id"> <InfiniteTable<Employee> columns={columns} columnDefaultWidth={50} viewportReservedWidth={viewportReservedWidth} onViewportReservedWidthChange={setViewportReservedWidth} defaultColumnSizing={defaultColumnSizing} /> </DataSource> </> ); } const dataSource = () => { return fetch('https://infinite-table.com/.netlify/functions/json-server' + '/employees100') .then((r) => r.json()) .then((data: Employee[]) => data); }; export type Employee = { id
Take a look at the snippet below to see column sizing at work with flexible and fixed columns.
View Mode
Fork Forkimport { InfiniteTable, DataSource, InfiniteTablePropColumnSizing, InfiniteTablePropColumns, } from '@infinite-table/infinite-react'; import * as React from 'react'; import { useState } from 'react'; export const columns: InfiniteTablePropColumns<Employee> = { firstName: { field: 'firstName', header: 'First Name', }, country: { field: 'country', header: 'Country', }, city: { field: 'city', header: 'City', }, salary: { field: 'salary', type: 'number', header: 'Salary', }, }; export default function App() { const [columnSizing, setColumnSizing] = React.useState<InfiniteTablePropColumnSizing>({ country: { width: 100 }, city: { flex: 1, minWidth: 100 }, salary: { flex: 2, maxWidth: 500 }, }); const [viewportReservedWidth, setViewportReservedWidth] = useState(0); return ( <> <p style={{ color: 'var(--infinite-cell-color)' }}> Current column sizing:{' '} <code> <pre>{JSON.stringify(columnSizing, null, 2)}</pre> </code> Viewport reserved width: {viewportReservedWidth} -{' '} <button onClick={() => setViewportReservedWidth(0)}> click to reset to 0 </button> </p> <DataSource<Employee> data={dataSource} primaryKey="id"> <InfiniteTable<Employee> columns={columns} columnDefaultWidth={50} columnSizing={columnSizing} onColumnSizingChange={setColumnSizing} viewportReservedWidth={viewportReservedWidth} onViewportReservedWidthChange={setViewportReservedWidth} /> </DataSource> </> ); } const dataSource = () => { return fetch('https://infinite-table.com/.netlify/functions/json-server' + '/employees100') .then((r) => r.json()) .then((data: Employee[]) => data); }; export type Employee = { id
You might find
viewportReservedWidth useful for advanced configuration when you have flexible columns.When he user is performing a column resize (via drag & drop),
onViewportReservedWidth is called when the resize is finished (not the case for resizing with the SHIFT key pressed, when adjacent columns share the space between them).You can also size (generated) group columns by using their
column.id property.For
groupRenderStrategy="multi-column", if no id is specified in the group column configuration, each column will have a generated id like this: "group-by-${field}".For
groupRenderStrategy="single-column", if no id is specified in the groupColumn it will default to: "group-by".Resizing columns via drag & drop#
Columns are user-resizable via drag & drop. If you don't want a column to be resizable, specify
column.resizable=falseBy default, all columns are resizable since
resizableColumns defaults to true. The resizableColumns prop controls the behavior for all columns that don't explicitly specify their column.resizable property.When initially rendered, columns are displayed with their
columns.defaultWidth (you can also use columnDefaultWidth) or columns.defaultFlex. Flexible columns take up available space taking into account their flex value, as detailed above.When the user is resizing columns (or column groups), the effect is seen in real-time, so it's very easy to adjust the columns to desired widths. After the user drops the resize handle to the desired position,
onColumnSizingChange is being called, to allow the developer to react to column sizing changes. Also onViewportReservedWidth is called as well when the resize is finished (not the case for resizing with the SHIFT key pressed, when adjacent columns share the space between them).When flexible columns are resized, they are kept flexible even after the resize. Note however that their flex values will be different to the original flex values and will reflect the new proportions each flex column is taking up at the moment of the resize.
More exactly, the new flex values will be the actual pixel widths. As an example, say there are 2 flex columns, first one with flex
1 and second one with flex 3 and they have an available space of 800px.const columns = {
first: { flex: 1, field: 'one' },
second: { flex: 2, field: 'two' },
}; COPY
Initially they will occupy
200px and 600px respectively. If the user resizes them to be of equal size, onColumnSizingChange will be called with an object like{
first: { flex: 400 },
second: {flex: 400 }
} COPY
since those are the actual widths measured from the DOM. This works out well, even if the available space of the table grows, as the proportions will be the same.
Resize Restrictions#
When resizing, the user needs to drag the resize handle to adjust the columns to new sizes. While doing so, the resize handle has a (green) color to indicate everything is okay. However, when restrictions are hit (either column
min or max widths), the resize handle turns red to indicate further resizing is not possible.Sharing space on resize#
By default when resizing a specific column, the following columns are pushed to the right (when making the column wider) or moved to the left (when making the column narrower).
For sharing space between resizable columns when resizing, the user needs to hold the SHIFT key when grabbing the resize handle. When the handle is dropped and the resize confirmed,
onColumnSizingChange is called, but onViewportReservedWidth is not called for this scenario, since the reserved width is preserved.Resizing column groups#
Just as columns are being resized, it is also possible to resize column groups. For this, the user needs to hover over the right border of the column group and start dragging the resize handle.
For multi-level column groups, it's possible to resize any of them. Just grab the handle from the desired group and start dragging. The handle height will indicate which column group is being resized.
If a column group has at least one resizable column, it can be resized.
When resizing, the space is shared proportionally betweem all resizable columns in the group.
Once a min/max limit has been reached for a certain column in the group, the column respects the limit and the other columns keep resizing as usual. When the min/max limit has been reached for all columns in the group, the resize handle turns red to indicate further resizing is no longer possible.
Try resizing the
Finance and Regional Info column groups.The columns in the
Finance group can be resized an extra 30px (they have a maxWidth of 130px).View Mode
Fork Forkimport { InfiniteTable, DataSource, InfiniteTableColumnGroup, } from '@infinite-table/infinite-react'; import type { InfiniteTablePropColumns } from '@infinite-table/infinite-react'; import * as React from 'react'; type Developer = { id: number; firstName: string; lastName: string; country: string; city: string; currency: string; preferredLanguage: string; stack: string; canDesign: 'yes' | 'no'; hobby: string; salary: number; age: number; }; const dataSource = () => { return fetch('https://infinite-table.com/.netlify/functions/json-server' + '/developers1k') .then((r) => r.json()) .then((data: Developer[]) => data); }; const columns: InfiniteTablePropColumns<Developer> = { currency: { field: 'currency', columnGroup: 'finance', maxWidth: 130, }, salary: { field: 'salary', columnGroup: 'finance', maxWidth: 130, }, country: { field: 'country', columnGroup: 'regionalInfo', maxWidth: 400, }, preferredLanguage: { field: 'preferredLanguage', columnGroup: 'regionalInfo', }, id: { field: 'id', defaultWidth: 80 }, firstName: { field: 'firstName', }, stack: { field: 'stack', }, }; const columnGrous: Record<string, InfiniteTableColumnGroup> = { regionalInfo: { header: 'Regional Info', }, finance: { header: 'Finance', columnGroup: 'regionalInfo', }, }; export default function ColumnValueGetterExample() { return ( <> <DataSource<Developer> primaryKey="id" data={dataSource}> <InfiniteTable<Developer> columnGroups={columnGrous} columns={columns} columnDefaultWidth={100} /> </DataSource> </>
Customizing the resize handle colors#
It's possible to customize the resize handle colors and width.
For adjusting the handle colors, use the following CSS variables:
--infinite-resize-handle-hover-background- the color of the resize handle when it's in agreen/all good state.--infinite-resize-handle-constrained-hover-background- the color of the resize handle when it has reached a min/max constraint.
You can also adjust the width of the resize handle:
--infinite-resize-handle-width- the width of thegreen/redcolumn resize handle. Defaults to2px--infinite-resize-handle-active-area-width- the width of the area you can hover over in order to grab the resize handle. Defaults to20px. The purpose of this active area is to make it easier to grab the resize handle.
Auto-sizing columns#
For sizing columns to the width of their content, you can use
autoSizeColumnsKey to declaratively auto-size columns:- when
autoSizeColumnsKeyis astringornumberand the value of the prop is changed, all columns will be auto-sized. - when
autoSizeColumnsKeyis an object, it needs to have akeyproperty (of typestringornumber), so whenever thekeychanges, the columns will be auto-sized. Specifying an object forautoSizeColumnsKeygives you more control over which columns are auto-sized and if the size measurements include the header or not.
When an object is used, the following properties are available:
key- mandatory property, which, when changed, triggers the updateincludeHeader- optional boolean, - decides whether the header will be included in the auto-sizing calculations. If not specified,trueis assumed.columnsToSkip- a list of column ids to skip from auto-sizing. If this is used, all columns except those in the list will be auto-sized.columnsToResize- the list of column ids to include in auto-sizing. If this is used, only columns in the list will be auto-sized.
View Mode
Fork Forkimport { InfiniteTable, DataSource } from '@infinite-table/infinite-react'; import type { InfiniteTablePropColumns } from '@infinite-table/infinite-react'; import * as React from 'react'; type Developer = { id: number; firstName: string; lastName: string; country: string; city: string; currency: string; preferredLanguage: string; stack: string; canDesign: 'yes' | 'no'; hobby: string; salary: number; age: number; }; const dataSource = () => { return fetch('https://infinite-table.com/.netlify/functions/json-server' + '/developers1k') .then((r) => r.json()) .then((data: Developer[]) => data); }; const columns: InfiniteTablePropColumns<Developer> = { id: { field: 'id' }, firstName: { field: 'firstName' }, preferredLanguage: { field: 'preferredLanguage' }, stack: { field: 'stack' }, country: { field: 'country' }, age: { field: 'age', type: 'number' }, salary: { field: 'salary', type: 'number' }, currency: { field: 'currency', type: 'number' }, }; export default function GroupByExample() { const [key, setKey] = React.useState(0); const [includeHeader, setIncludeHeader] = React.useState(false); const autoSizeColumnsKey = React.useMemo(() => { return { includeHeader, key, }; }, [key, includeHeader]); return ( <> <div style={{ color: 'var(--infinite-cell-color)', background: 'var(--infinite-background)', }} > <label> <input checked={includeHeader} type={'checkbox'} onChange={(e) => { setIncludeHeader(e.target.checked); }} />{' '} Include header </label> <button style={{ margin: 10, padding: 10, borderRadius: 5, border: '2px solid magenta', }} onClick={() => { setKey((key) => key + 1); }} > Click to auto-size </button> </div> <DataSource<Developer> primaryKey="id" data={dataSource}> <InfiniteTable<Developer> autoSizeColumnsKey={autoSizeColumnsKey} columns={columns} columnDefaultWidth={200} /> </DataSource> </>