DEV Community

Cover image for Let's create a React File Manager Chapter XX: Creating Create Directory Button
Hasan Zohdy
Hasan Zohdy

Posted on

Let's create a React File Manager Chapter XX: Creating Create Directory Button

So now we're good with the toolbar and actions so far, so let's create the create directory button and action.

The UI

The UI is pretty simple, we'll have a button with a plus icon, and when the user clicks on it, we'll show a modal with a form to create a new directory.

// components/Toolbar/Buttons/CreateDirectoryButton.tsx export default function CreateDirectoryButton() { return <div>CreateDirectoryButton</div>; } 
Enter fullscreen mode Exit fullscreen mode

Now let's import it in our toolbar.

// components/Toolbar/Toolbar.tsx import { Grid } from "@mantine/core"; import CreateDirectoryButton from "./Buttons/CreateDirectoryButton"; import HomeDirectoryButton from "./Buttons/HomeDirectoryButton"; import { ToolbarWrapper } from "./Toolbar.styles"; const buttons = [ HomeDirectoryButton, CreateDirectoryButton, ]; export default function Toolbar() { return ( <> <ToolbarWrapper shadow="sm"> <Grid> {buttons.map((Button, index) => ( <Button key={index} /> ))} </Grid> </ToolbarWrapper> </> ); } 
Enter fullscreen mode Exit fullscreen mode

Now it look like this

File Manager

Now let's enhance it, we'll actually copy/paste the HomeDirectoryButton and change it to our needs.

// components/Toolbar/Buttons/CreateDirectoryButton.tsximport { Text, ThemeIcon, Tooltip, useMantineTheme } from "@mantine/core"; import { IconCirclePlus } from "@tabler/icons"; import { ToolbarButtonText, ToolBarButtonWrapper, ToolbarIcon, } from "../Toolbar.styles"; export default function CreateDirectoryButton() { const theme = useMantineTheme(); return ( <Tooltip label={"Create New Directory"} position="bottom" transition="slide-up"> <ToolBarButtonWrapper> <ToolbarIcon variant="subtle"> <ThemeIcon variant="gradient" gradient={{ from: "indigo", to: "cyan" }}> <IconCirclePlus size={18} color={theme.white} /> </ThemeIcon> </ToolbarIcon> <ToolbarButtonText> <Text color="blue">Directory</Text> </ToolbarButtonText> </ToolBarButtonWrapper> </Tooltip> ); } 
Enter fullscreen mode Exit fullscreen mode

I know its not the best look but still not that bad ๐Ÿ˜

The Modal Popup

So now we want to create a popup to display a form to create the directory, so let's create a new component for that.

// components/Toolbar/Modals/CreateDirectoryModal.tsx import { Modal } from "@mantine/core"; export type CreateDirectoryModalProps = { open: boolean; onClose: () => void; }; export default function CreateDirectoryModal({ open, onClose, }: CreateDirectoryModalProps) { return ( <Modal opened={open} onClose={onClose}> <div>Modal content</div> </Modal> ); } 
Enter fullscreen mode Exit fullscreen mode

Now we create an open/close state in our button component and import our modal.

// components/Toolbar/Buttons/CreateDirectoryButton.tsx import { Text, ThemeIcon, Tooltip, useMantineTheme } from "@mantine/core"; import { IconCirclePlus } from "@tabler/icons"; import { useState } from "react"; import { ToolbarButtonText, ToolBarButtonWrapper, ToolbarIcon, } from "../Toolbar.styles"; import CreateDirectoryModal from "./CreateDirectoryModal"; export default function CreateDirectoryButton() { const theme = useMantineTheme(); const [openModal, setOpenModal] = useState(false); return ( <> <Tooltip label={"Create New Directory"} position="bottom" transition="slide-up"> <ToolBarButtonWrapper onClick={() => setOpenModal(true)}> <ToolbarIcon variant="subtle"> <ThemeIcon variant="gradient" gradient={{ from: "indigo", to: "cyan" }}> <IconCirclePlus size={18} color={theme.white} /> </ThemeIcon> </ToolbarIcon> <ToolbarButtonText> <Text color="blue">Directory</Text> </ToolbarButtonText> </ToolBarButtonWrapper> </Tooltip> <CreateDirectoryModal open={openModal} onClose={() => setOpenModal(false)} /> </> ); } 
Enter fullscreen mode Exit fullscreen mode

Now let's try it and click on the button, it should open the modal.

Create Directory Modal Popup

Let's update or modal to have a form.

// components/Toolbar/Modals/CreateDirectoryModal.tsx import { Modal } from "@mantine/core"; import { Form } from "@mongez/react-form"; import SubmitButton from "design-system/components/Form/SubmitButton"; import TextInput from "design-system/components/Form/TextInput"; import { useKernel } from "../../../hooks"; export type CreateDirectoryModalProps = { open: boolean; onClose: () => void; }; export default function CreateDirectoryModal({ open, onClose, }: CreateDirectoryModalProps) { const kernel = useKernel(); return ( <Modal title={<strong>{kernel.currentDirectoryNode?.path}</strong>} opened={open} trapFocus={false} onClose={onClose}> <Form> <h2>Create New Directory</h2> <TextInput name="name" required autoFocus placeholder="Please Enter Directory Name" /> <div style={{ textAlign: "end", marginTop: "1.5rem", }}> <SubmitButton>Create</SubmitButton> </div> </Form> </Modal> ); } 
Enter fullscreen mode Exit fullscreen mode

We imported the form component to manage the form submission and validation also we imported the SubmitButton and TextInput to create the form.

Now it look like this

File Manager

Now let's update the SubmitButton UI to be using Mantine button.

// design-system/components/Form/SubmitButton.tsx // ๐Ÿ‘‡๐Ÿป we import the button from Mantine import { Button } from "@mantine/core"; import { useForm } from "@mongez/react-form"; import { useEffect, useState } from "react"; import Loader from "./../Indicators/Loader"; type SubmitButtonProps = { children: React.ReactNode; [key: string]: any; }; export default function SubmitButton({ children, ...props }: SubmitButtonProps) { const [isSubmitting, submitting] = useState(false); const [isDisabled, disable] = useState(false); const formProvider = useForm(); useEffect(() => { if (!formProvider) return; const onSubmit = formProvider.form.on("submit", () => { submitting(formProvider.form.isSubmitting()); disable(formProvider.form.isSubmitting()); }); const inValidControls = formProvider.form.on("invalidControls", () => { disable(true); }); const validControl = formProvider.form.on("validControls", () => { disable(false); }); return () => { onSubmit.unsubscribe(); validControl.unsubscribe(); inValidControls.unsubscribe(); }; }, [formProvider]); return ( <> // ๐Ÿ‘‡๐Ÿป we use the Mantine button instead of the BaseButton <Button // ๐Ÿ‘‡๐Ÿป we use the gradient variant variant="gradient" gradient={{ from: "blue", to: "cyan" }} type="submit" // ๐Ÿ‘‡๐Ÿป we use the loading prop to show the loader loading={isSubmitting} {...props} disabled={isDisabled || isSubmitting}> {children} </Button> </> ); } 
Enter fullscreen mode Exit fullscreen mode

That's for the submit button, we won't do any more.

Now let's update the text input to be using Mantine text input.

// design-system/components/Form/BaseInput.tsx // ๐Ÿ‘‡๐Ÿป we import the Mantine text input import { Input } from "@mantine/core"; import { FormInputProps, useFormInput } from "@mongez/react-form"; import { requiredRule } from "@mongez/validator"; import InputError from "./InputError"; import InputLabel from "./InputLabel"; export default function BaseInput(props: FormInputProps) { const { name, id, value, label, placeholder, required, onChange, onBlur, error, // ๐Ÿ‘‡๐Ÿป extract the autoFocus prop as well to allow focusing on the input autoFocus, otherProps, } = useFormInput(props); return ( <> <div className="form-control"> <InputLabel htmlFor={id} required={required}> {label} </InputLabel> // ๐Ÿ‘‡๐Ÿป we use the Mantine text input instead of the native input <Input id={id} name={name} placeholder={placeholder as string} onChange={onChange} onBlur={onBlur as any} value={value} // ๐Ÿ‘‡๐Ÿป we use the autoFocus prop to focus on the input autoFocus={autoFocus} // ๐Ÿ‘‡๐Ÿป we use the invalid prop to show the input has error invalid={error !== null} {...otherProps} /> {error && <InputError error={error} />} </div> </> ); } BaseInput.defaultProps = { type: "text", rules: [requiredRule], }; 
Enter fullscreen mode Exit fullscreen mode

Now our final modal look like this

Directory Modal

The Action

So we're done now with the modal, now let's submit the form and create the directory.

import { Modal } from "@mantine/core"; import { Form, FormInterface } from "@mongez/react-form"; import SubmitButton from "design-system/components/Form/SubmitButton"; import TextInput from "design-system/components/Form/TextInput"; import React from "react"; import { useKernel } from "../../../hooks"; export type CreateDirectoryModalProps = { open: boolean; onClose: () => void; }; export default function CreateDirectoryModal({ open, onClose, }: CreateDirectoryModalProps) { const kernel = useKernel(); // ๐Ÿ‘‡๐Ÿป we use the onSubmit prop to submit the form const submitForm = (e: React.FormEvent, form: FormInterface) => { } return ( <Modal title={<strong>{kernel.currentDirectoryNode?.path}</strong>} opened={open} trapFocus={false} onClose={onClose}> // ๐Ÿ‘‡๐Ÿป we use the onSubmit prop to submit the form <Form onSubmit={submitForm}> <h2>Create New Directory</h2> <TextInput name="name" required autoFocus placeholder="Please Enter Directory Name" /> <div style={{ textAlign: "end", marginTop: "1.5rem", }}> <SubmitButton>Create</SubmitButton> </div> </Form> </Modal> ); } 
Enter fullscreen mode Exit fullscreen mode

The good thing here is the form submission will not occur until the input is filled, otherwise an error will be displayed.

Now we need only to get the input value so we don't need to send the entire form elements.

// ๐Ÿ‘‡๐Ÿป we use the onSubmit prop to submit the form const submitForm = (e: React.FormEvent, form: FormInterface) => { const directoryName = form.value("name"); kernel.actions.createDirectory(directoryName); }; 
Enter fullscreen mode Exit fullscreen mode

So we can get the input value using the input name, now let's pass it to our createDirectory action and log it for now.

Now once we submit the form, we'll see in the console the input value, so if we type App, we shall see create directory App in the console.

Let's make another small change, we'll also pass the directory path that we need to create this directory in.

// createDirectory.tsx import Kernel from "../Kernel"; export default function createDirectory(kernel: Kernel) { return function create(directoryName: string, directoryPath: string = kernel.currentDirectoryNode?.path as string) { console.log("create directory", directoryName); }; } 
Enter fullscreen mode Exit fullscreen mode

So we can pass the second parameter the directory path, but we'll set the current directory path to be the default value.

Next Chapter

In the next chapter, we'll create the directory in the backend first then we implement it in the React Project.

Article Repository

You can see chapter files in Github Repository

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

Tell me where you are now

If you're following up with me this series, tell me where are you now and what you're struggling with, i'll try to help you as much as i can.

Salam.

Top comments (0)