diff --git a/src/components/Table.tsx b/src/components/Table.tsx index 8f3759d2..52101bec 100644 --- a/src/components/Table.tsx +++ b/src/components/Table.tsx @@ -43,6 +43,7 @@ export type TableProps = Omit< | 'data' | 'columns' | 'getRowCanExpand' + | 'getRowIsSelected' | 'renderExpanded' | 'loose' | 'stickyColumn' @@ -55,6 +56,7 @@ export type TableProps = Omit< data: any[] columns: any[] getRowCanExpand?: any + getRowIsSelected?: (row: Row) => boolean renderExpanded?: any loose?: boolean stickyColumn?: boolean @@ -137,10 +139,23 @@ const Tbody = styled(TbodyUnstyled)(({ theme }) => ({ backgroundColor: theme.colors['fill-one'], })) -const Tr = styled.tr<{ clickable?: boolean; lighter?: boolean }>( - ({ theme, clickable = false, lighter = false }) => ({ +const Tr = styled.tr<{ + $selected?: boolean + $selectable?: boolean + $clickable?: boolean + $lighter?: boolean +}>( + ({ + theme, + $clickable: clickable = false, + $lighter: lighter = false, + $selectable: selectable = false, + $selected: selected = false, + }) => ({ display: 'contents', - backgroundColor: lighter + backgroundColor: selected + ? theme.colors['fill-one-selected'] + : lighter || (selectable && !selected) ? theme.colors['fill-one'] : theme.colors['fill-one-hover'], @@ -148,16 +163,20 @@ const Tr = styled.tr<{ clickable?: boolean; lighter?: boolean }>( cursor: 'pointer', '&:hover': { - backgroundColor: theme.colors['fill-one-selected'], + backgroundColor: selectable + ? selected + ? theme.colors['fill-one-selected'] + : theme.colors['fill-one-hover'] + : theme.colors['fill-one-selected'], }, }), }) ) const Th = styled.th<{ - stickyColumn: boolean - cursor?: CSSProperties['cursor'] -}>(({ theme, stickyColumn, cursor }) => ({ + $stickyColumn: boolean + $cursor?: CSSProperties['cursor'] +}>(({ theme, $stickyColumn: stickyColumn, $cursor: cursor }) => ({ padding: 0, position: 'sticky', top: 0, @@ -211,19 +230,19 @@ const Th = styled.th<{ // TODO: Set vertical align to top for tall cells (~3 lines of text or more). See ENG-683. const Td = styled.td<{ - firstRow?: boolean - loose?: boolean - stickyColumn: boolean - truncateColumn: boolean - center?: boolean + $firstRow?: boolean + $loose?: boolean + $stickyColumn: boolean + $truncateColumn: boolean + $center?: boolean }>( ({ theme, - firstRow, - loose, - stickyColumn, - truncateColumn = false, - center, + $firstRow: firstRow, + $loose: loose, + $stickyColumn: stickyColumn, + $truncateColumn: truncateColumn = false, + $center: center, }) => ({ display: 'flex', flexDirection: 'column', @@ -323,21 +342,25 @@ function FillerRow({ height, index, stickyColumn, + selectable, ...props }: { columns: unknown[] height: number index: number stickyColumn: boolean + selectable?: boolean }) { return ( @@ -366,6 +389,7 @@ function FillerRows({ position: 'top' | 'bottom' stickyColumn: boolean clickable?: boolean + selectable?: boolean }) { return ( <> @@ -421,6 +445,7 @@ function TableRef( data, columns, getRowCanExpand, + getRowIsSelected, renderExpanded, loose = false, stickyColumn = false, @@ -565,10 +590,10 @@ function TableRef( {headerGroup.headers.map((header) => ( onRowClick?.(e, row)} - lighter={i % 2 === 0} - clickable={!!onRowClick} + $lighter={i % 2 === 0} + $selectable={!!getRowIsSelected} + $selected={getRowIsSelected?.(row) ?? false} + $clickable={!!onRowClick} // data-index is required for virtual scrolling to work data-index={row.index} {...(virtualizeRows @@ -631,11 +658,11 @@ function TableRef( {row.getVisibleCells().map((cell) => ( {flexRender( cell.column.columnDef.cell, @@ -645,7 +672,7 @@ function TableRef( ))} {row.getIsExpanded() && ( - + {renderExpanded({ row })} diff --git a/src/stories/Table.stories.tsx b/src/stories/Table.stories.tsx index 954a0a2a..da6903db 100644 --- a/src/stories/Table.stories.tsx +++ b/src/stories/Table.stories.tsx @@ -1,6 +1,11 @@ import { createColumnHelper } from '@tanstack/react-table' import { Div, Flex, Input, type InputProps, P } from 'honorable' -import React, { type ComponentProps, type ReactElement, useEffect } from 'react' +import React, { + type ComponentProps, + type ReactElement, + useEffect, + useState, +} from 'react' import type { Row } from '@tanstack/react-table' import { @@ -205,6 +210,20 @@ function Template(args: any) { return } +function SelectableTemplate(args: any) { + const [selectedId, setSelectedId] = useState('') + + return ( +
row.original.id === selectedId} + onRowClick={(_, row) => { + setSelectedId(row.original.id) + }} + /> + ) +} + // A debounced input react component function DebouncedInput({ initialValue, @@ -335,3 +354,12 @@ FilterableAndSortable.args = { data: extremeLengthData, columns, } + +export const Selectable = SelectableTemplate.bind({}) + +Selectable.args = { + width: '900px', + height: '400px', + data: repeatedData, + columns: expandingColumns, +}