agent-ecosystem/src/renderer/components/team/editor/EditorSelectionMenu.tsx
iliya f4f02d5536 feat: enhance task management with new file renaming feature and notification settings
- Added a new file renaming functionality in the editor, allowing users to rename files and directories in place.
- Introduced notification settings for team inbox messages and task clarifications, enabling users to receive native OS notifications for important updates.
- Updated the README to reflect the new features and provide a clearer overview of the task management capabilities.
- Improved the application icon handling for notifications across different platforms.
2026-03-01 17:52:54 +02:00

112 lines
3.7 KiB
TypeScript

/**
* Floating action menu shown near text selection in the editor.
*
* Positioned absolutely relative to the editor content container.
* Uses onMouseDown preventDefault to avoid deselecting text in CM6.
*/
import { Button } from '@renderer/components/ui/button';
import { Tooltip, TooltipContent, TooltipTrigger } from '@renderer/components/ui/tooltip';
import { ListTodo, MessageSquare } from 'lucide-react';
import type { EditorSelectionInfo } from '@shared/types/editor';
// =============================================================================
// Types
// =============================================================================
interface EditorSelectionMenuProps {
info: EditorSelectionInfo;
/** Bounding rect of the editor content container (for viewport → container conversion) */
containerRect: DOMRect;
onSendMessage: () => void;
onCreateTask: () => void;
}
// =============================================================================
// Constants
// =============================================================================
const MENU_GAP = 8; // px gap between selection end and menu
const MENU_WIDTH = 68; // approximate menu width for clamping
const MENU_HEIGHT = 32; // approximate menu height for clamping
// =============================================================================
// Component
// =============================================================================
export const EditorSelectionMenu = ({
info,
containerRect,
onSendMessage,
onCreateTask,
}: EditorSelectionMenuProps): React.ReactElement | null => {
if (!info.text.trim()) return null;
// Convert viewport coords → container-relative
const rawTop = info.screenRect.top - containerRect.top;
const rawLeft = info.screenRect.right - containerRect.left + MENU_GAP;
// Check if selection is within visible container bounds
const selTopInContainer = info.screenRect.top - containerRect.top;
const selBottomInContainer = info.screenRect.bottom - containerRect.top;
if (selBottomInContainer < 0 || selTopInContainer > containerRect.height) {
return null; // selection is scrolled out of view
}
// Clamp to container bounds
const top = Math.max(0, Math.min(rawTop, containerRect.height - MENU_HEIGHT));
const left =
rawLeft + MENU_WIDTH > containerRect.width
? info.screenRect.right - containerRect.left - MENU_WIDTH - MENU_GAP // flip to left side
: rawLeft;
return (
<div
className="pointer-events-auto absolute z-20 flex items-center gap-0.5 rounded-md border border-border-emphasis bg-surface-overlay p-0.5 shadow-lg animate-in fade-in-0 zoom-in-95"
style={{ top, left: Math.max(0, left) }}
>
<MenuButton
icon={<MessageSquare className="size-3.5" />}
label="Write Teammate"
onClick={onSendMessage}
/>
<MenuButton
icon={<ListTodo className="size-3.5" />}
label="Create Task"
onClick={onCreateTask}
/>
</div>
);
};
// =============================================================================
// Menu button
// =============================================================================
interface MenuButtonProps {
icon: React.ReactNode;
label: string;
onClick: () => void;
}
const MenuButton = ({ icon, label, onClick }: MenuButtonProps): React.ReactElement => (
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="ghost"
size="icon"
tabIndex={-1}
aria-label={label}
onClick={onClick}
onMouseDown={(e) => e.preventDefault()} // prevent CM6 selection loss
className="size-7 p-1.5 text-text-secondary"
>
{icon}
</Button>
</TooltipTrigger>
<TooltipContent side="top" sideOffset={6}>
{label}
</TooltipContent>
</Tooltip>
);