Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

keyboard navigation improvements to select component #89

Merged
merged 1 commit into from
Dec 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 57 additions & 6 deletions src/form/Select/Select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,11 @@ export interface SelectProps {
*/
clearable?: boolean;

/**
* Whether you can use the Tab key to select the current active option.
*/
tabToSelect?: boolean;

/**
* Whether the select is in loading state or not.
*/
Expand Down Expand Up @@ -188,6 +193,7 @@ export const Select: FC<Partial<SelectProps>> = ({
name,
autoFocus,
clearable,
tabToSelect,
filterable,
menuPlacement,
closeOnSelect,
Expand Down Expand Up @@ -245,6 +251,16 @@ export const Select: FC<Partial<SelectProps>> = ({
}
);

// If a keyword is used to filter options, automatically
// highlight the first option for easy selection
useEffect(() => {
if (keyword && keyword.length > 0) {
if (index === -1 || !result[index]) {
setIndex(0);
}
}
}, [keyword, index, setIndex, result]);

const groups = useMemo(() => getGroups(result), [result]);

const selectedOption: SelectValue = useMemo(() => {
Expand All @@ -269,7 +285,7 @@ export const Select: FC<Partial<SelectProps>> = ({
}, [internalValue, updateMenuWidth]);

useEffect(() => {
// This is needed to alllow a select to have a
// This is needed to allow a select to have a
// starting variable that is set from state
if (!isEqual(value, internalValue)) {
setInternalValue(value);
Expand Down Expand Up @@ -442,7 +458,12 @@ export const Select: FC<Partial<SelectProps>> = ({
}

setInternalValue(newValue);
resetInput();

// keep current index if allowing multiple selections
// unless a search keyword was used to select
if (!multiple || keyword) {
resetInput();
}
onChange?.(newValue);
},
[
Expand All @@ -469,7 +490,8 @@ export const Select: FC<Partial<SelectProps>> = ({
if (index > -1 || createable) {
let newSelection;

if (createable) {
const hasSelection = index > -1 && result[index];
if (createable && !hasSelection) {
newSelection = {
value: inputValue,
children: inputValue
Expand All @@ -478,12 +500,37 @@ export const Select: FC<Partial<SelectProps>> = ({
newSelection = result[index];
}

toggleSelectedOption(newSelection);
if (newSelection) {
toggleSelectedOption(newSelection);
}
}
},
[createable, index, result, toggleSelectedOption]
);

const onTabKeyDown = useCallback(
(event: React.KeyboardEvent<HTMLInputElement>) => {
const inputElement = event.target as HTMLInputElement;
const inputValue = inputElement.value.trim();

if (event.shiftKey) {
setOpen(false);
return;
}

if (index > -1 || (createable && inputValue)) {
onEnterKeyUp(event);
}

if (multiple) {
event.preventDefault();
} else {
setOpen(false);
}
},
[index, onEnterKeyUp, setOpen, multiple, createable]
);

const onInputKeyedUp = useCallback(
(event: React.KeyboardEvent<HTMLInputElement>) => {
const key = event.key;
Expand All @@ -507,12 +554,16 @@ export const Select: FC<Partial<SelectProps>> = ({
(event: React.KeyboardEvent<HTMLInputElement>) => {
const key = event.key;
if (key === 'Tab') {
setOpen(false);
if (tabToSelect) {
onTabKeyDown(event);
} else {
setOpen(false);
}
}

onInputKeydown?.(event);
},
[onInputKeydown]
[onInputKeydown, onTabKeyDown, tabToSelect]
);

const onInputBlured = useCallback(
Expand Down
18 changes: 18 additions & 0 deletions src/form/Select/SingleSelect.story.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -539,3 +539,21 @@ export const Createable = () => {
</div>
);
};

export const TabToSelect = () => {
const [value, setValue] = useState<string | null>(null);
return (
<div style={{ width: 300 }}>
<Select
placeholder="Select a category..."
value={value}
onChange={v => setValue(v)}
tabToSelect
>
<SelectOption value="facebook">facebook</SelectOption>
<SelectOption value="twitter">twitter</SelectOption>
<SelectOption value="twitch">twitch</SelectOption>
</Select>
</div>
);
};
Loading