Skip to content

Commit

Permalink
feat: add modal component \ react hooks linter
Browse files Browse the repository at this point in the history
  • Loading branch information
TomatoVan committed Nov 6, 2023
1 parent 74b6e7b commit 18a141f
Show file tree
Hide file tree
Showing 5 changed files with 144 additions and 17 deletions.
21 changes: 5 additions & 16 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ module.exports = {
extends: [
'plugin:react/recommended',
'airbnb',
// 'plugin:i18next/recommended',
],
parser: '@typescript-eslint/parser',
parserOptions: {
Expand All @@ -21,6 +20,7 @@ module.exports = {
'react',
'@typescript-eslint',
'i18next',
'react-hooks',
],
rules: {
'react/jsx-indent': [2, 4],
Expand All @@ -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',
// },
// },
// ],
};
14 changes: 13 additions & 1 deletion src/app/App.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className={classNames('app', {}, [theme])}>
<Suspense fallback="">
<Navbar />
<button type="button" onClick={() => setIsOpen(true)}>
toggle
</button>
<Modal isOpen={isOpen} onClose={() => 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.
</Modal>
<div className="content-page">
<Sidebar />
<AppRouter />
Expand Down
6 changes: 6 additions & 0 deletions src/app/styles/variables/global.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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%);
}
44 changes: 44 additions & 0 deletions src/shared/ui/Modal/Modal.module.scss
Original file line number Diff line number Diff line change
@@ -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);
}
}
76 changes: 76 additions & 0 deletions src/shared/ui/Modal/Modal.tsx
Original file line number Diff line number Diff line change
@@ -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<ReturnType<typeof setTimeout>>();

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<string, boolean> = {
[cls.opened]: isOpen,
[cls.isClosing]: isClosing,
};

const onContentClick = (e: React.MouseEvent) => {
e.stopPropagation();
};

return (
<div className={classNames(cls.Modal, mods, [className])}>
<div className={cls.overlay} onClick={closeHandler}>
<div className={cls.content} onClick={onContentClick}>
{children}
</div>
</div>
</div>
);
};

0 comments on commit 18a141f

Please sign in to comment.