PasskeyToast.tsx

2.6 KB · raw

1import { createSignal, onMount, onCleanup } from 'solid-js';23interface PasskeyToastProps {4  site: string;5  displayName: string;6  username: string;7  requestId: string;8  onSignIn: () => void;9  onDeny: () => void;10}1112const TIMEOUT_MS = 30_000;1314export function PasskeyToast(props: PasskeyToastProps) {15  const [visible, setVisible] = createSignal(false);16  const [exiting, setExiting] = createSignal(false);17  let timeoutId: number;1819  onMount(() => {20    requestAnimationFrame(() => setVisible(true));2122    timeoutId = window.setTimeout(() => {23      handleDeny();24    }, TIMEOUT_MS);25  });2627  onCleanup(() => {28    clearTimeout(timeoutId);29  });3031  function handleSignIn() {32    clearTimeout(timeoutId);33    exit(props.onSignIn);34  }3536  function handleDeny() {37    clearTimeout(timeoutId);38    exit(props.onDeny);39  }4041  function exit(callback: () => void) {42    setExiting(true);43    setTimeout(callback, 220);44  }4546  // on:click bypasses Solid's document-level event delegation, which breaks47  // inside shadow DOM because event.target is retargeted to the shadow host.48  return (49    <div50      class={[51        'peach-passkey-toast',52        visible() ? 'is-visible' : '',53        exiting() ? 'is-exiting' : '',54      ].join(' ')}55    >56      <div class="peach-passkey-toast-body">57        <div class="peach-passkey-toast-icon">58          <KeyIcon />59        </div>6061        <div class="peach-passkey-toast-domain">{props.site}</div>6263        <h2 class="peach-passkey-toast-headline">64          sign in with passkey?65        </h2>6667        <div class="peach-passkey-toast-user">68          {props.displayName} · {props.username}69        </div>7071        <div class="peach-passkey-toast-countdown">72          <div73            class="peach-passkey-toast-countdown-bar"74            style={{ 'animation-duration': `${TIMEOUT_MS}ms` }}75          />76        </div>7778        <div class="peach-passkey-toast-actions">79          <button80            class="peach-passkey-toast-btn peach-passkey-toast-btn--deny"81            on:click={handleDeny}82          >83            deny84          </button>85          <button86            class="peach-passkey-toast-btn peach-passkey-toast-btn--signin"87            on:click={handleSignIn}88          >89            sign in90          </button>91        </div>92      </div>93    </div>94  );95}9697function KeyIcon() {98  return (99    <svg width="22" height="22" viewBox="0 0 22 22" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round">100      <circle cx="7" cy="11" r="3.5" />101      <path d="M10.5 11h9M16.5 11v3M19.5 11v2" />102    </svg>103  );104}