Давайте перейдем к той части, которая меня не очень восхищает — работе с UI. Давайте создадим наш компонент FileManager
, но сначала определим, какие реквизиты он принимает.
Для инкапсуляционного поведения я добавлю файл типов FileManager в каталог компонента.
Создайте директорию file-manager/components/FileManager
, затем создайте в ней FileManager.types.ts
.
// 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;
};
не пугайтесь, я просто пытаюсь заставить вас представить себе общую картину, постарайтесь медленно и внимательно читать реквизиты компонентов и комментарии над каждым реквизитом.
На данный момент нам понадобится только два реквизита, open
и onClose
, которые являются единственными необходимыми реквизитами.
// 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;
};
Эти два реквизита отвечают за открытие и закрытие файлового менеджера, так как мы будем открывать его в модальном режиме.
Теперь давайте создадим наш компонент FileManager
.
// file-manager/components/FileManager/FileManager.ts
import { FileManagerProps } from "./FileManager.types";
export default function FileManager({ open, onClose }: FileManagerProps) {
return <div>FileManager</div>;
}
Прежде чем перейти к следующему шагу, создадим файл index
для инкапсуляции импорта файлов из него напрямую.
// file-manager/components/FileManager/index.ts
export { default } from "./FileManager";
export * from "./FileManager.types";
Теперь перейдем на нашу HomePage
и вызовем наш только что созданный компонент FileManager
.
Небольшая модификация, мы также создадим файл компонента
HomePage
для домашней страницы, а не поместим его в индексный файл, затем мы экспортируем его из индекса, как мы это делали в файловом менеджере.
// 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)}
/>
</>
);
}
Мы создали состояние для управления состоянием открытия/закрытия для файлового менеджера, установили его в false
по умолчанию, затем вызвали наш компонент в функции return.
Теперь давайте посмотрим, что будет в браузере, у вас должно получиться что-то вроде этого:
Настройка стилей Mantine
Я не буду останавливаться здесь слишком долго, мы просто сделаем то, что написано в документации, и импортируем Mantine Theme Provider в верхнюю часть нашего приложения, который можно установить в компоненте Root
.
Давайте перейдем в src/apps/front-office/design-system/layouts/Root.tsx
и обновим его следующим образом:
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>
</>
);
}
Все, что мы сделали, это добавили провайдер темы, который будет обертывать наши дочерние компоненты, то есть буквально все приложение.
Компонент AppShell просто сделает некоторые отступы вокруг содержимого, вы можете пропустить это, если хотите.
Теперь вы должны увидеть что-то вроде этого:
Бонусный совет
Вы можете изменить цветовую схему Mantine, установив colorScheme
на 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>
</>
);
}
Теперь это будет выглядеть следующим образом:
Вы можете умно определить, предпочитает ли пользователь (как я) темный режим и устанавливает ли он его на своем устройстве, используя утилиту userPrefersDarkMode
из 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>
</>
);
}
Теперь он будет отображаться в соответствии с режимом темы по умолчанию на устройстве пользователя.
Для демонстрационной цели мы пока будем придерживаться светлого режима, возможно, мы изменим его позже.
Возвращаемся к нашему компоненту файлового менеджера.
Файловый менеджер в модальном окне
Теперь мы обернем наш файловый менеджер в Modal, чтобы мы могли открывать и закрывать его из родительского компонента.
// 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>
</>
);
}
Я просто добавил модал вокруг простого содержимого, которое всплывает с ключевыми словами File Manager
и установил размер xl
.
Теперь давайте добавим кнопку на главной странице, чтобы открыть файловый менеджер.
// 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)}
/>
</>
);
}
Я использовал красивую градиентную кнопку для открытия файлового менеджера.
Теперь вы должны увидеть это в своем браузере
А когда вы нажмете на кнопку, вы увидите следующее
Теперь мы готовы к созданию нашего файлового менеджера skelton, о котором мы расскажем в нашем следующем уроке.
Репозиторий статей
Вы можете увидеть файлы глав в репозитории Github.
Не забывайте, что ветка
main
содержит последний обновленный код.
Салам.