diff --git a/apps/admin-panel/components/data-table/index.tsx b/apps/admin-panel/components/data-table/index.tsx index 3fc94e50d..444fde2bb 100644 --- a/apps/admin-panel/components/data-table/index.tsx +++ b/apps/admin-panel/components/data-table/index.tsx @@ -50,7 +50,9 @@ const DataTable = ({ }: DataTableProps) => { const isMobile = useBreakpointDown("md") const [focusedRowIndex, setFocusedRowIndex] = useState(-1) + const [isTableFocused, setIsTableFocused] = useState(false) const tableRef = useRef(null) + const focusTimeoutRef = useRef() const router = useRouter() const getNavigationUrl = (item: T): string | null => { @@ -63,8 +65,46 @@ const DataTable = ({ return url !== null && url !== "" } + const isNoFocusActive = () => { + const activeElement = document.activeElement + const isBaseElement = + !activeElement || + activeElement === document.body || + activeElement === document.documentElement + const isOutsideTable = !tableRef.current?.contains(activeElement) + const isInteractiveElement = activeElement?.matches( + "button, input, select, textarea, a[href], [tabindex], [contenteditable]", + ) + return (isBaseElement || isOutsideTable) && !isInteractiveElement + } + + const smartFocus = () => { + if (isNoFocusActive()) { + if (focusTimeoutRef.current) { + clearTimeout(focusTimeoutRef.current) + } + + focusTimeoutRef.current = setTimeout(() => { + if (tableRef.current) { + tableRef.current.focus() + setIsTableFocused(true) + + const targetIndex = focusedRowIndex >= 0 ? focusedRowIndex : 0 + const targetRow = document.querySelector( + `[data-testid="table-row-${targetIndex}"]`, + ) as HTMLElement + + if (targetRow) { + targetRow.focus() + setFocusedRowIndex(targetIndex) + } + } + }, 0) + } + } + const focusRow = (index: number) => { - if (index < 0 || !data.length) return + if (index < 0 || !data.length || !isTableFocused) return const validIndex = Math.min(Math.max(0, index), data.length - 1) const row = document.querySelector( `[data-testid="table-row-${validIndex}"]`, @@ -78,15 +118,14 @@ const DataTable = ({ useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { + if (!tableRef.current?.contains(document.activeElement) || !isTableFocused) return if ( document.activeElement?.tagName === "INPUT" || document.activeElement?.tagName === "TEXTAREA" || document.activeElement?.tagName === "SELECT" || document.activeElement?.tagName === "BUTTON" - ) { + ) return - } - if (!data.length) return switch (e.key) { @@ -115,19 +154,44 @@ const DataTable = ({ } } - window.addEventListener("keydown", handleKeyDown) - return () => window.removeEventListener("keydown", handleKeyDown) + if (isTableFocused) { + window.addEventListener("keydown", handleKeyDown) + return () => window.removeEventListener("keydown", handleKeyDown) + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [data, focusedRowIndex, onRowClick, navigateTo, isTableFocused]) + + useEffect(() => { + const shouldAutoFocus = data && data.length > 0 && !loading + if (shouldAutoFocus) { + smartFocus() + } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [data, focusedRowIndex, onRowClick, navigateTo]) + }, [data?.length, loading]) useEffect(() => { - if (data.length && focusedRowIndex === -1) { - focusRow(0) + const handleFocusOut = (e: FocusEvent) => { + if (!tableRef.current?.contains(e.relatedTarget as Node)) { + if (isNoFocusActive()) { + smartFocus() + } + } } + + document.addEventListener("focusout", handleFocusOut) + return () => document.removeEventListener("focusout", handleFocusOut) // eslint-disable-next-line react-hooks/exhaustive-deps - }, [data.length]) + }, []) + + useEffect(() => { + return () => { + if (focusTimeoutRef.current) { + clearTimeout(focusTimeoutRef.current) + } + } + }, []) - if (loading && !data) { + if (loading && !data.length) { return isMobile ? (
{Array.from({ length: 5 }).map((_, idx) => ( @@ -257,9 +321,16 @@ const DataTable = ({ return (
setIsTableFocused(true)} + onBlur={(e) => { + if (!tableRef.current?.contains(e.relatedTarget as Node)) { + setIsTableFocused(false) + setFocusedRowIndex(-1) + } + }} > diff --git a/apps/admin-panel/cypress/e2e/user.cy.ts b/apps/admin-panel/cypress/e2e/user.cy.ts index a8ae0abbe..8daaa46df 100644 --- a/apps/admin-panel/cypress/e2e/user.cy.ts +++ b/apps/admin-panel/cypress/e2e/user.cy.ts @@ -21,7 +21,7 @@ describe("Users", () => { cy.takeScreenshot("2_click_create_button") cy.get('[data-testid="create-user-email-input"]') - .type(userEmail) + .type(userEmail, { delay: 0, waitForAnimations: false }) .should("have.value", userEmail) cy.takeScreenshot("3_enter_email")