Skip to content
Merged
Changes from 3 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/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1880,10 +1880,18 @@ export default function Sidebar() {
clearSelection();
}, [clearSelection, projectScopeKey]);

// 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 page. 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 handleProjectSettings = useCallback(
(event: ReactMouseEvent<HTMLButtonElement>, projectGroup: SidebarProjectSnapshot) => {
(event: ReactMouseEvent<HTMLElement>, projectGroup: SidebarProjectSnapshot) => {
event.preventDefault();
event.stopPropagation();
suppressNextScopeChangeRef.current = true;
setProjectScopeMenuOpen(false);
if (isMobile) {
setOpenMobile(false);
Expand Down Expand Up @@ -3329,7 +3337,13 @@ export default function Sidebar() {
</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 @@ -3356,9 +3370,13 @@ export default function Sidebar() {
<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));
}}
>
<MenuRadioItem
value="all"
Expand All @@ -3376,6 +3394,9 @@ export default function Sidebar() {
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 handleProjectSettings(event, project);
}}
>
<ProjectFavicon
environmentId={project.environmentId}
Expand All @@ -3384,9 +3405,13 @@ export default function Sidebar() {
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 settings for ${project.displayName}`}
tabIndex={-1}
aria-hidden="true"
title={`Project settings for ${project.displayName}`}
className="ml-auto inline-flex size-6 shrink-0 cursor-pointer items-center justify-center rounded-md text-icon-muted 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