add optional ref to always scroll to top

the more filters in explore was not scrolled to the top on open due to the use of framer motion
This commit is contained in:
Josh Hawkins 2025-11-07 07:45:07 -06:00
parent b1eca87cb5
commit b93dcf151d
2 changed files with 28 additions and 3 deletions

View File

@ -4,6 +4,7 @@ import {
useEffect, useEffect,
useState, useState,
useCallback, useCallback,
useRef,
} from "react"; } from "react";
import { createPortal } from "react-dom"; import { createPortal } from "react-dom";
import { motion, AnimatePresence } from "framer-motion"; import { motion, AnimatePresence } from "framer-motion";
@ -121,17 +122,20 @@ export function MobilePagePortal({
type MobilePageContentProps = { type MobilePageContentProps = {
children: React.ReactNode; children: React.ReactNode;
className?: string; className?: string;
scrollerRef?: React.RefObject<HTMLDivElement>;
}; };
export function MobilePageContent({ export function MobilePageContent({
children, children,
className, className,
scrollerRef,
}: MobilePageContentProps) { }: MobilePageContentProps) {
const context = useContext(MobilePageContext); const context = useContext(MobilePageContext);
if (!context) if (!context)
throw new Error("MobilePageContent must be used within MobilePage"); throw new Error("MobilePageContent must be used within MobilePage");
const [isVisible, setIsVisible] = useState(context.open); const [isVisible, setIsVisible] = useState(context.open);
const containerRef = useRef<HTMLDivElement | null>(null);
useEffect(() => { useEffect(() => {
if (context.open) { if (context.open) {
@ -140,15 +144,27 @@ export function MobilePageContent({
}, [context.open]); }, [context.open]);
const handleAnimationComplete = () => { const handleAnimationComplete = () => {
if (!context.open) { if (context.open) {
// After opening animation completes, ensure scroller is at the top
if (scrollerRef?.current) {
scrollerRef.current.scrollTop = 0;
}
} else {
setIsVisible(false); setIsVisible(false);
} }
}; };
useEffect(() => {
if (context.open && scrollerRef?.current) {
scrollerRef.current.scrollTop = 0;
}
}, [context.open, scrollerRef]);
return ( return (
<AnimatePresence> <AnimatePresence>
{isVisible && ( {isVisible && (
<motion.div <motion.div
ref={containerRef}
className={cn( className={cn(
"fixed inset-0 z-50 mb-12 bg-background", "fixed inset-0 z-50 mb-12 bg-background",
isPWA && "mb-16", isPWA && "mb-16",

View File

@ -22,6 +22,7 @@ import {
} from "@/components/ui/sheet"; } from "@/components/ui/sheet";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { isMobile } from "react-device-detect"; import { isMobile } from "react-device-detect";
import { useRef } from "react";
type PlatformAwareDialogProps = { type PlatformAwareDialogProps = {
trigger: JSX.Element; trigger: JSX.Element;
@ -80,6 +81,8 @@ export function PlatformAwareSheet({
open, open,
onOpenChange, onOpenChange,
}: PlatformAwareSheetProps) { }: PlatformAwareSheetProps) {
const scrollerRef = useRef<HTMLDivElement>(null);
if (isMobile) { if (isMobile) {
return ( return (
<MobilePage open={open} onOpenChange={onOpenChange}> <MobilePage open={open} onOpenChange={onOpenChange}>
@ -87,14 +90,20 @@ export function PlatformAwareSheet({
{trigger} {trigger}
</MobilePageTrigger> </MobilePageTrigger>
<MobilePagePortal> <MobilePagePortal>
<MobilePageContent className="flex h-full flex-col"> <MobilePageContent
className="flex h-full flex-col"
scrollerRef={scrollerRef}
>
<MobilePageHeader <MobilePageHeader
className="mx-2" className="mx-2"
onClose={() => onOpenChange(false)} onClose={() => onOpenChange(false)}
> >
<MobilePageTitle>{title}</MobilePageTitle> <MobilePageTitle>{title}</MobilePageTitle>
</MobilePageHeader> </MobilePageHeader>
<div className={cn("flex-1 overflow-y-auto", contentClassName)}> <div
ref={scrollerRef}
className={cn("flex-1 overflow-y-auto", contentClassName)}
>
{content} {content}
</div> </div>
</MobilePageContent> </MobilePageContent>