From 824a79a1175027772fc1ea879cf2e2d0f40e5fc1 Mon Sep 17 00:00:00 2001 From: aurinex Date: Tue, 8 Jul 2025 23:46:22 +0500 Subject: [PATCH] add anchor / delete linter error / small revision --- src/App.tsx | 9 ++- src/components/Feedback.tsx | 36 ++++++--- src/components/Header.tsx | 108 +++++++++++++++++++-------- src/pages/MainPage.tsx | 143 ++++++++++++++++++++++++++++++------ src/utils/scrollUtils.ts | 21 ++++++ tsconfig.app.json | 1 - 6 files changed, 254 insertions(+), 64 deletions(-) create mode 100644 src/utils/scrollUtils.ts diff --git a/src/App.tsx b/src/App.tsx index af22041..145ca18 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -7,7 +7,14 @@ function App() { <>
- } /> + + + + } + /> ) diff --git a/src/components/Feedback.tsx b/src/components/Feedback.tsx index fc80eca..b883f9b 100644 --- a/src/components/Feedback.tsx +++ b/src/components/Feedback.tsx @@ -1,6 +1,5 @@ import React, { useState } from "react"; import { - Dialog, TextField, Button, IconButton, @@ -10,7 +9,6 @@ import { RadioGroup, FormControlLabel, FormControl, - InputAdornment, InputLabel, OutlinedInput, Checkbox, @@ -37,7 +35,7 @@ const TextMaskCustom = React.forwardRef( _: /[0-9]/, }} inputRef={ref} - onAccept={(value: any) => + onAccept={(value: string) => onChange({ target: { name: props.name, value } }) } overwrite @@ -52,9 +50,10 @@ const TextMaskCustom = React.forwardRef( interface FeedbackProps { open: boolean; onClose: () => void; + handleScrollToAnchor?: (anchor: string) => void; } -const Feedback: React.FC = ({ open, onClose }) => { +const Feedback: React.FC = ({ open, onClose, handleScrollToAnchor }) => { const [name, setName] = useState(""); const [phone, setPhone] = useState("+7"); const [country, setCountry] = useState("Европа"); @@ -63,6 +62,14 @@ const Feedback: React.FC = ({ open, onClose }) => { const [agreeToPolicy, setAgreeToPolicy] = useState(false); const { isMobile } = useResponsive(); + React.useEffect(() => { + if (open) { + window.history.pushState(null, '', '#feedback'); + } else if (!open && !isMobile && handleScrollToAnchor) { + handleScrollToAnchor('#main'); + } + }, [open, handleScrollToAnchor]); + const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); if (!agreeToPolicy) { @@ -71,9 +78,12 @@ const Feedback: React.FC = ({ open, onClose }) => { ); return; } - // логика отправки формы console.log({ name, phone, country, budget, description, agreeToPolicy }); + onClose(); + if (handleScrollToAnchor) { + handleScrollToAnchor('#main'); + } }; const textFieldSx = { @@ -129,8 +139,13 @@ const Feedback: React.FC = ({ open, onClose }) => { justifyContent: "center", zIndex: 1300, // высокий z-index как у диалога bgcolor: "rgba(0, 0, 0, 0.5)", // затемнение фона + color: "black", }} - onClick={onClose} // закрытие при клике на фон + onClick={() => { + onClose(); + if (handleScrollToAnchor) handleScrollToAnchor('#main'); + }} + id="feedback" > = ({ open, onClose }) => { onClick={(e) => e.stopPropagation()} // предотвращаем закрытие при клике на контент > - { + onClose(); + if (handleScrollToAnchor) handleScrollToAnchor('#main'); + }} sx={{ position: "absolute", right: "1vw", @@ -226,7 +244,7 @@ const Feedback: React.FC = ({ open, onClose }) => { id="phone-input" value={phone} onChange={handlePhoneChange} - inputComponent={TextMaskCustom as any} + inputComponent={TextMaskCustom as unknown as React.ComponentType} label="Ваш телефон*" notched sx={{ diff --git a/src/components/Header.tsx b/src/components/Header.tsx index 89e82d0..06fa687 100644 --- a/src/components/Header.tsx +++ b/src/components/Header.tsx @@ -1,4 +1,4 @@ -import React, { useState } from "react"; +import React, { useState, useEffect } from "react"; import { Box, Typography, @@ -7,38 +7,62 @@ import { List, ListItem, Button, + Link, } from "@mui/material"; import MenuIcon from "@mui/icons-material/Menu"; import logo from "../assets/icon/autobro.png"; import { useResponsive } from "../theme/useResponsive"; import Feedback from "./Feedback"; - +import { scrollToAnchor } from "../utils/scrollUtils"; const Header = () => { const { isMobile } = useResponsive(); const [drawerOpen, setDrawerOpen] = useState(false); const [feedbackOpen, setFeedbackOpen] = useState(false); + const [scrolled, setScrolled] = useState(false); const menuItems = [ - "О нас", - "Калькулятор", - "Отзывы", - "Контакты", - "В наличии", - "Команда", - "Доставленные авто", - "Этапы работы", + { title: "О нас", anchor: "#about" }, + { title: "Калькулятор", anchor: "#calculator" }, + { title: "Отзывы", anchor: "#reviews" }, + { title: "Контакты", anchor: "#contacts" }, + { title: "В наличии", anchor: "#available" }, + { title: "Команда", anchor: "#team" }, + { title: "Доставленные авто", anchor: "#delivered" }, + { title: "Этапы работы", anchor: "#stages" }, + { title: " ", anchor: "#main"}, ]; - const toggleDrawer = (open) => (event) => { + // Отслеживание скролла + useEffect(() => { + const handleScroll = () => { + if (window.scrollY > 10) { + setScrolled(true); + } else { + setScrolled(false); + } + }; + + window.addEventListener("scroll", handleScroll); + return () => { + window.removeEventListener("scroll", handleScroll); + }; + }, []); + + const toggleDrawer = (open: boolean) => (event: React.KeyboardEvent | React.MouseEvent) => { if ( event.type === "keydown" && - (event.key === "Tab" || event.key === "Shift") + ((event as React.KeyboardEvent).key === "Tab" || (event as React.KeyboardEvent).key === "Shift") ) { return; } setDrawerOpen(open); }; + // Используем глобальную функцию из utils + const handleScrollToAnchor = (anchor: string) => { + scrollToAnchor(anchor, setDrawerOpen, isMobile); + }; + return ( <> { userSelect: "none", color: "white", padding: isMobile ? "0 4vw" : 0, + position: "fixed", + top: 0, + left: 0, + right: 0, + zIndex: 1000, + transition: "all 0.3s ease", + boxShadow: scrolled ? "0 2px 10px rgba(0,0,0,0.2)" : "none", }} > - logo handleScrollToAnchor("#main")} + sx={{ + width: isMobile ? "17vw" : "9vw", + height: isMobile ? "17vw" : "9vw", + cursor: "pointer", + "&:hover": { scale: 1.1 }, + transition: "all 0.4s ease", }} - /> + > + {isMobile ? ( { ) : ( // Desktop menu menuItems.map((item, index) => ( - handleScrollToAnchor(item.anchor)} + underline="none" sx={{ fontFamily: "Unbounded", - fontSize: "1vw", + fontSize: "1.2vw", cursor: "pointer", + color: "white", + "&:hover": { color: "#C27664" }, + transition: "all 0.3s ease", }} > - {item} - + {item.title} + )) )} + {/* Пустой блок для компенсации фиксированного хедера */} + + {/* Мобильное меню */} {menuItems.map((item, index) => ( - + handleScrollToAnchor(item.anchor)} + sx={{ cursor: "pointer" }} + > - {item} + {item.title} ))} @@ -139,8 +188,7 @@ const Header = () => { - {/* Форма обратной связи */} - setFeedbackOpen(false)} /> + setFeedbackOpen(false)} handleScrollToAnchor={handleScrollToAnchor} /> ); }; diff --git a/src/pages/MainPage.tsx b/src/pages/MainPage.tsx index 977d26f..3cd932b 100644 --- a/src/pages/MainPage.tsx +++ b/src/pages/MainPage.tsx @@ -1,11 +1,17 @@ import React, { useState } from "react"; -import { Button, Container, Box, Typography } from "@mui/material"; +import { Button, Box, Typography, IconButton } from "@mui/material"; import Feedback from "../components/Feedback"; import car from "../../src/assets/icon/car.png"; -import { Gradient } from "@mui/icons-material"; +import { useResponsive } from "../theme/useResponsive"; +import TelegramIcon from '@mui/icons-material/Telegram'; +import VkIcon from '@mui/icons-material/Facebook'; +import WhatsAppIcon from '@mui/icons-material/WhatsApp'; +import { scrollToAnchor } from "../utils/scrollUtils"; function MainPage() { const [feedbackOpen, setFeedbackOpen] = useState(false); + const setDrawerOpen = () => {}; + const { isMobile } = useResponsive(); const handleOpenFeedback = () => { setFeedbackOpen(true); @@ -15,8 +21,12 @@ function MainPage() { setFeedbackOpen(false); }; + const handleScrollToAnchor = (anchor: string) => { + scrollToAnchor(anchor, setDrawerOpen, isMobile); + }; + return ( - + - - - {/* - + + + + + - */} + position: "absolute", + right: "1vw", + top: isMobile ? "20.3vw" : "15.3vw", + display: "flex", + flexDirection: "column", + gap: "1vw", + zIndex: 1, + }}> + + + + + + + + + + + + + + + ); } diff --git a/src/utils/scrollUtils.ts b/src/utils/scrollUtils.ts new file mode 100644 index 0000000..64468e4 --- /dev/null +++ b/src/utils/scrollUtils.ts @@ -0,0 +1,21 @@ +/** + * Плавная прокрутка к указанному якорю + * @param anchor - идентификатор якоря, например "#about" + * @param setDrawerOpen - опциональная функция для закрытия мобильного меню + * @param isMobile - флаг мобильного устройства + */ +export const scrollToAnchor = ( + anchor: string, + setDrawerOpen?: (isOpen: boolean) => void, + isMobile?: boolean +): void => { + const element = document.querySelector(anchor); + if (element) { + element.scrollIntoView({ behavior: "smooth" }); + // Обновляем URL с хешем без перезагрузки страницы + window.history.pushState(null, '', anchor); + } + if (isMobile && setDrawerOpen) { + setDrawerOpen(false); + } +}; \ No newline at end of file diff --git a/tsconfig.app.json b/tsconfig.app.json index 227a6c6..1597d8c 100644 --- a/tsconfig.app.json +++ b/tsconfig.app.json @@ -19,7 +19,6 @@ "strict": true, "noUnusedLocals": true, "noUnusedParameters": true, - "erasableSyntaxOnly": true, "noFallthroughCasesInSwitch": true, "noUncheckedSideEffectImports": true },