@mrgnw/anahtar 0.0.17 → 0.0.19

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.
@@ -15,6 +15,7 @@ interface PasskeyInfo {
15
15
  interface Props {
16
16
  apiBase?: string;
17
17
  user?: { email: string } | null;
18
+ compact?: boolean;
18
19
  locale?: string;
19
20
  messages?: Partial<AuthMessages>;
20
21
  onSuccess?: () => void | Promise<void>;
@@ -26,6 +27,7 @@ interface Props {
26
27
  let {
27
28
  apiBase = '/api/auth',
28
29
  user = null,
30
+ compact = false,
29
31
  locale,
30
32
  messages: messageOverrides,
31
33
  onSuccess,
@@ -34,6 +36,8 @@ let {
34
36
  getPasskeys,
35
37
  }: Props = $props();
36
38
 
39
+ let expanded = $state(false);
40
+
37
41
  let m = $derived(resolveMessages(locale ?? detectLocaleClient(), messageOverrides));
38
42
 
39
43
  let email = $state('');
@@ -43,17 +47,26 @@ let otpStep = $state(false);
43
47
  let otpDigits = $state<string[]>(['', '', '', '', '']);
44
48
  let otpInputs = $state<HTMLInputElement[]>([]);
45
49
  let showPasskeys = $state(false);
50
+ let isTouch = $state(false);
46
51
  let hoveredKey = $state<string | null>(null);
47
52
  let passkeyRefresh = $state(0);
48
53
  let passkeyOnboarding = $state(false);
49
54
  let conditionalAbort: AbortController | null = null;
50
55
 
51
56
  const isAuthenticated = $derived(!!user);
57
+
58
+ function shortDate(ts?: number): string {
59
+ if (!ts) return '';
60
+ const d = new Date(ts);
61
+ const mon = d.toLocaleDateString(undefined, { month: 'short' });
62
+ return `${mon} ${d.getFullYear()}`;
63
+ }
52
64
  const passkeyPromise = $derived(
53
65
  isAuthenticated && getPasskeys && passkeyRefresh >= 0 ? getPasskeys() : null
54
66
  );
55
67
 
56
68
  onMount(() => {
69
+ isTouch = matchMedia('(pointer: coarse)').matches;
57
70
  if (!isAuthenticated) tryConditionalWebAuthn();
58
71
  return () => conditionalAbort?.abort();
59
72
  });
@@ -63,6 +76,7 @@ async function handleSignOut() {
63
76
  otpStep = false;
64
77
  passkeyOnboarding = false;
65
78
  showPasskeys = false;
79
+ expanded = false;
66
80
  error = '';
67
81
  await onSignOut?.();
68
82
  }
@@ -304,7 +318,13 @@ async function removePasskey(id: string) {
304
318
 
305
319
  <div class="anahtar-pill-island" class:anahtar-pill-loading={loading}>
306
320
  <div class="anahtar-pill">
307
- {#if isAuthenticated}
321
+ {#if isAuthenticated && compact && !expanded}
322
+ <button class="anahtar-pill-icon" onclick={() => (expanded = true)} title={user?.email}>
323
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
324
+ <circle cx="12" cy="8" r="5"/><path d="M20 21a8 8 0 0 0-16 0"/>
325
+ </svg>
326
+ </button>
327
+ {:else if isAuthenticated}
308
328
  <span class="anahtar-pill-email">{user?.email}</span>
309
329
  <span class="anahtar-pill-sep">&middot;</span>
310
330
  {#if getPasskeys}
@@ -326,6 +346,14 @@ async function removePasskey(id: string) {
326
346
  <path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/><polyline points="16 17 21 12 16 7"/><line x1="21" x2="9" y1="12" y2="12"/>
327
347
  </svg>
328
348
  </button>
349
+ {#if compact}
350
+ <span class="anahtar-pill-sep">&middot;</span>
351
+ <button class="anahtar-pill-icon" onclick={() => { expanded = false; showPasskeys = false; }} title="Collapse">
352
+ <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
353
+ <line x1="18" x2="6" y1="6" y2="18"/><line x1="6" x2="18" y1="6" y2="18"/>
354
+ </svg>
355
+ </button>
356
+ {/if}
329
357
 
330
358
  {:else if otpStep}
331
359
  <span class="anahtar-pill-otp-label">{email}</span>
@@ -390,7 +418,7 @@ async function removePasskey(id: string) {
390
418
  {#await passkeyPromise then keys}
391
419
  {#each keys as key}
392
420
  <!-- svelte-ignore a11y_no_static_element_interactions -->
393
- <div
421
+ <div
394
422
  class="anahtar-pill-key-row"
395
423
  onmouseenter={() => (hoveredKey = key.id)}
396
424
  onmouseleave={() => (hoveredKey = null)}
@@ -399,7 +427,10 @@ async function removePasskey(id: string) {
399
427
  <circle cx="7.5" cy="15.5" r="5.5"/><path d="m11.5 12 4-4"/><path d="m15 7 2 2"/><path d="m17.5 4.5 2 2"/>
400
428
  </svg>
401
429
  <span class="anahtar-pill-key-name">{key.name ?? 'Passkey'}</span>
402
- {#if hoveredKey === key.id}
430
+ {#if key.createdAt}
431
+ <span class="anahtar-pill-key-date">{shortDate(key.createdAt)}</span>
432
+ {/if}
433
+ {#if isTouch || hoveredKey === key.id}
403
434
  <button
404
435
  class="anahtar-pill-key-remove"
405
436
  onclick={() => removePasskey(key.id)}
@@ -414,7 +445,7 @@ async function removePasskey(id: string) {
414
445
  <svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
415
446
  <line x1="12" x2="12" y1="5" y2="19"/><line x1="5" x2="19" y1="12" y2="12"/>
416
447
  </svg>
417
- Add passkey
448
+ Add
418
449
  </button>
419
450
  </div>
420
451
  {/if}
@@ -588,21 +619,21 @@ async function removePasskey(id: string) {
588
619
  backdrop-filter: blur(8px);
589
620
  border: 1px solid var(--anahtar-pill-border, rgba(0,0,0,0.06));
590
621
  border-radius: 0.75rem;
591
- padding: 0.5rem 0.75rem;
622
+ padding: 0.375rem 0.625rem;
592
623
  box-shadow: var(--anahtar-pill-shadow, 0 2px 12px rgba(0,0,0,0.08));
593
624
  display: flex;
594
625
  flex-direction: column;
595
- gap: 0.25rem;
596
- min-width: 180px;
626
+ gap: 0.125rem;
627
+ min-width: 160px;
597
628
  }
598
629
 
599
630
  .anahtar-pill-key-row {
600
631
  display: flex;
601
632
  align-items: center;
602
- gap: 0.375rem;
603
- font-size: 0.8125rem;
633
+ gap: 0.3rem;
634
+ font-size: 0.75rem;
604
635
  color: var(--anahtar-pill-fg, #374151);
605
- padding: 0.2rem 0;
636
+ padding: 0.15rem 0;
606
637
  }
607
638
 
608
639
  .anahtar-pill-key-name {
@@ -612,12 +643,18 @@ async function removePasskey(id: string) {
612
643
  white-space: nowrap;
613
644
  }
614
645
 
646
+ .anahtar-pill-key-date {
647
+ font-size: 0.6875rem;
648
+ color: var(--anahtar-pill-icon, #9ca3af);
649
+ white-space: nowrap;
650
+ }
651
+
615
652
  .anahtar-pill-key-remove {
616
653
  background: none;
617
654
  border: none;
618
655
  color: var(--anahtar-pill-icon, #9ca3af);
619
656
  cursor: pointer;
620
- font-size: 1rem;
657
+ font-size: 0.875rem;
621
658
  line-height: 1;
622
659
  padding: 0 0.125rem;
623
660
  transition: color 0.15s;
@@ -628,15 +665,14 @@ async function removePasskey(id: string) {
628
665
  .anahtar-pill-key-add {
629
666
  display: flex;
630
667
  align-items: center;
631
- gap: 0.3rem;
632
- font-size: 0.75rem;
668
+ gap: 0.25rem;
669
+ font-size: 0.6875rem;
633
670
  color: var(--anahtar-pill-icon, #6b7280);
634
671
  background: none;
635
672
  border: none;
636
673
  cursor: pointer;
637
- padding: 0.2rem 0;
674
+ padding: 0.15rem 0;
638
675
  transition: color 0.15s;
639
- margin-top: 0.125rem;
640
676
  }
641
677
  .anahtar-pill-key-add:hover:not(:disabled) { color: var(--anahtar-primary, #3730a3); }
642
678
  .anahtar-pill-key-add:disabled { opacity: 0.4; cursor: not-allowed; }
@@ -10,6 +10,7 @@ interface Props {
10
10
  user?: {
11
11
  email: string;
12
12
  } | null;
13
+ compact?: boolean;
13
14
  locale?: string;
14
15
  messages?: Partial<AuthMessages>;
15
16
  onSuccess?: () => void | Promise<void>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrgnw/anahtar",
3
- "version": "0.0.17",
3
+ "version": "0.0.19",
4
4
  "description": "Opinionated, reusable auth for SvelteKit. Email+OTP + passkeys.",
5
5
  "license": "MIT",
6
6
  "type": "module",