PasskeyToast.tsx

2.7 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        <p class="peach-passkey-toast-notice">72          Peach will authenticate you with your stored passkey for {props.site}.73        </p>7475        <div class="peach-passkey-toast-countdown">76          <div77            class="peach-passkey-toast-countdown-bar"78            style={{ 'animation-duration': `${TIMEOUT_MS}ms` }}79          />80        </div>8182        <div class="peach-passkey-toast-actions">83          <button84            class="peach-passkey-toast-btn peach-passkey-toast-btn--deny"85            on:click={handleDeny}86          >87            deny88          </button>89          <button90            class="peach-passkey-toast-btn peach-passkey-toast-btn--signin"91            on:click={handleSignIn}92          >93            sign in94          </button>95        </div>96      </div>97    </div>98  );99}100101function KeyIcon() {102  return (103    <svg width="22" height="22" viewBox="0 0 22 22" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round">104      <circle cx="7" cy="11" r="3.5" />105      <path d="M10.5 11h9M16.5 11v3M19.5 11v2" />106    </svg>107  );108}