PluginProbe
Discount Rules for WooCommerce – Disco | Dynamic Pricing, Conditions, Bulk, Bundle, BOGO / 1.4.21
Discount Rules for WooCommerce – Disco | Dynamic Pricing, Conditions, Bulk, Bundle, BOGO v1.4.21
1.4.21 1.4.20 1.4.19 1.4.18 1.4.17 1.4.16 1.4.15 1.4.14 1.4.13 1.4.12 1.4.11 1.4.10 1.4.9 1.4.8 1.4.7 1.4.6 1.4.5 1.4.4 1.4.3 1.4.2 1.4.1 1.4.0 1.3.54 1.3.53 1.3.52 All 184 releases
disco / backend / views / components / Main / components / AsyncMultiSelect.jsx

AsyncMultiSelect.jsx in Discount Rules for WooCommerce – Disco | Dynamic Pricing, Conditions, Bulk, Bundle, BOGO 1.4.21, at backend/views/components/Main/components/AsyncMultiSelect.jsx

187 lines 4.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import { CheckIcon, XMarkIcon } from '@heroicons/react/24/outline';
2 import { __ } from '@wordpress/i18n';
3 import { useEffect, useRef, useState } from 'react';
4
5 const AsyncMultiSelect = ({
6 onChange,
7 queryHook,
8 selected = [],
9 placeHolder,
10 endpoint = '',
11 widthClass = 'disco:w-96',
12 }) => {
13 const searchRef = useRef();
14 const dropdownRef = useRef();
15 const debounceRef = useRef(null);
16
17 const [skip, setSkip] = useState(true);
18 const [searchQuery, setSearchQuery] = useState('');
19 const [showSearchResult, setShowSearchResult] = useState(false);
20 const [selectedItems, setSelectedItems] = useState(selected);
21
22 const { data, isError, isLoading, error } = queryHook(
23 { endpoint, searchQuery },
24 {
25 skip,
26 }
27 );
28
29 // handle dropdown outside click
30 useEffect(() => {
31 const handler = (e) => {
32 if (!dropdownRef.current.contains(e.target)) {
33 setShowSearchResult(false);
34 searchRef.current.value = '';
35 }
36 };
37 document.addEventListener('mousedown', handler);
38 return () => {
39 document.removeEventListener('mousedown', handler);
40 };
41 }, []);
42
43 useEffect(() => {
44 onChange(selectedItems);
45 }, [selectedItems]);
46
47 // handle item select
48 const handleSelect = (item) => {
49 if (selectedItems.find((_item) => _item.id === item.id)) {
50 setSelectedItems(
51 selectedItems.filter((_item) => _item.id !== item.id)
52 );
53 } else {
54 setSelectedItems((prevState) => [...prevState, item]);
55 }
56 };
57
58 const handleRemoveItem = (id) => {
59 setSelectedItems(selectedItems.filter((_item) => _item.id !== id));
60 };
61
62 const handleSearch = (e) => {
63 const query = e.target.value;
64 clearTimeout(debounceRef.current);
65 debounceRef.current = setTimeout(() => {
66 setSearchQuery(query);
67 if (query.trim().length === 0) {
68 setShowSearchResult(false);
69 setSkip(true);
70 return;
71 }
72 setSkip(false);
73 setShowSearchResult(true);
74 }, 500);
75 };
76
77 // set content for different state
78 let content = '';
79
80 // item loading state content
81 if (isLoading) {
82 content = (
83 <div className="disco:py-4">{__('Searching...', 'disco')}</div>
84 );
85 }
86
87 // error state content
88 if (isError) {
89 content = <div className="disco:py-4">{error?.data?.message}</div>;
90 }
91
92 // successfully item fetching state
93 if (showSearchResult && !isError && !isLoading) {
94 content = data.map((item) => (
95 <div
96 key={item.id}
97 className="disco:flex disco:items-center disco:gap-4 disco:my-4"
98 >
99 <input
100 checked={selected.find((_item) => _item.id === item.id)}
101 type="checkbox"
102 name=""
103 id={item.id}
104 onChange={() => handleSelect(item)}
105 className="disco:hidden!"
106 />
107
108 <label
109 className={`disco:text-sm disco:flex disco:items-center disco:gap-3 `}
110 htmlFor={item.id}
111 >
112 <div
113 className={`disco:shrink-0 disco:h-4 disco:w-4 disco:rounded-sm disco:border disco:flex disco:justify-center disco:items-center ${
114 selected.find((_item) => _item.id === item.id)
115 ? 'disco:border-primary-dark'
116 : 'disco:border-gray-500 '
117 }`}
118 >
119 {selected.find((_item) => _item.id === item.id) && (
120 <CheckIcon className="disco:text-primary-dark" />
121 )}
122 </div>
123 {item.image && (
124 <img
125 className="disco:shrink-0 disco:rounded disco:h-8 disco:w-8 disco:object-cover"
126 src={item.image}
127 alt={item.name}
128 />
129 )}
130 <span>{`${item.id} - ${item.name}`}</span>
131 </label>
132 </div>
133 ));
134 }
135
136 return (
137 <div ref={dropdownRef} className={`disco:relative ${widthClass}`}>
138 <div className="disco:relative disco:max-w-md disco:border disco:border-gray-200 disco:rounded-md">
139 <div
140 onClick={() => {
141 searchRef.current.focus();
142 }}
143 className="disco:flex disco:gap-1.5 disco:flex-wrap disco:items-center disco:rounded-md! disco:px-2 disco:py-1.5 disco:text-sm disco:outline-hidden"
144 >
145 {selected.length > 0 &&
146 selected.map((item) => (
147 <div
148 className="disco:text-xs disco:px-1.5 disco:py-1.5 disco:rounded disco:flex disco:items-center disco:bg-gray-200"
149 key={item.id}
150 >
151 <span>{`${item.id} - ${item.name}`}</span>
152
153 <XMarkIcon
154 role="button"
155 onClick={() => handleRemoveItem(item.id)}
156 className="disco:ml-1 disco:h-3 disco:w-3 "
157 />
158 </div>
159 ))}
160 <input
161 onFocus={(e) => {
162 if (e.target.value.length > 0) {
163 setShowSearchResult(true);
164 }
165 }}
166 ref={searchRef}
167 onChange={handleSearch}
168 className="disco:border-none! disco:p-0! disco:min-h-0! disco:shadow-none!"
169 type="text"
170 placeholder={placeHolder}
171 />
172 </div>
173 </div>
174 {showSearchResult && (
175 <div
176 className={`disco:absolute disco:mt-2 ${widthClass} disco:shadow-lg disco:z-50`}
177 >
178 <div className="disco:border disco:border-gray-200 disco:bg-white disco:rounded-md disco:px-4 disco:max-h-96 disco:overflow-y-auto">
179 {content}
180 </div>
181 </div>
182 )}
183 </div>
184 );
185 };
186 export default AsyncMultiSelect;
187