mirror of
https://github.com/JustAnimeCore/JustAnime.git
synced 2026-04-17 13:51:44 +00:00
fixed navbar
This commit is contained in:
@@ -3,6 +3,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|||||||
import {
|
import {
|
||||||
faBars,
|
faBars,
|
||||||
faRandom,
|
faRandom,
|
||||||
|
faMagnifyingGlass,
|
||||||
} from "@fortawesome/free-solid-svg-icons";
|
} from "@fortawesome/free-solid-svg-icons";
|
||||||
import { useLanguage } from "@/src/context/LanguageContext";
|
import { useLanguage } from "@/src/context/LanguageContext";
|
||||||
import { Link, useLocation } from "react-router-dom";
|
import { Link, useLocation } from "react-router-dom";
|
||||||
@@ -19,6 +20,7 @@ function Navbar() {
|
|||||||
);
|
);
|
||||||
const [isScrolled, setIsScrolled] = useState(false);
|
const [isScrolled, setIsScrolled] = useState(false);
|
||||||
const [isSidebarOpen, setIsSidebarOpen] = useState(false);
|
const [isSidebarOpen, setIsSidebarOpen] = useState(false);
|
||||||
|
const [isMobileSearchOpen, setIsMobileSearchOpen] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleScroll = () => {
|
const handleScroll = () => {
|
||||||
@@ -53,9 +55,8 @@ function Navbar() {
|
|||||||
return (
|
return (
|
||||||
<SearchProvider>
|
<SearchProvider>
|
||||||
<nav
|
<nav
|
||||||
className={`fixed top-0 left-0 w-full z-[1000000] transition-all duration-300 ease-in-out
|
className={`fixed top-0 left-0 w-full z-[1000000] transition-all duration-300 ease-in-out bg-[#0a0a0a]
|
||||||
${isNotHomePage ? "bg-[#18181B]" : "bg-opacity-0"}
|
${isScrolled ? "bg-opacity-80 backdrop-blur-md shadow-lg" : "bg-opacity-100"}`}
|
||||||
${isScrolled ? "bg-[#18181B]/80 backdrop-blur-md shadow-lg" : ""}`}
|
|
||||||
>
|
>
|
||||||
<div className="max-w-[1920px] mx-auto px-4 h-16 flex items-center justify-between">
|
<div className="max-w-[1920px] mx-auto px-4 h-16 flex items-center justify-between">
|
||||||
{/* Left Section */}
|
{/* Left Section */}
|
||||||
@@ -66,7 +67,7 @@ function Navbar() {
|
|||||||
className="text-xl text-gray-200 cursor-pointer hover:text-white transition-colors"
|
className="text-xl text-gray-200 cursor-pointer hover:text-white transition-colors"
|
||||||
onClick={handleHamburgerClick}
|
onClick={handleHamburgerClick}
|
||||||
/>
|
/>
|
||||||
<Link to="/" className="flex items-center">
|
<Link to="/home" className="flex items-center">
|
||||||
<img src="/logo.png" alt="JustAnime Logo" className="h-9 w-auto" />
|
<img src="/logo.png" alt="JustAnime Logo" className="h-9 w-auto" />
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
@@ -87,32 +88,45 @@ function Navbar() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Right Section */}
|
{/* Language Toggle - Desktop */}
|
||||||
<div className="flex items-center gap-6">
|
<div className="hidden md:flex items-center gap-2 bg-[#27272A] rounded-md p-1">
|
||||||
<div className="hidden md:flex items-center gap-2 bg-[#27272A] rounded-md p-1">
|
{["EN", "JP"].map((lang) => (
|
||||||
{["EN", "JP"].map((lang) => (
|
<button
|
||||||
<button
|
key={lang}
|
||||||
key={lang}
|
onClick={() => toggleLanguage(lang)}
|
||||||
onClick={() => toggleLanguage(lang)}
|
className={`px-3 py-1 text-sm font-medium rounded ${
|
||||||
className={`px-3 py-1 text-sm font-medium rounded ${
|
language === lang
|
||||||
language === lang
|
? "bg-[#3F3F46] text-white"
|
||||||
? "bg-[#3F3F46] text-white"
|
: "text-gray-400 hover:text-white"
|
||||||
: "text-gray-400 hover:text-white"
|
}`}
|
||||||
}`}
|
>
|
||||||
>
|
{lang}
|
||||||
{lang}
|
</button>
|
||||||
</button>
|
))}
|
||||||
))}
|
</div>
|
||||||
</div>
|
|
||||||
|
{/* Mobile Search Icon */}
|
||||||
|
<div className="md:hidden flex items-center">
|
||||||
|
<button
|
||||||
|
onClick={() => setIsMobileSearchOpen(!isMobileSearchOpen)}
|
||||||
|
className="p-[10px] aspect-square bg-[#2a2a2a]/75 text-white/50 hover:text-white rounded-lg transition-colors flex items-center justify-center"
|
||||||
|
title="Search Anime"
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={faMagnifyingGlass} className="text-lg" />
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Mobile Search */}
|
{/* Mobile Search Dropdown */}
|
||||||
<div className="md:hidden">
|
{isMobileSearchOpen && (
|
||||||
<MobileSearch />
|
<div className="md:hidden bg-[#18181B] shadow-lg">
|
||||||
</div>
|
<MobileSearch onClose={() => setIsMobileSearchOpen(false)} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Sidebar */}
|
||||||
|
<Sidebar isOpen={isSidebarOpen} onClose={handleCloseSidebar} />
|
||||||
</nav>
|
</nav>
|
||||||
<Sidebar isOpen={isSidebarOpen} onClose={handleCloseSidebar} />
|
|
||||||
</SearchProvider>
|
</SearchProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import Suggestion from '../suggestion/Suggestion';
|
import Suggestion from '../suggestion/Suggestion';
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
import { faMagnifyingGlass } from '@fortawesome/free-solid-svg-icons';
|
import { faMagnifyingGlass, faRandom } from '@fortawesome/free-solid-svg-icons';
|
||||||
import useSearch from '@/src/hooks/useSearch';
|
import useSearch from '@/src/hooks/useSearch';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { Link, useNavigate, useLocation } from 'react-router-dom';
|
||||||
|
|
||||||
function MobileSearch() {
|
function MobileSearch({ onClose }) {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const location = useLocation();
|
||||||
const {
|
const {
|
||||||
isSearchVisible,
|
isSearchVisible,
|
||||||
searchValue,
|
searchValue,
|
||||||
@@ -16,60 +17,80 @@ function MobileSearch() {
|
|||||||
suggestionRefs,
|
suggestionRefs,
|
||||||
addSuggestionRef,
|
addSuggestionRef,
|
||||||
} = useSearch();
|
} = useSearch();
|
||||||
|
|
||||||
const handleSearchClick = () => {
|
const handleSearchClick = () => {
|
||||||
if (searchValue.trim() && window.innerWidth <= 600) {
|
if (searchValue.trim()) {
|
||||||
navigate(`/search?keyword=${encodeURIComponent(searchValue)}`);
|
navigate(`/search?keyword=${encodeURIComponent(searchValue)}`);
|
||||||
|
onClose?.();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleRandomClick = () => {
|
||||||
|
if (location.pathname === "/random") {
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
onClose?.();
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div className="w-full p-4 flex flex-col gap-4">
|
||||||
{isSearchVisible && (
|
<div className="flex items-center gap-2">
|
||||||
<div className="flex w-full mt-2 relative custom-md:hidden px-4">
|
<div className="relative flex-1">
|
||||||
<div className="relative w-full">
|
<input
|
||||||
<input
|
type="text"
|
||||||
type="text"
|
className="w-full px-5 py-2 bg-[#2a2a2a]/75 text-white rounded-lg focus:outline-none transition-colors placeholder-white/50"
|
||||||
className="w-full px-5 py-2 bg-[#2a2a2a]/75 text-white border border-white/10 rounded-lg focus:outline-none focus:border-white/30 transition-colors placeholder-white/50"
|
placeholder="Search anime..."
|
||||||
placeholder="Search anime..."
|
value={searchValue}
|
||||||
value={searchValue}
|
onChange={(e) => setSearchValue(e.target.value)}
|
||||||
onChange={(e) => setSearchValue(e.target.value)}
|
onFocus={() => setIsFocused(true)}
|
||||||
onFocus={() => setIsFocused(true)}
|
onBlur={() => {
|
||||||
onBlur={() => {
|
setTimeout(() => {
|
||||||
setTimeout(() => {
|
const isInsideSuggestionBox = suggestionRefs.current.some(
|
||||||
const isInsideSuggestionBox = suggestionRefs.current.some(
|
(ref) => ref && ref.contains(document.activeElement),
|
||||||
(ref) => ref && ref.contains(document.activeElement),
|
);
|
||||||
);
|
if (!isInsideSuggestionBox) {
|
||||||
if (!isInsideSuggestionBox) {
|
setIsFocused(false);
|
||||||
setIsFocused(false);
|
|
||||||
}
|
|
||||||
}, 100);
|
|
||||||
}}
|
|
||||||
onKeyDown={(e) => {
|
|
||||||
if (e.key === 'Enter') {
|
|
||||||
handleSearchClick();
|
|
||||||
}
|
}
|
||||||
}}
|
}, 100);
|
||||||
|
}}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
handleSearchClick();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
className="absolute right-4 top-1/2 -translate-y-1/2 text-white/50 hover:text-white transition-colors"
|
||||||
|
onClick={handleSearchClick}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon
|
||||||
|
icon={faMagnifyingGlass}
|
||||||
|
className="text-lg"
|
||||||
/>
|
/>
|
||||||
<button
|
</button>
|
||||||
className="absolute right-4 top-1/2 -translate-y-1/2 text-white/50 hover:text-white transition-colors"
|
</div>
|
||||||
onClick={handleSearchClick}
|
<Link
|
||||||
>
|
to={location.pathname === "/random" ? "#" : "/random"}
|
||||||
<FontAwesomeIcon
|
onClick={handleRandomClick}
|
||||||
icon={faMagnifyingGlass}
|
className="p-[10px] aspect-square bg-[#2a2a2a]/75 text-white/50 hover:text-white rounded-lg transition-colors flex items-center justify-center shrink-0"
|
||||||
className="text-lg"
|
title="Random Anime"
|
||||||
/>
|
>
|
||||||
</button>
|
<FontAwesomeIcon icon={faRandom} className="text-lg" />
|
||||||
</div>
|
</Link>
|
||||||
{searchValue.trim() && isFocused && (
|
</div>
|
||||||
<div
|
{searchValue.trim() && isFocused && (
|
||||||
ref={addSuggestionRef}
|
<div
|
||||||
className="absolute z-[100000] top-full w-full"
|
ref={addSuggestionRef}
|
||||||
>
|
className="absolute z-[100000] left-0 right-0 px-4 mt-[60px]"
|
||||||
<Suggestion keyword={debouncedValue} className="w-full" />
|
>
|
||||||
</div>
|
<Suggestion
|
||||||
)}
|
keyword={debouncedValue}
|
||||||
|
className="w-full"
|
||||||
|
onSuggestionClick={onClose}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import BouncingLoader from "../ui/bouncingloader/Bouncingloader";
|
|||||||
import { FaChevronRight } from "react-icons/fa";
|
import { FaChevronRight } from "react-icons/fa";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
|
|
||||||
function Suggestion({ keyword, className }) {
|
function Suggestion({ keyword, className, onSuggestionClick }) {
|
||||||
const [suggestion, setSuggestion] = useState([]);
|
const [suggestion, setSuggestion] = useState([]);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [error, setError] = useState(null);
|
const [error, setError] = useState(null);
|
||||||
@@ -31,32 +31,36 @@ function Suggestion({ keyword, className }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`bg-[#2d2b44] ${className} flex ${
|
className={`bg-zinc-900 ${className} flex ${
|
||||||
loading ? "justify-center py-7" : "justify-start"
|
loading ? "justify-center py-4" : "justify-start"
|
||||||
} ${!suggestion ? "p-3" : "justify-start"} items-center`}
|
} ${!suggestion ? "p-2" : "justify-start"} items-center rounded-lg`}
|
||||||
style={{ boxShadow: "0 20px 20px rgba(0, 0, 0, .3)" }}
|
style={{
|
||||||
|
boxShadow: "0 8px 32px rgba(0, 0, 0, 0.2)",
|
||||||
|
border: "1px solid rgba(255, 255, 255, 0.05)"
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<BouncingLoader />
|
<BouncingLoader />
|
||||||
) : error && !suggestion ? (
|
) : error && !suggestion ? (
|
||||||
<div>Error loading suggestions</div>
|
<div className="text-gray-400 p-3">Error loading suggestions</div>
|
||||||
) : suggestion && hasFetched ? (
|
) : suggestion && hasFetched ? (
|
||||||
<div className="w-full flex flex-col pt-2 overflow-y-auto">
|
<div className="w-full flex flex-col pt-1 overflow-y-auto">
|
||||||
{suggestion.map((item, index) => (
|
{suggestion.map((item, index) => (
|
||||||
<Link
|
<Link
|
||||||
to={`/${item.id}`}
|
to={`/${item.id}`}
|
||||||
key={index}
|
key={index}
|
||||||
className="group py-2 flex items-start gap-x-3 hover:bg-[#3c3a5e] cursor-pointer px-[10px]"
|
onClick={onSuggestionClick}
|
||||||
|
className="group py-2 flex items-start gap-x-3 hover:bg-zinc-800 transition-all duration-200 cursor-pointer px-3"
|
||||||
style={{
|
style={{
|
||||||
borderBottom:
|
borderBottom:
|
||||||
index === suggestion.length - 1
|
index === suggestion.length - 1
|
||||||
? "none"
|
? "none"
|
||||||
: "1px dashed rgba(255, 255, 255, .075)",
|
: "1px solid rgba(255, 255, 255, 0.05)",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
src={`${item.poster}`}
|
src={`${item.poster}`}
|
||||||
className="w-[50px] h-[75px] flex-shrink-0 object-cover"
|
className="w-[45px] h-[65px] flex-shrink-0 object-cover rounded-md shadow-lg"
|
||||||
alt=""
|
alt=""
|
||||||
onError={(e) => {
|
onError={(e) => {
|
||||||
e.target.src = "https://i.postimg.cc/HnHKvHpz/no-avatar.jpg";
|
e.target.src = "https://i.postimg.cc/HnHKvHpz/no-avatar.jpg";
|
||||||
@@ -64,26 +68,26 @@ function Suggestion({ keyword, className }) {
|
|||||||
/>
|
/>
|
||||||
<div className="flex flex-col gap-y-[2px]">
|
<div className="flex flex-col gap-y-[2px]">
|
||||||
{item?.title && (
|
{item?.title && (
|
||||||
<h1 className="line-clamp-1 leading-5 font-bold text-[15px] group-hover:text-[#ffbade]">
|
<h1 className="line-clamp-1 leading-5 font-semibold text-[14px] text-gray-100 group-hover:text-white">
|
||||||
{item.title || "N/A"}
|
{item.title || "N/A"}
|
||||||
</h1>
|
</h1>
|
||||||
)}
|
)}
|
||||||
{item?.japanese_title && (
|
{item?.japanese_title && (
|
||||||
<h1 className="line-clamp-1 leading-5 text-[13px] font-light text-[#aaaaaa]">
|
<h1 className="line-clamp-1 leading-4 text-[12px] font-normal text-gray-400">
|
||||||
{item.japanese_title || "N/A"}
|
{item.japanese_title || "N/A"}
|
||||||
</h1>
|
</h1>
|
||||||
)}
|
)}
|
||||||
{(item?.releaseDate || item?.showType || item?.duration) && (
|
{(item?.releaseDate || item?.showType || item?.duration) && (
|
||||||
<div className="flex gap-x-[5px] items-center w-full justify-start mt-[4px]">
|
<div className="flex gap-x-2 items-center w-full justify-start mt-[2px]">
|
||||||
<p className="leading-5 text-[13px] font-light text-[#aaaaaa]">
|
<p className="leading-4 text-[12px] font-normal text-gray-400">
|
||||||
{item.releaseDate || "N/A"}
|
{item.releaseDate || "N/A"}
|
||||||
</p>
|
</p>
|
||||||
<span className="dot"></span>
|
<span className="w-1 h-1 rounded-full bg-gray-600"></span>
|
||||||
<p className="leading-5 text-[13px] font-medium group-hover:text-[#ffbade]">
|
<p className="leading-4 text-[12px] font-medium text-gray-300 group-hover:text-white">
|
||||||
{item.showType || "N/A"}
|
{item.showType || "N/A"}
|
||||||
</p>
|
</p>
|
||||||
<span className="dot"></span>
|
<span className="w-1 h-1 rounded-full bg-gray-600"></span>
|
||||||
<p className="leading-5 text-[13px] font-light text-[#aaaaaa]">
|
<p className="leading-4 text-[12px] font-normal text-gray-400">
|
||||||
{item.duration || "N/A"}
|
{item.duration || "N/A"}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -93,20 +97,21 @@ function Suggestion({ keyword, className }) {
|
|||||||
))}
|
))}
|
||||||
{!loading && hasFetched && (
|
{!loading && hasFetched && (
|
||||||
<Link
|
<Link
|
||||||
className="w-full flex py-4 justify-center items-center bg-[#ffbade]"
|
className="w-full flex py-2.5 justify-center items-center bg-zinc-800 hover:bg-zinc-700 transition-all duration-200 rounded-b-lg"
|
||||||
to={`/search?keyword=${encodeURIComponent(keyword)}`}
|
to={`/search?keyword=${encodeURIComponent(keyword)}`}
|
||||||
|
onClick={onSuggestionClick}
|
||||||
>
|
>
|
||||||
<div className="flex w-fit items-center gap-x-2">
|
<div className="flex w-fit items-center gap-x-2">
|
||||||
<p className="text-[17px] font-light text-black">
|
<p className="text-[14px] font-medium text-gray-200">
|
||||||
View all results
|
View all results
|
||||||
</p>
|
</p>
|
||||||
<FaChevronRight className="text-black text-[12px] font-black mt-[2px]" />
|
<FaChevronRight className="text-gray-200 text-[11px] mt-[1px]" />
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
) : hasFetched ? (
|
) : hasFetched ? (
|
||||||
<p className="text-[17px]">No results found!</p>
|
<p className="text-gray-300 p-3">No results found!</p>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user