diff --git a/.all-contributorsrc b/.all-contributorsrc index 7b9c595..08b3697 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/.github/workflows/prchecks.yml b/.github/workflows/prchecks.yml index 7b849bf..95ad8fd 100644 --- a/.github/workflows/prchecks.yml +++ b/.github/workflows/prchecks.yml @@ -10,6 +10,9 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v4 + with: + repository: ${{ github.event.pull_request.head.repo.full_name }} + ref: ${{ github.event.pull_request.head.sha }} - name: Setup Node.js uses: actions/setup-node@v4 diff --git a/.github/workflows/prcontributorbot.yaml b/.github/workflows/prcontributorbot.yaml index 2c048b5..51dfd42 100644 --- a/.github/workflows/prcontributorbot.yaml +++ b/.github/workflows/prcontributorbot.yaml @@ -9,6 +9,7 @@ on: jobs: format: runs-on: ubuntu-latest + if: ${{ github.event.pull_request.head.repo.full_name == github.repository }} permissions: contents: write @@ -17,7 +18,8 @@ jobs: - name: Checkout PR branch uses: actions/checkout@v4 with: - ref: ${{ github.head_ref }} + repository: ${{ github.event.pull_request.head.repo.full_name }} + ref: ${{ github.event.pull_request.head.sha }} - name: Setup Node uses: actions/setup-node@v4 diff --git a/.husky/pre-commit b/.husky/pre-commit index 4e058a5..ba403f4 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 dc45246..12456db 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 21e2241..e425da1 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 (