Finished the Navbar and product dropdown issue
This commit is contained in:
parent
1844fb8b45
commit
4508039803
@ -7,6 +7,7 @@ import OptimizedImage from '../common/OptimizedImage';
|
|||||||
import { menuItems, languages, mobileMenuVariants } from './constants/navigationData';
|
import { menuItems, languages, mobileMenuVariants } from './constants/navigationData';
|
||||||
import { headerSchema } from './schema/headerSchema';
|
import { headerSchema } from './schema/headerSchema';
|
||||||
import styles from './styles/Header.module.css';
|
import styles from './styles/Header.module.css';
|
||||||
|
import baseStyles from './styles/base.module.css';
|
||||||
import Logo from '@/assets/Logo/Tech4biz-logo.webp';
|
import Logo from '@/assets/Logo/Tech4biz-logo.webp';
|
||||||
|
|
||||||
const SearchOverlay = lazy(() => import('./components/SearchOverlay'));
|
const SearchOverlay = lazy(() => import('./components/SearchOverlay'));
|
||||||
@ -15,7 +16,7 @@ const MobileNav = lazy(() => import('./components/MobileNav'));
|
|||||||
const LanguageSelector = lazy(() => import('./components/LanguageSelector'));
|
const LanguageSelector = lazy(() => import('./components/LanguageSelector'));
|
||||||
|
|
||||||
const Navbar = () => {
|
const Navbar = () => {
|
||||||
// State management with hooks
|
// State management remains the same
|
||||||
const [state, setState] = useState({
|
const [state, setState] = useState({
|
||||||
isProductsOpen: false,
|
isProductsOpen: false,
|
||||||
isLanguageOpen: false,
|
isLanguageOpen: false,
|
||||||
@ -26,10 +27,11 @@ const Navbar = () => {
|
|||||||
isSticky: false
|
isSticky: false
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Refs and handlers remain the same
|
||||||
const desktopLanguageRef = useRef(null);
|
const desktopLanguageRef = useRef(null);
|
||||||
const mobileLanguageRef = useRef(null);
|
const mobileLanguageRef = useRef(null);
|
||||||
|
|
||||||
// Event handlers with useCallback
|
// Event handlers with useCallback remain the same
|
||||||
const handleClickOutside = useCallback((event) => {
|
const handleClickOutside = useCallback((event) => {
|
||||||
if (desktopLanguageRef.current && !desktopLanguageRef.current.contains(event.target)) {
|
if (desktopLanguageRef.current && !desktopLanguageRef.current.contains(event.target)) {
|
||||||
setState(prev => ({ ...prev, isLanguageOpen: false }));
|
setState(prev => ({ ...prev, isLanguageOpen: false }));
|
||||||
@ -43,12 +45,11 @@ const Navbar = () => {
|
|||||||
setState(prev => ({ ...prev, isSticky: window.scrollY > 0 }));
|
setState(prev => ({ ...prev, isSticky: window.scrollY > 0 }));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Effect for event listeners
|
// Effects remain the same
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
document.addEventListener('mousedown', handleClickOutside);
|
document.addEventListener('mousedown', handleClickOutside);
|
||||||
window.addEventListener('scroll', handleScroll);
|
window.addEventListener('scroll', handleScroll);
|
||||||
|
|
||||||
// Inject schema
|
|
||||||
const script = document.createElement('script');
|
const script = document.createElement('script');
|
||||||
script.type = 'application/ld+json';
|
script.type = 'application/ld+json';
|
||||||
script.text = JSON.stringify(headerSchema);
|
script.text = JSON.stringify(headerSchema);
|
||||||
@ -64,13 +65,15 @@ const Navbar = () => {
|
|||||||
return (
|
return (
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
<Suspense fallback={<div>Loading...</div>}>
|
<Suspense fallback={<div>Loading...</div>}>
|
||||||
<SearchOverlay isOpen={state.isSearchOpen} onClose={() => setState(prev => ({ ...prev, isSearchOpen: false }))} />
|
<SearchOverlay
|
||||||
|
isOpen={state.isSearchOpen}
|
||||||
|
onClose={() => setState(prev => ({ ...prev, isSearchOpen: false }))}
|
||||||
|
/>
|
||||||
|
|
||||||
<nav className={`${styles.navContainer} ${state.isSticky ? styles.stickyNav : ''}`}>
|
<nav className={`${styles.navContainer} ${state.isSticky ? styles.stickyNav : ''}`}>
|
||||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
<div className={baseStyles.container}>
|
||||||
<div className="flex justify-between items-center h-16">
|
<div className={`${baseStyles.flexBetween} h-16`}>
|
||||||
{/* Left section with logo and mobile actions */}
|
<div className={`${baseStyles.flexCenter} space-x-4`}>
|
||||||
<div className="flex items-center space-x-4">
|
|
||||||
<Link to="/" className="flex-shrink-0">
|
<Link to="/" className="flex-shrink-0">
|
||||||
<OptimizedImage
|
<OptimizedImage
|
||||||
src={Logo}
|
src={Logo}
|
||||||
@ -82,108 +85,69 @@ const Navbar = () => {
|
|||||||
priority={true}
|
priority={true}
|
||||||
/>
|
/>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
{/* Mobile search and language */}
|
{/* Mobile search and language */}
|
||||||
<div className="flex items-center space-x-4 md:hidden">
|
<div className="md:hidden flex items-center space-x-3">
|
||||||
<button
|
<button
|
||||||
onClick={() => setState(prev => ({ ...prev, isSearchOpen: true }))}
|
onClick={() => setState(prev => ({ ...prev, isSearchOpen: true }))}
|
||||||
className="p-2 text-gray-600 hover:text-[#1E0E62] transition-colors"
|
className={styles.searchButton}
|
||||||
aria-label="Search"
|
aria-label="Search"
|
||||||
>
|
>
|
||||||
<FiSearch className="h-5 w-5" />
|
<FiSearch className="h-5 w-5" />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div ref={mobileLanguageRef} className="relative">
|
|
||||||
<button
|
|
||||||
className="flex items-center justify-between text-gray-600 hover:text-[#1E0E62] transition-colors border rounded-md px-2 py-1 w-[60px]"
|
|
||||||
onClick={() => setState(prev => ({
|
|
||||||
...prev,
|
|
||||||
isMobileLanguageOpen: !prev.isMobileLanguageOpen
|
|
||||||
}))}
|
|
||||||
aria-label="Select language"
|
|
||||||
>
|
|
||||||
<span className="font-poppins font-bold text-[14px] truncate">
|
|
||||||
{state.selectedLanguage.substring(0, 2).toUpperCase()}
|
|
||||||
</span>
|
|
||||||
<FiChevronDown className="h-4 w-4" />
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<AnimatePresence>
|
|
||||||
{state.isMobileLanguageOpen && (
|
|
||||||
<motion.div
|
|
||||||
initial={{ opacity: 0, y: 10 }}
|
|
||||||
animate={{ opacity: 1, y: 0 }}
|
|
||||||
exit={{ opacity: 0, y: 10 }}
|
|
||||||
className="absolute left-0 mt-2 w-24 bg-white rounded-md shadow-lg py-1"
|
|
||||||
role="listbox"
|
|
||||||
>
|
|
||||||
{languages.map((lang) => (
|
|
||||||
<button
|
|
||||||
key={lang}
|
|
||||||
className="block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-[#1E0E62]/5 font-poppins font-bold text-[14px]"
|
|
||||||
onClick={() => setState(prev => ({
|
|
||||||
...prev,
|
|
||||||
selectedLanguage: lang,
|
|
||||||
isMobileLanguageOpen: false
|
|
||||||
}))}
|
|
||||||
role="option"
|
|
||||||
aria-selected={state.selectedLanguage === lang}
|
|
||||||
>
|
|
||||||
{lang}
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</motion.div>
|
|
||||||
)}
|
|
||||||
</AnimatePresence>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Desktop Navigation */}
|
|
||||||
<DesktopNav {...state} setIsProductsOpen={(value) =>
|
|
||||||
setState(prev => ({ ...prev, isProductsOpen: value }))}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* Right section */}
|
|
||||||
<div className="flex items-center space-x-4">
|
|
||||||
{/* Desktop search and language */}
|
|
||||||
<div className="hidden md:flex items-center space-x-8">
|
|
||||||
<button
|
|
||||||
onClick={() => setState(prev => ({ ...prev, isSearchOpen: true }))}
|
|
||||||
className={styles.searchButton}
|
|
||||||
aria-label="Open search"
|
|
||||||
>
|
|
||||||
<FiSearch className="h-5 w-5" />
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<LanguageSelector
|
<LanguageSelector
|
||||||
isOpen={state.isLanguageOpen}
|
isDesktop={false}
|
||||||
|
isOpen={state.isMobileLanguageOpen}
|
||||||
selectedLanguage={state.selectedLanguage}
|
selectedLanguage={state.selectedLanguage}
|
||||||
languageRef={desktopLanguageRef}
|
languageRef={mobileLanguageRef}
|
||||||
setState={setState}
|
setState={setState}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Mobile menu button */}
|
|
||||||
<button
|
|
||||||
className="md:hidden"
|
|
||||||
onClick={() => setState(prev => ({
|
|
||||||
...prev,
|
|
||||||
isMobileMenuOpen: !prev.isMobileMenuOpen
|
|
||||||
}))}
|
|
||||||
aria-label="Toggle mobile menu"
|
|
||||||
>
|
|
||||||
{state.isMobileMenuOpen ? (
|
|
||||||
<FiX className="h-6 w-6" />
|
|
||||||
) : (
|
|
||||||
<FiMenu className="h-6 w-6" />
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<DesktopNav
|
||||||
|
isProductsOpen={state.isProductsOpen}
|
||||||
|
setIsProductsOpen={(value) => setState(prev => ({ ...prev, isProductsOpen: value }))}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Desktop search and language */}
|
||||||
|
<div className="hidden md:flex items-center space-x-4">
|
||||||
|
<button
|
||||||
|
onClick={() => setState(prev => ({ ...prev, isSearchOpen: true }))}
|
||||||
|
className={styles.searchButton}
|
||||||
|
aria-label="Search"
|
||||||
|
>
|
||||||
|
<FiSearch className="h-5 w-5" />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<LanguageSelector
|
||||||
|
isDesktop={true}
|
||||||
|
isOpen={state.isLanguageOpen}
|
||||||
|
selectedLanguage={state.selectedLanguage}
|
||||||
|
languageRef={desktopLanguageRef}
|
||||||
|
setState={setState}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Mobile menu toggle button */}
|
||||||
|
<button
|
||||||
|
className="md:hidden text-gray-600 hover:text-[#1E0E62] transition-colors"
|
||||||
|
onClick={() => setState(prev => ({
|
||||||
|
...prev,
|
||||||
|
isMobileMenuOpen: !prev.isMobileMenuOpen
|
||||||
|
}))}
|
||||||
|
aria-label="Toggle mobile menu"
|
||||||
|
>
|
||||||
|
{state.isMobileMenuOpen ? (
|
||||||
|
<FiX className="h-6 w-6" />
|
||||||
|
) : (
|
||||||
|
<FiMenu className="h-6 w-6" />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Mobile Navigation */}
|
|
||||||
<MobileNav
|
<MobileNav
|
||||||
isMobileMenuOpen={state.isMobileMenuOpen}
|
isMobileMenuOpen={state.isMobileMenuOpen}
|
||||||
setState={setState}
|
setState={setState}
|
||||||
|
|||||||
@ -4,14 +4,13 @@ import { FiChevronDown } from 'react-icons/fi';
|
|||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { menuItems } from '../constants/navigationData';
|
import { menuItems } from '../constants/navigationData';
|
||||||
import ProductShowcase from './ProductShowcase';
|
import ProductShowcase from './ProductShowcase';
|
||||||
|
import styles from '../styles/DesktopNav.module.css';
|
||||||
|
import baseStyles from '../styles/base.module.css';
|
||||||
|
|
||||||
const DesktopNav = memo(({ isProductsOpen, setIsProductsOpen }) => {
|
const DesktopNav = memo(({ isProductsOpen, setIsProductsOpen }) => {
|
||||||
const handleMouseEnter = () => {
|
const handleProductsClick = (e) => {
|
||||||
setIsProductsOpen(true);
|
e.stopPropagation();
|
||||||
};
|
setIsProductsOpen(!isProductsOpen);
|
||||||
|
|
||||||
const handleMouseLeave = () => {
|
|
||||||
setIsProductsOpen(false);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const getItemPath = (item) => {
|
const getItemPath = (item) => {
|
||||||
@ -30,19 +29,17 @@ const DesktopNav = memo(({ isProductsOpen, setIsProductsOpen }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="hidden md:flex items-center space-x-8 max-[956px]:space-x-5">
|
<div className={styles.container}>
|
||||||
{menuItems.map((item) => (
|
{menuItems.map((item) => (
|
||||||
<div key={item} className="relative">
|
<div key={item} className="relative">
|
||||||
{item === 'PRODUCTS' ? (
|
{item === 'PRODUCTS' ? (
|
||||||
<div
|
<div>
|
||||||
className="static"
|
|
||||||
onMouseEnter={handleMouseEnter}
|
|
||||||
onMouseLeave={handleMouseLeave}
|
|
||||||
>
|
|
||||||
<button
|
<button
|
||||||
className="flex items-center text-gray-600 hover:text-[#1E0E62] transition-colors font-poppins font-bold text-[14px]"
|
className={styles.menuButton}
|
||||||
|
onClick={handleProductsClick}
|
||||||
aria-expanded={isProductsOpen}
|
aria-expanded={isProductsOpen}
|
||||||
aria-haspopup="true"
|
aria-haspopup="true"
|
||||||
|
data-products-trigger
|
||||||
>
|
>
|
||||||
{item}
|
{item}
|
||||||
<FiChevronDown
|
<FiChevronDown
|
||||||
@ -51,13 +48,13 @@ const DesktopNav = memo(({ isProductsOpen, setIsProductsOpen }) => {
|
|||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
<AnimatePresence mode="wait">
|
<AnimatePresence mode="wait">
|
||||||
{isProductsOpen && <ProductShowcase />}
|
{isProductsOpen && <ProductShowcase setIsProductsOpen={setIsProductsOpen} />}
|
||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<Link
|
<Link
|
||||||
to={getItemPath(item)}
|
to={getItemPath(item)}
|
||||||
className="text-gray-600 hover:text-[#1E0E62] transition-colors font-poppins font-bold text-[14px]"
|
className={styles.menuLink}
|
||||||
>
|
>
|
||||||
{item}
|
{item}
|
||||||
</Link>
|
</Link>
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import React, { memo } from 'react';
|
|||||||
import { motion, AnimatePresence } from 'framer-motion';
|
import { motion, AnimatePresence } from 'framer-motion';
|
||||||
import { FiChevronDown } from 'react-icons/fi';
|
import { FiChevronDown } from 'react-icons/fi';
|
||||||
import { languages } from '../constants/navigationData';
|
import { languages } from '../constants/navigationData';
|
||||||
import styles from '../styles/Header.module.css';
|
import styles from '../styles/LanguageSelector.module.css';
|
||||||
|
|
||||||
const LanguageSelector = memo(({
|
const LanguageSelector = memo(({
|
||||||
isDesktop = true,
|
isDesktop = true,
|
||||||
@ -11,12 +11,8 @@ const LanguageSelector = memo(({
|
|||||||
languageRef,
|
languageRef,
|
||||||
setState
|
setState
|
||||||
}) => {
|
}) => {
|
||||||
const containerClasses = isDesktop
|
|
||||||
? "hidden md:block relative"
|
|
||||||
: "relative";
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={languageRef} className={containerClasses}>
|
<div ref={languageRef} className={`${styles.container} ${isDesktop ? 'hidden md:block' : ''}`}>
|
||||||
<button
|
<button
|
||||||
onClick={() => setState(prev => ({
|
onClick={() => setState(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
@ -36,13 +32,13 @@ const LanguageSelector = memo(({
|
|||||||
initial={{ opacity: 0, y: 10 }}
|
initial={{ opacity: 0, y: 10 }}
|
||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
exit={{ opacity: 0, y: 10 }}
|
exit={{ opacity: 0, y: 10 }}
|
||||||
className="absolute right-[-35px] mt-2 w-40 bg-white rounded-md shadow-lg z-50"
|
className={styles.dropdown}
|
||||||
role="listbox"
|
role="listbox"
|
||||||
>
|
>
|
||||||
{languages.map((language) => (
|
{languages.map((language) => (
|
||||||
<button
|
<button
|
||||||
key={language}
|
key={language}
|
||||||
className="block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100"
|
className={styles.dropdownItem}
|
||||||
onClick={() => setState(prev => ({
|
onClick={() => setState(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
selectedLanguage: language,
|
selectedLanguage: language,
|
||||||
|
|||||||
@ -1,10 +1,29 @@
|
|||||||
import React, { memo } from 'react';
|
import React, { memo, useState } from 'react';
|
||||||
import { motion, AnimatePresence } from 'framer-motion';
|
import { motion, AnimatePresence } from 'framer-motion';
|
||||||
import { FiChevronDown, FiX } from 'react-icons/fi';
|
import { FiChevronDown } from 'react-icons/fi';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
import { menuItems, mobileMenuVariants } from '../constants/navigationData';
|
import { menuItems, mobileMenuVariants } from '../constants/navigationData';
|
||||||
import styles from '../styles/Header.module.css';
|
import MobileProductShowcase from './MobileProductShowcase';
|
||||||
|
import styles from '../styles/MobileNav.module.css';
|
||||||
|
|
||||||
const MobileNav = memo(({ isMobileMenuOpen, setState }) => {
|
const MobileNav = memo(({ isMobileMenuOpen, setState }) => {
|
||||||
|
const [isProductsOpen, setIsProductsOpen] = useState(false);
|
||||||
|
|
||||||
|
const getItemPath = (item) => {
|
||||||
|
switch (item) {
|
||||||
|
case 'HOME':
|
||||||
|
return '/';
|
||||||
|
case 'SERVICES':
|
||||||
|
return '/services';
|
||||||
|
case 'ABOUT':
|
||||||
|
return '/about';
|
||||||
|
case 'CONTACT':
|
||||||
|
return '/contact';
|
||||||
|
default:
|
||||||
|
return '#';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
{isMobileMenuOpen && (
|
{isMobileMenuOpen && (
|
||||||
@ -14,7 +33,7 @@ const MobileNav = memo(({ isMobileMenuOpen, setState }) => {
|
|||||||
exit="closed"
|
exit="closed"
|
||||||
variants={mobileMenuVariants}
|
variants={mobileMenuVariants}
|
||||||
transition={{ duration: 0.3, ease: "easeInOut" }}
|
transition={{ duration: 0.3, ease: "easeInOut" }}
|
||||||
className="md:hidden fixed top-16 right-0 h-[calc(100vh-64px)] w-full sm:w-[350px] z-30 bg-white shadow-lg overflow-y-auto"
|
className={styles.container}
|
||||||
role="dialog"
|
role="dialog"
|
||||||
aria-modal="true"
|
aria-modal="true"
|
||||||
aria-label="Mobile navigation menu"
|
aria-label="Mobile navigation menu"
|
||||||
@ -22,14 +41,43 @@ const MobileNav = memo(({ isMobileMenuOpen, setState }) => {
|
|||||||
<div className="flex flex-col h-full">
|
<div className="flex flex-col h-full">
|
||||||
<div className="overflow-y-auto">
|
<div className="overflow-y-auto">
|
||||||
{menuItems.map((item) => (
|
{menuItems.map((item) => (
|
||||||
<a
|
<div key={item}>
|
||||||
key={item}
|
{item === 'PRODUCTS' ? (
|
||||||
href="#"
|
<>
|
||||||
className="block px-4 py-3 text-gray-600 hover:text-[#1E0E62] hover:bg-[#1E0E62]/5 font-poppins font-bold text-[14px]"
|
<button
|
||||||
onClick={() => setState(prev => ({ ...prev, isMobileMenuOpen: false }))}
|
className={styles.menuButton}
|
||||||
>
|
onClick={() => setIsProductsOpen(!isProductsOpen)}
|
||||||
{item}
|
aria-expanded={isProductsOpen}
|
||||||
</a>
|
>
|
||||||
|
{item}
|
||||||
|
<FiChevronDown
|
||||||
|
className={`ml-1 transition-transform duration-300 ${isProductsOpen ? 'rotate-180' : ''}`}
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
<AnimatePresence>
|
||||||
|
{isProductsOpen && (
|
||||||
|
<motion.div
|
||||||
|
initial={{ height: 0, opacity: 0 }}
|
||||||
|
animate={{ height: 'auto', opacity: 1 }}
|
||||||
|
exit={{ height: 0, opacity: 0 }}
|
||||||
|
transition={{ duration: 0.3 }}
|
||||||
|
className="overflow-hidden"
|
||||||
|
>
|
||||||
|
<MobileProductShowcase />
|
||||||
|
</motion.div>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<Link
|
||||||
|
to={getItemPath(item)}
|
||||||
|
className={styles.menuLink}
|
||||||
|
onClick={() => setState(prev => ({ ...prev, isMobileMenuOpen: false }))}
|
||||||
|
>
|
||||||
|
{item}
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -39,6 +87,4 @@ const MobileNav = memo(({ isMobileMenuOpen, setState }) => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
MobileNav.displayName = 'MobileNav';
|
|
||||||
|
|
||||||
export default MobileNav;
|
export default MobileNav;
|
||||||
49
src/components/Header/components/MobileProductShowcase.jsx
Normal file
49
src/components/Header/components/MobileProductShowcase.jsx
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { motion } from 'framer-motion';
|
||||||
|
import { products } from '../constants/navigationData';
|
||||||
|
import styles from '../styles/MobileProductShowcase.module.css';
|
||||||
|
|
||||||
|
const MobileProductShowcase = () => {
|
||||||
|
return (
|
||||||
|
<div className={styles.container}>
|
||||||
|
<div className={styles.productList}>
|
||||||
|
{products.map((product) => (
|
||||||
|
<motion.div
|
||||||
|
key={product.id}
|
||||||
|
className={styles.productCard}
|
||||||
|
whileHover={{ x: 5 }}
|
||||||
|
transition={{ duration: 0.2 }}
|
||||||
|
>
|
||||||
|
<h3 className={styles.productTitle}>
|
||||||
|
{product.name}
|
||||||
|
</h3>
|
||||||
|
<p className={styles.productDescription}>
|
||||||
|
{product.description}
|
||||||
|
</p>
|
||||||
|
</motion.div>
|
||||||
|
))}
|
||||||
|
|
||||||
|
<div className={styles.buttonContainer}>
|
||||||
|
<motion.button
|
||||||
|
className={styles.viewAllButton}
|
||||||
|
whileHover={{ scale: 1.05 }}
|
||||||
|
whileTap={{ scale: 0.95 }}
|
||||||
|
>
|
||||||
|
View All Products
|
||||||
|
<svg
|
||||||
|
className="ml-2 w-5 h-5"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor"
|
||||||
|
>
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 5l7 7-7 7" />
|
||||||
|
</svg>
|
||||||
|
</motion.button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MobileProductShowcase;
|
||||||
@ -1,107 +1,74 @@
|
|||||||
import React from 'react';
|
import React, { useState, useEffect, useRef } from 'react';
|
||||||
import { motion } from 'framer-motion';
|
import { motion, AnimatePresence } from 'framer-motion';
|
||||||
|
import { ChevronRight } from 'lucide-react';
|
||||||
// Import images
|
import styles from '../styles/ProductShowcase.module.css';
|
||||||
import lmsImage from '@/assets/Header/Product-Card/learning-management-system.webp';
|
import baseStyles from '../styles/base.module.css';
|
||||||
import codenukImage from '@/assets/Header/Product-Card/codenuk-coding-solution-platform.webp';
|
import { products, productList } from '../constants/navigationData';
|
||||||
import cloudtopiaaImage from '@/assets/Header/Product-Card/cloudtopiaa-enterprise-solutions.webp';
|
|
||||||
import cloudDriveImage from '@/assets/Header/Product-Card/cloud-drive-storage-platform.webp';
|
|
||||||
|
|
||||||
const containerVariants = {
|
const containerVariants = {
|
||||||
initial: {
|
initial: {
|
||||||
opacity: 0,
|
opacity: 0,
|
||||||
height: 0,
|
y: -10,
|
||||||
transformOrigin: "top"
|
|
||||||
},
|
},
|
||||||
animate: {
|
animate: {
|
||||||
opacity: 1,
|
opacity: 1,
|
||||||
height: "auto",
|
y: 0,
|
||||||
transition: {
|
transition: {
|
||||||
height: {
|
duration: 0.15,
|
||||||
duration: 0.3,
|
ease: "easeOut"
|
||||||
ease: "easeOut"
|
|
||||||
},
|
|
||||||
opacity: {
|
|
||||||
duration: 0.2,
|
|
||||||
ease: "easeOut"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
exit: {
|
exit: {
|
||||||
opacity: 0,
|
opacity: 0,
|
||||||
height: 0,
|
y: -10,
|
||||||
transition: {
|
transition: {
|
||||||
height: {
|
duration: 0.15,
|
||||||
duration: 0.3,
|
ease: "easeIn"
|
||||||
ease: "easeIn"
|
|
||||||
},
|
|
||||||
opacity: {
|
|
||||||
duration: 0.2,
|
|
||||||
ease: "easeIn"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const ProductShowcase = () => {
|
const ProductShowcase = ({ setIsProductsOpen }) => {
|
||||||
const products = [
|
const [hoveredIndex, setHoveredIndex] = useState(0);
|
||||||
{
|
const [isFirstItemLocked, setIsFirstItemLocked] = useState(true);
|
||||||
id: 1,
|
const showcaseRef = useRef(null);
|
||||||
name: 'CloudDriv',
|
|
||||||
image: cloudDriveImage,
|
|
||||||
description: 'Secure cloud storage for seamless sharing and collaboration.'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
name: 'Codenuk',
|
|
||||||
image: codenukImage,
|
|
||||||
description: 'Breakthrough coding challenges instantly with Codenuk.'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 3,
|
|
||||||
name: 'LMS',
|
|
||||||
image: lmsImage,
|
|
||||||
description: 'Streamline education delivery with our comprehensive Learning Management System.'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 4,
|
|
||||||
name: 'Cloudtopiaa',
|
|
||||||
image: cloudtopiaaImage,
|
|
||||||
description: 'Secure, scalable cloud solutions that power your business growth.'
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
const productList = [
|
useEffect(() => {
|
||||||
"Product name one",
|
const handleClickOutside = (event) => {
|
||||||
"Product name two",
|
if (
|
||||||
"Product name three",
|
showcaseRef.current &&
|
||||||
"Product name four",
|
!showcaseRef.current.contains(event.target) &&
|
||||||
"Product name five",
|
!event.target.closest('[data-products-trigger]')
|
||||||
"Product name six",
|
) {
|
||||||
"Product name seven",
|
setIsProductsOpen(false);
|
||||||
"Product name eight"
|
}
|
||||||
];
|
};
|
||||||
|
|
||||||
|
document.addEventListener('mousedown', handleClickOutside, true);
|
||||||
|
return () => document.removeEventListener('mousedown', handleClickOutside, true);
|
||||||
|
}, [setIsProductsOpen]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<motion.div
|
<motion.div
|
||||||
|
ref={showcaseRef}
|
||||||
variants={containerVariants}
|
variants={containerVariants}
|
||||||
initial="initial"
|
initial="initial"
|
||||||
animate="animate"
|
animate="animate"
|
||||||
exit="exit"
|
exit="exit"
|
||||||
className="fixed left-0 right-0 w-full bg-white shadow-lg z-50 border-b-[5px] border-[#2563EB] overflow-hidden"
|
className={styles.container}
|
||||||
style={{ top: '64px' }}
|
style={{ top: '64px' }}
|
||||||
>
|
>
|
||||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
<div className={baseStyles.container}>
|
||||||
<div className="flex flex-col lg:flex-row gap-8">
|
<div className="flex flex-col lg:flex-row gap-8">
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 lg:w-2/3">
|
<div className={styles.productGrid}>
|
||||||
{products.map((product) => (
|
{products.map((product) => (
|
||||||
<motion.div
|
<motion.div
|
||||||
key={product.id}
|
key={product.id}
|
||||||
className="group cursor-pointer"
|
className={`group ${styles.productCard}`}
|
||||||
whileHover={{ scale: 1.02 }}
|
whileHover={{ scale: 1.02 }}
|
||||||
transition={{ duration: 0.2 }}
|
transition={{ duration: 0.2 }}
|
||||||
>
|
>
|
||||||
<div className="hidden md:block relative overflow-hidden rounded-lg">
|
<div className={styles.productImage}>
|
||||||
<img
|
<img
|
||||||
src={product.image}
|
src={product.image}
|
||||||
alt={product.name}
|
alt={product.name}
|
||||||
@ -109,43 +76,88 @@ const ProductShowcase = () => {
|
|||||||
loading="lazy"
|
loading="lazy"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<h3 className="mt-4 text-xl font-semibold text-indigo-900">
|
<h3 className={styles.productTitle}>
|
||||||
{product.name}
|
{product.name}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="mt-2 text-gray-600 text-sm">
|
<p className={styles.productDescription}>
|
||||||
{product.description}
|
{product.description}
|
||||||
</p>
|
</p>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Desktop view for All Products */}
|
||||||
<div className="hidden lg:block lg:w-1/3">
|
<div className="hidden lg:block lg:w-1/3">
|
||||||
<h2 className="text-2xl font-bold text-indigo-900 mb-6">All Products</h2>
|
<h2 className={styles.sidebarTitle}>All Products</h2>
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
{productList.map((product, index) => (
|
{productList.map((product, index) => (
|
||||||
<motion.div
|
<motion.div
|
||||||
key={index}
|
key={index}
|
||||||
className="cursor-pointer"
|
className={styles.sidebarItem}
|
||||||
whileHover={{ x: 10 }}
|
whileHover={{ x: 5 }}
|
||||||
transition={{ duration: 0.2 }}
|
transition={{ duration: 0.2 }}
|
||||||
|
onMouseEnter={() => setHoveredIndex(index)}
|
||||||
|
onMouseLeave={() => setHoveredIndex(null)}
|
||||||
>
|
>
|
||||||
<h3 className="text-lg font-semibold text-indigo-900">
|
<div className={styles.sidebarContent}>
|
||||||
{product}
|
<h3 className={styles.productTitle}>
|
||||||
</h3>
|
{product.name}
|
||||||
{index === 0 && (
|
</h3>
|
||||||
<>
|
<AnimatePresence mode="popLayout">
|
||||||
<p className="text-gray-600 text-sm mt-2">
|
{hoveredIndex === index && (
|
||||||
Breakthrough coding challenges instantly with Codenuk. Secure cloud storage for seamless sharing and collaboration.
|
<motion.div
|
||||||
</p>
|
className={styles.expandedContent}
|
||||||
<button className="text-indigo-600 text-sm mt-2 hover:text-indigo-800">
|
initial={{ height: 0, opacity: 0 }}
|
||||||
Read more
|
animate={{
|
||||||
</button>
|
height: "auto",
|
||||||
</>
|
opacity: 1,
|
||||||
)}
|
transition: {
|
||||||
|
height: { duration: 0.2 },
|
||||||
|
opacity: { duration: 0.1, delay: 0.1 }
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
exit={{
|
||||||
|
height: 0,
|
||||||
|
opacity: 0,
|
||||||
|
transition: {
|
||||||
|
height: { duration: 0.2 },
|
||||||
|
opacity: { duration: 0.1 }
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<p className={styles.productDescription}>
|
||||||
|
{product.description}
|
||||||
|
</p>
|
||||||
|
<motion.button
|
||||||
|
className="text-[#6B7280] text-sm mt-2 hover:text-[#1E0E62] font-poppins inline-flex items-center"
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
animate={{ opacity: 1 }}
|
||||||
|
transition={{ delay: 0.15 }}
|
||||||
|
>
|
||||||
|
Read more
|
||||||
|
<ChevronRight className="w-4 h-4 ml-1" />
|
||||||
|
</motion.button>
|
||||||
|
</motion.div>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Mobile/Tablet view with View All Products button */}
|
||||||
|
<div className="lg:hidden w-full">
|
||||||
|
<motion.button
|
||||||
|
className={styles.viewAllButton}
|
||||||
|
whileHover={{ scale: 1.05 }}
|
||||||
|
whileTap={{ scale: 0.95 }}
|
||||||
|
onClick={() => setIsProductsOpen(false)}
|
||||||
|
>
|
||||||
|
View All Products
|
||||||
|
<ChevronRight className="w-4 h-4 ml-1" />
|
||||||
|
</motion.button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|||||||
@ -1,12 +1,13 @@
|
|||||||
import React, { useCallback, memo } from 'react';
|
import React, { memo, useCallback } from 'react';
|
||||||
import { motion, AnimatePresence } from 'framer-motion';
|
import { motion, AnimatePresence } from 'framer-motion';
|
||||||
import { FiSearch, FiX } from 'react-icons/fi';
|
import { FiSearch, FiX } from 'react-icons/fi';
|
||||||
import { debounce } from 'lodash';
|
import { debounce } from 'lodash';
|
||||||
|
import styles from '../styles/SearchOverlay.module.css';
|
||||||
|
import baseStyles from '../styles/base.module.css';
|
||||||
|
|
||||||
const SearchOverlay = memo(({ isOpen, onClose }) => {
|
const SearchOverlay = memo(({ isOpen, onClose }) => {
|
||||||
const handleSearch = useCallback(
|
const handleSearch = useCallback(
|
||||||
debounce((searchTerm) => {
|
debounce((searchTerm) => {
|
||||||
// Implement search logic here
|
|
||||||
console.log('Searching for:', searchTerm);
|
console.log('Searching for:', searchTerm);
|
||||||
}, 300),
|
}, 300),
|
||||||
[]
|
[]
|
||||||
@ -21,7 +22,7 @@ const SearchOverlay = memo(({ isOpen, onClose }) => {
|
|||||||
animate={{ opacity: 0.4 }}
|
animate={{ opacity: 0.4 }}
|
||||||
exit={{ opacity: 0 }}
|
exit={{ opacity: 0 }}
|
||||||
transition={{ duration: 0.3 }}
|
transition={{ duration: 0.3 }}
|
||||||
className="fixed inset-0 bg-white z-40"
|
className={styles.overlay}
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
role="presentation"
|
role="presentation"
|
||||||
/>
|
/>
|
||||||
@ -31,24 +32,24 @@ const SearchOverlay = memo(({ isOpen, onClose }) => {
|
|||||||
animate={{ opacity: 1 }}
|
animate={{ opacity: 1 }}
|
||||||
exit={{ opacity: 0 }}
|
exit={{ opacity: 0 }}
|
||||||
transition={{ duration: 0.3 }}
|
transition={{ duration: 0.3 }}
|
||||||
className="fixed top-0 left-0 right-0 bg-white z-50"
|
className={styles.container}
|
||||||
role="dialog"
|
role="dialog"
|
||||||
aria-label="Search overlay"
|
aria-label="Search overlay"
|
||||||
>
|
>
|
||||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
<div className={baseStyles.container}>
|
||||||
<div className="flex items-center h-16 border-b border-gray-200">
|
<div className={`${baseStyles.flexCenter} h-16 border-b border-gray-200`}>
|
||||||
<FiSearch className="h-5 w-5 text-gray-400" aria-hidden="true" />
|
<FiSearch className="h-5 w-5 text-gray-400" aria-hidden="true" />
|
||||||
<input
|
<input
|
||||||
type="search"
|
type="search"
|
||||||
placeholder="What are you looking for?"
|
placeholder="What are you looking for?"
|
||||||
className="flex-1 px-4 text-gray-600 placeholder-gray-400 bg-transparent border-none focus:outline-none focus:ring-0"
|
className={styles.searchInput}
|
||||||
onChange={(e) => handleSearch(e.target.value)}
|
onChange={(e) => handleSearch(e.target.value)}
|
||||||
aria-label="Search input"
|
aria-label="Search input"
|
||||||
autoFocus
|
autoFocus
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
className="text-gray-400 hover:text-[#1E0E62] transition-colors"
|
className={styles.closeButton}
|
||||||
aria-label="Close search"
|
aria-label="Close search"
|
||||||
>
|
>
|
||||||
<FiX className="h-5 w-5" />
|
<FiX className="h-5 w-5" />
|
||||||
|
|||||||
@ -1,3 +1,8 @@
|
|||||||
|
import lmsImage from '@/assets/Header/Product-Card/learning-management-system.webp';
|
||||||
|
import codenukImage from '@/assets/Header/Product-Card/codenuk-coding-solution-platform.webp';
|
||||||
|
import cloudtopiaaImage from '@/assets/Header/Product-Card/cloudtopiaa-enterprise-solutions.webp';
|
||||||
|
import cloudDriveImage from '@/assets/Header/Product-Card/cloud-drive-storage-platform.webp';
|
||||||
|
|
||||||
export const menuItems = [
|
export const menuItems = [
|
||||||
'HOME',
|
'HOME',
|
||||||
'PRODUCTS',
|
'PRODUCTS',
|
||||||
@ -5,9 +10,72 @@ export const menuItems = [
|
|||||||
'ABOUT',
|
'ABOUT',
|
||||||
'CONTACT'
|
'CONTACT'
|
||||||
];
|
];
|
||||||
|
|
||||||
export const languages = ['English', 'Spanish', 'French'];
|
export const languages = ['English', 'Spanish', 'French'];
|
||||||
|
|
||||||
export const mobileMenuVariants = {
|
export const mobileMenuVariants = {
|
||||||
closed: { opacity: 0, x: "100%" },
|
closed: { opacity: 0, x: "100%" },
|
||||||
open: { opacity: 1, x: 0 }
|
open: { opacity: 1, x: 0 }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const products = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: 'CloudDriv',
|
||||||
|
image: cloudDriveImage,
|
||||||
|
description: 'Secure cloud storage for seamless sharing and collaboration.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: 'Codenuk',
|
||||||
|
image: codenukImage,
|
||||||
|
description: 'Breakthrough coding challenges instantly with Codenuk.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
name: 'LMS',
|
||||||
|
image: lmsImage,
|
||||||
|
description: 'Streamline education delivery with our comprehensive Learning Management System.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
name: 'Cloudtopiaa',
|
||||||
|
image: cloudtopiaaImage,
|
||||||
|
description: 'Secure, scalable cloud solutions that power your business growth.'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
export const productList = [
|
||||||
|
{
|
||||||
|
name: "Product name one",
|
||||||
|
description: "Breakthrough coding challenges instantly with Codenuk. Secure cloud storage for seamless sharing and collaboration."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Product name two",
|
||||||
|
description: "Advanced cloud computing solutions for enterprise-level businesses with scalable infrastructure."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Product name three",
|
||||||
|
description: "Comprehensive learning management system with interactive features and real-time analytics."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Product name four",
|
||||||
|
description: "Secure data storage solutions with enterprise-grade encryption and collaboration tools."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Product name five",
|
||||||
|
description: "AI-powered development tools for faster and more efficient coding workflows."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Product name six",
|
||||||
|
description: "Automated testing platform for comprehensive quality assurance and bug detection."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Product name seven",
|
||||||
|
description: "DevOps automation tools for streamlined deployment and continuous integration."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Product name eight",
|
||||||
|
description: "Cloud-native application platform with microservices architecture support."
|
||||||
|
}
|
||||||
|
];
|
||||||
11
src/components/Header/styles/DesktopNav.module.css
Normal file
11
src/components/Header/styles/DesktopNav.module.css
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
.container {
|
||||||
|
@apply hidden md:flex items-center space-x-8 max-[956px]:space-x-5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menuButton {
|
||||||
|
@apply flex items-center text-gray-600 hover:text-[#1E0E62] transition-colors font-poppins font-bold text-[14px];
|
||||||
|
}
|
||||||
|
|
||||||
|
.menuLink {
|
||||||
|
@apply text-gray-600 hover:text-[#1E0E62] transition-colors font-poppins font-bold text-[14px];
|
||||||
|
}
|
||||||
@ -7,7 +7,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.navLink {
|
.navLink {
|
||||||
@apply text-gray-600 hover:text-[#1E0E62] transition-colors font-poppins font-bold text-[14px];
|
@apply font-poppins font-bold text-[14px] text-gray-600 hover:text-[#1E0E62] transition-colors;
|
||||||
}
|
}
|
||||||
|
|
||||||
.activeNavLink {
|
.activeNavLink {
|
||||||
@ -20,4 +20,6 @@
|
|||||||
|
|
||||||
.searchButton {
|
.searchButton {
|
||||||
@apply text-gray-600 hover:text-[#1E0E62] transition-colors;
|
@apply text-gray-600 hover:text-[#1E0E62] transition-colors;
|
||||||
}
|
@apply max-md:scale-90;
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
17
src/components/Header/styles/LanguageSelector.module.css
Normal file
17
src/components/Header/styles/LanguageSelector.module.css
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
.container {
|
||||||
|
@apply relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.languageButton {
|
||||||
|
@apply flex items-center justify-between text-gray-600 hover:text-[#1E0E62] transition-colors border rounded-md px-3 py-1;
|
||||||
|
@apply max-md:text-sm max-md:px-2 max-md:py-0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown {
|
||||||
|
@apply absolute mt-2 w-40 bg-white rounded-md shadow-lg z-50;
|
||||||
|
@apply max-md:right-0 md:right-[-35px];
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdownItem {
|
||||||
|
@apply block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100;
|
||||||
|
}
|
||||||
11
src/components/Header/styles/MobileNav.module.css
Normal file
11
src/components/Header/styles/MobileNav.module.css
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
.container {
|
||||||
|
@apply md:hidden fixed top-16 right-0 h-[calc(100vh-64px)] w-full sm:w-[350px] z-30 bg-white shadow-lg overflow-y-auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menuButton {
|
||||||
|
@apply w-full flex justify-between items-center px-4 py-3 text-gray-600 hover:bg-[#1E0E62]/5 font-poppins font-bold text-[14px];
|
||||||
|
}
|
||||||
|
|
||||||
|
.menuLink {
|
||||||
|
@apply block px-4 py-3 text-gray-600 hover:bg-[#1E0E62]/5 font-poppins font-bold text-[14px];
|
||||||
|
}
|
||||||
@ -0,0 +1,27 @@
|
|||||||
|
.container {
|
||||||
|
@apply bg-gray-50 px-4 py-4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.productList {
|
||||||
|
@apply space-y-6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.productCard {
|
||||||
|
@apply cursor-pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.productTitle {
|
||||||
|
@apply text-[18px] font-[500] text-[#1E0E62] font-poppins;
|
||||||
|
}
|
||||||
|
|
||||||
|
.productDescription {
|
||||||
|
@apply text-[14px] font-[500] text-[#6B7280] font-poppins mt-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttonContainer {
|
||||||
|
@apply pt-4 text-center border-t border-gray-200;
|
||||||
|
}
|
||||||
|
|
||||||
|
.viewAllButton {
|
||||||
|
@apply inline-flex items-center px-4 py-2 bg-[#1E0E62] text-white font-semibold rounded-lg hover:bg-[#1E0E62] transition-colors font-poppins;
|
||||||
|
}
|
||||||
43
src/components/Header/styles/ProductShowcase.module.css
Normal file
43
src/components/Header/styles/ProductShowcase.module.css
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
.container {
|
||||||
|
@apply fixed left-0 right-0 w-full bg-white shadow-lg z-50 border-b-[5px] border-[#2563EB] py-6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.productGrid {
|
||||||
|
@apply grid grid-cols-1 md:grid-cols-2 gap-4 lg:w-2/3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.productCard {
|
||||||
|
@apply cursor-pointer p-2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.productImage {
|
||||||
|
@apply hidden md:block relative overflow-hidden rounded-lg mb-2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.productTitle {
|
||||||
|
@apply mt-2 text-[22px] font-[500] text-[#1E0E62] font-poppins;
|
||||||
|
}
|
||||||
|
|
||||||
|
.productDescription {
|
||||||
|
@apply mt-2 text-[14px] font-[500] text-[#6B7280] font-poppins mb-2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebarTitle {
|
||||||
|
@apply text-[32px] font-[700] text-[#1E0E62] mb-6 font-poppins;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebarItem {
|
||||||
|
@apply cursor-pointer mb-6 bg-white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.viewAllButton {
|
||||||
|
@apply w-full flex items-center justify-center px-4 py-3 bg-[#1E0E62] text-white font-semibold rounded-lg hover:bg-[#1E0E62]/90 transition-colors font-poppins mt-4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebarContent {
|
||||||
|
@apply relative overflow-hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.expandedContent {
|
||||||
|
@apply overflow-hidden;
|
||||||
|
}
|
||||||
15
src/components/Header/styles/SearchOverlay.module.css
Normal file
15
src/components/Header/styles/SearchOverlay.module.css
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
.overlay {
|
||||||
|
@apply fixed inset-0 bg-white z-40;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
@apply fixed top-0 left-0 right-0 bg-white z-50;
|
||||||
|
}
|
||||||
|
|
||||||
|
.searchInput {
|
||||||
|
@apply flex-1 px-4 text-gray-600 placeholder-gray-400 bg-transparent border-none focus:outline-none focus:ring-0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.closeButton {
|
||||||
|
@apply text-gray-400 hover:text-[#1E0E62] transition-colors;
|
||||||
|
}
|
||||||
31
src/components/Header/styles/base.module.css
Normal file
31
src/components/Header/styles/base.module.css
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
.container {
|
||||||
|
@apply max-w-6xl mx-auto px-4 sm:px-6 lg:px-8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flexCenter {
|
||||||
|
@apply flex items-center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flexBetween {
|
||||||
|
@apply flex justify-between items-center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.transitionBase {
|
||||||
|
@apply transition-all duration-300;
|
||||||
|
}
|
||||||
|
|
||||||
|
.textBase {
|
||||||
|
@apply font-poppins font-bold text-[14px];
|
||||||
|
}
|
||||||
|
|
||||||
|
.textPrimary {
|
||||||
|
@apply text-[#1E0E62];
|
||||||
|
}
|
||||||
|
|
||||||
|
.textGray {
|
||||||
|
@apply text-gray-600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hoverPrimary {
|
||||||
|
@apply hover:text-[#1E0E62] transition-colors;
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user