From 0e7bc8571994aea67e228da6c32ad1128c5b13c5 Mon Sep 17 00:00:00 2001 From: Vamsha vardhan Date: Fri, 15 May 2026 11:49:06 +0530 Subject: [PATCH 1/3] Fix ESLint flat config for Next.js --- .all-contributorsrc | 7 ++ .husky/pre-commit | 3 - app/(core)/components/Header.jsx | 91 +++++++++++++++++++- app/(core)/components/Nav.tsx | 21 ++++- app/(core)/styles/index.css | 137 ++++++++++++++++++++++++++----- eslint.config.mjs | 12 ++- next.config.ts | 2 +- package.json | 4 +- public/sitemap.xml | 7 +- tsconfig.json | 2 +- 10 files changed, 252 insertions(+), 34 deletions(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index 7b9c5958..08b36973 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -180,6 +180,13 @@ "avatar_url": "https://avatars.githubusercontent.com/u/166863746?v=4", "profile": "https://madebynitin.netlify.app/", "contributions": ["code", "bug"] + }, + { + "login": "Vamshavardhan50", + "name": "Vamsha vardhan", + "avatar_url": "https://avatars.githubusercontent.com/u/162793177?v=4", + "profile": "https://github.com/Vamshavardhan50", + "contributions": ["code", "bug"] } ] } diff --git a/.husky/pre-commit b/.husky/pre-commit index 4e058a5c..ba403f47 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,5 +1,2 @@ -#!/usr/bin/env sh -. "$(dirname -- "$0")/_/husky.sh" - npm run pre-commit npx lint-staged \ No newline at end of file diff --git a/app/(core)/components/Header.jsx b/app/(core)/components/Header.jsx index dc452468..12456dbb 100644 --- a/app/(core)/components/Header.jsx +++ b/app/(core)/components/Header.jsx @@ -1,6 +1,6 @@ // app/components/Header.jsx "use client"; -import { useState, useCallback } from "react"; +import { useState, useCallback, useEffect, useRef } from "react"; import useTranslation from "../hooks/useTranslation.ts"; import { Logo } from "./Logo"; import NavMenu from "./Nav"; @@ -20,6 +20,8 @@ export default function Header() { const pathname = usePathname(); const { t, meta } = useTranslation(); const isCompleted = meta?.completed || false; + const bodyStylesRef = useRef({ overflow: "", paddingRight: "" }); + const previousActiveRef = useRef(null); // Close menu when route changes const [prevPathname, setPrevPathname] = useState(pathname); @@ -29,6 +31,84 @@ export default function Header() { } const handleMenuToggle = useCallback(() => setMenuOpen((open) => !open), []); + const handleMenuClose = useCallback(() => setMenuOpen(false), []); + + useEffect(() => { + if (typeof document === "undefined") return; + + const body = document.body; + if (isMenuOpen) { + bodyStylesRef.current = { + overflow: body.style.overflow, + paddingRight: body.style.paddingRight, + }; + + const scrollbarWidth = + window.innerWidth - document.documentElement.clientWidth; + + body.style.overflow = "hidden"; + body.style.paddingRight = scrollbarWidth ? `${scrollbarWidth}px` : ""; + } else { + body.style.overflow = bodyStylesRef.current.overflow; + body.style.paddingRight = bodyStylesRef.current.paddingRight; + } + + return () => { + body.style.overflow = bodyStylesRef.current.overflow; + body.style.paddingRight = bodyStylesRef.current.paddingRight; + }; + }, [isMenuOpen]); + + useEffect(() => { + if (!isMenuOpen || typeof document === "undefined") return; + + const nav = document.querySelector(".nav-menu"); + if (!nav) return; + + const focusableSelector = + "a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex='-1'])"; + + const focusable = Array.from(nav.querySelectorAll(focusableSelector)); + + previousActiveRef.current = document.activeElement; + + requestAnimationFrame(() => { + const firstFocusable = focusable[0]; + if (firstFocusable && typeof firstFocusable.focus === "function") { + firstFocusable.focus(); + } + }); + + const handleKeyDown = (event) => { + if (event.key === "Escape") { + handleMenuClose(); + return; + } + + if (event.key !== "Tab" || focusable.length === 0) return; + + const firstFocusable = focusable[0]; + const lastFocusable = focusable[focusable.length - 1]; + + if (event.shiftKey && document.activeElement === firstFocusable) { + event.preventDefault(); + lastFocusable.focus(); + } else if (!event.shiftKey && document.activeElement === lastFocusable) { + event.preventDefault(); + firstFocusable.focus(); + } + }; + + document.addEventListener("keydown", handleKeyDown); + return () => { + document.removeEventListener("keydown", handleKeyDown); + + const previousActive = previousActiveRef.current; + if (previousActive && typeof previousActive.focus === "function") { + previousActive.focus(); + } + }; + }, [isMenuOpen, handleMenuClose]); return (
- +
@@ -54,6 +134,13 @@ export default function Header() {
+ +
); } diff --git a/app/(core)/components/Nav.tsx b/app/(core)/components/Nav.tsx index 21e22416..e425da10 100644 --- a/app/(core)/components/Nav.tsx +++ b/app/(core)/components/Nav.tsx @@ -2,6 +2,8 @@ import Link from "next/link"; import { usePathname } from "next/navigation"; import { useEffect, useRef, useState, useCallback } from "react"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faXmark } from "@fortawesome/free-solid-svg-icons"; import useTranslation from "../../(core)/hooks/useTranslation"; const menuItems = [ @@ -12,7 +14,11 @@ const menuItems = [ { href: "/contribute", label: "Contribute" }, ]; -export default function NavMenu() { +type NavMenuProps = { + onNavigate?: () => void; +}; + +export default function NavMenu({ onNavigate }: NavMenuProps) { const pathname = usePathname(); const navRef = useRef(null); const [underlineStyle, setUnderlineStyle] = useState({ left: 0, width: 0 }); @@ -44,14 +50,27 @@ export default function NavMenu() { return () => window.removeEventListener("resize", updateUnderline); }, [updateUnderline]); + const handleNavigate = useCallback(() => { + onNavigate?.(); + }, [onNavigate]); + return (