Добавлены настройки для оптимизации изображений в конфигурации Next.js и улучшена логика управления состоянием меню в компоненте Header. Обновлены стили и структура мобильного меню, добавлены данные о городах для отображения телефонных номеров.

This commit is contained in:
Bivekich
2025-04-18 11:23:59 +03:00
parent 1765adc418
commit 6ecc0affb8
3 changed files with 114 additions and 65 deletions

View File

@ -55,6 +55,18 @@ const Header = ({ selectedCity, onCityChange }: HeaderProps) => {
return () => window.removeEventListener('scroll', handleScroll);
}, [lastScrollY]);
// Закрываем меню при клике на ссылку
useEffect(() => {
if (isMenuOpen) {
document.body.style.overflow = 'hidden';
} else {
document.body.style.overflow = 'auto';
}
return () => {
document.body.style.overflow = 'auto';
};
}, [isMenuOpen]);
return (
<>
<motion.header
@ -65,35 +77,38 @@ const Header = ({ selectedCity, onCityChange }: HeaderProps) => {
isVisible ? 'translate-y-0' : '-translate-y-full'
}`}
>
<div className="container mx-auto px-4 py-4">
<div className="container mx-auto px-4 py-3">
<div className="flex items-center justify-between">
{/* Логотип */}
<Link
href="/"
className="flex items-center space-x-2 text-blue-700"
>
<Building className="h-6 w-6" />
<div>
<span className="text-xl font-bold">ЦКЭ</span>
<span className="block text-sm font-normal text-gray-600">
<div className="flex flex-col">
<span className="text-lg sm:text-xl font-bold">ЦКЭ</span>
<span className="block text-xs sm:text-sm font-normal text-gray-600">
Центр комплексных экспертиз
</span>
</div>
</Link>
<div className="flex items-center space-x-2">
{/* Мобильный номер телефона и кнопка меню */}
<div className="flex items-center space-x-4 lg:hidden">
<a
href={`tel:${cityData[selectedCity].phone}`}
className="flex items-center space-x-1 text-gray-600 hover:text-blue-700 lg:hidden"
className="flex items-center text-gray-600 hover:text-blue-700 whitespace-nowrap"
>
<Phone className="h-4 w-4 text-blue-700" />
<span className="text-sm font-medium">
<Phone className="h-4 w-4 text-blue-700 mr-1" />
<span className="text-xs font-medium">
{cityData[selectedCity].phone}
</span>
</a>
<button
className="lg:hidden p-2 hover:bg-gray-100 rounded-lg"
className="p-2 hover:bg-gray-100 rounded-lg"
onClick={() => setIsMenuOpen(!isMenuOpen)}
aria-label={isMenuOpen ? 'Закрыть меню' : 'Открыть меню'}
>
{isMenuOpen ? (
<X className="h-6 w-6 text-gray-600" />
@ -103,6 +118,7 @@ const Header = ({ selectedCity, onCityChange }: HeaderProps) => {
</button>
</div>
{/* Десктопная навигация */}
<nav className="hidden lg:flex items-center space-x-6">
{navigation.map((item) => (
<Link
@ -115,6 +131,7 @@ const Header = ({ selectedCity, onCityChange }: HeaderProps) => {
))}
</nav>
{/* Десктопные контакты и кнопки */}
<div className="hidden lg:flex items-center space-x-4">
<DropdownMenu>
<DropdownMenuTrigger asChild>
@ -158,62 +175,84 @@ const Header = ({ selectedCity, onCityChange }: HeaderProps) => {
</Button>
</div>
</div>
<div
className={`lg:hidden overflow-hidden transition-all duration-300 ${
isMenuOpen ? 'max-h-[500px] mt-4' : 'max-h-0'
}`}
>
<nav className="flex flex-col space-y-4 pb-4">
{navigation.map((item) => (
<Link
key={item.href}
href={item.href}
className="text-gray-600 hover:text-blue-700 transition-colors text-sm py-2"
onClick={() => setIsMenuOpen(false)}
>
{item.name}
</Link>
))}
<div className="pt-4 border-t">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" className="w-full justify-start">
<MapPin className="h-4 w-4 text-blue-700 mr-2" />
<span>{selectedCity}</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem
onClick={() => onCityChange('Москва')}
className="cursor-pointer"
>
Москва
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => onCityChange('Чебоксары')}
className="cursor-pointer"
>
Чебоксары
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<Button
className="w-full bg-blue-700 hover:bg-blue-800 text-white mt-4"
onClick={() => {
setIsModalOpen(true);
setIsMenuOpen(false);
}}
>
Оставить заявку
</Button>
</div>
</nav>
</div>
</div>
</motion.header>
<div className="h-[72px]" />
{/* Выпадающее мобильное меню */}
<motion.div
initial={false}
animate={isMenuOpen ? { opacity: 1, x: 0 } : { opacity: 0, x: '100%' }}
transition={{ duration: 0.3, ease: 'easeInOut' }}
className="fixed inset-y-0 right-0 w-full sm:w-80 bg-white shadow-xl z-50 lg:hidden overflow-y-auto"
style={{ top: '56px' }}
>
<div className="flex flex-col h-full py-8 px-6">
<nav className="flex flex-col space-y-6">
{navigation.map((item) => (
<Link
key={item.href}
href={item.href}
className="text-gray-800 hover:text-blue-700 transition-colors text-lg font-medium"
onClick={() => setIsMenuOpen(false)}
>
{item.name}
</Link>
))}
</nav>
<div className="mt-8 pt-8 border-t border-gray-200">
<p className="text-gray-500 mb-4">Выберите город:</p>
<div className="space-y-3 mb-8">
<button
onClick={() => onCityChange('Москва')}
className={`w-full py-2 px-4 rounded-lg text-left ${
selectedCity === 'Москва'
? 'bg-blue-50 text-blue-700'
: 'text-gray-700 hover:bg-gray-100'
}`}
>
<MapPin className="h-4 w-4 inline mr-2" />
Москва
</button>
<button
onClick={() => onCityChange('Чебоксары')}
className={`w-full py-2 px-4 rounded-lg text-left ${
selectedCity === 'Чебоксары'
? 'bg-blue-50 text-blue-700'
: 'text-gray-700 hover:bg-gray-100'
}`}
>
<MapPin className="h-4 w-4 inline mr-2" />
Чебоксары
</button>
</div>
<Button
className="w-full bg-blue-700 hover:bg-blue-800 text-white py-6 text-lg"
onClick={() => {
setIsModalOpen(true);
setIsMenuOpen(false);
}}
>
Оставить заявку
</Button>
</div>
</div>
</motion.div>
{/* Темный оверлей при открытом меню */}
{isMenuOpen && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 0.5 }}
exit={{ opacity: 0 }}
className="fixed inset-0 bg-black z-40 lg:hidden"
style={{ top: '56px' }}
onClick={() => setIsMenuOpen(false)}
/>
)}
<div className="h-[56px]" />
<ContactModal
isOpen={isModalOpen}

View File

@ -15,6 +15,13 @@ import ContactModal from '@/app/components/ContactModal';
import { services } from '@/app/components/Services';
import { use } from 'react';
// Импортируем данные о городах, чтобы использовать телефоны
const cityData = {
Москва: {
phone: '+7 (916) 830-58-58',
},
} as const;
interface ServicePageProps {
params: Promise<{
slug: string;
@ -330,11 +337,11 @@ export default function ServicePage({ params }: ServicePageProps) {
</p>
<div className="flex flex-col md:flex-row items-center justify-center gap-4 mb-6">
<a
href="tel:+79991234567"
href={`tel:${cityData.Москва.phone}`}
className="flex items-center text-white hover:text-blue-100"
>
<Phone className="h-5 w-5 mr-2" />
<span className="text-xl">+7 (999) 123-45-67</span>
<span className="text-xl">{cityData.Москва.phone}</span>
</a>
</div>
<Button

View File

@ -1,6 +1,9 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
output: 'standalone',
images: {
unoptimized: true,
},
};
module.exports = nextConfig;