@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.
Files changed (96) hide show
  1. package/LICENSE +5 -0
  2. package/README.md +43 -5
  3. package/components/attachment-image/README.md +118 -0
  4. package/components/attachment-image/index.js +105 -0
  5. package/components/attributes-panel/README.md +92 -0
  6. package/components/attributes-panel/index.js +324 -0
  7. package/components/badge/README.md +61 -0
  8. package/components/badge/index.js +105 -0
  9. package/components/block-appender/README.md +158 -0
  10. package/components/block-appender/index.js +135 -0
  11. package/components/block-extension/index.js +214 -0
  12. package/components/business-hours/SpecialHoursControl.js +211 -0
  13. package/components/business-hours/WeeklyHoursControl.js +419 -0
  14. package/components/business-hours/index.js +8 -0
  15. package/components/button/README.md +172 -0
  16. package/components/button/index.js +84 -0
  17. package/components/captcha-placeholder/CloudflareTurnstile.js +111 -0
  18. package/components/captcha-placeholder/GoogleRecaptcha.js +132 -0
  19. package/components/captcha-placeholder/HCaptcha.js +99 -0
  20. package/components/captcha-placeholder/README.md +228 -0
  21. package/components/captcha-placeholder/index.js +39 -0
  22. package/components/captcha-placeholder/styles.js +33 -0
  23. package/components/geocoder/AddressEntryControl.js +390 -0
  24. package/components/geocoder/GeocoderSearchControl.js +353 -0
  25. package/components/geocoder/countryOptions.js +66 -0
  26. package/components/geocoder/geocoder.js +400 -0
  27. package/components/geocoder/index.js +16 -0
  28. package/components/icon-picker/Icon.js +53 -0
  29. package/components/icon-picker/IconPicker.js +70 -0
  30. package/components/icon-picker/IconPickerModal.js +474 -0
  31. package/components/icon-picker/IconPickerToolbarButton.js +60 -0
  32. package/components/icon-picker/InlineIconPicker.js +92 -0
  33. package/components/icon-picker/api.js +181 -0
  34. package/components/icon-picker/config-bootstrap.js +108 -0
  35. package/components/icon-picker/hooks.js +157 -0
  36. package/components/icon-picker/index.js +39 -0
  37. package/components/icon-picker/load-script.js +33 -0
  38. package/components/icon-picker/loaders.js +79 -0
  39. package/components/icon-picker/store.js +223 -0
  40. package/components/icon-picker/styles.js +232 -0
  41. package/components/icon-picker/utils.js +138 -0
  42. package/components/layout/README.md +268 -0
  43. package/components/link-tools-panel/README.md +49 -0
  44. package/components/link-tools-panel/index.js +198 -0
  45. package/components/link-tools-panel/styles.js +90 -0
  46. package/components/media/README.md +288 -0
  47. package/components/media/SettingsMediaUpload.js +187 -0
  48. package/components/media/editor-media-upload.js +65 -0
  49. package/components/media/image-controls.js +196 -0
  50. package/components/media/index.js +13 -0
  51. package/components/media/inspector-media-upload.js +290 -0
  52. package/components/media/toolbar-media-upload.js +53 -0
  53. package/components/media/utils/aspect-ratios.js +118 -0
  54. package/components/meta/MetaPanel.js +75 -0
  55. package/components/meta/README.md +46 -0
  56. package/components/meta/index.js +6 -0
  57. package/components/meta/meta-gated-editor.js +282 -0
  58. package/components/query/README.md +340 -0
  59. package/components/query/cardTemplatePartPanel.js +69 -0
  60. package/components/query/cardTemplatePartSelect.js +165 -0
  61. package/components/query/cardTemplatePartUtils.js +119 -0
  62. package/components/query/columnCountControl.js +63 -0
  63. package/components/query/displayTypeSelect.js +52 -0
  64. package/components/query/examples.js +166 -0
  65. package/components/query/hideCurrentPostControl.js +47 -0
  66. package/components/query/index.js +20 -0
  67. package/components/query/manualPostSelector.js +125 -0
  68. package/components/query/manualTermSelector.js +141 -0
  69. package/components/query/orderBySelect.js +95 -0
  70. package/components/query/postTypeSelect.js +67 -0
  71. package/components/query/postsPerPageControl.js +51 -0
  72. package/components/query/selectionModeControl.js +61 -0
  73. package/components/query/taxonomySelect.js +139 -0
  74. package/components/query/utils.js +75 -0
  75. package/components/repeater/README.md +159 -0
  76. package/components/repeater/index.js +829 -0
  77. package/components/segmented-bar/README.md +30 -0
  78. package/components/segmented-bar/index.js +68 -0
  79. package/components/sortable-select/README.md +153 -0
  80. package/components/sortable-select/custom-sortable-token.js +127 -0
  81. package/components/sortable-select/index.js +693 -0
  82. package/components/style-picker/README.md +36 -0
  83. package/components/style-picker/index.js +92 -0
  84. package/components/variable-field/README.md +299 -0
  85. package/components/variable-field/index.js +592 -0
  86. package/components/variable-field/value-to-chip-html.js +38 -0
  87. package/components/variable-inserter/README.md +68 -0
  88. package/components/variable-inserter/index.js +168 -0
  89. package/constants/statusColors.js +1 -1
  90. package/editor.js +16 -0
  91. package/index.js +127 -0
  92. package/package.json +21 -13
  93. package/utils/kitaris-localize.js +128 -0
  94. package/utils/slug-aware-color-setter.js +27 -0
  95. package/build/index.asset.php +0 -20
  96. 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;