From e2b8f2cf19e0263296bda123bf1368829d30371b Mon Sep 17 00:00:00 2001 From: Mihai Albu Date: Wed, 10 Jul 2024 03:49:30 +0000 Subject: [PATCH] fix(flat-table-row-header): focus border partially covered in expanded rows updated the flat table row header and the flat table cell to prevent covering the focus border fixes: #6561 --- .../flat-table/__internal__/use-table-cell.ts | 62 ++++++ .../flat-table-cell.component.tsx | 8 +- .../flat-table-row-header.component.tsx | 6 + .../flat-table-row-header.spec.tsx | 97 ++++++++++ .../flat-table-row/flat-table-row.style.ts | 16 +- .../flat-table/flat-table-test.stories.tsx | 183 ++++++++++++++++++ src/components/flat-table/flat-table.style.ts | 8 + 7 files changed, 372 insertions(+), 8 deletions(-) diff --git a/src/components/flat-table/__internal__/use-table-cell.ts b/src/components/flat-table/__internal__/use-table-cell.ts index 59dc5d50c1..e81086c0b4 100644 --- a/src/components/flat-table/__internal__/use-table-cell.ts +++ b/src/components/flat-table/__internal__/use-table-cell.ts @@ -24,6 +24,67 @@ export default (id: string) => { const isFirstCell = id === firstCellId; const isExpandableCell = expandable && isFirstCell && firstColumnExpandable; + const bringToFront = ( + ev: + | React.MouseEvent + | React.FocusEvent, + tagName: "TD" | "TH" + ) => { + /* istanbul ignore if */ + if ( + !ev || + !ev.nativeEvent || + typeof ev.nativeEvent.composedPath !== "function" + ) { + return; + } + + const { nativeEvent } = ev; + + // get the entire path of the event + const path = nativeEvent.composedPath(); + + // get the table from the path + const tableBody = path.find((el) => { + return el instanceof HTMLElement && el.tagName === "TBODY"; + }); + + // if there is no table in the path we don't do anything + if (!tableBody) { + return; + } + + // get all the th and td elements that are sticky + const stickyCells = Array.from( + (tableBody as HTMLElement).querySelectorAll("th, td") + ).filter((el) => { + return ( + el.getAttribute("data-sticky-align") === "left" || + el.getAttribute("data-sticky-align") === "right" || + el.classList.contains("isSticky") + ); + }); + + // reset the z-index to the default value for all the sticky cells, in case other cells were clicked before + stickyCells.map((el) => { + (el as HTMLElement).classList.remove("bringToFront"); + return el; + }); + + // find the current cell in the path + const cell = path.find( + (el) => el instanceof HTMLElement && el.tagName === tagName + ); + + // if the current cell is sticky, increase the z-index value + const cellIndex = stickyCells.indexOf(cell as HTMLTableCellElement); + if (cellIndex !== -1) { + (stickyCells[cellIndex] as HTMLTableCellElement).classList.add( + "bringToFront" + ); + } + }; + useEffect(() => { const tabstopTimer = setTimeout(() => { setTabIndex(isExpandableCell && getTabStopElementId() === id ? 0 : -1); @@ -46,5 +107,6 @@ export default (id: string) => { tabIndex, isInHighlightedRow: highlighted, isInSelectedRow: selected, + bringToFront, }; }; diff --git a/src/components/flat-table/flat-table-cell/flat-table-cell.component.tsx b/src/components/flat-table/flat-table-cell/flat-table-cell.component.tsx index 9eeb876422..821369b181 100644 --- a/src/components/flat-table/flat-table-cell/flat-table-cell.component.tsx +++ b/src/components/flat-table/flat-table-cell/flat-table-cell.component.tsx @@ -1,4 +1,4 @@ -import React, { useRef } from "react"; +import React, { useRef, useCallback } from "react"; import { PaddingProps } from "styled-system"; import { TableBorderSize, TableCellAlign } from ".."; @@ -59,8 +59,13 @@ export const FlatTableCell = ({ isInHighlightedRow, isInSelectedRow, tabIndex, + bringToFront, } = useTableCell(internalId.current); + const handleOnFocus = (ev: React.FocusEvent) => { + bringToFront(ev, "TD"); + }; + return ( ) => { + bringToFront(ev, "TH"); + }; + const handleOnKeyDown = useCallback( (ev: React.KeyboardEvent) => { if (isExpandableCell && onKeyDown) { @@ -109,6 +114,7 @@ export const FlatTableRowHeader = ({ data-highlighted={isInHighlightedRow && isExpandableCell} {...rest} id={internalId.current} + onFocus={handleOnFocus} > { testStyledSystemPadding( @@ -321,6 +327,97 @@ describe("FlatTableRowHeader", () => { ); } ); + + it("increases the z-index of the sticky TH or TD if content is focused and they are part of a FlatTableBody", () => { + rtlRender( + + + + + + + + + + text content + + + text content + + + + text content + + + + ); + + const headerOne = screen.getByTestId("header-one"); + const headerOneButton = screen.getByRole("button", { + name: "header one button", + }); + + const headerTwo = screen.getByTestId("header-two"); + const headerTwoButton = screen.getByRole("button", { + name: "header two button", + }); + + const cell = screen.getByTestId("cell"); + const cellButton = screen.getByRole("button", { name: "cell button" }); + + headerOneButton.focus(); + + expect(cell).not.toHaveClass("bringToFront"); + expect(headerOne).toHaveClass("bringToFront"); + expect(headerTwo).not.toHaveClass("bringToFront"); + + headerTwoButton.focus(); + + expect(cell).not.toHaveClass("bringToFront"); + expect(headerOne).not.toHaveClass("bringToFront"); + expect(headerTwo).toHaveClass("bringToFront"); + + cellButton.focus(); + + expect(cell).toHaveClass("bringToFront"); + expect(headerOne).not.toHaveClass("bringToFront"); + expect(headerTwo).not.toHaveClass("bringToFront"); + }); + + it("does not increase the z-index of the sticky TH or TD if they are not part of a FlatTableBody", () => { + rtlRender( + + + + + + + + text content + + ); + + const headerOne = screen.getByTestId("header-one"); + const headerOneButton = screen.getByRole("button", { + name: "header one button", + }); + + headerOneButton.focus(); + + expect(headerOne).not.toHaveClass("bringToFront"); + }); }); describe.each([ diff --git a/src/components/flat-table/flat-table-row/flat-table-row.style.ts b/src/components/flat-table/flat-table-row/flat-table-row.style.ts index 20839177b2..25eb896420 100644 --- a/src/components/flat-table/flat-table-row/flat-table-row.style.ts +++ b/src/components/flat-table/flat-table-row/flat-table-row.style.ts @@ -26,10 +26,12 @@ const firstColumnOldFocusStyling = ` outline-offset: -1px; `; -const newFocusStyling = ` - ${addFocusStyling(true)} - z-index: 1000; -`; +const newFocusStyling = (theme: ThemeObject) => { + return ` + ${addFocusStyling(true)} + z-index: ${theme.zIndex.overlay + 5}; + `; +}; const getLeftStickyStyling = (index: number, themeOptOut: boolean) => index === 0 && @@ -266,7 +268,7 @@ const StyledFlatTableRow = styled.tr` top: 0; bottom: 0px; ${!theme.focusRedesignOptOut - ? newFocusStyling + ? newFocusStyling(theme) : /* istanbul ignore next */ oldFocusStyling} pointer-events: none; } @@ -306,7 +308,7 @@ const StyledFlatTableRow = styled.tr` border: none; content: ""; height: ${rowHeight}px; - ${newFocusStyling} + ${newFocusStyling(theme)} } `} `} @@ -357,7 +359,7 @@ const StyledFlatTableRow = styled.tr` :focus { ${!theme.focusRedesignOptOut - ? newFocusStyling + ? newFocusStyling(theme) : /* istanbul ignore next */ firstColumnOldFocusStyling} } diff --git a/src/components/flat-table/flat-table-test.stories.tsx b/src/components/flat-table/flat-table-test.stories.tsx index 005d961d48..d0982607d2 100644 --- a/src/components/flat-table/flat-table-test.stories.tsx +++ b/src/components/flat-table/flat-table-test.stories.tsx @@ -14,11 +14,14 @@ import { FlatTableRowHeaderProps, FlatTableCellProps, } from "."; +import Button from "../../../src/components/button"; import Box from "../../../src/components/box"; import Link from "../../../src/components/link"; import guid from "../../__internal__/utils/helpers/guid"; import { FLAT_TABLE_THEMES } from "./flat-table.config"; import { WithSortingHeaders } from "./flat-table.stories"; +import Textbox from "../textbox/textbox.component"; +import DateInput from "../date/date.component"; export default { title: "Flat Table/Test", @@ -28,6 +31,7 @@ export default { "SortableStory", "SubRowsAsAComponentStory", "FlatTableSizeFocus", + "FlatRowHeaderWithNoPaddingAndButtons", ], parameters: { info: { disable: true }, @@ -496,3 +500,182 @@ export const FlatTableSizeFocus = () => { ); }; + +export const FlatRowHeaderWithNoPaddingAndButtons = () => { + const SubRows = [ + + subrow content + + + + subrow content + subrow content + subrow content + + + + subrow content + subrow content + + subrow content + + subrow content + , + + subrow content + + subrow content + + + + + subrow content + + + + subrow content + subrow content + subrow content + + subrow content + + subrow content + , + ]; + return ( + + + + Sticky TD + + Sticky TH + + Column + Column + Column + Column + Column + Column + + Sticky TH + + Sticky TD + + + + + + + + + + + text content + text content + text content + text content + text content + text content + + + + text content + + + + {}} + onChange={() => {}} + onClick={() => {}} + onKeyDown={() => {}} + prefix="" + size="medium" + value="2019-04-04" + warning="" + /> + + + + + text content + + + + text content + text content + text content + text content + + + + + + + + + text content + + + + text content + text content + + + + text content + + + + text content + + + + text content + + + text content + + + + text content + text content + text content + text content + text content + text content + + + + text content + + + text content + + + + text content + text content + text content + text content + text content + text content + + + + text content + + + + ); +}; diff --git a/src/components/flat-table/flat-table.style.ts b/src/components/flat-table/flat-table.style.ts index 966ef1247b..eedf6f1bd3 100644 --- a/src/components/flat-table/flat-table.style.ts +++ b/src/components/flat-table/flat-table.style.ts @@ -284,6 +284,14 @@ const StyledFlatTableWrapper = styled(StyledBox)` z-index: ${({ theme }) => theme.zIndex.overlay}; } + tbody + ${StyledFlatTableRowHeader}.bringToFront, + ${StyledFlatTableCell}.bringToFront, + tbody + ${StyledFlatTableCheckbox}.bringToFront { + z-index: ${({ theme }) => theme.zIndex.overlay + 5}; + } + ${({ footer }) => footer && css`