@siratik/wp-component-library 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +5 -0
- package/README.md +43 -5
- package/components/attachment-image/README.md +118 -0
- package/components/attachment-image/index.js +105 -0
- package/components/attributes-panel/README.md +92 -0
- package/components/attributes-panel/index.js +324 -0
- package/components/badge/README.md +61 -0
- package/components/badge/index.js +105 -0
- package/components/block-appender/README.md +158 -0
- package/components/block-appender/index.js +135 -0
- package/components/block-extension/index.js +214 -0
- package/components/business-hours/SpecialHoursControl.js +211 -0
- package/components/business-hours/WeeklyHoursControl.js +419 -0
- package/components/business-hours/index.js +8 -0
- package/components/button/README.md +172 -0
- package/components/button/index.js +84 -0
- package/components/captcha-placeholder/CloudflareTurnstile.js +111 -0
- package/components/captcha-placeholder/GoogleRecaptcha.js +132 -0
- package/components/captcha-placeholder/HCaptcha.js +99 -0
- package/components/captcha-placeholder/README.md +228 -0
- package/components/captcha-placeholder/index.js +39 -0
- package/components/captcha-placeholder/styles.js +33 -0
- package/components/geocoder/AddressEntryControl.js +390 -0
- package/components/geocoder/GeocoderSearchControl.js +353 -0
- package/components/geocoder/countryOptions.js +66 -0
- package/components/geocoder/geocoder.js +400 -0
- package/components/geocoder/index.js +16 -0
- package/components/icon-picker/Icon.js +53 -0
- package/components/icon-picker/IconPicker.js +70 -0
- package/components/icon-picker/IconPickerModal.js +474 -0
- package/components/icon-picker/IconPickerToolbarButton.js +60 -0
- package/components/icon-picker/InlineIconPicker.js +92 -0
- package/components/icon-picker/api.js +181 -0
- package/components/icon-picker/config-bootstrap.js +108 -0
- package/components/icon-picker/hooks.js +157 -0
- package/components/icon-picker/index.js +39 -0
- package/components/icon-picker/load-script.js +33 -0
- package/components/icon-picker/loaders.js +79 -0
- package/components/icon-picker/store.js +223 -0
- package/components/icon-picker/styles.js +232 -0
- package/components/icon-picker/utils.js +138 -0
- package/components/layout/README.md +268 -0
- package/components/link-tools-panel/README.md +49 -0
- package/components/link-tools-panel/index.js +198 -0
- package/components/link-tools-panel/styles.js +90 -0
- package/components/media/README.md +288 -0
- package/components/media/SettingsMediaUpload.js +187 -0
- package/components/media/editor-media-upload.js +65 -0
- package/components/media/image-controls.js +196 -0
- package/components/media/index.js +13 -0
- package/components/media/inspector-media-upload.js +290 -0
- package/components/media/toolbar-media-upload.js +53 -0
- package/components/media/utils/aspect-ratios.js +118 -0
- package/components/meta/MetaPanel.js +75 -0
- package/components/meta/README.md +46 -0
- package/components/meta/index.js +6 -0
- package/components/meta/meta-gated-editor.js +282 -0
- package/components/query/README.md +340 -0
- package/components/query/cardTemplatePartPanel.js +69 -0
- package/components/query/cardTemplatePartSelect.js +165 -0
- package/components/query/cardTemplatePartUtils.js +119 -0
- package/components/query/columnCountControl.js +63 -0
- package/components/query/displayTypeSelect.js +52 -0
- package/components/query/examples.js +166 -0
- package/components/query/hideCurrentPostControl.js +47 -0
- package/components/query/index.js +20 -0
- package/components/query/manualPostSelector.js +125 -0
- package/components/query/manualTermSelector.js +141 -0
- package/components/query/orderBySelect.js +95 -0
- package/components/query/postTypeSelect.js +67 -0
- package/components/query/postsPerPageControl.js +51 -0
- package/components/query/selectionModeControl.js +61 -0
- package/components/query/taxonomySelect.js +139 -0
- package/components/query/utils.js +75 -0
- package/components/repeater/README.md +159 -0
- package/components/repeater/index.js +829 -0
- package/components/segmented-bar/README.md +30 -0
- package/components/segmented-bar/index.js +68 -0
- package/components/sortable-select/README.md +153 -0
- package/components/sortable-select/custom-sortable-token.js +127 -0
- package/components/sortable-select/index.js +693 -0
- package/components/style-picker/README.md +36 -0
- package/components/style-picker/index.js +92 -0
- package/components/variable-field/README.md +299 -0
- package/components/variable-field/index.js +592 -0
- package/components/variable-field/value-to-chip-html.js +38 -0
- package/components/variable-inserter/README.md +68 -0
- package/components/variable-inserter/index.js +168 -0
- package/constants/statusColors.js +1 -1
- package/editor.js +16 -0
- package/index.js +127 -0
- package/package.json +21 -13
- package/utils/kitaris-localize.js +128 -0
- package/utils/slug-aware-color-setter.js +27 -0
- package/build/index.asset.php +0 -20
- package/build/index.js +0 -1242
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Address entry: a Search / Type it in toggle over one address.
|
|
3
|
+
*
|
|
4
|
+
* Search uses GeocoderSearchControl and shows a summary; Type it in shows the
|
|
5
|
+
* address fields (with line 2) and a Find on map button. Lookups only run on
|
|
6
|
+
* an explicit Search or Find on map click, never while typing.
|
|
7
|
+
*
|
|
8
|
+
* The value uses the organization location keys: address_street,
|
|
9
|
+
* address_street_2, address_city, address_state, address_zip,
|
|
10
|
+
* address_country, location_lat, location_lng, location_zoom. Extra keys
|
|
11
|
+
* are passed through untouched.
|
|
12
|
+
*
|
|
13
|
+
* @package Siratik/WP-Component-Library
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import styled from "@emotion/styled";
|
|
17
|
+
import {
|
|
18
|
+
Button,
|
|
19
|
+
Notice,
|
|
20
|
+
SelectControl,
|
|
21
|
+
TextControl,
|
|
22
|
+
__experimentalToggleGroupControl as ToggleGroupControl,
|
|
23
|
+
__experimentalToggleGroupControlOption as ToggleGroupControlOption,
|
|
24
|
+
} from "@wordpress/components";
|
|
25
|
+
import {
|
|
26
|
+
useCallback,
|
|
27
|
+
useEffect,
|
|
28
|
+
useMemo,
|
|
29
|
+
useRef,
|
|
30
|
+
useState,
|
|
31
|
+
} from "@wordpress/element";
|
|
32
|
+
import { __ } from "@wordpress/i18n";
|
|
33
|
+
import { GeocoderSearchControl } from "./GeocoderSearchControl";
|
|
34
|
+
import {
|
|
35
|
+
geocodeAddress,
|
|
36
|
+
toAddressQuery,
|
|
37
|
+
toOrganizationLocation,
|
|
38
|
+
} from "./geocoder";
|
|
39
|
+
import { getCountryOptions } from "./countryOptions";
|
|
40
|
+
|
|
41
|
+
// Zoom after picking a search result, and after Find on map (a specific address).
|
|
42
|
+
const SEARCH_RESULT_ZOOM = 12;
|
|
43
|
+
const FIND_ON_MAP_ZOOM = 16;
|
|
44
|
+
|
|
45
|
+
const StyledAddressEntry = styled.div`
|
|
46
|
+
display: grid;
|
|
47
|
+
gap: 16px;
|
|
48
|
+
|
|
49
|
+
.address-entry__summary {
|
|
50
|
+
padding: 16px;
|
|
51
|
+
background: #f9f9f9;
|
|
52
|
+
border: 1px solid #e0e0e0;
|
|
53
|
+
border-radius: 4px;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.address-entry__summary-title {
|
|
57
|
+
margin: 0 0 8px;
|
|
58
|
+
font-size: 13px;
|
|
59
|
+
font-weight: 600;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.address-entry__line {
|
|
63
|
+
margin: 0;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.address-entry__empty {
|
|
67
|
+
margin: 0;
|
|
68
|
+
color: #757575;
|
|
69
|
+
font-style: italic;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.address-entry__remove {
|
|
73
|
+
margin-top: 12px;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.address-entry__fields {
|
|
77
|
+
display: grid;
|
|
78
|
+
gap: 16px;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.address-entry__row {
|
|
82
|
+
display: grid;
|
|
83
|
+
grid-template-columns: 1fr 1fr;
|
|
84
|
+
gap: 12px;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.address-entry__actions {
|
|
88
|
+
display: flex;
|
|
89
|
+
flex-wrap: wrap;
|
|
90
|
+
gap: 8px;
|
|
91
|
+
align-items: center;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.address-entry__notice {
|
|
95
|
+
margin: 0;
|
|
96
|
+
}
|
|
97
|
+
`;
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* @param {Object} props
|
|
101
|
+
* @param {Object} props.value Current address/location object.
|
|
102
|
+
* @param {Function} props.onChange Called with the next object (all keys kept).
|
|
103
|
+
* @param {Function} [props.onRemove] Shows a Remove address button when set.
|
|
104
|
+
* @param {string} [props.searchControlId] Id for the search input.
|
|
105
|
+
* @param {string} [props.notFoundMessage] Shown when Find on map finds nothing.
|
|
106
|
+
* @param {boolean} [props.showFindOnMap] False hides Find on map (no pin to place).
|
|
107
|
+
* @param {string} [props.className]
|
|
108
|
+
*/
|
|
109
|
+
export function AddressEntryControl({
|
|
110
|
+
value,
|
|
111
|
+
onChange,
|
|
112
|
+
onRemove,
|
|
113
|
+
searchControlId = "address-entry-search",
|
|
114
|
+
notFoundMessage,
|
|
115
|
+
showFindOnMap = true,
|
|
116
|
+
className = "",
|
|
117
|
+
}) {
|
|
118
|
+
const location = value && typeof value === "object" ? value : {};
|
|
119
|
+
|
|
120
|
+
// Latest value for async work (Find on map) and quick successive edits.
|
|
121
|
+
const valueRef = useRef(location);
|
|
122
|
+
valueRef.current = location;
|
|
123
|
+
|
|
124
|
+
// "search" or "manual". Both edit the same address; switching clears nothing.
|
|
125
|
+
const [entryMode, setEntryMode] = useState("search");
|
|
126
|
+
|
|
127
|
+
// "idle" | "searching" | "not_found"
|
|
128
|
+
const [findStatus, setFindStatus] = useState("idle");
|
|
129
|
+
const findAbortRef = useRef(null);
|
|
130
|
+
|
|
131
|
+
useEffect(() => () => findAbortRef.current?.abort(), []);
|
|
132
|
+
|
|
133
|
+
const setField = useCallback(
|
|
134
|
+
(key) => (next) => {
|
|
135
|
+
setFindStatus("idle");
|
|
136
|
+
onChange({ ...valueRef.current, [key]: next });
|
|
137
|
+
},
|
|
138
|
+
[onChange],
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
// A result replaces the address and pin; line 2 is kept (results never have one).
|
|
142
|
+
const handleSelectResult = useCallback(
|
|
143
|
+
(result) => {
|
|
144
|
+
const parsed = toOrganizationLocation(result);
|
|
145
|
+
if (!parsed) {
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
onChange({
|
|
149
|
+
...valueRef.current,
|
|
150
|
+
address_street: parsed.street,
|
|
151
|
+
address_city: parsed.city,
|
|
152
|
+
address_state: parsed.state,
|
|
153
|
+
address_zip: parsed.zip,
|
|
154
|
+
address_country: parsed.country,
|
|
155
|
+
location_lat: parsed.lat,
|
|
156
|
+
location_lng: parsed.lng,
|
|
157
|
+
location_zoom: SEARCH_RESULT_ZOOM,
|
|
158
|
+
});
|
|
159
|
+
},
|
|
160
|
+
[onChange],
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
// One lookup, only on click. Line 2 is left out: suite numbers make lookups fail.
|
|
164
|
+
const handleFindOnMap = useCallback(async () => {
|
|
165
|
+
const current = valueRef.current;
|
|
166
|
+
const query = toAddressQuery(current);
|
|
167
|
+
const country = String(current.address_country || "").toLowerCase();
|
|
168
|
+
|
|
169
|
+
findAbortRef.current?.abort();
|
|
170
|
+
const controller = new AbortController();
|
|
171
|
+
findAbortRef.current = controller;
|
|
172
|
+
setFindStatus("searching");
|
|
173
|
+
|
|
174
|
+
const results = await geocodeAddress({
|
|
175
|
+
query,
|
|
176
|
+
limit: 1,
|
|
177
|
+
signal: controller.signal,
|
|
178
|
+
...(/^[a-z]{2}$/.test(country) ? { countryCodes: [country] } : {}),
|
|
179
|
+
});
|
|
180
|
+
if (controller.signal.aborted) {
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const match = results[0];
|
|
185
|
+
if (!match) {
|
|
186
|
+
setFindStatus("not_found");
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
setFindStatus("idle");
|
|
191
|
+
onChange({
|
|
192
|
+
...valueRef.current,
|
|
193
|
+
location_lat: match.lat,
|
|
194
|
+
location_lng: match.lng,
|
|
195
|
+
location_zoom: FIND_ON_MAP_ZOOM,
|
|
196
|
+
});
|
|
197
|
+
}, [onChange]);
|
|
198
|
+
|
|
199
|
+
const handleRemove = useCallback(() => {
|
|
200
|
+
setFindStatus("idle");
|
|
201
|
+
onRemove?.();
|
|
202
|
+
}, [onRemove]);
|
|
203
|
+
|
|
204
|
+
const countryOptions = useMemo(
|
|
205
|
+
() => getCountryOptions(location.address_country),
|
|
206
|
+
[location.address_country],
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
const hasAddress = Boolean(
|
|
210
|
+
location.address_street || location.address_city || location.address_zip,
|
|
211
|
+
);
|
|
212
|
+
const canFindOnMap = Boolean(
|
|
213
|
+
String(location.address_street || "").trim() ||
|
|
214
|
+
String(location.address_city || "").trim() ||
|
|
215
|
+
String(location.address_zip || "").trim(),
|
|
216
|
+
);
|
|
217
|
+
const cityLine = [
|
|
218
|
+
location.address_city,
|
|
219
|
+
[location.address_state, location.address_zip].filter(Boolean).join(" "),
|
|
220
|
+
location.address_country,
|
|
221
|
+
]
|
|
222
|
+
.filter(Boolean)
|
|
223
|
+
.join(", ");
|
|
224
|
+
|
|
225
|
+
return (
|
|
226
|
+
<StyledAddressEntry className={`address-entry ${className}`.trim()}>
|
|
227
|
+
<ToggleGroupControl
|
|
228
|
+
__nextHasNoMarginBottom
|
|
229
|
+
__next40pxDefaultSize
|
|
230
|
+
label={__("Address entry", "wp-component-library")}
|
|
231
|
+
value={entryMode}
|
|
232
|
+
onChange={(mode) => setEntryMode(mode || "search")}
|
|
233
|
+
isBlock
|
|
234
|
+
>
|
|
235
|
+
<ToggleGroupControlOption
|
|
236
|
+
value="search"
|
|
237
|
+
label={__("Search", "wp-component-library")}
|
|
238
|
+
/>
|
|
239
|
+
<ToggleGroupControlOption
|
|
240
|
+
value="manual"
|
|
241
|
+
label={__("Type it in", "wp-component-library")}
|
|
242
|
+
/>
|
|
243
|
+
</ToggleGroupControl>
|
|
244
|
+
|
|
245
|
+
{entryMode === "search" ? (
|
|
246
|
+
<>
|
|
247
|
+
<GeocoderSearchControl
|
|
248
|
+
id={searchControlId}
|
|
249
|
+
placeholder={__(
|
|
250
|
+
"Enter an address and press Search",
|
|
251
|
+
"wp-component-library",
|
|
252
|
+
)}
|
|
253
|
+
onSelect={handleSelectResult}
|
|
254
|
+
/>
|
|
255
|
+
|
|
256
|
+
<div className="address-entry__summary">
|
|
257
|
+
<p className="address-entry__summary-title">
|
|
258
|
+
{__("Address", "wp-component-library")}
|
|
259
|
+
</p>
|
|
260
|
+
{hasAddress ? (
|
|
261
|
+
<>
|
|
262
|
+
{location.address_street && (
|
|
263
|
+
<p className="address-entry__line">
|
|
264
|
+
{location.address_street}
|
|
265
|
+
</p>
|
|
266
|
+
)}
|
|
267
|
+
{location.address_street_2 && (
|
|
268
|
+
<p className="address-entry__line">
|
|
269
|
+
{location.address_street_2}
|
|
270
|
+
</p>
|
|
271
|
+
)}
|
|
272
|
+
{cityLine && (
|
|
273
|
+
<p className="address-entry__line">{cityLine}</p>
|
|
274
|
+
)}
|
|
275
|
+
{onRemove && (
|
|
276
|
+
<Button
|
|
277
|
+
variant="secondary"
|
|
278
|
+
size="small"
|
|
279
|
+
isDestructive
|
|
280
|
+
onClick={handleRemove}
|
|
281
|
+
className="address-entry__remove"
|
|
282
|
+
>
|
|
283
|
+
{__("Remove address", "wp-component-library")}
|
|
284
|
+
</Button>
|
|
285
|
+
)}
|
|
286
|
+
</>
|
|
287
|
+
) : (
|
|
288
|
+
<p className="address-entry__empty">
|
|
289
|
+
{__("No address selected", "wp-component-library")}
|
|
290
|
+
</p>
|
|
291
|
+
)}
|
|
292
|
+
</div>
|
|
293
|
+
</>
|
|
294
|
+
) : (
|
|
295
|
+
<div className="address-entry__fields">
|
|
296
|
+
<TextControl
|
|
297
|
+
__nextHasNoMarginBottom
|
|
298
|
+
__next40pxDefaultSize
|
|
299
|
+
label={__("Street address", "wp-component-library")}
|
|
300
|
+
value={location.address_street ?? ""}
|
|
301
|
+
onChange={setField("address_street")}
|
|
302
|
+
autoComplete="address-line1"
|
|
303
|
+
/>
|
|
304
|
+
<TextControl
|
|
305
|
+
__nextHasNoMarginBottom
|
|
306
|
+
__next40pxDefaultSize
|
|
307
|
+
label={__("Address line 2", "wp-component-library")}
|
|
308
|
+
placeholder={__("Suite, unit, floor", "wp-component-library")}
|
|
309
|
+
value={location.address_street_2 ?? ""}
|
|
310
|
+
onChange={setField("address_street_2")}
|
|
311
|
+
autoComplete="address-line2"
|
|
312
|
+
/>
|
|
313
|
+
<TextControl
|
|
314
|
+
__nextHasNoMarginBottom
|
|
315
|
+
__next40pxDefaultSize
|
|
316
|
+
label={__("City", "wp-component-library")}
|
|
317
|
+
value={location.address_city ?? ""}
|
|
318
|
+
onChange={setField("address_city")}
|
|
319
|
+
autoComplete="address-level2"
|
|
320
|
+
/>
|
|
321
|
+
<div className="address-entry__row">
|
|
322
|
+
<TextControl
|
|
323
|
+
__nextHasNoMarginBottom
|
|
324
|
+
__next40pxDefaultSize
|
|
325
|
+
label={__("State / province", "wp-component-library")}
|
|
326
|
+
value={location.address_state ?? ""}
|
|
327
|
+
onChange={setField("address_state")}
|
|
328
|
+
autoComplete="address-level1"
|
|
329
|
+
/>
|
|
330
|
+
<TextControl
|
|
331
|
+
__nextHasNoMarginBottom
|
|
332
|
+
__next40pxDefaultSize
|
|
333
|
+
label={__("ZIP / postal code", "wp-component-library")}
|
|
334
|
+
value={location.address_zip ?? ""}
|
|
335
|
+
onChange={setField("address_zip")}
|
|
336
|
+
autoComplete="postal-code"
|
|
337
|
+
/>
|
|
338
|
+
</div>
|
|
339
|
+
<SelectControl
|
|
340
|
+
__nextHasNoMarginBottom
|
|
341
|
+
__next40pxDefaultSize
|
|
342
|
+
label={__("Country", "wp-component-library")}
|
|
343
|
+
value={String(location.address_country || "US").toUpperCase()}
|
|
344
|
+
options={countryOptions}
|
|
345
|
+
onChange={setField("address_country")}
|
|
346
|
+
/>
|
|
347
|
+
<div className="address-entry__actions">
|
|
348
|
+
{showFindOnMap && (
|
|
349
|
+
<Button
|
|
350
|
+
variant="secondary"
|
|
351
|
+
size="compact"
|
|
352
|
+
onClick={handleFindOnMap}
|
|
353
|
+
isBusy={findStatus === "searching"}
|
|
354
|
+
disabled={!canFindOnMap || findStatus === "searching"}
|
|
355
|
+
accessibleWhenDisabled
|
|
356
|
+
>
|
|
357
|
+
{__("Find on map", "wp-component-library")}
|
|
358
|
+
</Button>
|
|
359
|
+
)}
|
|
360
|
+
{onRemove && hasAddress && (
|
|
361
|
+
<Button
|
|
362
|
+
variant="tertiary"
|
|
363
|
+
size="compact"
|
|
364
|
+
isDestructive
|
|
365
|
+
onClick={handleRemove}
|
|
366
|
+
>
|
|
367
|
+
{__("Remove address", "wp-component-library")}
|
|
368
|
+
</Button>
|
|
369
|
+
)}
|
|
370
|
+
</div>
|
|
371
|
+
{findStatus === "not_found" && (
|
|
372
|
+
<Notice
|
|
373
|
+
status="warning"
|
|
374
|
+
isDismissible={false}
|
|
375
|
+
className="address-entry__notice"
|
|
376
|
+
>
|
|
377
|
+
{notFoundMessage ||
|
|
378
|
+
__(
|
|
379
|
+
"Couldn't find that address. Check it and try again.",
|
|
380
|
+
"wp-component-library",
|
|
381
|
+
)}
|
|
382
|
+
</Notice>
|
|
383
|
+
)}
|
|
384
|
+
</div>
|
|
385
|
+
)}
|
|
386
|
+
</StyledAddressEntry>
|
|
387
|
+
);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
export default AddressEntryControl;
|