@tfehotels/tfe-gatsby-library 1.0.0

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 (84) hide show
  1. package/README.md +41 -0
  2. package/lib/index.browser.cjs +2 -0
  3. package/lib/index.browser.cjs.map +1 -0
  4. package/lib/index.browser.d.ts +640 -0
  5. package/lib/index.browser.esm.js +2 -0
  6. package/lib/index.browser.esm.js.map +1 -0
  7. package/lib/index.node.cjs +2 -0
  8. package/lib/index.node.cjs.map +1 -0
  9. package/lib/index.node.d.ts +116 -0
  10. package/lib/index.node.esm.js +2 -0
  11. package/lib/index.node.esm.js.map +1 -0
  12. package/package.json +84 -0
  13. package/src/browser/api/button/index.tsx +16 -0
  14. package/src/browser/api/button/types.ts +7 -0
  15. package/src/browser/api/checkbox/index.tsx +74 -0
  16. package/src/browser/api/checkbox/types.tsx +21 -0
  17. package/src/browser/api/country-prefix/index.tsx +150 -0
  18. package/src/browser/api/country-prefix/types.tsx +17 -0
  19. package/src/browser/api/form/index.tsx +330 -0
  20. package/src/browser/api/form/types.ts +31 -0
  21. package/src/browser/api/google-places/index.tsx +90 -0
  22. package/src/browser/api/google-places/types.ts +24 -0
  23. package/src/browser/api/icon/index.tsx +26 -0
  24. package/src/browser/api/icon/types.tsx +24 -0
  25. package/src/browser/api/image/index.tsx +91 -0
  26. package/src/browser/api/image/types.ts +11 -0
  27. package/src/browser/api/input/index.tsx +44 -0
  28. package/src/browser/api/input/types.ts +10 -0
  29. package/src/browser/api/link/index.tsx +54 -0
  30. package/src/browser/api/link/types.ts +24 -0
  31. package/src/browser/api/linklist/index.tsx +17 -0
  32. package/src/browser/api/linklist/types.ts +6 -0
  33. package/src/browser/api/select/index.tsx +99 -0
  34. package/src/browser/api/select/types.ts +24 -0
  35. package/src/browser/api/svg/index.tsx +8 -0
  36. package/src/browser/api/svg/types.ts +8 -0
  37. package/src/browser/api/text/index.tsx +14 -0
  38. package/src/browser/api/text/types.ts +12 -0
  39. package/src/browser/api/textarea/index.tsx +12 -0
  40. package/src/browser/api/title/index.tsx +19 -0
  41. package/src/browser/api/title/types.ts +10 -0
  42. package/src/browser/api/types.ts +245 -0
  43. package/src/browser/carousel/buttons/index.tsx +81 -0
  44. package/src/browser/carousel/buttons/types.ts +15 -0
  45. package/src/browser/carousel/dots/index.tsx +53 -0
  46. package/src/browser/carousel/dots/types.ts +14 -0
  47. package/src/browser/carousel/index.tsx +131 -0
  48. package/src/browser/carousel/types.ts +21 -0
  49. package/src/browser/markdown/index.tsx +41 -0
  50. package/src/browser/markdown/types.ts +11 -0
  51. package/src/browser/modal/index.tsx +35 -0
  52. package/src/browser/modal/types.ts +9 -0
  53. package/src/browser/spinner/index.tsx +19 -0
  54. package/src/browser/spinner/types.ts +5 -0
  55. package/src/browser/toast/index.tsx +84 -0
  56. package/src/browser/use_viewport/index.tsx +34 -0
  57. package/src/browser/use_viewport/types.ts +5 -0
  58. package/src/browser/utils/animation.ts +12 -0
  59. package/src/browser/utils/booking_engine.ts +180 -0
  60. package/src/browser/utils/eclub.ts +30 -0
  61. package/src/browser/utils/forms.ts +76 -0
  62. package/src/browser/utils/hotel.ts +25 -0
  63. package/src/browser/utils/image.ts +63 -0
  64. package/src/browser/utils/location.ts +213 -0
  65. package/src/browser/utils/notifications.tsx +25 -0
  66. package/src/browser/utils/number.ts +6 -0
  67. package/src/browser/utils/requests.ts +2 -0
  68. package/src/browser/utils/search.ts +25 -0
  69. package/src/browser/utils/string.ts +9 -0
  70. package/src/browser/utils/types.ts +106 -0
  71. package/src/browser/utils/url.ts +116 -0
  72. package/src/browser/utils/viewport.ts +59 -0
  73. package/src/index.browser.ts +103 -0
  74. package/src/index.node.ts +16 -0
  75. package/src/node/api/index.ts +174 -0
  76. package/src/node/api/types.ts +19 -0
  77. package/src/node/build/index.ts +142 -0
  78. package/src/node/build/types.ts +5 -0
  79. package/src/node/config/index.ts +149 -0
  80. package/src/node/form/index.ts +23 -0
  81. package/src/node/form/types.ts +3 -0
  82. package/src/node/property/index.ts +78 -0
  83. package/src/node/property/types.ts +25 -0
  84. package/src/node/url/index.ts +8 -0
@@ -0,0 +1,150 @@
1
+ import React, {
2
+ startTransition,
3
+ useCallback,
4
+ useEffect,
5
+ useRef,
6
+ useState,
7
+ } from "react"
8
+ import { PhonePropsType } from "./types"
9
+ import { countryPrefixes } from "../../utils/location"
10
+ import { scrollToChild } from "../../utils/viewport"
11
+ import Markdown from "../../markdown"
12
+
13
+ export default function CountryPrefix({
14
+ className,
15
+ label,
16
+ description,
17
+ input_name,
18
+ onClick,
19
+ onChange,
20
+ value,
21
+ ...props
22
+ }: PhonePropsType) {
23
+ const [showOptions, setShowOptions] = useState<boolean>(false)
24
+ const [focused, setFocused] = useState<number>(0)
25
+ const [input, setInput] = useState("")
26
+ const listRef = useRef<HTMLUListElement>(null)
27
+
28
+ const handleSelection = useCallback(
29
+ (focused: number) => {
30
+ onChange?.(countryPrefixes[focused][1] as any)
31
+ startTransition(() => {
32
+ setShowOptions(false)
33
+ setFocused(focused)
34
+ })
35
+ },
36
+ [onChange, countryPrefixes]
37
+ )
38
+
39
+ const handleKeyDown = (e: React.KeyboardEvent<HTMLUListElement>) => {
40
+ e.stopPropagation()
41
+ e.preventDefault()
42
+ const { key } = e
43
+ if (key === "ArrowDown" || key === "ArrowUp") {
44
+ // Logic to navigate through the list items
45
+ startTransition(() => {
46
+ setFocused(
47
+ key === "ArrowDown"
48
+ ? Math.min(focused + 1, countryPrefixes.length - 1)
49
+ : Math.max(focused - 1, 0)
50
+ )
51
+ setInput("")
52
+ })
53
+ } else if (key === "Escape") {
54
+ startTransition(() => setShowOptions(false))
55
+ } else if (key === "Enter") {
56
+ handleSelection(focused)
57
+ } else if (key === "Backspace") {
58
+ startTransition(() => setInput(input.slice(0, -1)))
59
+ } else if (/^[ \p{L}]+$/u.test(key)) {
60
+ startTransition(() => setInput(input + key))
61
+ }
62
+ }
63
+
64
+ const scrollToFocusedCountry = useCallback(() => {
65
+ if (!listRef.current) return
66
+ const element = listRef.current.querySelector(
67
+ `#${props.name ?? "country-prefix"}__${countryPrefixes[focused][0]}`
68
+ )
69
+ if (!element) return
70
+ scrollToChild(listRef.current, element as HTMLElement)
71
+ }, [focused, countryPrefixes])
72
+
73
+ // Focus selected option based on input
74
+ useEffect(() => {
75
+ if (!input) return
76
+ let foundIndex = countryPrefixes.findIndex(row =>
77
+ row[2].toLowerCase().startsWith(input.toLowerCase())
78
+ )
79
+ if (foundIndex >= 0) {
80
+ startTransition(() => setFocused(foundIndex))
81
+ } else {
82
+ startTransition(() => setInput(""))
83
+ }
84
+ }, [input])
85
+
86
+ useEffect(() => {
87
+ scrollToFocusedCountry()
88
+ }, [focused])
89
+
90
+ useEffect(() => {
91
+ if (!listRef.current) return
92
+ if (showOptions) {
93
+ // Autofocus on open dropdown
94
+ listRef.current.focus()
95
+ return
96
+ }
97
+
98
+ startTransition(() => setInput(""))
99
+ }, [showOptions])
100
+
101
+ return (
102
+ <label
103
+ className={`country-prefix ${className ? ` ${className}` : ""}`}
104
+ >
105
+ {!!label && <span>{label}</span>}
106
+ <div
107
+ className="selected-prefix"
108
+ onClick={() => {
109
+ startTransition(() => setShowOptions(true))
110
+ listRef?.current?.focus()
111
+ }}
112
+ >
113
+ +{value ?? countryPrefixes[0][1]}
114
+ &gt;
115
+ </div>
116
+ <ul
117
+ className={showOptions ? "show" : ""}
118
+ ref={listRef}
119
+ role="listbox"
120
+ tabIndex={-1}
121
+ onKeyDown={handleKeyDown}
122
+ aria-activedescendant={`${props.name ?? "country-prefix"}__${
123
+ countryPrefixes[focused][0]
124
+ }`}
125
+ >
126
+ {countryPrefixes.map(([countryCode, prefix, name], k) => {
127
+ return (
128
+ <li
129
+ className={k === focused ? "selected" : ""}
130
+ role="option"
131
+ onClick={() => handleSelection(k)}
132
+ aria-selected={k === focused}
133
+ aria-label={`${name} +${prefix}`}
134
+ id={`${props.name ?? "country-prefix"}__${countryCode}`}
135
+ key={`${props.name ?? "country-prefix"}__${countryCode}`}
136
+ >
137
+ {name} +{prefix}
138
+ </li>
139
+ )
140
+ })}
141
+ </ul>
142
+ <input type="hidden" name={input_name} />
143
+ {!!description && (
144
+ <div>
145
+ <Markdown>{description}</Markdown>
146
+ </div>
147
+ )}
148
+ </label>
149
+ )
150
+ }
@@ -0,0 +1,17 @@
1
+ import { MouseEventHandler, ChangeEventHandler } from "react"
2
+
3
+ export interface PhonePropsType {
4
+ onClick?: MouseEventHandler<HTMLLabelElement>
5
+ onChange?: ChangeEventHandler<HTMLLabelElement>
6
+ className?: string
7
+ value: string
8
+ width?: number
9
+ height?: number
10
+ id?: string
11
+ name?: string
12
+ input_name?: string
13
+ color?: string
14
+ accept?: string
15
+ label?: string
16
+ description?: string
17
+ }
@@ -0,0 +1,330 @@
1
+ import { useGoogleReCaptcha } from "react-google-recaptcha-v3"
2
+ import React, { startTransition, useCallback, useEffect } from "react"
3
+ import { ErrorsType, FormPropsType } from "./types"
4
+ import { toastMsg } from "../../utils/notifications"
5
+ import {
6
+ INPUT_FIELDS,
7
+ getInputEventValue,
8
+ getInputHumanValue,
9
+ json_request,
10
+ } from "../../utils/forms"
11
+ import type {
12
+ FormFieldsType,
13
+ FormItemType,
14
+ } from "../../utils/types"
15
+ import classNames from "classnames"
16
+ import Markdown from "../../markdown"
17
+ import Spinner from "../../spinner"
18
+
19
+ const Element: any = <div/> //React.lazy(() => import(<div/>))
20
+
21
+
22
+ export default function Form({
23
+ className,
24
+ form,
25
+ setForm,
26
+ errors,
27
+ setErrors,
28
+ beforeSubmit,
29
+ formatParams,
30
+ validate,
31
+ validateResponse,
32
+ onSuccess,
33
+ onFocus,
34
+ onBlur,
35
+ onClick,
36
+ onChange,
37
+ onError,
38
+ afterSubmit,
39
+ loading,
40
+ }: FormPropsType) {
41
+ const { executeRecaptcha } = useGoogleReCaptcha()
42
+
43
+ const showErrors = (errors: ErrorsType, showInputName: boolean = true) => (
44
+ <div>
45
+ <ul className={"error"}>
46
+ {Object.entries(errors).map(([input, error], k: number) => (
47
+ <li key={`error-form${k}`}>
48
+ X
49
+ <span>
50
+ {showInputName ? <strong>{input}:</strong> : ""} {error}
51
+ </span>
52
+ </li>
53
+ ))}
54
+ </ul>
55
+ </div>
56
+ )
57
+
58
+ const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
59
+ e.preventDefault()
60
+ let errors: any = {}
61
+ let params = Object.values(form.fields).reduce((acc, field) => {
62
+ if (INPUT_FIELDS.has(field.type)) {
63
+ // @ts-ignore
64
+ if (field.required && !field.value) {
65
+ //@ts-ignore
66
+ errors[field.input_name] = `${
67
+ //@ts-ignore
68
+ field.label ? field.label : field.placeholder
69
+ } is required`
70
+ return acc
71
+ }
72
+ // @ts-ignore
73
+ // Skip non-checked radio buttons
74
+ if (field.input_type === "radio" && !field.checked) {
75
+ return acc
76
+ }
77
+ // @ts-ignore
78
+ acc[field.input_name] = getInputHumanValue(field.type, field.value)
79
+ }
80
+ return acc
81
+ }, {} as any)
82
+
83
+ // Get reCAPTCHA v3 token
84
+ if (form.settings.recaptcha_v3) {
85
+ params.recaptcha_token = await verifyRecaptcha()
86
+ if (!params.recaptcha_token) {
87
+ errors.recaptcha = "reCAPTCHA failed!"
88
+ }
89
+ }
90
+
91
+ // Check recaptcha v2 if it's within the fields
92
+ if (form.fields.recaptcha && !params.recaptcha) {
93
+ errors.recaptcha = "Recaptcha is mandatory"
94
+ }
95
+
96
+ if (validate) {
97
+ errors = { ...errors, ...validate(form.fields, params) }
98
+ }
99
+ if (Object.keys(errors).length) {
100
+ startTransition(() => {
101
+ setErrors(errors)
102
+ toastMsg(showErrors(errors, false))
103
+ })
104
+ return
105
+ }
106
+
107
+ if (beforeSubmit) {
108
+ beforeSubmit(form)
109
+ }
110
+
111
+ if (formatParams) {
112
+ params = formatParams(params)
113
+ }
114
+
115
+ // If is not an AJAX request, the page will be redirected into the form submit URL
116
+ if (!form.settings.form_ajax_request) {
117
+ // @ts-ignore
118
+ return e.target.submit()
119
+ }
120
+ const response = await json_request(
121
+ form.settings.form_submit,
122
+ params,
123
+ form.settings.form_method,
124
+ form.settings.form_recipient
125
+ ? {
126
+ "recipient-id": `${form.settings.form_recipient}`,
127
+ }
128
+ : {}
129
+ )
130
+ startTransition(() => {
131
+ // ON SUCCESS:
132
+ if (
133
+ response.status === 200 &&
134
+ (!validateResponse || validateResponse(response))
135
+ ) {
136
+ if (onSuccess) {
137
+ // Execute onSuccess action when defined:
138
+ onSuccess(response, form.settings.form_success_msg)
139
+ } else {
140
+ // Otherwise just show success message:
141
+ toastMsg(
142
+ form.settings.form_success_msg ? (
143
+ <Markdown>{form.settings.form_success_msg}</Markdown>
144
+ ) : (
145
+ "Success! Your form has been successfully sent. Thank you for reaching out, we will get back to you as soon as possible."
146
+ ),
147
+ "success"
148
+ )
149
+ }
150
+
151
+ if (form.settings.form_redirection) {
152
+ setTimeout(() => {
153
+ window.location.href = form.settings.form_redirection as string
154
+ }, 3000)
155
+ }
156
+
157
+ // Reset all form values:
158
+ setForm({
159
+ ...form,
160
+ fields: Object.entries(form.fields).reduce((acc, [name, field]) => {
161
+ acc[name] = INPUT_FIELDS.has(field.type) // @ts-ignore
162
+ ? { ...field, value: "" }
163
+ : field
164
+ return acc
165
+ }, {} as FormFieldsType),
166
+ })
167
+ // ON ERROR:
168
+ } else if (onError) {
169
+ onError(response)
170
+ // ELSE ON ERROR:
171
+ } else if (response.errors) {
172
+ let newErrors: ErrorsType = Object.entries(response.errors).reduce(
173
+ (acc, [inputName, errorList]) => {
174
+ acc[inputName] = errorList.join(", ")
175
+ return acc
176
+ },
177
+ {} as ErrorsType
178
+ )
179
+ toastMsg(showErrors(newErrors))
180
+ setErrors(newErrors)
181
+ // ELSE ON ERROR:
182
+ } else if (response.error) {
183
+ toastMsg(
184
+ showErrors(
185
+ {
186
+ failed: response.error ? (
187
+ <Markdown>{response.error}</Markdown>
188
+ ) : (
189
+ "Oops. Something went wrong. Please try again later, or contact us at members@tfehotels.com"
190
+ ),
191
+ },
192
+ false
193
+ )
194
+ )
195
+ }
196
+ })
197
+
198
+ if (afterSubmit) {
199
+ afterSubmit(response)
200
+ }
201
+ }
202
+
203
+ const verifyRecaptcha = useCallback(async () => {
204
+ if (!executeRecaptcha) {
205
+ console.log("Execute recaptcha not yet available")
206
+ return
207
+ }
208
+ return await executeRecaptcha(form.settings.recaptcha_action)
209
+ }, [executeRecaptcha])
210
+
211
+ useEffect(() => {
212
+ if (form.settings.recaptcha_v3) {
213
+ verifyRecaptcha()
214
+ }
215
+ }, [form.settings.recaptcha_v3, verifyRecaptcha])
216
+
217
+ const buildFormElement = (item: FormItemType) => {
218
+ if (typeof item === "string") {
219
+ const field: any = form.fields[item]
220
+ return (
221
+ <div
222
+ className={`${field.type} ${field.type}-${field.name}`}
223
+ key={`ff-${form.settings.pk}-${item}`}
224
+ >
225
+ {INPUT_FIELDS.has(field.type) ? (
226
+ <Element
227
+ className={classNames({
228
+ [field.data.class]: Boolean(field.data.class),
229
+ error: Boolean(errors[item]),
230
+ })}
231
+ onFocus={(e: React.FocusEvent) => {
232
+ if (onFocus) onFocus(e)
233
+ }}
234
+ onBlur={(e: React.FocusEvent) => {
235
+ if (onBlur) onBlur(e)
236
+ }}
237
+ onClick={(e: React.MouseEvent) => {
238
+ // Only for checkboxes/Radios
239
+ if (["checkbox", "radio"].includes(field.input_type)) {
240
+ let newForm = { ...form }
241
+ if (field.input_type === "checkbox") {
242
+ newForm.fields = {
243
+ ...form.fields,
244
+ [item]: { ...field, value: !field.value },
245
+ }
246
+ } else {
247
+ // Radio buttons (sets the "checked" flag on all radio buttons with same name)
248
+ newForm.fields = Object.entries(form.fields).reduce(
249
+ (acc, [fieldName, attrs]) => {
250
+ acc[fieldName] = attrs //@ts-ignore
251
+ if (attrs.input_name === field.input_name) {
252
+ //@ts-ignore
253
+ acc[fieldName].checked = //@ts-ignore
254
+ field.value === acc[fieldName].value
255
+ }
256
+ return acc
257
+ },
258
+ {} as FormFieldsType
259
+ )
260
+ }
261
+ startTransition(() => {
262
+ setForm(newForm)
263
+ if (errors[item]) {
264
+ setErrors({}) // reset errors
265
+ }
266
+ })
267
+ }
268
+ if (onClick) onClick(e)
269
+ }}
270
+ checked={
271
+ field.input_type === "radio" ? !!field.checked : undefined
272
+ }
273
+ onChange={(e: any) => {
274
+ if (!["checkbox", "radio"].includes(field.input_type)) {
275
+ startTransition(() => {
276
+ setForm({
277
+ ...form,
278
+ fields: {
279
+ ...form.fields,
280
+ [item]: {
281
+ ...field,
282
+ value: getInputEventValue(field.type, e),
283
+ data: e.target?.data
284
+ ? { ...field.data, ...e.target.data }
285
+ : field.data,
286
+ },
287
+ },
288
+ })
289
+
290
+ if (errors[item]) {
291
+ setErrors({}) // reset errors
292
+ }
293
+ })
294
+ }
295
+ if (onChange) onChange(e)
296
+ }}
297
+ {...{
298
+ ...field,
299
+ value: field.value ? field.value : "",
300
+ }}
301
+ />
302
+ ) : (
303
+ // Display Titles, Texts, Links, etc..
304
+ <Element {...field} />
305
+ )}
306
+ </div>
307
+ )
308
+ }
309
+ // Otherwise is a section
310
+ return (
311
+ <div
312
+ className={`form-section ${item.name}`}
313
+ key={`ff-${form.settings.pk}-${item.name}`}
314
+ >
315
+ {item.children.map(buildFormElement)}
316
+ </div>
317
+ )
318
+ }
319
+ return (
320
+ <form
321
+ className={className}
322
+ action={form.settings.form_submit}
323
+ method={form.settings.form_method}
324
+ onSubmit={e => handleSubmit(e)}
325
+ noValidate
326
+ >
327
+ {loading ? <Spinner color="#00b5ee" /> : form.items.map(buildFormElement)}
328
+ </form>
329
+ )
330
+ }
@@ -0,0 +1,31 @@
1
+ import { ReactNode } from "react";
2
+ import { InputType } from "../types";
3
+ import { FormType } from "../../utils/types";
4
+
5
+ export interface InputsType {
6
+ [field: string]: InputType;
7
+ }
8
+
9
+ export interface ErrorsType {
10
+ [field: string]: string | ReactNode;
11
+ }
12
+
13
+ export interface FormPropsType {
14
+ className?: string;
15
+ form: FormType;
16
+ setForm: Function;
17
+ errors: ErrorsType;
18
+ loading?: boolean;
19
+ setErrors: Function;
20
+ beforeSubmit?: Function;
21
+ formatParams?: Function;
22
+ validate?: Function;
23
+ validateResponse?: Function;
24
+ onSuccess?: Function;
25
+ onFocus?: Function;
26
+ onBlur?: Function;
27
+ onClick?: Function;
28
+ onChange?: Function;
29
+ onError?: Function;
30
+ afterSubmit?: Function;
31
+ }
@@ -0,0 +1,90 @@
1
+ import React, { useEffect, useState } from "react"
2
+ import { usePlacesWidget } from "react-google-autocomplete"
3
+ import { GooglePlacesPropsType, PlaceType } from "./types"
4
+ import { countryPrefixes } from "../../utils/location"
5
+
6
+ const getCountryCode = (place: google.maps.places.PlaceResult) => {
7
+ if (place.address_components) {
8
+ for (const component of place.address_components) {
9
+ if (component.types[0] == "country") {
10
+ return component.short_name
11
+ }
12
+ }
13
+ }
14
+ return null
15
+ }
16
+
17
+ const GooglePlaces = ({
18
+ label,
19
+ name,
20
+ input_name,
21
+ value,
22
+ defaultCountryCode,
23
+ onChange,
24
+ onClick,
25
+ onFocus,
26
+ onBlur,
27
+ data,
28
+ ...props
29
+ }: GooglePlacesPropsType) => {
30
+ const [place, setPlace] = useState<PlaceType | undefined>()
31
+
32
+ if (!process.env.GOOGLEMAPS_KEY) {
33
+ throw new Error("The environment variable GOOGLEMAPS_KEY is not defined!")
34
+ }
35
+
36
+ const { ref }: any = usePlacesWidget({
37
+ apiKey: process.env.GOOGLEMAPS_KEY,
38
+ onPlaceSelected: place =>
39
+ setPlace({
40
+ target: {
41
+ value: place.formatted_address,
42
+ data: { countryCode: getCountryCode(place) },
43
+ },
44
+ }),
45
+ ...data,
46
+ })
47
+
48
+ useEffect(() => {
49
+ if (onChange && place) {
50
+ onChange(place as any)
51
+ }
52
+ }, [place])
53
+
54
+ // Init country when countryCode is provided
55
+ useEffect(() => {
56
+ if (!defaultCountryCode || value || place) return
57
+ const found = countryPrefixes.find(
58
+ x => x[0] === defaultCountryCode.toLowerCase()
59
+ )
60
+ if (found) {
61
+ setPlace({
62
+ target: {
63
+ value: found[2],
64
+ data: { countryCode: defaultCountryCode },
65
+ },
66
+ })
67
+ }
68
+ }, [defaultCountryCode])
69
+
70
+ return (
71
+ <label className={`google-places label-${name}`}>
72
+ {!!label && <span>{label}</span>}
73
+
74
+ <input
75
+ ref={ref}
76
+ name={input_name}
77
+ autoComplete="off"
78
+ onChange={onChange}
79
+ onClick={onClick}
80
+ onFocus={onFocus}
81
+ onBlur={onBlur}
82
+ value={value}
83
+ placeholder={props.placeholder ?? undefined}
84
+ {...props}
85
+ />
86
+ </label>
87
+ )
88
+ }
89
+
90
+ export default GooglePlaces
@@ -0,0 +1,24 @@
1
+ import { InputType } from "../types";
2
+
3
+ export interface GooglePlacesPropsType
4
+ extends Omit<InputType, "type" | "data"> {
5
+ label?: string;
6
+ name?: string;
7
+ onChange: React.ChangeEventHandler<HTMLInputElement>;
8
+ onClick: React.MouseEventHandler<HTMLInputElement>;
9
+ onFocus: React.FocusEventHandler<HTMLInputElement>;
10
+ onBlur: React.FocusEventHandler<HTMLInputElement>;
11
+ value: any;
12
+ defaultCountryCode?: string;
13
+ data: {
14
+ options?: google.maps.places.AutocompleteOptions;
15
+ language?: string;
16
+ };
17
+ }
18
+
19
+ export interface PlaceType {
20
+ target: {
21
+ value: string | undefined;
22
+ data: { countryCode: string | null };
23
+ };
24
+ }
@@ -0,0 +1,26 @@
1
+ import React from "react"
2
+ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
3
+ import { IconPropsType, FontAwesomeGroupsType } from "./types"
4
+ // import { StaticImage } from "gatsby-plugin-image"
5
+
6
+
7
+ const FontAwesomeGroups: FontAwesomeGroupsType = {
8
+ FAB: "fab",
9
+ FAS: "fas",
10
+ }
11
+
12
+
13
+ const Icon = ({ icon, className, onClick, color }: IconPropsType) => {
14
+ return FontAwesomeGroups.hasOwnProperty(icon.group) ? (
15
+ <FontAwesomeIcon
16
+ className={className}
17
+ color={color}
18
+ onClick={onClick}
19
+ icon={[FontAwesomeGroups[icon.group], icon.icon]}
20
+ />
21
+ ) : (
22
+ <img className={className} alt={icon.icon} onClick={onClick} src={`/icons/${icon.icon}.svg`} />
23
+ )
24
+ }
25
+
26
+ export default Icon
@@ -0,0 +1,24 @@
1
+ import type { IconPrefix } from "@fortawesome/fontawesome-svg-core"
2
+ import { MouseEventHandler } from "react"
3
+
4
+ export interface IconType {
5
+ icon: any
6
+ group: string
7
+ }
8
+
9
+ export interface IconPropsType {
10
+ icon: IconType
11
+ className?: string
12
+ width?: number
13
+ height?: number
14
+ color?: string
15
+ bg?: string
16
+ title?: string
17
+ style?: { [key: string]: any }
18
+ direction?: string
19
+ onClick?: any
20
+ }
21
+
22
+ export interface FontAwesomeGroupsType {
23
+ [k: string]: IconPrefix
24
+ }