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 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 { usePostTemplateValue } from "@/controllers/API/queries/nodes/use-post-template-value";
|
||||||
import NodeDialog from "@/CustomNodes/GenericNode/components/NodeDialogComponent";
|
import NodeDialog from "@/CustomNodes/GenericNode/components/NodeDialogComponent";
|
||||||
import { mutateTemplate } from "@/CustomNodes/helpers/mutate-template";
|
import { mutateTemplate } from "@/CustomNodes/helpers/mutate-template";
|
||||||
|
|
@ -54,7 +55,6 @@ export default function Dropdown({
|
||||||
dialogInputs,
|
dialogInputs,
|
||||||
handleOnNewValue,
|
handleOnNewValue,
|
||||||
toggle,
|
toggle,
|
||||||
hasRefreshButton,
|
|
||||||
...baseInputProps
|
...baseInputProps
|
||||||
}: BaseInputProps & DropDownComponent): JSX.Element {
|
}: BaseInputProps & DropDownComponent): JSX.Element {
|
||||||
const validOptions = useMemo(
|
const validOptions = useMemo(
|
||||||
|
|
@ -66,12 +66,20 @@ export default function Dropdown({
|
||||||
const [open, setOpen] = useState(children ? true : false);
|
const [open, setOpen] = useState(children ? true : false);
|
||||||
const [openDialog, setOpenDialog] = useState(false);
|
const [openDialog, setOpenDialog] = useState(false);
|
||||||
const [customValue, setCustomValue] = useState("");
|
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 [filteredMetadata, setFilteredMetadata] = useState(optionsMetaData);
|
||||||
const [refreshOptions, setRefreshOptions] = useState(false);
|
const [refreshOptions, setRefreshOptions] = useState(false);
|
||||||
const refButton = useRef<HTMLButtonElement>(null);
|
const refButton = useRef<HTMLButtonElement>(null);
|
||||||
|
|
||||||
value = useMemo(() => {
|
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)) {
|
if (!options.includes(value) && !filteredOptions.includes(value)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
@ -112,24 +120,50 @@ export default function Dropdown({
|
||||||
|
|
||||||
if (!value) {
|
if (!value) {
|
||||||
// If search is cleared, show all options
|
// 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);
|
setFilteredMetadata(optionsMetaData);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Search existing options
|
// Search existing options
|
||||||
const searchValues = fuse.search(value);
|
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
|
// Update filteredOptions with the search results
|
||||||
setFilteredOptions(filtered);
|
setFilteredOptions(filtered);
|
||||||
|
|
||||||
// Update filteredMetadata to match the filtered options
|
// Update filteredMetadata to match the filtered options
|
||||||
if (optionsMetaData) {
|
if (optionsMetaData) {
|
||||||
const newMetadata = filtered.map((option) => {
|
const newMetadata = filtered
|
||||||
const originalIndex = validOptions.indexOf(option);
|
.filter((option) => validOptions.includes(option)) // Only map metadata for valid options
|
||||||
return optionsMetaData[originalIndex];
|
.map((option) => {
|
||||||
});
|
const originalIndex = validOptions.indexOf(option);
|
||||||
|
return optionsMetaData[originalIndex];
|
||||||
|
});
|
||||||
setFilteredMetadata(newMetadata);
|
setFilteredMetadata(newMetadata);
|
||||||
} else {
|
} else {
|
||||||
setFilteredMetadata(optionsMetaData);
|
setFilteredMetadata(optionsMetaData);
|
||||||
|
|
@ -181,7 +215,17 @@ export default function Dropdown({
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (open) {
|
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("");
|
setCustomValue("");
|
||||||
}
|
}
|
||||||
}, [open, validOptions]);
|
}, [open, validOptions]);
|
||||||
|
|
@ -233,14 +277,11 @@ export default function Dropdown({
|
||||||
editNode
|
editNode
|
||||||
? "dropdown-component-outline input-edit-node"
|
? "dropdown-component-outline input-edit-node"
|
||||||
: "dropdown-component-false-outline py-2",
|
: "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
|
<span
|
||||||
className={cn(
|
className="flex w-full items-center gap-2 overflow-hidden"
|
||||||
"flex w-full items-center gap-2 overflow-hidden",
|
|
||||||
hasRefreshButton && "max-w-[11rem]",
|
|
||||||
)}
|
|
||||||
data-testid={`value-dropdown-${id}`}
|
data-testid={`value-dropdown-${id}`}
|
||||||
>
|
>
|
||||||
{optionsMetaData?.[
|
{optionsMetaData?.[
|
||||||
|
|
@ -256,17 +297,23 @@ export default function Dropdown({
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<span className="truncate">
|
<span className="truncate">
|
||||||
{value && filteredOptions.includes(value)
|
{disabled ? (
|
||||||
? value
|
RECEIVING_INPUT_VALUE
|
||||||
: placeholderName}{" "}
|
) : (
|
||||||
|
<>
|
||||||
|
{value && filteredOptions.includes(value)
|
||||||
|
? value
|
||||||
|
: SELECT_AN_OPTION}{" "}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
<ForwardedIconComponent
|
<ForwardedIconComponent
|
||||||
name="ChevronsUpDown"
|
name={disabled ? "Lock" : "ChevronsUpDown"}
|
||||||
className={cn(
|
className={cn(
|
||||||
"ml-2 h-4 w-4 shrink-0 text-foreground",
|
"ml-2 h-4 w-4 shrink-0 text-foreground",
|
||||||
disabled
|
disabled
|
||||||
? "hover:text-placeholder-foreground"
|
? "text-placeholder-foreground hover:text-placeholder-foreground"
|
||||||
: "hover:text-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)))";
|
"linear-gradient(to right, hsl(var(--muted) / 0.3), hsl(var(--muted)))";
|
||||||
|
|
||||||
export const RECEIVING_INPUT_VALUE = "Receiving input";
|
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;
|
export const ICON_STROKE_WIDTH = 1.5;
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue