PasskeyToast.tsx

2.5 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        <h2 class="peach-passkey-toast-headline">62          sign in to {props.site}?63        </h2>6465        <div class="peach-passkey-toast-user">66          {props.displayName} · {props.username}67        </div>6869        <div class="peach-passkey-toast-countdown">70          <div71            class="peach-passkey-toast-countdown-bar"72            style={{ 'animation-duration': `${TIMEOUT_MS}ms` }}73          />74        </div>7576        <div class="peach-passkey-toast-actions">77          <button78            class="peach-passkey-toast-btn peach-passkey-toast-btn--deny"79            on:click={handleDeny}80          >81            deny82          </button>83          <button84            class="peach-passkey-toast-btn peach-passkey-toast-btn--signin"85            on:click={handleSignIn}86          >87            sign in88          </button>89        </div>90      </div>91    </div>92  );93}9495function KeyIcon() {96  return (97    <svg width="22" height="22" viewBox="0 0 22 22" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round">98      <circle cx="7" cy="11" r="3.5" />99      <path d="M10.5 11h9M16.5 11v3M19.5 11v2" />100    </svg>101  );102}