From 377670b34a26c6b864db93b1f9f2c7c1b48da2cc Mon Sep 17 00:00:00 2001 From: ManishMadan2882 Date: Wed, 5 Mar 2025 03:12:48 +0530 Subject: [PATCH] (feat:docs) adding view option --- frontend/src/assets/eye-view.svg | 11 ++++ frontend/src/components/DropdownMenu.tsx | 83 +++++++++++++++++++----- frontend/src/settings/Documents.tsx | 57 +++++++++------- 3 files changed, 112 insertions(+), 39 deletions(-) create mode 100644 frontend/src/assets/eye-view.svg diff --git a/frontend/src/assets/eye-view.svg b/frontend/src/assets/eye-view.svg new file mode 100644 index 00000000..1e569cfd --- /dev/null +++ b/frontend/src/assets/eye-view.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/frontend/src/components/DropdownMenu.tsx b/frontend/src/components/DropdownMenu.tsx index 5e278f8b..e0c046f4 100644 --- a/frontend/src/components/DropdownMenu.tsx +++ b/frontend/src/components/DropdownMenu.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import ReactDOM from 'react-dom'; type DropdownMenuProps = { name: string; @@ -10,7 +11,9 @@ type DropdownMenuProps = { onOpenChange?: (isOpen: boolean) => void; anchorRef?: React.RefObject; className?: string; - position?: 'left' | 'right'; + position?: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left'; + offset?: { x: number; y: number }; + contextMenuAdjacent?: boolean; // New prop to indicate if it should position next to context menu }; export default function DropdownMenu({ @@ -22,8 +25,10 @@ export default function DropdownMenu({ isOpen: controlledIsOpen, onOpenChange, anchorRef, - className, - position = 'left', + className = '', + position = 'bottom-right', + offset = { x: 0, y: 8 }, + contextMenuAdjacent = false, // Default to false for backward compatibility }: DropdownMenuProps) { const dropdownRef = React.useRef(null); const [internalIsOpen, setInternalIsOpen] = React.useState(false); @@ -38,7 +43,8 @@ export default function DropdownMenu({ const handleClickOutside = (event: MouseEvent) => { if ( dropdownRef.current && - !dropdownRef.current.contains(event.target as Node) + !dropdownRef.current.contains(event.target as Node) && + !anchorRef?.current?.contains(event.target as Node) ) { setIsOpen(false); } @@ -51,20 +57,62 @@ export default function DropdownMenu({ }; React.useEffect(() => { - document.addEventListener('mousedown', handleClickOutside); - return () => { - document.removeEventListener('mousedown', handleClickOutside); - }; - }, []); + if (isOpen) { + document.addEventListener('mousedown', handleClickOutside); + return () => + document.removeEventListener('mousedown', handleClickOutside); + } + }, [isOpen]); - return ( -
+ if (!isOpen) return null; + + const getMenuPosition = (): React.CSSProperties => { + if (!anchorRef?.current) return {}; + + const rect = anchorRef.current.getBoundingClientRect(); + + // Default positioning + let top = rect.bottom + offset.y; + let left = rect.right + offset.x; + + if (contextMenuAdjacent) { + // Position to the left of the context menu + left = rect.left - 50; // Width of dropdown + some spacing + top = rect.top; // Align tops + } else { + // Standard positioning based on position prop + switch (position) { + case 'bottom-left': + left = rect.left - offset.x; + break; + case 'top-right': + top = rect.top - offset.y; + break; + case 'top-left': + top = rect.top - offset.y; + left = rect.left - offset.x; + break; + // bottom-right is default + } + } + + return { + position: 'fixed', + top: `${top}px`, + left: `${left}px`, + zIndex: 9999, + }; + }; + + // Use a portal to render the dropdown outside the table flow + return ReactDOM.createPortal( +
e.stopPropagation()} + >
-
+
, + document.body, ); } diff --git a/frontend/src/settings/Documents.tsx b/frontend/src/settings/Documents.tsx index b1b04006..61cf063d 100644 --- a/frontend/src/settings/Documents.tsx +++ b/frontend/src/settings/Documents.tsx @@ -6,6 +6,7 @@ import userService from '../api/services/userService'; import ArrowLeft from '../assets/arrow-left.svg'; import caretSort from '../assets/caret-sort.svg'; import Edit from '../assets/edit.svg'; +import EyeView from '../assets/eye-view.svg'; import NoFilesDarkIcon from '../assets/no-files-dark.svg'; import NoFilesIcon from '../assets/no-files.svg'; import SyncIcon from '../assets/sync.svg'; @@ -81,13 +82,19 @@ export default function Documents({ e.preventDefault(); e.stopPropagation(); - // Close any open menu if clicking on a different button - if (activeMenuId && activeMenuId !== docId) { + const isAnyMenuOpen = + (syncMenuState.isOpen && syncMenuState.docId === docId) || + activeMenuId === docId; + + if (isAnyMenuOpen) { + // Close both menus + setSyncMenuState((prev) => ({ ...prev, isOpen: false, docId: null })); setActiveMenuId(null); + return; } - // Toggle the clicked menu - setActiveMenuId((prev) => (prev === docId ? null : docId)); + // If no menu is open, open the context menu + setActiveMenuId(docId); }; // Close menu when clicking outside @@ -223,6 +230,16 @@ export default function Documents({ const getActionOptions = (index: number, document: Doc): MenuOption[] => { const actions: MenuOption[] = [ + { + icon: EyeView, + label: t('settings.documents.view'), + onClick: () => { + setShowDocumentChunks(document); + }, + iconWidth: 18, + iconHeight: 18, + variant: 'primary', + }, { icon: Trash, label: t('convTile.delete'), @@ -312,8 +329,8 @@ export default function Documents({ {t('settings.documents.name')} - -
+ +
{t('settings.documents.date')}
- -
+ +
{t('settings.documents.tokenUsage')} @@ -339,10 +356,8 @@ export default function Documents({ />
- - - {t('settings.documents.actions')} - + + {t('settings.documents.actions')} @@ -363,27 +378,23 @@ export default function Documents({ const docId = document.id ? document.id.toString() : ''; return ( - setShowDocumentChunks(document)} - > + {document.name} - + {document.date ? formatDate(document.date) : ''} - + {document.tokens ? formatTokens(+document.tokens) : ''} e.stopPropagation()} >
)}