diff --git a/website/app/components/icons/Up.tsx b/website/app/components/icons/Up.tsx new file mode 100644 index 0000000000..4b3e5184fa --- /dev/null +++ b/website/app/components/icons/Up.tsx @@ -0,0 +1,25 @@ +import classes from './Icon.module.css'; +import type { IconProps } from './types'; + +const IconUp = ({ stroke, ...others }: IconProps) => { + return ( + + + + ); +}; + +export { IconUp }; diff --git a/website/app/components/search/Hits.tsx b/website/app/components/search/Hits.tsx index 8123dbece9..75c484a9de 100644 --- a/website/app/components/search/Hits.tsx +++ b/website/app/components/search/Hits.tsx @@ -138,7 +138,7 @@ const InfiniteHits = observer(({ state$ }: InfiniteHitsProps) => { } return ( - +
{display === 'grid' ? ( @@ -149,7 +149,7 @@ const InfiniteHits = observer(({ state$ }: InfiniteHitsProps) => { )} - +
); }); diff --git a/website/app/components/search/ScrollToTop.module.css b/website/app/components/search/ScrollToTop.module.css new file mode 100644 index 0000000000..4843c9918a --- /dev/null +++ b/website/app/components/search/ScrollToTop.module.css @@ -0,0 +1,31 @@ +.button { + position: fixed; + + bottom: 30px; + right: 55px; + + height: 56px; + width: 56px; + border-radius: 48px; + + @media (min-width: $mantine-breakpoint-xs) { + right: 60px; + } + + @media (min-width: $mantine-breakpoint-sm) { + bottom: 24px; + right: 56px; + } + + @media (min-width: $mantine-breakpoint-md) { + right: 56px; + } + + @media (min-width: $mantine-breakpoint-lg) { + right: 100px; + } + + @media (min-width: $mantine-breakpoint-xl) { + right: 80px; + } +} diff --git a/website/app/components/search/ScrollToTop.tsx b/website/app/components/search/ScrollToTop.tsx new file mode 100644 index 0000000000..23d52f01e1 --- /dev/null +++ b/website/app/components/search/ScrollToTop.tsx @@ -0,0 +1,65 @@ +import { ActionIcon, Portal, Transition } from '@mantine/core'; +import { useEffect, useState } from 'react'; + +import { IconUp } from '../icons/Up'; +import classes from './ScrollToTop.module.css'; + +interface ScrollToTopProps { + containerId: string; + targetRef: React.RefObject; +} + +const scrollUp = () => { + window.scrollTo({ + top: 0, + behavior: 'smooth', + }); +}; + +const ScrollToTop = ({ containerId, targetRef }: ScrollToTopProps) => { + const [showButton, setShowButton] = useState(false); + + useEffect(() => { + const observer = new IntersectionObserver( + ([entry]) => { + setShowButton(!entry.isIntersecting); + }, + { threshold: 0.5 }, + ); + + const targetNode = targetRef.current; + if (targetNode) { + observer.observe(targetNode); + } + + return () => { + if (targetNode) { + observer.unobserve(targetNode); + } + }; + }, [targetRef]); + + return ( + + {(styles) => ( + + + + + + )} + + ); +}; + +export { ScrollToTop }; diff --git a/website/app/routes/_index.tsx b/website/app/routes/_index.tsx index a8a64cc5fa..27b97acb0d 100644 --- a/website/app/routes/_index.tsx +++ b/website/app/routes/_index.tsx @@ -8,6 +8,7 @@ import { type UiState } from 'instantsearch.js'; // @ts-expect-error - No type definitions available import { history } from 'instantsearch.js/cjs/lib/routers/index.js'; import { type BrowserHistoryArgs } from 'instantsearch.js/es/lib/routers/history'; +import { useRef } from 'react'; import { renderToString } from 'react-dom/server'; import { getServerState, @@ -19,6 +20,7 @@ import { import { Filters } from '@/components/search/Filters'; import { InfiniteHits } from '@/components/search/Hits'; import { type SearchObject } from '@/components/search/observables'; +import { ScrollToTop } from '@/components/search/ScrollToTop'; import classes from '@/styles/global.module.css'; import { getSSRCache, setSSRCache } from '@/utils/algolia.server'; @@ -165,6 +167,7 @@ export const loader = async ({ request }: LoaderFunctionArgs) => { export default function Index() { const { serverState, serverUrl } = useLoaderData(); + const searchRef = useRef(null); const state$ = useObservable({ size: 32, @@ -195,12 +198,13 @@ export default function Index() { future={{ preserveSharedStateOnUnmount: true }} > - + + diff --git a/website/public/icons/up.svg b/website/public/icons/up.svg new file mode 100644 index 0000000000..8e0c13b9d9 --- /dev/null +++ b/website/public/icons/up.svg @@ -0,0 +1,5 @@ + + + + +