feat: implement smooth scrolling for provider sidebar in ModelSelector

This commit is contained in:
Anish Sarkar 2026-04-27 01:19:05 +05:30
parent 5e756c8dfa
commit e95e417cc8

View file

@ -320,6 +320,30 @@ export function ModelSelector({
[isMobile] [isMobile]
); );
const scrollProviderSidebar = useCallback(
(direction: "backward" | "forward") => {
const el = providerSidebarRef.current;
if (!el) return;
const delta = isMobile
? Math.max(56, Math.floor(el.clientWidth * 0.5))
: Math.max(44, Math.floor(el.clientHeight * 0.4));
if (isMobile) {
el.scrollBy({
left: direction === "backward" ? -delta : delta,
behavior: "smooth",
});
return;
}
el.scrollBy({
top: direction === "backward" ? -delta : delta,
behavior: "smooth",
});
},
[isMobile]
);
// Cmd/Ctrl+M shortcut (desktop only) // Cmd/Ctrl+M shortcut (desktop only)
useEffect(() => { useEffect(() => {
if (isMobile) return; if (isMobile) return;
@ -716,17 +740,40 @@ export function ModelSelector({
return ( return (
<div <div
className={cn( className={cn(
"shrink-0 border-border/50 flex", "shrink-0 border-border/50 flex relative",
isMobile ? "flex-row items-center border-b border-border/40" : "flex-col w-10 border-r" isMobile
? "flex-row items-center border-b border-border/40"
: "flex-col w-10 border-r"
)} )}
> >
{!isMobile && sidebarScrollPos !== "top" && ( {!isMobile && (
<div className="flex items-center justify-center py-0.5 pointer-events-none"> <div
<ChevronUp className="size-3 text-muted-foreground" /> className={cn(
"absolute top-0 left-0 right-0 z-10 h-5 flex items-center justify-center transition-all duration-200 ease-out",
sidebarScrollPos === "top"
? "opacity-0 -translate-y-1 pointer-events-none"
: "opacity-100 translate-y-0 pointer-events-auto"
)}
>
<button
type="button"
aria-label="Scroll providers up"
onClick={() => scrollProviderSidebar("backward")}
className="flex h-4 w-4 items-center justify-center rounded-sm text-muted-foreground/90 hover:text-foreground hover:bg-accent/60 transition-colors"
>
<ChevronUp className="size-3" />
</button>
</div> </div>
)} )}
{isMobile && sidebarScrollPos !== "top" && ( {isMobile && (
<div className="flex items-center justify-center px-0.5 shrink-0 pointer-events-none"> <div
className={cn(
"absolute left-0 top-0 bottom-0 z-10 w-5 flex items-center justify-center transition-all duration-200 ease-out pointer-events-none",
sidebarScrollPos === "top"
? "opacity-0 -translate-x-1"
: "opacity-100 translate-x-0"
)}
>
<ChevronLeft className="size-3 text-muted-foreground" /> <ChevronLeft className="size-3 text-muted-foreground" />
</div> </div>
)} )}
@ -802,13 +849,34 @@ export function ModelSelector({
); );
})} })}
</div> </div>
{!isMobile && sidebarScrollPos !== "bottom" && ( {!isMobile && (
<div className="flex items-center justify-center py-0.5 pointer-events-none"> <div
<ChevronDown className="size-3 text-muted-foreground" /> className={cn(
"absolute bottom-0 left-0 right-0 z-10 h-5 flex items-center justify-center transition-all duration-200 ease-out",
sidebarScrollPos === "bottom"
? "opacity-0 translate-y-1 pointer-events-none"
: "opacity-100 translate-y-0 pointer-events-auto"
)}
>
<button
type="button"
aria-label="Scroll providers down"
onClick={() => scrollProviderSidebar("forward")}
className="flex h-4 w-4 items-center justify-center rounded-sm text-muted-foreground/90 hover:text-foreground hover:bg-accent/60 transition-colors"
>
<ChevronDown className="size-3" />
</button>
</div> </div>
)} )}
{isMobile && sidebarScrollPos !== "bottom" && ( {isMobile && (
<div className="flex items-center justify-center px-0.5 shrink-0 pointer-events-none"> <div
className={cn(
"absolute right-0 top-0 bottom-0 z-10 w-5 flex items-center justify-center transition-all duration-200 ease-out pointer-events-none",
sidebarScrollPos === "bottom"
? "opacity-0 translate-x-1"
: "opacity-100 translate-x-0"
)}
>
<ChevronRight className="size-3 text-muted-foreground" /> <ChevronRight className="size-3 text-muted-foreground" />
</div> </div>
)} )}