✨(react) add ref to Select
We encountered a use-case where we needed to blur the select programatically but the component wasn't offering any way to do that.
This commit is contained in:
@@ -1,4 +1,10 @@
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import React, {
|
||||
forwardRef,
|
||||
useEffect,
|
||||
useImperativeHandle,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import { useCombobox } from "downshift";
|
||||
import { useCunningham } from ":/components/Provider";
|
||||
import {
|
||||
@@ -8,119 +14,129 @@ import {
|
||||
SelectMonoAux,
|
||||
SubProps,
|
||||
} from ":/components/Forms/Select/mono-common";
|
||||
import { SelectHandle } from ":/components/Forms/Select/index";
|
||||
|
||||
export const SelectMonoSearchable = (props: SubProps) => {
|
||||
const { t } = useCunningham();
|
||||
const [optionsToDisplay, setOptionsToDisplay] = useState(props.options);
|
||||
const [hasInputFocused, setHasInputFocused] = useState(false);
|
||||
const [inputFilter, setInputFilter] = useState<string>();
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const downshiftReturn = useCombobox({
|
||||
...props.downshiftProps,
|
||||
items: optionsToDisplay,
|
||||
itemToString: optionToString,
|
||||
onInputValueChange: (e) => {
|
||||
setInputFilter(e.inputValue);
|
||||
if (!e.inputValue) {
|
||||
downshiftReturn.selectItem(null);
|
||||
}
|
||||
},
|
||||
});
|
||||
const [labelAsPlaceholder, setLabelAsPlaceholder] = useState(
|
||||
!downshiftReturn.selectedItem,
|
||||
);
|
||||
useEffect(() => {
|
||||
if (hasInputFocused || downshiftReturn.inputValue) {
|
||||
setLabelAsPlaceholder(false);
|
||||
return;
|
||||
}
|
||||
setLabelAsPlaceholder(!downshiftReturn.selectedItem);
|
||||
}, [
|
||||
downshiftReturn.selectedItem,
|
||||
hasInputFocused,
|
||||
downshiftReturn.inputValue,
|
||||
]);
|
||||
|
||||
// When component is controlled, this useEffect will update the local selected item.
|
||||
useEffect(() => {
|
||||
if (inputFilter) {
|
||||
return;
|
||||
}
|
||||
|
||||
const selectedItem = downshiftReturn.selectedItem
|
||||
? optionToValue(downshiftReturn.selectedItem)
|
||||
: undefined;
|
||||
|
||||
const optionToSelect = props.options.find(
|
||||
(option) => optionToValue(option) === props.value,
|
||||
export const SelectMonoSearchable = forwardRef<SelectHandle, SubProps>(
|
||||
(props, ref) => {
|
||||
const { t } = useCunningham();
|
||||
const [optionsToDisplay, setOptionsToDisplay] = useState(props.options);
|
||||
const [hasInputFocused, setHasInputFocused] = useState(false);
|
||||
const [inputFilter, setInputFilter] = useState<string>();
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const downshiftReturn = useCombobox({
|
||||
...props.downshiftProps,
|
||||
items: optionsToDisplay,
|
||||
itemToString: optionToString,
|
||||
onInputValueChange: (e) => {
|
||||
setInputFilter(e.inputValue);
|
||||
if (!e.inputValue) {
|
||||
downshiftReturn.selectItem(null);
|
||||
}
|
||||
},
|
||||
});
|
||||
const [labelAsPlaceholder, setLabelAsPlaceholder] = useState(
|
||||
!downshiftReturn.selectedItem,
|
||||
);
|
||||
useEffect(() => {
|
||||
if (hasInputFocused || downshiftReturn.inputValue) {
|
||||
setLabelAsPlaceholder(false);
|
||||
return;
|
||||
}
|
||||
setLabelAsPlaceholder(!downshiftReturn.selectedItem);
|
||||
}, [
|
||||
downshiftReturn.selectedItem,
|
||||
hasInputFocused,
|
||||
downshiftReturn.inputValue,
|
||||
]);
|
||||
|
||||
// Already selected
|
||||
if (optionToSelect && selectedItem === props.value) {
|
||||
return;
|
||||
}
|
||||
// When component is controlled, this useEffect will update the local selected item.
|
||||
useEffect(() => {
|
||||
if (inputFilter) {
|
||||
return;
|
||||
}
|
||||
|
||||
downshiftReturn.selectItem(optionToSelect ?? null);
|
||||
}, [props.value, props.options, inputFilter]);
|
||||
const selectedItem = downshiftReturn.selectedItem
|
||||
? optionToValue(downshiftReturn.selectedItem)
|
||||
: undefined;
|
||||
|
||||
// Even there is already a value selected, when opening the combobox menu we want to display all available choices.
|
||||
useEffect(() => {
|
||||
if (downshiftReturn.isOpen) {
|
||||
setOptionsToDisplay(
|
||||
inputFilter
|
||||
? props.options.filter(getOptionsFilter(inputFilter))
|
||||
: props.options,
|
||||
const optionToSelect = props.options.find(
|
||||
(option) => optionToValue(option) === props.value,
|
||||
);
|
||||
} else {
|
||||
setInputFilter(undefined);
|
||||
}
|
||||
}, [downshiftReturn.isOpen, props.options, inputFilter]);
|
||||
|
||||
const onInputBlur = () => {
|
||||
setHasInputFocused(false);
|
||||
if (downshiftReturn.selectedItem) {
|
||||
// Here the goal is to make sure that when the input in blurred then the input value
|
||||
// has exactly the selectedItem label. Which is not the case by default.
|
||||
downshiftReturn.selectItem(downshiftReturn.selectedItem);
|
||||
} else {
|
||||
// We want the input to be empty when no item is selected.
|
||||
downshiftReturn.setInputValue("");
|
||||
}
|
||||
};
|
||||
// Already selected
|
||||
if (optionToSelect && selectedItem === props.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
const inputProps = downshiftReturn.getInputProps({
|
||||
ref: inputRef,
|
||||
disabled: props.disabled,
|
||||
});
|
||||
downshiftReturn.selectItem(optionToSelect ?? null);
|
||||
}, [props.value, props.options, inputFilter]);
|
||||
|
||||
return (
|
||||
<SelectMonoAux
|
||||
{...props}
|
||||
downshiftReturn={{
|
||||
...downshiftReturn,
|
||||
wrapperProps: {
|
||||
onClick: () => {
|
||||
inputRef.current?.focus();
|
||||
downshiftReturn.openMenu();
|
||||
// Even there is already a value selected, when opening the combobox menu we want to display all available choices.
|
||||
useEffect(() => {
|
||||
if (downshiftReturn.isOpen) {
|
||||
setOptionsToDisplay(
|
||||
inputFilter
|
||||
? props.options.filter(getOptionsFilter(inputFilter))
|
||||
: props.options,
|
||||
);
|
||||
} else {
|
||||
setInputFilter(undefined);
|
||||
}
|
||||
}, [downshiftReturn.isOpen, props.options, inputFilter]);
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
blur: () => {
|
||||
downshiftReturn.closeMenu();
|
||||
inputRef.current?.blur();
|
||||
},
|
||||
}));
|
||||
|
||||
const onInputBlur = () => {
|
||||
setHasInputFocused(false);
|
||||
if (downshiftReturn.selectedItem) {
|
||||
// Here the goal is to make sure that when the input in blurred then the input value
|
||||
// has exactly the selectedItem label. Which is not the case by default.
|
||||
downshiftReturn.selectItem(downshiftReturn.selectedItem);
|
||||
} else {
|
||||
// We want the input to be empty when no item is selected.
|
||||
downshiftReturn.setInputValue("");
|
||||
}
|
||||
};
|
||||
|
||||
const inputProps = downshiftReturn.getInputProps({
|
||||
ref: inputRef,
|
||||
disabled: props.disabled,
|
||||
});
|
||||
|
||||
return (
|
||||
<SelectMonoAux
|
||||
{...props}
|
||||
downshiftReturn={{
|
||||
...downshiftReturn,
|
||||
wrapperProps: {
|
||||
onClick: () => {
|
||||
inputRef.current?.focus();
|
||||
downshiftReturn.openMenu();
|
||||
},
|
||||
},
|
||||
},
|
||||
toggleButtonProps: downshiftReturn.getToggleButtonProps({
|
||||
disabled: props.disabled,
|
||||
"aria-label": t("components.forms.select.toggle_button_aria_label"),
|
||||
}),
|
||||
}}
|
||||
labelAsPlaceholder={labelAsPlaceholder}
|
||||
options={optionsToDisplay}
|
||||
>
|
||||
<input
|
||||
{...inputProps}
|
||||
onFocus={() => {
|
||||
setHasInputFocused(true);
|
||||
toggleButtonProps: downshiftReturn.getToggleButtonProps({
|
||||
disabled: props.disabled,
|
||||
"aria-label": t("components.forms.select.toggle_button_aria_label"),
|
||||
}),
|
||||
}}
|
||||
onBlur={() => {
|
||||
onInputBlur();
|
||||
}}
|
||||
/>
|
||||
</SelectMonoAux>
|
||||
);
|
||||
};
|
||||
labelAsPlaceholder={labelAsPlaceholder}
|
||||
options={optionsToDisplay}
|
||||
>
|
||||
<input
|
||||
{...inputProps}
|
||||
onFocus={() => {
|
||||
setHasInputFocused(true);
|
||||
}}
|
||||
onBlur={() => {
|
||||
onInputBlur();
|
||||
}}
|
||||
/>
|
||||
</SelectMonoAux>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user