You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
134 lines
4.3 KiB
134 lines
4.3 KiB
/*
|
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
*/
|
|
|
|
import {useRef, useCallback, useEffect, createRef} from 'react';
|
|
import cn from 'classnames';
|
|
import {IconChevron} from 'components/Icon/IconChevron';
|
|
import {ChallengeContents} from './Challenges';
|
|
import {debounce} from 'debounce';
|
|
|
|
export function Navigation({
|
|
challenges,
|
|
handleChange,
|
|
currentChallenge,
|
|
isRecipes,
|
|
}: {
|
|
challenges: ChallengeContents[];
|
|
handleChange: (index: number) => void;
|
|
currentChallenge: ChallengeContents;
|
|
isRecipes?: boolean;
|
|
}) {
|
|
const containerRef = useRef<HTMLDivElement>(null);
|
|
const challengesNavRef = useRef(
|
|
challenges.map(() => createRef<HTMLButtonElement>())
|
|
);
|
|
const scrollPos = currentChallenge.order - 1;
|
|
const canScrollLeft = scrollPos > 0;
|
|
const canScrollRight = scrollPos < challenges.length - 1;
|
|
|
|
const handleScrollRight = () => {
|
|
if (scrollPos < challenges.length - 1) {
|
|
const currentNavRef = challengesNavRef.current[scrollPos + 1].current;
|
|
if (!currentNavRef) {
|
|
return;
|
|
}
|
|
if (containerRef.current) {
|
|
containerRef.current.scrollLeft = currentNavRef.offsetLeft;
|
|
}
|
|
handleChange(scrollPos + 1);
|
|
}
|
|
};
|
|
|
|
const handleScrollLeft = () => {
|
|
if (scrollPos > 0) {
|
|
const currentNavRef = challengesNavRef.current[scrollPos - 1].current;
|
|
if (!currentNavRef) {
|
|
return;
|
|
}
|
|
if (containerRef.current) {
|
|
containerRef.current.scrollLeft = currentNavRef.offsetLeft;
|
|
}
|
|
handleChange(scrollPos - 1);
|
|
}
|
|
};
|
|
|
|
const handleSelectNav = (index: number) => {
|
|
const currentNavRef = challengesNavRef.current[index].current;
|
|
if (containerRef.current) {
|
|
containerRef.current.scrollLeft = currentNavRef?.offsetLeft || 0;
|
|
}
|
|
handleChange(index);
|
|
};
|
|
|
|
const handleResize = useCallback(() => {
|
|
if (containerRef.current) {
|
|
const el = containerRef.current;
|
|
el.scrollLeft =
|
|
challengesNavRef.current[scrollPos].current?.offsetLeft || 0;
|
|
}
|
|
}, [containerRef, challengesNavRef, scrollPos]);
|
|
|
|
useEffect(() => {
|
|
handleResize();
|
|
const debouncedHandleResize = debounce(handleResize, 200);
|
|
window.addEventListener('resize', debouncedHandleResize);
|
|
return () => {
|
|
window.removeEventListener('resize', debouncedHandleResize);
|
|
};
|
|
}, [handleResize]);
|
|
|
|
return (
|
|
<div className="flex items-center justify-between">
|
|
<div className="overflow-hidden">
|
|
<div
|
|
ref={containerRef}
|
|
className="flex relative transition-transform content-box overflow-x-auto">
|
|
{challenges.map(({name, id, order}, index) => (
|
|
<button
|
|
className={cn(
|
|
'py-2 mr-4 text-base border-b-4 duration-100 ease-in transition whitespace-nowrap text-ellipsis',
|
|
isRecipes &&
|
|
currentChallenge.id === id &&
|
|
'text-purple-50 border-purple-50 hover:text-purple-50 dark:text-purple-30 dark:border-purple-30 dark:hover:text-purple-30',
|
|
!isRecipes &&
|
|
currentChallenge.id === id &&
|
|
'text-link border-link hover:text-link dark:text-link-dark dark:border-link-dark dark:hover:text-link-dark'
|
|
)}
|
|
onClick={() => handleSelectNav(index)}
|
|
key={`button-${id}`}
|
|
ref={challengesNavRef.current[index]}>
|
|
{order}. {name}
|
|
</button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
<div className="flex z-10 pb-2 pl-2">
|
|
<button
|
|
onClick={handleScrollLeft}
|
|
aria-label="Scroll left"
|
|
className={cn(
|
|
'bg-secondary-button dark:bg-secondary-button-dark h-8 px-2 rounded-l border-gray-20 border-r',
|
|
{
|
|
'text-primary dark:text-primary-dark': canScrollLeft,
|
|
'text-gray-30': !canScrollLeft,
|
|
}
|
|
)}>
|
|
<IconChevron displayDirection="left" />
|
|
</button>
|
|
<button
|
|
onClick={handleScrollRight}
|
|
aria-label="Scroll right"
|
|
className={cn(
|
|
'bg-secondary-button dark:bg-secondary-button-dark h-8 px-2 rounded-r',
|
|
{
|
|
'text-primary dark:text-primary-dark': canScrollRight,
|
|
'text-gray-30': !canScrollRight,
|
|
}
|
|
)}>
|
|
<IconChevron displayDirection="right" />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|