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}
diff --git a/client/homebrew/pages/basePages/listPage/brewItem/brewItem.less b/client/homebrew/pages/basePages/listPage/brewItem/brewItem.less
index 4b3751ca3..d091f636f 100644
--- a/client/homebrew/pages/basePages/listPage/brewItem/brewItem.less
+++ b/client/homebrew/pages/basePages/listPage/brewItem/brewItem.less
@@ -145,8 +145,8 @@
box-shadow : 0 4px 5px 0 #333333;
.text {
min-height : 54px;
+ padding-left : 5px;
h2 { margin-bottom : 5px; }
- .description { padding-left : 5px; }
}
.info { padding-left : 5px; }
.links {
@@ -398,7 +398,7 @@
}
.storage { display : none; }
}
- .brewItem {
+ .brewItem {
h2 {
width : 200px;
font-size : 1em;
@@ -426,7 +426,7 @@
}
.info {
display : grid;
- grid-template-areas :
+ grid-template-areas :
'tags pages views views'
'authors update update storage';
grid-template-columns : 2fr 1fr 1fr 20px;
@@ -440,7 +440,7 @@
a,
button { margin : 0; }
}
- }
+ }
}
}
@container (max-width : 650px) {
diff --git a/client/homebrew/pages/basePages/listPage/listPage.jsx b/client/homebrew/pages/basePages/listPage/listPage.jsx
index 802c3a14a..8b5da0997 100644
--- a/client/homebrew/pages/basePages/listPage/listPage.jsx
+++ b/client/homebrew/pages/basePages/listPage/listPage.jsx
@@ -1,6 +1,6 @@
/*eslint max-lines: ["warn", {"max": 300, "skipBlankLines": true, "skipComments": true}]*/
import './listPage.less';
-import React, { useEffect, useState, useRef, useMemo } from 'react';
+import React, { useEffect, useState, useMemo } from 'react';
import moment from 'moment';
import _ from 'lodash';
@@ -26,24 +26,7 @@ const ListPage = ({ brewCollection = [{ title: '', class: '', brews: [] }], navI
const [layoutMode, setLayoutMode] = useState(DEFAULT_LAYOUT_MODE);
const [themeMode, setThemeMode] = useState(DEFAULT_THEME_MODE);
- const groupVisibilityRef = useRef(groupVisibility);
- const sortTypeRef = useRef(sortType);
- const sortDirRef = useRef(sortDir);
-
useEffect(()=>{
- groupVisibilityRef.current = groupVisibility;
- }, [groupVisibility]);
-
- useEffect(()=>{
- sortTypeRef.current = sortType;
- }, [sortType]);
-
- useEffect(()=>{
- sortDirRef.current = sortDir;
- }, [sortDir]);
-
- useEffect(()=>{
- window.onbeforeunload = saveToLocalStorage;
if(typeof window === 'undefined') return;
const newSortType = sortType ?? (localStorage.getItem(USERPAGE_SORT_TYPE) || DEFAULT_SORT_TYPE);
@@ -63,20 +46,8 @@ const ListPage = ({ brewCollection = [{ title: '', class: '', brews: [] }], navI
setSortDir(newSortDir);
setLayoutMode(newLayoutMode);
setThemeMode(newThemeMode);
-
- return ()=>{
- window.onbeforeunload = null;
- };
}, []);
- const saveToLocalStorage = ()=>{
- brewCollection.forEach((brewGroup)=>{
- localStorage.setItem(`${USERPAGE_GROUP_VISIBILITY_PREFIX}_${brewGroup.class}`, `${groupVisibilityRef.current[brewGroup.class]}`);
- });
- localStorage.setItem(USERPAGE_SORT_TYPE, sortTypeRef.current);
- localStorage.setItem(USERPAGE_SORT_DIR, sortDirRef.current);
- };
-
const renderBrews = (brews)=>{
if(!brews || !brews.length) return No Brews.
;
@@ -108,6 +79,8 @@ const ListPage = ({ brewCollection = [{ title: '', class: '', brews: [] }], navI
const handleSortOptionChange = (event)=>{
updateUrl(filterString, event.target.value, sortDir);
setSortType(event.target.value);
+ localStorage.setItem(USERPAGE_SORT_TYPE, event.target.value);
+ return;
};
const handleSortDirChange = (event)=>{
@@ -115,6 +88,8 @@ const ListPage = ({ brewCollection = [{ title: '', class: '', brews: [] }], navI
updateUrl(filterString, sortType, newDir);
setSortDir(newDir);
+ localStorage.setItem(USERPAGE_SORT_DIR, newDir);
+ return;
};
const renderSortOption = (sortTitle, sortValue)=>{
@@ -187,7 +162,7 @@ const ListPage = ({ brewCollection = [{ title: '', class: '', brews: [] }], navI
if(filterTags?.length == 0) return;
return (
- Tags:
+ Tags:
{_.map(filterTags, (tag, idx)=>{
const matches = tag.match(/^(?:([^:]+):)?([^:]+)$/);
return (
@@ -198,7 +173,7 @@ const ListPage = ({ brewCollection = [{ title: '', class: '', brews: [] }], navI
updateUrl(filterString, sortType, sortDir, tag);
}}>
{matches[2]}
-
+
);
})}
@@ -257,7 +232,13 @@ const ListPage = ({ brewCollection = [{ title: '', class: '', brews: [] }], navI
}, [brewCollection, filterString, filterTags, sortType, sortDir]);
const toggleBrewCollectionState = (brewGroupClass)=>{
- setGroupVisibility((prevVisibility)=>({ ...prevVisibility, [brewGroupClass]: !prevVisibility[brewGroupClass] }));
+ setGroupVisibility((prevVisibility)=>{
+ const visible = !prevVisibility[brewGroupClass];
+
+ localStorage.setItem(`${USERPAGE_GROUP_VISIBILITY_PREFIX}_${brewGroupClass}`, visible);
+
+ return {...prevVisibility, [brewGroupClass]: visible};
+ });
};
const renderBrewCollection = (brewCollection)=>{
@@ -298,8 +279,8 @@ const ListPage = ({ brewCollection = [{ title: '', class: '', brews: [] }], navI
View: