diff --git a/gui/src/components/AssistantAndOrgListbox/AssistantOption.tsx b/gui/src/components/AssistantAndOrgListbox/AssistantOption.tsx index 35ce80c7e9b..b012032188c 100644 --- a/gui/src/components/AssistantAndOrgListbox/AssistantOption.tsx +++ b/gui/src/components/AssistantAndOrgListbox/AssistantOption.tsx @@ -7,21 +7,15 @@ import { useAppDispatch } from "../../redux/hooks"; import { setSelectedProfile } from "../../redux/slices/profilesSlice"; import { CONFIG_ROUTES } from "../../util/navigation"; import { ToolTip } from "../gui/Tooltip"; -import { Button, ListboxOption, useFontSize } from "../ui"; +import { Button, ListboxOption } from "../ui"; import { AssistantIcon } from "./AssistantIcon"; interface AssistantOptionProps { profile: ProfileDescription; selected: boolean; - onClick: () => void; } -export function AssistantOption({ - profile, - selected, - onClick, -}: AssistantOptionProps) { - const tinyFont = useFontSize(-4); +export function AssistantOption({ profile, selected }: AssistantOptionProps) { const navigate = useNavigate(); const dispatch = useAppDispatch(); const ideMessenger = useContext(IdeMessengerContext); @@ -38,8 +32,6 @@ export function AssistantOption({ ideMessenger.post("didChangeSelectedProfile", { id: profile.id, }); - - onClick(); } return ( diff --git a/gui/src/components/AssistantAndOrgListbox/AssistantOptions.tsx b/gui/src/components/AssistantAndOrgListbox/AssistantOptions.tsx index a8baff97164..61c070776dc 100644 --- a/gui/src/components/AssistantAndOrgListbox/AssistantOptions.tsx +++ b/gui/src/components/AssistantAndOrgListbox/AssistantOptions.tsx @@ -3,13 +3,9 @@ import { AssistantOption } from "./AssistantOption"; interface AssistantOptionsProps { selectedProfileId: string | undefined; - onClose: () => void; } -export function AssistantOptions({ - selectedProfileId, - onClose, -}: AssistantOptionsProps) { +export function AssistantOptions({ selectedProfileId }: AssistantOptionsProps) { const { profiles } = useAuth(); return ( @@ -23,7 +19,6 @@ export function AssistantOptions({ )) diff --git a/gui/src/components/AssistantAndOrgListbox/OrganizationOption.tsx b/gui/src/components/AssistantAndOrgListbox/OrganizationOption.tsx index 80c42583d66..d6693761e05 100644 --- a/gui/src/components/AssistantAndOrgListbox/OrganizationOption.tsx +++ b/gui/src/components/AssistantAndOrgListbox/OrganizationOption.tsx @@ -12,7 +12,6 @@ import { ListboxOption } from "../ui"; interface OrganizationOptionProps { organization: { id: string; name: string; iconUrl?: string | null }; - onClose: () => void; } function getOrgIcon( @@ -40,10 +39,7 @@ function getOrgIcon( return ; } -export function OrganizationOption({ - organization, - onClose, -}: OrganizationOptionProps) { +export function OrganizationOption({ organization }: OrganizationOptionProps) { const dispatch = useAppDispatch(); const ideMessenger = useContext(IdeMessengerContext); const selectedOrgId = useAppSelector( @@ -56,7 +52,6 @@ export function OrganizationOption({ ideMessenger.post("didChangeSelectedOrg", { id: organization.id, }); - onClose(); } return ( diff --git a/gui/src/components/AssistantAndOrgListbox/OrganizationOptions.tsx b/gui/src/components/AssistantAndOrgListbox/OrganizationOptions.tsx index d374870d44f..eb09682c70c 100644 --- a/gui/src/components/AssistantAndOrgListbox/OrganizationOptions.tsx +++ b/gui/src/components/AssistantAndOrgListbox/OrganizationOptions.tsx @@ -1,17 +1,13 @@ import { useAuth } from "../../context/Auth"; import { OrganizationOption } from "./OrganizationOption"; -interface OrganizationOptionsProps { - onClose: () => void; -} - -export function OrganizationOptions({ onClose }: OrganizationOptionsProps) { +export function OrganizationOptions() { const { organizations } = useAuth(); return (
{organizations.map((org) => ( - + ))}
); diff --git a/gui/src/components/AssistantAndOrgListbox/SelectedAssistantButton.tsx b/gui/src/components/AssistantAndOrgListbox/SelectedAssistantButton.tsx index 09334a9ec12..eb456e3dfc6 100644 --- a/gui/src/components/AssistantAndOrgListbox/SelectedAssistantButton.tsx +++ b/gui/src/components/AssistantAndOrgListbox/SelectedAssistantButton.tsx @@ -10,11 +10,13 @@ import { AssistantIcon } from "./AssistantIcon"; interface SelectedAssistantButtonProps { selectedProfile: ProfileDescription | null; variant?: "lump" | "sidebar"; + setOptionsOpen: (open: boolean) => void; } export function SelectedAssistantButton({ selectedProfile, variant, + setOptionsOpen, }: SelectedAssistantButtonProps) { const configLoading = useAppSelector((store) => store.config.loading); @@ -28,6 +30,7 @@ export function SelectedAssistantButton({ data-testid="assistant-select-button" className={`text-description overflow-hidden border-none bg-transparent hover:brightness-110 ${isSidebar ? "w-full justify-start" : "gap-1.5"} ${buttonPadding}`} style={buttonStyle} + onClick={() => setOptionsOpen(true)} >
(null); const dispatch = useAppDispatch(); const navigate = useNavigate(); - const listboxRef = useRef(null); const currentOrg = useAppSelector(selectCurrentOrg); const ideMessenger = useContext(IdeMessengerContext); const { @@ -57,12 +53,6 @@ export function AssistantAndOrgListbox({ const shouldRenderOrgInfo = session && organizations.length > 1 && !isOnPremSession(session); - function close() { - // Close the listbox by clicking outside or programmatically - const event = new KeyboardEvent("keydown", { key: "Escape" }); - document.dispatchEvent(event); - } - function onNewAssistant() { if (session) { void ideMessenger.request("controlPlane/openUrl", { @@ -72,34 +62,34 @@ export function AssistantAndOrgListbox({ } else { void ideMessenger.request("config/newAssistantFile", undefined); } - close(); + setListboxOpen(false); } function onNewOrganization() { void ideMessenger.request("controlPlane/openUrl", { path: "/organizations/new", }); - close(); + setListboxOpen(false); } function onAgentsConfig() { navigate(CONFIG_ROUTES.AGENTS); - close(); + setListboxOpen(false); } function onOrganizationsConfig() { navigate(CONFIG_ROUTES.ORGANIZATIONS); - close(); + setListboxOpen(false); } function onRulesConfig() { navigate(CONFIG_ROUTES.RULES); - close(); + setListboxOpen(false); } function onToolsConfig() { navigate(CONFIG_ROUTES.TOOLS); - close(); + setListboxOpen(false); } useEffect(() => { @@ -143,190 +133,188 @@ export function AssistantAndOrgListbox({ }; }, [currentOrg, selectedProfile]); + useClickOutside(listboxOptionsRef, () => { + setListboxOpen(false); + }); + return ( -
+
setListboxOpen(val)} /> - - -
- - Agents - -
- - -
+ +
+ Agents +
+ +
+
- + - {shouldRenderOrgInfo && ( - <> - -
- - Organizations - -
- - -
+ {shouldRenderOrgInfo && ( + <> + +
+ + Organizations + +
+ +
+
- + - - - )} + + + )} - {/* Settings Section */} - {variant !== "sidebar" && ( -
- + {/* Settings Section */} + {variant !== "sidebar" && ( +
+ + + + {session ? ( + ) : ( - {session ? ( - - ) : ( - - )} + )} - -
- )} + +
+ )} - {/* Bottom Actions */} -
-
- - {getMetaKeyLabel()} ⇧ ' to toggle agent - -
+ {/* Bottom Actions */} +
+
+ + {getMetaKeyLabel()} ⇧ ' to toggle agent +
- - +
+
); diff --git a/gui/src/hooks/useClickOutside.tsx b/gui/src/hooks/useClickOutside.tsx new file mode 100644 index 00000000000..e5edf456880 --- /dev/null +++ b/gui/src/hooks/useClickOutside.tsx @@ -0,0 +1,16 @@ +import { useEffect } from "react"; + +export const useClickOutside = ( + ref: React.RefObject, + onClickOutsideCallback: (event: MouseEvent) => void, +) => { + useEffect(() => { + const onClickOutside = (event: MouseEvent) => { + if (ref.current && !ref.current.contains(event.target as Node)) { + onClickOutsideCallback(event); + } + }; + document.addEventListener("mousedown", onClickOutside); + return () => document.removeEventListener("mousedown", onClickOutside); + }, [ref.current]); +};