refactor(web): share one PointerMenu for the space and section menus
The space and section context menus repeated the same dropdown with an invisible trigger anchored at the pointer; only their label and items differ. Move-only: PointerMenu in pages/shell renders that dropdown and both menus pass their label, close handler and items. DOM, attributes and handlers are unchanged, and the labels stay in ShellPage, so the catalogs do not change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
This commit is contained in:
parent
0ab148ba03
commit
ebb3cecd97
@ -72,10 +72,7 @@ import {
|
||||
AvatarStyleProvider,
|
||||
BotAvatar,
|
||||
Button,
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
GroupAvatar,
|
||||
type GroupAvatarMember,
|
||||
InputGroup,
|
||||
@ -214,6 +211,7 @@ import {
|
||||
} from "./shell/dialogs";
|
||||
import { FullscreenComputerOverlay } from "./shell/fullscreen-computer-overlay";
|
||||
import { MainConversationRow } from "./shell/main-conversation-row";
|
||||
import { PointerMenu } from "./shell/pointer-menu";
|
||||
import { cleanRosterPreview, formatRosterTime, stripSpaceTitlePrefix } from "./shell/roster-format";
|
||||
import { rosterRowClass } from "./shell/roster-row-style";
|
||||
import { ProjectRosterRow, TeamRosterRow } from "./shell/roster-rows";
|
||||
@ -4552,43 +4550,23 @@ export function ShellPage() {
|
||||
) : null}
|
||||
|
||||
{spaceMenu ? (
|
||||
<DropdownMenu
|
||||
open
|
||||
onOpenChange={(open) => {
|
||||
if (!open) closeSpaceMenu();
|
||||
}}
|
||||
<PointerMenu
|
||||
position={spaceMenu.position}
|
||||
label={t`Actions for space`}
|
||||
onClose={closeSpaceMenu}
|
||||
>
|
||||
{/* Invisible anchor at the pointer position, mirroring the bot menu. */}
|
||||
<DropdownMenuTrigger
|
||||
render={
|
||||
<button
|
||||
type="button"
|
||||
tabIndex={-1}
|
||||
aria-hidden
|
||||
className="fixed size-0 p-0 opacity-0"
|
||||
style={{ left: spaceMenu.position.x, top: spaceMenu.position.y }}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<DropdownMenuContent
|
||||
aria-label={t`Actions for space`}
|
||||
align="start"
|
||||
sideOffset={0}
|
||||
className="w-[220px]"
|
||||
<DropdownMenuItem
|
||||
variant="destructive"
|
||||
onClick={() => {
|
||||
const target = spaces.find((space) => space.id === spaceMenu.id);
|
||||
if (target) setDeleteSpaceTarget(target);
|
||||
setSpaceMenu(null);
|
||||
}}
|
||||
>
|
||||
<DropdownMenuItem
|
||||
variant="destructive"
|
||||
onClick={() => {
|
||||
const target = spaces.find((space) => space.id === spaceMenu.id);
|
||||
if (target) setDeleteSpaceTarget(target);
|
||||
setSpaceMenu(null);
|
||||
}}
|
||||
>
|
||||
<Trash2 />
|
||||
{t`Delete space`}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
<Trash2 />
|
||||
{t`Delete space`}
|
||||
</DropdownMenuItem>
|
||||
</PointerMenu>
|
||||
) : null}
|
||||
|
||||
{deleteTarget ? (
|
||||
@ -4679,43 +4657,24 @@ export function ShellPage() {
|
||||
) : null}
|
||||
|
||||
{sectionMenu ? (
|
||||
<DropdownMenu
|
||||
open
|
||||
onOpenChange={(open) => {
|
||||
if (!open) closeSectionMenu();
|
||||
}}
|
||||
<PointerMenu
|
||||
position={sectionMenu.position}
|
||||
label={t`Actions for ${sectionMenu.section.name}`}
|
||||
onClose={closeSectionMenu}
|
||||
>
|
||||
<DropdownMenuTrigger
|
||||
render={
|
||||
<button
|
||||
type="button"
|
||||
tabIndex={-1}
|
||||
aria-hidden
|
||||
className="fixed size-0 p-0 opacity-0"
|
||||
style={{ left: sectionMenu.position.x, top: sectionMenu.position.y }}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<DropdownMenuContent
|
||||
aria-label={t`Actions for ${sectionMenu.section.name}`}
|
||||
align="start"
|
||||
sideOffset={0}
|
||||
className="w-[220px]"
|
||||
<DropdownMenuItem
|
||||
onClick={() => {
|
||||
setRenameSectionTarget({
|
||||
section: sectionMenu.section,
|
||||
spaceId: sectionMenu.spaceId,
|
||||
});
|
||||
setSectionMenu(null);
|
||||
}}
|
||||
>
|
||||
<DropdownMenuItem
|
||||
onClick={() => {
|
||||
setRenameSectionTarget({
|
||||
section: sectionMenu.section,
|
||||
spaceId: sectionMenu.spaceId,
|
||||
});
|
||||
setSectionMenu(null);
|
||||
}}
|
||||
>
|
||||
<Pencil />
|
||||
{t`Rename section`}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
<Pencil />
|
||||
{t`Rename section`}
|
||||
</DropdownMenuItem>
|
||||
</PointerMenu>
|
||||
) : null}
|
||||
|
||||
<CommandPalette
|
||||
|
||||
41
apps/web/src/pages/shell/pointer-menu.tsx
Normal file
41
apps/web/src/pages/shell/pointer-menu.tsx
Normal file
@ -0,0 +1,41 @@
|
||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuTrigger } from "@rakazo/ui-web";
|
||||
import type { ReactNode } from "react";
|
||||
import type { ContextMenuPosition } from "../BotContextMenu";
|
||||
|
||||
/** A dropdown menu opened at the pointer, for sidebar context menus. */
|
||||
export function PointerMenu({
|
||||
position,
|
||||
label,
|
||||
onClose,
|
||||
children,
|
||||
}: {
|
||||
position: ContextMenuPosition;
|
||||
label: string;
|
||||
onClose: () => void;
|
||||
children: ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<DropdownMenu
|
||||
open
|
||||
onOpenChange={(open) => {
|
||||
if (!open) onClose();
|
||||
}}
|
||||
>
|
||||
{/* Invisible anchor at the pointer position, mirroring the bot menu. */}
|
||||
<DropdownMenuTrigger
|
||||
render={
|
||||
<button
|
||||
type="button"
|
||||
tabIndex={-1}
|
||||
aria-hidden
|
||||
className="fixed size-0 p-0 opacity-0"
|
||||
style={{ left: position.x, top: position.y }}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<DropdownMenuContent aria-label={label} align="start" sideOffset={0} className="w-[220px]">
|
||||
{children}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user