import { forwardRef, useLayoutEffect, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import * as Popover from "@radix-ui/react-popover"; import * as ScrollArea from "@radix-ui/react-scroll-area"; import { Command } from "cmdk"; import { Check, ChevronDown, Settings2, UserCircle } from "lucide-react"; import { pickProfileIcon } from "@/modules/profiles/ProfileAvatar"; import type { Profile } from "@bindings/services/models.js"; import { Tooltip } from "@/components/Tooltip"; import { useProfile } from "@/contexts/ProfileContext"; import { useFocusVisible } from "@/hooks/useFocusVisible"; import { cn } from "@/lib/cn"; import { errorDialog, formatErrorMessage } from "@/lib/errors"; type ProfileDropdownProps = { onManageProfiles?: () => void; }; const MANAGE_VALUE = "__manage_profiles__"; export const ProfileDropdown = ({ onManageProfiles }: ProfileDropdownProps) => { const { t } = useTranslation(); const { activeProfile, activeProfileId, profiles, switchProfile, loaded } = useProfile(); const [open, setOpen] = useState(false); const [busy, setBusy] = useState(false); const listRef = useRef(null); const handleTriggerKeyDown = (e: React.KeyboardEvent) => { if (open) return; if (e.key === "ArrowDown" || e.key === "ArrowUp") { e.preventDefault(); setOpen(true); } }; const sortedProfiles = [...profiles].sort((a, b) => { if (a.id === activeProfileId) return -1; if (b.id === activeProfileId) return 1; return a.name.localeCompare(b.name); }); const guarded = async (title: string, fn: () => Promise) => { if (busy) return; setBusy(true); try { await fn(); } catch (e) { await errorDialog({ Title: title, Message: formatErrorMessage(e), }); } finally { setBusy(false); } }; const handleSelect = (id: string) => { setOpen(false); if (id === activeProfileId) return; void guarded(t("profile.error.switchTitle"), () => switchProfile(id)); }; const handleManage = () => { setOpen(false); onManageProfiles?.(); }; if (!loaded) return ; const hasProfile = !!activeProfileId; const activeFromList = profiles.find((p) => p.id === activeProfileId)?.name; const displayName = hasProfile ? (activeFromList ?? activeProfile) : t("profile.selector.noProfile"); return ( { e.preventDefault(); listRef.current?.focus(); }} className={cn( "wails-no-draggable z-50 min-w-64 select-none overflow-hidden rounded-lg border border-nb-gray-800 bg-nb-gray-950 p-1 text-nb-gray-200 shadow-lg dark:border-nb-gray-900 dark:bg-nb-gray-935", "data-[state=open]:animate-in data-[state=closed]:animate-out", "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0", "data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95", "data-[side=bottom]:origin-top data-[side=top]:origin-bottom", "data-[side=left]:origin-right data-[side=right]:origin-left", "data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2", "data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2", )} > e.stopPropagation()} className={"outline-none focus:outline-none focus-visible:outline-none"} > {sortedProfiles.length > 0 && ( <> {sortedProfiles.map((profile) => ( ))}
)}
{t("profile.dropdown.manageProfiles")}
); }; const ProfileTriggerSkeleton = () => (
); type ProfileTriggerButtonProps = React.ButtonHTMLAttributes & { name: string; }; const ProfileTriggerButton = forwardRef( function ProfileTriggerButton({ name, className, disabled, ...props }, ref) { const { t } = useTranslation(); const isFocusVisible = useFocusVisible(); const Icon = pickProfileIcon(name) ?? UserCircle; return ( ); }, ); type ProfileRowProps = { profile: Profile; isActive: boolean; onSelect: (id: string) => void; }; const ProfileRow = ({ profile, isActive, onSelect }: ProfileRowProps) => { const showEmail = !!profile.email; return ( onSelect(profile.id)} className={cn( "flex w-auto gap-2 px-2 py-2 pr-3 last:mb-1", "cursor-default rounded-md text-sm outline-none", "data-[selected=true]:bg-nb-gray-900", showEmail ? "items-start" : "items-center", )} >
{profile.name} {showEmail && }
{isActive && ( )}
); }; const TruncatedEmail = ({ email }: { email: string }) => { const ref = useRef(null); const [overflowing, setOverflowing] = useState(false); useLayoutEffect(() => { const el = ref.current; if (!el) return; setOverflowing(el.scrollWidth > el.clientWidth); }, [email]); const span = ( {email} ); if (!overflowing) return span; return {span}; };