From cbd209ababf962e2ae61200b775fe0841cb52bb9 Mon Sep 17 00:00:00 2001 From: Elenor Date: Wed, 23 Oct 2024 10:45:14 -0700 Subject: [PATCH] :bug: fix: improve dropdown focus handling, tag keyboard events --- src/components/Tag/Tag.tsx | 20 +++++- src/compositions/Dropdown/PartialDropdown.tsx | 6 +- .../Dropdown/stories/Dropdown.docs.mdx | 70 +++++++++++++++++++ 3 files changed, 92 insertions(+), 4 deletions(-) diff --git a/src/components/Tag/Tag.tsx b/src/components/Tag/Tag.tsx index 16cbcf9b..9494b0d2 100644 --- a/src/components/Tag/Tag.tsx +++ b/src/components/Tag/Tag.tsx @@ -1,5 +1,5 @@ import { cx } from '@emotion/css'; -import React from 'react'; +import React, { useCallback } from 'react'; import { AsReactType, MergeElementPropsWithoutRef, ThemeProps } from '../../types'; import { useThemeId } from '../../context/Theme'; import styles from './styles/Tag.module.css'; @@ -22,6 +22,7 @@ export type LocalTagProps = ThemeProps & { focused?: boolean; /** Manually apply the hover styles; this does not affect :hover */ hovered?: boolean; + onClick?: (event: React.MouseEvent | React.KeyboardEvent | React.TouchEvent) => void; shape?: TagShape; size?: TagSize; style?: React.CSSProperties; @@ -41,6 +42,7 @@ export function TagBase( focused = false, flush = false, hovered = false, + onClick, shape = 'pill', size = 'small', themeId: initThemeId, @@ -54,6 +56,20 @@ export function TagBase( const color = contrast ? 'contrast' : 'primary'; const element = as || (actionable ? 'button' : 'div'); + const handleKeyDown = useCallback( + (event: React.KeyboardEvent) => { + if (onClick) { + if (event.key === ' ' || event.key === 'Enter') { + event.preventDefault(); + event.stopPropagation(); + + onClick(event); + } + } + }, + [onClick], + ); + return React.createElement( element, { @@ -71,6 +87,8 @@ export function TagBase( weight && styles[`tag--${weight}`], className, ), + onClick, + onKeyDown: handleKeyDown, ref: forwardedRef, role: actionable ? 'button' : undefined, tabIndex: actionable ? 0 : undefined, diff --git a/src/compositions/Dropdown/PartialDropdown.tsx b/src/compositions/Dropdown/PartialDropdown.tsx index 89504035..1f1be9d4 100644 --- a/src/compositions/Dropdown/PartialDropdown.tsx +++ b/src/compositions/Dropdown/PartialDropdown.tsx @@ -244,7 +244,7 @@ export function PartialDropdownBase( }); const { addRef, clearRefs, handleFocus, handleBlur, isIdFocused } = useDeepFocusGroup({}, { blurDelay: 150 }); - const { focus, cancel } = useTriggerFocus({ focusDelay: 150 }); + const { focus, cancel } = useTriggerFocus({ focusDelay: 0 }); const isClearFocused = isIdFocused(FOCUS_REFS.CLEAR); const isContainerFocused = isIdFocused(FOCUS_REFS.CONTAINER); @@ -404,7 +404,7 @@ export function PartialDropdownBase( mouseDownRef.current = { isDropdownVisible, - isListClicked: listRef.current?.container?.contains(event.target as Node), + isListClicked: listRef.current?.container.contains(event.target as Node), }; }, [isDropdownVisible], @@ -477,7 +477,7 @@ export function PartialDropdownBase( // if the input wasn't part of the form or the clear button then show and focus the list case 'Enter': case ' ': - if (!isListFocused && !isClearFocused && !isInputTarget) { + if (!isClearFocused && !isInputTarget) { event.preventDefault(); event.stopPropagation(); dropdownDispatch({ type: ACTIONS.SHOW_DROPDOWN }); diff --git a/src/compositions/Dropdown/stories/Dropdown.docs.mdx b/src/compositions/Dropdown/stories/Dropdown.docs.mdx index 4609dfee..a857d782 100644 --- a/src/compositions/Dropdown/stories/Dropdown.docs.mdx +++ b/src/compositions/Dropdown/stories/Dropdown.docs.mdx @@ -186,6 +186,76 @@ export const dropdownContainerStyle = { +### Multi-select filterable dropdown + + + + ( +
+ +
+ )} + /> +
+
+ +### Contained filterable dropdown + + + + ( +
+ +
+ )} + /> +
+
+ +### Multi-select contained filterable dropdown + + + + ( +
+ +
+ )} + /> +
+
+ ### Searchable dropdown _A searchable dropdown is similar to a filterable dropdown except that it starts with no initial values._