diff --git a/frontend/src/FileManager/FileList/FileList.jsx b/frontend/src/FileManager/FileList/FileList.jsx
index e4d71e4..15d4509 100644
--- a/frontend/src/FileManager/FileList/FileList.jsx
+++ b/frontend/src/FileManager/FileList/FileList.jsx
@@ -1,212 +1,49 @@
-import { useEffect, useRef, useState } from "react";
+import { useRef } from "react";
import FileItem from "./FileItem";
-import { duplicateNameHandler } from "../../utils/duplicateNameHandler";
import { useFileNavigation } from "../../contexts/FileNavigationContext";
-import { useSelection } from "../../contexts/SelectionContext";
import { useLayout } from "../../contexts/LayoutContext";
import ContextMenu from "../../components/ContextMenu/ContextMenu";
import { useDetectOutsideClick } from "../../hooks/useDetectOutsideClick";
-import { BsCopy, BsFolderPlus, BsScissors } from "react-icons/bs";
-import { MdOutlineDelete, MdOutlineFileDownload, MdOutlineFileUpload } from "react-icons/md";
-import { FiRefreshCw } from "react-icons/fi";
+import useFileList from "./useFileList";
+import FilesHeader from "./FilesHeader";
import "./FileList.scss";
-import { PiFolderOpen } from "react-icons/pi";
-import { FaRegFile, FaRegPaste } from "react-icons/fa6";
-import { BiRename } from "react-icons/bi";
-import { useClipBoard } from "../../contexts/ClipboardContext";
-const FileList = ({ onCreateFolder, onRename, onFileOpen, enableFilePreview, triggerAction }) => {
- const [selectedFileIndexes, setSelectedFileIndexes] = useState([]);
- const [visible, setVisible] = useState(false);
- const [isSelectionCtx, setIsSelectionCtx] = useState(false);
- const [clickPosition, setClickPosition] = useState({ clickX: 0, clickY: 0 });
- const [lastSelectedFile, setLastSelectedFile] = useState(null);
-
- const { currentPath, setCurrentPath, currentPathFiles, setCurrentPathFiles } =
- useFileNavigation();
+const FileList = ({
+ onCreateFolder,
+ onRename,
+ onFileOpen,
+ onRefresh,
+ enableFilePreview,
+ triggerAction,
+}) => {
+ const { currentPathFiles } = useFileNavigation();
const filesViewRef = useRef(null);
- const { selectedFiles, setSelectedFiles, handleDownload } = useSelection();
- const { clipBoard, handleCutCopy, handlePasting } = useClipBoard();
const { activeLayout } = useLayout();
- const contextMenuRef = useDetectOutsideClick(() => setVisible(false));
-
- const emptySelecCtxItems = [
- {
- title: "Refresh",
- icon: ,
- onClick: () => {},
- },
- {
- title: "New Folder",
- icon: ,
- onClick: () => {},
- },
- {
- title: "Upload",
- icon: ,
- onClick: () => {},
- },
- ];
-
- const selecCtxItems = [
- {
- title: "Open",
- icon: lastSelectedFile?.isDirectory ? : ,
- onClick: handleFileOpen,
- },
- {
- title: "Cut",
- icon: ,
- onClick: () => handleMoveOrCopyItems(true),
- },
- {
- title: "Copy",
- icon: ,
- onClick: () => handleMoveOrCopyItems(false),
- },
- {
- title: "Paste",
- icon: ,
- onClick: handleFilePasting,
- className: `${clipBoard ? "" : "disable-paste"}`,
- hidden: !lastSelectedFile?.isDirectory,
- },
- {
- title: "Rename",
- icon: ,
- onClick: handleRenaming,
- hidden: selectedFiles.length > 1,
- },
- {
- title: "Download",
- icon: ,
- onClick: handleDownloadItems,
- hidden: lastSelectedFile?.isDirectory,
- },
- {
- title: "Delete",
- icon: ,
- onClick: handleDelete,
- },
- ];
-
- function handleFileOpen() {
- if (lastSelectedFile.isDirectory) {
- setCurrentPath(lastSelectedFile.path);
- setSelectedFileIndexes([]);
- setSelectedFiles([]);
- } else {
- enableFilePreview && triggerAction.show("previewFile");
- }
- setVisible(false);
- }
-
- function handleMoveOrCopyItems(isMoving) {
- handleCutCopy(isMoving);
- setVisible(false);
- }
-
- function handleFilePasting() {
- handlePasting(lastSelectedFile);
- setVisible(false);
- }
-
- function handleRenaming() {
- setVisible(false);
- triggerAction.show("rename");
- }
-
- function handleDownloadItems() {
- handleDownload();
- setVisible(false);
- }
-
- function handleDelete() {
- setVisible(false);
- triggerAction.show("delete");
- }
- const handleFolderCreating = () => {
- setCurrentPathFiles((prev) => {
- return [
- ...prev,
- {
- name: duplicateNameHandler("New Folder", true, prev),
- isDirectory: true,
- path: currentPath,
- isEditing: true,
- key: new Date().valueOf(),
- },
- ];
- });
- };
+ const {
+ emptySelecCtxItems,
+ selecCtxItems,
+ handleContextMenu,
+ unselectFiles,
+ visible,
+ setVisible,
+ setLastSelectedFile,
+ selectedFileIndexes,
+ clickPosition,
+ isSelectionCtx,
+ } = useFileList(onRefresh, enableFilePreview, triggerAction);
- const handleItemRenaming = () => {
- setCurrentPathFiles((prev) => {
- if (prev[selectedFileIndexes.at(-1)]) {
- prev[selectedFileIndexes.at(-1)].isEditing = true;
- }
- return prev;
- });
-
- setSelectedFileIndexes([]);
- setSelectedFiles([]);
- };
-
- const handleContextMenu = (e, isSelection) => {
- e.preventDefault();
- setClickPosition({ clickX: e.clientX, clickY: e.clientY });
- setIsSelectionCtx(isSelection);
- setVisible(true);
- };
-
- useEffect(() => {
- if (triggerAction.isActive) {
- switch (triggerAction.actionType) {
- case "createFolder":
- handleFolderCreating();
- break;
- case "rename":
- handleItemRenaming();
- break;
- }
- }
- }, [triggerAction.isActive]);
-
- useEffect(() => {
- setSelectedFileIndexes([]);
- setSelectedFiles([]);
- }, [currentPath]);
-
- useEffect(() => {
- if (selectedFiles.length > 0) {
- setSelectedFileIndexes(() => {
- return selectedFiles.map((selectedFile) => {
- return currentPathFiles.findIndex((f) => f.path === selectedFile.path);
- });
- });
- } else {
- setSelectedFileIndexes([]);
- }
- }, [selectedFiles, currentPathFiles]);
+ const contextMenuRef = useDetectOutsideClick(() => setVisible(false));
return (
handleContextMenu(e, false)}
- onClick={() => {
- setSelectedFileIndexes([]);
- setSelectedFiles((prev) => (prev.length > 0 ? [] : prev));
- }}
+ onContextMenu={handleContextMenu}
+ onClick={unselectFiles}
>
- {activeLayout === "list" && (
-
-
Name
-
Modified
-
Size
-
- )}
+ {activeLayout === "list" &&
}
+
{currentPathFiles?.length > 0 ? (
<>
{currentPathFiles.map((file, index) => (
@@ -218,9 +55,9 @@ const FileList = ({ onCreateFolder, onRename, onFileOpen, enableFilePreview, tri
onRename={onRename}
onFileOpen={onFileOpen}
enableFilePreview={enableFilePreview}
+ triggerAction={triggerAction}
filesViewRef={filesViewRef}
selectedFileIndexes={selectedFileIndexes}
- triggerAction={triggerAction}
handleContextMenu={handleContextMenu}
setVisible={setVisible}
setLastSelectedFile={setLastSelectedFile}
diff --git a/frontend/src/FileManager/FileList/FileList.scss b/frontend/src/FileManager/FileList/FileList.scss
index 1d391d1..d20b226 100644
--- a/frontend/src/FileManager/FileList/FileList.scss
+++ b/frontend/src/FileManager/FileList/FileList.scss
@@ -90,7 +90,7 @@
.rename-file-container.list {
top: 6px;
- left: 48px;
+ left: 58px;
text-align: left;
.rename-file {
@@ -101,7 +101,7 @@
top: 5px;
white-space: nowrap;
overflow-x: hidden;
- max-width: calc(100% - 48px);
+ max-width: calc(100% - 62px);
}
.folder-name-error.right {
@@ -127,37 +127,6 @@
.file-moving {
opacity: 0.5;
}
-
- .file-context-menu-list {
- font-size: 1.1em;
-
- ul {
- list-style-type: none;
- padding-left: 0;
- margin: 0;
-
- li {
- display: flex;
- gap: 6px;
- align-items: center;
- padding: 6px 22px 6px 14px;
-
- &:hover {
- cursor: pointer;
- background-color: rgb(0, 0, 0, 0.04);
- }
- }
-
- li.disable-paste {
- opacity: 0.5;
-
- &:hover {
- cursor: default;
- background-color: transparent;
- }
- }
- }
- }
}
.files.list {
@@ -173,15 +142,20 @@
display: flex;
gap: 5px;
border-bottom: 1px solid #dddddd;
- padding: 4px 0;
+ padding: 4px 0 4px 5px;
position: sticky;
top: 0;
background-color: #f5f5f5;
z-index: 1;
+ .file-select-all {
+ width: 5%;
+ height: 0.83em;
+ }
+
.file-name {
- width: calc(70% - 65px);
- padding-left: 65px;
+ width: calc(65% - 35px);
+ padding-left: 35px;
}
.file-date {
@@ -199,6 +173,7 @@
display: flex;
width: 100%;
margin: 0;
+ border-radius: 0;
&:hover {
background-color: rgb(0, 0, 0, 0.04);
diff --git a/frontend/src/FileManager/FileList/FilesHeader.jsx b/frontend/src/FileManager/FileList/FilesHeader.jsx
new file mode 100644
index 0000000..95a2207
--- /dev/null
+++ b/frontend/src/FileManager/FileList/FilesHeader.jsx
@@ -0,0 +1,43 @@
+import { useMemo, useState } from "react";
+import Checkbox from "../../components/Checkbox/Checkbox";
+import { useSelection } from "../../contexts/SelectionContext";
+import { useFileNavigation } from "../../contexts/FileNavigationContext";
+
+const FilesHeader = ({ unselectFiles }) => {
+ const [showSelectAll, setShowSelectAll] = useState(false);
+
+ const { selectedFiles, setSelectedFiles } = useSelection();
+ const { currentPathFiles } = useFileNavigation();
+
+ const allFilesSelected = useMemo(() => {
+ return selectedFiles.length === currentPathFiles.length;
+ }, [selectedFiles, currentPathFiles]);
+
+ const handleSelectAll = (e) => {
+ if (e.target.checked) {
+ setSelectedFiles(currentPathFiles);
+ setShowSelectAll(true);
+ } else {
+ unselectFiles();
+ }
+ };
+
+ return (
+
setShowSelectAll(true)}
+ onMouseLeave={() => setShowSelectAll(false)}
+ >
+
+ {(showSelectAll || allFilesSelected) && (
+
+ )}
+
+
Name
+
Modified
+
Size
+
+ );
+};
+
+export default FilesHeader;
diff --git a/frontend/src/FileManager/FileList/useFileList.jsx b/frontend/src/FileManager/FileList/useFileList.jsx
new file mode 100644
index 0000000..1e86a94
--- /dev/null
+++ b/frontend/src/FileManager/FileList/useFileList.jsx
@@ -0,0 +1,266 @@
+import { BiRename, BiSelectMultiple } from "react-icons/bi";
+import { BsCopy, BsFolderPlus, BsGrid, BsScissors } from "react-icons/bs";
+import { FaListUl, FaRegFile, FaRegPaste } from "react-icons/fa6";
+import { FiRefreshCw } from "react-icons/fi";
+import { MdOutlineDelete, MdOutlineFileDownload, MdOutlineFileUpload } from "react-icons/md";
+import { PiFolderOpen } from "react-icons/pi";
+import { useClipBoard } from "../../contexts/ClipboardContext";
+import { useEffect, useState } from "react";
+import { useSelection } from "../../contexts/SelectionContext";
+import { useLayout } from "../../contexts/LayoutContext";
+import { useFileNavigation } from "../../contexts/FileNavigationContext";
+import { duplicateNameHandler } from "../../utils/duplicateNameHandler";
+import { validateApiCallback } from "../../utils/validateApiCallback";
+
+const useFileList = (onRefresh, enableFilePreview, triggerAction) => {
+ const [selectedFileIndexes, setSelectedFileIndexes] = useState([]);
+ const [visible, setVisible] = useState(false);
+ const [isSelectionCtx, setIsSelectionCtx] = useState(false);
+ const [clickPosition, setClickPosition] = useState({ clickX: 0, clickY: 0 });
+ const [lastSelectedFile, setLastSelectedFile] = useState(null);
+
+ const { clipBoard, setClipBoard, handleCutCopy, handlePasting } = useClipBoard();
+ const { selectedFiles, setSelectedFiles, handleDownload } = useSelection();
+ const { currentPath, setCurrentPath, currentPathFiles, setCurrentPathFiles } =
+ useFileNavigation();
+ const { activeLayout, setActiveLayout } = useLayout();
+
+ // Context Menu
+ const handleFileOpen = () => {
+ if (lastSelectedFile.isDirectory) {
+ setCurrentPath(lastSelectedFile.path);
+ setSelectedFileIndexes([]);
+ setSelectedFiles([]);
+ } else {
+ enableFilePreview && triggerAction.show("previewFile");
+ }
+ setVisible(false);
+ };
+
+ const handleMoveOrCopyItems = (isMoving) => {
+ handleCutCopy(isMoving);
+ setVisible(false);
+ };
+
+ const handleFilePasting = () => {
+ handlePasting(lastSelectedFile);
+ setVisible(false);
+ };
+
+ const handleRenaming = () => {
+ setVisible(false);
+ triggerAction.show("rename");
+ };
+
+ const handleDownloadItems = () => {
+ handleDownload();
+ setVisible(false);
+ };
+
+ const handleDelete = () => {
+ setVisible(false);
+ triggerAction.show("delete");
+ };
+
+ const handleRefresh = () => {
+ setVisible(false);
+ validateApiCallback(onRefresh, "onRefresh");
+ setClipBoard(null);
+ };
+
+ const handleCreateNewFolder = () => {
+ triggerAction.show("createFolder");
+ setVisible(false);
+ };
+
+ const handleUpload = () => {
+ setVisible(false);
+ triggerAction.show("uploadFile");
+ };
+
+ const handleselectAllFiles = () => {
+ setSelectedFiles(currentPathFiles);
+ setVisible(false);
+ };
+
+ const emptySelecCtxItems = [
+ {
+ title: "View",
+ icon: activeLayout === "grid" ?
:
,
+ onClick: () => {},
+ children: [
+ {
+ title: "Grid",
+ icon:
,
+ selected: activeLayout === "grid",
+ onClick: () => {
+ setActiveLayout("grid");
+ setVisible(false);
+ },
+ },
+ {
+ title: "List",
+ icon:
,
+ selected: activeLayout === "list",
+ onClick: () => {
+ setActiveLayout("list");
+ setVisible(false);
+ },
+ },
+ ],
+ },
+ {
+ title: "Refresh",
+ icon:
,
+ onClick: handleRefresh,
+ divider: true,
+ },
+ {
+ title: "New folder",
+ icon:
,
+ onClick: handleCreateNewFolder,
+ },
+ {
+ title: "Upload",
+ icon:
,
+ onClick: handleUpload,
+ divider: true,
+ },
+ {
+ title: "Select all",
+ icon:
,
+ onClick: handleselectAllFiles,
+ },
+ ];
+
+ const selecCtxItems = [
+ {
+ title: "Open",
+ icon: lastSelectedFile?.isDirectory ?
:
,
+ onClick: handleFileOpen,
+ divider: true,
+ },
+ {
+ title: "Cut",
+ icon:
,
+ onClick: () => handleMoveOrCopyItems(true),
+ },
+ {
+ title: "Copy",
+ icon:
,
+ onClick: () => handleMoveOrCopyItems(false),
+ divider: !lastSelectedFile?.isDirectory,
+ },
+ {
+ title: "Paste",
+ icon:
,
+ onClick: handleFilePasting,
+ className: `${clipBoard ? "" : "disable-paste"}`,
+ hidden: !lastSelectedFile?.isDirectory,
+ divider: true,
+ },
+ {
+ title: "Rename",
+ icon:
,
+ onClick: handleRenaming,
+ hidden: selectedFiles.length > 1,
+ },
+ {
+ title: "Download",
+ icon:
,
+ onClick: handleDownloadItems,
+ hidden: lastSelectedFile?.isDirectory,
+ },
+ {
+ title: "Delete",
+ icon:
,
+ onClick: handleDelete,
+ },
+ ];
+ //
+
+ const handleFolderCreating = () => {
+ setCurrentPathFiles((prev) => {
+ return [
+ ...prev,
+ {
+ name: duplicateNameHandler("New Folder", true, prev),
+ isDirectory: true,
+ path: currentPath,
+ isEditing: true,
+ key: new Date().valueOf(),
+ },
+ ];
+ });
+ };
+
+ const handleItemRenaming = () => {
+ setCurrentPathFiles((prev) => {
+ if (prev[selectedFileIndexes.at(-1)]) {
+ prev[selectedFileIndexes.at(-1)].isEditing = true;
+ }
+ return prev;
+ });
+
+ setSelectedFileIndexes([]);
+ setSelectedFiles([]);
+ };
+
+ const unselectFiles = () => {
+ setSelectedFileIndexes([]);
+ setSelectedFiles((prev) => (prev.length > 0 ? [] : prev));
+ };
+
+ const handleContextMenu = (e, isSelection = false) => {
+ e.preventDefault();
+ setClickPosition({ clickX: e.clientX, clickY: e.clientY });
+ setIsSelectionCtx(isSelection);
+ !isSelection && unselectFiles();
+ setVisible(true);
+ };
+
+ useEffect(() => {
+ if (triggerAction.isActive) {
+ switch (triggerAction.actionType) {
+ case "createFolder":
+ handleFolderCreating();
+ break;
+ case "rename":
+ handleItemRenaming();
+ break;
+ }
+ }
+ }, [triggerAction.isActive]);
+
+ useEffect(() => {
+ setSelectedFileIndexes([]);
+ setSelectedFiles([]);
+ }, [currentPath]);
+
+ useEffect(() => {
+ if (selectedFiles.length > 0) {
+ setSelectedFileIndexes(() => {
+ return selectedFiles.map((selectedFile) => {
+ return currentPathFiles.findIndex((f) => f.path === selectedFile.path);
+ });
+ });
+ } else {
+ setSelectedFileIndexes([]);
+ }
+ }, [selectedFiles, currentPathFiles]);
+
+ return {
+ emptySelecCtxItems,
+ selecCtxItems,
+ handleContextMenu,
+ unselectFiles,
+ visible,
+ setVisible,
+ setLastSelectedFile,
+ selectedFileIndexes,
+ clickPosition,
+ isSelectionCtx,
+ };
+};
+
+export default useFileList;
diff --git a/frontend/src/FileManager/FileManager.jsx b/frontend/src/FileManager/FileManager.jsx
index 41eac05..c778406 100644
--- a/frontend/src/FileManager/FileManager.jsx
+++ b/frontend/src/FileManager/FileManager.jsx
@@ -81,6 +81,7 @@ const FileManager = ({
onCreateFolder={onCreateFolder}
onRename={onRename}
onFileOpen={onFileOpen}
+ onRefresh={onRefresh}
enableFilePreview={enableFilePreview}
triggerAction={triggerAction}
/>
diff --git a/frontend/src/FileManager/Toolbar/Toolbar.jsx b/frontend/src/FileManager/Toolbar/Toolbar.jsx
index 07d3efe..b3e5971 100644
--- a/frontend/src/FileManager/Toolbar/Toolbar.jsx
+++ b/frontend/src/FileManager/Toolbar/Toolbar.jsx
@@ -34,7 +34,7 @@ const Toolbar = ({
const toolbarLeftItems = [
{
icon:
,
- text: "New Folder",
+ text: "New folder",
permission: allowCreateFolder,
onClick: () => triggerAction.show("createFolder"),
},
diff --git a/frontend/src/components/Checkbox/Checkbox.jsx b/frontend/src/components/Checkbox/Checkbox.jsx
index 928f081..a31e7db 100644
--- a/frontend/src/components/Checkbox/Checkbox.jsx
+++ b/frontend/src/components/Checkbox/Checkbox.jsx
@@ -1,6 +1,6 @@
import "./Checkbox.scss";
-const Checkbox = ({ name, id, checked, onClick, onChange, className = "" }) => {
+const Checkbox = ({ name, id, checked, onClick, onChange, className = "", title }) => {
return (
{
checked={checked}
onClick={onClick}
onChange={onChange}
+ title={title}
/>
);
};
diff --git a/frontend/src/components/ContextMenu/ContextMenu.jsx b/frontend/src/components/ContextMenu/ContextMenu.jsx
index 69b0590..916031d 100644
--- a/frontend/src/components/ContextMenu/ContextMenu.jsx
+++ b/frontend/src/components/ContextMenu/ContextMenu.jsx
@@ -1,16 +1,12 @@
import { useEffect, useState } from "react";
+import { FaChevronRight } from "react-icons/fa6";
+import SubMenu from "./SubMenu";
import "./ContextMenu.scss";
-const ContextMenu = ({
- filesViewRef,
- contextMenuRef,
- menuItems,
- visible,
- setVisible,
- clickPosition,
-}) => {
+const ContextMenu = ({ filesViewRef, contextMenuRef, menuItems, visible, clickPosition }) => {
const [left, setLeft] = useState(0);
const [top, setTop] = useState(0);
+ const [activeSubMenuIndex, setActiveSubMenuIndex] = useState(null);
const contextMenuPosition = () => {
const { clickX, clickY } = clickPosition;
@@ -52,12 +48,17 @@ const ContextMenu = ({
e.stopPropagation();
};
+ const handleMouseOver = (index) => {
+ setActiveSubMenuIndex(index);
+ };
+
useEffect(() => {
if (visible && contextMenuRef.current) {
contextMenuPosition();
} else {
setTop(0);
setLeft(0);
+ setActiveSubMenuIndex(null);
}
}, [visible]);
@@ -77,12 +78,29 @@ const ContextMenu = ({
{menuItems
.filter((item) => !item.hidden)
- .map((item) => (
- -
- {item.icon}
- {item.title}
-
- ))}
+ .map((item, index) => {
+ const hasChildren = item.hasOwnProperty("children");
+ const activeSubMenu = activeSubMenuIndex === index;
+ return (
+
+
- handleMouseOver(index)}
+ >
+ {item.icon}
+ {item.title}
+ {hasChildren && (
+ <>
+
+ {activeSubMenu && }
+ >
+ )}
+
+ {item.divider &&
}
+
+ );
+ })}
diff --git a/frontend/src/components/ContextMenu/ContextMenu.scss b/frontend/src/components/ContextMenu/ContextMenu.scss
index f171836..3ab6754 100644
--- a/frontend/src/components/ContextMenu/ContextMenu.scss
+++ b/frontend/src/components/ContextMenu/ContextMenu.scss
@@ -1,11 +1,86 @@
+@import "../../styles/variables";
+
.fm-context-menu {
position: absolute;
background-color: white;
border: 1px solid #c6c6c6;
- border-radius: 5px;
- padding: 5px 0;
+ border-radius: 6px;
+ padding: 4px;
z-index: 1;
transition: opacity 0.1s linear;
+
+ .file-context-menu-list {
+ font-size: 1.1em;
+
+ ul {
+ list-style-type: none;
+ padding-left: 0;
+ margin: 0;
+ display: flex;
+ flex-direction: column;
+ gap: 3px;
+
+ li {
+ display: flex;
+ gap: 9px;
+ align-items: center;
+ padding: 3px 13px;
+ position: relative;
+ border-radius: 4px;
+
+ &:hover {
+ cursor: pointer;
+ background-color: rgb(0, 0, 0, 0.07);
+ }
+ }
+
+ li.active {
+ background-color: rgb(0, 0, 0, 0.07);
+ }
+
+ li.disable-paste {
+ opacity: 0.5;
+
+ &:hover {
+ cursor: default;
+ background-color: transparent;
+ }
+ }
+ }
+
+ .divider {
+ border-bottom: 1px solid #c6c6c6;
+ margin: 5px 0 3px 0;
+ }
+
+ .list-expand-icon {
+ margin-left: auto;
+ color: #444444;
+ }
+
+ .sub-menu {
+ position: absolute;
+ left: calc(100% - 2px);
+ top: 0;
+ background-color: white;
+ border: 1px solid #c6c6c6;
+ border-radius: 6px;
+ padding: 4px;
+ z-index: 1;
+
+ .item-selected {
+ width: 13px;
+ color: #444444;
+ }
+
+ li {
+
+ &:hover {
+ background-color: rgb(0, 0, 0, 0.07) !important;
+ }
+ }
+ }
+ }
}
.fm-context-menu.hidden {
@@ -18,4 +93,4 @@
opacity: 1;
pointer-events: all;
visibility: visible;
-}
+}
\ No newline at end of file
diff --git a/frontend/src/components/ContextMenu/SubMenu.jsx b/frontend/src/components/ContextMenu/SubMenu.jsx
new file mode 100644
index 0000000..df04d88
--- /dev/null
+++ b/frontend/src/components/ContextMenu/SubMenu.jsx
@@ -0,0 +1,17 @@
+import { FaCheck } from "react-icons/fa6";
+
+const SubMenu = ({ list }) => {
+ return (
+