mirror of
https://github.com/freeCodeCamp/freeCodeCamp.git
synced 2026-05-10 15:03:12 -04:00
fix(a11y): improve RWD challege grid accessibility (#45957)
* fix: improve RWD challege grid accessibility * fix: change wording from challenge to step * fix: improve keyboard focus indicator on step links * fix: style jump link as button * fix: accessible background color for completed steps * refactor: make steps a nav region * refactor: remove duplicate selector * fix: enhance focus indicator on jump link * fix: TS type * refactor: remove unneeded CSS * chore: resolve conflict * chore: translate accessible name on nav * fix: remove h4 from button * fix: add course name to toggle button * fix: improve accessibility of course stats on toggle button * fix: prefix step links with sr-only "Step" * fix: add start project button * refactor: remove unnecessary spaces * fix: clean up styles * Update client/src/templates/Introduction/intro.css Co-authored-by: ahmad abdolsaheb <ahmad.abdolsaheb@gmail.com>
This commit is contained in:
@@ -193,12 +193,23 @@ export class Block extends Component<BlockProps> {
|
||||
}}
|
||||
>
|
||||
<Caret />
|
||||
<h4 className='course-title'>
|
||||
{`${isExpanded ? collapseText : expandText}`}
|
||||
</h4>
|
||||
<div className='course-title'>
|
||||
{`${isExpanded ? collapseText : expandText}`}{' '}
|
||||
<span className='sr-only'>{blockTitle}</span>
|
||||
</div>
|
||||
<div className='map-title-completed course-title'>
|
||||
{this.renderCheckMark(isBlockCompleted)}
|
||||
<span className='map-completed-count'>{`${completedCount}/${challengesWithCompleted.length}`}</span>
|
||||
<span
|
||||
aria-hidden='true'
|
||||
className='map-completed-count'
|
||||
>{`${completedCount}/${challengesWithCompleted.length}`}</span>
|
||||
<span className='sr-only'>
|
||||
,{' '}
|
||||
{t('learn.challenges-completed', {
|
||||
completedCount,
|
||||
totalChallenges: challengesWithCompleted.length
|
||||
})}
|
||||
</span>
|
||||
</div>
|
||||
</button>
|
||||
{isExpanded && (
|
||||
@@ -268,7 +279,7 @@ export class Block extends Component<BlockProps> {
|
||||
<span className='block-header-button-text map-title'>
|
||||
{this.renderCheckMark(isBlockCompleted)}
|
||||
<span>
|
||||
{blockTitle}{' '}
|
||||
{blockTitle}
|
||||
<span className='sr-only'>
|
||||
, {courseCompletionStatus()}
|
||||
</span>
|
||||
@@ -293,13 +304,12 @@ export class Block extends Component<BlockProps> {
|
||||
</div>
|
||||
{isExpanded && this.renderBlockIntros(blockIntroArr)}
|
||||
{isExpanded && (
|
||||
<>
|
||||
<Challenges
|
||||
challengesWithCompleted={challengesWithCompleted}
|
||||
isProjectBlock={isProjectBlock}
|
||||
superBlock={superBlock}
|
||||
/>
|
||||
</>
|
||||
<Challenges
|
||||
challengesWithCompleted={challengesWithCompleted}
|
||||
isProjectBlock={isProjectBlock}
|
||||
superBlock={superBlock}
|
||||
blockTitle={blockTitle}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</ScrollableAnchor>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Link } from 'gatsby';
|
||||
import React from 'react';
|
||||
import { withTranslation } from 'react-i18next';
|
||||
import { withTranslation, useTranslation } from 'react-i18next';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import type { Dispatch } from 'redux';
|
||||
@@ -21,6 +21,7 @@ interface Challenges {
|
||||
executeGA: (payload: ExecuteGaArg) => void;
|
||||
isProjectBlock: boolean;
|
||||
superBlock: SuperBlocks;
|
||||
blockTitle?: string | null;
|
||||
}
|
||||
|
||||
const mapIconStyle = { height: '15px', marginRight: '10px', width: '15px' };
|
||||
@@ -29,8 +30,10 @@ function Challenges({
|
||||
challengesWithCompleted,
|
||||
executeGA,
|
||||
isProjectBlock,
|
||||
superBlock
|
||||
superBlock,
|
||||
blockTitle
|
||||
}: Challenges): JSX.Element {
|
||||
const { t } = useTranslation();
|
||||
const handleChallengeClick = (slug: string) =>
|
||||
executeGA({
|
||||
type: 'event',
|
||||
@@ -49,40 +52,78 @@ function Challenges({
|
||||
|
||||
const isGridMap = isNewRespCert(superBlock) || isNewJsCert(superBlock);
|
||||
|
||||
const firstIncompleteChallenge = challengesWithCompleted.find(
|
||||
challenge => !challenge.isCompleted
|
||||
);
|
||||
|
||||
const isChallengeStarted = !!challengesWithCompleted.find(
|
||||
challenge => challenge.isCompleted
|
||||
);
|
||||
|
||||
return isGridMap ? (
|
||||
<ul className={`map-challenges-ul map-challenges-grid `}>
|
||||
{challengesWithCompleted.map((challenge, i) => (
|
||||
<li
|
||||
className={`map-challenge-title map-challenge-title-grid ${
|
||||
isProjectBlock ? 'map-project-wrap' : 'map-challenge-wrap'
|
||||
}`}
|
||||
id={challenge.dashedName}
|
||||
key={`map-challenge ${challenge.fields.slug}`}
|
||||
>
|
||||
{!isProjectBlock ? (
|
||||
<Link
|
||||
onClick={() => handleChallengeClick(challenge.fields.slug)}
|
||||
to={challenge.fields.slug}
|
||||
className={`map-grid-item ${
|
||||
challenge.isCompleted ? 'challenge-completed' : ''
|
||||
<>
|
||||
{firstIncompleteChallenge && (
|
||||
<div className='challenge-jump-link'>
|
||||
<Link
|
||||
className='btn btn-primary'
|
||||
onClick={() =>
|
||||
handleChallengeClick(firstIncompleteChallenge.fields.slug)
|
||||
}
|
||||
to={firstIncompleteChallenge.fields.slug}
|
||||
>
|
||||
{!isChallengeStarted
|
||||
? t('buttons.start-project')
|
||||
: t('buttons.resume-project')}{' '}
|
||||
{blockTitle && <span className='sr-only'>{blockTitle}</span>}
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
<nav
|
||||
aria-label={
|
||||
blockTitle ? t('aria.steps-for', { blockTitle }) : t('aria.steps')
|
||||
}
|
||||
>
|
||||
<ul className={`map-challenges-ul map-challenges-grid `}>
|
||||
{challengesWithCompleted.map((challenge, i) => (
|
||||
<li
|
||||
className={`map-challenge-title map-challenge-title-grid ${
|
||||
isProjectBlock ? 'map-project-wrap' : 'map-challenge-wrap'
|
||||
}`}
|
||||
id={challenge.dashedName}
|
||||
key={`map-challenge ${challenge.fields.slug}`}
|
||||
>
|
||||
{i + 1}
|
||||
</Link>
|
||||
) : (
|
||||
<Link
|
||||
onClick={() => handleChallengeClick(challenge.fields.slug)}
|
||||
to={challenge.fields.slug}
|
||||
>
|
||||
{challenge.title}
|
||||
<span className=' badge map-badge map-project-checkmark'>
|
||||
{renderCheckMark(challenge.isCompleted)}
|
||||
</span>
|
||||
</Link>
|
||||
)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
{!isProjectBlock ? (
|
||||
<Link
|
||||
onClick={() => handleChallengeClick(challenge.fields.slug)}
|
||||
to={challenge.fields.slug}
|
||||
className={`map-grid-item ${
|
||||
+challenge.isCompleted ? 'challenge-completed' : ''
|
||||
}`}
|
||||
>
|
||||
<span className='sr-only'>{t('aria.step')}</span>
|
||||
<span>{i + 1}</span>
|
||||
<span className='sr-only'>
|
||||
{challenge.isCompleted
|
||||
? t('icons.passed')
|
||||
: t('icons.not-passed')}
|
||||
</span>
|
||||
</Link>
|
||||
) : (
|
||||
<Link
|
||||
onClick={() => handleChallengeClick(challenge.fields.slug)}
|
||||
to={challenge.fields.slug}
|
||||
>
|
||||
{challenge.title}
|
||||
<span className=' badge map-badge map-project-checkmark'>
|
||||
{renderCheckMark(challenge.isCompleted)}
|
||||
</span>
|
||||
</Link>
|
||||
)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</nav>
|
||||
</>
|
||||
) : (
|
||||
<ul className={`map-challenges-ul`}>
|
||||
{challengesWithCompleted.map(challenge => (
|
||||
|
||||
Reference in New Issue
Block a user