diff --git a/packages/blade/src/components/Table/Table.web.tsx b/packages/blade/src/components/Table/Table.web.tsx index eb3aefa5449..0c5d6e4e723 100644 --- a/packages/blade/src/components/Table/Table.web.tsx +++ b/packages/blade/src/components/Table/Table.web.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useEffect, useMemo } from 'react'; +import React, { forwardRef, useCallback, useEffect, useMemo } from 'react'; import { Table as ReactTable } from '@table-library/react-table-library/table'; import { useTheme as useTableTheme } from '@table-library/react-table-library/theme'; import type { MiddlewareFunction } from '@table-library/react-table-library/types/common'; @@ -20,6 +20,7 @@ import { refreshWrapperZIndex, tableBackgroundColor, tablePagination, + tableRow, } from './tokens'; import type { TableProps, @@ -43,6 +44,8 @@ import getIn from '~utils/lodashButBetter/get'; import { makeAccessible } from '~utils/makeAccessible'; import { useIsMobile } from '~utils/useIsMobile'; import { makeAnalyticsAttribute } from '~utils/makeAnalyticsAttribute'; +import { getTableActionsHoverStyles, getTableRowBackgroundTransition } from './utils'; +import { Virtualized } from '@table-library/react-table-library/virtualized'; const rowSelectType: Record< NonNullable['selectionType']>, @@ -120,6 +123,8 @@ const StyledReactTable = styled(ReactTable)<{ width: $styledProps?.width, // auto is isVirtualized ? 'scroll' : 'auto', }); + const $isSelectable = true; + const $showStripedRows = true; return { '&&&': { @@ -138,6 +143,144 @@ const StyledReactTable = styled(ReactTable)<{ '&': { overflow: 'hidden !important', }, + '.tbody tr:last-child .cell-wrapper': { + borderBottom: 'none', + }, + + '.tbody .row-select-single-selected .cell-wrapper-base, .row-select-selected .cell-wrapper-base': { + backgroundColor: getIn(theme.colors, tableRow.nonStripe.backgroundColorSelected), + }, + '.tbody .row-select-single-selected:hover:not(.disabled-row) .cell-wrapper-base, .row-select-selected:hover:not(.disabled-row) .cell-wrapper-base': { + backgroundColor: getIn(theme.colors, tableRow.nonStripe.backgroundColorSelectedHover), + ...getTableActionsHoverStyles({ + hoverColor: tableRow.nonStripe.backgroundColorSelectedHover, + backgroundGradientColor: tableRow.nonStripeWrapper.backgroundColorSelectedHover, + theme, + }), + }, + '.tbody .row-select-single-selected:focus:not(.disabled-row) .cell-wrapper-base, .row-select-selected:focus:not(.disabled-row) .cell-wrapper-base': { + backgroundColor: getIn(theme.colors, tableRow.nonStripe.backgroundColorSelectedFocus), + ...getTableActionsHoverStyles({ + hoverColor: tableRow.nonStripe.backgroundColorSelectedFocus, + backgroundGradientColor: tableRow.nonStripeWrapper.backgroundColorSelectedFocus, + theme, + }), + }, + '.tbody .row-select-single-selected:active:not(.disabled-row) .cell-wrapper-base, .row-select-selected:active:not(.disabled-row) .cell-wrapper-base': { + backgroundColor: getIn(theme.colors, tableRow.nonStripe.backgroundColorSelectedActive), + ...getTableActionsHoverStyles({ + hoverColor: tableRow.nonStripe.backgroundColorSelectedActive, + backgroundGradientColor: tableRow.nonStripe.backgroundColorHover, + theme, + }), + }, + + ...($isSelectable && { + '.tbody tr:active:not(.disabled-row) .cell-wrapper': { + backgroundColor: getIn(theme.colors, tableRow.nonStripeWrapper.backgroundColorActive), + }, + }), + + ...($showStripedRows && { + '.tbody tr:nth-child(even) .cell-wrapper': { + backgroundColor: getIn(theme.colors, tableRow.stripeWrapper.backgroundColor), + }, + '.tbody tr:nth-child(even) .cell-wrapper-base': { + backgroundColor: tableRow.stripe.backgroundColor, + }, + }), + + ...($showStripedRows && + $isSelectable && { + '.tbody tr:nth-child(even):hover:not(.disabled-row) .cell-wrapper': { + backgroundColor: getIn(theme.colors, tableRow.stripeWrapper.backgroundColorHover), + }, + '.tbody tr:nth-child(even):focus:not(.disabled-row) .cell-wrapper': { + backgroundColor: getIn(theme.colors, tableRow.stripeWrapper.backgroundColorFocus), + }, + '.tbody tr:nth-child(even):active:not(.disabled-row) .cell-wrapper': { + backgroundColor: getIn(theme.colors, tableRow.stripeWrapper.backgroundColorActive), + }, + '.tbody .row-select-single-selected:nth-child(even) .cell-wrapper, .row-select-selected:nth-child(even) .cell-wrapper': { + backgroundColor: getIn(theme.colors, tableRow.stripeWrapper.backgroundColorSelected), + }, + '.tbody .row-select-single-selected:nth-child(even):hover:not(.disabled-row) .cell-wrapper, .row-select-selected:nth-child(even):hover:not(.disabled-row) .cell-wrapper': { + backgroundColor: getIn( + theme.colors, + tableRow.stripeWrapper.backgroundColorSelectedHover, + ), + }, + '.tbody .row-select-single-selected:nth-child(even):focus:not(.disabled-row) .cell-wrapper, .row-select-selected:nth-child(even):focus:not(.disabled-row) .cell-wrapper': { + backgroundColor: getIn( + theme.colors, + tableRow.stripeWrapper.backgroundColorSelectedFocus, + ), + }, + '.tbody .row-select-single-selected:nth-child(even):active:not(.disabled-row) .cell-wrapper, .row-select-selected:nth-child(even):active:not(.disabled-row) .cell-wrapper': { + backgroundColor: getIn( + theme.colors, + tableRow.stripeWrapper.backgroundColorSelectedActive, + ), + }, + + '.tbody tr:nth-child(even):hover:not(.disabled-row) .cell-wrapper-base': { + backgroundColor: getIn(theme.colors, tableRow.stripe.backgroundColorHover), + ...getTableActionsHoverStyles({ + hoverColor: tableRow.stripe.backgroundColorHover, + theme, + backgroundGradientColor: tableRow.stripeWrapper.backgroundColorHover, + }), + }, + '.tbody tr:nth-child(even):focus:not(.disabled-row) .cell-wrapper-base': { + backgroundColor: getIn(theme.colors, tableRow.stripe.backgroundColorFocus), + ...getTableActionsHoverStyles({ + hoverColor: tableRow.stripe.backgroundColorFocus, + theme, + backgroundGradientColor: tableRow.stripeWrapper.backgroundColorFocus, + }), + }, + '.tbody tr:nth-child(even):active:not(.disabled-row) .cell-wrapper-base': { + backgroundColor: getIn(theme.colors, tableRow.stripe.backgroundColorActive), + ...getTableActionsHoverStyles({ + hoverColor: tableRow.stripe.backgroundColorActive, + backgroundGradientColor: tableRow.stripe.backgroundColorHover, + theme, + }), + }, + + '.tbody .row-select-single-selected:nth-child(even) .cell-wrapper-base, .row-select-selected:nth-child(even) .cell-wrapper-base ': { + backgroundColor: getIn(theme.colors, tableRow.stripe.backgroundColorSelected), + ...getTableActionsHoverStyles({ + hoverColor: tableRow.stripe.backgroundColorSelected, + theme, + backgroundGradientColor: tableRow.stripeWrapper.backgroundColorSelected, + }), + }, + '.tbody .row-select-single-selected:nth-child(even):hover:not(.disabled-row) .cell-wrapper-base, .row-select-selected:nth-child(even):hover:not(.disabled-row) .cell-wrapper-base ': { + backgroundColor: getIn(theme.colors, tableRow.stripe.backgroundColorSelectedHover), + ...getTableActionsHoverStyles({ + hoverColor: tableRow.stripe.backgroundColorSelectedHover, + theme, + backgroundGradientColor: tableRow.stripeWrapper.backgroundColorSelectedHover, + }), + }, + '.tbody .row-select-single-selected:nth-child(even):focus:not(.disabled-row) .cell-wrapper-base, .row-select-selected:nth-child(even):focus:not(.disabled-row) .cell-wrapper-base ': { + backgroundColor: getIn(theme.colors, tableRow.stripe.backgroundColorSelectedFocus), + ...getTableActionsHoverStyles({ + hoverColor: tableRow.stripe.backgroundColorSelectedFocus, + theme, + backgroundGradientColor: tableRow.stripeWrapper.backgroundColorSelectedFocus, + }), + }, + '.tbody .row-select-single-selected:nth-child(even):active:not(.disabled-row) .cell-wrapper-base, .row-select-selected:nth-child(even):active:not(.disabled-row) .cell-wrapper-base ': { + backgroundColor: getIn(theme.colors, tableRow.stripe.backgroundColorSelectedActive), + ...getTableActionsHoverStyles({ + hoverColor: tableRow.stripe.backgroundColorSelectedActive, + theme, + backgroundGradientColor: tableRow.stripe.backgroundColorHover, + }), + }, + }), }), }; }); @@ -159,64 +302,73 @@ const RefreshWrapper = styled(BaseBox)<{ }; }); -const _Table = ({ - children, - data, - multiSelectTrigger = 'row', - selectionType = 'none', - onSelectionChange, - isHeaderSticky, - isFooterSticky, - isFirstColumnSticky, - rowDensity = 'normal', - onSortChange, - sortFunctions, - toolbar, - pagination, - height, - width, - showStripedRows, - gridTemplateColumns, - isLoading = false, - isRefreshing = false, - showBorderedCells = false, - defaultSelectedIds = [], - isVirtualized = false, - ...rest -}: TableProps): React.ReactElement => { - const { theme } = useTheme(); - const [selectedRows, setSelectedRows] = React.useState['id'][]>( - selectionType !== 'none' ? defaultSelectedIds : [], - ); - const [disabledRows, setDisabledRows] = React.useState['id'][]>([]); - const [totalItems, setTotalItems] = React.useState(data.nodes.length || 0); - const [paginationType, setPaginationType] = React.useState>( - 'client', - ); - const [headerRowDensity, setHeaderRowDensity] = React.useState( - undefined, - ); - const [hasHoverActions, setHasHoverActions] = React.useState(false); - // Need to make header is sticky if first column is sticky otherwise the first header cell will not be sticky - const shouldHeaderBeSticky = isHeaderSticky ?? isFirstColumnSticky; - const backgroundColor = tableBackgroundColor; - - const isMobile = useIsMobile(); - const lastHoverActionsColWidth = isMobile ? '1fr' : '0px'; - - const { - isEntering: isRefreshSpinnerEntering, - isMounted: isRefreshSpinnerMounted, - isExiting: isRefreshSpinnerExiting, - isVisible: isRefreshSpinnerVisible, - } = usePresence(isRefreshing, { - transitionDuration: theme.motion.duration.quick, - }); +const _Table = forwardRef( + ( + { + children, + data, + multiSelectTrigger = 'row', + selectionType = 'none', + onSelectionChange, + isHeaderSticky, + isFooterSticky, + isFirstColumnSticky, + rowDensity = 'normal', + onSortChange, + sortFunctions, + toolbar, + pagination, + height, + width, + showStripedRows, + gridTemplateColumns, + isLoading = false, + isRefreshing = false, + showBorderedCells = false, + defaultSelectedIds = [], + isVirtualized = false, + ...rest + }: TableProps, + ref: React.Ref | undefined, + ): React.ReactElement => { + const { theme } = useTheme(); + const [selectedRows, setSelectedRows] = React.useState['id'][]>( + selectionType !== 'none' ? defaultSelectedIds : [], + ); + const [disabledRows, setDisabledRows] = React.useState['id'][]>([]); + const [totalItems, setTotalItems] = React.useState(data.nodes.length || 0); + const [paginationType, setPaginationType] = React.useState>( + 'client', + ); + const [headerRowDensity, setHeaderRowDensity] = React.useState< + TableHeaderRowProps['rowDensity'] + >(undefined); + const [hasHoverActions, setHasHoverActions] = React.useState(false); + const [VirtualizedTableDimensions, setVirtualizedTableDimensions] = React.useState({ + width: 0, + height: 0, + }); + + // Need to make header is sticky if first column is sticky otherwise the first header cell will not be sticky + const shouldHeaderBeSticky = isHeaderSticky ?? isFirstColumnSticky; + const backgroundColor = tableBackgroundColor; + + const isMobile = useIsMobile(); + const lastHoverActionsColWidth = isMobile ? '1fr' : '0px'; - // Table Theme - const columnCount = getTableHeaderCellCount(children, isVirtualized); - const firstColumnStickyHeaderCellCSS = isFirstColumnSticky - ? ` + const { + isEntering: isRefreshSpinnerEntering, + isMounted: isRefreshSpinnerMounted, + isExiting: isRefreshSpinnerExiting, + isVisible: isRefreshSpinnerVisible, + } = usePresence(isRefreshing, { + transitionDuration: theme.motion.duration.quick, + }); + + // Table Theme + const columnCount = getTableHeaderCellCount(children, isVirtualized); + const firstColumnStickyHeaderCellCSS = isFirstColumnSticky + ? ` &:nth-of-type(1) { left: 0 !important; position: sticky !important; @@ -231,9 +383,9 @@ const _Table = ({ } ` }` - : ''; - const firstColumnStickyFooterCellCSS = isFirstColumnSticky - ? ` + : ''; + const firstColumnStickyFooterCellCSS = isFirstColumnSticky + ? ` &:nth-of-type(1) { left: 0 !important; position: sticky !important; @@ -248,9 +400,9 @@ const _Table = ({ } ` }` - : ''; - const firstColumnStickyBodyCellCSS = isFirstColumnSticky - ? ` + : ''; + const firstColumnStickyBodyCellCSS = isFirstColumnSticky + ? ` &:nth-of-type(1) { left: 0 !important; position: sticky !important; @@ -265,14 +417,14 @@ const _Table = ({ } ` }` - : ''; + : ''; - const tableTheme = useTableTheme({ - Table: ` + const tableTheme = useTableTheme({ + Table: ` height:${isFooterSticky ? `100%` : undefined}; border: ${makeBorderSize(theme.border.width.thin)} solid ${ - theme.colors.surface.border.gray.muted - }; + theme.colors.surface.border.gray.muted + }; --data-table-library_grid-template-columns: ${ gridTemplateColumns ? `${gridTemplateColumns} ${hasHoverActions ? lastHoverActionsColWidth : ''}` @@ -284,298 +436,309 @@ const _Table = ({ } !important; background-color: ${getIn(theme.colors, backgroundColor)}; `, - HeaderCell: ` + HeaderCell: ` position: ${shouldHeaderBeSticky ? 'sticky' : 'relative'}; top: ${shouldHeaderBeSticky ? '0' : undefined}; ${firstColumnStickyHeaderCellCSS} `, - Cell: ` + Cell: ` ${firstColumnStickyBodyCellCSS} `, - FooterCell: ` + FooterCell: ` position: ${isFooterSticky ? 'sticky' : 'relative'}; bottom: ${isFooterSticky ? '0' : undefined}; ${firstColumnStickyFooterCellCSS} `, - }); - - useEffect(() => { - // Get the total number of items - setTotalItems(data.nodes.length); - }, [data.nodes]); - - // Selection Logic - const onSelectChange: MiddlewareFunction = (action, state): void => { - const selectedIds: Identifier[] = state.id ? [state.id] : state.ids ?? []; - setSelectedRows(selectedIds); - onSelectionChange?.({ - selectedIds, - values: data.nodes.filter((node) => selectedIds.includes(node.id)), }); - }; - const rowSelectConfig = useRowSelect( - data, - { - onChange: onSelectChange, - state: { - ...(selectionType === 'multiple' - ? { ids: selectedRows } - : selectionType === 'single' - ? { id: selectedRows[0] } - : {}), - }, - }, - { - clickType: - multiSelectTrigger === 'row' ? SelectClickTypes.RowClick : SelectClickTypes.ButtonClick, - rowSelect: selectionType !== 'none' ? rowSelectType[selectionType] : undefined, - }, - ); + useEffect(() => { + console.log('ref', ref); + if (ref?.current && !height && !width) { + const { width, height } = ref?.current.getBoundingClientRect(); + setVirtualizedTableDimensions({ width, height }); + console.log('Parent dimensions:', { width, height }); + // You can use the width and height to set your table dimensions + } + }, [ref]); + + useEffect(() => { + // Get the total number of items + setTotalItems(data.nodes.length); + }, [data.nodes]); + + // Selection Logic + const onSelectChange: MiddlewareFunction = (action, state): void => { + const selectedIds: Identifier[] = state.id ? [state.id] : state.ids ?? []; + setSelectedRows(selectedIds); + onSelectionChange?.({ + selectedIds, + values: data.nodes.filter((node) => selectedIds.includes(node.id)), + }); + }; - const toggleRowSelectionById = useMemo( - () => (id: Identifier): void => { - rowSelectConfig.fns.onToggleById(id); - }, - [rowSelectConfig.fns], - ); + const rowSelectConfig = useRowSelect( + data, + { + onChange: onSelectChange, + state: { + ...(selectionType === 'multiple' + ? { ids: selectedRows } + : selectionType === 'single' + ? { id: selectedRows[0] } + : {}), + }, + }, + { + clickType: + multiSelectTrigger === 'row' ? SelectClickTypes.RowClick : SelectClickTypes.ButtonClick, + rowSelect: selectionType !== 'none' ? rowSelectType[selectionType] : undefined, + }, + ); - const deselectAllRows = useMemo( - () => (): void => { - rowSelectConfig.fns.onRemoveAll(); - }, - [rowSelectConfig.fns], - ); + const toggleRowSelectionById = useMemo( + () => (id: Identifier): void => { + rowSelectConfig.fns.onToggleById(id); + }, + [rowSelectConfig.fns], + ); - const toggleAllRowsSelection = useMemo( - () => (): void => { - if (selectedRows.length > 0) { + const deselectAllRows = useMemo( + () => (): void => { rowSelectConfig.fns.onRemoveAll(); - } else { - const ids = data.nodes - .map((item: TableNode) => (disabledRows.includes(item.id) ? null : item.id)) - .filter(Boolean) as Identifier[]; - - rowSelectConfig.fns.onAddAll(ids); - } - }, - [rowSelectConfig.fns, data.nodes, selectedRows, disabledRows], - ); - - // Sort Logic - const handleSortChange: MiddlewareFunction = (action, state) => { - onSortChange?.({ - sortKey: state.sortKey, - isSortReversed: state.reverse, - }); - }; - - const sort = useSort( - data, - { - onChange: handleSortChange, - }, - { - // @ts-expect-error ignore this, if sortFunctions is undefined, it will be ignored - sortFns: sortFunctions, - }, - ); - - const currentSortedState: TableContextType['currentSortedState'] = useMemo(() => { - return { - sortKey: sort.state.sortKey, - isSortReversed: sort.state.reverse, - sortableColumns: Object.keys(sortFunctions ?? {}), + }, + [rowSelectConfig.fns], + ); + + const toggleAllRowsSelection = useMemo( + () => (): void => { + if (selectedRows.length > 0) { + rowSelectConfig.fns.onRemoveAll(); + } else { + const ids = data.nodes + .map((item: TableNode) => (disabledRows.includes(item.id) ? null : item.id)) + .filter(Boolean) as Identifier[]; + + rowSelectConfig.fns.onAddAll(ids); + } + }, + [rowSelectConfig.fns, data.nodes, selectedRows, disabledRows], + ); + + // Sort Logic + const handleSortChange: MiddlewareFunction = (action, state) => { + onSortChange?.({ + sortKey: state.sortKey, + isSortReversed: state.reverse, + }); }; - }, [sort.state, sortFunctions]); - const toggleSort = useCallback( - (sortKey: string): void => { - sort.fns.onToggleSort({ - sortKey, - }); - }, - [sort.fns], - ); + const sort = useSort( + data, + { + onChange: handleSortChange, + }, + { + // @ts-expect-error ignore this, if sortFunctions is undefined, it will be ignored + sortFns: sortFunctions, + }, + ); + + const currentSortedState: TableContextType['currentSortedState'] = useMemo(() => { + return { + sortKey: sort.state.sortKey, + isSortReversed: sort.state.reverse, + sortableColumns: Object.keys(sortFunctions ?? {}), + }; + }, [sort.state, sortFunctions]); + + const toggleSort = useCallback( + (sortKey: string): void => { + sort.fns.onToggleSort({ + sortKey, + }); + }, + [sort.fns], + ); - // Pagination + // Pagination - const hasPagination = Boolean(pagination); + const hasPagination = Boolean(pagination); - const paginationConfig = usePagination( - data, - { - state: { - page: 0, - size: tablePagination.defaultPageSize, + const paginationConfig = usePagination( + data, + { + state: { + page: 0, + size: tablePagination.defaultPageSize, + }, }, - }, - { - isServer: paginationType === 'server', - }, - ); - - const currentPaginationState = useMemo(() => { - return hasPagination - ? { - page: paginationConfig.state.page, - size: paginationConfig.state.size, - } - : undefined; - }, [paginationConfig.state, hasPagination]); - - const setPaginationPage = useCallback( - (page: number): void => { - paginationConfig.fns.onSetPage(page); - }, - [paginationConfig.fns], - ); + { + isServer: paginationType === 'server', + }, + ); + + const currentPaginationState = useMemo(() => { + return hasPagination + ? { + page: paginationConfig.state.page, + size: paginationConfig.state.size, + } + : undefined; + }, [paginationConfig.state, hasPagination]); + + const setPaginationPage = useCallback( + (page: number): void => { + paginationConfig.fns.onSetPage(page); + }, + [paginationConfig.fns], + ); - const setPaginationRowSize = useCallback( - (size: number): void => { - paginationConfig.fns.onSetSize(size); - }, - [paginationConfig.fns], - ); - - // Toolbar Component - if (__DEV__) { - if (toolbar && !isValidAllowedChildren(toolbar, ComponentIds.TableToolbar)) { - throwBladeError({ - message: 'Only TableToolbar component is allowed in the `toolbar` prop', - moduleName: 'Table', - }); + const setPaginationRowSize = useCallback( + (size: number): void => { + paginationConfig.fns.onSetSize(size); + }, + [paginationConfig.fns], + ); + + // Toolbar Component + if (__DEV__) { + if (toolbar && !isValidAllowedChildren(toolbar, ComponentIds.TableToolbar)) { + throwBladeError({ + message: 'Only TableToolbar component is allowed in the `toolbar` prop', + moduleName: 'Table', + }); + } } - } - // Table Context - const tableContext: TableContextType = useMemo( - () => ({ - selectionType, - selectedRows, - totalItems, - toggleRowSelectionById, - toggleAllRowsSelection, - deselectAllRows, - rowDensity, - toggleSort, - currentSortedState, - setPaginationPage, - setPaginationRowSize, - currentPaginationState, - showStripedRows, - disabledRows, - setDisabledRows, - paginationType, - setPaginationType, - backgroundColor, - headerRowDensity, - setHeaderRowDensity, - showBorderedCells, - hasHoverActions, - setHasHoverActions, - columnCount, - gridTemplateColumns, - }), - [ - selectionType, - selectedRows, - totalItems, - toggleRowSelectionById, - toggleAllRowsSelection, - deselectAllRows, - gridTemplateColumns, - rowDensity, - toggleSort, - columnCount, - currentSortedState, - setPaginationPage, - setPaginationRowSize, - currentPaginationState, - showStripedRows, - disabledRows, - setDisabledRows, - paginationType, - setPaginationType, - backgroundColor, - headerRowDensity, - setHeaderRowDensity, - showBorderedCells, - hasHoverActions, - setHasHoverActions, - ], - ); - - return ( - - {isLoading ? ( - - - - ) : ( - - {isRefreshSpinnerMounted && ( - - - - )} - {toolbar} - ({ + selectionType, + selectedRows, + totalItems, + toggleRowSelectionById, + toggleAllRowsSelection, + deselectAllRows, + rowDensity, + toggleSort, + currentSortedState, + setPaginationPage, + setPaginationRowSize, + currentPaginationState, + showStripedRows, + disabledRows, + setDisabledRows, + paginationType, + setPaginationType, + backgroundColor, + headerRowDensity, + setHeaderRowDensity, + showBorderedCells, + hasHoverActions, + setHasHoverActions, + columnCount, + gridTemplateColumns, + }), + [ + selectionType, + selectedRows, + totalItems, + toggleRowSelectionById, + toggleAllRowsSelection, + deselectAllRows, + gridTemplateColumns, + rowDensity, + toggleSort, + columnCount, + currentSortedState, + setPaginationPage, + setPaginationRowSize, + currentPaginationState, + showStripedRows, + disabledRows, + setDisabledRows, + paginationType, + setPaginationType, + backgroundColor, + headerRowDensity, + setHeaderRowDensity, + showBorderedCells, + hasHoverActions, + setHasHoverActions, + ], + ); + + return ( + + {isLoading ? ( + - {children} - - {pagination} - - )} - - ); -}; + + + ) : ( + + {isRefreshSpinnerMounted && ( + + + + )} + {toolbar} + + {children} + + {pagination} + + )} + + ); + }, +); const Table = assignWithoutSideEffects(_Table, { componentId: ComponentIds.Table, diff --git a/packages/blade/src/components/Table/TableBody.web.tsx b/packages/blade/src/components/Table/TableBody.web.tsx index 9a05dc9ca8a..585afc87151 100644 --- a/packages/blade/src/components/Table/TableBody.web.tsx +++ b/packages/blade/src/components/Table/TableBody.web.tsx @@ -27,51 +27,24 @@ import { makeAccessible } from '~utils/makeAccessible'; import { useIsomorphicLayoutEffect } from '~utils/useIsomorphicLayoutEffect'; import type { Theme } from '~components/BladeProvider'; import { makeAnalyticsAttribute } from '~utils/makeAnalyticsAttribute'; - -const getTableRowBackgroundTransition = (theme: Theme): string => { - const rowBackgroundTransition = `background-color ${makeMotionTime( - getIn(theme.motion, tableRow.backgroundColorMotionDuration), - )} ${getIn(theme.motion, tableRow.backgroundColorMotionEasing)}`; - - return rowBackgroundTransition; -}; - -const getTableActionsHoverStyles = ({ - hoverColor, - theme, - backgroundGradientColor, -}: { - hoverColor: DotNotationToken; - backgroundGradientColor?: DotNotationToken>; - theme: Theme; -}): React.CSSProperties => { - const rowBackgroundTransition = getTableRowBackgroundTransition(theme); - - return { - // Solid layer 1 background - should match the table background - [`& .${classes.HOVER_ACTIONS}`]: { - backgroundColor: getIn(theme.colors, tableBackgroundColor), - transition: rowBackgroundTransition, - }, - // Alpha layer 2 background - Stripped row background, Hover background in selected state, etc - [`& .${classes.HOVER_ACTIONS_LAYER2}`]: { - backgroundColor: getIn(theme.colors, backgroundGradientColor ?? 'transparent'), - transition: rowBackgroundTransition, - }, - // Alpha layer 3 background - Hover, selection, active background - [`& .${classes.HOVER_ACTIONS_LAYER3}`]: { - backgroundColor: getIn(theme.colors, hoverColor), - transition: rowBackgroundTransition, - }, - }; -}; +import { getTableActionsHoverStyles, getTableRowBackgroundTransition } from './utils'; const StyledVirtualized = styled(Virtualized)<{ $isSelectable: boolean; $showStripedRows: boolean; }>(({ theme, $showStripedRows, $isSelectable }) => { const rowBackgroundTransition = getTableRowBackgroundTransition(theme); - return {}; + return { + // '&': { + // backgroundColor: 'yellow !important', + // border: '1px solid black', + // }, + // change backgroundColor to yellow + '& .cell-wrapper': { + backgroundColor: 'yellow !important', + }, + }; + // return { // '&&&': { // border: 'none', @@ -514,6 +487,7 @@ const StyledRow = styled(Row)<{ $showBorderedCells: boolean; }>(({ theme, $isSelectable, $isHoverable, $showBorderedCells }) => { const { hasHoverActions } = useTableContext(); + console.log({ hasHoverActions }); const rowBackgroundTransition = `background-color ${makeMotionTime( getIn(theme.motion, tableRow.backgroundColorMotionDuration), @@ -522,8 +496,6 @@ const StyledRow = styled(Row)<{ return { '&&&': { backgroundColor: 'transparent', - // display: 'grid', - // gridTemplateColumns: 'repeat(auto-fit, minmax(0, 1fr))', '& .cell-wrapper': $showBorderedCells ? { borderRightWidth: makeSpace(getIn(theme.border.width, tableRow.borderBottomWidth)), @@ -673,6 +645,7 @@ const _TableRow = ({ flexShrink={0} flexGrow={1} width="max-content" + border="1px solid black" > >; const nodes: Item[] = [ - ...Array.from({ length: 400 }, (_, i) => ({ + ...Array.from({ length: 4 }, (_, i) => ({ id: (i + 1).toString(), paymentId: `rzp${Math.floor(Math.random() * 1000000)}`, amount: Number((Math.random() * 10000).toFixed(2)), @@ -120,16 +121,17 @@ const data: TableData = { }; const TableTemplate: StoryFn = ({ ...args }) => { + const tableRef = useRef(null); return ( - + <> total rows : {nodes.length} @@ -140,6 +142,14 @@ const TableTemplate: StoryFn = ({ ...args }) => { } + sortFunctions={{ + ID: (array) => array.sort((a, b) => Number(a.id) - Number(b.id)), + AMOUNT: (array) => array.sort((a, b) => a.amount - b.amount), + PAYMENT_ID: (array) => array.sort((a, b) => a.paymentId.localeCompare(b.paymentId)), + DATE: (array) => array.sort((a, b) => a.date.getTime() - b.date.getTime()), + STATUS: (array) => array.sort((a, b) => a.status.localeCompare(b.status)), + }} + ref={tableRef} isVirtualized > {(tableData) => ( @@ -155,7 +165,7 @@ const TableTemplate: StoryFn = ({ ...args }) => { Account Date Method - Status demo + Status )} @@ -166,6 +176,29 @@ const TableTemplate: StoryFn = ({ ...args }) => { onClick={() => { console.log('where'); }} + hoverActions={ + <> + + { + console.log('Approved', tableItem.id); + }} + /> + { + console.log('Rejected', tableItem.id); + }} + /> + + } > {tableItem.paymentId} diff --git a/packages/blade/src/components/Table/utils.ts b/packages/blade/src/components/Table/utils.ts new file mode 100644 index 00000000000..5872cbc585a --- /dev/null +++ b/packages/blade/src/components/Table/utils.ts @@ -0,0 +1,46 @@ +import { classes, tableRow, tableBackgroundColor } from './tokens'; +import type { Theme } from '~components/BladeProvider'; +import type { DotNotationToken } from '~utils/lodashButBetter/get'; + +import { makeMotionTime } from '~utils'; +import getIn from '~utils/lodashButBetter/get'; + +const getTableRowBackgroundTransition = (theme: Theme): string => { + const rowBackgroundTransition = `background-color ${makeMotionTime( + getIn(theme.motion, tableRow.backgroundColorMotionDuration), + )} ${getIn(theme.motion, tableRow.backgroundColorMotionEasing)}`; + + return rowBackgroundTransition; +}; + +const getTableActionsHoverStyles = ({ + hoverColor, + theme, + backgroundGradientColor, +}: { + hoverColor: DotNotationToken; + backgroundGradientColor?: DotNotationToken>; + theme: Theme; +}): React.CSSProperties => { + const rowBackgroundTransition = getTableRowBackgroundTransition(theme); + + return { + // Solid layer 1 background - should match the table background + [`& .${classes.HOVER_ACTIONS}`]: { + backgroundColor: getIn(theme.colors, tableBackgroundColor), + transition: rowBackgroundTransition, + }, + // Alpha layer 2 background - Stripped row background, Hover background in selected state, etc + [`& .${classes.HOVER_ACTIONS_LAYER2}`]: { + backgroundColor: getIn(theme.colors, backgroundGradientColor ?? 'transparent'), + transition: rowBackgroundTransition, + }, + // Alpha layer 3 background - Hover, selection, active background + [`& .${classes.HOVER_ACTIONS_LAYER3}`]: { + backgroundColor: getIn(theme.colors, hoverColor), + transition: rowBackgroundTransition, + }, + }; +}; + +export { getTableActionsHoverStyles, getTableRowBackgroundTransition };