| 1 |
import React, { useCallback, useEffect, useState } from 'react'; |
| 2 |
import { __, sprintf } from "@wordpress/i18n"; |
| 3 |
import ReactSelect from "react-select"; |
| 4 |
import { |
| 5 |
isArray, |
| 6 |
isObject, |
| 7 |
merge, |
| 8 |
useBuilderContext, |
| 9 |
useOptions, |
| 10 |
valueExists, |
| 11 |
when, |
| 12 |
withLabel, |
| 13 |
wpFetch |
| 14 |
} from 'quickbuilder'; |
| 15 |
|
| 16 |
import Tippy from '@tippyjs/react'; // Install with `npm install @tippyjs/react` |
| 17 |
import 'tippy.js/dist/tippy.css'; // Tippy.js styles |
| 18 |
import '../../scss/model-select.scss'; |
| 19 |
|
| 20 |
const CustomOption = (props) => { |
| 21 |
const { |
| 22 |
data, |
| 23 |
innerRef, |
| 24 |
innerProps, |
| 25 |
isFocused, |
| 26 |
isSelected, |
| 27 |
selectProps, // Contains classNamePrefix |
| 28 |
} = props; |
| 29 |
|
| 30 |
// Construct class names using classNamePrefix |
| 31 |
const prefix = selectProps.classNamePrefix || 'react-select'; |
| 32 |
const optionClassName = `${prefix}__option`; |
| 33 |
const focusedClassName = isFocused ? `${optionClassName}--is-focused` : ''; |
| 34 |
const selectedClassName = isSelected ? `${optionClassName}--is-selected` : ''; |
| 35 |
|
| 36 |
return ( |
| 37 |
<div |
| 38 |
ref={innerRef} |
| 39 |
{...innerProps} |
| 40 |
className={`${optionClassName} ${focusedClassName} ${selectedClassName} custom-option`} |
| 41 |
style={{ |
| 42 |
cursor: 'pointer', |
| 43 |
|
| 44 |
}} |
| 45 |
> |
| 46 |
<span>{data.label}</span> |
| 47 |
{data.tooltip && ( |
| 48 |
<Tippy content={data.tooltip} placement='right'> |
| 49 |
<span className="tooltip-icon" style={{ marginLeft: '0px' }}> |
| 50 |
<svg |
| 51 |
width="14" |
| 52 |
height="14" |
| 53 |
viewBox="0 0 14 14" |
| 54 |
fill="none" |
| 55 |
xmlns="http://www.w3.org/2000/svg" |
| 56 |
> |
| 57 |
<path |
| 58 |
d="M6.33203 10.332H7.66536V6.33203H6.33203V10.332ZM6.9987 4.9987C7.18759 4.9987 7.34592 4.93481 7.4737 4.80703C7.60148 4.67925 7.66536 4.52092 7.66536 4.33203C7.66536 4.14314 7.60148 3.98481 7.4737 3.85703C7.34592 3.72925 7.18759 3.66536 6.9987 3.66536C6.80981 3.66536 6.65148 3.72925 6.5237 3.85703C6.39592 3.98481 6.33203 4.14314 6.33203 4.33203C6.33203 4.52092 6.39592 4.67925 6.5237 4.80703C6.65148 4.93481 6.80981 4.9987 6.9987 4.9987ZM6.9987 13.6654C6.07648 13.6654 5.20981 13.4904 4.3987 13.1404C3.58759 12.7904 2.88203 12.3154 2.28203 11.7154C1.68203 11.1154 1.20703 10.4098 0.857031 9.5987C0.507031 8.78759 0.332031 7.92092 0.332031 6.9987C0.332031 6.07648 0.507031 5.20981 0.857031 4.3987C1.20703 3.58759 1.68203 2.88203 2.28203 2.28203C2.88203 1.68203 3.58759 1.20703 4.3987 0.857031C5.20981 0.507031 6.07648 0.332031 6.9987 0.332031C7.92092 0.332031 8.78759 0.507031 9.5987 0.857031C10.4098 1.20703 11.1154 1.68203 11.7154 2.28203C12.3154 2.88203 12.7904 3.58759 13.1404 4.3987C13.4904 5.20981 13.6654 6.07648 13.6654 6.9987C13.6654 7.92092 13.4904 8.78759 13.1404 9.5987C12.7904 10.4098 12.3154 11.1154 11.7154 11.7154C11.1154 12.3154 10.4098 12.7904 9.5987 13.1404C8.78759 13.4904 7.92092 13.6654 6.9987 13.6654ZM6.9987 12.332C8.48759 12.332 9.7487 11.8154 10.782 10.782C11.8154 9.7487 12.332 8.48759 12.332 6.9987C12.332 5.50981 11.8154 4.2487 10.782 3.21536C9.7487 2.18203 8.48759 1.66536 6.9987 1.66536C5.50981 1.66536 4.2487 2.18203 3.21536 3.21536C2.18203 4.2487 1.66536 5.50981 1.66536 6.9987C1.66536 8.48759 2.18203 9.7487 3.21536 10.782C4.2487 11.8154 5.50981 12.332 6.9987 12.332Z" |
| 59 |
fill="#667085" |
| 60 |
/> |
| 61 |
</svg> |
| 62 |
</span> |
| 63 |
</Tippy> |
| 64 |
)} |
| 65 |
</div> |
| 66 |
); |
| 67 |
}; |
| 68 |
|
| 69 |
|
| 70 |
const EmbedModelSelect = (props) => { |
| 71 |
const builderContext = useBuilderContext(); |
| 72 |
let { id, name, multiple, placeholder, search = false, onChange, parentIndex } = props; |
| 73 |
const { options, selectedOption, setOptions, setData } = useOptions(props, 'options'); |
| 74 |
const [sOption, setSOption] = useState(null); |
| 75 |
const [isLoading, setIsLoading] = useState(false); |
| 76 |
const [isAjaxComplete, setIsAjaxComplete] = useState(false); |
| 77 |
|
| 78 |
const handleMenuOpen = () => { |
| 79 |
if (props.ajax && (!props.ajax.rules || when(props.ajax.rules, builderContext.values))) { |
| 80 |
setIsLoading(true); |
| 81 |
let data = {}; |
| 82 |
Object.keys(props?.ajax.data).map(singleData => { |
| 83 |
if (props?.ajax.data[singleData].indexOf('@') > -1) { |
| 84 |
let eligibleKey = props?.ajax.data[singleData].substr(1); |
| 85 |
data[singleData] = builderContext.values?.[eligibleKey]; |
| 86 |
} else { |
| 87 |
data[singleData] = props?.ajax.data[singleData]; |
| 88 |
} |
| 89 |
}); |
| 90 |
if (!isAjaxComplete) { |
| 91 |
return wpFetch({ |
| 92 |
path: props?.ajax.api, |
| 93 |
data: data |
| 94 |
}).then((response) => { |
| 95 |
setIsLoading(false); |
| 96 |
const arrayMerge = merge(props.options, response, 'value'); |
| 97 |
builderContext.setFormField([...parentIndex, 'options'], arrayMerge); |
| 98 |
setData({ |
| 99 |
options: arrayMerge, |
| 100 |
parentIndex: [...parentIndex, 'options'] |
| 101 |
}); |
| 102 |
return response; |
| 103 |
}); |
| 104 |
} |
| 105 |
} |
| 106 |
}; |
| 107 |
|
| 108 |
const handleMenuClose = () => { |
| 109 |
setIsLoading(false); |
| 110 |
}; |
| 111 |
|
| 112 |
useEffect(() => { |
| 113 |
if (!isArray(sOption) && isObject(sOption)) { |
| 114 |
onChange({ |
| 115 |
target: { |
| 116 |
type: 'select', |
| 117 |
name, |
| 118 |
value: sOption.value, |
| 119 |
options, |
| 120 |
multiple |
| 121 |
}, |
| 122 |
}); |
| 123 |
} |
| 124 |
if (isArray(sOption)) { |
| 125 |
onChange({ |
| 126 |
target: { |
| 127 |
type: 'select', |
| 128 |
name, |
| 129 |
value: sOption.map(item => item.value), |
| 130 |
options, |
| 131 |
multiple |
| 132 |
}, |
| 133 |
}); |
| 134 |
} |
| 135 |
}, [sOption]); |
| 136 |
|
| 137 |
useEffect(() => { |
| 138 |
handleMenuOpen(); |
| 139 |
}, []); |
| 140 |
|
| 141 |
useEffect(() => { |
| 142 |
if (props?.menuOpen) { |
| 143 |
handleMenuOpen(); |
| 144 |
} |
| 145 |
}, [props?.menuOpen]); |
| 146 |
|
| 147 |
const handleOptionChange = useCallback((option) => { |
| 148 |
if (isArray(option) && props?.filterValue?.length > 0) { |
| 149 |
const origialValues = option; |
| 150 |
let values = origialValues; |
| 151 |
|
| 152 |
let filterValue = props?.filterValue ?? ['all']; |
| 153 |
if (!isArray(filterValue)) { |
| 154 |
filterValue = [filterValue]; |
| 155 |
} |
| 156 |
|
| 157 |
if (origialValues?.length > 1 && valueExists(origialValues.map(item => item.value), filterValue)) { |
| 158 |
values = origialValues.filter((item) => !filterValue.includes(item?.value)); |
| 159 |
} |
| 160 |
|
| 161 |
option = values; |
| 162 |
} |
| 163 |
|
| 164 |
setSOption(option); |
| 165 |
}, [name, id, parentIndex]); |
| 166 |
|
| 167 |
// Conditional components prop |
| 168 |
const selectComponents = props.options_tooltip |
| 169 |
? { Option: CustomOption } |
| 170 |
: undefined; |
| 171 |
|
| 172 |
return ( |
| 173 |
<div className="wprf-select-wrapper"> |
| 174 |
<ReactSelect |
| 175 |
isDisabled={props?.disable} |
| 176 |
className="wprf-select" |
| 177 |
classNamePrefix="wprf-select" |
| 178 |
isSearchable={search ?? false} |
| 179 |
id={id} |
| 180 |
name={name} |
| 181 |
isMulti={multiple ?? false} |
| 182 |
placeholder={placeholder} |
| 183 |
isLoading={isLoading} |
| 184 |
options={options} |
| 185 |
value={selectedOption} |
| 186 |
onMenuOpen={handleMenuOpen} |
| 187 |
onMenuClose={handleMenuClose} |
| 188 |
isOptionDisabled={(option) => option?.disabled} |
| 189 |
onChange={handleOptionChange} |
| 190 |
components={selectComponents} // Conditional rendering of tooltip-enabled options |
| 191 |
|
| 192 |
/> |
| 193 |
|
| 194 |
{betterdocs_admin?.count_new_docs > 0 && ( |
| 195 |
<div className='model-wrapper'> |
| 196 |
<div className="sync-docs-wrapper"> |
| 197 |
<a href={`?page=betterdocs-settings&sync-new-docs=1&_nonce=${betterdocs_admin?.sync_nonce}`}> |
| 198 |
{sprintf( |
| 199 |
__('Sync pending posts (%d)', 'betterdocs'), |
| 200 |
betterdocs_admin?.count_new_docs |
| 201 |
)} |
| 202 |
</a> |
| 203 |
</div> |
| 204 |
</div> |
| 205 |
)} |
| 206 |
</div> |
| 207 |
); |
| 208 |
}; |
| 209 |
|
| 210 |
export default withLabel(EmbedModelSelect); |
| 211 |
|