diff --git a/.eslintrc.js b/.eslintrc.js
index 4f77473..96b990b 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -7,7 +7,6 @@ module.exports = {
extends: [
'plugin:react/recommended',
'airbnb',
- // 'plugin:i18next/recommended',
],
parser: '@typescript-eslint/parser',
parserOptions: {
@@ -21,6 +20,7 @@ module.exports = {
'react',
'@typescript-eslint',
'i18next',
+ 'react-hooks',
],
rules: {
'react/jsx-indent': [2, 4],
@@ -42,24 +42,13 @@ module.exports = {
'import/no-extraneous-dependencies': 'off',
'no-underscore-dangle': 'off',
'no-tabs': 'off',
- // 'i18next/no-literal-string': [
- // 'error',
- // {
- // markupOnly: true,
- // ignoreAttribute: ['data-testid', 'to'],
- // },
- // ],
+ 'jsx-a11y/no-static-element-interactions': 'off',
+ 'jsx-a11y/click-events-have-key-events': 'off',
+ 'react-hooks/rules-of-hooks': 'error', // Checks rules of Hooks
+ 'react-hooks/exhaustive-deps': 'error', // Checks effect dependencies
'max-len': ['error', { ignoreComments: true, code: 180 }],
},
globals: {
__IS_DEV__: true,
},
- // overrides: [
- // {
- // files: ['**/src/**/*.test.{ts,tsx}'],
- // rules: {
- // 'i18next/no-literal-string': 'off',
- // },
- // },
- // ],
};
diff --git a/src/app/App.tsx b/src/app/App.tsx
index 52d948f..420b66a 100644
--- a/src/app/App.tsx
+++ b/src/app/App.tsx
@@ -1,18 +1,30 @@
-import React, { Suspense, useEffect } from 'react';
+import React, { Suspense, useState } from 'react';
import './styles/index.scss';
import { classNames } from 'shared/lib/classNames/classNames';
import { useTheme } from 'app/providers/ThemeProvider';
import { AppRouter } from 'app/providers/router';
import { Navbar } from 'widgets/Navbar';
import { Sidebar } from 'widgets/Sidebar';
+import { Modal } from 'shared/ui/Modal/Modal';
function App() {
const { theme } = useTheme();
+ const [isOpen, setIsOpen] = useState(false);
+
return (
+ setIsOpen(true)}>
+ toggle
+
+ setIsOpen(false)}>
+ Lorem ipsum dolor sit amet, consectetur adipisicing elit. A ab
+ accusantium aliquid architecto autem consequuntur debitis et facere,
+ laudantium modi nostrum nulla obcaecati odit quas quod sapiente,
+ veritatis? Illo, veritatis.
+
diff --git a/src/app/styles/variables/global.scss b/src/app/styles/variables/global.scss
index b7ee4c9..2f256bc 100644
--- a/src/app/styles/variables/global.scss
+++ b/src/app/styles/variables/global.scss
@@ -14,4 +14,10 @@
--navbar-height: 50px;
--sidebar-width: 300px;
--sidebar-width-collapsed: 80px;
+
+ // z - Индексы
+ --modal-z-index: 10;
+
+ // Цвета
+ --overlay-color: rgba(0 0 0 / 60%);
}
diff --git a/src/shared/ui/Modal/Modal.module.scss b/src/shared/ui/Modal/Modal.module.scss
new file mode 100644
index 0000000..acb14cf
--- /dev/null
+++ b/src/shared/ui/Modal/Modal.module.scss
@@ -0,0 +1,44 @@
+.Modal {
+ position: fixed;
+ top: 0;
+ bottom: 0;
+ right: 0;
+ left: 0;
+ opacity: 0;
+ pointer-events: none;
+ z-index: -1;
+}
+
+.overlay {
+ width: 100%;
+ height: 100%;
+ background: var(--overlay-color);
+ display: flex;
+ justify-content: center;
+ align-items: center;
+}
+
+.content {
+ padding: 20px;
+ border-radius: 12px;
+ background: var(--bg-color);
+ transition: 0.3s transform;
+ transform: scale(0.5);
+ max-width: 60%;
+}
+
+.opened {
+ opacity: 1;
+ pointer-events: auto;
+ z-index: var(--modal-z-index);
+
+ .content {
+ transform: scale(1);
+ }
+}
+
+.isClosing {
+ .content {
+ transform: scale(0.2);
+ }
+}
diff --git a/src/shared/ui/Modal/Modal.tsx b/src/shared/ui/Modal/Modal.tsx
new file mode 100644
index 0000000..0441c9b
--- /dev/null
+++ b/src/shared/ui/Modal/Modal.tsx
@@ -0,0 +1,76 @@
+import React, {
+ ReactNode,
+ useCallback,
+ useEffect,
+ useRef,
+ useState,
+} from 'react';
+import { classNames } from 'shared/lib/classNames/classNames';
+import cls from './Modal.module.scss';
+
+interface ModalProps {
+ className?: string;
+ children?: ReactNode;
+ isOpen?: boolean;
+ onClose?: () => void;
+}
+
+const ANIMATION_DELAY = 300;
+
+export const Modal = (props: ModalProps) => {
+ const {
+ className,
+ children,
+ isOpen,
+ onClose,
+ } = props;
+
+ const [isClosing, setIsClosing] = useState(false);
+ const timerRef = useRef
>();
+
+ const closeHandler = useCallback(() => {
+ if (onClose) {
+ setIsClosing(true);
+ timerRef.current = setTimeout(() => {
+ onClose();
+ setIsClosing(false);
+ }, ANIMATION_DELAY);
+ }
+ }, [onClose]);
+
+ const onKeyDown = useCallback((e: KeyboardEvent) => {
+ if (e.key === 'Escape') {
+ closeHandler();
+ }
+ }, [closeHandler]);
+
+ useEffect(() => {
+ if (isOpen) {
+ window.addEventListener('keydown', onKeyDown);
+ }
+
+ return (() => {
+ clearTimeout(timerRef.current);
+ window.removeEventListener('keydown', onKeyDown);
+ });
+ }, [isOpen, onKeyDown]);
+
+ const mods:Record = {
+ [cls.opened]: isOpen,
+ [cls.isClosing]: isClosing,
+ };
+
+ const onContentClick = (e: React.MouseEvent) => {
+ e.stopPropagation();
+ };
+
+ return (
+
+ );
+};