Skip to content
Snippets Groups Projects
Commit 51f92878 authored by Tom Robinson's avatar Tom Robinson Committed by GitHub
Browse files

Merge pull request #4977 from metabase/tablepalooza

Table layout improvements
parents 220a5400 bc264fb4
No related branches found
No related tags found
No related merge requests found
......@@ -67,10 +67,12 @@
display: none; /* Safari and Chrome */
}
.scroll-hide-all, .scroll-hide-all * {
.scroll-hide-all,
.scroll-hide-all * {
-ms-overflow-style: none; /* IE 10+ */
overflow: -moz-scrollbars-none; /* Firefox */
}
.scroll-hide-all::-webkit-scrollbar,
.scroll-hide-all *::-webkit-scrollbar {
display: none; /* Safari and Chrome */
}
......@@ -6,13 +6,11 @@
font-weight: 700;
}
.TableInteractive-headerCellData:hover {
cursor: pointer;
.TableInteractive-headerCellData .Icon {
opacity: 0;
}
.TableInteractive-headerCellData .Icon { opacity: 0; }
.TableInteractive-headerCellData:hover .Icon,
.TableInteractive-headerCellData--sorted .Icon {
.TableInteractive-headerCellData--sorted .Icon {
opacity: 1;
transition: opacity .3s linear;
}
......
......@@ -8,11 +8,9 @@ import "./TableInteractive.css";
import Icon from "metabase/components/Icon.jsx";
import Value from "metabase/components/Value.jsx";
import { capitalize } from "metabase/lib/formatting";
import { formatValue, capitalize } from "metabase/lib/formatting";
import { getFriendlyName } from "metabase/visualizations/lib/utils";
import { getTableCellClickedObject } from "metabase/visualizations/lib/table";
import { getTableCellClickedObject, isColumnRightAligned } from "metabase/visualizations/lib/table";
import _ from "underscore";
import cx from "classnames";
......@@ -21,8 +19,8 @@ import ExplicitSize from "metabase/components/ExplicitSize.jsx";
import { Grid, ScrollSync } from "react-virtualized";
import Draggable from "react-draggable";
const HEADER_HEIGHT = 50;
const ROW_HEIGHT = 35;
const HEADER_HEIGHT = 36;
const ROW_HEIGHT = 30;
const MIN_COLUMN_WIDTH = ROW_HEIGHT;
const RESIZE_HANDLE_WIDTH = 5;
......@@ -226,21 +224,23 @@ export default class TableInteractive extends Component<*, Props, State> {
return (
<div
key={key} style={style}
className={cx("TableInteractive-cellWrapper cellData", {
className={cx("TableInteractive-cellWrapper", {
"TableInteractive-cellWrapper--firstColumn": columnIndex === 0,
"cursor-pointer": isClickable
"cursor-pointer": isClickable,
"justify-end": isColumnRightAligned(column)
})}
onClick={isClickable && ((e) => {
onVisualizationClick({ ...clicked, element: e.currentTarget });
})}
>
<Value
className="link"
type="cell"
value={value}
column={column}
onResize={this.onCellResize.bind(this, columnIndex)}
/>
<div className="cellData">
{/* using formatValue instead of <Value> here for performance. The later wraps in an extra <span> */}
{formatValue(value, {
column: column,
type: "cell",
jsx: true
})}
</div>
</div>
);
}
......@@ -271,6 +271,10 @@ export default class TableInteractive extends Component<*, Props, State> {
const isClickable = onVisualizationClick && visualizationIsClickable(clicked);
const isSortable = isClickable && column.source;
const isRightAligned = isColumnRightAligned(column);
const isSorted = sort && sort[0] && sort[0][0] === column.id;
const isAscending = sort && sort[0] && sort[0][1] === "ascending";
return (
<div
......@@ -278,22 +282,21 @@ export default class TableInteractive extends Component<*, Props, State> {
style={{ ...style, overflow: "visible" /* ensure resize handle is visible */ }}
className={cx("TableInteractive-cellWrapper TableInteractive-headerCellData", {
"TableInteractive-cellWrapper--firstColumn": columnIndex === 0,
"TableInteractive-headerCellData--sorted": (sort && sort[0] && sort[0][0] === column.id),
"TableInteractive-headerCellData--sorted": isSorted,
"cursor-pointer": isClickable,
"justify-end": isRightAligned
})}
onClick={isClickable && ((e) => {
onVisualizationClick({ ...clicked, element: e.currentTarget });
})}
>
<div
className={cx("cellData", { "cursor-pointer": isClickable })}
onClick={isClickable && ((e) => {
onVisualizationClick({ ...clicked, element: e.currentTarget });
})}
>
<div className="cellData">
{isSortable && isRightAligned &&
<Icon className="Icon mr1" name={isAscending ? "chevronup" : "chevrondown"} size={8} />
}
{columnTitle}
{isSortable &&
<Icon
className="Icon ml1"
name={sort && sort[0] && sort[0][1] === "ascending" ? "chevronup" : "chevrondown"}
size={8}
/>
{isSortable && !isRightAligned &&
<Icon className="Icon ml1" name={isAscending ? "chevronup" : "chevrondown"} size={8} />
}
</div>
<Draggable
......
......@@ -11,7 +11,7 @@ import Icon from "metabase/components/Icon.jsx";
import { formatValue } from "metabase/lib/formatting";
import { getFriendlyName } from "metabase/visualizations/lib/utils";
import { getTableCellClickedObject } from "metabase/visualizations/lib/table";
import { getTableCellClickedObject, isColumnRightAligned } from "metabase/visualizations/lib/table";
import cx from "classnames";
import _ from "underscore";
......@@ -97,7 +97,14 @@ export default class TableSimple extends Component<*, Props, State> {
<thead ref="header">
<tr>
{cols.map((col, colIndex) =>
<th key={colIndex} className={cx("TableInteractive-headerCellData cellData text-brand-hover", { "TableInteractive-headerCellData--sorted": sortColumn === colIndex })} onClick={() => this.setSort(colIndex)}>
<th
key={colIndex}
className={cx("TableInteractive-headerCellData cellData text-brand-hover", {
"TableInteractive-headerCellData--sorted": sortColumn === colIndex,
"text-right": isColumnRightAligned(col)
})}
onClick={() => this.setSort(colIndex)}
>
<div className="relative">
<Icon
name={sortDescending ? "chevrondown" : "chevronup"}
......@@ -120,14 +127,16 @@ export default class TableSimple extends Component<*, Props, State> {
<td
key={columnIndex}
style={{ whiteSpace: "nowrap" }}
className={cx("px1 border-bottom", {
"cursor-pointer text-brand-hover": isClickable
})}
onClick={isClickable && ((e) => {
onVisualizationClick({ ...clicked, element: e.currentTarget });
})}
className={cx("px1 border-bottom", { "text-right": isColumnRightAligned(cols[columnIndex]) })}
>
{ cell == null ? "-" : formatValue(cell, { column: cols[columnIndex], jsx: true }) }
<span
className={cx({ "cursor-pointer text-brand-hover": isClickable })}
onClick={isClickable && ((e) => {
onVisualizationClick({ ...clicked, element: e.currentTarget });
})}
>
{ cell == null ? "-" : formatValue(cell, { column: cols[columnIndex], jsx: true }) }
</span>
</td>
);
})}
......
/* @flow */
import type { DatasetData } from "metabase/meta/types/Dataset";
import type { DatasetData, Column } from "metabase/meta/types/Dataset";
import type { ClickObject } from "metabase/meta/types/Visualization";
import { isNumber, isCoordinate } from "metabase/lib/schema_metadata";
export function getTableCellClickedObject(data: DatasetData, rowIndex: number, columnIndex: number, isPivoted: boolean): ClickObject {
const { rows, cols } = data;
......@@ -35,3 +36,11 @@ export function getTableCellClickedObject(data: DatasetData, rowIndex: number, c
return { value, column };
}
}
/*
* Returns whether the column should be right-aligned in a table.
* Includes numbers and lat/lon coordinates, but not zip codes, IDs, etc.
*/
export function isColumnRightAligned(column: Column) {
return isNumber(column) || isCoordinate(column);
}
import { getTableCellClickedObject } from "./table";
import { getTableCellClickedObject, isColumnRightAligned } from "./table";
import { TYPE } from "metabase/lib/types";
const RAW_COLUMN = {
source: "fields"
......@@ -40,4 +41,24 @@ describe("metabase/visualization/lib/table", () => {
// TODO:
})
})
describe("isColumnRightAligned", () => {
it("should return true for numeric columns without a special type", () => {
expect(isColumnRightAligned({ base_type: TYPE.Integer })).toBe(true);
});
it("should return true for numeric columns with special type Number", () => {
expect(isColumnRightAligned({ base_type: TYPE.Integer, special_type: TYPE.Number })).toBe(true);
});
it("should return true for numeric columns with special type latitude or longitude ", () => {
expect(isColumnRightAligned({ base_type: TYPE.Integer, special_type: TYPE.Latitude })).toBe(true);
expect(isColumnRightAligned({ base_type: TYPE.Integer, special_type: TYPE.Longitude })).toBe(true);
});
it("should return false for numeric columns with special type zip code", () => {
expect(isColumnRightAligned({ base_type: TYPE.Integer, special_type: TYPE.ZipCode })).toBe(false)
});
it("should return false for numeric columns with special type FK or PK", () => {
expect(isColumnRightAligned({ base_type: TYPE.Integer, special_type: TYPE.FK })).toBe(false);
expect(isColumnRightAligned({ base_type: TYPE.Integer, special_type: TYPE.FK })).toBe(false);
});
})
})
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment