mirror of
https://github.com/freeCodeCamp/freeCodeCamp.git
synced 2025-12-30 12:05:39 -05: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:
@@ -73,7 +73,9 @@
|
||||
"click-start-course": "Start the course",
|
||||
"click-start-project": "Start the project",
|
||||
"change-language": "Change Language",
|
||||
"cancel-change": "Cancel Change"
|
||||
"cancel-change": "Cancel Change",
|
||||
"resume-project": "Resume project",
|
||||
"start-project": "Start project"
|
||||
},
|
||||
"landing": {
|
||||
"big-heading-1": "Learn to code — for free.",
|
||||
@@ -321,7 +323,8 @@
|
||||
"sorry-keep-trying":"Sorry, your code does not pass. Keep trying.",
|
||||
"sorry-getting-there":"Sorry, your code does not pass. You're getting there.",
|
||||
"sorry-hang-in-there":"Sorry, your code does not pass. Hang in there.",
|
||||
"sorry-dont-giveup":"Sorry, your code does not pass. Don't give up."
|
||||
"sorry-dont-giveup":"Sorry, your code does not pass. Don't give up.",
|
||||
"challenges-completed": "{{completedCount}} of {{totalChallenges}} challenges completed"
|
||||
},
|
||||
"donate": {
|
||||
"title": "Support our nonprofit",
|
||||
@@ -482,7 +485,10 @@
|
||||
"primary-nav": "primary",
|
||||
"breadcrumb-nav": "breadcrumb",
|
||||
"submit": "Use Ctrl + Enter to submit.",
|
||||
"running-tests": "Running tests"
|
||||
"running-tests": "Running tests",
|
||||
"step": "Step",
|
||||
"steps": "Steps",
|
||||
"steps-for": "Steps for {{blockTitle}}"
|
||||
},
|
||||
"flash": {
|
||||
"honest-first": "To claim a certification, you must first accept our academic honesty policy",
|
||||
|
||||
@@ -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 => (
|
||||
|
||||
@@ -44,11 +44,23 @@ a.cert-tag:active {
|
||||
}
|
||||
|
||||
.block-grid-title {
|
||||
font-size: 1.2rem;
|
||||
margin: 0;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
.block-grid .challenge-jump-link {
|
||||
margin: 0 25px 25px;
|
||||
}
|
||||
|
||||
.block-grid .challenge-jump-link a:focus {
|
||||
outline: 3px solid var(--blue-mid);
|
||||
outline-offset: 0;
|
||||
}
|
||||
|
||||
.block-grid .challenge-jump-link a:focus:not(:focus-visible) {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.block-title-translation-cta {
|
||||
white-space: nowrap;
|
||||
padding: 0.2em 0.5em;
|
||||
@@ -234,11 +246,30 @@ button.map-title {
|
||||
.map-challenge-title a {
|
||||
width: 100%;
|
||||
padding: 10px 15px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.map-challenges-grid .map-challenge-title a {
|
||||
width: 3.4rem;
|
||||
height: 2.75rem;
|
||||
min-width: 24px;
|
||||
min-height: 24px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.map-challenges-grid .map-challenge-title a:focus {
|
||||
outline: 3px solid var(--secondary-color);
|
||||
outline-offset: -3px;
|
||||
}
|
||||
|
||||
.map-challenges-grid .map-challenge-title a:focus:not(:focus-visible) {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.map-challenges-grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
.map-challenge-title-grid {
|
||||
flex: 0 1 60px;
|
||||
@@ -261,6 +292,13 @@ button.map-title {
|
||||
|
||||
.challenge-completed {
|
||||
background: var(--highlight-background);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.challenge-completed span {
|
||||
font-weight: 700;
|
||||
font-size: 1.3rem;
|
||||
margin-top: -0.05rem;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 500px) {
|
||||
@@ -346,7 +384,7 @@ button.map-title {
|
||||
}
|
||||
|
||||
.block-grid {
|
||||
border-bottom: 3px solid var(--secondary-background);
|
||||
border-bottom: 4px solid var(--secondary-background);
|
||||
}
|
||||
|
||||
.block-grid .block-header {
|
||||
@@ -366,7 +404,7 @@ button.map-title {
|
||||
}
|
||||
|
||||
.block-grid .block-header[aria-expanded='true'] {
|
||||
border-bottom: 3px solid var(--secondary-background);
|
||||
border-bottom: 2px solid var(--secondary-background);
|
||||
}
|
||||
|
||||
.block-header-button-text {
|
||||
|
||||
Reference in New Issue
Block a user