Browse Source

[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 <jiawei.jing@ambergroup.co.jp>
main
Jing Jiawei 2 years ago
committed by GitHub
parent
commit
2e539fa81d
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 7
      beta/src/components/MDX/Challenges/Challenge.tsx
  2. 31
      beta/src/components/MDX/Challenges/Challenges.tsx
  3. 26
      beta/src/components/MDX/ExpandableExample.tsx

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

@ -9,6 +9,7 @@ import {ChallengeContents} from './Challenges';
import {IconHint} from '../../Icon/IconHint'; import {IconHint} from '../../Icon/IconHint';
import {IconSolution} from '../../Icon/IconSolution'; import {IconSolution} from '../../Icon/IconSolution';
import {IconArrowSmall} from '../../Icon/IconArrowSmall'; import {IconArrowSmall} from '../../Icon/IconArrowSmall';
import {H4} from '../Heading';
interface ChallengeProps { interface ChallengeProps {
isRecipes?: boolean; isRecipes?: boolean;
@ -45,14 +46,16 @@ export function Challenge({
return ( return (
<div className="p-5 sm:py-8 sm:px-8"> <div className="p-5 sm:py-8 sm:px-8">
<div> <div>
<h3 className="text-xl text-primary dark:text-primary-dark mb-2"> <H4
className="text-xl text-primary dark:text-primary-dark mb-2 mt-0 font-medium"
id={currentChallenge.id}>
<div className="font-bold block md:inline"> <div className="font-bold block md:inline">
{isRecipes ? 'Example' : 'Challenge'} {currentChallenge.order} of{' '} {isRecipes ? 'Example' : 'Challenge'} {currentChallenge.order} of{' '}
{totalChallenges} {totalChallenges}
<span className="text-primary dark:text-primary-dark">: </span> <span className="text-primary dark:text-primary-dark">: </span>
</div> </div>
{currentChallenge.name} {currentChallenge.name}
</h3> </H4>
{currentChallenge.content} {currentChallenge.content}
</div> </div>
<div className="flex justify-between items-center mt-4"> <div className="flex justify-between items-center mt-4">

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

@ -9,6 +9,7 @@ import {H2} from 'components/MDX/Heading';
import {H4} from 'components/MDX/Heading'; import {H4} from 'components/MDX/Heading';
import {Challenge} from './Challenge'; import {Challenge} from './Challenge';
import {Navigation} from './Navigation'; import {Navigation} from './Navigation';
import {useRouter} from 'next/router';
interface ChallengesProps { interface ChallengesProps {
children: React.ReactElement[]; children: React.ReactElement[];
@ -67,6 +68,11 @@ const parseChallengeContents = (
return contents; return contents;
}; };
enum QueuedScroll {
INIT = 'init',
NEXT = 'next',
}
export function Challenges({ export function Challenges({
children, children,
isRecipes, isRecipes,
@ -76,19 +82,32 @@ export function Challenges({
const challenges = parseChallengeContents(children); const challenges = parseChallengeContents(children);
const totalChallenges = challenges.length; const totalChallenges = challenges.length;
const scrollAnchorRef = useRef<HTMLDivElement>(null); const scrollAnchorRef = useRef<HTMLDivElement>(null);
const queuedScrollRef = useRef<boolean>(false); const queuedScrollRef = useRef<undefined | QueuedScroll>(QueuedScroll.INIT);
const [activeIndex, setActiveIndex] = useState(0); const [activeIndex, setActiveIndex] = useState(0);
const currentChallenge = challenges[activeIndex]; const currentChallenge = challenges[activeIndex];
const {asPath} = useRouter();
useEffect(() => { useEffect(() => {
if (queuedScrollRef.current === true) { if (queuedScrollRef.current === QueuedScroll.INIT) {
queuedScrollRef.current = false; 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({ scrollAnchorRef.current!.scrollIntoView({
block: 'start', block: 'start',
behavior: 'smooth', ...(queuedScrollRef.current === QueuedScroll.NEXT && {
behavior: 'smooth',
}),
}); });
queuedScrollRef.current = undefined;
} }
}); }, [activeIndex, asPath, challenges]);
const handleChallengeChange = (index: number) => { const handleChallengeChange = (index: number) => {
setActiveIndex(index); setActiveIndex(index);
@ -129,7 +148,7 @@ export function Challenges({
hasNextChallenge={activeIndex < totalChallenges - 1} hasNextChallenge={activeIndex < totalChallenges - 1}
handleClickNextChallenge={() => { handleClickNextChallenge={() => {
setActiveIndex((i) => i + 1); setActiveIndex((i) => i + 1);
queuedScrollRef.current = true; queuedScrollRef.current = QueuedScroll.NEXT;
}} }}
/> />
</div> </div>

26
beta/src/components/MDX/ExpandableExample.tsx

@ -9,6 +9,8 @@ import {IconDeepDive} from '../Icon/IconDeepDive';
import {IconCodeBlock} from '../Icon/IconCodeBlock'; import {IconCodeBlock} from '../Icon/IconCodeBlock';
import {Button} from '../Button'; import {Button} from '../Button';
import {H4} from './Heading'; import {H4} from './Heading';
import {useRouter} from 'next/router';
import {useEffect, useRef, useState} from 'react';
interface ExpandableExampleProps { interface ExpandableExampleProps {
children: React.ReactNode; children: React.ReactNode;
@ -17,15 +19,29 @@ interface ExpandableExampleProps {
} }
function ExpandableExample({children, excerpt, type}: 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') { if (!Array.isArray(children) || children[0].type.mdxName !== 'h4') {
throw Error( throw Error(
`Expandable content ${type} is missing a corresponding title at the beginning` `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<boolean>(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 ( return (
<details <details
@ -67,7 +83,7 @@ function ExpandableExample({children, excerpt, type}: ExpandableExampleProps) {
</h5> </h5>
<div className="mb-4"> <div className="mb-4">
<H4 <H4
id={children[0].props.id} id={id}
className="text-xl font-bold text-primary dark:text-primary-dark"> className="text-xl font-bold text-primary dark:text-primary-dark">
{children[0].props.children} {children[0].props.children}
</H4> </H4>

Loading…
Cancel
Save