DEV Community

Cover image for Let's create a File Manager From Scratch With React And Typescript Chapter IV: Creating FileManager Component
Hasan Zohdy
Hasan Zohdy

Posted on

Let's create a File Manager From Scratch With React And Typescript Chapter IV: Creating FileManager Component

Let's head now to the part where i don't really admire, working with UI, let's create our FileManager component, but firstly let's define what props it accepts.

For encapsulation behavior, i'll add the FileManager types file inside the component directory.

Create file-manager/components/FileManager directory then create FileManager.types.ts inside it

// file-manager/components/FileManager/FileManager.types.ts
import { Node } from "../../types/FileManager.types";

export type FileManagerProps = {
  /**
   * Open State for the file manager
   */
  open: boolean;
  /**
   * Callback for when the file manager is closed
   */
  onClose: () => void;
  /**
   * Root path to open in the file manager
   *
   * @default "/"
   */
  rootPath?: string;
  /**
   * Callback for when a file/directory is selected
   */
  onSelect?: (node: Node) => void;
  /**
   * Callback for when a file/directory is double clicked
   */
  onDoubleClick?: (node: Node) => void;
  /**
   * Callback for when a file/directory is right clicked
   */
  onRightClick?: (node: Node) => void;
  /**
   * Callback for when a file/directory is copied
   */
  onCopy?: (node: Node) => void;
  /**
   * Callback for when a file/directory is cut
   */
  onCut?: (node: Node) => void;
  /**
   * Callback for when a file/directory is pasted
   * The old node will contain the old path 
   * and the new node will contain the new path
   */
  onPaste?: (node: Node, oldNode: Node) => void;
  /**
   * Callback for when a file/directory is deleted
   */
  onDelete?: (node: Node) => void;
  /**
   * Callback for when a file/directory is renamed
   * The old node will contain the old path/name 
   * and the new node will contain the new path/name
   */
  onRename?: (node: Node, oldNode: Node) => void;
  /**
   * Callback for when a directory is created
   */
  onCreateDirectory?: (directory: Node) => void;
  /**
   * Callback for when file(s) is uploaded
   */
  onUpload?: (files: Node[]) => void;
  /**
   * Callback for when a file is downloaded
   */
  onDownload?: (node: Node) => void;
};
Enter fullscreen mode Exit fullscreen mode

wooohoo, don't freak out, i'm just trying to make you imagine the big picture, try to read the component props slowly and carefully and the comments above each prop.

For the time being we'll just need only two props, open and onClose which are the only required props.

// file-manager/components/FileManager/FileManager.types.ts
export type FileManagerProps = {
  /**
   * Open State for the file manager
   */
  open: boolean;
  /**
   * Root path to open in the file manager
   *
   * @default "/"
   */
  rootPath?: string;
  /**
   * Callback for when the file manager is closed
   */
  onClose: () => void;
};
Enter fullscreen mode Exit fullscreen mode

These two props are responsible for opening and closing the file manager, as we will open it up in a modal.

Now let's create our FileManager Component.

// file-manager/components/FileManager/FileManager.ts
import { FileManagerProps } from "./FileManager.types";

export default function FileManager({ open, onClose }: FileManagerProps) {
  return <div>FileManager</div>;
}
Enter fullscreen mode Exit fullscreen mode

Before we proceed to the next step, we'll create an index file to encapsulate the files imports from it directly.

// file-manager/components/FileManager/index.ts
export { default } from "./FileManager";
export * from "./FileManager.types";
Enter fullscreen mode Exit fullscreen mode

Now let's go to our HomePage and call our newly created component, the FileManager.

Small modification, we'll also create a component file HomePage for the home page rather than sitting it in the index file, then we'll export it from the index as we did in the file manager.

// home/components/HomePage/HomePage.tsx
import Helmet from "@mongez/react-helmet";
import FileManager from "app/file-manager/components/FileManager";
import { useState } from "react";

export default function HomePage() {
  const [openFileManager, setOpenFileManager] = useState(false);

  return (
    <>
      <Helmet title="home" appendAppName={false} />
      <h1>Welcome To Home Page</h1>
      <FileManager
        open={openFileManager}
        onClose={() => setOpenFileManager(false)}
      />
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

We created a state to manage the open/close state for the file manager we set it to false as default, then we called our component in the function return.

Now let's see what will be in the browser, you should have something like this:

Home Page

Setting up Mantine Styles

I won't stop here too long, we'll just do what the docs says and import Mantine Theme Provider in top of our application, which can be set in the Root component.

Let's go to src/apps/front-office/design-system/layouts/Root.tsx and update it with the following:

import { AppShell, MantineProvider } from "@mantine/core";
import { BasicComponentProps } from "../../utils/types";

/**
 * The root should be used with react-router's configuration for rootComponent.
 * So it will wrap the entire app.
 * It can be useful for single operations as this component will only render once in the entire application life cycle.
 * You may for instance fetch settings from the server before loading the app or a Bearer token to work with the API.
 */
export default function Root({ children }: BasicComponentProps) {
  return (
    <>
      <MantineProvider withGlobalStyles withNormalizeCSS>
        <AppShell>{children}</AppShell>
      </MantineProvider>
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

All what we did is we added the theme provider to be wrapping our children, which is literally the entire application.

The AppShell Component will just make some paddings around the content, you can skip it if you like.

Now you should see something like this:

Home Page

Bonus Tip

You can change the color scheme of Mantine by setting colorScheme to dark.

import { AppShell, MantineProvider } from "@mantine/core";
import { BasicComponentProps } from "../../utils/types";

/**
 * The root should be used with react-router's configuration for rootComponent.
 * So it will wrap the entire app.
 * It can be useful for single operations as this component will only render once in the entire application life cycle.
 * You may for instance fetch settings from the server before loading the app or a Bearer token to work with the API.
 */
export default function Root({ children }: BasicComponentProps) {
  return (
    <>
      <MantineProvider
        theme={{
          colorScheme: "dark",
        }}
        withGlobalStyles
        withNormalizeCSS>
        <AppShell>{children}</AppShell>
      </MantineProvider>
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

Now it will look like this:

Dark Theme

You can smartly detect if the user (like me) prefers dark mode and is setting it in his/her device using userPrefersDarkMode utility from Mongez Dom.

import { AppShell, MantineProvider } from "@mantine/core";
import { userPrefersDarkMode } from "@mongez/dom";
import { BasicComponentProps } from "../../utils/types";

/**
 * The root should be used with react-router's configuration for rootComponent.
 * So it will wrap the entire app.
 * It can be useful for single operations as this component will only render once in the entire application life cycle.
 * You may for instance fetch settings from the server before loading the app or a Bearer token to work with the API.
 */
export default function Root({ children }: BasicComponentProps) {
  return (
    <>
      <MantineProvider
        theme={{
          colorScheme: userPrefersDarkMode() ? "dark" : "light",
        }}
        withGlobalStyles
        withNormalizeCSS>
        <AppShell>{children}</AppShell>
      </MantineProvider>
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

Now it will appear based on the user's device default theme mode.

For the demo purpose, we'll stick to the light mode for now, maybe we change it later.

Heading back to our file manager component.

File Manager In Modal

Now we'll add wrap our file manager in a Modal so we can open and close it from the parent component.

// FileManager.tsx
import { Modal } from "@mantine/core";
import { FileManagerProps } from "./FileManager.types";

export default function FileManager({ open, onClose }: FileManagerProps) {
  return (
    <>
      <Modal size="xl" opened={open} onClose={onClose}>
        <h1>File Manager</h1>
      </Modal>
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

I just added the modal around simple content which pops with the File Manager keywords and set the size to be xl.

Now let's add a button in the home page to open the file manager.

// HomePage.tsx
import { Button } from "@mantine/core";
import Helmet from "@mongez/react-helmet";
import FileManager from "app/file-manager/components/FileManager";
import { useState } from "react";

export default function HomePage() {
  const [openFileManager, setOpenFileManager] = useState(false);

  return (
    <>
      <Helmet title="home" appendAppName={false} />
      <h1>Welcome To Home Page</h1>
      <Button
        onClick={() => setOpenFileManager(true)}
        variant="gradient"
        gradient={{ from: "red", to: "orange" }}>
        Open File Manager
      </Button>
      <FileManager
        open={openFileManager}
        onClose={() => setOpenFileManager(false)}
      />
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

I used a nice Gradient Button to open the file manager.

Now you should see this in your browser

Home Page

And when you click on the button, you should see this

File Manager Modal

We are now ready to establish our file manager skelton, which will be in our next tutorial.

Article Repository

You can see chapter files in Github Repository

Don't forget the main branch has the latest updated code.

Salam.

Top comments (0)