Skip to content

Add collapsible navigation pane #192

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jun 1, 2025
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,8 @@ type File = {
| Name | Type | Description |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `acceptedFileTypes` | string | (Optional) A comma-separated list of allowed file extensions for uploading specific file types (e.g., `.txt, .png, .pdf`). If omitted, all file types are accepted. |
| `collapsibleNav` | boolean | Enables a collapsible navigation pane on the left side. When `true`, a toggle will be shown to expand or collapse the navigation pane. `default: false`. |
| `defaultNavExpanded` | boolean | Sets the default expanded (`true`) or collapsed (`false`) state of the navigation pane when `collapsibleNav` is enabled. This only affects the initial render. `default: true`. |
| `enableFilePreview` | boolean | A boolean flag indicating whether to use the default file previewer in the file manager `default: true`. |
| `filePreviewPath` | string | The base URL for file previews e.g.`https://example.com`, file path will be appended automatically to it i.e. `https://example.com/yourFilePath`. |
| `filePreviewComponent` | (file: [File](#-file-structure)) => React.ReactNode | (Optional) A callback function that provides a custom file preview. It receives the selected file as its argument and must return a valid React node, JSX element, or HTML. Use this prop to override the default file preview behavior. Example: [Custom Preview Usage](#custom-file-preview). |
Expand Down
2 changes: 2 additions & 0 deletions frontend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,8 @@ type File = {
| Name | Type | Description |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `acceptedFileTypes` | string | (Optional) A comma-separated list of allowed file extensions for uploading specific file types (e.g., `.txt, .png, .pdf`). If omitted, all file types are accepted. |
| `collapsibleNav` | boolean | Enables a collapsible navigation pane on the left side. When `true`, a toggle will be shown to expand or collapse the navigation pane. `default: false`. |
| `defaultNavExpanded` | boolean | Sets the default expanded (`true`) or collapsed (`false`) state of the navigation pane when `collapsibleNav` is enabled. This only affects the initial render. `default: true`. |
| `enableFilePreview` | boolean | A boolean flag indicating whether to use the default file previewer in the file manager `default: true`. |
| `filePreviewPath` | string | The base URL for file previews e.g.`https://example.com`, file path will be appended automatically to it i.e. `https://example.com/yourFilePath`. |
| `filePreviewComponent` | (file: [File](#-file-structure)) => React.ReactNode | (Optional) A callback function that provides a custom file preview. It receives the selected file as its argument and must return a valid React node, JSX element, or HTML. Use this prop to override the default file preview behavior. Example: [Custom Preview Usage](#custom-file-preview). |
Expand Down
42 changes: 39 additions & 3 deletions frontend/src/FileManager/BreadCrumb/BreadCrumb.jsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { useEffect, useRef, useState } from "react";
import PropTypes from "prop-types";
import { MdHome, MdMoreHoriz, MdOutlineNavigateNext } from "react-icons/md";
import { TbLayoutSidebarLeftExpand, TbLayoutSidebarLeftCollapseFilled } from "react-icons/tb";
import { useFileNavigation } from "../../contexts/FileNavigationContext";
import { useDetectOutsideClick } from "../../hooks/useDetectOutsideClick";
import { useTranslation } from "../../contexts/TranslationProvider";
import "./BreadCrumb.scss";

const BreadCrumb = () => {
const BreadCrumb = ({ collapsibleNav, isNavigationPaneOpen, setNavigationPaneOpen }) => {
const [folders, setFolders] = useState([]);
const [hiddenFolders, setHiddenFolders] = useState([]);
const [hiddenFoldersWidth, setHiddenFoldersWidth] = useState([]);
Expand All @@ -19,6 +21,7 @@ const BreadCrumb = () => {
setShowHiddenFolders(false);
});
const t = useTranslation();
const navTogglerRef = useRef(null);

useEffect(() => {
setFolders(() => {
Expand All @@ -42,9 +45,14 @@ const BreadCrumb = () => {
const containerWidth = breadCrumbRef.current.clientWidth;
const containerStyles = getComputedStyle(breadCrumbRef.current);
const paddingLeft = parseFloat(containerStyles.paddingLeft);
const navTogglerGap = collapsibleNav ? 2 : 0;
const navTogglerDividerWidth = 1;
const navTogglerWidth = collapsibleNav
? navTogglerRef.current?.clientWidth + navTogglerDividerWidth
: 0;
const moreBtnGap = hiddenFolders.length > 0 ? 1 : 0;
const flexGap = parseFloat(containerStyles.gap) * (folders.length + moreBtnGap);
return containerWidth - (paddingLeft + flexGap);
const flexGap = parseFloat(containerStyles.gap) * (folders.length + moreBtnGap + navTogglerGap);
return containerWidth - (paddingLeft + flexGap + navTogglerWidth);
};

const checkAvailableSpace = () => {
Expand Down Expand Up @@ -79,6 +87,29 @@ const BreadCrumb = () => {
return (
<div className="bread-crumb-container">
<div className="breadcrumb" ref={breadCrumbRef}>
{collapsibleNav && (
<>
<div
ref={navTogglerRef}
className="nav-toggler"
title={`${
isNavigationPaneOpen ? t("collapseNavigationPane") : t("expandNavigationPane")
}`}
>
<span
className="folder-name folder-name-btn"
onClick={() => setNavigationPaneOpen((prev) => !prev)}
>
{isNavigationPaneOpen ? (
<TbLayoutSidebarLeftCollapseFilled />
) : (
<TbLayoutSidebarLeftExpand />
)}
</span>
</div>
<div className="divider" />
</>
)}
{folders.map((folder, index) => (
<div key={index} style={{ display: "contents" }}>
<span
Expand Down Expand Up @@ -124,4 +155,9 @@ const BreadCrumb = () => {

BreadCrumb.displayName = "BreadCrumb";

BreadCrumb.propTypes = {
isNavigationPaneOpen: PropTypes.bool.isRequired,
setNavigationPaneOpen: PropTypes.func.isRequired,
};

export default BreadCrumb;
10 changes: 10 additions & 0 deletions frontend/src/FileManager/BreadCrumb/BreadCrumb.scss
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,16 @@
background: var(--file-manager-primary-color) !important;
}

.nav-toggler {
display: flex;
align-items: center;
}

.divider {
width: 1px;
background-color: $border-color;
}

.folder-name {
display: flex;
align-items: center;
Expand Down
25 changes: 21 additions & 4 deletions frontend/src/FileManager/FileManager.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { useColumnResize } from "../hooks/useColumnResize";
import PropTypes from "prop-types";
import { dateStringValidator, urlValidator } from "../validators/propValidators";
import { TranslationProvider } from "../contexts/TranslationProvider";
import { useMemo } from "react";
import { useMemo, useState } from "react";
import { defaultPermissions } from "../constants";
import "./FileManager.scss";

Expand Down Expand Up @@ -49,7 +49,10 @@ const FileManager = ({
fontFamily = "Nunito Sans, sans-serif",
language = "en",
permissions: userPermissions = {},
collapsibleNav = false,
defaultNavExpanded = true,
}) => {
const [isNavigationPaneOpen, setNavigationPaneOpen] = useState(defaultNavExpanded);
const triggerAction = useTriggerAction();
const { containerRef, colSizes, isDragging, handleMouseMove, handleMouseUp, handleMouseDown } =
useColumnResize(20, 80);
Expand Down Expand Up @@ -86,16 +89,28 @@ const FileManager = ({
onMouseUp={handleMouseUp}
className="files-container"
>
<div className="navigation-pane" style={{ width: colSizes.col1 + "%" }}>
<div
className={`navigation-pane ${isNavigationPaneOpen ? "open" : "closed"}`}
style={{
width: colSizes.col1 + "%",
}}
>
<NavigationPane onFileOpen={onFileOpen} />
<div
className={`sidebar-resize ${isDragging ? "sidebar-dragging" : ""}`}
onMouseDown={handleMouseDown}
/>
</div>

<div className="folders-preview" style={{ width: colSizes.col2 + "%" }}>
<BreadCrumb />
<div
className="folders-preview"
style={{ width: (isNavigationPaneOpen ? colSizes.col2 : 100) + "%" }}
>
<BreadCrumb
collapsibleNav={collapsibleNav}
isNavigationPaneOpen={isNavigationPaneOpen}
setNavigationPaneOpen={setNavigationPaneOpen}
/>
<FileList
onCreateFolder={onCreateFolder}
onRename={onRename}
Expand Down Expand Up @@ -184,6 +199,8 @@ FileManager.propTypes = {
download: PropTypes.bool,
delete: PropTypes.bool,
}),
collapsibleNav: PropTypes.bool,
defaultNavExpanded: PropTypes.bool,
};

export default FileManager;
9 changes: 9 additions & 0 deletions frontend/src/FileManager/FileManager.scss
Original file line number Diff line number Diff line change
Expand Up @@ -78,12 +78,21 @@ svg {
}
}

.navigation-pane.open {
display: block;
}

.navigation-pane.closed {
display: none;
}

.folders-preview {
z-index: 2;
background-color: white;
padding-right: 0px;
padding-left: 0px;
border-bottom-right-radius: 8px;
border-bottom-left-radius: 8px;
}
}
}
Expand Down
6 changes: 4 additions & 2 deletions frontend/src/locales/ar-SA.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,5 +48,7 @@
"percentDone": "{{percent}}% تم",
"canceled": "تم الإلغاء",
"invalidFileName": "لا يمكن أن يحتوي اسم الملف على أي من الحروف التالية: \\ / : * ? \" < > |",
"folderExists": "هذا الموقع يحتوي بالفعل على مجلد باسم \"{{renameFile}}\"."
}
"folderExists": "هذا الموقع يحتوي بالفعل على مجلد باسم \"{{renameFile}}\".",
"collapseNavigationPane": "طي لوحة التنقل",
"expandNavigationPane": "توسيع لوحة التنقل"
}
6 changes: 4 additions & 2 deletions frontend/src/locales/de-DE.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,5 +48,7 @@
"percentDone": "{{percent}}% erledigt",
"canceled": "Abgebrochen",
"invalidFileName": "Ein Dateiname darf keines der folgenden Zeichen enthalten: \\ / : * ? \" < > |",
"folderExists": "In diesem Zielordner gibt es bereits einen Ordner namens \"{{renameFile}}\"."
}
"folderExists": "In diesem Zielordner gibt es bereits einen Ordner namens \"{{renameFile}}\".",
"collapseNavigationPane": "Navigationsbereich einklappen",
"expandNavigationPane": "Navigationsbereich erweitern"
}
6 changes: 4 additions & 2 deletions frontend/src/locales/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,5 +48,7 @@
"percentDone": "{{percent}}% done",
"canceled": "Canceled",
"invalidFileName": "A file name can't contain any of the following characters: \\ / : * ? \" < > |",
"folderExists": "This destination already contains a folder named \"{{renameFile}}\"."
}
"folderExists": "This destination already contains a folder named \"{{renameFile}}\".",
"collapseNavigationPane": "Collapse Navigation Pane",
"expandNavigationPane": "Expand Navigation Pane"
}
6 changes: 4 additions & 2 deletions frontend/src/locales/es-ES.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,5 +48,7 @@
"percentDone": "{{percent}}% completado",
"canceled": "Cancelado",
"invalidFileName": "Un nombre de archivo no puede contener ninguno de los siguientes caracteres: \\ / : * ? \" < > |",
"folderExists": "Ya existe una carpeta llamada \"{{renameFile}}\" en este destino."
}
"folderExists": "Ya existe una carpeta llamada \"{{renameFile}}\" en este destino.",
"collapseNavigationPane": "Contraer panel de navegación",
"expandNavigationPane": "Expandir panel de navegación"
}
6 changes: 4 additions & 2 deletions frontend/src/locales/fr-FR.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,5 +48,7 @@
"percentDone": "{{percent}}% terminé",
"canceled": "Annulé",
"invalidFileName": "Un nom de fichier ne peut pas contenir les caractères suivants : \\ / : * ? \" < > |",
"folderExists": "Cette destination contient déjà un dossier nommé \"{{renameFile}}\"."
}
"folderExists": "Cette destination contient déjà un dossier nommé \"{{renameFile}}\".",
"collapseNavigationPane": "Réduire le panneau de navigation",
"expandNavigationPane": "Développer le panneau de navigation"
}
Loading