PluginProbe
Extendify / 0.5.0
Extendify v0.5.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.5.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 '@extendify/state/Templates'
6 import { useUserStore } from '@extendify/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="mb-1 text-sm">
79 {__('Site Type', 'extendify')}
80 </span>
81 <span className="text-xs font-light">{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('stroke-current text-gray-700', {
111 '-translate-x-1 rotate-90 transform': 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 text-xss font-medium uppercase text-gray-700">
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 'm-0 w-full cursor-pointer bg-transparent pl-0 text-left text-sm hover:text-wp-theme-500',
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 rounded bg-extendify-transparent-black">
171 <button
172 type="button"
173 onClick={() => setExpanded((expanded) => !expanded)}
174 className="button-focus m-0 flex w-full cursor-pointer items-center justify-between rounded bg-transparent p-4 text-gray-800 hover:bg-extendify-transparent-black-100">
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="max-h-96 overflow-y-auto p-4 pt-0">
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 m-0 w-full rounded border-0 bg-white p-3.5 py-2.5 text-sm"
196 placeholder={__('Search', 'extendify')}
197 />
198 <svg
199 className="pointer-events-none absolute top-2 right-2 hidden lg:block"
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 cursor-pointer bg-transparent pl-0 text-left text-sm text-wp-theme-500 hover: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