Skip to content

Commit a460b28

Browse files
andreancardonaalisonjosephfrancineluccakodiakhq[bot]guidari
authored
fix: dropdown contrast focus styles (#14017)
* feat: contained list persistent search * docs: update docs for contained list search * fix: fix border * feat: functionality working * fix(tabs): hover style bugs * fix: remove hover from disabled state * feat: clean up code * Update packages/react/src/components/ContainedList/ContainedList.mdx Co-authored-by: Francine Lucca <40550942+francinelucca@users.noreply.github.com> * fix: update docs * fix: fix styling Co-authored-by: Francine Lucca <francinelucca@users.noreply.github.com> * fix: update docs * fix: failing test * fix: fix tests * fix: list box keyboard focus * fix: implement highlighted index * fix: format * added aria-owns for VO on FireFox * fix: fix failing test --------- Co-authored-by: Alison Joseph <alison.joseph@us.ibm.com> Co-authored-by: Francine Lucca <40550942+francinelucca@users.noreply.github.com> Co-authored-by: Francine Lucca <francinelucca@users.noreply.github.com> Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> Co-authored-by: Francine Lucca <francinelucca@hotmail.com> Co-authored-by: Guilherme Datilio Ribeiro <guilhermedatilio@gmail.com> Co-authored-by: TJ Egan <tw15egan@gmail.com>
1 parent 86eb048 commit a460b28

3 files changed

Lines changed: 95 additions & 32 deletions

File tree

packages/react/src/components/ComboBox/ComboBox.tsx

Lines changed: 48 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,15 @@ import deprecate from '../../prop-types/deprecate';
2828
import { usePrefix } from '../../internal/usePrefix';
2929
import { FormContext } from '../FluidForm';
3030

31+
const {
32+
keyDownArrowDown,
33+
keyDownArrowUp,
34+
keyDownEscape,
35+
clickButton,
36+
blurButton,
37+
changeInput,
38+
} = Downshift.stateChangeTypes;
39+
3140
const defaultItemToString = (item) => {
3241
if (typeof item === 'string') {
3342
return item;
@@ -302,6 +311,7 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => {
302311
const [isFocused, setIsFocused] = useState(false);
303312
const [prevSelectedItem, setPrevSelectedItem] = useState<any>();
304313
const [doneInitialSelectedItem, setDoneInitialSelectedItem] = useState(false);
314+
const [highlightedIndex, setHighlightedIndex] = useState<number>();
305315
const savedOnInputChange = useRef(onInputChange);
306316

307317
if (!doneInitialSelectedItem || prevSelectedItem !== selectedItem) {
@@ -354,19 +364,43 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => {
354364
}
355365
};
356366

357-
const handleOnStateChange = (newState, { setHighlightedIndex }) => {
358-
if (Object.prototype.hasOwnProperty.call(newState, 'inputValue')) {
359-
const { inputValue } = newState;
367+
const getHighlightedIndex = (changes) => {
368+
if (Object.prototype.hasOwnProperty.call(changes, 'inputValue')) {
369+
const { inputValue } = changes;
360370
const filteredItems = filterItems(items, itemToString, inputValue);
361-
setHighlightedIndex(
362-
findHighlightedIndex(
363-
{
364-
...props,
365-
items: filteredItems,
366-
},
367-
inputValue
368-
)
371+
const indexToHighlight = findHighlightedIndex(
372+
{
373+
...props,
374+
items: filteredItems,
375+
},
376+
inputValue
369377
);
378+
setHighlightedIndex(indexToHighlight);
379+
return indexToHighlight;
380+
}
381+
return highlightedIndex;
382+
};
383+
384+
const handleOnStateChange = (
385+
changes,
386+
{ setHighlightedIndex: updateHighlightedIndex }
387+
) => {
388+
const { type } = changes;
389+
switch (type) {
390+
case keyDownArrowDown:
391+
case keyDownArrowUp:
392+
setHighlightedIndex(changes.highlightedIndex);
393+
break;
394+
case blurButton:
395+
case keyDownEscape:
396+
setHighlightedIndex(changes.highlightedIndex);
397+
break;
398+
case clickButton:
399+
setHighlightedIndex(changes.highlightedIndex);
400+
break;
401+
case changeInput:
402+
updateHighlightedIndex(getHighlightedIndex(changes));
403+
break;
370404
}
371405
};
372406

@@ -412,6 +446,7 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => {
412446

413447
// needs to be Capitalized for react to render it correctly
414448
const ItemToElement = itemToElement;
449+
415450
return (
416451
<Downshift
417452
{...downshiftProps}
@@ -436,7 +471,6 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => {
436471
isOpen,
437472
inputValue,
438473
selectedItem,
439-
highlightedIndex,
440474
clearSelection,
441475
toggleMenu,
442476
}) => {
@@ -549,6 +583,7 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => {
549583
aria-expanded={rootProps['aria-expanded']}
550584
aria-haspopup="listbox"
551585
aria-controls={inputProps['aria-controls']}
586+
aria-owns={getMenuProps().id}
552587
title={textInput?.current?.value}
553588
{...inputProps}
554589
{...rest}
@@ -606,12 +641,7 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => {
606641
<ListBox.MenuItem
607642
key={itemProps.id}
608643
isActive={selectedItem === item}
609-
isHighlighted={
610-
highlightedIndex === index ||
611-
((selectedItem as any)?.id &&
612-
(selectedItem as any)?.id === item.id) ||
613-
false
614-
}
644+
isHighlighted={highlightedIndex === index}
615645
title={
616646
itemToElement
617647
? item.text

packages/react/src/components/Dropdown/Dropdown.tsx

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,13 @@ import React, {
1414
MouseEvent,
1515
ReactNode,
1616
} from 'react';
17-
import { useSelect, UseSelectProps, UseSelectState } from 'downshift';
17+
import {
18+
useSelect,
19+
UseSelectInterface,
20+
UseSelectProps,
21+
UseSelectState,
22+
UseSelectStateChangeTypes,
23+
} from 'downshift';
1824
import cx from 'classnames';
1925
import PropTypes from 'prop-types';
2026
import {
@@ -36,6 +42,16 @@ import setupGetInstanceId from '../../tools/setupGetInstanceId';
3642

3743
const getInstanceId = setupGetInstanceId();
3844

45+
const {
46+
MenuBlur,
47+
MenuKeyDownArrowDown,
48+
MenuKeyDownArrowUp,
49+
MenuKeyDownEscape,
50+
ToggleButtonClick,
51+
} = useSelect.stateChangeTypes as UseSelectInterface['stateChangeTypes'] & {
52+
ToggleButtonClick: UseSelectStateChangeTypes.ToggleButtonClick;
53+
};
54+
3955
const defaultItemToString = <ItemType,>(item?: ItemType): string => {
4056
if (typeof item === 'string') {
4157
return item;
@@ -242,16 +258,36 @@ const Dropdown = React.forwardRef(
242258
ref: ForwardedRef<HTMLButtonElement>
243259
) => {
244260
const prefix = usePrefix();
261+
const [highlightedIndex, setHighlightedIndex] = useState();
245262
const { isFluid } = useContext(FormContext);
246263
const selectProps: UseSelectProps<ItemType> = {
247264
...downshiftProps,
248265
items,
249266
itemToString,
267+
highlightedIndex,
250268
initialSelectedItem,
251269
onSelectedItemChange,
270+
onStateChange,
252271
};
253272
const { current: dropdownInstanceId } = useRef(getInstanceId());
254273

274+
function onStateChange(changes) {
275+
const { type } = changes;
276+
switch (type) {
277+
case MenuKeyDownArrowDown:
278+
case MenuKeyDownArrowUp:
279+
setHighlightedIndex(changes.highlightedIndex);
280+
break;
281+
case MenuBlur:
282+
case MenuKeyDownEscape:
283+
setHighlightedIndex(changes.highlightedIndex);
284+
break;
285+
case ToggleButtonClick:
286+
setHighlightedIndex(changes.highlightedIndex);
287+
break;
288+
}
289+
}
290+
255291
// only set selectedItem if the prop is defined. Setting if it is undefined
256292
// will overwrite default selected items from useSelect
257293
if (controlledSelectedItem !== undefined) {
@@ -264,7 +300,6 @@ const Dropdown = React.forwardRef(
264300
getLabelProps,
265301
getMenuProps,
266302
getItemProps,
267-
highlightedIndex,
268303
selectedItem,
269304
} = useSelect(selectProps);
270305
const inline = type === 'inline';
@@ -364,7 +399,6 @@ const Dropdown = React.forwardRef(
364399
}
365400
: {
366401
onKeyDown: (evt: React.KeyboardEvent<HTMLButtonElement>) => {
367-
console.log('typing should be false', isTyping);
368402
if (
369403
evt.code !== 'Space' ||
370404
!['ArrowDown', 'ArrowUp', ' ', 'Enter'].includes(evt.key)
@@ -376,7 +410,6 @@ const Dropdown = React.forwardRef(
376410
(isTyping && evt.code === 'Space') ||
377411
!['ArrowDown', 'ArrowUp', ' ', 'Enter'].includes(evt.key)
378412
) {
379-
console.log(evt.key);
380413
if (evt.code === 'Space') {
381414
evt.preventDefault();
382415
return;
@@ -476,9 +509,7 @@ const Dropdown = React.forwardRef(
476509
<ListBox.MenuItem
477510
key={itemProps.id}
478511
isActive={selectedItem === item}
479-
isHighlighted={
480-
highlightedIndex === index || selectedItem === item
481-
}
512+
isHighlighted={highlightedIndex === index}
482513
title={title}
483514
ref={{
484515
menuItemOptionRef: menuItemOptionRefs.current[index],

packages/styles/scss/components/list-box/_list-box.scss

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -510,6 +510,7 @@ $list-box-menu-width: rem(300px);
510510
z-index: z('dropdown');
511511
right: 0;
512512
left: 0;
513+
display: none;
513514
width: $list-box-width;
514515
background-color: $layer;
515516
overflow-y: auto;
@@ -521,11 +522,12 @@ $list-box-menu-width: rem(300px);
521522
}
522523
}
523524

524-
.#{$prefix}--list-box
525-
.#{$prefix}--list-box__field[aria-expanded='false']
526-
+ .#{$prefix}--list-box__menu {
527-
display: none;
528-
max-height: 0;
525+
.#{$prefix}--list-box .#{$prefix}--list-box__field[aria-expanded='false'] {
526+
.#{$prefix}--list-box__menu {
527+
display: none;
528+
max-height: 0;
529+
visibility: hidden;
530+
}
529531
}
530532

531533
.#{$prefix}--list-box--expanded .#{$prefix}--list-box__menu {
@@ -768,8 +770,8 @@ $list-box-menu-width: rem(300px);
768770
}
769771

770772
.#{$prefix}--list-box__menu-item--highlighted {
771-
border-color: transparent;
772-
background-color: $layer-selected;
773+
@include focus-outline('outline');
774+
773775
color: $text-primary;
774776
}
775777

0 commit comments

Comments
 (0)