PluginProbe
WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance / trunk
WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance vtrunk
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 trunk, at vendor/team-updraft/lib-onboarding-wizard/Wizard/Onboarding/src/components/Fields/MultiSelectDropdown.tsx

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