PluginProbe
Smart Post – Post Grid, Post Carousel, Post Slider Gutenberg Blocks for Blog & News / 4.0.2
Smart Post – Post Grid, Post Carousel, Post Slider Gutenberg Blocks for Blog & News v4.0.2
4.0.8 4.0.7 4.0.6 4.0.5 4.0.4 4.0.3 4.0.2 4.0.1 2.3.5 2.3.6 2.4.0 2.4.1 2.4.10 2.4.11 2.4.12 2.4.13 2.4.14 2.4.15 2.4.16 2.4.17 2.4.18 2.4.19 2.4.2 2.4.20 2.4.21 All 88 releases
post-carousel / src / components / multipleSelect / multiSelectDndKit.js

multiSelectDndKit.js in Smart Post – Post Grid, Post Carousel, Post Slider Gutenberg Blocks for Blog & News 4.0.2, at src/components/multipleSelect/multiSelectDndKit.js

228 lines 6.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import { DndContext, PointerSensor, useSensor, useSensors } from "@dnd-kit/core";
2 import { restrictToVerticalAxis } from "@dnd-kit/modifiers";
3 import { arrayMove, SortableContext, verticalListSortingStrategy, useSortable } from "@dnd-kit/sortable";
4 import { memo, useEffect, useMemo, useRef, useState } from "@wordpress/element";
5 import { CSS } from "@dnd-kit/utilities";
6 import "./dnd-select.scss";
7
8 const SortableItem = memo((props) => {
9 const { attributes, listeners, setNodeRef, transform, transition } = useSortable({ id: props.id });
10
11 const style = {
12 transform: CSS.Transform.toString(transform),
13 transition: transition,
14 };
15 const { item, onChange } = props;
16
17 // Delete Icon
18 const DeleteIcon = () => {
19 return (
20 <svg xmlns="http://www.w3.org/2000/svg" width={8} height={8} fill="none">
21 <path
22 fill="#2F2F2F"
23 d="M4 4.778 6.722 7.5l.778-.778L4.778 4 7.5 1.278 6.722.5 4 3.222 1.278.5.5 1.278 3.222 4 .5 6.722l.778.778L4 4.778Z"
24 />
25 </svg>
26 );
27 };
28
29 return (
30 <>
31 <div ref={setNodeRef} style={style} {...attributes} {...listeners}>
32 <div className="sp-selected-option">
33 {/* Display the label of the item */}
34 <span className="sp-select-label">{item?.label}</span>
35
36 {/* Delete button */}
37 <span
38 className="sp-select-remove-button"
39 role="button"
40 aria-label={`Remove ${item?.label}`}
41 onClick={() => onChange(item.id)}
42 >
43 <DeleteIcon />
44 </span>
45 </div>
46 </div>
47 </>
48 );
49 });
50
51 const MultiSelectDndKit = ({ label = "", items, values, onChange, onInputChange = false, searchable = false }) => {
52 const [allOptions, setAllOptions] = useState([]);
53 const [toggleSelectField, setToggleSelectField] = useState(false);
54 const [optionOpen, setOptionOpen] = useState(true);
55 const [searchFieldData, setSearchFieldData] = useState("");
56 const dndRef = useRef(null);
57 const containerRef = useRef(null);
58 const allValues = useMemo(() => {
59 if (!Array.isArray(values) || values.some(item => !item.value) ) {return []}; // prevents the crash
60 return values.map(item => item.value);
61 }
62 , [values]);
63
64 const sensors = useSensors(
65 useSensor(PointerSensor, {
66 activationConstraint: { distance: 5 },
67 })
68 );
69
70 // icons
71 const ArrowIconOne = () => (
72 <svg xmlns="http://www.w3.org/2000/svg" width={24} height={24} aria-hidden="true" className="">
73 <path d="M17.5 11.6 12 16l-5.5-4.4.9-1.2L12 14l4.5-3.6 1 1.2z" />
74 </svg>
75 );
76 const ArrowIconTwo = () => (
77 <svg xmlns="http://www.w3.org/2000/svg" width={24} height={24} aria-hidden="true">
78 <path d="M6.5 12.4 12 8l5.5 4.4-.9 1.2L12 10l-4.5 3.6-1-1.2z" />
79 </svg>
80 );
81
82 const containerHeight = values?.length * 34 + 2 + "px";
83
84 const handleDragEnd = (event) => {
85 setToggleSelectField(false);
86 const { active, over } = event;
87 if (active.id !== over.id) {
88 const oldIndex = values.findIndex((i) => `${i.id}` === `${active.id}`);
89 const newIndex = values.findIndex((i) => `${i.id}` === `${over.id}`);
90 const updateValues = arrayMove(values, oldIndex, newIndex);
91 onChange(updateValues);
92 setOptionOpen(true);
93 }
94 };
95
96 const onDragStart = () => {
97 setOptionOpen(false);
98 setToggleSelectField(false);
99 };
100
101 const handleRemoveItems = (id) => {
102 const updatedValues = values?.filter((val) => val.id !== id);
103 onChange(updatedValues);
104 };
105
106 const toggleSelectFieldVal = () => {
107 if (optionOpen) {
108 setToggleSelectField((prev) => !prev);
109 }
110 };
111
112 const filterNotSelectedItems = (selectFieldItems) => {
113 const result = selectFieldItems?.filter((item) => !allValues.includes(item.value));
114 return result;
115 };
116
117 const handleSelectField = (data) => {
118 // const { id, value, label, slug, order } = data;
119 // const newValue = [ ...values, { id, value, label, slug, order } ];
120 const newValue = [...values, { ...data }];
121 setSearchFieldData("");
122 onChange(newValue);
123 };
124
125 const handleSearchField = (event) => {
126 const value = event.target.value.toLowerCase();
127 setSearchFieldData(value);
128 const searchableArray = filterNotSelectedItems(items);
129 const matchedOption = searchableArray?.filter((i) => i.label.toLowerCase().includes(value));
130 onInputChange ? onInputChange(value) : setAllOptions(matchedOption);
131 };
132
133 useEffect(() => {
134 let options = [];
135 if (toggleSelectField) {
136 options = filterNotSelectedItems(items);
137 } else {
138 options = [items];
139 }
140 setAllOptions(options);
141 }, [items, values, toggleSelectField]);
142
143 useEffect(() => {
144 const handleClickOutside = (event) => {
145 if (containerRef.current && !containerRef.current.contains(event.target)) {
146 setToggleSelectField(false);
147 }
148 };
149
150 if (toggleSelectField) {
151 document.addEventListener("click", handleClickOutside);
152 }
153
154 return () => {
155 document.removeEventListener("click", handleClickOutside);
156 };
157 }, [toggleSelectField]);
158
159 return (
160 <div className="shaped-plugin-multiple-select sp-smart-post-component-mb" ref={dndRef}>
161 <div className="sp-multiple-select-dnd-label">
162 <p> {label}</p>
163 </div>
164 <div className="sp-multiple-select-dnd-container" ref={containerRef}>
165 <div onClick={toggleSelectFieldVal} className="sp-select-header">
166 <div className="sp-selected-options" style={{ height: containerHeight }}>
167 <DndContext
168 sensors={sensors}
169 onDragEnd={handleDragEnd}
170 onDragStart={onDragStart}
171 modifiers={[restrictToVerticalAxis]}
172 >
173 <SortableContext
174 items={values?.map((item) => `${item.id}`)}
175 strategy={verticalListSortingStrategy}
176 >
177 {values?.map((item, index) => (
178 <SortableItem
179 key={item.id}
180 id={`${item.id}`}
181 index={index}
182 item={item}
183 onChange={handleRemoveItems}
184 />
185 ))}
186 </SortableContext>
187 </DndContext>
188 </div>
189 <span className="custom-select-arrow">
190 {toggleSelectField ? <ArrowIconTwo /> : <ArrowIconOne />}
191 </span>
192 </div>
193 {toggleSelectField && (
194 <div className="sp-select-options">
195 {searchable && (
196 <input
197 placeholder="Search here for more..."
198 value={searchFieldData}
199 onChange={(e) => handleSearchField(e)}
200 className="sp-select-search-field"
201 />
202 )}
203 {allOptions?.length > 0 &&
204 allOptions?.map((option, i) => {
205 return (
206 <div key={i} className={option?.disabled ? "disabled" : ""}>
207 {option?.label && option?.value && (
208 <p
209 onClick={() => handleSelectField(option)}
210 className={`sp-select-option ${
211 values === option.value ? "selected" : ""
212 }`}
213 >
214 {option.label}
215 </p>
216 )}
217 </div>
218 );
219 })}
220 </div>
221 )}
222 </div>
223 </div>
224 );
225 };
226
227 export default memo(MultiSelectDndKit);
228