From 5ebbc6cab01e6e669d19f064aaba8dbec8b60886 Mon Sep 17 00:00:00 2001 From: dgreif Date: Tue, 15 Jun 2021 08:20:52 -0700 Subject: [PATCH 01/12] feat(useFocusZone): update active descendant on mousemove --- src/ActionList/Item.tsx | 25 ++++--- src/ActionList/List.tsx | 8 ++- src/FilteredActionList/FilteredActionList.tsx | 22 ++---- .../__snapshots__/ActionMenu.tsx.snap | 4 +- .../__snapshots__/AnchoredOverlay.tsx.snap | 4 +- .../__snapshots__/DropdownMenu.tsx.snap | 4 +- .../__snapshots__/SelectPanel.tsx.snap | 4 +- src/__tests__/behaviors/focusZone.tsx | 45 ++++++++++--- src/behaviors/focusZone.ts | 67 +++++++++++++++---- 9 files changed, 125 insertions(+), 58 deletions(-) diff --git a/src/ActionList/Item.tsx b/src/ActionList/Item.tsx index 46d9dd521c4..e250f4bf6f4 100644 --- a/src/ActionList/Item.tsx +++ b/src/ActionList/Item.tsx @@ -8,7 +8,11 @@ import styled from 'styled-components' import {StyledHeader} from './Header' import {StyledDivider} from './Divider' import {useColorSchemeVar, useTheme} from '../ThemeProvider' -import {uniqueId} from '../utils/uniqueId' +import { + activeDescendantActivatedDirectly, + activeDescendantActivatedIndirectly, + isActiveDescendantAttribute +} from '../behaviors/focusZone' /** * These colors are not yet in our default theme. Need to remove this once they are added. @@ -121,8 +125,6 @@ export interface ItemProps extends Omit, ' id?: number | string } -export const itemActiveDescendantClass = `${uniqueId()}active-descendant` - const getItemVariant = (variant = 'default', disabled?: boolean) => { if (disabled) { return { @@ -212,16 +214,23 @@ const StyledItem = styled.div< // '*' instead of '&' because '&' maps to separate class names depending on 'variant' :focus + * ${StyledItemContent}::before, // - above Active Descendent - &.${itemActiveDescendantClass} ${StyledItemContent}::before, + &[${isActiveDescendantAttribute}] ${StyledItemContent}::before, // - below Active Descendent - .${itemActiveDescendantClass} + & ${StyledItemContent}::before { + [${isActiveDescendantAttribute}] + & ${StyledItemContent}::before { // '!important' because all the ':not's above give higher specificity border-color: transparent !important; } - // Focused OR Active Descendant - &:focus, - &.${itemActiveDescendantClass} { + // Active Descendant + &[${isActiveDescendantAttribute}='${activeDescendantActivatedDirectly}'] { + background: ${({focusBackground}) => focusBackground}; + } + &[${isActiveDescendantAttribute}='${activeDescendantActivatedIndirectly}'] { + background: ${({hoverBackground}) => hoverBackground}; + } + + // Focused + &:focus { background: ${({focusBackground}) => focusBackground}; outline: none; } diff --git a/src/ActionList/List.tsx b/src/ActionList/List.tsx index b9ddc67408a..04e915a709d 100644 --- a/src/ActionList/List.tsx +++ b/src/ActionList/List.tsx @@ -132,7 +132,7 @@ function useListVariant(variant: ListProps['variant'] = 'inset'): { /** * Lists `Item`s, either grouped or ungrouped, with a `Divider` between each `Group`. */ -export function List(props: ListProps): JSX.Element { +export const List = React.forwardRef((props, forwardedRef): JSX.Element => { // Get `sx` prop values for `List` children matching the given `List` style variation. const {firstGroupStyle, lastGroupStyle, headerStyle, itemStyle} = useListVariant(props.variant) @@ -216,7 +216,7 @@ export function List(props: ListProps): JSX.Element { } return ( - + {groups.map(({header, ...groupProps}, index) => { const hasFilledHeader = header?.variant === 'filled' const shouldShowDivider = index > 0 && !hasFilledHeader @@ -242,4 +242,6 @@ export function List(props: ListProps): JSX.Element { })} ) -} +}) + +List.displayName = 'ActionList' diff --git a/src/FilteredActionList/FilteredActionList.tsx b/src/FilteredActionList/FilteredActionList.tsx index ca471baaffe..c667389e202 100644 --- a/src/FilteredActionList/FilteredActionList.tsx +++ b/src/FilteredActionList/FilteredActionList.tsx @@ -7,7 +7,6 @@ import {ActionList} from '../ActionList' import Spinner from '../Spinner' import {useFocusZone} from '../hooks/useFocusZone' import {uniqueId} from '../utils/uniqueId' -import {itemActiveDescendantClass} from '../ActionList/Item' import {useProvidedStateOrCreate} from '../hooks/useProvidedStateOrCreate' import styled from 'styled-components' import {get} from '../constants' @@ -71,6 +70,7 @@ export function FilteredActionList({ [onFilterChange, setInternalFilterValue] ) + const scrollContainerRef = useRef(null) const listContainerRef = useRef(null) const inputRef = useProvidedRefOrCreate(providedInputRef) const activeDescendantRef = useRef() @@ -100,19 +100,11 @@ export function FilteredActionList({ return true }, activeDescendantFocus: inputRef, - onActiveDescendantChanged: (current, previous) => { + onActiveDescendantChanged: (current, previous, activatedByKeyboardNavigation) => { activeDescendantRef.current = current - if (previous) { - previous.classList.remove(itemActiveDescendantClass) - } - - if (current) { - current.classList.add(itemActiveDescendantClass) - - if (listContainerRef.current) { - scrollIntoViewingArea(current, listContainerRef.current) - } + if (current && scrollContainerRef.current && activatedByKeyboardNavigation) { + scrollIntoViewingArea(current, scrollContainerRef.current) } } }) @@ -124,7 +116,7 @@ export function FilteredActionList({ } }, [items]) - useScrollFlash(listContainerRef) + useScrollFlash(scrollContainerRef) return ( @@ -143,13 +135,13 @@ export function FilteredActionList({ {...textInputProps} /> - + {loading ? ( ) : ( - + )} diff --git a/src/__tests__/__snapshots__/ActionMenu.tsx.snap b/src/__tests__/__snapshots__/ActionMenu.tsx.snap index 7db066bfa39..a92e9626f63 100644 --- a/src/__tests__/__snapshots__/ActionMenu.tsx.snap +++ b/src/__tests__/__snapshots__/ActionMenu.tsx.snap @@ -70,9 +70,9 @@ exports[`ActionMenu renders consistently 1`] = `