Skip to content

Commit d4093a6

Browse files
authored
🐛 fix: improve dropdown focus handling, tag keyboard events (#124)
1 parent eba6d75 commit d4093a6

File tree

3 files changed

+92
-4
lines changed

3 files changed

+92
-4
lines changed

src/components/Tag/Tag.tsx

+19-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { cx } from '@emotion/css';
2-
import React from 'react';
2+
import React, { useCallback } from 'react';
33
import { AsReactType, MergeElementPropsWithoutRef, ThemeProps } from '../../types';
44
import { useThemeId } from '../../context/Theme';
55
import styles from './styles/Tag.module.css';
@@ -22,6 +22,7 @@ export type LocalTagProps = ThemeProps & {
2222
focused?: boolean;
2323
/** Manually apply the hover styles; this does not affect :hover */
2424
hovered?: boolean;
25+
onClick?: (event: React.MouseEvent | React.KeyboardEvent | React.TouchEvent) => void;
2526
shape?: TagShape;
2627
size?: TagSize;
2728
style?: React.CSSProperties;
@@ -41,6 +42,7 @@ export function TagBase<T extends TagElementType = 'div'>(
4142
focused = false,
4243
flush = false,
4344
hovered = false,
45+
onClick,
4446
shape = 'pill',
4547
size = 'small',
4648
themeId: initThemeId,
@@ -54,6 +56,20 @@ export function TagBase<T extends TagElementType = 'div'>(
5456
const color = contrast ? 'contrast' : 'primary';
5557
const element = as || (actionable ? 'button' : 'div');
5658

59+
const handleKeyDown = useCallback(
60+
(event: React.KeyboardEvent) => {
61+
if (onClick) {
62+
if (event.key === ' ' || event.key === 'Enter') {
63+
event.preventDefault();
64+
event.stopPropagation();
65+
66+
onClick(event);
67+
}
68+
}
69+
},
70+
[onClick],
71+
);
72+
5773
return React.createElement(
5874
element,
5975
{
@@ -71,6 +87,8 @@ export function TagBase<T extends TagElementType = 'div'>(
7187
weight && styles[`tag--${weight}`],
7288
className,
7389
),
90+
onClick,
91+
onKeyDown: handleKeyDown,
7492
ref: forwardedRef,
7593
role: actionable ? 'button' : undefined,
7694
tabIndex: actionable ? 0 : undefined,

src/compositions/Dropdown/PartialDropdown.tsx

+3-3
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,7 @@ export function PartialDropdownBase(
244244
});
245245

246246
const { addRef, clearRefs, handleFocus, handleBlur, isIdFocused } = useDeepFocusGroup({}, { blurDelay: 150 });
247-
const { focus, cancel } = useTriggerFocus({ focusDelay: 150 });
247+
const { focus, cancel } = useTriggerFocus({ focusDelay: 0 });
248248

249249
const isClearFocused = isIdFocused(FOCUS_REFS.CLEAR);
250250
const isContainerFocused = isIdFocused(FOCUS_REFS.CONTAINER);
@@ -404,7 +404,7 @@ export function PartialDropdownBase(
404404

405405
mouseDownRef.current = {
406406
isDropdownVisible,
407-
isListClicked: listRef.current?.container?.contains(event.target as Node),
407+
isListClicked: listRef.current?.container.contains(event.target as Node),
408408
};
409409
},
410410
[isDropdownVisible],
@@ -477,7 +477,7 @@ export function PartialDropdownBase(
477477
// if the input wasn't part of the form or the clear button then show and focus the list
478478
case 'Enter':
479479
case ' ':
480-
if (!isListFocused && !isClearFocused && !isInputTarget) {
480+
if (!isClearFocused && !isInputTarget) {
481481
event.preventDefault();
482482
event.stopPropagation();
483483
dropdownDispatch({ type: ACTIONS.SHOW_DROPDOWN });

src/compositions/Dropdown/stories/Dropdown.docs.mdx

+70
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,76 @@ export const dropdownContainerStyle = {
186186
</Flex>
187187
</Canvas>
188188

189+
### Multi-select filterable dropdown
190+
191+
<Canvas>
192+
<Flex full wrap direction="row" justifyContent="space-between">
193+
<Looper
194+
list={variants}
195+
render={variant => (
196+
<div style={dropdownContainerStyle}>
197+
<Dropdown
198+
transitional
199+
getFilteredOptions={handleFilter}
200+
initialSelected={[options[3]]}
201+
inputVariant={variant}
202+
label="Super awesome dropdown"
203+
maxSelect={-1}
204+
options={options}
205+
/>
206+
</div>
207+
)}
208+
/>
209+
</Flex>
210+
</Canvas>
211+
212+
### Contained filterable dropdown
213+
214+
<Canvas>
215+
<Flex full wrap direction="row" justifyContent="space-between">
216+
<Looper
217+
list={variants}
218+
render={variant => (
219+
<div style={dropdownContainerStyle}>
220+
<Dropdown
221+
transitional
222+
getFilteredOptions={handleFilter}
223+
initialSelected={[options[3]]}
224+
inputVariant={variant}
225+
label="Super awesome dropdown"
226+
layout="contained"
227+
options={options}
228+
/>
229+
</div>
230+
)}
231+
/>
232+
</Flex>
233+
</Canvas>
234+
235+
### Multi-select contained filterable dropdown
236+
237+
<Canvas>
238+
<Flex full wrap direction="row" justifyContent="space-between">
239+
<Looper
240+
list={variants}
241+
render={variant => (
242+
<div style={dropdownContainerStyle}>
243+
<Dropdown
244+
transitional
245+
getFilteredOptions={handleFilter}
246+
initialSelected={[options[3]]}
247+
inputVariant={variant}
248+
label="Super awesome dropdown"
249+
layout="contained"
250+
maxSelect={-1}
251+
options={options}
252+
/>
253+
</div>
254+
)}
255+
/>
256+
</Flex>
257+
</Canvas>
258+
189259
### Searchable dropdown
190260

191261
_A searchable dropdown is similar to a filterable dropdown except that it starts with no initial values._

0 commit comments

Comments
 (0)