Skip to content

Commit bef1ea5

Browse files
author
Igor Aralov
committed
[Паттерны] Отрефакторил компоненты используя реакт паттерны
1 parent 03a7bb0 commit bef1ea5

File tree

12 files changed

+161
-118
lines changed

12 files changed

+161
-118
lines changed

src/pages/OperationForm/OperationForm.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ export const OperationForm = () => {
3434
};
3535

3636
return (
37-
<RegularForm title="Операция" onSubmit={handleSubmit(onSubmit)}>
37+
<RegularForm onSubmit={handleSubmit(onSubmit)}>
38+
<RegularForm.Title>Операция</RegularForm.Title>
3839
<FormSelectField
3940
name="type"
4041
options={[costOperationOption, profitOperationOption]}

src/pages/ProductForm/ProductForm.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ export const ProductForm = () => {
2323
};
2424

2525
return (
26-
<RegularForm title="Товар" onSubmit={handleSubmit(onSubmit)}>
26+
<RegularForm onSubmit={handleSubmit(onSubmit)}>
27+
<RegularForm.Title>Товар</RegularForm.Title>
2728
<FormInputField name="name" register={register} type="text" errors={errors.name}>
2829
Название
2930
</FormInputField>

src/pages/ProfileForm/ProfileForm.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ const ChangeProfileForm = () => {
3030
};
3131

3232
return (
33-
<RegularForm title="Изменить профиль" onSubmit={handleSubmit(onSubmit)}>
33+
<RegularForm onSubmit={handleSubmit(onSubmit)}>
34+
<RegularForm.Title>Изменить профиль</RegularForm.Title>
3435
<FormInputField name="name" register={register} type="text" errors={errors.name}>
3536
Псевдоним
3637
</FormInputField>
@@ -57,7 +58,8 @@ const ChangePasswordForm = () => {
5758
};
5859

5960
return (
60-
<RegularForm title="Изменить пароль" onSubmit={handleSubmit(onSubmit)}>
61+
<RegularForm onSubmit={handleSubmit(onSubmit)}>
62+
<RegularForm.Title>Изменить пароль</RegularForm.Title>
6163
<FormInputField name="password" register={register} type="password" errors={errors.password}>
6264
Пароль
6365
</FormInputField>

src/pages/SignInForm/SignInForm.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ export const SignInForm = () => {
2525
};
2626

2727
return (
28-
<RegularForm title="Войти" onSubmit={handleSubmit(onSubmit)}>
28+
<RegularForm onSubmit={handleSubmit(onSubmit)}>
29+
<RegularForm.Title>Войти</RegularForm.Title>
2930
<FormInputField name="email" register={register} type="email" errors={errors.email}>
3031
Email
3132
</FormInputField>

src/pages/SignUpForm/SignUpForm.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ export const SignUpForm = () => {
2525
};
2626

2727
return (
28-
<RegularForm title="Зарегистрироваться" onSubmit={handleSubmit(onSubmit)}>
28+
<RegularForm onSubmit={handleSubmit(onSubmit)}>
29+
<RegularForm.Title>Зарегистрироваться</RegularForm.Title>
2930
<FormInputField name="email" register={register} type="email" errors={errors.email}>
3031
Email
3132
</FormInputField>

src/shared/collapse/Collapse.tsx

Lines changed: 6 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,28 @@
1-
import React, { useState, useRef, useEffect, ReactNode } from 'react';
1+
import React, { useState, ReactNode } from 'react';
2+
import { useCollapseHeight } from './hooks/useCollapseHeight';
23
import s from './Collapse.module.scss';
34

45
type CollapseProps = {
56
title: string;
67
children: ReactNode;
78
};
89

9-
export const Collapse = ({ title, children }: CollapseProps) => {
10+
export const Collapse: React.FC<CollapseProps> = ({ title, children }) => {
1011
const [isOpen, setIsOpen] = useState(false);
11-
const [height, setHeight] = useState(0);
12-
const contentRef = useRef<HTMLDivElement>(null);
12+
const { height, contentRef } = useCollapseHeight(isOpen);
1313

1414
const toggleCollapse = () => {
15-
setIsOpen(!isOpen);
15+
setIsOpen((prev) => !prev);
1616
};
1717

18-
useEffect(() => {
19-
const resizeObserver = new ResizeObserver((entries) =>
20-
entries.forEach((entry) => setHeight(entry.borderBoxSize[0].blockSize))
21-
);
22-
23-
const currentContentRef = contentRef.current;
24-
25-
currentContentRef && resizeObserver.observe(currentContentRef);
26-
27-
return () => {
28-
currentContentRef && resizeObserver.unobserve(currentContentRef);
29-
};
30-
}, []);
31-
3218
return (
3319
<div className={s['collapse-container']}>
3420
<button className={s['collapse-button']} onClick={toggleCollapse}>
3521
{title}
3622
</button>
3723
<div
3824
style={{
39-
height: `${isOpen ? height : 0}px`,
25+
height: isOpen ? `${height}px` : '0px',
4026
overflow: 'hidden',
4127
transition: 'height 300ms ease',
4228
}}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import React, { useEffect, useRef, useState } from "react";
2+
3+
export const useCollapseHeight = (isOpen: boolean) => {
4+
const [height, setHeight] = useState(0);
5+
const contentRef = useRef<HTMLDivElement>(null);
6+
7+
useEffect(() => {
8+
const resizeObserver = new ResizeObserver((entries) => {
9+
entries.forEach((entry) => setHeight(entry.borderBoxSize[0].blockSize));
10+
});
11+
12+
const currentContentRef = contentRef.current;
13+
14+
if (currentContentRef) {
15+
resizeObserver.observe(currentContentRef);
16+
}
17+
18+
return () => {
19+
if (currentContentRef) {
20+
resizeObserver.unobserve(currentContentRef);
21+
}
22+
};
23+
}, []);
24+
25+
return { height, contentRef };
26+
};

src/shared/forms/RegularForm/RegularForm.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,18 @@ import s from './RegularForm.module.scss';
44
type RegularFormProps = {
55
onSubmit: FormEventHandler<HTMLFormElement>;
66
children: ReactNode;
7-
title?: string;
87
};
98

10-
export const RegularForm = ({ onSubmit, title, children }: RegularFormProps) => {
9+
const RegularForm = ({ onSubmit, children }: RegularFormProps) => {
1110
return (
1211
<form className={s.form} onSubmit={onSubmit}>
13-
{title && <h2 className={s.title}>{title}</h2>}
1412
{children}
1513
</form>
1614
);
1715
};
16+
17+
const Title = ({ children }: { children: ReactNode }) => <h2 className={s.title}>{children}</h2>;
18+
19+
RegularForm.Title = Title;
20+
21+
export { RegularForm };

src/shared/tooltip-buttons/TooltipButtons.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import React, { ReactNode } from 'react';
2-
import { Position, Tooltip } from '../tooltip/Tooltip';
2+
import { Position } from '../tooltip/utils/tooltipPosition';
3+
import {Tooltip} from "../tooltip/Tooltip";
34
import { Button } from '../button/Button';
45
import s from './TooltipButtons.module.scss';
56

src/shared/tooltip/Tooltip.tsx

Lines changed: 7 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,10 @@
1-
import React, { useState, useRef, useLayoutEffect, ReactNode } from 'react';
1+
import React, { ReactNode } from 'react';
22
import { createPortal } from 'react-dom';
3+
import { useTooltip } from './hooks/useTooltip';
4+
import { Position } from './utils/tooltipPosition';
35
import cn from 'clsx';
46
import s from './Tooltip.module.scss';
57

6-
type Coords = {
7-
top: number;
8-
left: number;
9-
};
10-
11-
type CoordProps = {
12-
targetRect: DOMRect;
13-
tooltipRect: DOMRect;
14-
offset: number;
15-
};
16-
17-
export type Position = 'top' | 'bottom' | 'left' | 'right';
18-
19-
type PositionMap = Record<Position, (props: CoordProps) => Coords>;
20-
21-
const getCenterCoord = (primary: number, secondary: number) => (primary - secondary) / 2;
22-
23-
const YLeft = (primary: DOMRect, secondary: DOMRect) =>
24-
primary.left + window.scrollX + getCenterCoord(primary.width, secondary.width);
25-
const XTop = (primary: DOMRect, secondary: DOMRect) =>
26-
primary.top + window.scrollY + getCenterCoord(primary.height, secondary.height);
27-
28-
const positionMap: PositionMap = {
29-
top: ({ targetRect, tooltipRect, offset }) => ({
30-
top: targetRect.top + window.scrollY - tooltipRect.height - offset,
31-
left: targetRect.left + window.scrollX + getCenterCoord(targetRect.width, tooltipRect.width),
32-
}),
33-
34-
bottom: ({ targetRect, tooltipRect, offset }) => ({
35-
top: targetRect.bottom + window.scrollY + offset,
36-
left: YLeft(targetRect, tooltipRect),
37-
}),
38-
left: ({ targetRect, tooltipRect, offset }) => ({
39-
top: XTop(targetRect, tooltipRect),
40-
left: targetRect.left + window.scrollX - tooltipRect.width - offset,
41-
}),
42-
43-
right: ({ targetRect, tooltipRect, offset }) => ({
44-
top: XTop(targetRect, tooltipRect),
45-
left: targetRect.left + window.scrollX + targetRect.width + offset,
46-
}),
47-
};
48-
498
type TooltipProps = {
509
children: ReactNode;
5110
content: ReactNode;
@@ -54,50 +13,10 @@ type TooltipProps = {
5413
};
5514

5615
export const Tooltip = ({ children, content, duration = 1000, position = 'bottom' }: TooltipProps) => {
57-
const [visible, setVisible] = useState(false);
58-
const [mounted, setMounted] = useState(false);
59-
const [coords, setCoords] = useState({ top: 0, left: 0 });
60-
const tooltipRef = useRef<HTMLDivElement>(null);
61-
const targetRef = useRef<HTMLDivElement>(null);
62-
const timerRef = useRef(null);
63-
const mountTimerRef = useRef(null);
64-
65-
const mountTimer = 50;
66-
67-
const clearTimeouts = () => {
68-
timerRef.current && clearTimeout(timerRef.current);
69-
mountTimerRef.current && clearTimeout(mountTimerRef.current);
70-
};
71-
72-
const handleMouseEnter = () => {
73-
clearTimeouts();
74-
setMounted(true);
75-
mountTimerRef.current = setTimeout(() => setVisible(true), mountTimer);
76-
};
77-
78-
const handleMouseLeave = () => {
79-
setVisible(false);
80-
timerRef.current = setTimeout(() => setMounted(false), duration + mountTimer);
81-
};
82-
83-
useLayoutEffect(() => {
84-
const target = targetRef.current;
85-
const tooltip = tooltipRef.current;
86-
87-
if (!target || !tooltip) return;
88-
89-
if (mounted) {
90-
tooltipRef.current?.style.setProperty('--tooltip-animation-ms', `${duration + mountTimer}ms`);
91-
const targetRect = target.getBoundingClientRect();
92-
const tooltipRect = tooltip.getBoundingClientRect();
93-
const calcPosition = positionMap[position] ?? positionMap['bottom'];
94-
setCoords(calcPosition({ targetRect, tooltipRect, offset: 5 }));
95-
}
96-
97-
return () => {
98-
clearTimeouts();
99-
};
100-
}, [mounted]);
16+
const { visible, mounted, coords, tooltipRef, targetRef, handleMouseEnter, handleMouseLeave } = useTooltip(
17+
position,
18+
duration
19+
);
10120

10221
return (
10322
<>

0 commit comments

Comments
 (0)