@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.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,31 +554,50 @@ 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
|
}
|
|
584
|
+
@keyframes sneek-otp-caret {
|
|
585
|
+
0%, 45% { opacity: 1; }
|
|
586
|
+
50%, 100% { opacity: 0; }
|
|
587
|
+
}
|
|
588
|
+
.sneek-otp-caret {
|
|
589
|
+
animation: sneek-otp-caret 1s step-end infinite;
|
|
590
|
+
}
|
|
235
591
|
`;
|
|
236
592
|
}
|
|
237
593
|
|
|
238
594
|
// src/component.tsx
|
|
239
|
-
var
|
|
595
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
240
596
|
var CODE_LENGTH = 6;
|
|
241
597
|
var RESEND_SECONDS = 30;
|
|
598
|
+
var MAX_NATIONAL_DIGITS = 12;
|
|
242
599
|
function SneekMark() {
|
|
243
|
-
return /* @__PURE__ */ (0,
|
|
600
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
244
601
|
"svg",
|
|
245
602
|
{
|
|
246
603
|
width: "36",
|
|
@@ -250,8 +607,8 @@ function SneekMark() {
|
|
|
250
607
|
role: "img",
|
|
251
608
|
"aria-label": "Sneek",
|
|
252
609
|
children: [
|
|
253
|
-
/* @__PURE__ */ (0,
|
|
254
|
-
/* @__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)" })
|
|
255
612
|
]
|
|
256
613
|
}
|
|
257
614
|
);
|
|
@@ -261,8 +618,10 @@ function SneekOtpLogin(props) {
|
|
|
261
618
|
title = "Sign in",
|
|
262
619
|
verifyTitle = "Enter the code",
|
|
263
620
|
subtitle = "Enter your email or phone. We will send you a code.",
|
|
264
|
-
|
|
265
|
-
|
|
621
|
+
identifierMode = "both",
|
|
622
|
+
defaultCountry,
|
|
623
|
+
identifierLabel,
|
|
624
|
+
identifierPlaceholder,
|
|
266
625
|
theme = "auto",
|
|
267
626
|
accentColor,
|
|
268
627
|
logo,
|
|
@@ -272,12 +631,49 @@ function SneekOtpLogin(props) {
|
|
|
272
631
|
...handlers
|
|
273
632
|
} = props;
|
|
274
633
|
const otp = useSneekOtp(handlers);
|
|
275
|
-
const uid = (0,
|
|
276
|
-
const [resendIn, setResendIn] = (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)("");
|
|
277
644
|
const identifierId = `${uid}-identifier`;
|
|
278
645
|
const codeId = `${uid}-code`;
|
|
279
646
|
const statusId = `${uid}-status`;
|
|
280
|
-
(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)(() => {
|
|
281
677
|
if (typeof document === "undefined") return;
|
|
282
678
|
let el = document.getElementById(SNEEK_STYLE_ID);
|
|
283
679
|
if (!el) {
|
|
@@ -295,15 +691,15 @@ function SneekOtpLogin(props) {
|
|
|
295
691
|
event.preventDefault();
|
|
296
692
|
void otp.verify();
|
|
297
693
|
};
|
|
298
|
-
(0,
|
|
694
|
+
(0, import_react3.useEffect)(() => {
|
|
299
695
|
if (otp.step === "verify") setResendIn(RESEND_SECONDS);
|
|
300
696
|
}, [otp.step]);
|
|
301
|
-
(0,
|
|
697
|
+
(0, import_react3.useEffect)(() => {
|
|
302
698
|
if (resendIn <= 0) return;
|
|
303
699
|
const timer = setTimeout(() => setResendIn((n) => n - 1), 1e3);
|
|
304
700
|
return () => clearTimeout(timer);
|
|
305
701
|
}, [resendIn]);
|
|
306
|
-
(0,
|
|
702
|
+
(0, import_react3.useEffect)(() => {
|
|
307
703
|
if (otp.step === "verify" && otp.code.length === CODE_LENGTH && !otp.isBusy) {
|
|
308
704
|
void otp.verify();
|
|
309
705
|
}
|
|
@@ -357,7 +753,7 @@ function SneekOtpLogin(props) {
|
|
|
357
753
|
fontSize: 14,
|
|
358
754
|
color: "var(--sneek-text-muted)"
|
|
359
755
|
};
|
|
360
|
-
return /* @__PURE__ */ (0,
|
|
756
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
361
757
|
"div",
|
|
362
758
|
{
|
|
363
759
|
className: className ? `sneek-otp ${className}` : "sneek-otp",
|
|
@@ -376,8 +772,8 @@ function SneekOtpLogin(props) {
|
|
|
376
772
|
...style
|
|
377
773
|
},
|
|
378
774
|
children: [
|
|
379
|
-
logo === null ? null : logo ?? /* @__PURE__ */ (0,
|
|
380
|
-
/* @__PURE__ */ (0,
|
|
775
|
+
logo === null ? null : logo ?? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(SneekMark, {}),
|
|
776
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
381
777
|
"h1",
|
|
382
778
|
{
|
|
383
779
|
style: {
|
|
@@ -390,7 +786,7 @@ function SneekOtpLogin(props) {
|
|
|
390
786
|
children: otp.step === "verify" ? verifyTitle : title
|
|
391
787
|
}
|
|
392
788
|
),
|
|
393
|
-
otp.step === "verify" ? /* @__PURE__ */ (0,
|
|
789
|
+
otp.step === "verify" ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
394
790
|
"p",
|
|
395
791
|
{
|
|
396
792
|
style: {
|
|
@@ -402,9 +798,9 @@ function SneekOtpLogin(props) {
|
|
|
402
798
|
children: [
|
|
403
799
|
"Sent to",
|
|
404
800
|
" ",
|
|
405
|
-
/* @__PURE__ */ (0,
|
|
801
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { color: "var(--sneek-text)" }, children: otp.identifier }),
|
|
406
802
|
" \xB7 ",
|
|
407
|
-
/* @__PURE__ */ (0,
|
|
803
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
408
804
|
"button",
|
|
409
805
|
{
|
|
410
806
|
type: "button",
|
|
@@ -417,7 +813,7 @@ function SneekOtpLogin(props) {
|
|
|
417
813
|
)
|
|
418
814
|
]
|
|
419
815
|
}
|
|
420
|
-
) : subtitle ? /* @__PURE__ */ (0,
|
|
816
|
+
) : subtitle ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
421
817
|
"p",
|
|
422
818
|
{
|
|
423
819
|
style: {
|
|
@@ -429,7 +825,7 @@ function SneekOtpLogin(props) {
|
|
|
429
825
|
children: subtitle
|
|
430
826
|
}
|
|
431
827
|
) : null,
|
|
432
|
-
/* @__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)(
|
|
433
829
|
"div",
|
|
434
830
|
{
|
|
435
831
|
role: "alert",
|
|
@@ -441,32 +837,99 @@ function SneekOtpLogin(props) {
|
|
|
441
837
|
children: otp.error
|
|
442
838
|
}
|
|
443
839
|
) : null }),
|
|
444
|
-
otp.step === "identify" ? /* @__PURE__ */ (0,
|
|
840
|
+
otp.step === "identify" ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
445
841
|
"form",
|
|
446
842
|
{
|
|
447
843
|
onSubmit: onIdentifySubmit,
|
|
448
844
|
style: { display: "flex", flexDirection: "column", gap: 16, marginTop: 16 },
|
|
449
845
|
children: [
|
|
450
|
-
/* @__PURE__ */ (0,
|
|
451
|
-
identifierLabel,
|
|
452
|
-
/* @__PURE__ */ (0,
|
|
453
|
-
"
|
|
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",
|
|
454
850
|
{
|
|
455
|
-
|
|
456
|
-
className: "sneek-otp-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
autoComplete: "username",
|
|
462
|
-
autoFocus: true,
|
|
463
|
-
required: true,
|
|
464
|
-
"aria-describedby": statusId,
|
|
465
|
-
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"
|
|
466
857
|
}
|
|
467
|
-
)
|
|
858
|
+
) : null
|
|
468
859
|
] }),
|
|
469
|
-
/* @__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)(
|
|
470
933
|
"button",
|
|
471
934
|
{
|
|
472
935
|
type: "submit",
|
|
@@ -478,43 +941,90 @@ function SneekOtpLogin(props) {
|
|
|
478
941
|
)
|
|
479
942
|
]
|
|
480
943
|
}
|
|
481
|
-
) : /* @__PURE__ */ (0,
|
|
944
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
482
945
|
"form",
|
|
483
946
|
{
|
|
484
947
|
onSubmit: onVerifySubmit,
|
|
485
948
|
style: { display: "flex", flexDirection: "column", gap: 16, marginTop: 16 },
|
|
486
949
|
children: [
|
|
487
|
-
/* @__PURE__ */ (0,
|
|
950
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { style: { ...label, marginBottom: -8 }, htmlFor: codeId, children: [
|
|
488
951
|
CODE_LENGTH,
|
|
489
952
|
"-digit code"
|
|
490
953
|
] }),
|
|
491
|
-
/* @__PURE__ */ (0,
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
954
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { position: "relative" }, children: [
|
|
955
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
956
|
+
"input",
|
|
957
|
+
{
|
|
958
|
+
id: codeId,
|
|
959
|
+
className: "sneek-otp-code-input",
|
|
960
|
+
type: "text",
|
|
961
|
+
inputMode: "numeric",
|
|
962
|
+
pattern: "[0-9]*",
|
|
963
|
+
autoComplete: "one-time-code",
|
|
964
|
+
maxLength: CODE_LENGTH,
|
|
965
|
+
value: otp.code,
|
|
966
|
+
onChange: (e) => otp.setCode(
|
|
967
|
+
e.target.value.replace(/\D/g, "").slice(0, CODE_LENGTH)
|
|
968
|
+
),
|
|
969
|
+
onFocus: () => setCodeFocused(true),
|
|
970
|
+
onBlur: () => setCodeFocused(false),
|
|
971
|
+
autoFocus: true,
|
|
972
|
+
required: true,
|
|
973
|
+
"aria-describedby": statusId,
|
|
974
|
+
style: {
|
|
975
|
+
position: "absolute",
|
|
976
|
+
inset: 0,
|
|
977
|
+
width: "100%",
|
|
978
|
+
height: "100%",
|
|
979
|
+
opacity: 0,
|
|
980
|
+
caretColor: "transparent",
|
|
981
|
+
cursor: "text"
|
|
982
|
+
}
|
|
514
983
|
}
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
984
|
+
),
|
|
985
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
986
|
+
"div",
|
|
987
|
+
{
|
|
988
|
+
className: "sneek-otp-code-cells",
|
|
989
|
+
"aria-hidden": "true",
|
|
990
|
+
style: { display: "flex", gap: 10 },
|
|
991
|
+
children: Array.from({ length: CODE_LENGTH }).map((_, i) => {
|
|
992
|
+
const digit = otp.code[i];
|
|
993
|
+
const isActive = codeFocused && i === otp.code.length;
|
|
994
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
995
|
+
"div",
|
|
996
|
+
{
|
|
997
|
+
"data-active": isActive,
|
|
998
|
+
style: {
|
|
999
|
+
flex: 1,
|
|
1000
|
+
height: 44,
|
|
1001
|
+
display: "flex",
|
|
1002
|
+
alignItems: "center",
|
|
1003
|
+
justifyContent: "center",
|
|
1004
|
+
fontSize: 22,
|
|
1005
|
+
color: "var(--sneek-text)",
|
|
1006
|
+
borderBottom: `2px solid ${isActive ? "var(--sneek-accent)" : "var(--sneek-border)"}`,
|
|
1007
|
+
transition: "border-color 200ms var(--sneek-ease)"
|
|
1008
|
+
},
|
|
1009
|
+
children: digit ?? (isActive ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1010
|
+
"span",
|
|
1011
|
+
{
|
|
1012
|
+
className: "sneek-otp-caret",
|
|
1013
|
+
style: {
|
|
1014
|
+
width: 2,
|
|
1015
|
+
height: 22,
|
|
1016
|
+
background: "var(--sneek-accent)"
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
) : null)
|
|
1020
|
+
},
|
|
1021
|
+
i
|
|
1022
|
+
);
|
|
1023
|
+
})
|
|
1024
|
+
}
|
|
1025
|
+
)
|
|
1026
|
+
] }),
|
|
1027
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
518
1028
|
"button",
|
|
519
1029
|
{
|
|
520
1030
|
type: "submit",
|
|
@@ -527,7 +1037,7 @@ function SneekOtpLogin(props) {
|
|
|
527
1037
|
children: otp.isVerifying ? "Verifying\u2026" : "Verify"
|
|
528
1038
|
}
|
|
529
1039
|
),
|
|
530
|
-
/* @__PURE__ */ (0,
|
|
1040
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { textAlign: "center" }, children: resendIn > 0 ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
531
1041
|
"span",
|
|
532
1042
|
{
|
|
533
1043
|
style: {
|
|
@@ -540,7 +1050,7 @@ function SneekOtpLogin(props) {
|
|
|
540
1050
|
"s"
|
|
541
1051
|
]
|
|
542
1052
|
}
|
|
543
|
-
) : /* @__PURE__ */ (0,
|
|
1053
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
544
1054
|
"button",
|
|
545
1055
|
{
|
|
546
1056
|
type: "button",
|
|
@@ -554,7 +1064,7 @@ function SneekOtpLogin(props) {
|
|
|
554
1064
|
]
|
|
555
1065
|
}
|
|
556
1066
|
),
|
|
557
|
-
showBranding ? /* @__PURE__ */ (0,
|
|
1067
|
+
showBranding ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
558
1068
|
"p",
|
|
559
1069
|
{
|
|
560
1070
|
style: {
|
|
@@ -564,7 +1074,26 @@ function SneekOtpLogin(props) {
|
|
|
564
1074
|
lineHeight: "16px",
|
|
565
1075
|
color: "var(--sneek-text-muted)"
|
|
566
1076
|
},
|
|
567
|
-
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
|
+
]
|
|
568
1097
|
}
|
|
569
1098
|
) : null
|
|
570
1099
|
]
|