Skip to content

Commit 394ce2b

Browse files
committed
Refactor react-select into downshift
Refactor react-select into downshift
1 parent ff6019c commit 394ce2b

File tree

3 files changed

+174
-68
lines changed

3 files changed

+174
-68
lines changed

client/finder/FileDetails.tsx

Lines changed: 0 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,74 +1,8 @@
11
import React, {useMemo, useState} from 'react';
2-
import Select from 'react-select';
32
import {ControlButtons} from 'finder/ControlButtons';
43
import {ProgressBar, ProgressOverlay} from 'finder/UploadProgress';
54

65

7-
function SelectLabels(props) {
8-
const {settings} = props;
9-
const LabelOption = ({innerProps, data}) => (
10-
<div {...innerProps} className="select-labels-option">
11-
<span style={{backgroundColor: data.color}} className="select-labels-dot" />
12-
{data.label}
13-
</div>
14-
);
15-
const MultiValueLabel = ({data}) => (
16-
<div>
17-
<span style={{backgroundColor: data.color}} className="select-labels-dot" />
18-
{data.label}
19-
</div>
20-
);
21-
const MultiValueContainer = ({children}) => (
22-
<div className="select-labels-value">
23-
{children}
24-
</div>
25-
);
26-
const defaultValues = useMemo(() => {
27-
const defaultValues = [];
28-
if (settings.labels) {
29-
let labelsElement = document.getElementById('id_labels') ?? settings.mainContent.querySelector('#id_labels');
30-
if (labelsElement instanceof HTMLSelectElement) {
31-
// extract selected values from the original <select multiple name="labels"> element
32-
for (const option of labelsElement.selectedOptions) {
33-
const found = settings.labels.find(label => label.value == option.value);
34-
if (found) {
35-
defaultValues.push(found);
36-
}
37-
}
38-
// remove the original <select multiple name="labels"> element
39-
while (labelsElement) {
40-
if (labelsElement.classList.contains('field-labels')) {
41-
labelsElement.remove();
42-
break;
43-
}
44-
labelsElement = labelsElement.parentElement;
45-
}
46-
}
47-
}
48-
return defaultValues;
49-
}, []);
50-
51-
if (settings.labels) return (
52-
<div className="aligned">
53-
<div className="form-row" style={{overflow: "visible"}}>
54-
<div className="flex-container">
55-
<label>{gettext("Labels")}:</label>
56-
<Select
57-
components={{Option: LabelOption, MultiValueLabel, MultiValueContainer}}
58-
defaultValue={defaultValues}
59-
options={settings.labels}
60-
name="labels"
61-
placeholder={gettext("Choose Labels")}
62-
className="select-labels"
63-
isMulti={true}
64-
/>
65-
</div>
66-
</div>
67-
</div>
68-
);
69-
}
70-
71-
726
export default function FileDetails(props) {
737
const {settings} = props;
748
const [uploadFile, setUploadFile] = useState<Promise<Response>>(null);
@@ -86,7 +20,6 @@ export default function FileDetails(props) {
8620
<h2>{subtitle}</h2>
8721
{props.children}
8822
<ControlButtons setUploadFile={setUploadFile} settings={settings}>{props.controlButtons}</ControlButtons>
89-
<SelectLabels settings={settings} />
9023
</div>
9124
{uploadFile &&
9225
<ProgressOverlay>

client/finder/SelectLabels.tsx

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
import React from 'react';
2+
import {useMultipleSelection, useSelect} from 'downshift';
3+
4+
5+
type Label = {
6+
value: string,
7+
label: string,
8+
color: string,
9+
};
10+
11+
12+
export default function SelectLabels(props) {
13+
const {labels, initial, original} = props as { labels: Label[]; initial: Label[]; original: HTMLSelectElement };
14+
const {
15+
getSelectedItemProps,
16+
getDropdownProps,
17+
addSelectedItem,
18+
removeSelectedItem,
19+
selectedItems,
20+
} = useMultipleSelection({initialSelectedItems: initial});
21+
const items = labels.filter(label => !selectedItems.find((selectedItem: any) => selectedItem.value === label.value));
22+
const {
23+
isOpen,
24+
getToggleButtonProps,
25+
getMenuProps,
26+
getItemProps,
27+
} = useSelect({
28+
selectedItem: null,
29+
items,
30+
stateReducer: (state, actionAndChanges) => {
31+
const {changes, type} = actionAndChanges;
32+
switch (type) {
33+
case useSelect.stateChangeTypes.ToggleButtonKeyDownEnter:
34+
case useSelect.stateChangeTypes.ToggleButtonKeyDownSpaceButton:
35+
case useSelect.stateChangeTypes.ItemClick:
36+
return {
37+
...changes,
38+
isOpen: true, // keep the menu open after selection.
39+
}
40+
default:
41+
return changes;
42+
}
43+
},
44+
onStateChange: ({type, selectedItem: newSelectedItem}) => {
45+
switch (type) {
46+
case useSelect.stateChangeTypes.ToggleButtonKeyDownEnter:
47+
case useSelect.stateChangeTypes.ToggleButtonKeyDownSpaceButton:
48+
case useSelect.stateChangeTypes.ItemClick:
49+
case useSelect.stateChangeTypes.ToggleButtonBlur:
50+
if (newSelectedItem) {
51+
const option = Array.from(original.options).find(o => o.value == newSelectedItem.value);
52+
if (option) {
53+
option.selected = true;
54+
}
55+
addSelectedItem(newSelectedItem);
56+
}
57+
break;
58+
default:
59+
break;
60+
}
61+
},
62+
})
63+
64+
function removeLabel(event: React.MouseEvent, label: Label) {
65+
event.stopPropagation();
66+
const option = Array.from(original.options).find(o => o.value == label.value);
67+
if (option) {
68+
option.selected = false;
69+
}
70+
removeSelectedItem(label);
71+
}
72+
73+
return (
74+
<div className="select-labels-field">
75+
{selectedItems.map((selectedItem, index) => {
76+
return (
77+
<div className="select-labels-value"
78+
key={`selected-item-${index}`}
79+
{...getSelectedItemProps({selectedItem, index})}
80+
>
81+
<span style={{backgroundColor: selectedItem.color}} className="select-labels-dot"/>
82+
{selectedItem.label}
83+
<span
84+
className="deselect-label"
85+
onClick={(event: React.MouseEvent) => removeLabel(event, selectedItem)}
86+
>&times;</span>
87+
</div>
88+
)
89+
})}
90+
<div className="caret" {...getToggleButtonProps(getDropdownProps({preventKeyAction: isOpen}))}></div>
91+
<ul {...getMenuProps()}>{
92+
isOpen && items.map((item, index) => (
93+
<li key={`item-${index}`} {...getItemProps({item, index})}>
94+
<span style={{backgroundColor: item.color}} className="select-labels-dot"/>
95+
{item.label}
96+
</li>
97+
))
98+
}</ul>
99+
</div>
100+
)
101+
}
102+
103+
104+
// function SelectLabels(props) {
105+
// const {settings} = props;
106+
// const LabelOption = ({innerProps, data}) => (
107+
// <div {...innerProps} className="select-labels-option">
108+
// <span style={{backgroundColor: data.color}} className="select-labels-dot" />
109+
// {data.label}
110+
// </div>
111+
// );
112+
// const MultiValueLabel = ({data}) => (
113+
// <div>
114+
// <span style={{backgroundColor: data.color}} className="select-labels-dot" />
115+
// {data.label}
116+
// </div>
117+
// );
118+
// const MultiValueContainer = ({children}) => (
119+
// <div className="select-labels-value">
120+
// {children}
121+
// </div>
122+
// );
123+
// const defaultValues = useMemo(() => {
124+
// const defaultValues = [];
125+
// if (settings.labels) {
126+
// const labelsElement = document.getElementById('id_labels') ?? settings.mainContent.querySelector('#id_labels');
127+
// if (labelsElement instanceof HTMLSelectElement) {
128+
// // extract selected values from the original <select multiple name="labels"> element
129+
// for (const option of labelsElement.selectedOptions) {
130+
// const found = settings.labels.find(label => label.value == option.value);
131+
// if (found) {
132+
// defaultValues.push(found);
133+
// }
134+
// }
135+
// }
136+
// }
137+
// return defaultValues;
138+
// }, []);
139+
//
140+
// useEffect(() => {
141+
// if (settings.labels) {
142+
// // remove the original <select multiple name="labels"> element together with its <label> element
143+
// let labelsElement = document.getElementById('id_labels') ?? settings.mainContent.querySelector('#id_labels');
144+
// while (labelsElement) {
145+
// if (labelsElement.classList.contains('form-row')) {
146+
// labelsElement.remove();
147+
// break;
148+
// }
149+
// labelsElement = labelsElement.parentElement;
150+
// }
151+
// }
152+
// }, []);
153+
//
154+
// if (settings.labels) return (
155+
// <div className="aligned">
156+
// <div className="form-row" style={{overflow: "visible"}}>
157+
// <div className="flex-container">
158+
// <label>{gettext("Labels")}:</label>
159+
// <Select
160+
// components={{Option: LabelOption, MultiValueLabel, MultiValueContainer}}
161+
// defaultValue={defaultValues}
162+
// options={settings.labels}
163+
// name="labels"
164+
// placeholder={gettext("Choose Labels")}
165+
// className="select-labels-container"
166+
// classNamePrefix="select-labels"
167+
// isMulti={true}
168+
// />
169+
// </div>
170+
// </div>
171+
// </div>
172+
// );
173+
// }

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,12 @@
1515
"@wavesurfer/react": "^1.0.7",
1616
"bootstrap": "^5.3.3",
1717
"concurrently": "^8.2.0",
18+
"downshift": "^9.0.8",
1819
"esbuild": "^0.19.12",
1920
"esbuild-plugin-svgr": "^2.1.0",
2021
"react-h5-audio-player": "^3.9.3",
2122
"react-image-crop": "^11.0.7",
2223
"react-player": "^2.16.0",
23-
"react-select": "^5.8.1",
2424
"react-tooltip": "^5.28.0",
2525
"request": "^2.88.2",
2626
"sass": "^1.80.4",

0 commit comments

Comments
 (0)