feat: Allow dropdown to add new values when they don't exist in options list (#7641)
* 🔧 (dropdownComponent/index.tsx): refactor filteredOptions state initialization to include custom values not in validOptions ♻️ (dropdownComponent/index.tsx): refactor value memoization logic to handle custom values and improve performance 🔧 (dropdownComponent/index.tsx): refactor filteredOptions state update logic to handle custom values and improve user experience * 📝 (RenderInputParameters/index.tsx): Remove unnecessary console.log statement 🔧 (dropdownComponent/index.tsx): Add constant RECEIVING_INPUT_VALUE and update styles for disabled state in Dropdown component * ✨ (dropdownComponent/index.tsx): add new constant SELECT_AN_OPTION to improve user experience by providing a default option when no value is selected. * ✨ (constants.ts): add constant SELECT_AN_OPTION to improve user experience by providing a clear message to select an option --------- Co-authored-by: deon-sanchez <deon.sanchez@datastax.com> Co-authored-by: Edwin Jose <edwin.jose@datastax.com>
This commit is contained in:
parent
a8ae17b86d
commit
f213f487a6
2 changed files with 67 additions and 20 deletions
|
|
@ -1,4 +1,5 @@
|
|||
import LoadingTextComponent from "@/components/common/loadingTextComponent";
|
||||
import { RECEIVING_INPUT_VALUE, SELECT_AN_OPTION } from "@/constants/constants";
|
||||
import { usePostTemplateValue } from "@/controllers/API/queries/nodes/use-post-template-value";
|
||||
import NodeDialog from "@/CustomNodes/GenericNode/components/NodeDialogComponent";
|
||||
import { mutateTemplate } from "@/CustomNodes/helpers/mutate-template";
|
||||
|
|
@ -54,7 +55,6 @@ export default function Dropdown({
|
|||
dialogInputs,
|
||||
handleOnNewValue,
|
||||
toggle,
|
||||
hasRefreshButton,
|
||||
...baseInputProps
|
||||
}: BaseInputProps & DropDownComponent): JSX.Element {
|
||||
const validOptions = useMemo(
|
||||
|
|
@ -66,12 +66,20 @@ export default function Dropdown({
|
|||
const [open, setOpen] = useState(children ? true : false);
|
||||
const [openDialog, setOpenDialog] = useState(false);
|
||||
const [customValue, setCustomValue] = useState("");
|
||||
const [filteredOptions, setFilteredOptions] = useState(validOptions);
|
||||
const [filteredOptions, setFilteredOptions] = useState(() => {
|
||||
// Include the current value in filteredOptions if it's a custom value not in validOptions
|
||||
if (value && !validOptions.includes(value)) {
|
||||
return [...validOptions, value];
|
||||
}
|
||||
return validOptions;
|
||||
});
|
||||
const [filteredMetadata, setFilteredMetadata] = useState(optionsMetaData);
|
||||
const [refreshOptions, setRefreshOptions] = useState(false);
|
||||
const refButton = useRef<HTMLButtonElement>(null);
|
||||
|
||||
value = useMemo(() => {
|
||||
// We should only reset the value if it's not in options and not in filteredOptions
|
||||
// and not a recently added custom value
|
||||
if (!options.includes(value) && !filteredOptions.includes(value)) {
|
||||
return null;
|
||||
}
|
||||
|
|
@ -112,24 +120,50 @@ export default function Dropdown({
|
|||
|
||||
if (!value) {
|
||||
// If search is cleared, show all options
|
||||
setFilteredOptions(validOptions);
|
||||
// Preserve any custom values that were in filteredOptions
|
||||
const customValuesInFiltered = filteredOptions.filter(
|
||||
(option) => !validOptions.includes(option),
|
||||
);
|
||||
setFilteredOptions([...validOptions, ...customValuesInFiltered]);
|
||||
setFilteredMetadata(optionsMetaData);
|
||||
return;
|
||||
}
|
||||
|
||||
// Search existing options
|
||||
const searchValues = fuse.search(value);
|
||||
const filtered = searchValues.map((search) => search.item);
|
||||
let filtered = searchValues.map((search) => search.item);
|
||||
|
||||
// If the search value exactly matches one of the custom options, include it
|
||||
const customOptions = filteredOptions.filter(
|
||||
(option) => !validOptions.includes(option),
|
||||
);
|
||||
const matchingCustomOption = customOptions.find(
|
||||
(option) => option.toLowerCase() === value.toLowerCase(),
|
||||
);
|
||||
|
||||
// Include matching custom options or allow adding the current search if combobox is true
|
||||
if (matchingCustomOption && !filtered.includes(matchingCustomOption)) {
|
||||
filtered.push(matchingCustomOption);
|
||||
} else if (
|
||||
combobox &&
|
||||
value &&
|
||||
!filtered.some((opt) => opt.toLowerCase() === value.toLowerCase())
|
||||
) {
|
||||
// If combobox is enabled and we're typing a new value, include it in the filtered list
|
||||
filtered = [value, ...filtered];
|
||||
}
|
||||
|
||||
// Update filteredOptions with the search results
|
||||
setFilteredOptions(filtered);
|
||||
|
||||
// Update filteredMetadata to match the filtered options
|
||||
if (optionsMetaData) {
|
||||
const newMetadata = filtered.map((option) => {
|
||||
const originalIndex = validOptions.indexOf(option);
|
||||
return optionsMetaData[originalIndex];
|
||||
});
|
||||
const newMetadata = filtered
|
||||
.filter((option) => validOptions.includes(option)) // Only map metadata for valid options
|
||||
.map((option) => {
|
||||
const originalIndex = validOptions.indexOf(option);
|
||||
return optionsMetaData[originalIndex];
|
||||
});
|
||||
setFilteredMetadata(newMetadata);
|
||||
} else {
|
||||
setFilteredMetadata(optionsMetaData);
|
||||
|
|
@ -181,7 +215,17 @@ export default function Dropdown({
|
|||
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
setFilteredOptions(validOptions);
|
||||
// Check if filteredOptions contains any custom values not in validOptions
|
||||
const customValuesInFiltered = filteredOptions.filter(
|
||||
(option) => !validOptions.includes(option),
|
||||
);
|
||||
|
||||
// If there are custom values, preserve them when resetting filtered options
|
||||
if (customValuesInFiltered.length > 0) {
|
||||
setFilteredOptions([...validOptions, ...customValuesInFiltered]);
|
||||
} else {
|
||||
setFilteredOptions(validOptions);
|
||||
}
|
||||
setCustomValue("");
|
||||
}
|
||||
}, [open, validOptions]);
|
||||
|
|
@ -233,14 +277,11 @@ export default function Dropdown({
|
|||
editNode
|
||||
? "dropdown-component-outline input-edit-node"
|
||||
: "dropdown-component-false-outline py-2",
|
||||
"no-focus-visible w-full justify-between font-normal",
|
||||
"no-focus-visible w-full justify-between font-normal disabled:bg-muted disabled:text-muted-foreground",
|
||||
)}
|
||||
>
|
||||
<span
|
||||
className={cn(
|
||||
"flex w-full items-center gap-2 overflow-hidden",
|
||||
hasRefreshButton && "max-w-[11rem]",
|
||||
)}
|
||||
className="flex w-full items-center gap-2 overflow-hidden"
|
||||
data-testid={`value-dropdown-${id}`}
|
||||
>
|
||||
{optionsMetaData?.[
|
||||
|
|
@ -256,17 +297,23 @@ export default function Dropdown({
|
|||
/>
|
||||
)}
|
||||
<span className="truncate">
|
||||
{value && filteredOptions.includes(value)
|
||||
? value
|
||||
: placeholderName}{" "}
|
||||
{disabled ? (
|
||||
RECEIVING_INPUT_VALUE
|
||||
) : (
|
||||
<>
|
||||
{value && filteredOptions.includes(value)
|
||||
? value
|
||||
: SELECT_AN_OPTION}{" "}
|
||||
</>
|
||||
)}
|
||||
</span>
|
||||
</span>
|
||||
<ForwardedIconComponent
|
||||
name="ChevronsUpDown"
|
||||
name={disabled ? "Lock" : "ChevronsUpDown"}
|
||||
className={cn(
|
||||
"ml-2 h-4 w-4 shrink-0 text-foreground",
|
||||
disabled
|
||||
? "hover:text-placeholder-foreground"
|
||||
? "text-placeholder-foreground hover:text-placeholder-foreground"
|
||||
: "hover:text-foreground",
|
||||
)}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1005,7 +1005,7 @@ export const GRADIENT_CLASS_DISABLED =
|
|||
"linear-gradient(to right, hsl(var(--muted) / 0.3), hsl(var(--muted)))";
|
||||
|
||||
export const RECEIVING_INPUT_VALUE = "Receiving input";
|
||||
export const SELECT_AN_OPTION = "Select an option...";
|
||||
export const SELECT_AN_OPTION = "Select an option";
|
||||
|
||||
export const ICON_STROKE_WIDTH = 1.5;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue