From 2e539fa81d597f5a7bdae6a0a8dae7b190f195e0 Mon Sep 17 00:00:00 2001 From: Jing Jiawei <30466424+ibarapascal@users.noreply.github.com> Date: Thu, 8 Dec 2022 01:50:50 +0900 Subject: [PATCH] [Beta] Anchor for individual challenges & deepdive (#5318) * [Beta] open deepdive content once access * [Beta] anchor individual challenges * [Beta] fix challenges anchor scroll when multiple * [Beta] refactor chanllenges anchor effects Co-authored-by: Jiawei.Jing --- .../components/MDX/Challenges/Challenge.tsx | 7 +++-- .../components/MDX/Challenges/Challenges.tsx | 31 +++++++++++++++---- beta/src/components/MDX/ExpandableExample.tsx | 26 +++++++++++++--- 3 files changed, 51 insertions(+), 13 deletions(-) diff --git a/beta/src/components/MDX/Challenges/Challenge.tsx b/beta/src/components/MDX/Challenges/Challenge.tsx index 82b3535ce..24e99541c 100644 --- a/beta/src/components/MDX/Challenges/Challenge.tsx +++ b/beta/src/components/MDX/Challenges/Challenge.tsx @@ -9,6 +9,7 @@ import {ChallengeContents} from './Challenges'; import {IconHint} from '../../Icon/IconHint'; import {IconSolution} from '../../Icon/IconSolution'; import {IconArrowSmall} from '../../Icon/IconArrowSmall'; +import {H4} from '../Heading'; interface ChallengeProps { isRecipes?: boolean; @@ -45,14 +46,16 @@ export function Challenge({ return (
-

+

{isRecipes ? 'Example' : 'Challenge'} {currentChallenge.order} of{' '} {totalChallenges} :
{currentChallenge.name} -

+ {currentChallenge.content}
diff --git a/beta/src/components/MDX/Challenges/Challenges.tsx b/beta/src/components/MDX/Challenges/Challenges.tsx index 70df4228b..25b1979fd 100644 --- a/beta/src/components/MDX/Challenges/Challenges.tsx +++ b/beta/src/components/MDX/Challenges/Challenges.tsx @@ -9,6 +9,7 @@ import {H2} from 'components/MDX/Heading'; import {H4} from 'components/MDX/Heading'; import {Challenge} from './Challenge'; import {Navigation} from './Navigation'; +import {useRouter} from 'next/router'; interface ChallengesProps { children: React.ReactElement[]; @@ -67,6 +68,11 @@ const parseChallengeContents = ( return contents; }; +enum QueuedScroll { + INIT = 'init', + NEXT = 'next', +} + export function Challenges({ children, isRecipes, @@ -76,19 +82,32 @@ export function Challenges({ const challenges = parseChallengeContents(children); const totalChallenges = challenges.length; const scrollAnchorRef = useRef(null); - const queuedScrollRef = useRef(false); + const queuedScrollRef = useRef(QueuedScroll.INIT); const [activeIndex, setActiveIndex] = useState(0); const currentChallenge = challenges[activeIndex]; + const {asPath} = useRouter(); useEffect(() => { - if (queuedScrollRef.current === true) { - queuedScrollRef.current = false; + if (queuedScrollRef.current === QueuedScroll.INIT) { + const initIndex = challenges.findIndex( + (challenge) => challenge.id === asPath.split('#')[1] + ); + if (initIndex === -1) { + queuedScrollRef.current = undefined; + } else if (initIndex !== activeIndex) { + setActiveIndex(initIndex); + } + } + if (queuedScrollRef.current) { scrollAnchorRef.current!.scrollIntoView({ block: 'start', - behavior: 'smooth', + ...(queuedScrollRef.current === QueuedScroll.NEXT && { + behavior: 'smooth', + }), }); + queuedScrollRef.current = undefined; } - }); + }, [activeIndex, asPath, challenges]); const handleChallengeChange = (index: number) => { setActiveIndex(index); @@ -129,7 +148,7 @@ export function Challenges({ hasNextChallenge={activeIndex < totalChallenges - 1} handleClickNextChallenge={() => { setActiveIndex((i) => i + 1); - queuedScrollRef.current = true; + queuedScrollRef.current = QueuedScroll.NEXT; }} />
diff --git a/beta/src/components/MDX/ExpandableExample.tsx b/beta/src/components/MDX/ExpandableExample.tsx index 2baf9b4ec..5cb731532 100644 --- a/beta/src/components/MDX/ExpandableExample.tsx +++ b/beta/src/components/MDX/ExpandableExample.tsx @@ -9,6 +9,8 @@ import {IconDeepDive} from '../Icon/IconDeepDive'; import {IconCodeBlock} from '../Icon/IconCodeBlock'; import {Button} from '../Button'; import {H4} from './Heading'; +import {useRouter} from 'next/router'; +import {useEffect, useRef, useState} from 'react'; interface ExpandableExampleProps { children: React.ReactNode; @@ -17,15 +19,29 @@ interface ExpandableExampleProps { } function ExpandableExample({children, excerpt, type}: ExpandableExampleProps) { - const [isExpanded, setIsExpanded] = React.useState(false); - const isDeepDive = type === 'DeepDive'; - const isExample = type === 'Example'; - if (!Array.isArray(children) || children[0].type.mdxName !== 'h4') { throw Error( `Expandable content ${type} is missing a corresponding title at the beginning` ); } + const isDeepDive = type === 'DeepDive'; + const isExample = type === 'Example'; + const id = children[0].props.id; + + const queuedExpandRef = useRef(true); + const {asPath} = useRouter(); + // init as expanded to prevent flash + const [isExpanded, setIsExpanded] = useState(true); + + // asPath would mismatch between server and client, reset here instead of put it into init state + useEffect(() => { + if (queuedExpandRef.current) { + queuedExpandRef.current = false; + if (id !== asPath.split('#')[1]) { + setIsExpanded(false); + } + } + }, [asPath, id]); return (

{children[0].props.children}