~ knotless Search/
↑↓ move↵ open⇧↵ all results

chadtmiller.com/pds.js

An atproto PDS in JavaScript, for Cloudflare Workers and Node

0
0
main
77 lines · 3.2 KB Raw History
import { Select } from '@base-ui/react/select';
import { CheckIcon, ChevronsUpDownIcon, GitBranchIcon } from 'lucide-react';
import { branchRefs, shortRef, tagRefs } from '#/lib/git.js';
import { cn } from '#/lib/utils.js';

/**
 * The branch and tag switcher. Both groups come from the repository record,
 * so opening the menu costs nothing. Choosing a ref lands on that ref's root:
 * the path in view may not exist on it.
 *
 * `tags` off leaves the tags out, for a caller asking which branch rather
 * than which ref. A repository with nothing to choose between renders none
 * of this.
 */
export function RefSelect({ repo, value, onSelect, tags = true, className }) {
  const branches = branchRefs(repo).map((ref) => shortRef(ref.name));
  const tagNames = tags ? tagRefs(repo).map((ref) => shortRef(ref.name)) : [];
  if (branches.length + tagNames.length === 0) return null;

  const groups = [
    ['Branches', branches],
    ['Tags', tagNames],
  ].filter(([, names]) => names.length > 0);

  return (
    <Select.Root value={value} onValueChange={onSelect}>
      <Select.Trigger
        className={cn(
          'inline-flex h-9 cursor-pointer items-center gap-2 rounded-xl border border-input bg-background px-3 text-sm outline-none transition-colors',
          'hover:bg-accent focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50',
          className,
        )}
      >
        <GitBranchIcon className="size-3.5 shrink-0 text-faint" />
        <Select.Value className="max-w-40 truncate font-mono text-[13px]" />
        <Select.Icon>
          <ChevronsUpDownIcon className="size-3.5 shrink-0 text-faint" />
        </Select.Icon>
      </Select.Trigger>
      <Select.Portal>
        <Select.Positioner
          className="z-50"
          sideOffset={6}
          alignItemWithTrigger={false}
        >
          <Select.Popup className="max-h-80 min-w-[var(--anchor-width)] overflow-y-auto rounded-xl border border-border bg-card p-1 shadow-lg outline-none">
            {groups.map(([label, names]) => (
              <Select.Group key={label}>
                <Select.GroupLabel className="px-2 py-1.5 text-[11px] text-faint uppercase tracking-wide">
                  {label}
                </Select.GroupLabel>
                {names.map((name) => (
                  <Select.Item
                    key={name}
                    value={name}
                    className="flex cursor-pointer items-center gap-2 rounded-lg py-1.5 pr-2 pl-2 font-mono text-[13px] outline-none data-[highlighted]:bg-accent"
                  >
                    {/* The indicator renders only for the selected item, so
                        the fixed box keeps every label on one edge. */}
                    <span className="flex size-3.5 shrink-0 items-center">
                      <Select.ItemIndicator>
                        <CheckIcon className="size-3.5 text-key" />
                      </Select.ItemIndicator>
                    </span>
                    <Select.ItemText className="truncate">
                      {name}
                    </Select.ItemText>
                  </Select.Item>
                ))}
              </Select.Group>
            ))}
          </Select.Popup>
        </Select.Positioner>
      </Select.Portal>
    </Select.Root>
  );
}