Skip to content

Commit 87a6ce3

Browse files
authored
Merge pull request #1 from SafeExamBrowser/dev
Dev
2 parents d2921ba + 7fd535b commit 87a6ce3

2 files changed

Lines changed: 103 additions & 40 deletions

File tree

app/faq/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ export default function FAQPage() {
3939
>
4040
<button
4141
onClick={() => toggleQuestion(category.id, questionIndex)}
42-
className="w-full px-6 py-4 text-left flex items-center justify-between gap-4 hover:bg-(--color-surface) transition-colors"
42+
className="w-full cursor-pointer px-6 py-4 text-left flex items-center justify-between gap-4 hover:bg-(--color-surface) transition-colors"
4343
>
4444
<span className="font-semibold text-(--color-gray)">{item.question}</span>
4545
<span

components/navigation.tsx

Lines changed: 102 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -2,66 +2,129 @@
22

33
import Link from "next/link"
44
import { usePathname } from "next/navigation"
5-
import { useState, useEffect } from "react"
5+
import { useEffect, useState } from "react"
66
import navigationData from "@/data/navigation.json"
77

88
export function Navigation() {
99
const pathname = usePathname()
1010
const [scrolled, setScrolled] = useState(false)
11+
const [mobileOpen, setMobileOpen] = useState(false)
1112

1213
useEffect(() => {
13-
const handleScroll = () => {
14-
setScrolled(window.scrollY > 20)
15-
}
14+
const handleScroll = () => setScrolled(window.scrollY > 20)
1615
window.addEventListener("scroll", handleScroll)
1716
return () => window.removeEventListener("scroll", handleScroll)
1817
}, [])
1918

19+
// Close mobile menu when route changes
20+
useEffect(() => {
21+
setMobileOpen(false)
22+
}, [pathname])
23+
24+
// Close on Escape
25+
useEffect(() => {
26+
const onKeyDown = (e: KeyboardEvent) => {
27+
if (e.key === "Escape") setMobileOpen(false)
28+
}
29+
window.addEventListener("keydown", onKeyDown)
30+
return () => window.removeEventListener("keydown", onKeyDown)
31+
}, [])
32+
33+
const navClass = `fixed top-0 left-0 right-0 z-50 transition-all duration-300 ${
34+
scrolled ? "bg-white/95 dark:bg-gray-900/95 backdrop-blur-md shadow-md" : "bg-white dark:bg-gray-900"
35+
}`
36+
2037
return (
21-
<nav
22-
className={`fixed top-0 left-0 right-0 z-50 transition-all duration-300 ${
23-
scrolled ? "bg-white/95 dark:bg-gray-900/95 backdrop-blur-md shadow-md" : "bg-white dark:bg-gray-900"
24-
}`}
25-
>
26-
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
27-
<div className="flex items-center justify-between h-16">
28-
<Link href="/" className="flex items-center gap-3 group transition-transform hover:scale-105">
29-
<img
30-
src="/images/image.png"
31-
alt="Safe Exam Browser"
32-
className="h-10 w-10 transition-transform group-hover:rotate-12"
33-
/>
34-
<span className="font-bold text-lg text-(--color-primary) dark:text-blue-400">Safe Exam Browser</span>
35-
</Link>
38+
<nav className={navClass}>
39+
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
40+
<div className="flex items-center justify-between h-16">
41+
<Link href="/" className="flex items-center gap-3 group transition-transform hover:scale-105">
42+
<img
43+
src="/images/image.png"
44+
alt="Safe Exam Browser"
45+
className="h-10 w-10 transition-transform group-hover:rotate-12"
46+
/>
47+
<span className="font-bold text-lg text-(--color-primary) dark:text-blue-400">Safe Exam Browser</span>
48+
</Link>
49+
50+
{/* Desktop nav */}
51+
<div className="hidden md:flex items-center gap-3">
52+
<div className="flex items-center gap-1">
53+
{navigationData.main.map((item, index) => {
54+
const isActive = pathname === item.href
55+
const isExternal = item.external
56+
57+
return (
58+
<Link
59+
key={item.id}
60+
href={item.href}
61+
target={isExternal ? "_blank" : undefined}
62+
rel={isExternal ? "noopener noreferrer" : undefined}
63+
className={`px-4 py-2 rounded-lg font-medium transition-all duration-200 ${
64+
isActive
65+
? "bg-(--color-primary) dark:bg-blue-600 text-white"
66+
: "text-(--color-gray) dark:text-gray-300 hover:bg-(--color-surface) dark:hover:bg-gray-800 hover:text-(--color-primary) dark:hover:text-blue-400"
67+
}`}
68+
style={{ animationDelay: `${index * 50}ms` }}
69+
>
70+
{item.label}
71+
{isExternal && <span className="ml-1 text-xs"></span>}
72+
</Link>
73+
)
74+
})}
75+
</div>
76+
</div>
77+
78+
{/* Mobile button */}
79+
<button
80+
type="button"
81+
className="md:hidden inline-flex items-center justify-center rounded-lg p-2
82+
text-(--color-gray) dark:text-gray-300 hover:bg-(--color-surface) dark:hover:bg-gray-800"
83+
aria-label="Toggle navigation menu"
84+
aria-expanded={mobileOpen}
85+
aria-controls="mobile-nav"
86+
onClick={() => setMobileOpen((v) => !v)}
87+
>
88+
{/* simple icon swap */}
89+
<span className="text-xl leading-none">{mobileOpen ? "✕" : "☰"}</span>
90+
</button>
91+
</div>
92+
</div>
3693

37-
<div className="flex items-center gap-3">
38-
<div className="flex items-center gap-1">
39-
{navigationData.main.map((item, index) => {
94+
{/* Mobile dropdown */}
95+
<div
96+
id="mobile-nav"
97+
className={`md:hidden overflow-hidden transition-all duration-300 ${
98+
mobileOpen ? "max-h-96 opacity-100" : "max-h-0 opacity-0"
99+
}`}
100+
>
101+
<div className="px-4 sm:px-6 lg:px-8 pb-4">
102+
<div className="mt-2 rounded-xl border border-black/5 dark:border-white/10 bg-white/90 dark:bg-gray-900/90 backdrop-blur-md shadow-sm p-2">
103+
{navigationData.main.map((item) => {
40104
const isActive = pathname === item.href
41105
const isExternal = item.external
42106

43107
return (
44-
<Link
45-
key={item.id}
46-
href={item.href}
47-
target={isExternal ? "_blank" : undefined}
48-
rel={isExternal ? "noopener noreferrer" : undefined}
49-
className={`px-4 py-2 rounded-lg font-medium transition-all duration-200 ${
50-
isActive
51-
? "bg-(--color-primary) dark:bg-blue-600 text-white"
52-
: "text-(--color-gray) dark:text-gray-300 hover:bg-(--color-surface) dark:hover:bg-gray-800 hover:text-(--color-primary) dark:hover:text-blue-400"
53-
}`}
54-
style={{ animationDelay: `${index * 50}ms` }}
55-
>
56-
{item.label}
57-
{isExternal && <span className="ml-1 text-xs"></span>}
58-
</Link>
108+
<Link
109+
key={item.id}
110+
href={item.href}
111+
target={isExternal ? "_blank" : undefined}
112+
rel={isExternal ? "noopener noreferrer" : undefined}
113+
onClick={() => setMobileOpen(false)}
114+
className={`flex items-center justify-between w-full px-4 py-3 rounded-lg font-medium transition-all duration-200 ${
115+
isActive
116+
? "bg-(--color-primary) dark:bg-blue-600 text-white"
117+
: "text-(--color-gray) dark:text-gray-300 hover:bg-(--color-surface) dark:hover:bg-gray-800 hover:text-(--color-primary) dark:hover:text-blue-400"
118+
}`}
119+
>
120+
<span>{item.label}</span>
121+
{isExternal && <span className="text-sm"></span>}
122+
</Link>
59123
)
60124
})}
61125
</div>
62126
</div>
63127
</div>
64-
</div>
65-
</nav>
128+
</nav>
66129
)
67130
}

0 commit comments

Comments
 (0)