Skip to content

Commit

Permalink
feat: editable cells on hover support
Browse files Browse the repository at this point in the history
  • Loading branch information
0ces committed Nov 18, 2024
1 parent 9815324 commit 4ce98a4
Show file tree
Hide file tree
Showing 8 changed files with 150 additions and 14 deletions.
87 changes: 86 additions & 1 deletion src/components/Table/GridTable.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,9 @@ import {
useGridTableApi,
} from "src/components/index";
import { Css, Palette } from "src/Css";
import { jan1, jan2, jan29 } from "src/forms/formStateDomain";
import { useComputed } from "src/hooks";
import { SelectField } from "src/inputs";
import { DateField, SelectField } from "src/inputs";
import { NumberField } from "src/inputs/NumberField";
import { noop } from "src/utils";
import { newStory, withRouter, zeroTo } from "src/utils/sb";
Expand Down Expand Up @@ -2144,3 +2145,87 @@ export function MinColumnWidths() {
</div>
);
}

enum EditableRowStatus {
Active = "Active",
Inactive = "Inactive",
}

type EditableRowData = {
kind: "data";
id: string;
data: { id: string; name: string; status: EditableRowStatus; value: number; date?: Date };
};
type EditableRow = EditableRowData | HeaderRow;

export function EditableRows() {
const [rows, setRows] = useState<GridDataRow<EditableRow>[]>([
simpleHeader,
{
kind: "data" as const,
id: "1",
data: { id: "1", name: "Tony Stark", status: EditableRowStatus.Active, value: 1, date: jan1 },
},
{
kind: "data" as const,
id: "2",
data: { id: "2", name: "Natasha Romanova", status: EditableRowStatus.Active, value: 2, date: jan2 },
},
{
kind: "data" as const,
id: "3",
data: { id: "3", name: "Thor Odinson", status: EditableRowStatus.Active, value: 3, date: jan29 },
},
]);

const nameColumn: GridColumn<EditableRow> = {
header: "Name",
data: ({ name }) => name,
};

const selectColumn: GridColumn<EditableRow> = {
header: "Status",
data: (row) => ({
content: (
<SelectField
label=""
options={Object.values(EditableRowStatus).map((status) => ({ label: status, code: status }))}
value={row.status}
onSelect={noop}
/>
),
editableOnHover: true,
}),
w: "100px",
};

const date1Column: GridColumn<EditableRow> = {
header: "Date",
data: (row, { editable }) => ({
content: (
<DateField label="" value={row.date} onChange={noop} readOnly={!editable} hideCalendarIcon format="medium" />
),
editableOnHover: true,
}),
w: "120px",
};

const date2Column: GridColumn<EditableRow> = {
header: "Date",
data: (row, { editable }) => ({
content: (
<DateField label="" value={row.date} onChange={noop} readOnly={!editable} hideCalendarIcon format="medium" />
),
editableOnHover: true,
}),
w: "120px",
};

return (
<GridTable
columns={[nameColumn, selectColumn, date1Column, date2Column]}
rows={rows}
style={{ bordered: true, allWhite: true, rowHoverColor: Palette.Blue50 }}
/>
);
}
2 changes: 1 addition & 1 deletion src/components/Table/GridTableApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ export class GridTableApiImpl<R extends Kinded> implements GridTableApi<R> {
.filter((c) => !c.isAction)
.map((c) => {
// Just guessing for level=1
const maybeContent = applyRowFn(c, rs.row, this as any as GridRowApi<R>, 1, true, undefined);
const maybeContent = applyRowFn(c, rs.row, this as any as GridRowApi<R>, 1, true, false, undefined);
if (isGridCellContent(maybeContent)) {
const cell = maybeContent;
const content = maybeApply(cell.content);
Expand Down
2 changes: 2 additions & 0 deletions src/components/Table/TableStyles.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ export interface GridStyle {
firstRowMessageCss?: Properties;
/** Applied on hover if a row has a rowLink/onClick set. */
rowHoverColor?: Palette | "none";
/** Applied on hover to a cell TextFieldBase */
rowEditableCellBorderColor?: Palette;
/** Applied on hover of a row */
nonHeaderRowHoverCss?: Properties;
/** Default content to put into an empty cell */
Expand Down
47 changes: 39 additions & 8 deletions src/components/Table/components/Row.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ function RowImpl<R extends Kinded, S>(props: RowProps<R>): ReactElement {
const sortOn = tableState.sortConfig?.on;

const revealOnRowHoverClass = "revealOnRowHover";
const editableOnRowHoverClass = "editableOnRowHover";

const showRowHoverColor = !reservedRowKinds.includes(row.kind) && !omitRowHover && style.rowHoverColor !== "none";

Expand Down Expand Up @@ -123,6 +124,11 @@ function RowImpl<R extends Kinded, S>(props: RowProps<R>): ReactElement {
[` > .${revealOnRowHoverClass} > *`]: Css.vh.$,
[`:hover > .${revealOnRowHoverClass} > *`]: Css.vv.$,
},
...{
[`:hover > .${editableOnRowHoverClass} .textFieldBaseWrapper`]: Css.px1.br4.ba.bc(
style.rowEditableCellBorderColor ?? Palette.Blue300,
).$,
},
...(isLastKeptRow && Css.addIn("&>*", style.keptLastRowCss).$),
};

Expand Down Expand Up @@ -210,7 +216,19 @@ function RowImpl<R extends Kinded, S>(props: RowProps<R>): ReactElement {
onDragOver: onDragOverDebounced,
};

const maybeContent = applyRowFn(column as GridColumnWithId<R>, row, rowApi, level, isExpanded, dragData);
const cellId = `${row.kind}_${row.id}_${column.id}`;
const applyCellHighlight = cellHighlight && !!column.id && !isHeader && !isTotals;
const isCellActive = tableState.activeCellId === cellId;

const maybeContent = applyRowFn(
column as GridColumnWithId<R>,
row,
rowApi,
level,
isExpanded,
isCellActive,
dragData,
);

// Only use the `numExpandedColumns` as the `colspan` when rendering the "Expandable Header"
currentColspan =
Expand All @@ -220,6 +238,7 @@ function RowImpl<R extends Kinded, S>(props: RowProps<R>): ReactElement {
? numExpandedColumns + 1
: 1;
const revealOnRowHover = isGridCellContent(maybeContent) ? maybeContent.revealOnRowHover : false;
const editableOnRowHover = isGridCellContent(maybeContent) ? maybeContent.editableOnHover : false;

const canSortColumn =
(sortOn === "client" && column.clientSideSort !== false) ||
Expand Down Expand Up @@ -274,11 +293,6 @@ function RowImpl<R extends Kinded, S>(props: RowProps<R>): ReactElement {

// This relies on our column sizes being defined in pixel values, which is currently true as we calculate to pixel values in the `useSetupColumnSizes` hook
minStickyLeftOffset += maybeSticky === "left" ? parseInt(columnSizes[columnIndex].replace("px", ""), 10) : 0;

const cellId = `${row.kind}_${row.id}_${column.id}`;
const applyCellHighlight = cellHighlight && !!column.id && !isHeader && !isTotals;
const isCellActive = tableState.activeCellId === cellId;

// Note that it seems expensive to calc a per-cell class name/CSS-in-JS output,
// vs. setting global/table-wide CSS like `style.cellCss` on the root grid div with
// a few descendent selectors. However, that approach means the root grid-applied
Expand Down Expand Up @@ -334,8 +348,15 @@ function RowImpl<R extends Kinded, S>(props: RowProps<R>): ReactElement {
})`,
};

const cellClassNames = revealOnRowHover ? revealOnRowHoverClass : undefined;
const cellClassNames = [
...(revealOnRowHover ? [revealOnRowHoverClass] : []),
...(editableOnRowHover && (isCellActive || !tableState.activeCellId) ? [editableOnRowHoverClass] : []),
].join(" ");

const cellOnHover =
isGridCellContent(maybeContent) && maybeContent.editableOnHover
? (enter: boolean) => (enter ? api.setActiveCellId(cellId) : api.setActiveCellId(undefined))
: undefined;
const cellOnClick = applyCellHighlight ? () => api.setActiveCellId(cellId) : undefined;
const tooltip = isGridCellContent(maybeContent) ? maybeContent.tooltip : undefined;

Expand All @@ -348,7 +369,17 @@ function RowImpl<R extends Kinded, S>(props: RowProps<R>): ReactElement {
? rowClickRenderFn(as, api, currentColspan)
: defaultRenderFn(as, currentColspan);

return renderFn(columnIndex, cellCss, content, row, rowStyle, cellClassNames, cellOnClick, tooltip);
return renderFn(
columnIndex,
cellCss,
content,
row,
rowStyle,
cellClassNames,
cellOnClick,
cellOnHover,
tooltip,
);
})
)}
</RowTag>
Expand Down
8 changes: 7 additions & 1 deletion src/components/Table/components/cell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ export type GridCellContent = {
revealOnRowHover?: true;
/** Tooltip to add to a cell */
tooltip?: ReactNode;
/** Allows cell to be editable when hovering, and also highlights the field on hover */
editableOnHover?: true;
};

/** Allows rendering a specific cell. */
Expand All @@ -45,19 +47,23 @@ export type RenderCellFn<R extends Kinded> = (
rowStyle: RowStyle<R> | undefined,
classNames: string | undefined,
onClick: VoidFunction | undefined,
onHover: ((enter: boolean) => void) | undefined,
tooltip: ReactNode | undefined,
) => ReactNode;

/** Renders our default cell element, i.e. if no row links and no custom renderCell are used. */
export const defaultRenderFn: (as: RenderAs, colSpan: number) => RenderCellFn<any> =
(as: RenderAs, colSpan) => (key, css, content, row, rowStyle, classNames: string | undefined, onClick, tooltip) => {
(as: RenderAs, colSpan) =>
(key, css, content, row, rowStyle, classNames: string | undefined, onClick, onHover, tooltip) => {
const Cell = as === "table" ? "td" : "div";
return (
<Cell
key={key}
css={{ ...css, ...Css.cursor("default").$ }}
className={classNames}
onClick={onClick}
onMouseEnter={() => onHover?.(true)}
onMouseLeave={() => onHover?.(false)}
{...(as === "table" && { colSpan })}
>
{content}
Expand Down
6 changes: 4 additions & 2 deletions src/components/Table/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,11 @@ export type GridColumn<R extends Kinded> = {
| (DiscriminateUnion<R, "kind", K> extends { data: infer D }
? (
data: D,
opts: { row: GridRowKind<R, K>; api: GridRowApi<R>; level: number; expanded: boolean },
opts: { row: GridRowKind<R, K>; api: GridRowApi<R>; level: number; expanded: boolean; editable: boolean },
) => ReactNode | GridCellContent
: (
data: undefined,
opts: { row: GridRowKind<R, K>; api: GridRowApi<R>; level: number; expanded: boolean },
opts: { row: GridRowKind<R, K>; api: GridRowApi<R>; level: number; expanded: boolean; editable: boolean },
) => ReactNode | GridCellContent);
} & {
/**
Expand Down Expand Up @@ -85,6 +85,8 @@ export type GridColumn<R extends Kinded> = {
initExpanded?: boolean;
/** Determines whether this column should be hidden when expanded (only the 'expandColumns' would show) */
hideOnExpand?: boolean;
/** Flag that changes the field behavior to be editable on hover */
editableOnHover?: boolean;
};

/**
Expand Down
10 changes: 9 additions & 1 deletion src/components/Table/utils/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -146,13 +146,21 @@ export function applyRowFn<R extends Kinded>(
api: GridRowApi<R>,
level: number,
expanded: boolean,
editable: boolean,
dragData?: DragData<R>,
): ReactNode | GridCellContent {
// Usually this is a function to apply against the row, but sometimes it's a hard-coded value, i.e. for headers
const maybeContent = column[row.kind];
if (typeof maybeContent === "function") {
// Auto-destructure data
return (maybeContent as Function)((row as any)["data"], { row: row as any, api, level, expanded, dragData });
return (maybeContent as Function)((row as any)["data"], {
row: row as any,
api,
level,
expanded,
editable,
dragData,
});
} else {
return maybeContent;
}
Expand Down
2 changes: 2 additions & 0 deletions src/inputs/TextFieldBase.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,7 @@ export function TextFieldBase<X extends Only<TextFieldXss, X>>(props: TextFieldB
...(multiline ? Css.fdc.aifs.gap2.$ : Css.if(wrap === false).truncate.$),
...xss,
}}
className="textFieldBaseWrapper"
data-readonly="true"
{...tid}
>
Expand Down Expand Up @@ -259,6 +260,7 @@ export function TextFieldBase<X extends Only<TextFieldXss, X>>(props: TextFieldB
...(errorMsg && !inputProps.disabled ? fieldStyles.error : {}),
...Css.if(multiline).aifs.oh.mhPx(textAreaMinHeight).$,
}}
className="textFieldBaseWrapper"
{...hoverProps}
ref={inputWrapRef as any}
onClick={unfocusedPlaceholder ? handleUnfocusedPlaceholderClick : undefined}
Expand Down

0 comments on commit 4ce98a4

Please sign in to comment.