fix: scroll active sidebar items into view (#4965)

* Add centering of settings sidebar link when isActive

* abstract redundant logic into a reusable hook

* add jsdocs | refactor hook to consume behavior and block args

* remove unused import

* dev

---------

Co-authored-by: Timothy Carambat <rambat1010@gmail.com>
This commit is contained in:
Marcello Fitton
2026-02-06 19:23:14 -08:00
committed by GitHub
parent 71cfff8091
commit 8f7e0fb1f8
4 changed files with 52 additions and 7 deletions

View File

@@ -6,7 +6,7 @@ concurrency:
on:
push:
branches: ["web-push-notifications-bootstrap"] # put your current branch to create a build. Core team only.
branches: ["4963-sidebar-selection-srcoll-into-view"] # put your current branch to create a build. Core team only.
paths-ignore:
- "**.md"
- "cloud-deployments/*"

View File

@@ -2,6 +2,7 @@ import React, { useEffect, useState } from "react";
import { CaretRight } from "@phosphor-icons/react";
import { Link, useLocation } from "react-router-dom";
import { safeJsonParse } from "@/utils/request";
import useScrollActiveItemIntoView from "@/hooks/useScrollActiveItemIntoView";
export default function MenuOption({
btnText,
@@ -25,6 +26,18 @@ export default function MenuOption({
location: location.pathname,
});
const isActive = hasChildren
? (!isExpanded &&
childOptions.some((child) => child.href === location.pathname)) ||
location.pathname === href
: location.pathname === href;
const { ref } = useScrollActiveItemIntoView({
isActive,
behavior: "instant",
block: "center",
});
if (hidden) return null;
// If this option is a parent level option
@@ -43,12 +56,6 @@ export default function MenuOption({
if (flex && !!user && !roles.includes(user?.role)) return null;
}
const isActive = hasChildren
? (!isExpanded &&
childOptions.some((child) => child.href === location.pathname)) ||
location.pathname === href
: location.pathname === href;
const handleClick = (e) => {
if (hasChildren) {
e.preventDefault();
@@ -73,6 +80,7 @@ export default function MenuOption({
`}
>
<Link
ref={ref}
to={href}
className={`flex flex-grow items-center px-[12px] h-[32px] font-medium ${
isChild ? "hover:text-white" : "text-white light:text-black"

View File

@@ -1,3 +1,4 @@
import useScrollActiveItemIntoView from "@/hooks/useScrollActiveItemIntoView";
import Workspace from "@/models/workspace";
import paths from "@/utils/paths";
import showToast from "@/utils/toast";
@@ -30,6 +31,11 @@ export default function ThreadItem({
? paths.workspace.chat(slug)
: paths.workspace.thread(slug, thread.slug);
const { ref } = useScrollActiveItemIntoView({
isActive,
behavior: "instant",
block: "center",
});
return (
<div
className="w-full relative flex h-[38px] items-center border-none rounded-lg"
@@ -88,6 +94,7 @@ export default function ThreadItem({
</div>
) : (
<a
ref={ref}
href={
window.location.pathname === linkTo || ctrlPressed ? "#" : linkTo
}

View File

@@ -0,0 +1,30 @@
import { useEffect, useRef } from "react";
/**
* Hook that scrolls an element into view when it becomes active.
* @param {Object} options - The options for the hook.
* @param {boolean} options.isActive - Whether the element is currently active.
* @param {"smooth" | "instant" | "auto"} options.behavior - The scroll behavior.
* @param {"start" | "center" | "end" | "nearest"} options.block - The vertical alignment of the element within the scrollable container.
* @returns {{ ref: React.RefObject<HTMLElement> }} An object containing the ref to attach to the target element.
*/
export default function useScrollActiveItemIntoView({
isActive,
behavior,
block,
}) {
const ref = useRef(null);
useEffect(() => {
if (isActive) {
ref.current.scrollIntoView({
behavior,
block,
});
}
}, [isActive]);
return {
ref,
};
}