diff --git a/apps/dashboard/src/@/components/blocks/SignatureSelector.tsx b/apps/dashboard/src/@/components/blocks/SignatureSelector.tsx index f08fc851803..7e813621973 100644 --- a/apps/dashboard/src/@/components/blocks/SignatureSelector.tsx +++ b/apps/dashboard/src/@/components/blocks/SignatureSelector.tsx @@ -10,13 +10,14 @@ interface SignatureOption { interface SignatureSelectorProps { options: SignatureOption[]; - value: string; - onChange: (val: string) => void; + value: string | string[]; + onChange: (val: string | string[]) => void; setAbi?: (abi: string) => void; placeholder?: string; disabled?: boolean; secondaryTextFormatter?: (sig: SignatureOption) => string; className?: string; + multiSelect?: boolean; } export function SignatureSelector({ @@ -28,6 +29,7 @@ export function SignatureSelector({ disabled, secondaryTextFormatter, className, + multiSelect = false, }: SignatureSelectorProps) { const [searchValue, setSearchValue] = useState(""); const inputRef = useRef(null); @@ -42,38 +44,96 @@ export function SignatureSelector({ })); }, [options, secondaryTextFormatter]); - // Check if the current value is a custom value (not in options) - const isCustomValue = value && !options.some((opt) => opt.value === value); + // Handle both single and multi-select values + const currentValues = useMemo((): string[] => { + if (multiSelect) { + if (Array.isArray(value)) { + return value.filter( + (val): val is string => + val !== undefined && val !== null && val !== "", + ); + } else { + return value ? [value] : []; + } + } else { + if (Array.isArray(value)) { + return value.length > 0 && value[0] ? [value[0]] : []; + } else { + return value ? [value] : []; + } + } + }, [value, multiSelect]); + + // Check if the current values include custom values (not in options) + const customValues = useMemo((): string[] => { + return currentValues.filter( + (val): val is string => + val !== undefined && + val !== null && + val !== "" && + !options.some((opt) => opt.value === val), + ); + }, [currentValues, options]); - // Add the custom value as an option if needed + // Add the custom values as options if needed const allOptions = useMemo(() => { - if (isCustomValue && value) { - return [...formattedOptions, { label: value, value }]; - } - return formattedOptions; - }, [formattedOptions, isCustomValue, value]); + const customOptions = customValues.map((val) => ({ + label: val, + value: val, + })); + return [...formattedOptions, ...customOptions]; + }, [formattedOptions, customValues]); - // Single-select MultiSelect wrapper + // Multi-select or single-select MultiSelect wrapper const handleSelectedValuesChange = useCallback( (selected: string[]) => { - // Always use the last selected value for single-select behavior - const selectedValue = - selected.length > 0 ? (selected[selected.length - 1] ?? "") : ""; - onChange(selectedValue); - const found = options.find((opt) => opt.value === selectedValue); - if (setAbi) { - setAbi(found?.abi || ""); + if (multiSelect) { + // Multi-select behavior + onChange(selected); + // For multi-select, we'll use the ABI from the first selected option that has one + const firstOptionWithAbi = selected.find((selectedValue) => { + const found = options.find((opt) => opt.value === selectedValue); + return found?.abi; + }); + if (setAbi && firstOptionWithAbi) { + const found = options.find((opt) => opt.value === firstOptionWithAbi); + setAbi(found?.abi || ""); + } + } else { + // Single-select behavior (maintain backward compatibility) + const selectedValue = + selected.length > 0 ? (selected[selected.length - 1] ?? "") : ""; + onChange(selectedValue); + const found = options.find((opt) => opt.value === selectedValue); + if (setAbi) { + setAbi(found?.abi || ""); + } } setSearchValue(""); }, - [onChange, setAbi, options], + [onChange, setAbi, options, multiSelect], ); // Handle custom value entry const handleInputKeyDown = (event: React.KeyboardEvent) => { if (event.key === "Enter" && searchValue.trim()) { if (!options.some((opt) => opt.value === searchValue.trim())) { - onChange(searchValue.trim()); + if (multiSelect) { + // Add to existing values for multi-select + const currentArray = Array.isArray(value) + ? value + : value + ? [value] + : []; + const filteredArray = currentArray.filter( + (val): val is string => val !== undefined && val !== null, + ); + const newValues = [...filteredArray, searchValue.trim()]; + onChange(newValues); + } else { + // Replace value for single-select + onChange(searchValue.trim()); + } if (setAbi) setAbi(""); setSearchValue(""); // Optionally blur input @@ -106,7 +166,7 @@ export function SignatureSelector({ customSearchInput={customSearchInput} customTrigger={null} disabled={disabled} - maxCount={1} + maxCount={multiSelect ? 100 : 1} onSelectedValuesChange={handleSelectedValuesChange} options={allOptions} overrideSearchFn={(option, searchTerm) => @@ -116,11 +176,13 @@ export function SignatureSelector({ placeholder={placeholder} renderOption={(option) => {option.label}} searchPlaceholder={placeholder} - selectedValues={value ? [value] : []} + selectedValues={currentValues} /> - {isCustomValue && ( + {customValues.length > 0 && (
- You entered a custom signature. Please provide the ABI below. + {multiSelect + ? `You entered ${customValues.length} custom signature${customValues.length > 1 ? "s" : ""}. Please provide the ABI below.` + : "You entered a custom signature. Please provide the ABI below."}
)} diff --git a/apps/dashboard/src/@/components/blocks/multi-select.tsx b/apps/dashboard/src/@/components/blocks/multi-select.tsx index f36b90754e6..e0eaf69b0b1 100644 --- a/apps/dashboard/src/@/components/blocks/multi-select.tsx +++ b/apps/dashboard/src/@/components/blocks/multi-select.tsx @@ -64,6 +64,7 @@ export const MultiSelect = forwardRef( popoverContentClassName, showSelectedValuesInModal = false, customSearchInput, + customTrigger, ...props }, ref, @@ -144,13 +145,18 @@ export const MultiSelect = forwardRef( // scroll to top when options change const popoverElRef = useRef(null); + // Filter out customTrigger from props to avoid passing it to Button + const buttonProps = Object.fromEntries( + Object.entries(props).filter(([key]) => key !== "customTrigger"), + ) as React.ButtonHTMLAttributes; + return ( - {props.customTrigger || ( + {customTrigger || (