Browse Source

fix: reset showHint to false when click Next Challenge (#4948)

* fix: reset showHint to false when click Next Challenge

* Split Challenges into two components

* clean code, add  to flag whether the activeChallenge has changed to avoid scrolling to Chalenges when page mounted

* Simplify

Co-authored-by: Dan Abramov <dan.abramov@gmail.com>
main
Guihua Ran 2 years ago
committed by GitHub
parent
commit
2d42098bc8
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 130
      beta/src/components/MDX/Challenges/Challenge.tsx
  2. 162
      beta/src/components/MDX/Challenges/Challenges.tsx
  3. 17
      beta/src/components/MDX/Challenges/Navigation.tsx

130
beta/src/components/MDX/Challenges/Challenge.tsx

@ -0,0 +1,130 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*/
import * as React from 'react';
import cn from 'classnames';
import {Button} from 'components/Button';
import {ChallengeContents} from './Challenges';
import {IconHint} from '../../Icon/IconHint';
import {IconSolution} from '../../Icon/IconSolution';
import {IconArrowSmall} from '../../Icon/IconArrowSmall';
interface ChallengeProps {
isRecipes?: boolean;
totalChallenges: number;
currentChallenge: ChallengeContents;
hasNextChallenge: boolean;
handleClickNextChallenge: () => void;
}
export function Challenge({
isRecipes,
totalChallenges,
currentChallenge,
hasNextChallenge,
handleClickNextChallenge,
}: ChallengeProps) {
const [showHint, setShowHint] = React.useState(false);
const [showSolution, setShowSolution] = React.useState(false);
const toggleHint = () => {
if (showSolution && !showHint) {
setShowSolution(false);
}
setShowHint((hint) => !hint);
};
const toggleSolution = () => {
if (showHint && !showSolution) {
setShowHint(false);
}
setShowSolution((solution) => !solution);
};
return (
<div className="p-5 sm:py-8 sm:px-8">
<div>
<h3 className="text-xl text-primary dark:text-primary-dark mb-2">
<div className="font-bold block md:inline">
{isRecipes ? 'Example' : 'Challenge'} {currentChallenge.order} of{' '}
{totalChallenges}
<span className="text-primary dark:text-primary-dark">: </span>
</div>
{currentChallenge.name}
</h3>
{currentChallenge.content}
</div>
<div className="flex justify-between items-center mt-4">
{currentChallenge.hint ? (
<div>
<Button className="mr-2" onClick={toggleHint} active={showHint}>
<IconHint className="mr-1.5" />{' '}
{showHint ? 'Hide hint' : 'Show hint'}
</Button>
<Button
className="mr-2"
onClick={toggleSolution}
active={showSolution}>
<IconSolution className="mr-1.5" />{' '}
{showSolution ? 'Hide solution' : 'Show solution'}
</Button>
</div>
) : (
!isRecipes && (
<Button
className="mr-2"
onClick={toggleSolution}
active={showSolution}>
<IconSolution className="mr-1.5" />{' '}
{showSolution ? 'Hide solution' : 'Show solution'}
</Button>
)
)}
{hasNextChallenge && (
<Button
className={cn(
isRecipes
? 'bg-purple-50 border-purple-50 hover:bg-purple-50 focus:bg-purple-50 active:bg-purple-50'
: 'bg-link dark:bg-link-dark'
)}
onClick={handleClickNextChallenge}
active>
Next {isRecipes ? 'Example' : 'Challenge'}
<IconArrowSmall displayDirection="right" className="block ml-1.5" />
</Button>
)}
</div>
{showHint && currentChallenge.hint}
{showSolution && (
<div className="mt-6">
<h3 className="text-2xl font-bold text-primary dark:text-primary-dark">
Solution
</h3>
{currentChallenge.solution}
<div className="flex justify-between items-center mt-4">
<Button onClick={() => setShowSolution(false)}>
Close solution
</Button>
{hasNextChallenge && (
<Button
className={cn(
isRecipes ? 'bg-purple-50' : 'bg-link dark:bg-link-dark'
)}
onClick={handleClickNextChallenge}
active>
Next Challenge
<IconArrowSmall
displayDirection="right"
className="block ml-1.5"
/>
</Button>
)}
</div>
</div>
)}
</div>
);
}

162
beta/src/components/MDX/Challenges/Challenges.tsx

@ -4,13 +4,10 @@
import * as React from 'react';
import cn from 'classnames';
import {Button} from 'components/Button';
import {H2} from 'components/MDX/Heading';
import {H4} from 'components/MDX/Heading';
import {Challenge} from './Challenge';
import {Navigation} from './Navigation';
import {IconHint} from '../../Icon/IconHint';
import {IconSolution} from '../../Icon/IconSolution';
import {IconArrowSmall} from '../../Icon/IconArrowSmall';
interface ChallengesProps {
children: React.ReactElement[];
@ -76,42 +73,26 @@ export function Challenges({
titleId = isRecipes ? 'examples' : 'challenges',
}: ChallengesProps) {
const challenges = parseChallengeContents(children);
const totalChallenges = challenges.length;
const scrollAnchorRef = React.useRef<HTMLDivElement>(null);
const [showHint, setShowHint] = React.useState(false);
const [showSolution, setShowSolution] = React.useState(false);
const [activeChallenge, setActiveChallenge] = React.useState(
challenges[0].id
);
const handleChallengeChange = (challengeId: string) => {
setShowHint(false);
setShowSolution(false);
setActiveChallenge(challengeId);
};
const toggleHint = () => {
if (showSolution && !showHint) {
setShowSolution(false);
const queuedScrollRef = React.useRef<boolean>(false);
const [activeIndex, setActiveIndex] = React.useState(0);
const currentChallenge = challenges[activeIndex];
React.useEffect(() => {
if (queuedScrollRef.current === true) {
queuedScrollRef.current = false;
scrollAnchorRef.current!.scrollIntoView({
block: 'start',
behavior: 'smooth',
});
}
setShowHint((hint) => !hint);
};
});
const toggleSolution = () => {
if (showHint && !showSolution) {
setShowHint(false);
}
setShowSolution((solution) => !solution);
const handleChallengeChange = (index: number) => {
setActiveIndex(index);
};
const currentChallenge = challenges.find(({id}) => id === activeChallenge);
if (currentChallenge === undefined) {
throw new TypeError('currentChallenge should always exist');
}
const nextChallenge = challenges.find(({order}) => {
return order === currentChallenge.order + 1;
});
const Heading = isRecipes ? H4 : H2;
return (
<div className="max-w-7xl mx-auto py-4">
@ -130,7 +111,7 @@ export function Challenges({
)}>
{titleText}
</Heading>
{challenges.length > 1 && (
{totalChallenges > 1 && (
<Navigation
currentChallenge={currentChallenge}
challenges={challenges}
@ -139,104 +120,17 @@ export function Challenges({
/>
)}
</div>
<div className="p-5 sm:py-8 sm:px-8">
<div key={activeChallenge}>
<h3 className="text-xl text-primary dark:text-primary-dark mb-2">
<div className="font-bold block md:inline">
{isRecipes ? 'Example' : 'Challenge'} {currentChallenge.order}{' '}
of {challenges.length}
<span className="text-primary dark:text-primary-dark">: </span>
</div>
{currentChallenge.name}
</h3>
<>{currentChallenge.content}</>
</div>
<div className="flex justify-between items-center mt-4">
{currentChallenge.hint ? (
<div>
<Button className="mr-2" onClick={toggleHint} active={showHint}>
<IconHint className="mr-1.5" />{' '}
{showHint ? 'Hide hint' : 'Show hint'}
</Button>
<Button
className="mr-2"
onClick={toggleSolution}
active={showSolution}>
<IconSolution className="mr-1.5" />{' '}
{showSolution ? 'Hide solution' : 'Show solution'}
</Button>
</div>
) : (
!isRecipes && (
<Button
className="mr-2"
onClick={toggleSolution}
active={showSolution}>
<IconSolution className="mr-1.5" />{' '}
{showSolution ? 'Hide solution' : 'Show solution'}
</Button>
)
)}
{nextChallenge && (
<Button
className={cn(
isRecipes
? 'bg-purple-50 border-purple-50 hover:bg-purple-50 focus:bg-purple-50 active:bg-purple-50'
: 'bg-link dark:bg-link-dark'
)}
onClick={() => {
setActiveChallenge(nextChallenge.id);
setShowSolution(false);
}}
active>
Next {isRecipes ? 'Example' : 'Challenge'}
<IconArrowSmall
displayDirection="right"
className="block ml-1.5"
/>
</Button>
)}
</div>
{showHint && currentChallenge.hint}
{showSolution && (
<div className="mt-6">
<h3 className="text-2xl font-bold text-primary dark:text-primary-dark">
Solution
</h3>
{currentChallenge.solution}
<div className="flex justify-between items-center mt-4">
<Button onClick={() => setShowSolution(false)}>
Close solution
</Button>
{nextChallenge && (
<Button
className={cn(
isRecipes ? 'bg-purple-50' : 'bg-link dark:bg-link-dark'
)}
onClick={() => {
setActiveChallenge(nextChallenge.id);
setShowSolution(false);
if (scrollAnchorRef.current) {
scrollAnchorRef.current.scrollIntoView({
block: 'start',
behavior: 'smooth',
});
}
}}
active>
Next Challenge
<IconArrowSmall
displayDirection="right"
className="block ml-1.5"
/>
</Button>
)}
</div>
</div>
)}
</div>
<Challenge
key={currentChallenge.id}
isRecipes={isRecipes}
currentChallenge={currentChallenge}
totalChallenges={totalChallenges}
hasNextChallenge={activeIndex < totalChallenges - 1}
handleClickNextChallenge={() => {
setActiveIndex((i) => i + 1);
queuedScrollRef.current = true;
}}
/>
</div>
</div>
);

17
beta/src/components/MDX/Challenges/Navigation.tsx

@ -15,7 +15,7 @@ export function Navigation({
isRecipes,
}: {
challenges: ChallengeContents[];
handleChange: (id: string) => void;
handleChange: (index: number) => void;
currentChallenge: ChallengeContents;
isRecipes?: boolean;
}) {
@ -36,7 +36,7 @@ export function Navigation({
if (containerRef.current) {
containerRef.current.scrollLeft = currentNavRef.offsetLeft;
}
handleChange(challenges[scrollPos + 1].id);
handleChange(scrollPos + 1);
}
};
@ -49,19 +49,16 @@ export function Navigation({
if (containerRef.current) {
containerRef.current.scrollLeft = currentNavRef.offsetLeft;
}
handleChange(challenges[scrollPos - 1].id);
handleChange(scrollPos - 1);
}
};
const handleSelectNav = (id: string) => {
const selectedChallenge = challenges.findIndex(
(challenge) => challenge.id === id
);
const currentNavRef = challengesNavRef.current[selectedChallenge].current;
const handleSelectNav = (index: number) => {
const currentNavRef = challengesNavRef.current[index].current;
if (containerRef.current) {
containerRef.current.scrollLeft = currentNavRef?.offsetLeft || 0;
}
handleChange(id);
handleChange(index);
};
const handleResize = React.useCallback(() => {
@ -97,7 +94,7 @@ export function Navigation({
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(id)}
onClick={() => handleSelectNav(index)}
key={`button-${id}`}
ref={challengesNavRef.current[index]}>
{order}. {name}

Loading…
Cancel
Save