PluginProbe
Extendify / 0.4.0
Extendify v0.4.0
3.2.1 3.2.0 3.1.6 3.1.5 3.1.4 3.1.3 3.1.2 3.1.1 3.1.0 3.0.6 3.0.5 3.0.4 trunk 0.1.0 0.10.0 0.10.1 0.10.2 0.11.0 0.11.1 0.2.0 0.3.0 0.3.1 0.4.0 0.5.0 0.6.0 All 127 releases
extendify / src / components / SiteTypeSelector.js

SiteTypeSelector.js in Extendify 0.4.0, at src/components/SiteTypeSelector.js

242 lines 10.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import { useEffect, useState, useRef, useMemo } from '@wordpress/element'
2 import { __ } from '@wordpress/i18n'
3 import classNames from 'classnames'
4 import Fuse from 'fuse.js'
5 import { useTemplatesStore } from '../state/Templates'
6 import { useUserStore } from '../state/User'
7
8 const searchMemo = new Map()
9
10 export const SiteTypeSelector = ({ value, setValue, terms }) => {
11 const preferredOptionsHistory = useUserStore(
12 (state) =>
13 state.preferredOptionsHistory?.siteType?.filter((t) => t.slug) ??
14 {},
15 )
16 const searchParams = useTemplatesStore((state) => state.searchParams)
17 const [expanded, setExpanded] = useState(false)
18 const searchRef = useRef()
19 const [fuzzy, setFuzzy] = useState({})
20 const [tempValue, setTempValue] = useState('')
21 const [visibleChoices, setVisibleChoices] = useState([])
22
23 const examples = useMemo(() => {
24 return terms
25 .filter((t) => t?.featured)
26 .sort((a, b) => {
27 if (a.slug < b.slug) return -1
28 if (a.slug > b.slug) return 1
29 return 0
30 })
31 }, [terms])
32
33 const updateSearch = (term) => {
34 setTempValue(term)
35 filter(term)
36 }
37
38 const filter = (term = '') => {
39 if (searchMemo.has(term)) {
40 setVisibleChoices(searchMemo.get(term))
41 return
42 }
43 const results = fuzzy.search(term)
44 searchMemo.set(
45 term,
46 results?.length ? results.map((t) => t.item) : examples,
47 )
48 setVisibleChoices(searchMemo.get(term))
49 }
50
51 const showRecent = () =>
52 visibleChoices === examples &&
53 Object.keys(preferredOptionsHistory).length > 0
54 const unknown = value.slug === 'unknown' || !value?.slug
55
56 useEffect(() => {
57 setFuzzy(
58 new Fuse(terms, {
59 keys: ['slug', 'title', 'keywords'],
60 minMatchCharLength: 2,
61 threshold: 0.3,
62 }),
63 )
64 }, [terms])
65
66 useEffect(() => {
67 if (!tempValue.length) setVisibleChoices(examples)
68 }, [examples, tempValue])
69
70 useEffect(() => {
71 expanded && searchRef.current.focus()
72 }, [expanded])
73
74 const contentHeader = (description) => {
75 return (
76 <>
77 <span className="flex flex-col text-left">
78 <span className="text-sm mb-1">
79 {__('Site Type', 'extendify')}
80 </span>
81 <span className="font-light text-xs">{description}</span>
82 </span>
83 <span className="flex items-center space-x-4">
84 {unknown && !expanded && (
85 <svg
86 className="text-wp-alert-red"
87 aria-hidden="true"
88 focusable="false"
89 width="21"
90 height="21"
91 viewBox="0 0 21 21"
92 fill="none"
93 xmlns="http://www.w3.org/2000/svg">
94 <path
95 className="stroke-current"
96 d="M10.9982 4.05371C7.66149 4.05371 4.95654 6.75866 4.95654 10.0954C4.95654 13.4321 7.66149 16.137 10.9982 16.137C14.3349 16.137 17.0399 13.4321 17.0399 10.0954C17.0399 6.75866 14.3349 4.05371 10.9982 4.05371V4.05371Z"
97 strokeWidth="1.25"
98 />
99 <path
100 className="fill-current"
101 d="M10.0205 12.8717C10.0205 12.3287 10.4508 11.8881 10.9938 11.8881C11.5368 11.8881 11.9774 12.3287 11.9774 12.8717C11.9774 13.4147 11.5368 13.8451 10.9938 13.8451C10.4508 13.8451 10.0205 13.4147 10.0205 12.8717Z"
102 />
103 <path
104 className="fill-current"
105 d="M11.6495 10.2591C11.6086 10.6177 11.3524 10.9148 10.9938 10.9148C10.625 10.9148 10.3791 10.6074 10.3483 10.2591L10.0205 7.31855C9.95901 6.81652 10.4918 6.34521 10.9938 6.34521C11.4959 6.34521 12.0286 6.81652 11.9774 7.31855L11.6495 10.2591Z"
106 />
107 </svg>
108 )}
109 <svg
110 className={classNames('text-gray-700 stroke-current', {
111 'transform rotate-90 -translate-x-1': expanded,
112 })}
113 aria-hidden="true"
114 focusable="false"
115 width="8"
116 height="13"
117 viewBox="0 0 8 13"
118 fill="none"
119 xmlns="http://www.w3.org/2000/svg">
120 <path
121 d="M1.24194 11.5952L6.24194 6.09519L1.24194 0.595215"
122 strokeWidth="1.5"
123 />
124 </svg>
125 </span>
126 </>
127 )
128 }
129
130 const choicesList = (choices, title = __('Suggestions', 'extendify')) => {
131 if (choices === examples) {
132 title = __('Examples', 'extendify')
133 }
134 return (
135 <>
136 <h4 className="mt-4 mb-2 text-left uppercase text-xss text-gray-700 font-medium">
137 {title}
138 </h4>
139 <ul className="m-0">
140 {choices.map((item) => {
141 const label = item?.title ?? item.slug
142 const current =
143 searchParams?.taxonomies?.siteType?.slug ===
144 item.slug
145 return (
146 <li
147 key={item.slug + item?.title}
148 className="m-0 mb-1">
149 <button
150 type="button"
151 className={classNames(
152 'w-full text-left text-sm bg-transparent hover:text-wp-theme-500 m-0 pl-0 cursor-pointer',
153 { 'text-gray-800': !current },
154 )}
155 onClick={() => {
156 setExpanded(false)
157 setValue(item)
158 }}>
159 {label}
160 </button>
161 </li>
162 )
163 })}
164 </ul>
165 </>
166 )
167 }
168
169 return (
170 <div className="w-full bg-extendify-transparent-black rounded">
171 <button
172 type="button"
173 onClick={() => setExpanded((expanded) => !expanded)}
174 className="flex items-center justify-between text-gray-800 button-focus w-full p-4 m-0 cursor-pointer bg-transparent hover:bg-extendify-transparent-black-100 rounded">
175 {contentHeader(
176 expanded
177 ? __('What kind of site is this?', 'extendify')
178 : value?.title ?? value.slug ?? 'Unknown',
179 )}
180 </button>
181 {expanded && (
182 <div className="p-4 pt-0 overflow-y-auto max-h-96">
183 <div className="relative my-2">
184 <label htmlFor="site-type-search" className="sr-only">
185 {__('Search', 'extendify')}
186 </label>
187 <input
188 ref={searchRef}
189 id="site-type-search"
190 value={tempValue ?? ''}
191 onChange={(event) =>
192 updateSearch(event.target.value)
193 }
194 type="text"
195 className="button-focus bg-white border-0 m-0 p-3.5 py-2.5 rounded text-sm w-full"
196 placeholder={__('Search', 'extendify')}
197 />
198 <svg
199 className="absolute top-2 right-2 hidden lg:block pointer-events-none"
200 xmlns="http://www.w3.org/2000/svg"
201 viewBox="0 0 24 24"
202 width="24"
203 height="24"
204 role="img"
205 aria-hidden="true"
206 focusable="false">
207 <path d="M13.5 6C10.5 6 8 8.5 8 11.5c0 1.1.3 2.1.9 3l-3.4 3 1 1.1 3.4-2.9c1 .9 2.2 1.4 3.6 1.4 3 0 5.5-2.5 5.5-5.5C19 8.5 16.5 6 13.5 6zm0 9.5c-2.2 0-4-1.8-4-4s1.8-4 4-4 4 1.8 4 4-1.8 4-4 4z"></path>
208 </svg>
209 </div>
210 {tempValue.length > 1 && visibleChoices === examples && (
211 <p className="text-left">
212 {__('Nothing found...', 'extendify')}
213 </p>
214 )}
215 {showRecent() && (
216 <div className="mb-8">
217 {choicesList(
218 preferredOptionsHistory,
219 __('Recent', 'extendify'),
220 )}
221 </div>
222 )}
223 {visibleChoices.length > 0 && (
224 <div>{choicesList(visibleChoices)}</div>
225 )}
226 {unknown ? null : (
227 <button
228 type="button"
229 className="mt-4 w-full text-left text-sm bg-transparent hover:text-wp-theme-500 pl-0 cursor-pointer text-wp-theme-500"
230 onClick={() => {
231 setExpanded(false)
232 setValue('Unknown')
233 }}>
234 {__('Reset', 'extendify')}
235 </button>
236 )}
237 </div>
238 )}
239 </div>
240 )
241 }
242