fix: [AA-1078] a11y and styling for weekly goals (#710)
* fix: [AA-1078] weekly learning goals fine tune - Fix a11y issues with buttons - Replace media query with algorithmic approach for determining button layout - Misc styling fixes
This commit is contained in:
@@ -437,7 +437,7 @@ describe('Outline Tab', () => {
|
||||
const spy = jest.spyOn(thunks, 'saveWeeklyLearningGoal');
|
||||
|
||||
await fetchAndRender();
|
||||
const button = await screen.getByTestId('weekly-learning-goal-input-regular');
|
||||
const button = await screen.getByTestId('weekly-learning-goal-input-Regular');
|
||||
fireEvent.click(button);
|
||||
expect(spy).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
@@ -468,9 +468,9 @@ describe('Outline Tab', () => {
|
||||
|
||||
it.each`
|
||||
level | days
|
||||
${'casual'} | ${1}
|
||||
${'regular'} | ${3}
|
||||
${'intense'} | ${5}
|
||||
${'Casual'} | ${1}
|
||||
${'Regular'} | ${3}
|
||||
${'Intense'} | ${5}
|
||||
`('calls the API with a goal of $days when $level goal is clicked', async ({ level, days }) => {
|
||||
// click on Casual goal
|
||||
const button = await screen.queryByTestId(`weekly-learning-goal-input-${level}`);
|
||||
@@ -486,7 +486,7 @@ describe('Outline Tab', () => {
|
||||
expect(screen.getByLabelText(messages.setGoalReminder.defaultMessage)).toBeEnabled();
|
||||
});
|
||||
it('shows and hides subscribe to reminders additional text', async () => {
|
||||
const button = await screen.getByTestId('weekly-learning-goal-input-regular');
|
||||
const button = await screen.getByTestId('weekly-learning-goal-input-Regular');
|
||||
fireEvent.click(button);
|
||||
// Verify the request was made
|
||||
await waitFor(() => {
|
||||
@@ -526,11 +526,6 @@ describe('Outline Tab', () => {
|
||||
});
|
||||
await fetchAndRender();
|
||||
});
|
||||
|
||||
it('has button for weekly learning goal selected', async () => {
|
||||
const radio = await screen.queryByTestId('weekly-learning-goal-input-regular');
|
||||
expect(radio.checked).toEqual(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,25 +1,35 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
|
||||
function FlagButton({
|
||||
buttonIcon,
|
||||
title,
|
||||
text,
|
||||
handleSelect,
|
||||
isSelected,
|
||||
}) {
|
||||
return (
|
||||
<div
|
||||
className="flag-button col flex-grow-1 p-3"
|
||||
<button
|
||||
type="button"
|
||||
className={classnames('flag-button m-1.5 pt-3 pb-md-3 pb-xl-0',
|
||||
isSelected ? 'flag-button-selected' : '')}
|
||||
// aria-checked={isSelected}
|
||||
// role="radio"
|
||||
// tabIndex={isSelected ? '0' : '-1'}
|
||||
onClick={() => handleSelect()}
|
||||
data-testid={`weekly-learning-goal-input-${title}`}
|
||||
>
|
||||
<div className="row justify-content-center">
|
||||
<div className="row justify-content-center pb-1 w-100 m-0">
|
||||
{buttonIcon}
|
||||
</div>
|
||||
<div className="text-center small text-gray-700">
|
||||
<div className={classnames('text-center small text-gray-700 pb-1', isSelected ? 'font-weight-bold' : '')}>
|
||||
{title}
|
||||
</div>
|
||||
<div className="text-center micro text-gray-500">
|
||||
<div className={classnames('text-center micro text-gray-500 pb-2', isSelected ? 'font-weight-bold' : '')}>
|
||||
{text}
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -27,6 +37,8 @@ FlagButton.propTypes = {
|
||||
buttonIcon: PropTypes.element.isRequired,
|
||||
title: PropTypes.string.isRequired,
|
||||
text: PropTypes.string.isRequired,
|
||||
handleSelect: PropTypes.func.isRequired,
|
||||
isSelected: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
export default FlagButton;
|
||||
|
||||
@@ -3,23 +3,35 @@
|
||||
@import "~@edx/brand/paragon/overrides";
|
||||
|
||||
.flag-button {
|
||||
background-color: white;
|
||||
border: 2px solid $light-400;
|
||||
background-color: $white;
|
||||
border: 1px solid $light-400;
|
||||
border-radius:.2rem;
|
||||
|
||||
|
||||
&:focus {
|
||||
border: 2px $blue;
|
||||
box-shadow: 2px solid $yellow;
|
||||
}
|
||||
box-shadow: 0 0 0 2px $light-400;
|
||||
|
||||
&:hover {
|
||||
border: 2px solid $green;
|
||||
box-shadow: 2px solid $teal;
|
||||
border: 1px solid $primary-300;
|
||||
box-shadow: 0 0 0 2px $white;
|
||||
}
|
||||
}
|
||||
|
||||
input[type=radio]:checked + .flag-button {
|
||||
border: 2px solid $red;
|
||||
box-shadow: 2px solid $green;
|
||||
.flag-button-selected {
|
||||
border: 1px solid $primary-300;
|
||||
box-shadow: 0 0 0 2px $primary-300;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
// @see https://heydonworks.com/article/the-flexbox-holy-albatross-reincarnated/
|
||||
// use the container size for layout instead of device media query
|
||||
.flag-button-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
--margin: 1rem;
|
||||
--modifier: calc(20rem - 100%);
|
||||
margin: calc(var(--margin) * -1);
|
||||
}
|
||||
|
||||
.flag-button-container > * {
|
||||
flex-grow: 1;
|
||||
flex-basis: calc(var(--modifier) * 999);
|
||||
margin: var(--margin);
|
||||
}
|
||||
|
||||
@@ -3,8 +3,6 @@ import PropTypes from 'prop-types';
|
||||
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import { ReactComponent as FlagIntenseIcon } from '@edx/paragon/icons/svg/flag.svg';
|
||||
import { useMediaQuery } from 'react-responsive';
|
||||
import classNames from 'classnames';
|
||||
import { ReactComponent as FlagCasualIcon } from './flag_outline.svg';
|
||||
import { ReactComponent as FlagRegularIcon } from './flag_gray.svg';
|
||||
import FlagButton from './FlagButton';
|
||||
@@ -16,9 +14,6 @@ function LearningGoalButton({
|
||||
handleSelect,
|
||||
intl,
|
||||
}) {
|
||||
/* This is not the standard XL media query */
|
||||
const isPastXl = useMediaQuery({ query: '(min-width: 1225px)' });
|
||||
|
||||
const buttonDetails = {
|
||||
casual: {
|
||||
daysPerWeek: 1,
|
||||
@@ -43,33 +38,13 @@ function LearningGoalButton({
|
||||
const values = buttonDetails[level];
|
||||
|
||||
return (
|
||||
<label
|
||||
htmlFor={`weekly-learning-goal-input-${level}`}
|
||||
className={classNames('col-auto col-md-12 m-0 p-0 pb-md-3 pb-xl-0 shadow-none',
|
||||
`${isPastXl ? 'col-xl-auto' : ''}`)}
|
||||
// This is required to make the component visible to tabbing
|
||||
// eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex
|
||||
tabIndex="0"
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
data-testid={`weekly-learning-goal-input-${level}`}
|
||||
id={`weekly-learning-goal-input-${level}`}
|
||||
name="learningGoal"
|
||||
radioGroup="learningGoal"
|
||||
value={values.daysPerWeek}
|
||||
onChange={() => handleSelect(values.daysPerWeek)}
|
||||
tabIndex="0"
|
||||
checked={values.daysPerWeek === currentGoal}
|
||||
className="position-absolute"
|
||||
style={{ opacity: 0 }}
|
||||
/>
|
||||
<FlagButton
|
||||
buttonIcon={values.icon}
|
||||
title={intl.formatMessage(values.title)}
|
||||
text={intl.formatMessage(values.text)}
|
||||
/>
|
||||
</label>
|
||||
<FlagButton
|
||||
buttonIcon={values.icon}
|
||||
title={intl.formatMessage(values.title)}
|
||||
text={intl.formatMessage(values.text)}
|
||||
handleSelect={() => handleSelect(values.daysPerWeek)}
|
||||
isSelected={currentGoal === values.daysPerWeek}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import messages from '../messages';
|
||||
import LearningGoalButton from './LearningGoalButton';
|
||||
import { saveWeeklyLearningGoal } from '../../data';
|
||||
import { useModel } from '../../../generic/model-store';
|
||||
import './FlagButton.scss';
|
||||
|
||||
function WeeklyLearningGoalCard({
|
||||
daysPerWeek,
|
||||
@@ -47,16 +48,26 @@ function WeeklyLearningGoalCard({
|
||||
|
||||
return (
|
||||
<div className="row w-100 m-0 p-0">
|
||||
<Card className="mb-3 shadow-sm border-0" data-testid="weekly-learning-goal-card">
|
||||
<Card
|
||||
className="mb-3 shadow border-0"
|
||||
data-testid="weekly-learning-goal-card"
|
||||
>
|
||||
<Card.Body className="p-3.5">
|
||||
<Card.Title>
|
||||
<h2 className="h4 m-0 text-primary-500">{intl.formatMessage(messages.setWeeklyGoal)}</h2>
|
||||
</Card.Title>
|
||||
<Card.Text className="text-gray-700">
|
||||
<Card.Text
|
||||
className="text-gray-700"
|
||||
// id="learning_goal_choices_label"
|
||||
>
|
||||
{intl.formatMessage(messages.setWeeklyGoalDetail)}
|
||||
</Card.Text>
|
||||
<div
|
||||
className="row w-100 m-0 p-0 justify-content-between"
|
||||
className="flag-button-container m-0 p-0"
|
||||
// making this work as a radio button for keyboard nav
|
||||
// is not working
|
||||
// role="radiogroup"
|
||||
// aria-labelledby="learning_goal_choices_label"
|
||||
>
|
||||
<LearningGoalButton
|
||||
level="casual"
|
||||
|
||||
Reference in New Issue
Block a user