Skip to content

Commit

Permalink
feat(website): add scroll to top button (#965)
Browse files Browse the repository at this point in the history
  • Loading branch information
ayuhito committed Apr 16, 2024
1 parent ae11885 commit dc8167e
Show file tree
Hide file tree
Showing 6 changed files with 133 additions and 3 deletions.
25 changes: 25 additions & 0 deletions 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 (
<svg
xmlns="http://www.w3.org/2000/svg"
height={20}
width={20}
fill="none"
className={classes.icon}
{...others}
>
<path
stroke={stroke ?? '#fff'}
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={3.5}
d="M4.8 2.4h14.4M17 14l-5-5-5 5M12 9v12"
/>
</svg>
);
};

export { IconUp };
4 changes: 2 additions & 2 deletions website/app/components/search/Hits.tsx
Expand Up @@ -138,7 +138,7 @@ const InfiniteHits = observer(({ state$ }: InfiniteHitsProps) => {
}

return (
<Box>
<div id="hits">
<Sort state$={state$} count={results.nbHits} />
{display === 'grid' ? (
<SimpleGrid cols={{ base: 1, sm: 2, md: 3, xl: 4 }} spacing={16}>
Expand All @@ -149,7 +149,7 @@ const InfiniteHits = observer(({ state$ }: InfiniteHitsProps) => {
<HitsMap state$={state$} hits={hits} sentinelRef={sentinelRef} />
</SimpleGrid>
)}
</Box>
</div>
);
});

Expand Down
31 changes: 31 additions & 0 deletions 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;
}
}
65 changes: 65 additions & 0 deletions 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<HTMLElement>;
}

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 (
<Transition
mounted={showButton}
transition="fade-up"
duration={300}
timingFunction="ease"
>
{(styles) => (
<Portal target={containerId}>
<ActionIcon
style={styles}
className={classes.button}
onClick={scrollUp}
aria-label="Scroll to top"
>
<IconUp height={24} width={24} />
</ActionIcon>
</Portal>
)}
</Transition>
);
};

export { ScrollToTop };
6 changes: 5 additions & 1 deletion website/app/routes/_index.tsx
Expand Up @@ -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,
Expand All @@ -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';

Expand Down Expand Up @@ -165,6 +167,7 @@ export const loader = async ({ request }: LoaderFunctionArgs) => {

export default function Index() {
const { serverState, serverUrl } = useLoaderData<typeof loader>();
const searchRef = useRef(null);

const state$ = useObservable<SearchObject>({
size: 32,
Expand Down Expand Up @@ -195,12 +198,13 @@ export default function Index() {
future={{ preserveSharedStateOnUnmount: true }}
>
<Box className={classes.background}>
<Box className={classes.container}>
<Box className={classes.container} ref={searchRef}>
<Filters state$={state$} />
</Box>
</Box>
<Box className={classes.container}>
<InfiniteHits state$={state$} />
<ScrollToTop containerId="#hits" targetRef={searchRef} />
</Box>
</InstantSearch>
</InstantSearchSSRProvider>
Expand Down
5 changes: 5 additions & 0 deletions website/public/icons/up.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit dc8167e

Please sign in to comment.