From 1c0f0f3b4807477e7530abc26267f37e5215bd38 Mon Sep 17 00:00:00 2001 From: Yash Raj Chhabra Date: Mon, 27 Feb 2023 17:43:29 +0530 Subject: [PATCH 1/5] feat: table: show scroller on table rows --- .../Table/Internal/Body/Scroller.tsx | 63 +++++-------------- src/components/Table/Internal/OcTable.tsx | 11 +++- .../Table/Internal/OcTable.types.ts | 1 + src/components/Table/Table.stories.tsx | 13 +++- 4 files changed, 37 insertions(+), 51 deletions(-) diff --git a/src/components/Table/Internal/Body/Scroller.tsx b/src/components/Table/Internal/Body/Scroller.tsx index aac4f9bea..cddc8be81 100644 --- a/src/components/Table/Internal/Body/Scroller.tsx +++ b/src/components/Table/Internal/Body/Scroller.tsx @@ -10,7 +10,6 @@ import { ButtonShape, ButtonSize, SecondaryButton } from '../../../Button'; import { IconName } from '../../../Icon'; import { ColumnType } from '../../Table.types'; import { ScrollerProps, ScrollerRef } from '../OcTable.types'; -import { useDebounce } from '../../../../hooks/useDebounce'; import styles from '../octable.module.scss'; @@ -26,13 +25,13 @@ export const Scroller = React.forwardRef( scrollHeaderRef, scrollLeftAriaLabelText, scrollRightAriaLabelText, + hoveredRowBoundingRect, }: ScrollerProps, ref: ForwardedRef ) => { const [visible, setVisible] = useState(false); const [leftButtonVisible, setLeftButtonVisible] = useState(false); const [rightButtonVisible, setRightButtonVisible] = useState(true); - const [buttonStyle, setButtonStyle] = useState({}); // todo @yash: handle rtl @@ -68,54 +67,26 @@ export const Scroller = React.forwardRef( [stickyOffsets, flattenColumns] ); - const computePosition = useCallback((): void => { + const getButtonStyle = (): number => { if (!scrollBodyRef.current) { - return; + return 0; } - const { - height: scrollBodyHeight, - top: scrollBodyTop, - bottom: scrollBodyBottom, - } = scrollBodyRef.current.getBoundingClientRect(); + const { top: scrollBodyTop } = + scrollBodyRef.current.getBoundingClientRect(); + const { top: rowTop, height: rowHeight } = hoveredRowBoundingRect ?? {}; const { height: stickyHeaderHeight = 0 } = scrollHeaderRef?.current?.getBoundingClientRect?.() || {}; - const { height: viewportHeight } = document.body.getBoundingClientRect(); - - let buttonTop: number = 0; - - if (scrollBodyTop > 0) { - // When the top of the table is in the viewport - - if (scrollBodyBottom > viewportHeight) { - // When bottom of the table is out of the viewport - buttonTop = (viewportHeight - scrollBodyTop) / 2; - } else if (scrollBodyBottom < viewportHeight) { - // When full table is in the viewport - buttonTop = scrollBodyHeight / 2; - } - } else if (scrollBodyTop < 0) { - // When the top of the table is out the viewport - - if (scrollBodyBottom > viewportHeight) { - // When bottom of the table is out of the viewport - buttonTop = Math.abs(scrollBodyTop) + viewportHeight / 2; - } else if (scrollBodyBottom < viewportHeight) { - // When bottom of the table is in the viewport - buttonTop = - Math.abs(scrollBodyTop) + - (viewportHeight - (viewportHeight - scrollBodyBottom)) / 2; - } - } - setButtonStyle({ - top: buttonTop + stickyHeaderHeight - BUTTON_HEIGHT / 2, - }); - }, []); - - const debouncedComputePosition = useDebounce(computePosition, 500); + return ( + rowTop - + scrollBodyTop + + stickyHeaderHeight + + rowHeight / 2 - + BUTTON_HEIGHT / 2 + ); + }; const onMouseEnter = useCallback((): void => { setVisible(true); - computePosition(); }, []); const onMouseLeave = useCallback((): void => setVisible(false), []); @@ -163,11 +134,9 @@ export const Scroller = React.forwardRef( })); useEffect(() => { - document.addEventListener('scroll', debouncedComputePosition); scrollBodyRef.current?.addEventListener?.('mouseenter', onMouseEnter); scrollBodyRef.current?.addEventListener?.('mouseleave', onMouseLeave); return () => { - document.removeEventListener('scroll', debouncedComputePosition); scrollBodyRef.current?.removeEventListener?.( 'mouseenter', onMouseEnter @@ -186,7 +155,7 @@ export const Scroller = React.forwardRef( style={{ left: leftButtonOffset, opacity: leftButtonVisible && visible ? 1 : 0, - ...buttonStyle, + top: getButtonStyle(), }} shape={ButtonShape.Round} size={ButtonSize.Medium} @@ -201,7 +170,7 @@ export const Scroller = React.forwardRef( style={{ right: rightButtonOffset, opacity: rightButtonVisible && visible ? 1 : 0, - ...buttonStyle, + top: getButtonStyle(), }} shape={ButtonShape.Round} size={ButtonSize.Medium} diff --git a/src/components/Table/Internal/OcTable.tsx b/src/components/Table/Internal/OcTable.tsx index 4913924cd..d64ef5319 100644 --- a/src/components/Table/Internal/OcTable.tsx +++ b/src/components/Table/Internal/OcTable.tsx @@ -278,6 +278,8 @@ function OcTable( const [colsWidths, updateColsWidths] = useLayoutState( new Map() ); + const [hoveredRowBoundingRect, setHoveredRowBoundingRect] = + useState(null); // Convert map to number width const colsKeys = getColumnsKey(flattenColumns); @@ -506,7 +508,12 @@ function OcTable( onRow={onRow} emptyNode={emptyNode} childrenColumnName={mergedChildrenColumnName} - onRowHoverEnter={onRowHoverEnter} + onRowHoverEnter={(i, r, e) => { + const hoveredCell = e.target as HTMLElement; + setHoveredRowBoundingRect(hoveredCell.getBoundingClientRect()); + console.log(hoveredCell.getBoundingClientRect()); + onRowHoverEnter?.(i, r, e); + }} onRowHoverLeave={onRowHoverLeave} /> ); @@ -563,6 +570,7 @@ function OcTable( scrollHeaderRef={scrollHeaderRef} scrollLeftAriaLabelText={scrollLeftAriaLabelText} scrollRightAriaLabelText={scrollRightAriaLabelText} + hoveredRowBoundingRect={hoveredRowBoundingRect} /> )} ( stickyOffsets={stickyOffsets} scrollLeftAriaLabelText={scrollLeftAriaLabelText} scrollRightAriaLabelText={scrollRightAriaLabelText} + hoveredRowBoundingRect={hoveredRowBoundingRect} /> )} { * @default 'Scroll left' */ scrollLeftAriaLabelText?: string; + hoveredRowBoundingRect?: DOMRect; } export type ScrollerRef = { diff --git a/src/components/Table/Table.stories.tsx b/src/components/Table/Table.stories.tsx index 229574b05..2c5f7296f 100644 --- a/src/components/Table/Table.stories.tsx +++ b/src/components/Table/Table.stories.tsx @@ -1188,9 +1188,16 @@ const Table_Base_Story: ComponentStory = (args) => { const Table_Wrapped_Story: ComponentStory = (args) => { return ( -
- - + <> +
+
+
+ + ); }; From 29bbb9d217e3bfa8a0e9ce0b438362fd4006a85f Mon Sep 17 00:00:00 2001 From: Yash Raj Chhabra Date: Mon, 27 Feb 2023 18:06:24 +0530 Subject: [PATCH 2/5] chore: update --- src/components/Table/Internal/Body/Scroller.tsx | 13 +++++++++---- .../Internal/FrameWrapper/FrameWrapper.tsx | 17 +++++++++++++---- src/components/Table/Internal/OcTable.tsx | 5 ++++- src/components/Table/Internal/OcTable.types.ts | 7 +++++++ src/components/Table/Table.stories.tsx | 13 +++---------- 5 files changed, 36 insertions(+), 19 deletions(-) diff --git a/src/components/Table/Internal/Body/Scroller.tsx b/src/components/Table/Internal/Body/Scroller.tsx index cddc8be81..5895d6bcd 100644 --- a/src/components/Table/Internal/Body/Scroller.tsx +++ b/src/components/Table/Internal/Body/Scroller.tsx @@ -23,6 +23,7 @@ export const Scroller = React.forwardRef( scrollBodyRef, stickyOffsets, scrollHeaderRef, + titleRef, scrollLeftAriaLabelText, scrollRightAriaLabelText, hoveredRowBoundingRect, @@ -67,12 +68,15 @@ export const Scroller = React.forwardRef( [stickyOffsets, flattenColumns] ); - const getButtonStyle = (): number => { + const getButtonTop = (): number => { if (!scrollBodyRef.current) { return 0; } const { top: scrollBodyTop } = scrollBodyRef.current.getBoundingClientRect(); + const { height: titleHeight = 0 } = + titleRef.current?.getBoundingClientRect?.() || {}; + const { top: rowTop, height: rowHeight } = hoveredRowBoundingRect ?? {}; const { height: stickyHeaderHeight = 0 } = scrollHeaderRef?.current?.getBoundingClientRect?.() || {}; @@ -81,7 +85,8 @@ export const Scroller = React.forwardRef( scrollBodyTop + stickyHeaderHeight + rowHeight / 2 - - BUTTON_HEIGHT / 2 + BUTTON_HEIGHT / 2 + + titleHeight ); }; @@ -155,7 +160,7 @@ export const Scroller = React.forwardRef( style={{ left: leftButtonOffset, opacity: leftButtonVisible && visible ? 1 : 0, - top: getButtonStyle(), + top: getButtonTop(), }} shape={ButtonShape.Round} size={ButtonSize.Medium} @@ -170,7 +175,7 @@ export const Scroller = React.forwardRef( style={{ right: rightButtonOffset, opacity: rightButtonVisible && visible ? 1 : 0, - top: getButtonStyle(), + top: getButtonTop(), }} shape={ButtonShape.Round} size={ButtonSize.Medium} diff --git a/src/components/Table/Internal/FrameWrapper/FrameWrapper.tsx b/src/components/Table/Internal/FrameWrapper/FrameWrapper.tsx index 9d20cae66..cda6d13be 100644 --- a/src/components/Table/Internal/FrameWrapper/FrameWrapper.tsx +++ b/src/components/Table/Internal/FrameWrapper/FrameWrapper.tsx @@ -1,6 +1,15 @@ -import React from 'react'; +import React, { ForwardedRef } from 'react'; import { FrameWrapperProps } from './FrameWrapper.types'; -export const FrameWrapper = ({ classNames, children }: FrameWrapperProps) => { - return
{children}
; -}; +export const FrameWrapper = React.forwardRef( + ( + { classNames, children }: FrameWrapperProps, + ref: ForwardedRef + ) => { + return ( +
+ {children} +
+ ); + } +); diff --git a/src/components/Table/Internal/OcTable.tsx b/src/components/Table/Internal/OcTable.tsx index d64ef5319..0e939c4bd 100644 --- a/src/components/Table/Internal/OcTable.tsx +++ b/src/components/Table/Internal/OcTable.tsx @@ -272,6 +272,7 @@ function OcTable( const scrollHeaderRef = useRef(); const scrollBodyRef = useRef(); const scrollSummaryRef = useRef(); + const titleRef = useRef(null); const scrollerRef = useRef(null); const [pingedLeft, setPingedLeft] = useState(false); const [pingedRight, setPingedRight] = useState(false); @@ -568,6 +569,7 @@ function OcTable( scrollBodyRef={scrollBodyRef} stickyOffsets={stickyOffsets} scrollHeaderRef={scrollHeaderRef} + titleRef={titleRef} scrollLeftAriaLabelText={scrollLeftAriaLabelText} scrollRightAriaLabelText={scrollRightAriaLabelText} hoveredRowBoundingRect={hoveredRowBoundingRect} @@ -674,6 +676,7 @@ function OcTable( ref={scrollerRef} {...columnContext} scrollBodyRef={scrollBodyRef} + titleRef={titleRef} stickyOffsets={stickyOffsets} scrollLeftAriaLabelText={scrollLeftAriaLabelText} scrollRightAriaLabelText={scrollRightAriaLabelText} @@ -739,7 +742,7 @@ function OcTable( props={{ ...props, stickyOffsets, mergedExpandedKeys }} > {title && ( - + {title(mergedData)} )} diff --git a/src/components/Table/Internal/OcTable.types.ts b/src/components/Table/Internal/OcTable.types.ts index 828e21de4..889b52eb9 100644 --- a/src/components/Table/Internal/OcTable.types.ts +++ b/src/components/Table/Internal/OcTable.types.ts @@ -417,6 +417,10 @@ export interface ScrollerProps { scrollBodyRef: RefObject; stickyOffsets: StickyOffsets; scrollHeaderRef?: RefObject; + /** + * Ref of the table title + */ + titleRef?: RefObject; /** * The Table scroller right button aria label * @default 'Scroll right' @@ -427,6 +431,9 @@ export interface ScrollerProps { * @default 'Scroll left' */ scrollLeftAriaLabelText?: string; + /** + * DOMRect of the hovered row + */ hoveredRowBoundingRect?: DOMRect; } diff --git a/src/components/Table/Table.stories.tsx b/src/components/Table/Table.stories.tsx index 2c5f7296f..229574b05 100644 --- a/src/components/Table/Table.stories.tsx +++ b/src/components/Table/Table.stories.tsx @@ -1188,16 +1188,9 @@ const Table_Base_Story: ComponentStory = (args) => { const Table_Wrapped_Story: ComponentStory = (args) => { return ( - <> -
-
-
- - +
+
+ ); }; From f4eb96d1088387e2795ba52d2ddb0d4df1f790b0 Mon Sep 17 00:00:00 2001 From: Yash Raj Chhabra Date: Mon, 27 Feb 2023 19:17:31 +0530 Subject: [PATCH 3/5] chore: add padding --- src/components/Table/Internal/Body/Scroller.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/Table/Internal/Body/Scroller.tsx b/src/components/Table/Internal/Body/Scroller.tsx index 5895d6bcd..a85cd112a 100644 --- a/src/components/Table/Internal/Body/Scroller.tsx +++ b/src/components/Table/Internal/Body/Scroller.tsx @@ -14,6 +14,7 @@ import { ScrollerProps, ScrollerRef } from '../OcTable.types'; import styles from '../octable.module.scss'; const BUTTON_HEIGHT: number = 36; +const BUTTON_PADDING: number = 2; export const Scroller = React.forwardRef( ( @@ -158,7 +159,7 @@ export const Scroller = React.forwardRef( Date: Mon, 27 Feb 2023 21:54:40 +0530 Subject: [PATCH 4/5] chore: remove log --- src/components/Table/Internal/OcTable.tsx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/components/Table/Internal/OcTable.tsx b/src/components/Table/Internal/OcTable.tsx index 0e939c4bd..6a336c6cf 100644 --- a/src/components/Table/Internal/OcTable.tsx +++ b/src/components/Table/Internal/OcTable.tsx @@ -509,11 +509,10 @@ function OcTable( onRow={onRow} emptyNode={emptyNode} childrenColumnName={mergedChildrenColumnName} - onRowHoverEnter={(i, r, e) => { - const hoveredCell = e.target as HTMLElement; + onRowHoverEnter={(index, key, event) => { + const hoveredCell = event.target as HTMLElement; setHoveredRowBoundingRect(hoveredCell.getBoundingClientRect()); - console.log(hoveredCell.getBoundingClientRect()); - onRowHoverEnter?.(i, r, e); + onRowHoverEnter?.(index, key, event); }} onRowHoverLeave={onRowHoverLeave} /> From ed867195dfe848428e3921def55222502684a849 Mon Sep 17 00:00:00 2001 From: Yash Raj Chhabra Date: Tue, 28 Feb 2023 14:00:53 +0530 Subject: [PATCH 5/5] fix: scroller not updating on after scroll --- .../Table/Internal/Body/Scroller.tsx | 5 +++-- src/components/Table/Internal/OcTable.tsx | 20 +++++++++---------- .../Table/Internal/OcTable.types.ts | 8 ++++---- 3 files changed, 17 insertions(+), 16 deletions(-) diff --git a/src/components/Table/Internal/Body/Scroller.tsx b/src/components/Table/Internal/Body/Scroller.tsx index a85cd112a..a5a868664 100644 --- a/src/components/Table/Internal/Body/Scroller.tsx +++ b/src/components/Table/Internal/Body/Scroller.tsx @@ -27,14 +27,14 @@ export const Scroller = React.forwardRef( titleRef, scrollLeftAriaLabelText, scrollRightAriaLabelText, - hoveredRowBoundingRect, }: ScrollerProps, ref: ForwardedRef ) => { const [visible, setVisible] = useState(false); const [leftButtonVisible, setLeftButtonVisible] = useState(false); const [rightButtonVisible, setRightButtonVisible] = useState(true); - + const [hoveredRowBoundingRect, setHoveredRowBoundingRect] = + useState(null); // todo @yash: handle rtl const scrollOffsets: number[] = useMemo( @@ -137,6 +137,7 @@ export const Scroller = React.forwardRef( useImperativeHandle(ref, () => ({ onBodyScroll, + onRowHover: setHoveredRowBoundingRect, })); useEffect(() => { diff --git a/src/components/Table/Internal/OcTable.tsx b/src/components/Table/Internal/OcTable.tsx index 6a336c6cf..f8e043e86 100644 --- a/src/components/Table/Internal/OcTable.tsx +++ b/src/components/Table/Internal/OcTable.tsx @@ -279,8 +279,6 @@ function OcTable( const [colsWidths, updateColsWidths] = useLayoutState( new Map() ); - const [hoveredRowBoundingRect, setHoveredRowBoundingRect] = - useState(null); // Convert map to number width const colsKeys = getColumnsKey(flattenColumns); @@ -498,7 +496,15 @@ function OcTable( return emptyText; }, [hasData, emptyText]); - // Body + const _onRowHoverEnter = useCallback( + (index: number, key: Key, event: React.MouseEvent) => { + const hoveredCell = event.target as HTMLElement; + scrollerRef.current?.onRowHover?.(hoveredCell.getBoundingClientRect()); + onRowHoverEnter?.(index, key, event); + }, + [] + ); + const bodyTable = ( ( onRow={onRow} emptyNode={emptyNode} childrenColumnName={mergedChildrenColumnName} - onRowHoverEnter={(index, key, event) => { - const hoveredCell = event.target as HTMLElement; - setHoveredRowBoundingRect(hoveredCell.getBoundingClientRect()); - onRowHoverEnter?.(index, key, event); - }} + onRowHoverEnter={_onRowHoverEnter} onRowHoverLeave={onRowHoverLeave} /> ); @@ -571,7 +573,6 @@ function OcTable( titleRef={titleRef} scrollLeftAriaLabelText={scrollLeftAriaLabelText} scrollRightAriaLabelText={scrollRightAriaLabelText} - hoveredRowBoundingRect={hoveredRowBoundingRect} /> )} ( stickyOffsets={stickyOffsets} scrollLeftAriaLabelText={scrollLeftAriaLabelText} scrollRightAriaLabelText={scrollRightAriaLabelText} - hoveredRowBoundingRect={hoveredRowBoundingRect} /> )} { * @default 'Scroll left' */ scrollLeftAriaLabelText?: string; - /** - * DOMRect of the hovered row - */ - hoveredRowBoundingRect?: DOMRect; } export type ScrollerRef = { @@ -442,6 +438,10 @@ export type ScrollerRef = { * Helper method to handle body scroll changes */ onBodyScroll: () => void; + /** + * Helper method triggered on hover of a row + */ + onRowHover: (boundingRect: DOMRect) => void; }; export interface OcTableProps {