refactor: Split folderSidebarComponent into modular components (#5000)

*  (index.tsx): Refactor SideBarFoldersButtonsComponent to improve code readability and maintainability. Add new functionalities such as drag and drop, folder hovering effects, and folder actions like upload, download, add, update, and delete. Update folder state management and handle loading states for various folder operations.

*  (add-folder-button.tsx): Add a new component for adding folders to the sidebar
 (folder-select-item.tsx): Add a new component for displaying folder select items in the sidebar
 (header-buttons.tsx): Add a new component for displaying header buttons in the sidebar
 (input-edit-folder-name.tsx): Add a new component for editing folder names in the sidebar
 (select-options.tsx): Add a new component for displaying select options in the sidebar
 (upload-folder-button.tsx): Add a new component for uploading folders to the sidebar
 (handle-select-change.ts): Add a new helper function for handling select changes in the sidebar
🔧 (index.tsx): Refactor sidebar folder buttons component to use new components and helpers for better organization and functionality
This commit is contained in:
Cristhian Zanforlin Lousa 2024-12-03 17:18:37 -03:00 committed by GitHub
commit 8a516de691
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 337 additions and 221 deletions

View file

@ -0,0 +1,27 @@
import IconComponent from "@/components/common/genericIconComponent";
import ShadTooltip from "@/components/common/shadTooltipComponent";
import { Button } from "@/components/ui/button";
export const AddFolderButton = ({
onClick,
disabled,
loading,
}: {
onClick: () => void;
disabled: boolean;
loading: boolean;
}) => (
<ShadTooltip content="Create new folder" styleClasses="z-50">
<Button
variant="ghost"
size="icon"
className="h-7 w-7 border-0 text-zinc-500 hover:bg-zinc-200 dark:text-zinc-400 dark:hover:bg-zinc-800 dark:hover:text-white"
onClick={onClick}
data-testid="add-folder-button"
disabled={disabled}
loading={loading}
>
<IconComponent name="Plus" className="h-4 w-4" />
</Button>
</ShadTooltip>
);

View file

@ -0,0 +1,14 @@
import IconComponent from "@/components/common/genericIconComponent";
import { cn } from "@/utils/utils";
export const FolderSelectItem = ({ name, iconName }) => (
<div
className={cn(
name === "Delete" ? "text-destructive" : "",
"flex items-center font-medium",
)}
>
<IconComponent name={iconName} className="mr-2 w-4" />
<span>{name}</span>
</div>
);

View file

@ -0,0 +1,35 @@
import IconComponent from "@/components/common/genericIconComponent";
import { SidebarTrigger } from "@/components/ui/sidebar";
import { AddFolderButton } from "./add-folder-button";
import { UploadFolderButton } from "./upload-folder-button";
export const HeaderButtons = ({
handleUploadFlowsToFolder,
isUpdatingFolder,
isPending,
addNewFolder,
}: {
handleUploadFlowsToFolder: () => void;
isUpdatingFolder: boolean;
isPending: boolean;
addNewFolder: () => void;
}) => (
<div className="flex shrink-0 items-center justify-between gap-2">
<SidebarTrigger className="lg:hidden">
<IconComponent name="PanelLeftClose" className="h-4 w-4" />
</SidebarTrigger>
<div className="flex-1 text-sm font-semibold">Folders</div>
<div className="flex items-center gap-1">
<UploadFolderButton
onClick={handleUploadFlowsToFolder}
disabled={isUpdatingFolder}
/>
<AddFolderButton
onClick={addNewFolder}
disabled={isUpdatingFolder}
loading={isPending}
/>
</div>
</div>
);

View file

@ -0,0 +1,67 @@
import { Input } from "@/components/ui/input";
import { FolderType } from "@/pages/MainPage/entities";
export const InputEditFolderName = ({
handleEditFolderName,
item,
refInput,
handleKeyDownFn,
handleKeyDown,
handleEditNameFolder,
editFolderName,
foldersNames,
}: {
handleEditFolderName: (
e: React.ChangeEvent<HTMLInputElement>,
folderName: string,
) => void;
item: FolderType;
refInput: React.RefObject<HTMLInputElement>;
handleKeyDownFn: (
e: React.KeyboardEvent<HTMLInputElement>,
folder: FolderType,
) => void;
handleKeyDown: (
e: React.KeyboardEvent<HTMLInputElement>,
key: string,
folderName: string,
) => void;
handleEditNameFolder: (item: FolderType) => void;
editFolderName: { name: string; edit: boolean };
foldersNames: Record<string, string>;
}) => {
return (
<>
<Input
className="h-6 flex-1 focus:border-0"
onChange={(e) => {
handleEditFolderName(e, item.name);
}}
maxLength={38}
ref={refInput}
onKeyDown={(e) => {
handleKeyDownFn(e, item);
handleKeyDown(e, e.key, "");
}}
autoFocus={true}
onBlur={(e) => {
// fixes autofocus problem where cursor isn't present
if (e.relatedTarget?.id === `options-trigger-${item.name}`) {
refInput.current?.focus();
return;
}
if (refInput.current?.value !== item.name) {
handleEditNameFolder(item);
} else {
editFolderName.edit = false;
}
refInput.current?.blur();
}}
value={foldersNames[item.name]}
id={`input-folder-${item.name}`}
data-testid={`input-folder`}
/>
</>
);
};

View file

@ -0,0 +1,80 @@
import IconComponent from "@/components/common/genericIconComponent";
import ShadTooltip from "@/components/common/shadTooltipComponent";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
} from "@/components/ui/select-custom";
import { FolderType } from "@/pages/MainPage/entities";
import { cn } from "@/utils/utils";
import { handleSelectChange } from "../helpers/handle-select-change";
import { FolderSelectItem } from "./folder-select-item";
export const SelectOptions = ({
item,
index,
handleDeleteFolder,
handleDownloadFolder,
handleSelectFolderToRename,
checkPathName,
}: {
item: FolderType;
index: number;
handleDeleteFolder: ((folder: FolderType) => void) | undefined;
handleDownloadFolder: (folderId: string) => void;
handleSelectFolderToRename: (folder: FolderType) => void;
checkPathName: (folderId: string) => boolean;
}) => {
return (
<>
<Select
onValueChange={(value) =>
handleSelectChange(
value,
item,
handleDeleteFolder,
handleDownloadFolder,
handleSelectFolderToRename,
)
}
value=""
>
<ShadTooltip content="Options" side="right" styleClasses="z-50">
<SelectTrigger
className="w-fit"
id={`options-trigger-${item.name}`}
data-testid="more-options-button"
>
<IconComponent
name={"MoreHorizontal"}
className={cn(
`w-4 stroke-[1.5] px-0 text-muted-foreground group-hover/menu-button:block group-hover/menu-button:text-foreground`,
checkPathName(item.id!) ? "block" : "hidden",
)}
/>
</SelectTrigger>
</ShadTooltip>
<SelectContent align="end" alignOffset={-16} position="popper">
{item.name !== "My Projects" && (
<SelectItem
id="rename-button"
value="rename"
data-testid="btn-rename-folder"
>
<FolderSelectItem name="Rename" iconName="SquarePen" />
</SelectItem>
)}
<SelectItem value="download" data-testid="btn-download-folder">
<FolderSelectItem name="Download Content" iconName="Download" />
</SelectItem>
{index > 0 && (
<SelectItem value="delete" data-testid="btn-delete-folder">
<FolderSelectItem name="Delete" iconName="Trash2" />
</SelectItem>
)}
</SelectContent>
</Select>
</>
);
};

View file

@ -0,0 +1,18 @@
import IconComponent from "@/components/common/genericIconComponent";
import ShadTooltip from "@/components/common/shadTooltipComponent";
import { Button } from "@/components/ui/button";
export const UploadFolderButton = ({ onClick, disabled }) => (
<ShadTooltip content="Upload a flow" styleClasses="z-50">
<Button
variant="ghost"
size="icon"
className="h-7 w-7 border-0 text-zinc-500 hover:bg-zinc-200 dark:text-zinc-400 dark:hover:bg-zinc-800 dark:hover:text-white"
onClick={onClick}
data-testid="upload-folder-button"
disabled={disabled}
>
<IconComponent name="Upload" className="h-4 w-4" />
</Button>
</ShadTooltip>
);

View file

@ -0,0 +1,21 @@
import { FolderType } from "@/pages/MainPage/entities";
export const handleSelectChange = (
option: string,
folder: FolderType,
handleDeleteFolder: ((folder: FolderType) => void) | undefined,
handleDownloadFolder: (folderId: string) => void,
handleSelectFolderToRename: (folder: FolderType) => void,
) => {
switch (option) {
case "delete":
handleDeleteFolder!(folder);
break;
case "download":
handleDownloadFolder(folder.id!);
break;
case "rename":
handleSelectFolderToRename(folder);
break;
}
};

View file

@ -1,10 +1,3 @@
import ShadTooltip from "@/components/common/shadTooltipComponent";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
} from "@/components/ui/select-custom";
import {
Sidebar,
SidebarContent,
@ -14,7 +7,6 @@ import {
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
SidebarTrigger,
} from "@/components/ui/sidebar";
import {
usePatchFolders,
@ -37,11 +29,11 @@ import useFlowsManagerStore from "../../../../../stores/flowsManagerStore";
import { useFolderStore } from "../../../../../stores/foldersStore";
import { handleKeyDown } from "../../../../../utils/reactflowUtils";
import { cn } from "../../../../../utils/utils";
import IconComponent from "../../../../common/genericIconComponent";
import { Button } from "../../../../ui/button";
import { Input } from "../../../../ui/input";
import useFileDrop from "../../hooks/use-on-file-drop";
import { SidebarFolderSkeleton } from "../sidebarFolderSkeleton";
import { HeaderButtons } from "./components/header-buttons";
import { InputEditFolderName } from "./components/input-edit-folder-name";
import { SelectOptions } from "./components/select-options";
type SideBarFoldersButtonsComponentProps = {
handleChangeFolder?: (id: string) => void;
@ -54,37 +46,68 @@ const SideBarFoldersButtonsComponent = ({
const location = useLocation();
const pathname = location.pathname;
const folders = useFolderStore((state) => state.folders);
const isFetchingFolders = !!useIsFetching({
queryKey: ["useGetFolders"],
exact: false,
});
const loading = !folders;
const refInput = useRef<HTMLInputElement>(null);
const [foldersNames, setFoldersNames] = useState({});
const takeSnapshot = useFlowsManagerStore((state) => state.takeSnapshot);
const [editFolders, setEditFolderName] = useState(
folders.map((obj) => ({ name: obj.name, edit: false })) ?? [],
);
const currentFolder = pathname.split("/");
const urlWithoutPath =
pathname.split("/").length < (ENABLE_CUSTOM_PARAM ? 5 : 4);
const myCollectionId = useFolderStore((state) => state.myCollectionId);
const checkPathName = (itemId: string) => {
if (urlWithoutPath && itemId === myCollectionId) {
return true;
}
return currentFolder.includes(itemId);
};
const folderId = useParams().folderId ?? myCollectionId ?? "";
const setErrorData = useAlertStore((state) => state.setErrorData);
const setSuccessData = useAlertStore((state) => state.setSuccessData);
const uploadFlow = useUploadFlow();
const isMobile = useIsMobile({ maxWidth: 1024 });
const folderIdDragging = useFolderStore((state) => state.folderIdDragging);
const myCollectionId = useFolderStore((state) => state.myCollectionId);
const takeSnapshot = useFlowsManagerStore((state) => state.takeSnapshot);
const folderId = useParams().folderId ?? myCollectionId ?? "";
const { dragOver, dragEnter, dragLeave, onDrop } = useFileDrop(folderId);
const uploadFlow = useUploadFlow();
const [foldersNames, setFoldersNames] = useState({});
const [editFolders, setEditFolderName] = useState(
folders.map((obj) => ({ name: obj.name, edit: false })) ?? [],
);
const isFetchingFolders = !!useIsFetching({
queryKey: ["useGetFolders"],
exact: false,
});
const { mutate: mutateDownloadFolder } = useGetDownloadFolders({});
const { mutate: mutateAddFolder, isPending } = usePostFolders();
const { mutate: mutateUpdateFolder } = usePatchFolders();
const { mutate } = usePostUploadFolders();
const checkHoveringFolder = (folderId: string) => {
if (folderId === folderIdDragging) {
return "bg-accent text-accent-foreground";
}
};
const isFetchingFolder = !!useIsFetching({
queryKey: ["useGetFolder"],
exact: false,
});
const isDeletingFolder = !!useIsMutating({
mutationKey: ["useDeleteFolders"],
});
const isUpdatingFolder =
isFetchingFolders ||
isFetchingFolder ||
isPending ||
loading ||
isDeletingFolder;
const handleUploadFlowsToFolder = () => {
createFileUpload().then((files: File[]) => {
if (files?.length === 0) {
@ -125,8 +148,6 @@ const SideBarFoldersButtonsComponent = ({
});
};
const { mutate: mutateDownloadFolder } = useGetDownloadFolders({});
const handleDownloadFolder = (id: string) => {
mutateDownloadFolder(
{
@ -166,9 +187,6 @@ const SideBarFoldersButtonsComponent = ({
);
};
const { mutate: mutateAddFolder, isPending } = usePostFolders();
const { mutate: mutateUpdateFolder } = usePatchFolders();
function addNewFolder() {
mutateAddFolder(
{
@ -257,86 +275,6 @@ const SideBarFoldersButtonsComponent = ({
}
};
const isFetchingFolder = !!useIsFetching({
queryKey: ["useGetFolder"],
exact: false,
});
const isDeletingFolder = !!useIsMutating({
mutationKey: ["useDeleteFolders"],
});
const isUpdatingFolder =
isFetchingFolders ||
isFetchingFolder ||
isPending ||
loading ||
isDeletingFolder;
const HeaderButtons = () => (
<div className="flex shrink-0 items-center justify-between gap-2">
<SidebarTrigger className="lg:hidden">
<IconComponent name="PanelLeftClose" className="h-4 w-4" />
</SidebarTrigger>
<div className="flex-1 text-sm font-semibold">Folders</div>
<div className="flex items-center gap-1">
<UploadFolderButton
onClick={handleUploadFlowsToFolder}
disabled={isUpdatingFolder}
/>
<AddFolderButton
onClick={addNewFolder}
disabled={isUpdatingFolder}
loading={isPending}
/>
</div>
</div>
);
const AddFolderButton = ({ onClick, disabled, loading }) => (
<ShadTooltip content="Create new folder" styleClasses="z-50">
<Button
variant="ghost"
size="icon"
className="h-7 w-7 border-0 text-zinc-500 hover:bg-zinc-200 dark:text-zinc-400 dark:hover:bg-zinc-800 dark:hover:text-white"
onClick={onClick}
data-testid="add-folder-button"
disabled={disabled}
loading={loading}
>
<IconComponent name="Plus" className="h-4 w-4" />
</Button>
</ShadTooltip>
);
const UploadFolderButton = ({ onClick, disabled }) => (
<ShadTooltip content="Upload a flow" styleClasses="z-50">
<Button
variant="ghost"
size="icon"
className="h-7 w-7 border-0 text-zinc-500 hover:bg-zinc-200 dark:text-zinc-400 dark:hover:bg-zinc-800 dark:hover:text-white"
onClick={onClick}
data-testid="upload-folder-button"
disabled={disabled}
>
<IconComponent name="Upload" className="h-4 w-4" />
</Button>
</ShadTooltip>
);
const FolderSelectItem = ({ name, iconName }) => (
<div
className={cn(
name === "Delete" ? "text-destructive" : "",
"flex items-center font-medium",
)}
>
<IconComponent name={iconName} className="mr-2 w-4" />
<span>{name}</span>
</div>
);
const handleDoubleClick = (event, item) => {
if (item.name === "My Projects") {
return;
@ -395,29 +333,18 @@ const SideBarFoldersButtonsComponent = ({
}
};
const handleSelectChange = (option, folder) => {
switch (option) {
case "delete":
handleDeleteFolder!(folder);
break;
case "download":
handleDownloadFolder(folder.id!);
break;
case "rename":
handleSelectFolderToRename(folder);
break;
}
};
const isMobile = useIsMobile({ maxWidth: 1024 });
return (
<Sidebar
collapsible={isMobile ? "offcanvas" : "none"}
data-testid="folder-sidebar"
>
<SidebarHeader className="p-4">
<HeaderButtons />
<HeaderButtons
handleUploadFlowsToFolder={handleUploadFlowsToFolder}
isUpdatingFolder={isUpdatingFolder}
isPending={isPending}
addNewFolder={addNewFolder}
/>
</SidebarHeader>
<SidebarContent>
<SidebarGroup className="p-4 py-2">
@ -440,7 +367,10 @@ const SideBarFoldersButtonsComponent = ({
data-testid={`sidebar-nav-${item.name}`}
isActive={checkPathName(item.id!)}
onClick={() => handleChangeFolder!(item.id!)}
className="group/menu-button"
className={cn(
"group/menu-button",
checkHoveringFolder(item.id!),
)}
>
<div
onDoubleClick={(event) => {
@ -450,38 +380,15 @@ const SideBarFoldersButtonsComponent = ({
>
<div className="flex flex-1 items-center gap-2">
{editFolderName?.edit && !isUpdatingFolder ? (
<Input
className="h-6 flex-1 focus:border-0"
onChange={(e) => {
handleEditFolderName(e, item.name);
}}
maxLength={38}
ref={refInput}
onKeyDown={(e) => {
handleKeyDownFn(e, item);
handleKeyDown(e, e.key, "");
}}
autoFocus={true}
onBlur={(e) => {
// fixes autofocus problem where cursor isn't present
if (
e.relatedTarget?.id ===
`options-trigger-${item.name}`
) {
refInput.current?.focus();
return;
}
if (refInput.current?.value !== item.name) {
handleEditNameFolder(item);
} else {
editFolderName.edit = false;
}
refInput.current?.blur();
}}
value={foldersNames[item.name]}
id={`input-folder-${item.name}`}
data-testid={`input-folder`}
<InputEditFolderName
handleEditFolderName={handleEditFolderName}
item={item}
refInput={refInput}
handleKeyDownFn={handleKeyDownFn}
handleEditNameFolder={handleEditNameFolder}
editFolderName={editFolderName}
foldersNames={foldersNames}
handleKeyDown={handleKeyDown}
/>
) : (
<span className="block w-0 grow truncate text-[13px] opacity-100">
@ -489,69 +396,16 @@ const SideBarFoldersButtonsComponent = ({
</span>
)}
</div>
<Select
onValueChange={(value) =>
handleSelectChange(value, item)
<SelectOptions
item={item}
index={index}
handleDeleteFolder={handleDeleteFolder}
handleDownloadFolder={handleDownloadFolder}
handleSelectFolderToRename={
handleSelectFolderToRename
}
value=""
>
<ShadTooltip
content="Options"
side="right"
styleClasses="z-50"
>
<SelectTrigger
className="w-fit"
id={`options-trigger-${item.name}`}
data-testid="more-options-button"
>
<IconComponent
name={"MoreHorizontal"}
className={`w-4 stroke-[1.5] px-0 text-muted-foreground group-hover/menu-button:block group-hover/menu-button:text-foreground ${
checkPathName(item.id!) ? "block" : "hidden"
}`}
/>
</SelectTrigger>
</ShadTooltip>
<SelectContent
align="end"
alignOffset={-16}
position="popper"
>
{item.name !== "My Projects" && (
<SelectItem
id="rename-button"
value="rename"
data-testid="btn-rename-folder"
>
<FolderSelectItem
name="Rename"
iconName="SquarePen"
/>
</SelectItem>
)}
<SelectItem
value="download"
data-testid="btn-download-folder"
>
<FolderSelectItem
name="Download"
iconName="Download"
/>
</SelectItem>
{index > 0 && (
<SelectItem
value="delete"
data-testid="btn-delete-folder"
>
<FolderSelectItem
name="Delete"
iconName="Trash2"
/>
</SelectItem>
)}
</SelectContent>
</Select>
checkPathName={checkPathName}
/>
</div>
</SidebarMenuButton>
</SidebarMenuItem>