feat(client):update accordion map (#63053)

This commit is contained in:
Ahmad Abdolsaheb
2025-10-30 16:31:28 +03:00
committed by GitHub
parent c527dcff3d
commit 8248cd638c
7 changed files with 434 additions and 151 deletions

View File

@@ -1,5 +1,6 @@
import React from 'react';
import { isEmpty } from 'lodash';
import { Button } from '@freecodecamp/ui';
import type { BlockLabel as BlockLabelType } from '../../../../../shared-dist/config/blocks';
import { ProgressBar } from '../../../components/Progress/progress-bar';
@@ -19,6 +20,8 @@ interface BlockHeaderProps {
isExpanded: boolean;
percentageCompleted: number;
blockIntroArr?: string[];
accordion?: boolean;
blockUrl?: string;
}
function BlockHeader({
@@ -31,27 +34,37 @@ function BlockHeader({
isCompleted,
isExpanded,
percentageCompleted,
blockIntroArr
blockIntroArr,
accordion,
blockUrl
}: BlockHeaderProps): JSX.Element {
return (
<>
<h3 className='block-grid-title'>
<button
<Button
aria-expanded={isExpanded ? 'true' : 'false'}
aria-controls={`${blockDashed}-panel`}
className='block-header'
onClick={handleClick}
{...(accordion && blockUrl ? { href: blockUrl } : {})}
>
<span className='block-header-button-text map-title'>
{accordion &&
(blockUrl ? (
<span className='aligner-dash'></span>
) : (
<DropDown />
))}
<CheckMark isCompleted={isCompleted} />
{blockLabel && <BlockLabel blockLabel={blockLabel} />}
{!accordion && blockLabel && <BlockLabel blockLabel={blockLabel} />}
<span>
{blockTitle}
<span className='sr-only'>, {courseCompletionStatus}</span>
</span>
<DropDown />
{accordion && blockLabel && <BlockLabel blockLabel={blockLabel} />}
{!accordion && <DropDown />}
</span>
{!isExpanded && !isCompleted && completedCount > 0 && (
{!accordion && !isExpanded && !isCompleted && completedCount > 0 && (
<div aria-hidden='true' className='progress-wrapper'>
<div>
<ProgressBar now={percentageCompleted} />
@@ -59,7 +72,7 @@ function BlockHeader({
<span>{`${percentageCompleted}%`}</span>
</div>
)}
</button>
</Button>
</h3>
{isExpanded && !isEmpty(blockIntroArr) && (
<BlockIntros intros={blockIntroArr as string[]} />

View File

@@ -31,7 +31,6 @@ import {
ChallengesWithDialogues
} from './challenges';
import BlockLabel from './block-label';
import BlockIntros from './block-intros';
import BlockHeader from './block-header';
import '../intro.css';
@@ -74,6 +73,7 @@ interface BlockProps {
superBlock: SuperBlocks;
t: TFunction;
toggleBlock: typeof toggleBlock;
accordion?: boolean;
}
export class Block extends Component<BlockProps> {
@@ -113,7 +113,8 @@ export class Block extends Component<BlockProps> {
challenges,
isExpanded,
superBlock,
t
t,
accordion = false
} = this.props;
let completedCount = 0;
@@ -199,7 +200,6 @@ export class Block extends Component<BlockProps> {
</div>
)}
</div>
<BlockIntros intros={blockIntroArr} />
<button
aria-expanded={isExpanded}
className='map-title'
@@ -255,7 +255,6 @@ export class Block extends Component<BlockProps> {
</div>
)}
</div>
<BlockIntros intros={blockIntroArr} />
<ChallengesList challenges={extendedChallenges} />
</div>
</Element>
@@ -283,6 +282,8 @@ export class Block extends Component<BlockProps> {
isCompleted={isBlockCompleted}
isExpanded={isExpanded}
percentageCompleted={percentageCompleted}
accordion={accordion}
blockIntroArr={!accordion ? blockIntroArr : undefined}
/>
{isExpanded && (
@@ -299,8 +300,8 @@ export class Block extends Component<BlockProps> {
)}
<div id={`${block}-panel`}>
<BlockIntros intros={blockIntroArr} />
<GridMapChallenges
jumpLink={!accordion}
challenges={extendedChallenges}
isProjectBlock={isProjectBlock}
blockTitle={blockTitle}
@@ -330,6 +331,8 @@ export class Block extends Component<BlockProps> {
isCompleted={isBlockCompleted}
isExpanded={isExpanded}
percentageCompleted={percentageCompleted}
accordion={accordion}
blockIntroArr={!accordion ? blockIntroArr : undefined}
/>
{isExpanded && (
@@ -346,10 +349,10 @@ export class Block extends Component<BlockProps> {
)}
<div id={`${block}-panel`}>
<BlockIntros intros={blockIntroArr} />
<ChallengesWithDialogues
challenges={extendedChallenges}
blockTitle={blockTitle}
jumpLink={!accordion}
/>
</div>
</>
@@ -406,7 +409,6 @@ export class Block extends Component<BlockProps> {
</Link>
</h3>
</div>
<BlockIntros intros={blockIntroArr} />
</div>
</Element>
);
@@ -414,13 +416,13 @@ export class Block extends Component<BlockProps> {
/**
* AccordionBlock is used as the block layout in new accordion style superblocks.
*/
const AccordionBlock = (
const AccordionBlock = accordion ? (
<>
<Element name={block}>
<span className='hide-scrollable-anchor'></span>
</Element>
<div
className={`block block-grid challenge-grid-block ${isExpanded ? 'open' : ''}`}
className={`block block-grid block-grid-no-border challenge-grid-block ${isExpanded ? 'open' : ''}`}
onMouseOver={this.handleBlockHover}
onFocus={this.handleBlockHover}
>
@@ -434,7 +436,7 @@ export class Block extends Component<BlockProps> {
isCompleted={isBlockCompleted}
isExpanded={isExpanded}
percentageCompleted={percentageCompleted}
blockIntroArr={blockIntroArr}
accordion={accordion}
/>
{isExpanded && (
@@ -458,6 +460,62 @@ export class Block extends Component<BlockProps> {
challenges={extendedChallenges}
blockTitle={blockTitle}
isProjectBlock={isProjectBlock}
jumpLink={false}
/>
) : (
<ChallengesList challenges={extendedChallenges} />
)}
</div>
</div>
)}
</div>
</>
) : (
<>
<Element name={block}>
<span className='hide-scrollable-anchor'></span>
</Element>
<div
className={`block block-grid challenge-grid-block ${isExpanded ? 'open' : ''}`}
onMouseOver={this.handleBlockHover}
onFocus={this.handleBlockHover}
>
<BlockHeader
blockDashed={block}
blockTitle={blockTitle}
blockLabel={blockLabel}
completedCount={completedCount}
courseCompletionStatus={courseCompletionStatus()}
handleClick={this.handleBlockClick}
isCompleted={isBlockCompleted}
isExpanded={isExpanded}
percentageCompleted={percentageCompleted}
accordion={accordion}
blockIntroArr={blockIntroArr}
/>
{isExpanded && (
<div className='accordion-block-expanded'>
{!isAudited && (
<div className='tags-wrapper'>
<Link
className='cert-tag'
to={t('links:help-translate-link-url')}
>
{t('misc.translation-pending')}
</Link>
</div>
)}
<div
id={`${block}-panel`}
className={isGridBlock ? 'challenge-grid-block-panel' : ''}
>
{isGridBlock ? (
<GridMapChallenges
jumpLink={true}
challenges={extendedChallenges}
blockTitle={blockTitle}
isProjectBlock={isProjectBlock}
/>
) : (
<ChallengesList challenges={extendedChallenges} />
@@ -469,10 +527,31 @@ export class Block extends Component<BlockProps> {
</>
);
const LinkBlock = (
<>
<Element name={block}>
<span className='hide-scrollable-anchor'></span>
</Element>
<BlockHeader
blockDashed={block}
blockTitle={blockTitle}
blockLabel={blockLabel}
completedCount={completedCount}
courseCompletionStatus={courseCompletionStatus()}
handleClick={this.handleBlockClick}
isCompleted={isBlockCompleted}
isExpanded={isExpanded}
percentageCompleted={percentageCompleted}
accordion={accordion}
blockUrl={challenges[0].fields.slug}
/>
</>
);
const layoutToComponent = {
[BlockLayouts.ChallengeGrid]: AccordionBlock,
[BlockLayouts.ChallengeList]: AccordionBlock,
[BlockLayouts.Link]: AccordionBlock,
[BlockLayouts.Link]: accordion ? LinkBlock : AccordionBlock,
[BlockLayouts.ProjectList]: ProjectListBlock,
[BlockLayouts.LegacyLink]: LegacyLinkBlock,
[BlockLayouts.LegacyChallengeList]: LegacyChallengeListBlock,

View File

@@ -22,6 +22,10 @@ interface ChallengesProps {
challenges: ChallengeInfo[];
}
interface JumpLinkProps {
jumpLink?: boolean;
}
interface BlockTitleProps {
blockTitle: string;
}
@@ -122,16 +126,18 @@ const LinkToFirstIncompleteChallenge = ({
export const GridMapChallenges = ({
challenges,
blockTitle,
isProjectBlock
}: ChallengesProps & BlockTitleProps & IsProjectBlockProps) => {
isProjectBlock,
jumpLink
}: ChallengesProps & BlockTitleProps & IsProjectBlockProps & JumpLinkProps) => {
const { t } = useTranslation();
return (
<>
<LinkToFirstIncompleteChallenge
challenges={challenges}
blockTitle={blockTitle}
/>
{jumpLink && (
<LinkToFirstIncompleteChallenge
challenges={challenges}
blockTitle={blockTitle}
/>
)}
<nav aria-label={t('aria.steps-for', { blockTitle })}>
<ul className={`map-challenges-ul map-challenges-grid`}>
{challenges.map(challenge => (
@@ -161,16 +167,19 @@ export const GridMapChallenges = ({
export const ChallengesWithDialogues = ({
challenges,
blockTitle
}: ChallengesProps & BlockTitleProps) => {
blockTitle,
jumpLink
}: ChallengesProps & BlockTitleProps & JumpLinkProps) => {
const { t } = useTranslation();
return (
<>
<LinkToFirstIncompleteChallenge
challenges={challenges}
blockTitle={blockTitle}
/>
{jumpLink && (
<LinkToFirstIncompleteChallenge
challenges={challenges}
blockTitle={blockTitle}
/>
)}
<nav aria-label={t('aria.dialogues-and-tasks-for', { blockTitle })}>
<ul className={`map-challenges-ul map-challenges-grid`}>
{challenges.map(challenge => (

View File

@@ -1,12 +1,12 @@
.super-block-accordion {
display: flex;
flex-direction: column;
gap: 10px;
padding: 0;
}
.super-block-accordion ul {
padding: 0;
margin: 0;
}
.super-block-accordion li {
@@ -17,14 +17,14 @@
text-decoration: none;
}
.super-block-accordion .chapter,
.super-block-accordion .link-block {
background-color: var(--primary-background);
.super-block-accordion .chapter {
border: 3px solid var(--tertiary-background);
margin-bottom: 16px;
}
.super-block-accordion .chapter-button {
border: none;
background-color: transparent;
background-color: var(--primary-background);
width: 100%;
padding: 16px;
display: flex;
@@ -32,12 +32,43 @@
justify-content: space-between;
}
.super-block-accordion .aligner-dash {
width: 14px;
}
.super-block-accordion .map-icon {
width: 26px;
max-height: 40px;
}
.super-block-accordion .chapter-button .chapter-button-left {
display: flex;
align-items: center;
text-align: start;
column-gap: 10px;
font-size: 1.5em;
}
.super-block-accordion .block-grid {
border: none;
}
.super-block-accordion .block-grid .block-header[aria-expanded='true'] {
border: none;
}
button .block-header-button-text {
font-weight: normal;
font-size: 1rem;
}
.super-block-accordion .map-title,
.super-block-accordion .block-header-button-text {
gap: 10px;
padding: 0px;
}
.super-block-accordion .block-header-button-text {
justify-content: start;
}
.super-block-accordion .chapter-button .chapter-button-right {
@@ -56,15 +87,16 @@
right: 5px;
}
.super-block-accordion .module-button {
.super-block-accordion .module-button,
.super-block-accordion .block-header {
border: none;
background-color: transparent;
width: 100%;
font-size: 1.25em;
padding: 16px;
padding: 10px;
display: flex;
align-items: center;
justify-content: space-between;
font-weight: normal;
gap: 10px;
}
@@ -88,7 +120,8 @@
}
.super-block-accordion .chapter-button:hover,
.super-block-accordion .module-button:hover {
.super-block-accordion .module-button:hover,
.super-block-accordion .block-header:hover {
background-color: var(--tertiary-background);
color: var(--primary-text);
}
@@ -104,17 +137,60 @@
}
.super-block-accordion .chapter-panel {
padding: 0 16px;
background-color: var(--primary-background);
border-top: 1px solid var(--tertiary-background);
}
.super-block-accordion .module-panel {
padding: 0 24px;
.super-block-accordion .module-panel .block-header {
padding: 10px 0px;
padding-inline-start: 26px;
}
.super-block-accordion .chapter-panel > li {
border-bottom: 4px solid var(--secondary-background);
.super-block-accordion .block-label {
font-size: 0.6rem;
}
.super-block-accordion
.module-panel
.accordion-block-expanded
.map-challenge-title
> * {
padding-inline: 64px 10px;
padding-block: 10px;
}
.super-block-accordion
.module-panel
.accordion-block-expanded
.map-challenge-title
> .map-grid-item {
padding: 0;
height: 2.1rem;
width: 2.1rem;
}
.super-block-accordion
.module-panel
.accordion-block-expanded
.map-challenges-grid {
padding-block: 10px;
padding-inline-start: 64px;
grid-template-columns: repeat(auto-fill, 2.1rem);
}
.super-block-accordion .map-challenge-title {
font-size: 1rem;
}
.super-block-accordion .accordion-block-expanded .map-challenges-grid,
.super-block-accordion .challenge-grid-block .challenge-grid-block-panel {
margin: 0;
}
.super-block-accordion .map-challenge-title a,
.super-block-accordion .accordion-block-expanded {
padding: 0;
}
.super-block-accordion .chapter-panel > li:last-child {
border-bottom: none;
}
@@ -124,6 +200,34 @@
border-bottom: 0;
}
.super-block-accordion .module-panel,
.super-block-accordion .accordion-block-expanded {
position: relative;
}
.super-block-accordion .module-panel::before {
position: absolute;
left: 16px;
width: 1px;
height: 100%;
content: '';
background: var(--quaternary-background);
}
.super-block-accordion .accordion-block-expanded::before {
position: absolute;
left: 32px;
width: 1px;
height: 100%;
top: 0;
content: '';
background: var(--quaternary-background);
}
.super-block-accordion .chapter-steps {
font-size: 0.85rem;
color: var(--quaternary-color);
}
.super-block-accordion .badge {
color: var(--quaternary-color);
border: 1px solid var(--quaternary-color);
@@ -140,8 +244,44 @@
}
.super-block-accordion .module-intro {
margin-left: 17px;
margin-top: 16px;
padding: 16px 16px 0px 32px;
}
.super-block-accordion .module-intro p {
font-size: 1rem;
margin-bottom: 1rem;
}
.super-block-accordion
.chapter-panel
.chapter-button[aria-expanded='false']
.dropdown-icon,
.super-block-accordion
.chapter-panel
.module-button[aria-expanded='false']
.dropdown-icon,
.super-block-accordion
.chapter-panel
.block-grid
.block-header[aria-expanded='false']
.dropdown-icon {
transform: rotate(90deg);
}
.super-block-accordion
.chapter-panel
.chapter-button[aria-expanded='true']
.dropdown-icon,
.super-block-accordion
.chapter-panel
.module-button[aria-expanded='true']
.dropdown-icon,
.super-block-accordion
.chapter-panel
.block-grid
.block-header[aria-expanded='true']
.dropdown-icon {
transform: rotate(180deg);
}
.full-width-container {

View File

@@ -8,6 +8,7 @@ import DropDown from '../../../assets/icons/dropdown';
import type { ChapterBasedSuperBlockStructure } from '../../../redux/prop-types';
import { ChapterIcon } from '../../../assets/chapter-icon';
import { type Chapter } from '../../../../../shared-dist/config/chapters';
import { Link } from '../../../components/helpers';
import {
BlockLayouts,
BlockLabel
@@ -30,6 +31,8 @@ interface ChapterProps {
totalSteps: number;
completedSteps: number;
superBlock: SuperBlocks;
isLinkChapter?: boolean;
examSlug?: string;
}
interface ModuleProps {
@@ -39,6 +42,7 @@ interface ModuleProps {
totalSteps: number;
completedSteps: number;
superBlock: SuperBlocks;
comingSoon: boolean;
}
interface Challenge {
@@ -53,6 +57,25 @@ interface Challenge {
superBlock: SuperBlocks;
}
interface PopulatedBlock {
name: string;
blockLabel: BlockLabel | null;
challenges: Challenge[];
}
interface PopulatedModule {
name: string;
comingSoon?: boolean;
moduleType?: string;
blocks: PopulatedBlock[];
}
interface PopulatedChapter {
name: string;
comingSoon?: boolean;
modules: PopulatedModule[];
}
interface SuperBlockAccordionProps {
challenges: Challenge[];
superBlock: SuperBlocks;
@@ -68,10 +91,50 @@ const Chapter = ({
comingSoon,
totalSteps,
completedSteps,
superBlock
superBlock,
isLinkChapter,
examSlug
}: ChapterProps) => {
const { t } = useTranslation();
const isComplete = completedSteps === totalSteps;
const chapterLabel = t(`intro:${superBlock}.chapters.${dashedName}`);
const chapterButtonContent = (
<>
<div className='chapter-button-left'>
<span className='checkmark-wrap chapter-checkmark-wrap'>
<CheckMark isCompleted={isComplete} />
</span>
<ChapterIcon className='map-icon' chapter={dashedName as FsdChapters} />
{chapterLabel}
</div>
<div className='chapter-button-right'>
{!comingSoon && (
<span className='chapter-steps'>
{t('learn.steps-completed', {
totalSteps,
completedSteps
})}
</span>
)}
<span className='dropdown-wrap'>{!isLinkChapter && <DropDown />}</span>
</div>
</>
);
if (isLinkChapter && examSlug) {
return (
<li className='chapter'>
<Link
className='chapter-button'
data-playwright-test-label='chapter-button'
to={examSlug}
>
{chapterButtonContent}
</Link>
</li>
);
}
return (
<Disclosure as='li' className='chapter' defaultOpen={isExpanded}>
@@ -79,35 +142,13 @@ const Chapter = ({
className='chapter-button'
data-playwright-test-label='chapter-button'
>
<div className='chapter-button-left'>
<ChapterIcon
className='map-icon'
chapter={dashedName as FsdChapters}
/>
{t(`intro:${superBlock}.chapters.${dashedName}`)}
</div>
<div className='chapter-button-right'>
{!comingSoon && (
<>
<span className='chapter-steps'>
{t('learn.steps-completed', {
totalSteps,
completedSteps
})}
</span>
<span className='checkmark-wrap chapter-checkmark-wrap'>
<CheckMark isCompleted={isComplete} />
</span>
</>
)}
<span className='dropdown-wrap'>
<DropDown />
</span>
</div>
{chapterButtonContent}
</Disclosure.Button>
<Disclosure.Panel as='ul' className='chapter-panel'>
{children}
</Disclosure.Panel>
{!isLinkChapter && !examSlug && (
<Disclosure.Panel as='ul' className='chapter-panel'>
{children}
</Disclosure.Panel>
)}
</Disclosure>
);
};
@@ -118,10 +159,19 @@ const Module = ({
isExpanded,
totalSteps,
completedSteps,
superBlock
superBlock,
comingSoon
}: ModuleProps) => {
const { t } = useTranslation();
const isComplete = completedSteps === totalSteps;
const isComplete = totalSteps === 0 ? false : completedSteps === totalSteps;
const { note, intro } = t(`intro:${superBlock}.module-intros.${dashedName}`, {
returnObjects: true
}) as {
note: string;
intro: string[];
};
const showModuleContent = !(comingSoon && !showUpcomingChanges);
return (
<Disclosure as='li' defaultOpen={isExpanded}>
@@ -130,44 +180,52 @@ const Module = ({
<span className='dropdown-wrap'>
<DropDown />
</span>
{t(`intro:${superBlock}.modules.${dashedName}`)}
</div>
<div className='module-button-right'>
<span className='module-steps'>
{t('learn.steps-completed', {
totalSteps,
completedSteps
})}
</span>
<span className='checkmark-wrap'>
<CheckMark isCompleted={isComplete} />
</span>
{t(`intro:${superBlock}.modules.${dashedName}`)}
</div>
</Disclosure.Button>
<Disclosure.Panel as='ul' className='module-panel'>
{children}
{comingSoon && (
<div className='module-intro'>
{note && (
<p>
<b>{note}</b>
</p>
)}
{intro?.length && intro.map(ntro => <p key={ntro}>{ntro}</p>)}
</div>
)}
{showModuleContent && children}
</Disclosure.Panel>
</Disclosure>
);
};
const LinkBlock = ({
const LinkModule = ({
superBlock,
challenges
challenges,
accordion
}: {
superBlock: SuperBlocks;
challenges?: Challenge[];
}) =>
challenges?.length ? (
accordion: boolean;
}) => {
if (!challenges?.length) return null;
return (
<li className='link-block'>
<Block
block={challenges[0].block}
blockLabel={challenges[0].blockLabel}
challenges={challenges}
superBlock={superBlock}
accordion={accordion}
/>
</li>
) : null;
);
};
export const SuperBlockAccordion = ({
challenges,
@@ -185,7 +243,11 @@ export const SuperBlockAccordion = ({
const isLinkModule = (name: string) => {
const module = modules.find(module => module.dashedName === name);
return module?.moduleType === 'review' || module?.moduleType === 'exam';
return (
module?.moduleType === 'review' ||
module?.moduleType === 'exam' ||
module?.moduleType === 'quiz'
);
};
const getBlockToChapterMap = () => {
@@ -214,11 +276,8 @@ export const SuperBlockAccordion = ({
const blockToChapterMap = getBlockToChapterMap();
const blockToModuleMap = getBlockToModuleMap();
const { t } = useTranslation();
const { allChapters } = useMemo(() => {
const chapters = superBlockStructure.chapters;
const populateBlocks = (blocks: string[]) =>
const allChapters = useMemo<PopulatedChapter[]>(() => {
const populateBlocks = (blocks: string[]): PopulatedBlock[] =>
blocks.map(block => {
const blockChallenges = challenges.filter(
({ block: blockName }) => blockName === block
@@ -231,7 +290,7 @@ export const SuperBlockAccordion = ({
};
});
const allChapters = chapters.map(chapter => ({
return superBlockStructure.chapters.map((chapter: Chapter) => ({
name: chapter.dashedName,
comingSoon: chapter.comingSoon,
modules: chapter.modules.map((module: Module) => ({
@@ -241,13 +300,12 @@ export const SuperBlockAccordion = ({
blocks: populateBlocks(module.blocks)
}))
}));
return { allChapters };
}, [challenges, superBlockStructure.chapters]);
// Expand the outer layers in order to reveal the chosen block.
const expandedChapter = blockToChapterMap.get(chosenBlock);
const expandedModule = blockToModuleMap.get(chosenBlock);
const accordion = true;
return (
<ul className='super-block-accordion'>
@@ -261,10 +319,25 @@ export const SuperBlockAccordion = ({
});
const chapterStepIdsSet = new Set(chapterStepIds);
const completedStepsInChapter = new Set(
completedChallengeIds.filter(id => chapterStepIdsSet.has(id))
).size;
const [firstChapterModule] = chapter.modules;
const [firstModuleBlock] = firstChapterModule?.blocks ?? [];
const isLinkChapter =
chapter.modules.length === 1 &&
firstChapterModule?.blocks.length === 1 &&
firstModuleBlock?.blockLabel === BlockLabel.exam &&
firstModuleBlock.challenges.length === 1;
const examSlug = isLinkChapter
? firstModuleBlock?.challenges[0]?.fields.slug
: undefined;
return (
<Chapter
key={chapter.name}
@@ -276,57 +349,23 @@ export const SuperBlockAccordion = ({
totalSteps={chapterStepIds.length}
completedSteps={completedStepsInChapter}
superBlock={superBlock}
isLinkChapter={isLinkChapter}
examSlug={examSlug}
>
{chapter.modules.map(module => {
if (module.comingSoon && !showUpcomingChanges) {
if (module.moduleType === 'review') {
return null;
}
const { note, intro } = t(
`intro:${superBlock}.module-intros.${module.name}`,
{ returnObjects: true }
) as {
note: string;
intro: string[];
};
return (
<Disclosure
key={module.name}
as='li'
defaultOpen={expandedModule === module.name}
>
<Disclosure.Button className='module-button'>
<div className='module-button-left'>
<span className='dropdown-wrap'>
<DropDown />
</span>
{t(`intro:${superBlock}.modules.${module.name}`)}
</div>
</Disclosure.Button>
<Disclosure.Panel as='ul' className='module-panel'>
<div className='module-intro'>
{note && (
<p>
<b>{note}</b>
</p>
)}
{intro &&
intro.length > 0 &&
intro.map(ntro => <p key={ntro}>{ntro}</p>)}
</div>
</Disclosure.Panel>
</Disclosure>
);
}
if (isLinkModule(module.name)) {
return (
<LinkBlock
<LinkModule
key={module.name}
superBlock={superBlock}
challenges={module.blocks[0]?.challenges}
accordion={accordion}
/>
);
}
@@ -349,6 +388,7 @@ export const SuperBlockAccordion = ({
totalSteps={moduleStepIds.length}
completedSteps={completedStepsInModule}
superBlock={superBlock}
comingSoon={!!module.comingSoon}
>
{module.blocks.map(block => (
// maybe TODO: allow blocks to be "coming soon"
@@ -358,6 +398,7 @@ export const SuperBlockAccordion = ({
blockLabel={block.blockLabel}
challenges={block.challenges}
superBlock={superBlock}
accordion={accordion}
/>
</li>
))}

View File

@@ -937,6 +937,7 @@
{ "dashedName": "capstone-project", "comingSoon": true, "blocks": [] },
{
"dashedName": "certified-full-stack-developer-exam",
"moduleType": "exam",
"comingSoon": true,
"blocks": ["exam-certified-full-stack-developer"]
}

View File

@@ -43,7 +43,7 @@ test.describe('Super Block Page - Authenticated User', () => {
await page.addInitScript(() => {
window.localStorage.setItem(
'currentChallengeId',
'660ee6e3a242da6bd579de69' // JS Pyramid Generator step 2
'62a3b3eab50e193608c19fc6' // Learn Basic JavaScript by Building a Role Playing Game step 9
);
});
@@ -51,7 +51,7 @@ test.describe('Super Block Page - Authenticated User', () => {
await expect(
page.getByRole('button', {
name: 'Learn Introductory JavaScript by Building a Pyramid Generator'
name: 'Learn Basic JavaScript by Building a Role Playing Game'
})
).toHaveAttribute('aria-expanded', 'true');
});
@@ -124,14 +124,14 @@ test.describe('Super Block Page - Authenticated User', () => {
// Module
await expect(
page.getByRole('button', {
name: /Basic CSS \d+ of \d+ steps complete/
name: 'Basic CSS'
})
).toHaveAttribute('aria-expanded', 'true');
// Block
await expect(
page.getByRole('button', {
name: /Workshop Design a Cafe Menu/
name: 'Design a Cafe Menu'
})
).toHaveAttribute('aria-expanded', 'true');
});
@@ -156,14 +156,14 @@ test.describe('Super Block Page - Authenticated User', () => {
// Basic HTML module
await expect(
page.getByRole('button', {
name: /Basic HTML \d+ of \d+ steps complete/
name: 'Basic HTML'
})
).toHaveAttribute('aria-expanded', 'true');
// Understanding HTML Attributes block
await expect(
page.getByRole('button', {
name: /Theory Understanding HTML Attributes/
name: 'Understanding HTML Attributes'
})
).toHaveAttribute('aria-expanded', 'true');
});
@@ -183,14 +183,14 @@ test.describe('Super Block Page - Authenticated User', () => {
// First module
await expect(
page.getByRole('button', {
name: /Basic HTML \d+ of \d+ steps complete/
name: 'Basic HTML'
})
).toHaveAttribute('aria-expanded', 'true');
// First block
await expect(
page.getByRole('button', {
name: /Build a Curriculum Outline/
name: 'Build a Curriculum Outline'
})
).toHaveAttribute('aria-expanded', 'true');
@@ -212,7 +212,7 @@ test.describe('Super Block Page - Authenticated User', () => {
// Cat Blog Page block
await expect(
page.getByRole('button', {
name: 'Workshop Build a Cat Blog Page'
name: 'Build a Cat Blog Page'
})
).toHaveAttribute('aria-expanded', 'true');
});
@@ -258,7 +258,7 @@ test.describe('Super Block Page - Unauthenticated User', () => {
// First module
await expect(
page.getByRole('button', {
name: /Basic HTML \d+ of \d+ steps complete/
name: 'Basic HTML'
})
).toHaveAttribute('aria-expanded', 'true');