Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .all-contributorsrc
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
}
]
}
3 changes: 3 additions & 0 deletions .github/workflows/prchecks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 3 additions & 1 deletion .github/workflows/prcontributorbot.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
3 changes: 0 additions & 3 deletions .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -1,5 +1,2 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

npm run pre-commit
npx lint-staged
91 changes: 89 additions & 2 deletions app/(core)/components/Header.jsx
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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);
Expand All @@ -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 (
<header
Expand All @@ -46,14 +126,21 @@ export default function Header() {
<FontAwesomeIcon icon={faHamburger} />
</button>

<NavMenu isOpen={isMenuOpen} />
<NavMenu onNavigate={handleMenuClose} />

<div className="controls">
<GoogleTranslator />
<GitHubHeaderBadge mode={mode} />
<Theme mode={mode} onToggle={toggleMode} />
</div>
</div>

<button
className={`nav-backdrop ${isMenuOpen ? "open" : ""}`}
type="button"
aria-label={t("Close menu")}
onClick={handleMenuClose}
/>
</header>
);
}
21 changes: 20 additions & 1 deletion app/(core)/components/Nav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
Expand All @@ -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<HTMLUListElement>(null);
const [underlineStyle, setUnderlineStyle] = useState({ left: 0, width: 0 });
Expand Down Expand Up @@ -44,14 +50,27 @@ export default function NavMenu() {
return () => window.removeEventListener("resize", updateUnderline);
}, [updateUnderline]);

const handleNavigate = useCallback(() => {
onNavigate?.();
}, [onNavigate]);

return (
<nav className={`nav-menu ${isCompleted ? "notranslate" : ""}`}>
<button
className="nav-close"
type="button"
aria-label={t("Close menu")}
onClick={handleNavigate}
>
<FontAwesomeIcon icon={faXmark} aria-hidden="true" />
</button>
<ul className="nav-list" ref={navRef}>
{menuItems.map(({ href, label }) => (
<li key={href}>
<Link
href={href}
className={`nav-link ${pathname === href ? "active" : ""}`}
onClick={handleNavigate}
>
{t(label)}
</Link>
Expand Down
2 changes: 1 addition & 1 deletion app/(core)/components/inputs/DynamicInputs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ interface Props {
}

export default function DynamicInputs({ config, values, onChange }: Props) {
const { t, meta } = useTranslation();
const { meta } = useTranslation();
const isCompleted = meta?.completed || false;
const [lastValidValues, setLastValidValues] =
useState<Record<string, string | number | boolean>>(values);
Expand Down
1 change: 0 additions & 1 deletion app/(core)/data/configs/BallAcceleration.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
// app/data/configs/BallAcceleration.js
import { physicsYToScreenY } from "../../constants/Utils.js";

export const INITIAL_INPUTS = {
size: 0.5, // diametro palla in metri
Expand Down
3 changes: 1 addition & 2 deletions app/(core)/data/configs/BouncingBall.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
// app/data/configs/bouncingBall.js
import { invertYAxis } from "../../constants/Utils.js";
import { gravityTypes, EARTH_G_SI } from "../../constants/Config.js";

export const INITIAL_INPUTS = {
Expand Down Expand Up @@ -86,7 +85,7 @@ export const FORCES = [
*/
export const SimInfoMapper = (state, context, refs) => {
const { pos, vel, mass } = state;
const { gravity, canvasHeight } = context;
const { gravity } = context;
const { maxHeightRef } = refs;

// FIX: pos is already in meters — no unit conversion needed.
Expand Down
4 changes: 1 addition & 3 deletions app/(core)/data/configs/CircularMotion.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { physicsYToScreenY } from "../../constants/Utils.js";

export const INITIAL_INPUTS = {
radius: 1,
speed: 2,
Expand Down Expand Up @@ -50,7 +48,7 @@ export const FORCES = [
{
key: "centripetal",
color: "blue",
computeFn: ({ pos, center, mass, speed, radius }) => {
computeFn: ({ pos, center, speed, radius }) => {
if (!pos || !center) return null;

const dx = center.x - pos.x;
Expand Down
4 changes: 1 addition & 3 deletions app/(core)/data/configs/HorizontalSpring.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { EARTH_G_SI } from "../../constants/Config.js";

// Initial values (SI units, Y-up physics coordinates)
export const INITIAL_INPUTS = {
bobMass: 1,
Expand Down Expand Up @@ -69,7 +67,7 @@ export const INPUT_FIELDS = [
{ name: "springColor", label: "Spring color:", type: "color" },
];

export const SimInfoMapper = (state, context) => {
export const SimInfoMapper = (state) => {
const {
pos,
vel,
Expand Down
1 change: 0 additions & 1 deletion app/(core)/data/configs/SimplePendulum.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
// app/data/configs/SimplePendulum.js
import { EARTH_G_SI, gravityTypes } from "../../constants/Config.js";

// Valori iniziali della simulazione (SI)
export const INITIAL_INPUTS = {
Expand Down
Loading
Loading