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 {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 (
<div className="p-5 sm:py-8 sm:px-8">
<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">
{isRecipes ? 'Example' : 'Challenge'} {currentChallenge.order} of{' '}
{totalChallenges}
<span className="text-primary dark:text-primary-dark">: </span>
</div>
{currentChallenge.name}
</h3>
</H4>
{currentChallenge.content}
</div>
<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 {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<HTMLDivElement>(null);
const queuedScrollRef = useRef<boolean>(false);
const queuedScrollRef = useRef<undefined | QueuedScroll>(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;
}}
/>
</div>

26
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<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 (
<details
@ -67,7 +83,7 @@ function ExpandableExample({children, excerpt, type}: ExpandableExampleProps) {
</h5>
<div className="mb-4">
<H4
id={children[0].props.id}
id={id}
className="text-xl font-bold text-primary dark:text-primary-dark">
{children[0].props.children}
</H4>

Loading…
Cancel
Save