@sneekin/ui 0.2.1 → 0.3.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.
- package/README.md +28 -2
- package/dist/component.d.ts +14 -2
- package/dist/countries.d.ts +26 -0
- package/dist/country-picker.d.ts +12 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +600 -71
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +605 -71
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -136,12 +136,349 @@ function useSneekOtp(handlers) {
|
|
|
136
136
|
}
|
|
137
137
|
|
|
138
138
|
// src/component.tsx
|
|
139
|
+
import {
|
|
140
|
+
useEffect as useEffect2,
|
|
141
|
+
useId as useId2,
|
|
142
|
+
useState as useState2
|
|
143
|
+
} from "react";
|
|
144
|
+
|
|
145
|
+
// src/country-picker.tsx
|
|
139
146
|
import {
|
|
140
147
|
useEffect,
|
|
141
148
|
useId,
|
|
149
|
+
useRef,
|
|
142
150
|
useState
|
|
143
151
|
} from "react";
|
|
144
152
|
|
|
153
|
+
// src/countries.ts
|
|
154
|
+
function flagFromIso2(iso2) {
|
|
155
|
+
const A = 127462;
|
|
156
|
+
const codePoints = [...iso2.toUpperCase()].map((c) => A + (c.charCodeAt(0) - 65));
|
|
157
|
+
return String.fromCodePoint(...codePoints);
|
|
158
|
+
}
|
|
159
|
+
var RAW = [
|
|
160
|
+
["US", "United States", "1"],
|
|
161
|
+
["CA", "Canada", "1"],
|
|
162
|
+
["GB", "United Kingdom", "44"],
|
|
163
|
+
["IN", "India", "91"],
|
|
164
|
+
["AU", "Australia", "61"],
|
|
165
|
+
["DE", "Germany", "49"],
|
|
166
|
+
["FR", "France", "33"],
|
|
167
|
+
["IT", "Italy", "39"],
|
|
168
|
+
["ES", "Spain", "34"],
|
|
169
|
+
["PT", "Portugal", "351"],
|
|
170
|
+
["NL", "Netherlands", "31"],
|
|
171
|
+
["BE", "Belgium", "32"],
|
|
172
|
+
["CH", "Switzerland", "41"],
|
|
173
|
+
["AT", "Austria", "43"],
|
|
174
|
+
["SE", "Sweden", "46"],
|
|
175
|
+
["NO", "Norway", "47"],
|
|
176
|
+
["DK", "Denmark", "45"],
|
|
177
|
+
["FI", "Finland", "358"],
|
|
178
|
+
["IE", "Ireland", "353"],
|
|
179
|
+
["IS", "Iceland", "354"],
|
|
180
|
+
["PL", "Poland", "48"],
|
|
181
|
+
["CZ", "Czechia", "420"],
|
|
182
|
+
["SK", "Slovakia", "421"],
|
|
183
|
+
["HU", "Hungary", "36"],
|
|
184
|
+
["RO", "Romania", "40"],
|
|
185
|
+
["BG", "Bulgaria", "359"],
|
|
186
|
+
["GR", "Greece", "30"],
|
|
187
|
+
["TR", "Turkiye", "90"],
|
|
188
|
+
["RU", "Russia", "7"],
|
|
189
|
+
["UA", "Ukraine", "380"],
|
|
190
|
+
["BY", "Belarus", "375"],
|
|
191
|
+
["LT", "Lithuania", "370"],
|
|
192
|
+
["LV", "Latvia", "371"],
|
|
193
|
+
["EE", "Estonia", "372"],
|
|
194
|
+
["HR", "Croatia", "385"],
|
|
195
|
+
["RS", "Serbia", "381"],
|
|
196
|
+
["SI", "Slovenia", "386"],
|
|
197
|
+
["CY", "Cyprus", "357"],
|
|
198
|
+
["MT", "Malta", "356"],
|
|
199
|
+
["LU", "Luxembourg", "352"],
|
|
200
|
+
["CN", "China", "86"],
|
|
201
|
+
["JP", "Japan", "81"],
|
|
202
|
+
["KR", "South Korea", "82"],
|
|
203
|
+
["HK", "Hong Kong", "852"],
|
|
204
|
+
["TW", "Taiwan", "886"],
|
|
205
|
+
["MO", "Macau", "853"],
|
|
206
|
+
["SG", "Singapore", "65"],
|
|
207
|
+
["MY", "Malaysia", "60"],
|
|
208
|
+
["ID", "Indonesia", "62"],
|
|
209
|
+
["TH", "Thailand", "66"],
|
|
210
|
+
["VN", "Vietnam", "84"],
|
|
211
|
+
["PH", "Philippines", "63"],
|
|
212
|
+
["PK", "Pakistan", "92"],
|
|
213
|
+
["BD", "Bangladesh", "880"],
|
|
214
|
+
["LK", "Sri Lanka", "94"],
|
|
215
|
+
["NP", "Nepal", "977"],
|
|
216
|
+
["MM", "Myanmar", "95"],
|
|
217
|
+
["KH", "Cambodia", "855"],
|
|
218
|
+
["LA", "Laos", "856"],
|
|
219
|
+
["MN", "Mongolia", "976"],
|
|
220
|
+
["KZ", "Kazakhstan", "7"],
|
|
221
|
+
["UZ", "Uzbekistan", "998"],
|
|
222
|
+
["AE", "United Arab Emirates", "971"],
|
|
223
|
+
["SA", "Saudi Arabia", "966"],
|
|
224
|
+
["QA", "Qatar", "974"],
|
|
225
|
+
["KW", "Kuwait", "965"],
|
|
226
|
+
["BH", "Bahrain", "973"],
|
|
227
|
+
["OM", "Oman", "968"],
|
|
228
|
+
["JO", "Jordan", "962"],
|
|
229
|
+
["LB", "Lebanon", "961"],
|
|
230
|
+
["IL", "Israel", "972"],
|
|
231
|
+
["IQ", "Iraq", "964"],
|
|
232
|
+
["IR", "Iran", "98"],
|
|
233
|
+
["EG", "Egypt", "20"],
|
|
234
|
+
["ZA", "South Africa", "27"],
|
|
235
|
+
["NG", "Nigeria", "234"],
|
|
236
|
+
["KE", "Kenya", "254"],
|
|
237
|
+
["GH", "Ghana", "233"],
|
|
238
|
+
["ET", "Ethiopia", "251"],
|
|
239
|
+
["TZ", "Tanzania", "255"],
|
|
240
|
+
["UG", "Uganda", "256"],
|
|
241
|
+
["MA", "Morocco", "212"],
|
|
242
|
+
["DZ", "Algeria", "213"],
|
|
243
|
+
["TN", "Tunisia", "216"],
|
|
244
|
+
["BR", "Brazil", "55"],
|
|
245
|
+
["MX", "Mexico", "52"],
|
|
246
|
+
["AR", "Argentina", "54"],
|
|
247
|
+
["CL", "Chile", "56"],
|
|
248
|
+
["CO", "Colombia", "57"],
|
|
249
|
+
["PE", "Peru", "51"],
|
|
250
|
+
["VE", "Venezuela", "58"],
|
|
251
|
+
["EC", "Ecuador", "593"],
|
|
252
|
+
["UY", "Uruguay", "598"],
|
|
253
|
+
["PY", "Paraguay", "595"],
|
|
254
|
+
["BO", "Bolivia", "591"],
|
|
255
|
+
["CR", "Costa Rica", "506"],
|
|
256
|
+
["PA", "Panama", "507"],
|
|
257
|
+
["DO", "Dominican Republic", "1"],
|
|
258
|
+
["GT", "Guatemala", "502"],
|
|
259
|
+
["NZ", "New Zealand", "64"],
|
|
260
|
+
["FJ", "Fiji", "679"]
|
|
261
|
+
];
|
|
262
|
+
var COUNTRIES = RAW.map(([iso2, name, dial]) => ({
|
|
263
|
+
iso2,
|
|
264
|
+
name,
|
|
265
|
+
dial
|
|
266
|
+
}));
|
|
267
|
+
var DEFAULT_COUNTRY_ISO2 = "IN";
|
|
268
|
+
function flagEmoji(iso2) {
|
|
269
|
+
return flagFromIso2(iso2);
|
|
270
|
+
}
|
|
271
|
+
function countryByIso2(iso2) {
|
|
272
|
+
return COUNTRIES.find((c) => c.iso2 === iso2);
|
|
273
|
+
}
|
|
274
|
+
function countryByDialPrefix(digits) {
|
|
275
|
+
for (const len of [3, 2, 1]) {
|
|
276
|
+
const prefix = digits.slice(0, len);
|
|
277
|
+
if (prefix.length < len) continue;
|
|
278
|
+
const match = COUNTRIES.find((c) => c.dial === prefix);
|
|
279
|
+
if (match) return match;
|
|
280
|
+
}
|
|
281
|
+
return void 0;
|
|
282
|
+
}
|
|
283
|
+
function detectCountryFromLocale() {
|
|
284
|
+
if (typeof navigator === "undefined") return void 0;
|
|
285
|
+
const locales = navigator.languages ?? [navigator.language];
|
|
286
|
+
for (const locale of locales) {
|
|
287
|
+
const match = /-([A-Z]{2})$/.exec(locale ?? "");
|
|
288
|
+
if (match) {
|
|
289
|
+
const found = countryByIso2(match[1]);
|
|
290
|
+
if (found) return found;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
return void 0;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// src/country-picker.tsx
|
|
297
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
298
|
+
function ChevronDown() {
|
|
299
|
+
return /* @__PURE__ */ jsx("svg", { width: "10", height: "10", viewBox: "0 0 10 10", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx(
|
|
300
|
+
"path",
|
|
301
|
+
{
|
|
302
|
+
d: "M2 3.5 5 6.5 8 3.5",
|
|
303
|
+
stroke: "currentColor",
|
|
304
|
+
strokeWidth: "1.4",
|
|
305
|
+
strokeLinecap: "round",
|
|
306
|
+
strokeLinejoin: "round"
|
|
307
|
+
}
|
|
308
|
+
) });
|
|
309
|
+
}
|
|
310
|
+
function CountryPicker({ country, onSelect, disabled }) {
|
|
311
|
+
const [open, setOpen] = useState(false);
|
|
312
|
+
const [query, setQuery] = useState("");
|
|
313
|
+
const rootRef = useRef(null);
|
|
314
|
+
const searchRef = useRef(null);
|
|
315
|
+
const uid = useId();
|
|
316
|
+
useEffect(() => {
|
|
317
|
+
if (!open) return;
|
|
318
|
+
searchRef.current?.focus();
|
|
319
|
+
const onDocPointerDown = (event) => {
|
|
320
|
+
if (rootRef.current && !rootRef.current.contains(event.target)) {
|
|
321
|
+
setOpen(false);
|
|
322
|
+
}
|
|
323
|
+
};
|
|
324
|
+
const onKeyDown = (event) => {
|
|
325
|
+
if (event.key === "Escape") setOpen(false);
|
|
326
|
+
};
|
|
327
|
+
document.addEventListener("mousedown", onDocPointerDown);
|
|
328
|
+
document.addEventListener("keydown", onKeyDown);
|
|
329
|
+
return () => {
|
|
330
|
+
document.removeEventListener("mousedown", onDocPointerDown);
|
|
331
|
+
document.removeEventListener("keydown", onKeyDown);
|
|
332
|
+
};
|
|
333
|
+
}, [open]);
|
|
334
|
+
const q = query.trim().toLowerCase();
|
|
335
|
+
const filtered = q ? COUNTRIES.filter(
|
|
336
|
+
(c) => c.name.toLowerCase().includes(q) || c.dial.includes(q.replace(/^\+/, ""))
|
|
337
|
+
) : COUNTRIES;
|
|
338
|
+
const listId = `${uid}-country-list`;
|
|
339
|
+
return /* @__PURE__ */ jsxs("div", { ref: rootRef, style: { position: "relative", flexShrink: 0 }, children: [
|
|
340
|
+
/* @__PURE__ */ jsxs(
|
|
341
|
+
"button",
|
|
342
|
+
{
|
|
343
|
+
type: "button",
|
|
344
|
+
className: "sneek-otp-country-chip",
|
|
345
|
+
onClick: () => setOpen((o) => !o),
|
|
346
|
+
disabled,
|
|
347
|
+
"aria-haspopup": "listbox",
|
|
348
|
+
"aria-expanded": open,
|
|
349
|
+
style: {
|
|
350
|
+
display: "flex",
|
|
351
|
+
alignItems: "center",
|
|
352
|
+
gap: 6,
|
|
353
|
+
height: "100%",
|
|
354
|
+
padding: "0 10px 0 14px",
|
|
355
|
+
border: "none",
|
|
356
|
+
borderRadius: "12px 0 0 12px",
|
|
357
|
+
background: "transparent",
|
|
358
|
+
color: "var(--sneek-text)",
|
|
359
|
+
fontSize: 15,
|
|
360
|
+
cursor: disabled ? "not-allowed" : "pointer"
|
|
361
|
+
},
|
|
362
|
+
children: [
|
|
363
|
+
/* @__PURE__ */ jsx("span", { style: { fontSize: 18, lineHeight: 1 }, children: flagEmoji(country.iso2) }),
|
|
364
|
+
/* @__PURE__ */ jsxs("span", { style: { color: "var(--sneek-text-muted)" }, children: [
|
|
365
|
+
"+",
|
|
366
|
+
country.dial
|
|
367
|
+
] }),
|
|
368
|
+
/* @__PURE__ */ jsx("span", { style: { color: "var(--sneek-text-muted)" }, children: /* @__PURE__ */ jsx(ChevronDown, {}) })
|
|
369
|
+
]
|
|
370
|
+
}
|
|
371
|
+
),
|
|
372
|
+
open ? /* @__PURE__ */ jsxs(
|
|
373
|
+
"div",
|
|
374
|
+
{
|
|
375
|
+
role: "presentation",
|
|
376
|
+
style: {
|
|
377
|
+
position: "absolute",
|
|
378
|
+
top: "calc(100% + 6px)",
|
|
379
|
+
left: 0,
|
|
380
|
+
zIndex: 10,
|
|
381
|
+
width: 260,
|
|
382
|
+
maxWidth: "80vw",
|
|
383
|
+
border: "1px solid var(--sneek-border)",
|
|
384
|
+
borderRadius: 12,
|
|
385
|
+
background: "var(--sneek-surface)",
|
|
386
|
+
boxShadow: "var(--sneek-shadow), 0 8px 24px rgb(0 0 0 / 0.12)",
|
|
387
|
+
overflow: "hidden"
|
|
388
|
+
},
|
|
389
|
+
children: [
|
|
390
|
+
/* @__PURE__ */ jsx(
|
|
391
|
+
"input",
|
|
392
|
+
{
|
|
393
|
+
ref: searchRef,
|
|
394
|
+
value: query,
|
|
395
|
+
onChange: (e) => setQuery(e.target.value),
|
|
396
|
+
onKeyDown: (e) => {
|
|
397
|
+
if (e.key === "Enter" && filtered[0]) {
|
|
398
|
+
onSelect(filtered[0]);
|
|
399
|
+
setOpen(false);
|
|
400
|
+
setQuery("");
|
|
401
|
+
}
|
|
402
|
+
},
|
|
403
|
+
placeholder: "Search country or code",
|
|
404
|
+
"aria-controls": listId,
|
|
405
|
+
role: "combobox",
|
|
406
|
+
"aria-expanded": open,
|
|
407
|
+
style: {
|
|
408
|
+
width: "100%",
|
|
409
|
+
padding: "10px 12px",
|
|
410
|
+
border: "none",
|
|
411
|
+
borderBottom: "1px solid var(--sneek-border)",
|
|
412
|
+
background: "transparent",
|
|
413
|
+
color: "var(--sneek-text)",
|
|
414
|
+
fontSize: 14,
|
|
415
|
+
outline: "none"
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
),
|
|
419
|
+
/* @__PURE__ */ jsx(
|
|
420
|
+
"div",
|
|
421
|
+
{
|
|
422
|
+
id: listId,
|
|
423
|
+
role: "listbox",
|
|
424
|
+
style: { maxHeight: 240, overflowY: "auto", padding: 6 },
|
|
425
|
+
children: filtered.length === 0 ? /* @__PURE__ */ jsx(
|
|
426
|
+
"p",
|
|
427
|
+
{
|
|
428
|
+
style: {
|
|
429
|
+
padding: "8px 10px",
|
|
430
|
+
fontSize: 13,
|
|
431
|
+
color: "var(--sneek-text-muted)"
|
|
432
|
+
},
|
|
433
|
+
children: "No match."
|
|
434
|
+
}
|
|
435
|
+
) : filtered.map((c) => {
|
|
436
|
+
const optionStyle = {
|
|
437
|
+
display: "flex",
|
|
438
|
+
width: "100%",
|
|
439
|
+
alignItems: "center",
|
|
440
|
+
gap: 10,
|
|
441
|
+
padding: "8px 10px",
|
|
442
|
+
border: "none",
|
|
443
|
+
borderRadius: 8,
|
|
444
|
+
background: c.iso2 === country.iso2 ? "var(--sneek-surface-2)" : "transparent",
|
|
445
|
+
color: "var(--sneek-text)",
|
|
446
|
+
fontSize: 14,
|
|
447
|
+
textAlign: "left",
|
|
448
|
+
cursor: "pointer"
|
|
449
|
+
};
|
|
450
|
+
return /* @__PURE__ */ jsxs(
|
|
451
|
+
"button",
|
|
452
|
+
{
|
|
453
|
+
type: "button",
|
|
454
|
+
role: "option",
|
|
455
|
+
"aria-selected": c.iso2 === country.iso2,
|
|
456
|
+
onClick: () => {
|
|
457
|
+
onSelect(c);
|
|
458
|
+
setOpen(false);
|
|
459
|
+
setQuery("");
|
|
460
|
+
},
|
|
461
|
+
style: optionStyle,
|
|
462
|
+
children: [
|
|
463
|
+
/* @__PURE__ */ jsx("span", { style: { fontSize: 17, lineHeight: 1 }, children: flagEmoji(c.iso2) }),
|
|
464
|
+
/* @__PURE__ */ jsx("span", { style: { flex: 1 }, children: c.name }),
|
|
465
|
+
/* @__PURE__ */ jsxs("span", { style: { color: "var(--sneek-text-muted)" }, children: [
|
|
466
|
+
"+",
|
|
467
|
+
c.dial
|
|
468
|
+
] })
|
|
469
|
+
]
|
|
470
|
+
},
|
|
471
|
+
c.iso2
|
|
472
|
+
);
|
|
473
|
+
})
|
|
474
|
+
}
|
|
475
|
+
)
|
|
476
|
+
]
|
|
477
|
+
}
|
|
478
|
+
) : null
|
|
479
|
+
] });
|
|
480
|
+
}
|
|
481
|
+
|
|
145
482
|
// src/theme.ts
|
|
146
483
|
var SNEEK_STYLE_ID = "sneek-otp-styles";
|
|
147
484
|
var SNEEK_ACCENT_DARK = "#f86513";
|
|
@@ -187,31 +524,50 @@ function buildStyles(accent) {
|
|
|
187
524
|
.sneek-otp *, .sneek-otp *::before, .sneek-otp *::after { box-sizing: border-box; }
|
|
188
525
|
.sneek-otp-input:focus-visible,
|
|
189
526
|
.sneek-otp-button:focus-visible,
|
|
190
|
-
.sneek-otp-link:focus-visible
|
|
527
|
+
.sneek-otp-link:focus-visible,
|
|
528
|
+
.sneek-otp-country-chip:focus-visible,
|
|
529
|
+
.sneek-otp-brand-link:focus-visible {
|
|
191
530
|
outline: 2px solid var(--sneek-accent);
|
|
192
531
|
outline-offset: 2px;
|
|
193
532
|
}
|
|
194
533
|
.sneek-otp-input:focus {
|
|
195
534
|
border-color: var(--sneek-accent);
|
|
196
535
|
}
|
|
536
|
+
.sneek-otp-phone-field:focus-within {
|
|
537
|
+
border-color: var(--sneek-accent);
|
|
538
|
+
}
|
|
197
539
|
.sneek-otp-button:not(:disabled):hover {
|
|
198
540
|
filter: brightness(1.08);
|
|
199
541
|
}
|
|
200
542
|
.sneek-otp-link:not(:disabled):hover {
|
|
201
543
|
text-decoration: underline;
|
|
202
544
|
}
|
|
545
|
+
.sneek-otp-country-chip:not(:disabled):hover {
|
|
546
|
+
filter: brightness(1.08);
|
|
547
|
+
}
|
|
548
|
+
.sneek-otp-brand-link:hover {
|
|
549
|
+
color: var(--sneek-accent);
|
|
550
|
+
}
|
|
203
551
|
@media (prefers-reduced-motion: reduce) {
|
|
204
552
|
.sneek-otp * { transition-duration: 0.01ms !important; }
|
|
205
553
|
}
|
|
554
|
+
@keyframes sneek-otp-caret {
|
|
555
|
+
0%, 45% { opacity: 1; }
|
|
556
|
+
50%, 100% { opacity: 0; }
|
|
557
|
+
}
|
|
558
|
+
.sneek-otp-caret {
|
|
559
|
+
animation: sneek-otp-caret 1s step-end infinite;
|
|
560
|
+
}
|
|
206
561
|
`;
|
|
207
562
|
}
|
|
208
563
|
|
|
209
564
|
// src/component.tsx
|
|
210
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
565
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
211
566
|
var CODE_LENGTH = 6;
|
|
212
567
|
var RESEND_SECONDS = 30;
|
|
568
|
+
var MAX_NATIONAL_DIGITS = 12;
|
|
213
569
|
function SneekMark() {
|
|
214
|
-
return /* @__PURE__ */
|
|
570
|
+
return /* @__PURE__ */ jsxs2(
|
|
215
571
|
"svg",
|
|
216
572
|
{
|
|
217
573
|
width: "36",
|
|
@@ -221,8 +577,8 @@ function SneekMark() {
|
|
|
221
577
|
role: "img",
|
|
222
578
|
"aria-label": "Sneek",
|
|
223
579
|
children: [
|
|
224
|
-
/* @__PURE__ */
|
|
225
|
-
/* @__PURE__ */
|
|
580
|
+
/* @__PURE__ */ jsx2("path", { d: "M21.3 1 29.3 16H13.3L21.3 1Z", fill: "var(--sneek-accent)" }),
|
|
581
|
+
/* @__PURE__ */ jsx2("path", { d: "M2.7 17.5H18.7L10.7 31 2.7 17.5Z", fill: "var(--sneek-accent)" })
|
|
226
582
|
]
|
|
227
583
|
}
|
|
228
584
|
);
|
|
@@ -232,8 +588,10 @@ function SneekOtpLogin(props) {
|
|
|
232
588
|
title = "Sign in",
|
|
233
589
|
verifyTitle = "Enter the code",
|
|
234
590
|
subtitle = "Enter your email or phone. We will send you a code.",
|
|
235
|
-
|
|
236
|
-
|
|
591
|
+
identifierMode = "both",
|
|
592
|
+
defaultCountry,
|
|
593
|
+
identifierLabel,
|
|
594
|
+
identifierPlaceholder,
|
|
237
595
|
theme = "auto",
|
|
238
596
|
accentColor,
|
|
239
597
|
logo,
|
|
@@ -243,12 +601,49 @@ function SneekOtpLogin(props) {
|
|
|
243
601
|
...handlers
|
|
244
602
|
} = props;
|
|
245
603
|
const otp = useSneekOtp(handlers);
|
|
246
|
-
const uid =
|
|
247
|
-
const [resendIn, setResendIn] =
|
|
604
|
+
const uid = useId2();
|
|
605
|
+
const [resendIn, setResendIn] = useState2(0);
|
|
606
|
+
const [codeFocused, setCodeFocused] = useState2(false);
|
|
607
|
+
const [mode, setMode] = useState2(
|
|
608
|
+
identifierMode === "email" ? "email" : "phone"
|
|
609
|
+
);
|
|
610
|
+
const [country, setCountry] = useState2(
|
|
611
|
+
() => defaultCountry && countryByIso2(defaultCountry) || countryByIso2(DEFAULT_COUNTRY_ISO2)
|
|
612
|
+
);
|
|
613
|
+
const [nationalNumber, setNationalNumber] = useState2("");
|
|
248
614
|
const identifierId = `${uid}-identifier`;
|
|
249
615
|
const codeId = `${uid}-code`;
|
|
250
616
|
const statusId = `${uid}-status`;
|
|
251
|
-
|
|
617
|
+
useEffect2(() => {
|
|
618
|
+
if (defaultCountry) return;
|
|
619
|
+
const guess = detectCountryFromLocale();
|
|
620
|
+
if (guess) setCountry(guess);
|
|
621
|
+
}, []);
|
|
622
|
+
const onCountrySelect = (next) => {
|
|
623
|
+
setCountry(next);
|
|
624
|
+
otp.setIdentifier(`+${next.dial}${nationalNumber}`);
|
|
625
|
+
};
|
|
626
|
+
const onNationalNumberChange = (raw) => {
|
|
627
|
+
let digits = raw.replace(/\D/g, "").slice(0, MAX_NATIONAL_DIGITS + 3);
|
|
628
|
+
let effectiveCountry = country;
|
|
629
|
+
if (raw.trim().startsWith("+")) {
|
|
630
|
+
const guess = countryByDialPrefix(digits);
|
|
631
|
+
if (guess && digits.startsWith(guess.dial)) {
|
|
632
|
+
effectiveCountry = guess;
|
|
633
|
+
digits = digits.slice(guess.dial.length);
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
digits = digits.slice(0, MAX_NATIONAL_DIGITS);
|
|
637
|
+
if (effectiveCountry !== country) setCountry(effectiveCountry);
|
|
638
|
+
setNationalNumber(digits);
|
|
639
|
+
otp.setIdentifier(`+${effectiveCountry.dial}${digits}`);
|
|
640
|
+
};
|
|
641
|
+
const switchMode = (next) => {
|
|
642
|
+
setMode(next);
|
|
643
|
+
setNationalNumber("");
|
|
644
|
+
otp.setIdentifier("");
|
|
645
|
+
};
|
|
646
|
+
useEffect2(() => {
|
|
252
647
|
if (typeof document === "undefined") return;
|
|
253
648
|
let el = document.getElementById(SNEEK_STYLE_ID);
|
|
254
649
|
if (!el) {
|
|
@@ -266,15 +661,15 @@ function SneekOtpLogin(props) {
|
|
|
266
661
|
event.preventDefault();
|
|
267
662
|
void otp.verify();
|
|
268
663
|
};
|
|
269
|
-
|
|
664
|
+
useEffect2(() => {
|
|
270
665
|
if (otp.step === "verify") setResendIn(RESEND_SECONDS);
|
|
271
666
|
}, [otp.step]);
|
|
272
|
-
|
|
667
|
+
useEffect2(() => {
|
|
273
668
|
if (resendIn <= 0) return;
|
|
274
669
|
const timer = setTimeout(() => setResendIn((n) => n - 1), 1e3);
|
|
275
670
|
return () => clearTimeout(timer);
|
|
276
671
|
}, [resendIn]);
|
|
277
|
-
|
|
672
|
+
useEffect2(() => {
|
|
278
673
|
if (otp.step === "verify" && otp.code.length === CODE_LENGTH && !otp.isBusy) {
|
|
279
674
|
void otp.verify();
|
|
280
675
|
}
|
|
@@ -328,7 +723,7 @@ function SneekOtpLogin(props) {
|
|
|
328
723
|
fontSize: 14,
|
|
329
724
|
color: "var(--sneek-text-muted)"
|
|
330
725
|
};
|
|
331
|
-
return /* @__PURE__ */
|
|
726
|
+
return /* @__PURE__ */ jsxs2(
|
|
332
727
|
"div",
|
|
333
728
|
{
|
|
334
729
|
className: className ? `sneek-otp ${className}` : "sneek-otp",
|
|
@@ -347,8 +742,8 @@ function SneekOtpLogin(props) {
|
|
|
347
742
|
...style
|
|
348
743
|
},
|
|
349
744
|
children: [
|
|
350
|
-
logo === null ? null : logo ?? /* @__PURE__ */
|
|
351
|
-
/* @__PURE__ */
|
|
745
|
+
logo === null ? null : logo ?? /* @__PURE__ */ jsx2(SneekMark, {}),
|
|
746
|
+
/* @__PURE__ */ jsx2(
|
|
352
747
|
"h1",
|
|
353
748
|
{
|
|
354
749
|
style: {
|
|
@@ -361,7 +756,7 @@ function SneekOtpLogin(props) {
|
|
|
361
756
|
children: otp.step === "verify" ? verifyTitle : title
|
|
362
757
|
}
|
|
363
758
|
),
|
|
364
|
-
otp.step === "verify" ? /* @__PURE__ */
|
|
759
|
+
otp.step === "verify" ? /* @__PURE__ */ jsxs2(
|
|
365
760
|
"p",
|
|
366
761
|
{
|
|
367
762
|
style: {
|
|
@@ -373,9 +768,9 @@ function SneekOtpLogin(props) {
|
|
|
373
768
|
children: [
|
|
374
769
|
"Sent to",
|
|
375
770
|
" ",
|
|
376
|
-
/* @__PURE__ */
|
|
771
|
+
/* @__PURE__ */ jsx2("span", { style: { color: "var(--sneek-text)" }, children: otp.identifier }),
|
|
377
772
|
" \xB7 ",
|
|
378
|
-
/* @__PURE__ */
|
|
773
|
+
/* @__PURE__ */ jsx2(
|
|
379
774
|
"button",
|
|
380
775
|
{
|
|
381
776
|
type: "button",
|
|
@@ -388,7 +783,7 @@ function SneekOtpLogin(props) {
|
|
|
388
783
|
)
|
|
389
784
|
]
|
|
390
785
|
}
|
|
391
|
-
) : subtitle ? /* @__PURE__ */
|
|
786
|
+
) : subtitle ? /* @__PURE__ */ jsx2(
|
|
392
787
|
"p",
|
|
393
788
|
{
|
|
394
789
|
style: {
|
|
@@ -400,7 +795,7 @@ function SneekOtpLogin(props) {
|
|
|
400
795
|
children: subtitle
|
|
401
796
|
}
|
|
402
797
|
) : null,
|
|
403
|
-
/* @__PURE__ */
|
|
798
|
+
/* @__PURE__ */ jsx2("div", { role: "status", "aria-live": "polite", id: statusId, style: { marginTop: 32 }, children: otp.error ? /* @__PURE__ */ jsx2(
|
|
404
799
|
"div",
|
|
405
800
|
{
|
|
406
801
|
role: "alert",
|
|
@@ -412,32 +807,99 @@ function SneekOtpLogin(props) {
|
|
|
412
807
|
children: otp.error
|
|
413
808
|
}
|
|
414
809
|
) : null }),
|
|
415
|
-
otp.step === "identify" ? /* @__PURE__ */
|
|
810
|
+
otp.step === "identify" ? /* @__PURE__ */ jsxs2(
|
|
416
811
|
"form",
|
|
417
812
|
{
|
|
418
813
|
onSubmit: onIdentifySubmit,
|
|
419
814
|
style: { display: "flex", flexDirection: "column", gap: 16, marginTop: 16 },
|
|
420
815
|
children: [
|
|
421
|
-
/* @__PURE__ */
|
|
422
|
-
identifierLabel,
|
|
423
|
-
/* @__PURE__ */
|
|
424
|
-
"
|
|
816
|
+
/* @__PURE__ */ jsxs2("div", { style: { display: "flex", alignItems: "baseline", justifyContent: "space-between" }, children: [
|
|
817
|
+
/* @__PURE__ */ jsx2("label", { style: { ...label, gap: 0 }, htmlFor: identifierId, children: identifierLabel ?? (mode === "phone" ? "Mobile number" : "Email address") }),
|
|
818
|
+
identifierMode === "both" ? /* @__PURE__ */ jsx2(
|
|
819
|
+
"button",
|
|
425
820
|
{
|
|
426
|
-
|
|
427
|
-
className: "sneek-otp-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
autoComplete: "username",
|
|
433
|
-
autoFocus: true,
|
|
434
|
-
required: true,
|
|
435
|
-
"aria-describedby": statusId,
|
|
436
|
-
style: input
|
|
821
|
+
type: "button",
|
|
822
|
+
className: "sneek-otp-link",
|
|
823
|
+
onClick: () => switchMode(mode === "phone" ? "email" : "phone"),
|
|
824
|
+
disabled: otp.isBusy,
|
|
825
|
+
style: { ...link, fontSize: 13 },
|
|
826
|
+
children: mode === "phone" ? "Use email instead" : "Use phone instead"
|
|
437
827
|
}
|
|
438
|
-
)
|
|
828
|
+
) : null
|
|
439
829
|
] }),
|
|
440
|
-
/* @__PURE__ */
|
|
830
|
+
mode === "phone" ? /* @__PURE__ */ jsxs2(
|
|
831
|
+
"div",
|
|
832
|
+
{
|
|
833
|
+
className: "sneek-otp-phone-field",
|
|
834
|
+
style: {
|
|
835
|
+
display: "flex",
|
|
836
|
+
alignItems: "stretch",
|
|
837
|
+
border: "1px solid var(--sneek-border)",
|
|
838
|
+
borderRadius: 12,
|
|
839
|
+
background: "var(--sneek-surface-2)",
|
|
840
|
+
transition: "border-color 200ms var(--sneek-ease)"
|
|
841
|
+
},
|
|
842
|
+
children: [
|
|
843
|
+
/* @__PURE__ */ jsx2(
|
|
844
|
+
CountryPicker,
|
|
845
|
+
{
|
|
846
|
+
country,
|
|
847
|
+
onSelect: onCountrySelect,
|
|
848
|
+
disabled: otp.isBusy
|
|
849
|
+
}
|
|
850
|
+
),
|
|
851
|
+
/* @__PURE__ */ jsx2(
|
|
852
|
+
"span",
|
|
853
|
+
{
|
|
854
|
+
"aria-hidden": "true",
|
|
855
|
+
style: {
|
|
856
|
+
width: 1,
|
|
857
|
+
margin: "9px 0",
|
|
858
|
+
background: "var(--sneek-border)"
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
),
|
|
862
|
+
/* @__PURE__ */ jsx2(
|
|
863
|
+
"input",
|
|
864
|
+
{
|
|
865
|
+
id: identifierId,
|
|
866
|
+
type: "tel",
|
|
867
|
+
inputMode: "tel",
|
|
868
|
+
autoComplete: "tel-national",
|
|
869
|
+
value: nationalNumber,
|
|
870
|
+
onChange: (e) => onNationalNumberChange(e.target.value),
|
|
871
|
+
placeholder: "Mobile number",
|
|
872
|
+
autoFocus: true,
|
|
873
|
+
required: true,
|
|
874
|
+
"aria-describedby": statusId,
|
|
875
|
+
style: {
|
|
876
|
+
...input,
|
|
877
|
+
flex: 1,
|
|
878
|
+
border: "none",
|
|
879
|
+
background: "transparent",
|
|
880
|
+
borderRadius: 0
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
)
|
|
884
|
+
]
|
|
885
|
+
}
|
|
886
|
+
) : /* @__PURE__ */ jsx2(
|
|
887
|
+
"input",
|
|
888
|
+
{
|
|
889
|
+
id: identifierId,
|
|
890
|
+
className: "sneek-otp-input",
|
|
891
|
+
type: "email",
|
|
892
|
+
value: otp.identifier,
|
|
893
|
+
onChange: (e) => otp.setIdentifier(e.target.value),
|
|
894
|
+
placeholder: identifierPlaceholder ?? "you@company.com",
|
|
895
|
+
autoComplete: "email",
|
|
896
|
+
autoFocus: true,
|
|
897
|
+
required: true,
|
|
898
|
+
"aria-describedby": statusId,
|
|
899
|
+
style: input
|
|
900
|
+
}
|
|
901
|
+
),
|
|
902
|
+
/* @__PURE__ */ jsx2(
|
|
441
903
|
"button",
|
|
442
904
|
{
|
|
443
905
|
type: "submit",
|
|
@@ -449,43 +911,90 @@ function SneekOtpLogin(props) {
|
|
|
449
911
|
)
|
|
450
912
|
]
|
|
451
913
|
}
|
|
452
|
-
) : /* @__PURE__ */
|
|
914
|
+
) : /* @__PURE__ */ jsxs2(
|
|
453
915
|
"form",
|
|
454
916
|
{
|
|
455
917
|
onSubmit: onVerifySubmit,
|
|
456
918
|
style: { display: "flex", flexDirection: "column", gap: 16, marginTop: 16 },
|
|
457
919
|
children: [
|
|
458
|
-
/* @__PURE__ */
|
|
920
|
+
/* @__PURE__ */ jsxs2("label", { style: { ...label, marginBottom: -8 }, htmlFor: codeId, children: [
|
|
459
921
|
CODE_LENGTH,
|
|
460
922
|
"-digit code"
|
|
461
923
|
] }),
|
|
462
|
-
/* @__PURE__ */
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
924
|
+
/* @__PURE__ */ jsxs2("div", { style: { position: "relative" }, children: [
|
|
925
|
+
/* @__PURE__ */ jsx2(
|
|
926
|
+
"input",
|
|
927
|
+
{
|
|
928
|
+
id: codeId,
|
|
929
|
+
className: "sneek-otp-code-input",
|
|
930
|
+
type: "text",
|
|
931
|
+
inputMode: "numeric",
|
|
932
|
+
pattern: "[0-9]*",
|
|
933
|
+
autoComplete: "one-time-code",
|
|
934
|
+
maxLength: CODE_LENGTH,
|
|
935
|
+
value: otp.code,
|
|
936
|
+
onChange: (e) => otp.setCode(
|
|
937
|
+
e.target.value.replace(/\D/g, "").slice(0, CODE_LENGTH)
|
|
938
|
+
),
|
|
939
|
+
onFocus: () => setCodeFocused(true),
|
|
940
|
+
onBlur: () => setCodeFocused(false),
|
|
941
|
+
autoFocus: true,
|
|
942
|
+
required: true,
|
|
943
|
+
"aria-describedby": statusId,
|
|
944
|
+
style: {
|
|
945
|
+
position: "absolute",
|
|
946
|
+
inset: 0,
|
|
947
|
+
width: "100%",
|
|
948
|
+
height: "100%",
|
|
949
|
+
opacity: 0,
|
|
950
|
+
caretColor: "transparent",
|
|
951
|
+
cursor: "text"
|
|
952
|
+
}
|
|
485
953
|
}
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
954
|
+
),
|
|
955
|
+
/* @__PURE__ */ jsx2(
|
|
956
|
+
"div",
|
|
957
|
+
{
|
|
958
|
+
className: "sneek-otp-code-cells",
|
|
959
|
+
"aria-hidden": "true",
|
|
960
|
+
style: { display: "flex", gap: 10 },
|
|
961
|
+
children: Array.from({ length: CODE_LENGTH }).map((_, i) => {
|
|
962
|
+
const digit = otp.code[i];
|
|
963
|
+
const isActive = codeFocused && i === otp.code.length;
|
|
964
|
+
return /* @__PURE__ */ jsx2(
|
|
965
|
+
"div",
|
|
966
|
+
{
|
|
967
|
+
"data-active": isActive,
|
|
968
|
+
style: {
|
|
969
|
+
flex: 1,
|
|
970
|
+
height: 44,
|
|
971
|
+
display: "flex",
|
|
972
|
+
alignItems: "center",
|
|
973
|
+
justifyContent: "center",
|
|
974
|
+
fontSize: 22,
|
|
975
|
+
color: "var(--sneek-text)",
|
|
976
|
+
borderBottom: `2px solid ${isActive ? "var(--sneek-accent)" : "var(--sneek-border)"}`,
|
|
977
|
+
transition: "border-color 200ms var(--sneek-ease)"
|
|
978
|
+
},
|
|
979
|
+
children: digit ?? (isActive ? /* @__PURE__ */ jsx2(
|
|
980
|
+
"span",
|
|
981
|
+
{
|
|
982
|
+
className: "sneek-otp-caret",
|
|
983
|
+
style: {
|
|
984
|
+
width: 2,
|
|
985
|
+
height: 22,
|
|
986
|
+
background: "var(--sneek-accent)"
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
) : null)
|
|
990
|
+
},
|
|
991
|
+
i
|
|
992
|
+
);
|
|
993
|
+
})
|
|
994
|
+
}
|
|
995
|
+
)
|
|
996
|
+
] }),
|
|
997
|
+
/* @__PURE__ */ jsx2(
|
|
489
998
|
"button",
|
|
490
999
|
{
|
|
491
1000
|
type: "submit",
|
|
@@ -498,7 +1007,7 @@ function SneekOtpLogin(props) {
|
|
|
498
1007
|
children: otp.isVerifying ? "Verifying\u2026" : "Verify"
|
|
499
1008
|
}
|
|
500
1009
|
),
|
|
501
|
-
/* @__PURE__ */
|
|
1010
|
+
/* @__PURE__ */ jsx2("div", { style: { textAlign: "center" }, children: resendIn > 0 ? /* @__PURE__ */ jsxs2(
|
|
502
1011
|
"span",
|
|
503
1012
|
{
|
|
504
1013
|
style: {
|
|
@@ -511,7 +1020,7 @@ function SneekOtpLogin(props) {
|
|
|
511
1020
|
"s"
|
|
512
1021
|
]
|
|
513
1022
|
}
|
|
514
|
-
) : /* @__PURE__ */
|
|
1023
|
+
) : /* @__PURE__ */ jsx2(
|
|
515
1024
|
"button",
|
|
516
1025
|
{
|
|
517
1026
|
type: "button",
|
|
@@ -525,7 +1034,7 @@ function SneekOtpLogin(props) {
|
|
|
525
1034
|
]
|
|
526
1035
|
}
|
|
527
1036
|
),
|
|
528
|
-
showBranding ? /* @__PURE__ */
|
|
1037
|
+
showBranding ? /* @__PURE__ */ jsxs2(
|
|
529
1038
|
"p",
|
|
530
1039
|
{
|
|
531
1040
|
style: {
|
|
@@ -535,7 +1044,26 @@ function SneekOtpLogin(props) {
|
|
|
535
1044
|
lineHeight: "16px",
|
|
536
1045
|
color: "var(--sneek-text-muted)"
|
|
537
1046
|
},
|
|
538
|
-
children:
|
|
1047
|
+
children: [
|
|
1048
|
+
"Secured by",
|
|
1049
|
+
" ",
|
|
1050
|
+
/* @__PURE__ */ jsx2(
|
|
1051
|
+
"a",
|
|
1052
|
+
{
|
|
1053
|
+
href: "https://sneek.in",
|
|
1054
|
+
target: "_blank",
|
|
1055
|
+
rel: "noopener noreferrer",
|
|
1056
|
+
className: "sneek-otp-brand-link",
|
|
1057
|
+
style: {
|
|
1058
|
+
color: "inherit",
|
|
1059
|
+
textDecoration: "underline",
|
|
1060
|
+
textUnderlineOffset: 2,
|
|
1061
|
+
transition: "color 200ms var(--sneek-ease)"
|
|
1062
|
+
},
|
|
1063
|
+
children: "Sneek"
|
|
1064
|
+
}
|
|
1065
|
+
)
|
|
1066
|
+
]
|
|
539
1067
|
}
|
|
540
1068
|
) : null
|
|
541
1069
|
]
|
|
@@ -571,10 +1099,16 @@ function createFetchHandlers(options = {}) {
|
|
|
571
1099
|
};
|
|
572
1100
|
}
|
|
573
1101
|
export {
|
|
1102
|
+
COUNTRIES,
|
|
1103
|
+
DEFAULT_COUNTRY_ISO2,
|
|
574
1104
|
SNEEK_ACCENT_DARK,
|
|
575
1105
|
SNEEK_ACCENT_LIGHT,
|
|
576
1106
|
SneekOtpLogin,
|
|
1107
|
+
countryByDialPrefix,
|
|
1108
|
+
countryByIso2,
|
|
577
1109
|
createFetchHandlers,
|
|
1110
|
+
detectCountryFromLocale,
|
|
1111
|
+
flagEmoji,
|
|
578
1112
|
formatChannels,
|
|
579
1113
|
initialOtpState,
|
|
580
1114
|
otpReducer,
|