Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 31 additions & 6 deletions apps/web/src/components/SidebarV2.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1586,10 +1586,18 @@ export default function SidebarV2() {
[projectGroupingSettings.sidebarProjectGroupingOverrides, updateSettings],
);

// On macOS, Ctrl+click fires contextmenu and Safari then also fires click on
// the same element, which would activate the radio item and change the scope
// underneath the settings dialog. stopPropagation on contextmenu cannot block
// that separate click, so the next scope change is suppressed instead. The
// flag clears when the menu reopens, and a real selection always starts by
// reopening the menu, so it can never swallow one.
const suppressNextScopeChangeRef = useRef(false);
const handleProjectActions = useCallback(
(event: ReactMouseEvent<HTMLButtonElement>, projectGroup: SidebarProjectSnapshot) => {
(event: ReactMouseEvent<HTMLElement>, projectGroup: SidebarProjectSnapshot) => {
event.preventDefault();
event.stopPropagation();
suppressNextScopeChangeRef.current = true;
setProjectScopeMenuOpen(false);
window.requestAnimationFrame(() => setProjectActionsTarget(projectGroup));
},
Expand Down Expand Up @@ -2783,7 +2791,13 @@ export default function SidebarV2() {
</div>
{projectGroups.length > 0 ? (
<div className="flex items-center gap-1">
<Menu open={projectScopeMenuOpen} onOpenChange={setProjectScopeMenuOpen}>
<Menu
open={projectScopeMenuOpen}
onOpenChange={(open) => {
if (open) suppressNextScopeChangeRef.current = false;
setProjectScopeMenuOpen(open);
}}
>
<MenuTrigger
render={
<SidebarMenuButton
Expand All @@ -2809,9 +2823,13 @@ export default function SidebarV2() {
<MenuPopup align="start" className="w-(--anchor-width)">
<MenuRadioGroup
value={projectScopeKey ?? "all"}
onValueChange={(value) =>
setProjectScopeKey(value === "all" ? null : (value as string))
}
onValueChange={(value) => {
if (suppressNextScopeChangeRef.current) {
suppressNextScopeChangeRef.current = false;
return;
}
setProjectScopeKey(value === "all" ? null : (value as string));
}}
Comment thread
cursor[bot] marked this conversation as resolved.
>
<MenuRadioItem
value="all"
Expand All @@ -2829,16 +2847,23 @@ export default function SidebarV2() {
value={scopeKey}
closeOnClick
className="h-8 min-h-8 px-1 py-0 text-sm font-medium [&>span:last-child]:flex [&>span:last-child]:min-w-0 [&>span:last-child]:items-center [&>span:last-child]:gap-2"
onContextMenu={(event) => {
void handleProjectActions(event, project);
}}
Comment thread
cursor[bot] marked this conversation as resolved.
>
<ProjectFavicon
environmentId={project.environmentId}
cwd={project.workspaceRoot}
className="size-4 shrink-0"
/>
<span className="min-w-0 truncate text-sm">{project.displayName}</span>
{/* Mouse-only affordance: hidden from AT because interactive children
are invalid inside menuitemradio and pollute its accessible name.
Keyboard/AT path is Shift+F10 (contextmenu) on the item itself. */}
<button
type="button"
aria-label={`Project actions for ${project.displayName}`}
tabIndex={-1}
aria-hidden="true"
title={`Project actions for ${project.displayName}`}
className="ml-auto inline-flex size-6 shrink-0 cursor-pointer items-center justify-center rounded-md text-muted-foreground/55 outline-none transition-colors hover:bg-accent hover:text-foreground focus-visible:bg-accent focus-visible:text-foreground focus-visible:ring-2 focus-visible:ring-ring"
onPointerDown={(event) => event.stopPropagation()}
Expand Down
Loading