Vectrum
Sign In
111 lines · 3.6 KB · TypeScript
"use client";

import Link from "next/link";
import { useEffect, useRef, useState } from "react";
import { Button, Field, IconButton, Popover, Switcher, Tooltip } from "@unlocalhosted/metalui";
import { ChevronDownIcon, CodeIcon, MorphIcon, TerminalIcon } from "./icons";

/**
 * Copies `text`: a ghost icon key whose glyph morphs from duplicate to a check once the text is on the
 * clipboard, and back after a moment.
 */
export function CopyButton({ text, label = "Copy" }: { text: string; label?: string }) {
  const [copied, setCopied] = useState(false);
  const timer = useRef<ReturnType<typeof setTimeout>>(undefined);
  useEffect(() => () => clearTimeout(timer.current), []);
  return (
    <Tooltip label={copied ? "Copied" : label}>
      <IconButton
        label={label}
        icon={<MorphIcon name={copied ? "check" : "duplicate"} />}
        className={copied ? "text-green-deep hover:text-green-deep" : ""}
        onClick={async () => {
          await navigator.clipboard.writeText(text);
          setCopied(true);
          clearTimeout(timer.current);
          timer.current = setTimeout(() => setCopied(false), 1500);
        }}
      />
    </Tooltip>
  );
}

export type CloneUrls = { https: string; ssh: string };

type Protocol = keyof CloneUrls;

/** The clone URL: HTTPS or SSH on a switcher, the URL in a read-only well with its copy key. */
export function CloneUrlTabs({ urls }: { urls: CloneUrls }) {
  const [protocol, setProtocol] = useState<Protocol>("https");
  const url = urls[protocol];
  return (
    <div className="flex flex-col gap-2.5">
      <Switcher<Protocol>
        aria-label="Clone protocol"
        size="compact"
        value={protocol}
        onValueChange={setProtocol}
        options={[
          { value: "https", label: "HTTPS" },
          { value: "ssh", label: "SSH" },
        ]}
        className="self-start"
      />
      <Field size="regular" className="pr-1">
        <Field.Input
          readOnly
          value={url}
          aria-label="Clone URL"
          className="font-mono text-[11.5px]"
          onFocus={(e) => e.target.select()}
        />
        <Field.Trail>
          <CopyButton text={url} label="Copy URL" />
        </Field.Trail>
      </Field>
      <p className="type-meta text-ink3">
        {protocol === "https" ? (
          <>
            Private repos and pushes need an{" "}
            <Link href="/settings/tokens" className="text-ink underline decoration-rule underline-offset-2 hover:decoration-current">
              API token
            </Link>{" "}
            as the password.
          </>
        ) : (
          <>
            Uses an{" "}
            <Link href="/settings/keys" className="text-ink underline decoration-rule underline-offset-2 hover:decoration-current">
              SSH key
            </Link>{" "}
            from your account.
          </>
        )}
      </p>
    </div>
  );
}

/** The repository's "Code" key: a popover with the clone URL. */
export function CloneButton({ urls }: { urls: CloneUrls }) {
  return (
    <Popover>
      <Popover.Trigger>
        <Button cap="primary" icon={<CodeIcon />}>
          Code
          <span aria-hidden className="-mr-1 inline-grid opacity-70">
            <ChevronDownIcon width={12} height={12} />
          </span>
        </Button>
      </Popover.Trigger>
      <Popover.Content side="bottom" align="end" className="w-[320px]">
        <Popover.Title className="flex items-center gap-2">
          <TerminalIcon width={14} height={14} className="text-ink2" /> Clone this repository
        </Popover.Title>
        <Popover.Body>
          <CloneUrlTabs urls={urls} />
        </Popover.Body>
      </Popover.Content>
    </Popover>
  );
}