diff --git a/src/components/Grid/Cell.tsx b/src/components/Grid/Cell.tsx index a107e287..bcf09236 100644 --- a/src/components/Grid/Cell.tsx +++ b/src/components/Grid/Cell.tsx @@ -1,20 +1,14 @@ -import { memo } from "react"; +import { memo, useEffect, useRef } from "react"; import { GridChildComponentProps, areEqual } from "react-window"; import { ItemDataType } from "./types"; import { StyledCell } from "./StyledCell"; type CellProps = GridChildComponentProps & { width: number; -} +}; export const Cell = memo( - ({ - data, - rowIndex, - columnIndex, - style, - ...props - }: CellProps) => { + ({ data, rowIndex, columnIndex, style, ...props }: CellProps) => { const { cell: CellData, getSelectionType, @@ -25,6 +19,8 @@ export const Cell = memo( showHeader, rowHeight, rowStart, + rowAutoHeight, + updateRowHeight, } = data; const currentRowIndex = rowIndex + rowStart; @@ -60,11 +56,29 @@ export const Cell = memo( const selectionBorderLeft = rightOfSelectionBorder || rightOfFocus || isFocused; const selectionBorderTop = belowSelectionBorder || belowFocus || isFocused; + const cellRef = useRef(null); + + useEffect(() => { + if (!rowAutoHeight) { + return; + } + if (cellRef.current) { + const height = cellRef.current.getBoundingClientRect().height; + updateRowHeight(rowIndex, height); + } + }, [updateRowHeight, rowIndex, rowAutoHeight]); + + const styleWithHeight = { + ...style, + height: "auto", + }; + return (
diff --git a/src/components/Grid/Grid.stories.tsx b/src/components/Grid/Grid.stories.tsx index 9cbd5767..af78ee61 100644 --- a/src/components/Grid/Grid.stories.tsx +++ b/src/components/Grid/Grid.stories.tsx @@ -2,7 +2,14 @@ import { useCallback, useEffect, useState } from "react"; import { CellProps, GridContextMenuItemProps, SelectedRegion, SelectionFocus } from ".."; import { Grid as CUIGrid } from "./Grid"; -const Cell: CellProps = ({ type, rowIndex, columnIndex, isScrolling, width, ...props }) => { +const Cell: CellProps = ({ + type, + rowIndex, + columnIndex, + isScrolling, + width, + ...props +}) => { return (
{ const [focus, setFocus] = useState(focusProp); @@ -71,6 +79,7 @@ const Grid = ({ columnCount, rowCount, focus: focusProp, ...props }: Props) => { }); }} getMenuOptions={getMenuOptions} + rowAutoHeight={props.rowAutoHeight} {...props} />
diff --git a/src/components/Grid/Grid.test.tsx b/src/components/Grid/Grid.test.tsx index d5e34f4c..6df8ab49 100644 --- a/src/components/Grid/Grid.test.tsx +++ b/src/components/Grid/Grid.test.tsx @@ -1,16 +1,32 @@ -import {CellProps, Grid, GridProps} from "@/components"; -import {renderCUI} from "@/utils/test-utils"; -import {SelectionFocus} from "./types"; -import {ReactNode} from "react"; +import { CellProps, Grid, GridProps } from "@/components"; +import { renderCUI } from "@/utils/test-utils"; +import { SelectionFocus } from "./types"; +import { ReactNode } from "react"; const Cell: CellProps = ({ type, rowIndex, columnIndex, isScrolling, ...props }) => { + let content = `${rowIndex} ${columnIndex} - ${type}`; + + if (rowIndex === 0 && columnIndex === 0) { + content = `CREATE TABLE random_user_events ( + user_id UInt32, + event_time DateTime, + event_type Enum8('click' = 1, 'view' = 2, 'purchase' = 3), + item_id String, + price Decimal(10,2), + quantity UInt16 + ) ENGINE = MergeTree() + ORDER BY (user_id, event_time) + PARTITION BY toYYYYMM(event_time) + SETTINGS index_granularity = 8192;`; + } + return (
- {rowIndex} {columnIndex} - {type} + {content}
); }; @@ -31,6 +47,7 @@ interface Props focus?: SelectionFocus; onFocusChange?: (rowIndex: number, columnIndex: number) => void; onColumnResize?: () => void; + rowAutoHeight?: boolean; } type AutoSizerModule = typeof import("react-virtualized-auto-sizer"); @@ -60,6 +77,7 @@ describe("Grid", () => { onColumnResize, focus, onFocusChange, + rowAutoHeight, ...props }: Props) => renderCUI( @@ -72,6 +90,7 @@ describe("Grid", () => { onColumnResize={onColumnResize ?? onColumnResizeTestFn} onFocusChange={onFocusChange ?? onFocusChangeTestFn} getMenuOptions={getMenuOptions} + rowAutoHeight={rowAutoHeight} {...props} /> ); @@ -99,4 +118,31 @@ describe("Grid", () => { cell && expect(cell.dataset.selected).toEqual("true"); cell && expect(cell.dataset.focused).toEqual("true"); }); + + it("should set row height to default (33px) when rowAutoHeight is false", async () => { + const { getByTestId } = renderGrid({ + rowCount: 10, + columnCount: 10, + rowAutoHeight: false, + }); + + const cell = getByTestId("row-cell-0-0"); + + const computedHeight = window.getComputedStyle(cell).height; + const heightValue = parseFloat(computedHeight); + expect(heightValue).toBe(33); + }); + + it("should expand row height to 100% when rowAutoHeight is true", async () => { + const { getByTestId } = renderGrid({ + rowCount: 1, + columnCount: 1, + rowAutoHeight: true, + }); + + const cell = getByTestId("row-cell-0-0"); + + const computedHeight = window.getComputedStyle(cell).height; + expect(computedHeight).toBe("100%"); + }); }); diff --git a/src/components/Grid/Grid.tsx b/src/components/Grid/Grid.tsx index e1fe99b6..2191b6e7 100644 --- a/src/components/Grid/Grid.tsx +++ b/src/components/Grid/Grid.tsx @@ -149,6 +149,7 @@ export const Grid = forwardRef( onContextMenu: onContextMenuProp, forwardedGridRef, onItemsRendered: onItemsRenderedProp, + rowAutoHeight, ...props }, forwardedRef @@ -212,6 +213,34 @@ export const Grid = forwardRef( onCopyCallback, ]); + const rowHeightsRef = useRef(new Map()); + + const getRowHeight = useCallback( + (index: number) => { + if (rowAutoHeight && rowHeightsRef.current.get(index)) { + return rowHeightsRef.current.get(index) + rowHeight; + } + return rowHeight; + }, + [rowHeight, rowAutoHeight] + ); + + const updateRowHeight = useCallback( + (rowIndex: number, height: number) => { + if (!rowAutoHeight) { + return; + } + const prevHeight = rowHeightsRef.current.get(rowIndex) ?? 0; + if (height > prevHeight) { + rowHeightsRef.current.set(rowIndex, height); + if (gridRef.current) { + gridRef.current.resetAfterRowIndex(rowIndex); + } + } + }, + [rowAutoHeight] + ); + const customOnCopy: () => Promise = useMemo(() => { const result = async () => { if (onCopyProp) { @@ -405,6 +434,9 @@ export const Grid = forwardRef( headerHeight, rowNumberWidth, rowStart, + rowAutoHeight, + updateRowHeight, + getRowHeight, }; const InnerElementType = forwardRef( @@ -435,6 +467,7 @@ export const Grid = forwardRef( showHeader={showHeader} rowStart={rowStart} showBorder={showBorder} + rowAutoHeight={rowAutoHeight} /> )} @@ -755,7 +788,6 @@ export const Grid = forwardRef( const onItemsRendered = useCallback( (props: GridOnItemsRenderedProps) => { lastItemsRenderedProps.current = props; - return onItemsRenderedProp?.({ ...props, visibleRowStartIndex: props.visibleRowStartIndex + rowStart, @@ -786,6 +818,13 @@ export const Grid = forwardRef( ); }; + // Handles the case when rowCount/columnCount changes, rerenders styles + useEffect(() => { + if (gridRef.current) { + gridRef.current.resetAfterRowIndex(0); + } + }, [rowCount, columnCount]); + return ( ( height={height} width={width} columnCount={columnCount} - rowHeight={() => rowHeight} + rowHeight={getRowHeight} useIsScrolling={useIsScrolling} innerElementType={InnerElementType} itemData={data} diff --git a/src/components/Grid/Header.tsx b/src/components/Grid/Header.tsx index b5d1265c..32b5a9de 100644 --- a/src/components/Grid/Header.tsx +++ b/src/components/Grid/Header.tsx @@ -99,6 +99,7 @@ const RowColumnContainer = styled(HeaderCellContainer)<{ const RowColumn = styled(StyledCell)` width: 100%; text-align: right; + overflow: hidden; `; const Column = ({ @@ -130,7 +131,7 @@ const Column = ({ (leftSelectionType === "selectDirect" || isSelected) && leftSelectionType !== selectionType; - const columnWidth = getColumnWidth(columnIndex) + const columnWidth = getColumnWidth(columnIndex); return ( { const selectedAllType = getSelectionType({ type: "all", diff --git a/src/components/Grid/RowNumberColumn.tsx b/src/components/Grid/RowNumberColumn.tsx index feed1bc9..8c39cc4c 100644 --- a/src/components/Grid/RowNumberColumn.tsx +++ b/src/components/Grid/RowNumberColumn.tsx @@ -5,13 +5,14 @@ const RowNumberColumnContainer = styled.div<{ $height: number; $width: number; $scrolledHorizontal: boolean; + $rowAutoHeight?: boolean; }>` position: sticky; left: 0; ${({ $height, $width }) => ` top: ${$height}px; width: ${$width}px; - height: 100%; + height: 100% `} ${({ $scrolledHorizontal, theme }) => @@ -23,6 +24,7 @@ const RowNumberColumnContainer = styled.div<{ const RowNumberCell = styled.div<{ $height: number; $rowNumber: number; + $rowAutoHeight?: boolean; }>` position: absolute; left: 0; @@ -30,9 +32,9 @@ const RowNumberCell = styled.div<{ text-overflow: ellipsis; white-space: nowrap; width: 100%; - ${({ $height, $rowNumber }) => ` + ${({ $height, $rowNumber, $rowAutoHeight }) => ` top: ${$height * $rowNumber}px; - height: ${$height}px; + height: ${$rowAutoHeight ? "100%" : `${$height}px`}; `} `; interface RowNumberColumnProps { @@ -47,6 +49,7 @@ interface RowNumberColumnProps { scrolledHorizontal: boolean; rowStart: number; showBorder: boolean; + rowAutoHeight?: boolean; } interface RowNumberProps extends Pick< @@ -56,6 +59,7 @@ interface RowNumberProps rowIndex: number; isLastRow: boolean; isFirstRow: boolean; + rowAutoHeight?: boolean; } const RowNumber = ({ rowIndex, @@ -65,6 +69,7 @@ const RowNumber = ({ isFirstRow, showBorder, rowStart, + rowAutoHeight, }: RowNumberProps) => { const currentRowIndex = rowIndex + rowStart; const selectionType = getSelectionType({ @@ -84,6 +89,7 @@ const RowNumber = ({ { return ( {Array.from({ length: maxRow - minRow + 1 }, (_, index) => minRow + index).map( rowIndex => ( @@ -139,6 +148,7 @@ const RowNumberColumn = ({ isFirstRow={!showHeader && rowIndex === 0} showBorder={showBorder} rowStart={rowStart} + rowAutoHeight={rowAutoHeight} /> ) )} diff --git a/src/components/Grid/StyledCell.tsx b/src/components/Grid/StyledCell.tsx index 1b6bb906..975cfdee 100644 --- a/src/components/Grid/StyledCell.tsx +++ b/src/components/Grid/StyledCell.tsx @@ -13,6 +13,7 @@ export const StyledCell = styled.div<{ $height: number; $type?: "body" | "header"; $showBorder: boolean; + $rowAutoHeight?: boolean; }>` display: block; text-align: left; @@ -34,8 +35,11 @@ export const StyledCell = styled.div<{ $height, $type = "body", $showBorder, + $rowAutoHeight, }) => ` - height: ${$height}px; + height: ${$rowAutoHeight ? "100%" : `${$height}px`}; + min-height: ${$rowAutoHeight ? "auto" : ""}; + overflow-y: ${$rowAutoHeight ? "auto" : ""}; background: ${theme.click.grid[$type].cell.color.background[$selectionType]}; color: ${ $type === "header" @@ -86,6 +90,7 @@ export const StyledCell = styled.div<{ ` : "border-right: none;" } + ${$rowAutoHeight && "border: none;"} `} ${({ theme, @@ -95,10 +100,12 @@ export const StyledCell = styled.div<{ $type = "body", $isSelectedTop, $isSelectedLeft, + $rowAutoHeight, }) => $isSelectedTop || $isSelectedLeft || - ($selectionType === "selectDirect" && ($isLastRow || $isLastColumn)) + ($selectionType === "selectDirect" && ($isLastRow || $isLastColumn)) || + $rowAutoHeight ? ` &::before { content: ""; @@ -127,6 +134,7 @@ export const StyledCell = styled.div<{ ? `border-right: 1px solid ${theme.click.grid[$type].cell.color.stroke.selectDirect};` : "" } + ${$rowAutoHeight && "border: none;"} } ` : ""}; diff --git a/src/components/Grid/types.ts b/src/components/Grid/types.ts index 722f5523..35d6b228 100644 --- a/src/components/Grid/types.ts +++ b/src/components/Grid/types.ts @@ -157,6 +157,9 @@ export interface ItemDataType { headerHeight: number; rowNumberWidth: number; rowStart: number; + rowAutoHeight?: boolean; + updateRowHeight: (rowIndex: number, height: number) => void; + getRowHeight: (index: number) => number; } export interface GridContextMenuItemProps extends Omit { @@ -204,6 +207,7 @@ export interface GridProps onCopyCallback?: (copied: boolean) => void; onContextMenu?: MouseEventHandler; forwardedGridRef?: MutableRefObject; + rowAutoHeight?: boolean; } export type ResizerPosition = {