Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Remove rowLink from RowStyles. #764

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
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
76 changes: 37 additions & 39 deletions src/components/Table/GridTable.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -177,11 +177,12 @@ describe("GridTable", () => {

it("can indent rows", async () => {
// Given the data row is indented
const rowStyles: RowStyles<Row> = {
header: {},
data: { indent: 1 },
const column: GridColumn<Row> = {
id: "name",
header: () => "Name",
data: ({ name }) => ({ content: name, indent: 1 }),
};
const r = await render(<GridTable columns={[nameColumn, valueColumn]} rows={rows} rowStyles={rowStyles} />);
const r = await render(<GridTable<Row> columns={[column]} rows={rows} />);
// Then the data row has the style added
expect(cell(r, 1, 0)).toHaveStyleRule("padding-left", "32px");
// But the header row does not
Expand Down Expand Up @@ -957,21 +958,26 @@ describe("GridTable", () => {

it("can handle onClick for rows", async () => {
const onClick = jest.fn();
const rowStyles: RowStyles<Row> = { header: {}, data: { onClick } };
const r = await render(<GridTable {...{ columns, rows, rowStyles }} />);
const r = await render(
<GridTable
{...{
columns,
rows: [simpleHeader, { kind: "data", id: "1", data: { name: "foo", value: 1 }, onClick }],
}}
/>,
);
click(cell(r, 1, 0));
expect(onClick).toHaveBeenCalledTimes(1);
expect(onClick.mock.calls[0][0].data.name).toEqual("foo");
});

it("can omit onClick for columns", async () => {
// Given rowStyles that specify an action for each row
// Given a row.onClick is specified
const onClick = jest.fn();
const rowStyles: RowStyles<Row> = { header: {}, data: { onClick } };
// And a table where one columns omits wrapping the action
const r = await render(
<GridTable {...{ columns: [{ ...columns[0], wrapAction: false }, columns[1]], rows, rowStyles }} />,
);
const row = { kind: "data", id: "1", data: { name: "foo", value: 1 }, onClick } as const;
// And a table where the 1st column omits wrapping the action
const column1 = { ...columns[0], wrapAction: false as const };
const r = await render(<GridTable {...{ columns: [column1, columns[1]], rows: [simpleHeader, row] }} />);
// When clicking on both columns
click(cell(r, 1, 0));
click(cell(r, 1, 1));
Expand All @@ -981,20 +987,18 @@ describe("GridTable", () => {

it("can omit rowLink for columns", async () => {
// Given rowStyles that specify an action for each row
const rowStyles: RowStyles<Row> = {
header: {},
data: {
rowLink: () => "https://www.homebound.com",
},
};
// And a table where one columns omits wrapping the action
const r = await render(
<GridTable {...{ columns: [{ ...columns[0], wrapAction: false }, columns[1]], rows, rowStyles }} />,
withRouter(),
);
const row = {
kind: "data",
id: "1",
data: { name: "foo", value: 1 },
onClick: "https://www.homebound.com",
} as const;
// And a table where the 1st column omits wrapping the action
const column1 = { ...columns[0], wrapAction: false as const };
const r = await render(<GridTable {...{ columns: [column1, columns[1]], rows: [row] }} />, withRouter());
// Then expect that only one column is wrapped in an anchor tag
expect(cell(r, 1, 0).tagName).toBe("DIV");
expect(cell(r, 1, 1).tagName).toBe("A");
expect(cell(r, 0, 0).tagName).toBe("DIV");
expect(cell(r, 0, 1).tagName).toBe("A");
});

it("can handle onClick for GridCellContent", async () => {
Expand Down Expand Up @@ -1993,7 +1997,7 @@ describe("GridTable", () => {

it("provides simpleDataRows", async () => {
// Given a row that uses SimpleHeaderAndData
type Row = SimpleHeaderAndData<{ value: number }>;
type Row = SimpleHeaderAndData<{ id: string; value: number }>;
// And also uses the simpleDataRows factory method
const rows: GridDataRow<Row>[] = simpleDataRows([
{ id: "a:1", value: 1 },
Expand All @@ -2011,7 +2015,7 @@ describe("GridTable", () => {

it("simpleDataRows can accept undefined", async () => {
// Given a row that uses SimpleHeaderAndData
type Row = SimpleHeaderAndData<{ value: number }>;
type Row = SimpleHeaderAndData<{ id: string; value: number }>;
// And we don't have any data defined yet
const rows: GridDataRow<Row>[] = simpleDataRows(undefined);
// Then we still get back the header row
Expand Down Expand Up @@ -2110,24 +2114,18 @@ describe("GridTable", () => {
});

it("reacts to setting activeRowId", async () => {
const activeRowIdRowStyles: RowStyles<Row> = {
data: {
onClick: (row, api) => {
api.setActiveRowId(`${row.kind}_${row.id}`);
},
},
const row: GridDataRow<Row> = {
kind: "data",
id: "1",
data: { name: "foo", value: 1 },
onClick: (row, api) => api.setActiveRowId(`${row.kind}_${row.id}`),
};

// Given a table initially rendered without an active row id
const r = await render(
<GridTable columns={columns} rows={rows} rowStyles={activeRowIdRowStyles} style={{ cellCss: Css.bgWhite.$ }} />,
);
const r = await render(<GridTable columns={columns} rows={[row]} style={{ cellCss: Css.bgWhite.$ }} />);
// And the first row/cell has the default background color
expect(cell(r, 1, 1)).toHaveStyleRule("background-color", Palette.White);

// When clicking the cell
click(cell(r, 1, 1));

// Then the first row/cell has the 'active' background color
expect(cell(r, 1, 1)).toHaveStyleRule("background-color", Palette.LightBlue50);
});
Expand Down
8 changes: 4 additions & 4 deletions src/components/Table/GridTableApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export type GridTableApi<R extends Kinded> = {
// Using `FooImpl`to keep the public GridTableApi definition separate.
export class GridTableApiImpl<R extends Kinded> implements GridTableApi<R> {
// This is public to GridTable but not exported outside of Beam
readonly tableState: TableState = new TableState();
readonly tableState: TableState<R> = new TableState();
virtuosoRef: MutableRefObject<VirtuosoHandle | null> = { current: null };

/** Called once by the GridTable when it takes ownership of this api instance. */
Expand All @@ -77,17 +77,17 @@ export class GridTableApiImpl<R extends Kinded> implements GridTableApi<R> {
this.virtuosoRef.current && this.virtuosoRef.current.scrollToIndex(index);
}

public getSelectedRowIds(kind?: string): string[] {
public getSelectedRowIds(kind?: R["kind"]): string[] {
return this.getSelectedRows(kind).map((row: any) => row.id);
}

// The any is not great, but getting the overload to handle the optional kind is annoying
public getSelectedRows(kind?: string): any {
public getSelectedRows(kind?: R["kind"]): any {
const ids = this.tableState.selectedIds;
const selected: GridDataRow<R>[] = [];
visit(this.tableState.rows, (row) => {
if (row.selectable !== false && ids.includes(row.id) && (!kind || row.kind === kind)) {
selected.push(row as any);
selected.push(row);
}
});
return selected;
Expand Down
10 changes: 2 additions & 8 deletions src/components/Table/TableStyles.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ReactNode } from "react";
import { PresentationContextProps, PresentationFieldProps } from "src/components/PresentationContext";
import { DiscriminateUnion, GridColumn, GridDataRow, GridTableApi, RenderCellFn } from "src/components/Table/index";
import { DiscriminateUnion, GridColumn, RenderCellFn } from "src/components/Table/index";
import { Kinded, RenderAs } from "src/components/Table/types";
import { Css, Palette, Properties } from "src/Css";
import { safeKeys } from "src/utils";
Expand All @@ -9,7 +9,7 @@ import { safeKeys } from "src/utils";
export interface GridStyle {
/** Applied to the base div element. */
rootCss?: Properties;
/** Applied with the owl operator between rows for rendering border lines. */
/** Applied with the owl operator between rows for rendering borderlines. */
betweenRowsCss?: Properties;
/** Applied on the last row of the table. */
lastRowCss?: Properties;
Expand Down Expand Up @@ -158,12 +158,6 @@ export interface RowStyle<R extends Kinded> {
cellCss?: Properties | ((row: R) => Properties);
/** Renders the cell element, i.e. a link to get whole-row links. */
renderCell?: RenderCellFn<R>;
/** Whether the row should be indented (via a style applied to the 1st cell). */
indent?: 1 | 2;
/** Whether the row should be a link. */
rowLink?: (row: R) => string;
/** Fired when the row is clicked, similar to rowLink but for actions that aren't 'go to this link'. */
onClick?: (row: GridDataRow<R>, api: GridTableApi<R>) => void;
}

/** Our original table look & feel/style. */
Expand Down
14 changes: 8 additions & 6 deletions src/components/Table/components/Row.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ function RowImpl<R extends Kinded, S>(props: RowProps<R>): ReactElement {
// For virtual tables use `display: flex` to keep all cells on the same row. For each cell in the row use `flexNone` to ensure they stay their defined widths
...(as === "table" ? {} : Css.relative.df.fg1.fs1.addIn("&>*", Css.flexNone.$).$),
// Apply `cursorPointer` to the row if it has a link or `onClick` value.
...((rowStyle?.rowLink || rowStyle?.onClick) && { "&:hover": Css.cursorPointer.$ }),
...(row.onClick && { "&:hover": Css.cursorPointer.$ }),
...maybeApplyFunction(row as any, rowStyle?.rowCss),
...{
[` > .${revealOnRowHoverClass} > *`]: Css.invisible.$,
Expand Down Expand Up @@ -228,7 +228,7 @@ function RowImpl<R extends Kinded, S>(props: RowProps<R>): ReactElement {
...getFirstOrLastCellCss(style, columnIndex, columns),
// Then override with per-cell/per-row justification/indentation
...justificationCss,
...getIndentationCss(style, rowStyle, columnIndex, maybeContent),
...getIndentationCss(style, columnIndex, maybeContent),
// Then apply any header-specific override
...(isHeader && style.headerCellCss),
// Then apply any totals-specific override
Expand Down Expand Up @@ -267,15 +267,15 @@ function RowImpl<R extends Kinded, S>(props: RowProps<R>): ReactElement {
const cellOnClick = applyCellHighlight ? () => api.setActiveCellId(cellId) : undefined;

const renderFn: RenderCellFn<any> =
(rowStyle?.renderCell || rowStyle?.rowLink) && wrapAction
(rowStyle?.renderCell || typeof row.onClick === "string") && wrapAction
? rowLinkRenderFn(as)
: isHeader || isTotals || isExpandableHeader
? headerRenderFn(column, as, currentColspan)
: rowStyle?.onClick && wrapAction
: typeof row.onClick === "function" && wrapAction
? rowClickRenderFn(as, api)
: defaultRenderFn(as);

return renderFn(columnIndex, cellCss, content, row, rowStyle, cellClassNames, cellOnClick);
return renderFn(columnIndex, cellCss, content, row, cellClassNames, cellOnClick);
})}
</RowTag>
);
Expand Down Expand Up @@ -319,7 +319,7 @@ export type GridDataRow<R extends Kinded> = {
kind: R["kind"];
/** Combined with the `kind` to determine a table wide React key. */
id: string;
/** A list of parent/grand-parent ids for collapsing parent/child rows. */
/** A list of parent/grandparent ids for collapsing parent/child rows. */
children?: GridDataRow<R>[];
/** * Whether to pin this sort to the first/last of its parent's children.
*
Expand All @@ -334,4 +334,6 @@ export type GridDataRow<R extends Kinded> = {
initSelected?: boolean;
/** Whether row can be selected */
selectable?: false;
/** Whether the entire row should be clickable. */
onClick?: string | ((row: GridDataRow<R>, api: GridTableApi<R>) => void);
} & IfAny<R, {}, DiscriminateUnion<R, "kind", R["kind"]>>;
22 changes: 10 additions & 12 deletions src/components/Table/components/cell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React, { ReactNode } from "react";
import { Link } from "react-router-dom";
import { navLink } from "src/components/CssReset";
import { GridTableApi } from "src/components/Table/GridTableApi";
import { RowStyle, tableRowStyles } from "src/components/Table/TableStyles";
import { tableRowStyles } from "src/components/Table/TableStyles";
import { GridCellAlignment, GridColumnWithId, Kinded, MaybeFn, RenderAs } from "src/components/Table/types";
import { Css, Properties, Typography } from "src/Css";

Expand All @@ -25,7 +25,7 @@ export type GridCellContent = {
/** Allows the cell to stay in place when the user scrolls horizontally, i.e. frozen columns. */
sticky?: "left" | "right";
/** If provided, content of the cell will be wrapped within a <button /> or <a /> tag depending on if the value is a function or a string. */
onClick?: () => {} | string;
onClick?: () => void | string;
/** Custom css to apply directly to this cell, i.e. cell-specific borders. */
css?: Properties;
/** Allows cell to reveal content when the user hovers over a row. Content must be wrapped in an element in order to be hidden. IE <div>{value}</div>*/
Expand All @@ -38,14 +38,13 @@ export type RenderCellFn<R extends Kinded> = (
css: Properties,
content: ReactNode,
row: R,
rowStyle: RowStyle<R> | undefined,
classNames: string | undefined,
onClick: (() => void) | undefined,
) => ReactNode;

/** Renders our default cell element, i.e. if no row links and no custom renderCell are used. */
export const defaultRenderFn: (as: RenderAs) => RenderCellFn<any> =
(as: RenderAs) => (key, css, content, row, rowStyle, classNames: string | undefined, onClick) => {
(as: RenderAs) => (key, css, content, row, classNames: string | undefined, onClick) => {
const Cell = as === "table" ? "td" : "div";
return (
<Cell key={key} css={{ ...css, ...tableRowStyles(as) }} className={classNames} onClick={onClick}>
Expand All @@ -59,7 +58,7 @@ export const defaultRenderFn: (as: RenderAs) => RenderCellFn<any> =
* Used for the Header, Totals, and Expanded Header row's cells.
* */
export const headerRenderFn: (column: GridColumnWithId<any>, as: RenderAs, colSpan: number) => RenderCellFn<any> =
(column, as, colSpan) => (key, css, content, row, rowStyle, classNames: string | undefined) => {
(column, as, colSpan) => (key, css, content, row, classNames: string | undefined) => {
const Cell = as === "table" ? "th" : "div";
return (
<Cell
Expand All @@ -75,8 +74,8 @@ export const headerRenderFn: (column: GridColumnWithId<any>, as: RenderAs, colSp

/** Renders a cell element when a row link is in play. */
export const rowLinkRenderFn: (as: RenderAs) => RenderCellFn<any> =
(as: RenderAs) => (key, css, content, row, rowStyle, classNames: string | undefined) => {
const to = rowStyle!.rowLink!(row);
(as: RenderAs) => (key, css, content, row, classNames: string | undefined) => {
const to = row.onClick as string;
if (as === "table") {
return (
<td key={key} css={{ ...css, ...tableRowStyles(as) }} className={classNames}>
Expand All @@ -98,18 +97,17 @@ export const rowLinkRenderFn: (as: RenderAs) => RenderCellFn<any> =
);
};

/** Renders a cell that will fire the RowStyle.onClick. */
/** Renders a cell that will fire the Row.onClick. */
export const rowClickRenderFn: (as: RenderAs, api: GridTableApi<any>) => RenderCellFn<any> =
(as: RenderAs, api: GridTableApi<any>) =>
(key, css, content, row, rowStyle, classNames: string | undefined, onClick) => {
(as: RenderAs, api: GridTableApi<any>) => (key, css, content, row, classNames: string | undefined, onClick) => {
const Row = as === "table" ? "tr" : "div";
return (
<Row
{...{ key }}
css={{ ...css, ...tableRowStyles(as) }}
className={classNames}
onClick={(e) => {
rowStyle!.onClick!(row, api);
onClick={() => {
row.onClick!(row, api);
onClick && onClick();
}}
>
Expand Down
Loading