@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/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,19 +524,30 @@ 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
  }
@@ -214,11 +562,12 @@ function buildStyles(accent) {
214
562
  }
215
563
 
216
564
  // src/component.tsx
217
- import { jsx, jsxs } from "react/jsx-runtime";
565
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
218
566
  var CODE_LENGTH = 6;
219
567
  var RESEND_SECONDS = 30;
568
+ var MAX_NATIONAL_DIGITS = 12;
220
569
  function SneekMark() {
221
- return /* @__PURE__ */ jsxs(
570
+ return /* @__PURE__ */ jsxs2(
222
571
  "svg",
223
572
  {
224
573
  width: "36",
@@ -228,8 +577,8 @@ function SneekMark() {
228
577
  role: "img",
229
578
  "aria-label": "Sneek",
230
579
  children: [
231
- /* @__PURE__ */ jsx("path", { d: "M21.3 1 29.3 16H13.3L21.3 1Z", fill: "var(--sneek-accent)" }),
232
- /* @__PURE__ */ jsx("path", { d: "M2.7 17.5H18.7L10.7 31 2.7 17.5Z", fill: "var(--sneek-accent)" })
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)" })
233
582
  ]
234
583
  }
235
584
  );
@@ -239,8 +588,10 @@ function SneekOtpLogin(props) {
239
588
  title = "Sign in",
240
589
  verifyTitle = "Enter the code",
241
590
  subtitle = "Enter your email or phone. We will send you a code.",
242
- identifierLabel = "Email or mobile",
243
- identifierPlaceholder = "you@company.com or +91\u2026",
591
+ identifierMode = "both",
592
+ defaultCountry,
593
+ identifierLabel,
594
+ identifierPlaceholder,
244
595
  theme = "auto",
245
596
  accentColor,
246
597
  logo,
@@ -250,13 +601,49 @@ function SneekOtpLogin(props) {
250
601
  ...handlers
251
602
  } = props;
252
603
  const otp = useSneekOtp(handlers);
253
- const uid = useId();
254
- const [resendIn, setResendIn] = useState(0);
255
- const [codeFocused, setCodeFocused] = useState(false);
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("");
256
614
  const identifierId = `${uid}-identifier`;
257
615
  const codeId = `${uid}-code`;
258
616
  const statusId = `${uid}-status`;
259
- useEffect(() => {
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(() => {
260
647
  if (typeof document === "undefined") return;
261
648
  let el = document.getElementById(SNEEK_STYLE_ID);
262
649
  if (!el) {
@@ -274,15 +661,15 @@ function SneekOtpLogin(props) {
274
661
  event.preventDefault();
275
662
  void otp.verify();
276
663
  };
277
- useEffect(() => {
664
+ useEffect2(() => {
278
665
  if (otp.step === "verify") setResendIn(RESEND_SECONDS);
279
666
  }, [otp.step]);
280
- useEffect(() => {
667
+ useEffect2(() => {
281
668
  if (resendIn <= 0) return;
282
669
  const timer = setTimeout(() => setResendIn((n) => n - 1), 1e3);
283
670
  return () => clearTimeout(timer);
284
671
  }, [resendIn]);
285
- useEffect(() => {
672
+ useEffect2(() => {
286
673
  if (otp.step === "verify" && otp.code.length === CODE_LENGTH && !otp.isBusy) {
287
674
  void otp.verify();
288
675
  }
@@ -336,7 +723,7 @@ function SneekOtpLogin(props) {
336
723
  fontSize: 14,
337
724
  color: "var(--sneek-text-muted)"
338
725
  };
339
- return /* @__PURE__ */ jsxs(
726
+ return /* @__PURE__ */ jsxs2(
340
727
  "div",
341
728
  {
342
729
  className: className ? `sneek-otp ${className}` : "sneek-otp",
@@ -355,8 +742,8 @@ function SneekOtpLogin(props) {
355
742
  ...style
356
743
  },
357
744
  children: [
358
- logo === null ? null : logo ?? /* @__PURE__ */ jsx(SneekMark, {}),
359
- /* @__PURE__ */ jsx(
745
+ logo === null ? null : logo ?? /* @__PURE__ */ jsx2(SneekMark, {}),
746
+ /* @__PURE__ */ jsx2(
360
747
  "h1",
361
748
  {
362
749
  style: {
@@ -369,7 +756,7 @@ function SneekOtpLogin(props) {
369
756
  children: otp.step === "verify" ? verifyTitle : title
370
757
  }
371
758
  ),
372
- otp.step === "verify" ? /* @__PURE__ */ jsxs(
759
+ otp.step === "verify" ? /* @__PURE__ */ jsxs2(
373
760
  "p",
374
761
  {
375
762
  style: {
@@ -381,9 +768,9 @@ function SneekOtpLogin(props) {
381
768
  children: [
382
769
  "Sent to",
383
770
  " ",
384
- /* @__PURE__ */ jsx("span", { style: { color: "var(--sneek-text)" }, children: otp.identifier }),
771
+ /* @__PURE__ */ jsx2("span", { style: { color: "var(--sneek-text)" }, children: otp.identifier }),
385
772
  " \xB7 ",
386
- /* @__PURE__ */ jsx(
773
+ /* @__PURE__ */ jsx2(
387
774
  "button",
388
775
  {
389
776
  type: "button",
@@ -396,7 +783,7 @@ function SneekOtpLogin(props) {
396
783
  )
397
784
  ]
398
785
  }
399
- ) : subtitle ? /* @__PURE__ */ jsx(
786
+ ) : subtitle ? /* @__PURE__ */ jsx2(
400
787
  "p",
401
788
  {
402
789
  style: {
@@ -408,7 +795,7 @@ function SneekOtpLogin(props) {
408
795
  children: subtitle
409
796
  }
410
797
  ) : null,
411
- /* @__PURE__ */ jsx("div", { role: "status", "aria-live": "polite", id: statusId, style: { marginTop: 32 }, children: otp.error ? /* @__PURE__ */ jsx(
798
+ /* @__PURE__ */ jsx2("div", { role: "status", "aria-live": "polite", id: statusId, style: { marginTop: 32 }, children: otp.error ? /* @__PURE__ */ jsx2(
412
799
  "div",
413
800
  {
414
801
  role: "alert",
@@ -420,32 +807,99 @@ function SneekOtpLogin(props) {
420
807
  children: otp.error
421
808
  }
422
809
  ) : null }),
423
- otp.step === "identify" ? /* @__PURE__ */ jsxs(
810
+ otp.step === "identify" ? /* @__PURE__ */ jsxs2(
424
811
  "form",
425
812
  {
426
813
  onSubmit: onIdentifySubmit,
427
814
  style: { display: "flex", flexDirection: "column", gap: 16, marginTop: 16 },
428
815
  children: [
429
- /* @__PURE__ */ jsxs("label", { style: label, htmlFor: identifierId, children: [
430
- identifierLabel,
431
- /* @__PURE__ */ jsx(
432
- "input",
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",
433
820
  {
434
- id: identifierId,
435
- className: "sneek-otp-input",
436
- type: "text",
437
- value: otp.identifier,
438
- onChange: (e) => otp.setIdentifier(e.target.value),
439
- placeholder: identifierPlaceholder,
440
- autoComplete: "username",
441
- autoFocus: true,
442
- required: true,
443
- "aria-describedby": statusId,
444
- 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"
445
827
  }
446
- )
828
+ ) : null
447
829
  ] }),
448
- /* @__PURE__ */ jsx(
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(
449
903
  "button",
450
904
  {
451
905
  type: "submit",
@@ -457,18 +911,18 @@ function SneekOtpLogin(props) {
457
911
  )
458
912
  ]
459
913
  }
460
- ) : /* @__PURE__ */ jsxs(
914
+ ) : /* @__PURE__ */ jsxs2(
461
915
  "form",
462
916
  {
463
917
  onSubmit: onVerifySubmit,
464
918
  style: { display: "flex", flexDirection: "column", gap: 16, marginTop: 16 },
465
919
  children: [
466
- /* @__PURE__ */ jsxs("label", { style: { ...label, marginBottom: -8 }, htmlFor: codeId, children: [
920
+ /* @__PURE__ */ jsxs2("label", { style: { ...label, marginBottom: -8 }, htmlFor: codeId, children: [
467
921
  CODE_LENGTH,
468
922
  "-digit code"
469
923
  ] }),
470
- /* @__PURE__ */ jsxs("div", { style: { position: "relative" }, children: [
471
- /* @__PURE__ */ jsx(
924
+ /* @__PURE__ */ jsxs2("div", { style: { position: "relative" }, children: [
925
+ /* @__PURE__ */ jsx2(
472
926
  "input",
473
927
  {
474
928
  id: codeId,
@@ -498,7 +952,7 @@ function SneekOtpLogin(props) {
498
952
  }
499
953
  }
500
954
  ),
501
- /* @__PURE__ */ jsx(
955
+ /* @__PURE__ */ jsx2(
502
956
  "div",
503
957
  {
504
958
  className: "sneek-otp-code-cells",
@@ -507,7 +961,7 @@ function SneekOtpLogin(props) {
507
961
  children: Array.from({ length: CODE_LENGTH }).map((_, i) => {
508
962
  const digit = otp.code[i];
509
963
  const isActive = codeFocused && i === otp.code.length;
510
- return /* @__PURE__ */ jsx(
964
+ return /* @__PURE__ */ jsx2(
511
965
  "div",
512
966
  {
513
967
  "data-active": isActive,
@@ -522,7 +976,7 @@ function SneekOtpLogin(props) {
522
976
  borderBottom: `2px solid ${isActive ? "var(--sneek-accent)" : "var(--sneek-border)"}`,
523
977
  transition: "border-color 200ms var(--sneek-ease)"
524
978
  },
525
- children: digit ?? (isActive ? /* @__PURE__ */ jsx(
979
+ children: digit ?? (isActive ? /* @__PURE__ */ jsx2(
526
980
  "span",
527
981
  {
528
982
  className: "sneek-otp-caret",
@@ -540,7 +994,7 @@ function SneekOtpLogin(props) {
540
994
  }
541
995
  )
542
996
  ] }),
543
- /* @__PURE__ */ jsx(
997
+ /* @__PURE__ */ jsx2(
544
998
  "button",
545
999
  {
546
1000
  type: "submit",
@@ -553,7 +1007,7 @@ function SneekOtpLogin(props) {
553
1007
  children: otp.isVerifying ? "Verifying\u2026" : "Verify"
554
1008
  }
555
1009
  ),
556
- /* @__PURE__ */ jsx("div", { style: { textAlign: "center" }, children: resendIn > 0 ? /* @__PURE__ */ jsxs(
1010
+ /* @__PURE__ */ jsx2("div", { style: { textAlign: "center" }, children: resendIn > 0 ? /* @__PURE__ */ jsxs2(
557
1011
  "span",
558
1012
  {
559
1013
  style: {
@@ -566,7 +1020,7 @@ function SneekOtpLogin(props) {
566
1020
  "s"
567
1021
  ]
568
1022
  }
569
- ) : /* @__PURE__ */ jsx(
1023
+ ) : /* @__PURE__ */ jsx2(
570
1024
  "button",
571
1025
  {
572
1026
  type: "button",
@@ -580,7 +1034,7 @@ function SneekOtpLogin(props) {
580
1034
  ]
581
1035
  }
582
1036
  ),
583
- showBranding ? /* @__PURE__ */ jsx(
1037
+ showBranding ? /* @__PURE__ */ jsxs2(
584
1038
  "p",
585
1039
  {
586
1040
  style: {
@@ -590,7 +1044,26 @@ function SneekOtpLogin(props) {
590
1044
  lineHeight: "16px",
591
1045
  color: "var(--sneek-text-muted)"
592
1046
  },
593
- children: "Secured by Sneek"
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
+ ]
594
1067
  }
595
1068
  ) : null
596
1069
  ]
@@ -626,10 +1099,16 @@ function createFetchHandlers(options = {}) {
626
1099
  };
627
1100
  }
628
1101
  export {
1102
+ COUNTRIES,
1103
+ DEFAULT_COUNTRY_ISO2,
629
1104
  SNEEK_ACCENT_DARK,
630
1105
  SNEEK_ACCENT_LIGHT,
631
1106
  SneekOtpLogin,
1107
+ countryByDialPrefix,
1108
+ countryByIso2,
632
1109
  createFetchHandlers,
1110
+ detectCountryFromLocale,
1111
+ flagEmoji,
633
1112
  formatChannels,
634
1113
  initialOtpState,
635
1114
  otpReducer,