PluginProbe
WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance / 4.5.4
WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance v4.5.4
4.7.0 4.6.1 4.6.0 4.5.5 4.5.4 4.5.3 4.5.2 3.2.20 3.2.21 3.2.22 3.2.3 3.2.5 3.2.6 3.2.7 3.2.9 3.3.0 3.3.1 3.3.2 3.4.0 3.4.1 3.4.2 3.5.0 3.6.0 3.7.0 3.7.1 All 111 releases
wp-optimize / vendor / team-updraft / lib-onboarding-wizard / Wizard / Onboarding / src / components / Fields / MultiSelectDropdown.tsx

MultiSelectDropdown.tsx in WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance 4.5.4, at vendor/team-updraft/lib-onboarding-wizard/Wizard/Onboarding/src/components/Fields/MultiSelectDropdown.tsx

163 lines 6.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 "use client";
2
3 import { memo, useState, useEffect, useRef } from 'react';
4 import FieldWrapper from './FieldWrapper';
5 import { __ } from '@wordpress/i18n';
6 import {
7 Select,
8 SelectContent,
9 SelectItem,
10 SelectTrigger,
11 SelectValue,
12 } from './DropdownInput';
13 import Icon from '../../utils/Icon';
14
15 interface MultiSelectOption {
16 value: string;
17 label: string;
18 icon?: string;
19 }
20
21 interface MultiSelectFieldProps {
22 field: {
23 id: string;
24 label?: string;
25 options: MultiSelectOption[];
26 is_lock?: boolean;
27 tooltip?: any;
28 placeholder?: string;
29 [key: string]: any;
30 };
31 value: string[];
32 onChange: (value: string[]) => void;
33 }
34
35 const MultiSelect = ({ field, value, onChange } : MultiSelectFieldProps ) => {
36 const [open, setOpen] = useState(false);
37 const selectedValues = Array.isArray(value) ? value : [];
38 const removeButtonRefs = useRef<Record<string, HTMLSpanElement | null>>({});
39
40 const selectedOptions = selectedValues
41 .map(val => field.options.find(opt => opt.value === val))
42 .filter((opt): opt is MultiSelectOption => opt !== undefined);
43
44 const handleValueChange = (newValue: string) => {
45 const isSelected = selectedValues.includes(newValue);
46 let updatedValues;
47
48 if (isSelected) {
49 updatedValues = selectedValues.filter((item) => item !== newValue);
50 } else {
51 updatedValues = [...selectedValues, newValue];
52 }
53 onChange(updatedValues);
54 };
55
56 const handleRemoveTag = (e: React.MouseEvent<HTMLSpanElement> | React.KeyboardEvent<HTMLSpanElement>, itemToRemove: string) => {
57 e.stopPropagation();
58 e.preventDefault();
59 const updatedValues = selectedValues.filter((item) => item !== itemToRemove);
60 onChange(updatedValues);
61 };
62
63 const handleSelectOpenChange = (newOpen: boolean) => {
64 const clickedRemoveButton = Object.values(removeButtonRefs.current).some(
65 (ref) => ref && ref.contains(event.target as Node)
66 );
67
68 if (clickedRemoveButton) {
69 return;
70 }
71 setOpen(newOpen);
72 };
73
74 return (
75 <FieldWrapper
76 inputId={field.id}
77 label={
78 <div className="flex items-center gap-2">
79 <span>{field.label}</span>
80 {field.tooltip && (
81 <Icon
82 name={field.tooltip.icon ?? 'info'}
83 color="gray500"
84 fill="gray500"
85 size={16}
86 tooltip={field.tooltip}
87 className="ml-[-4px]"
88 />
89 )}
90 </div>
91 }
92 >
93 <Select onValueChange={handleValueChange} value="" open={open} onOpenChange={handleSelectOpenChange} disabled={field.is_lock}>
94 <SelectTrigger className="flex w-full items-center border border-gray-400 rounded-md min-h-[40px] flex-wrap p-2 pr-8">
95 <div className="flex flex-wrap flex-1 items-center gap-1">
96 {selectedOptions.map((opt) => (
97 <div
98 key={opt.value}
99 className="flex items-center bg-gray-200 rounded-md px-2 py-1 text-sm"
100 >
101 {opt?.icon && (
102 <Icon
103 name={opt.icon}
104 size={16}
105 color="gray-700"
106 className="mr-1"
107 />
108 )}
109 {opt.label}
110 <span
111 ref={(el) => (removeButtonRefs.current[opt.value] = el)}
112 role="button"
113 tabIndex={0}
114 onClick={(e) => handleRemoveTag(e, opt.value)}
115 onKeyDown={(e) => {
116 if (e.key === 'Enter' || e.key === ' ') {
117 handleRemoveTag(e, opt.value);
118 }
119 }}
120 className="ml-2 p-1 text-gray-600 hover:text-gray-900 cursor-pointer"
121 >
122 <Icon name="times" size={18} color="gray-600" />
123 </span>
124 </div>
125 ))}
126 {selectedValues.length === 0 && (
127 <SelectValue placeholder={field.placeholder || __("Select options...", "ONBOARDING_WIZARD_TEXT_DOMAIN")} className="flex-1" />
128 )}
129 {selectedValues.length > 0 && (
130 <span className="flex-1 min-w-[50px] h-full" />
131 )}
132 </div>
133 </SelectTrigger>
134 <SelectContent>
135 {field.options.map((option) => {
136 const isSelected = selectedValues.includes(option.value);
137 return (
138 <SelectItem key={option.value} value={option.value}>
139 <div className="flex items-center w-full gap-2">
140 {option.icon && (
141 <Icon
142 name={option.icon}
143 size={16}
144 color="gray-700"
145 />
146 )}
147 <span>{option.label}</span>
148 </div>
149 {isSelected && (
150 <span className="absolute right-2 top-1/2 -translate-y-1/2 flex h-3.5 w-3.5 items-center justify-center">
151 <Icon name="CheckRoundedIcon" size={16} color="blue" />
152 </span>
153 )}
154 </SelectItem>
155 );
156 })}
157 </SelectContent>
158 </Select>
159 </FieldWrapper>
160 );
161 };
162
163 export default memo(MultiSelect);