fix(webui): position sidebar highlight on mount

This commit is contained in:
Xubin Ren
2026-08-01 23:01:43 +08:00
parent 0cb7dd5cc9
commit db6c9effc3
5 changed files with 77 additions and 56 deletions
@@ -1,11 +1,11 @@
import {
type HTMLAttributes,
type RefObject,
useLayoutEffect,
useRef,
} from "react";
interface SidebarSelectionHighlightProps {
containerRef: RefObject<HTMLElement>;
interface SidebarSelectionHighlightProps extends HTMLAttributes<HTMLDivElement> {
targetRef: RefObject<HTMLElement>;
activeId: string | null;
scope: string;
@@ -18,11 +18,13 @@ export const SIDEBAR_SELECTION_ACTION_ITEM_CLASS =
"relative z-[1] transition-[width,padding,color] [transition-duration:300ms,300ms,150ms] ease-out motion-reduce:transition-none";
export function SidebarSelectionHighlight({
containerRef,
targetRef,
activeId,
scope,
children,
...containerProps
}: SidebarSelectionHighlightProps) {
const containerRef = useRef<HTMLDivElement>(null);
const highlightRef = useRef<HTMLDivElement>(null);
const positionedRef = useRef(false);
@@ -30,11 +32,19 @@ export function SidebarSelectionHighlight({
const highlight = highlightRef.current;
const container = containerRef.current;
const target = targetRef.current;
if (!highlight) return;
if (!activeId || !container || !target) {
highlight.style.opacity = "0";
positionedRef.current = false;
return;
}
let restoreTransitionFrame: number | null = null;
const position = () => {
if (!highlight) return;
if (!activeId || !container || !target) {
const containerRect = container.getBoundingClientRect();
const targetRect = target.getBoundingClientRect();
if (targetRect.width === 0 || targetRect.height === 0) {
highlight.style.opacity = "0";
positionedRef.current = false;
return;
@@ -43,8 +53,6 @@ export function SidebarSelectionHighlight({
const firstPosition = !positionedRef.current;
if (firstPosition) highlight.style.transitionProperty = "none";
const containerRect = container.getBoundingClientRect();
const targetRect = target.getBoundingClientRect();
highlight.style.width = `${targetRect.width}px`;
highlight.style.height = `${targetRect.height}px`;
highlight.style.transform = `translate3d(${targetRect.left - containerRect.left}px, ${
@@ -64,8 +72,8 @@ export function SidebarSelectionHighlight({
position();
const resizeObserver =
typeof ResizeObserver === "undefined" ? null : new ResizeObserver(position);
if (container) resizeObserver?.observe(container);
if (target) resizeObserver?.observe(target);
resizeObserver?.observe(container);
resizeObserver?.observe(target);
window.addEventListener("resize", position);
return () => {
@@ -79,12 +87,15 @@ export function SidebarSelectionHighlight({
});
return (
<div
ref={highlightRef}
data-testid={`${scope}-selection-highlight`}
data-active-id={activeId ?? undefined}
aria-hidden="true"
className="pointer-events-none absolute left-0 top-0 z-0 !mt-0 rounded-xl bg-sidebar-foreground/[0.055] opacity-0 transition-[transform,width,height] duration-300 ease-out will-change-transform motion-reduce:transition-none dark:bg-white/[0.07]"
/>
<div {...containerProps} ref={containerRef}>
{children}
<div
ref={highlightRef}
data-testid={`${scope}-selection-highlight`}
data-active-id={activeId ?? undefined}
aria-hidden="true"
className="pointer-events-none absolute left-0 top-0 z-0 !mt-0 rounded-xl bg-sidebar-foreground/[0.055] opacity-0 transition-[transform,width,height] duration-300 ease-out will-change-transform motion-reduce:transition-none dark:bg-white/[0.07]"
/>
</div>
);
}