Skip to content
This repository was archived by the owner on Aug 29, 2025. It is now read-only.
Merged
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
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,3 +188,15 @@
Issue: https://github.com/plotly/dash-table/issues/91

Sorting arrow will no longer highlight.

## RC23 - Width percentage

Columns can now accept '%' width, minWidth, maxWidth.

For the percentages to have meaning, the dash-table must be forced to have a width and the content of the dash-table must be forced to grow to fill the available space made available by the container (by default the table is only as big as it needs to be).

Added prop content_style that takes values 'fit' or 'grow' (Default='fit')

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Default table behavior is to take as little space as possible (fit) but for % based columns and it sometimes (always?) desirable to fill the dash-table container (grow)


1. Add the prop content_style='grow' to make the table fill its space, this will make sure the % are applied to all the space available.
2. Add the following selector (with the proper values) to the table_style prop
{ selector: '.dash-spreadsheet', rule: 'width: 100%; max-width: 100%' }
6 changes: 3 additions & 3 deletions dash_table/bundle.js

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions dash_table/demo.js

Large diffs are not rendered by default.

21 changes: 21 additions & 0 deletions dash_table/metadata.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,27 @@
"computed": false
}
},
"content_style": {
"type": {
"name": "enum",
"value": [
{
"value": "'fit'",
"computed": false
},
{
"value": "'grow'",
"computed": false
}
]
},
"required": false,
"description": "",
"defaultValue": {
"value": "'fit'",
"computed": false
}
},
"dataframe": {
"type": {
"name": "arrayOf",
Expand Down
2 changes: 1 addition & 1 deletion dash_table/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "dash-table",
"version": "3.0.0rc22",
"version": "3.0.0rc23",
"description": "Dash table",
"main": "build/index.js",
"scripts": {
Expand Down
1 change: 1 addition & 0 deletions demo/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ class App extends Component {
deletable: true,
// type: 'dropdown'
})),
content_style: 'grow',
editable: true,
sorting: true,
n_fixed_rows: 4,
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "dash-table",
"version": "3.0.0rc22",
"version": "3.0.0rc23",
"description": "Dash table",
"main": "build/index.js",
"scripts": {
Expand Down
2 changes: 2 additions & 0 deletions src/dash-table/Table.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export const defaultProps = {
},
navigation: 'page',

content_style: 'fit',
filtering: false,
filtering_settings: '',
filtering_type: 'basic',
Expand Down Expand Up @@ -87,6 +88,7 @@ export const defaultProps = {
export const propTypes = {
active_cell: PropTypes.array,
columns: PropTypes.arrayOf(PropTypes.object),
content_style: PropTypes.oneOf(['fit', 'grow']),

dataframe: PropTypes.arrayOf(PropTypes.object),
dataframe_previous: PropTypes.arrayOf(PropTypes.object),
Expand Down
69 changes: 50 additions & 19 deletions src/dash-table/components/ControlledTable/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,10 @@ export default class ControlledTable extends Component<ControlledTableProps> {
this.props.setProps({ active_cell: this.props.selected_cell[0] });
}

this.handleResize();
}

componentWillMount() {
// Fallback method for paste handling in Chrome
// when no input element has focused inside the table
window.addEventListener('resize', this.handleResize);
Expand All @@ -87,14 +91,6 @@ export default class ControlledTable extends Component<ControlledTableProps> {
document.removeEventListener('paste', this.handlePaste);
}

componentWillUpdate() {
const { table_style } = this.props;

R.forEach(({ selector, rule }) => {
this.stylesheet.setRule(selector, rule);
}, table_style);
}

componentDidUpdate() {
this.handleResize();
this.handleDropdown();
Expand Down Expand Up @@ -128,6 +124,12 @@ export default class ControlledTable extends Component<ControlledTableProps> {
handleResize = () => {
const { r0c0, r0c1, r1c0, r1c1 } = this.refs as { [key: string]: HTMLElement };

const { n_fixed_columns, n_fixed_rows, table_style } = this.props;

R.forEach(({ selector, rule }) => {
this.stylesheet.setRule(selector, rule);
}, table_style);

// Adjust [fixed columns/fixed rows combo] to fixed rows height
let trs = r0c1.querySelectorAll('tr');
r0c0.querySelectorAll('tr').forEach((tr, index) => {
Expand All @@ -152,6 +154,28 @@ export default class ControlledTable extends Component<ControlledTableProps> {

this.stylesheet.setRule('.cell-1-0 tr', `height: ${getComputedStyle(contentTr).height}`);
}

// Adjust the width of the fixed row header
if (n_fixed_rows) {
r1c1.querySelectorAll('tr:first-of-type td').forEach((td, index) => {
const width: any = getComputedStyle(td).width;
this.stylesheet.setRule(
`.dash-fixed-row:not(.dash-fixed-column) th:nth-of-type(${index + 1})`,
`width: ${width}; min-width: ${width}; max-width: ${width};`
);
});
}

// Adjust the width of the fixed row / fixed columns header
if (n_fixed_columns && n_fixed_rows) {
r1c0.querySelectorAll('tr:first-of-type td').forEach((td, index) => {
const width: any = getComputedStyle(td).width;
this.stylesheet.setRule(
`.dash-fixed-column.dash-fixed-row th:nth-of-type(${index + 1})`,
`width: ${width}; min-width: ${width}; max-width: ${width};`
);
});
}
}

get $el() {
Expand Down Expand Up @@ -594,10 +618,6 @@ export default class ControlledTable extends Component<ControlledTableProps> {
`.dash-spreadsheet-inner td.column-${typeIndex}`,
`width: 30px; max-width: 30px; min-width: 30px;`
);
this.stylesheet.setRule(
`.dash-spreadsheet-inner th.column-${typeIndex}`,
`width: 30px; max-width: 30px; min-width: 30px;`
);

++typeIndex;
}
Expand All @@ -607,10 +627,6 @@ export default class ControlledTable extends Component<ControlledTableProps> {
`.dash-spreadsheet-inner td.column-${typeIndex}`,
`width: 30px; max-width: 30px; min-width: 30px;`
);
this.stylesheet.setRule(
`.dash-spreadsheet-inner th.column-${typeIndex}`,
`width: 30px; max-width: 30px; min-width: 30px;`
);

++typeIndex;
}
Expand All @@ -624,6 +640,7 @@ export default class ControlledTable extends Component<ControlledTableProps> {
`.dash-spreadsheet-inner td.column-${typeIndex}`,
`width: ${width}; max-width: ${maxWidth}; min-width: ${minWidth};`
);

this.stylesheet.setRule(
`.dash-spreadsheet-inner th.column-${typeIndex}`,
`width: ${width}; max-width: ${maxWidth}; min-width: ${minWidth};`
Expand Down Expand Up @@ -707,6 +724,7 @@ export default class ControlledTable extends Component<ControlledTableProps> {
const {
id,
columns,
content_style,
n_fixed_columns,
n_fixed_rows,
row_deletable,
Expand All @@ -719,14 +737,27 @@ export default class ControlledTable extends Component<ControlledTableProps> {
'dash-spreadsheet-inner',
'dash-spreadsheet',
...(n_fixed_rows ? ['freeze-top'] : []),
...(n_fixed_columns ? ['freeze-left'] : [])
...(n_fixed_columns ? ['freeze-left'] : []),
[`dash-${content_style}`]
];

const containerClasses = [
'dash-spreadsheet',
'dash-spreadsheet-container',
...(n_fixed_rows ? ['freeze-top'] : []),
...(n_fixed_columns ? ['freeze-left'] : [])
...(n_fixed_columns ? ['freeze-left'] : []),
[`dash-${content_style}`]

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dash-grow or dash-fit, also contract material

];

const fragmentClasses = [
[
n_fixed_rows && n_fixed_columns ? 'dash-fixed-row dash-fixed-column' : '',
n_fixed_rows ? 'dash-fixed-row' : ''
],
[
n_fixed_columns ? 'dash-fixed-column' : '',
'dash-fixed-content'
]
];

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apply dash-* classes that are part of the table contract to the table fragments that are applicable


const cells = this.getCells();
Expand All @@ -748,7 +779,7 @@ export default class ControlledTable extends Component<ControlledTableProps> {
>{row.map((cell, columnIndex) => (<div
key={columnIndex}
ref={`r${rowIndex}c${columnIndex}`}
className={`cell cell-${rowIndex}-${columnIndex}`}
className={`cell cell-${rowIndex}-${columnIndex} ${fragmentClasses[rowIndex][columnIndex]}`}
>{cell}</div>))
}</div>))}
</div>
Expand Down
26 changes: 1 addition & 25 deletions src/dash-table/components/HeaderFactory.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import React from 'react';
import * as R from 'ramda';

import Stylesheet from 'core/Stylesheet';
import { SortDirection, SortSettings } from 'core/sorting';
import multiUpdateSettings from 'core/sorting/multi';
import singleUpdateSettings from 'core/sorting/single';
Expand Down Expand Up @@ -117,7 +116,6 @@ export default class HeaderFactory {
columnRowIndex,
labels,
mergeCells,
n_fixed_columns,
offset,
rowSorting,
virtualization
Expand All @@ -141,16 +139,12 @@ export default class HeaderFactory {
});
}

const visibleColumns = columns.filter(column => !column.hidden);

return R.filter(column => !!column, columnIndices.map((columnId, spanId) => {
const c = columns[columnId];
if (c.hidden) {
return null;
}

const visibleIndex = visibleColumns.indexOf(c) + offset;

let colSpan: number;
if (!mergeCells) {
colSpan = 1;
Expand All @@ -166,39 +160,21 @@ export default class HeaderFactory {
}
}

// This is not efficient and can be improved upon...
// Fixed columns need to override the default cell behavior when they span multiple columns
// Find all columns that fit the header's range [index, index+colspan[ and keep the fixed/visible ones
const visibleColumnId = visibleColumns.indexOf(c);

const spannedColumns = visibleColumns.filter((column, index) =>
!column.hidden &&
index >= visibleColumnId &&
index < visibleColumnId + colSpan &&
index + offset < n_fixed_columns
);

// Calculate the width of all those columns combined
const width = `calc(${spannedColumns.map(column => Stylesheet.unit(column.width || DEFAULT_CELL_WIDTH, 'px')).join(' + ')})`;
const maxWidth = `calc(${spannedColumns.map(column => Stylesheet.unit(column.maxWidth || column.width || DEFAULT_CELL_WIDTH, 'px')).join(' + ')})`;
const minWidth = `calc(${spannedColumns.map(column => Stylesheet.unit(column.minWidth || column.width || DEFAULT_CELL_WIDTH, 'px')).join(' + ')})`;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All of these are not applicable anymore and taken care of by the new sync style above

return (<th
key={`header-cell-${columnId}`}
colSpan={colSpan}
className={
`column-${columnId + offset} ` +
(columnId === columns.length - 1 || columnId === R.last(columnIndices) ? 'cell--right-last ' : '')
}
style={visibleIndex < n_fixed_columns ? { maxWidth, minWidth, width } : undefined}
>
{rowSorting ? (
<span
className='sort'
onClick={HeaderFactory.doSort(c.id, options)}
>
{HeaderFactory.getSortingIcon(c.id, options)}
</span>) : ('')
</span>) : ''
}

{((c.editable_name && R.type(c.editable_name) === 'Boolean') ||
Expand Down
11 changes: 11 additions & 0 deletions src/dash-table/components/Table/Table.less
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,17 @@
}
}

&.dash-grow {
.cell-0-1,
.cell-1-1 {
flex: 1 0 auto;
}

table {
width: 100%;
}
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When content_style='grow' we want the right side of the table (non-frozen columns) to fill the available space.
We also want all tables to grow as well


&:not(.freeze-top):not(.freeze-left) {
.cell-1-1 {
.top-left-cells();
Expand Down
6 changes: 6 additions & 0 deletions src/dash-table/components/Table/props.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ export enum FilteringType {
Basic = 'basic'
}

export enum ContentStyle {
Fit = 'fit',
Grow = 'grow'
}

export type ActiveCell = CellCoordinates | [];
export type CellCoordinates = [number, number];
export type ColumnId = string | number;
Expand Down Expand Up @@ -63,6 +68,7 @@ interface IProps {
column_conditional_styles?: any[];
column_static_dropdown?: any;
column_static_style?: any;
content_style: ContentStyle;
dataframe?: Dataframe;
dropdown_properties: any; // legacy
editable?: boolean;
Expand Down
2 changes: 1 addition & 1 deletion tests/visual/percy-storybook/DashTable.percy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import fixtures from './fixtures';
const setProps = () => { };

// Legacy: Tests previously run in Python
const fixtureStories = storiesOf('DashTable/Fixtures');
const fixtureStories = storiesOf('DashTable/Fixtures', module);
fixtures.forEach(fixture => fixtureStories.add(fixture.name, () => (<DashTable {...Object.assign(fixture.props)} />)));

storiesOf('DashTable/Without Data', module)
Expand Down
Loading