diff --git a/client/components/combobox.jsx b/client/components/combobox.jsx
index 8f6f4ead9..230d079db 100644
--- a/client/components/combobox.jsx
+++ b/client/components/combobox.jsx
@@ -1,143 +1,133 @@
-import React from 'react';
-import createReactClass from 'create-react-class';
+import React, { useState, useEffect, useRef, useCallback } from 'react';
import _ from 'lodash';
import './combobox.less';
-const Combobox = createReactClass({
- displayName : 'Combobox',
- getDefaultProps : function() {
- return {
- id : '',
- className : '',
- trigger : 'hover',
- default : '',
- placeholder : '',
- tooltip : '',
- autoSuggest : {
- clearAutoSuggestOnClick : true,
- suggestMethod : 'includes',
- filterOn : [] // should allow as array to filter on multiple attributes, or even custom filter
- },
- valuePatterns : /.+/
- };
+const Combobox = ({
+ id = '',
+ className = '',
+ trigger = 'hover',
+ defaultValue = '',
+ placeholder = '',
+ tooltip = '',
+ autoSuggest = {
+ clearAutoSuggestOnClick : true,
+ suggestMethod : 'includes',
+ filterOn : [] // should allow as array to filter on multiple attributes, or even custom filter
},
- getInitialState : function() {
- this.dropdownRef = React.createRef();
- return {
- showDropdown : false,
- value : '',
- options : [...this.props.options],
- inputFocused : false
- };
- },
- componentDidMount : function() {
- if(this.props.trigger == 'click')
- document.addEventListener('click', this.handleClickOutside);
- this.setState({
- value : this.props.default
- });
- },
- componentWillUnmount : function() {
- if(this.props.trigger == 'click')
- document.removeEventListener('click', this.handleClickOutside);
- },
- handleClickOutside : function(e){
- // Close dropdown when clicked outside
- if(this.dropdownRef.current && !this.dropdownRef.current.contains(e.target)) {
- this.handleDropdown(false);
+ valuePatterns = '.+',
+ options = [],
+ keepValueOnClose = false,
+ ...props
+})=>{
+ const dropdownRef = useRef(null);
+ const [showDropdown, setShowDropdown] = useState(false);
+ const [inputFocused, setInputFocused] = useState(false);
+ const [value, setValue] = useState(defaultValue);
+
+ useEffect(()=>{
+ if(trigger === 'click') {
+ document.addEventListener('click', handleClickOutside);
+ return ()=>document.removeEventListener('click', handleClickOutside);
}
- },
- handleDropdown : function(show){
- this.setState({
- value : show ? '' : this.props.default,
- showDropdown : show,
- inputFocused : this.props.autoSuggest.clearAutoSuggestOnClick ? show : false
- });
- },
- handleInput : function(e){
- e.persist();
- this.setState({
- value : e.target.value,
- inputFocused : false
- }, ()=>{
- this.props.onEntry(e);
- });
- },
- handleSelect : function(value, data=value){
- this.setState({
- value : value
- }, ()=>{this.props.onSelect(data);});
- ;
- },
- renderTextInput : function(){
+ }, []);
+
+ const handleClickOutside = useCallback((e)=>{
+ // Close dropdown when clicked outside
+ if(!dropdownRef.current?.contains(e.target)) {
+ handleDropdown(false);
+ }
+ }, []);
+
+ const handleDropdown = (show)=>{
+ if(show) {
+ if(autoSuggest.clearAutoSuggestOnClick) {
+ setValue('');
+ }
+ } else if(!keepValueOnClose) {
+ setValue(defaultValue);
+ }
+ setShowDropdown(show);
+ setInputFocused(autoSuggest.clearAutoSuggestOnClick && show);
+ };
+
+ const handleInput = (e)=>{
+ setValue(e.target.value);
+ setInputFocused(false);
+ props.onEntry(e);
+ };
+
+ const handleSelect = (value, data=value)=>{
+ setValue(value);
+ props.onSelect(data);
+ };
+
+ const renderTextInput = ()=>{
return (
{this.handleDropdown(true);} : undefined}
- onClick= {this.props.trigger == 'click' ? ()=>{this.handleDropdown(true);} : undefined}
- {...(this.props.tooltip ? { 'data-tooltip-right': this.props.tooltip } : {})}>
+ onMouseEnter={trigger == 'hover' ? ()=>{handleDropdown(true);} : undefined}
+ onClick= {trigger == 'click' ? ()=>{handleDropdown(true);} : undefined}
+ {...(tooltip ? { 'data-tooltip-right': tooltip } : {})}>
this.handleInput(e)}
- value={this.state.value || ''}
+ onChange={(e)=>handleInput(e)}
+ value={value || ''}
title=''
- pattern={this.props.valuePatterns}
- placeholder={this.props.placeholder}
+ pattern={valuePatterns}
+ placeholder={placeholder}
onBlur={(e)=>{
if(!e.target.checkValidity()){
- this.setState({
- value : this.props.default
- });
+ setValue(defaultValue);
}
}}
onKeyDown={(e)=>{
if(e.key === 'Enter') {
e.preventDefault();
- this.props.onEntry(e);
+ props.onEntry(e);
}
}}
/>
);
- },
- renderDropdown : function(dropdownChildren){
- if(!this.state.showDropdown) return null;
- if(this.props.autoSuggest && !this.state.inputFocused){
- const suggestMethod = this.props.autoSuggest.suggestMethod;
- const filterOn = _.isString(this.props.autoSuggest.filterOn) ? [this.props.autoSuggest.filterOn] : this.props.autoSuggest.filterOn;
+ };
+
+ const renderDropdown = ()=>{
+ if(!showDropdown) return null;
+ let dropdownChildren = options.map((child, i)=>{
+ const clone = React.cloneElement(child, { onClick: ()=>handleSelect(child.props.value, child.props.data) });
+ return clone;
+ });
+ if(autoSuggest && !inputFocused){
+ const suggestMethod = autoSuggest.suggestMethod;
+ const filterOn = _.isString(autoSuggest.filterOn) ? [autoSuggest.filterOn] : autoSuggest.filterOn;
const filteredArrays = filterOn.map((attr)=>{
const children = dropdownChildren.filter((item)=>{
if(suggestMethod === 'includes')
- return item.props[attr]?.toLowerCase().includes(this.state.value.toLowerCase());
+ return item.props[attr]?.toLowerCase().includes(value.toLowerCase());
if(suggestMethod === 'startsWith')
- return item.props[attr]?.toLowerCase().startsWith(this.state.value.toLowerCase());
+ return item.props[attr]?.toLowerCase().startsWith(value.toLowerCase());
});
return children;
});
dropdownChildren = _.uniq(filteredArrays.flat(1));
}
-
return (
{dropdownChildren}
);
- },
- render : function () {
- const dropdownChildren = this.state.options.map((child, i)=>{
- const clone = React.cloneElement(child, { onClick: ()=>this.handleSelect(child.props.value, child.props.data) });
- return clone;
- });
- return (
- {this.handleDropdown(false);} : undefined}>
- {this.renderTextInput()}
- {this.renderDropdown(dropdownChildren)}
-
- );
- }
-});
+ };
+
+ return (
+ {handleDropdown(false);} : undefined}>
+ {renderTextInput()}
+ {renderDropdown()}
+
+ );
+
+};
export default Combobox;
diff --git a/client/homebrew/editor/metadataEditor/metadataEditor.jsx b/client/homebrew/editor/metadataEditor/metadataEditor.jsx
index d5c4387e9..feb1cf2b1 100644
--- a/client/homebrew/editor/metadataEditor/metadataEditor.jsx
+++ b/client/homebrew/editor/metadataEditor/metadataEditor.jsx
@@ -219,7 +219,7 @@ const MetadataEditor = ({
handleTheme(value)}
onEntry={(e)=>{
@@ -261,8 +261,10 @@ const MetadataEditor = ({
handleLanguage(value)}
onEntry={(e)=>{
e.target.setCustomValidity(''); //Clear the validation popup while typing
diff --git a/client/homebrew/editor/tagInput/tagInput.jsx b/client/homebrew/editor/tagInput/tagInput.jsx
index a153fef8b..ede194834 100644
--- a/client/homebrew/editor/tagInput/tagInput.jsx
+++ b/client/homebrew/editor/tagInput/tagInput.jsx
@@ -131,7 +131,7 @@ const TagInput = ({ id, tooltip, label, valuePatterns, values = [], unique = tru
id={id}
trigger='click'
className='tagInput-dropdown'
- default=''
+ defaultValue=''
placeholder={placeholder}
options={label === 'tags' ? suggestionOptions : []}
tooltip={tooltip}