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