Fix answer box styling. TNL-10333 (#197)
* fix: use feedback icon with correct hover color * fix: problem answer layout squishes delete button background * fix: remove borders from textarea * fix: textarea resize * refactor: remove renderThing-antipattern in answer option * fix: answer option feedback color * fix: add second feedback box to all problem types * refactor: move extra components out of answer option file * fix: icon disappearing on hover when active * fix: update snapshot * fix: lint * fix: add tests * fix: add tests * fix: snapshots * Update src/editors/containers/ProblemEditor/components/EditProblemView/AnswerWidget/AnswerOption.jsx Co-authored-by: Kristin Aoki <42981026+KristinAoki@users.noreply.github.com> * fix: resolve discussions from PR Co-authored-by: Kristin Aoki <42981026+KristinAoki@users.noreply.github.com>
This commit is contained in:
2
src/colors.scss
Normal file
2
src/colors.scss
Normal file
@@ -0,0 +1,2 @@
|
||||
$white: #fff;
|
||||
$black: #000;
|
||||
@@ -2,57 +2,18 @@ import React, { memo } from 'react';
|
||||
import { connect, useDispatch } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
Col, Collapsible, Icon, IconButton, Form, Row,
|
||||
Collapsible, Icon, IconButton, Form,
|
||||
} from '@edx/paragon';
|
||||
import { AddComment, Delete } from '@edx/paragon/icons';
|
||||
import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import { Feedback, Delete } from '@edx/paragon/icons';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
|
||||
import messages from './messages';
|
||||
import { selectors } from '../../../../../data/redux';
|
||||
import { answerOptionProps } from '../../../../../data/services/cms/types';
|
||||
import { ProblemTypeKeys } from '../../../../../data/constants/problem';
|
||||
import Checker from './components/Checker';
|
||||
import { FeedbackBox } from './components/Feedback';
|
||||
import * as hooks from './hooks';
|
||||
|
||||
const Checker = ({
|
||||
hasSingleAnswer, answer, setAnswer,
|
||||
}) => {
|
||||
let CheckerType = Form.Checkbox;
|
||||
if (hasSingleAnswer) {
|
||||
CheckerType = Form.Radio;
|
||||
}
|
||||
return (
|
||||
<CheckerType
|
||||
className="pl-4 mt-3"
|
||||
value={answer.id}
|
||||
onChange={(e) => setAnswer({ correct: e.target.checked })}
|
||||
checked={answer.correct}
|
||||
>
|
||||
{answer.id}
|
||||
</CheckerType>
|
||||
);
|
||||
};
|
||||
|
||||
const FeedbackControl = ({
|
||||
feedback, onChange, labelMessage, labelMessageBoldUnderline, key, answer, intl,
|
||||
}) => (
|
||||
<Form.Group key={key}>
|
||||
<Form.Label className="mb-3">
|
||||
<FormattedMessage
|
||||
{...labelMessage}
|
||||
values={{
|
||||
answerId: answer.id,
|
||||
boldunderline: <b><u><FormattedMessage {...labelMessageBoldUnderline} /></u></b>,
|
||||
}}
|
||||
/>
|
||||
</Form.Label>
|
||||
<Form.Control
|
||||
placeholder={intl.formatMessage(messages.feedbackPlaceholder)}
|
||||
value={feedback}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</Form.Group>
|
||||
);
|
||||
|
||||
export const AnswerOption = ({
|
||||
answer,
|
||||
hasSingleAnswer,
|
||||
@@ -66,91 +27,56 @@ export const AnswerOption = ({
|
||||
const setAnswer = hooks.setAnswer({ answer, hasSingleAnswer, dispatch });
|
||||
const { isFeedbackVisible, toggleFeedback } = hooks.prepareFeedback(answer);
|
||||
|
||||
const displayFeedbackControl = (answerObject) => {
|
||||
if (problemType !== ProblemTypeKeys.MULTISELECT) {
|
||||
return FeedbackControl({
|
||||
key: `feedback-${answerObject.id}`,
|
||||
feedback: answerObject.selectedFeedback,
|
||||
onChange: (e) => setAnswer({ selectedFeedback: e.target.value }),
|
||||
labelMessage: messages.selectedFeedbackLabel,
|
||||
labelMessageBoldUnderline: messages.selectedFeedbackLabelBoldUnderlineText,
|
||||
answer: answerObject,
|
||||
intl,
|
||||
});
|
||||
}
|
||||
return [
|
||||
FeedbackControl({
|
||||
key: `selectedfeedback-${answerObject.id}`,
|
||||
feedback: answerObject.selectedFeedback,
|
||||
onChange: (e) => setAnswer({ selectedFeedback: e.target.value }),
|
||||
labelMessage: messages.selectedFeedbackLabel,
|
||||
labelMessageBoldUnderline: messages.selectedFeedbackLabelBoldUnderlineText,
|
||||
answer: answerObject,
|
||||
intl,
|
||||
}),
|
||||
FeedbackControl({
|
||||
key: `unselectedfeedback-${answerObject.id}`,
|
||||
feedback: answerObject.unselectedFeedback,
|
||||
onChange: (e) => setAnswer({ unselectedFeedback: e.target.value }),
|
||||
labelMessage: messages.unSelectedFeedbackLabel,
|
||||
labelMessageBoldUnderline: messages.unSelectedFeedbackLabelBoldUnderlineText,
|
||||
answer: answerObject,
|
||||
intl,
|
||||
}),
|
||||
];
|
||||
};
|
||||
|
||||
return (
|
||||
<Collapsible.Advanced
|
||||
open={isFeedbackVisible}
|
||||
onToggle={toggleFeedback}
|
||||
className="collapsible-card"
|
||||
className="answer-option d-flex flex-row justify-content-between flex-nowrap pb-2 pt-2"
|
||||
>
|
||||
<Row className="my-2">
|
||||
|
||||
<Col xs={1}>
|
||||
<Checker
|
||||
hasSingleAnswer={hasSingleAnswer}
|
||||
<div className="answer-option-flex-item-1 mr-1 d-flex align-items-center">
|
||||
<Checker
|
||||
hasSingleAnswer={hasSingleAnswer}
|
||||
answer={answer}
|
||||
setAnswer={setAnswer}
|
||||
/>
|
||||
</div>
|
||||
<div className="answer-option-flex-item-2 ml-1">
|
||||
<Form.Control
|
||||
as="textarea"
|
||||
className="answer-option-textarea text-gray-500 small"
|
||||
autoResize
|
||||
rows={1}
|
||||
value={answer.title}
|
||||
onChange={(e) => { setAnswer({ title: e.target.value }); }}
|
||||
placeholder={intl.formatMessage(messages.answerTextboxPlaceholder)}
|
||||
/>
|
||||
<Collapsible.Body>
|
||||
<FeedbackBox
|
||||
problemType={problemType}
|
||||
answer={answer}
|
||||
setAnswer={setAnswer}
|
||||
intl={intl}
|
||||
/>
|
||||
</Col>
|
||||
|
||||
<Col xs={10}>
|
||||
<Form.Control
|
||||
as="textarea"
|
||||
rows={1}
|
||||
value={answer.title}
|
||||
onChange={(e) => { setAnswer({ title: e.target.value }); }}
|
||||
placeholder={intl.formatMessage(messages.answerTextboxPlaceholder)}
|
||||
/>
|
||||
|
||||
<Collapsible.Body>
|
||||
<div className="bg-dark-100 p-4 mt-3">
|
||||
{displayFeedbackControl(answer)}
|
||||
</div>
|
||||
</Collapsible.Body>
|
||||
</Col>
|
||||
|
||||
<Col xs={1} className="d-inline-flex mt-1">
|
||||
<Collapsible.Trigger>
|
||||
<IconButton
|
||||
src={AddComment}
|
||||
iconAs={Icon}
|
||||
alt={intl.formatMessage(messages.feedbackToggleIconAltText)}
|
||||
variant="primary"
|
||||
/>
|
||||
</Collapsible.Trigger>
|
||||
</Collapsible.Body>
|
||||
</div>
|
||||
<div className="answer-option-flex-item-3 d-flex flex-row flex-nowrap">
|
||||
<Collapsible.Trigger>
|
||||
<IconButton
|
||||
src={Delete}
|
||||
src={Feedback}
|
||||
className="feedback-icon-button"
|
||||
iconAs={Icon}
|
||||
alt={intl.formatMessage(messages.answerDeleteIconAltText)}
|
||||
onClick={removeAnswer}
|
||||
alt={intl.formatMessage(messages.feedbackToggleIconAltText)}
|
||||
variant="primary"
|
||||
/>
|
||||
</Col>
|
||||
|
||||
</Row>
|
||||
</Collapsible.Trigger>
|
||||
<IconButton
|
||||
src={Delete}
|
||||
iconAs={Icon}
|
||||
alt={intl.formatMessage(messages.answerDeleteIconAltText)}
|
||||
onClick={removeAnswer}
|
||||
variant="primary"
|
||||
/>
|
||||
</div>
|
||||
</Collapsible.Advanced>
|
||||
);
|
||||
};
|
||||
@@ -164,22 +90,6 @@ AnswerOption.propTypes = {
|
||||
problemType: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
FeedbackControl.propTypes = {
|
||||
feedback: PropTypes.string.isRequired,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
labelMessage: PropTypes.string.isRequired,
|
||||
labelMessageBoldUnderline: PropTypes.string.isRequired,
|
||||
key: PropTypes.string.isRequired,
|
||||
answer: answerOptionProps.isRequired,
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
Checker.propTypes = {
|
||||
hasSingleAnswer: PropTypes.bool.isRequired,
|
||||
answer: answerOptionProps.isRequired,
|
||||
setAnswer: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export const mapStateToProps = (state) => ({
|
||||
problemType: selectors.problem.problemType(state),
|
||||
});
|
||||
|
||||
@@ -2,17 +2,40 @@
|
||||
|
||||
exports[`AnswerOption render snapshot: renders correct option with feedback 1`] = `
|
||||
<Advanced
|
||||
className="collapsible-card"
|
||||
className="answer-option d-flex flex-row justify-content-between flex-nowrap pb-2 pt-2"
|
||||
onToggle={[Function]}
|
||||
open={false}
|
||||
>
|
||||
<Row
|
||||
className="my-2"
|
||||
<div
|
||||
className="answer-option-flex-item-1 mr-1 d-flex align-items-center"
|
||||
>
|
||||
<Col
|
||||
xs={1}
|
||||
>
|
||||
<Checker
|
||||
<Checker
|
||||
answer={
|
||||
Object {
|
||||
"correct": true,
|
||||
"id": "A",
|
||||
"selectedFeedback": "some feedback",
|
||||
"title": "Answer 1",
|
||||
}
|
||||
}
|
||||
hasSingleAnswer={false}
|
||||
setAnswer={[Function]}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="answer-option-flex-item-2 ml-1"
|
||||
>
|
||||
<Form.Control
|
||||
as="textarea"
|
||||
autoResize={true}
|
||||
className="answer-option-textarea text-gray-500 small"
|
||||
onChange={[Function]}
|
||||
placeholder="Enter an answer"
|
||||
rows={1}
|
||||
value="Answer 1"
|
||||
/>
|
||||
<Body>
|
||||
<injectIntl(ShimmedIntlComponent)
|
||||
answer={
|
||||
Object {
|
||||
"correct": true,
|
||||
@@ -21,94 +44,74 @@ exports[`AnswerOption render snapshot: renders correct option with feedback 1`]
|
||||
"title": "Answer 1",
|
||||
}
|
||||
}
|
||||
hasSingleAnswer={false}
|
||||
intl={
|
||||
Object {
|
||||
"formatMessage": [Function],
|
||||
}
|
||||
}
|
||||
problemType="multiplechoiceresponse"
|
||||
setAnswer={[Function]}
|
||||
/>
|
||||
</Col>
|
||||
<Col
|
||||
xs={10}
|
||||
>
|
||||
<Form.Control
|
||||
as="textarea"
|
||||
onChange={[Function]}
|
||||
placeholder="Enter an answer"
|
||||
rows={1}
|
||||
value="Answer 1"
|
||||
/>
|
||||
<Body>
|
||||
<div
|
||||
className="bg-dark-100 p-4 mt-3"
|
||||
>
|
||||
<Form.Group
|
||||
key="feedback-A"
|
||||
>
|
||||
<Form.Label
|
||||
className="mb-3"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Show following feedback when {answerId} {boldunderline}:"
|
||||
description="Label text for feedback if option is selected"
|
||||
id="authoring.answerwidget.feedback.selected.label"
|
||||
values={
|
||||
Object {
|
||||
"answerId": "A",
|
||||
"boldunderline": <b>
|
||||
<u>
|
||||
<FormattedMessage
|
||||
defaultMessage="is selected"
|
||||
description="Bold & underlined text for feedback if option is selected"
|
||||
id="authoring.answerwidget.feedback.selected.label.boldunderline"
|
||||
/>
|
||||
</u>
|
||||
</b>,
|
||||
}
|
||||
}
|
||||
/>
|
||||
</Form.Label>
|
||||
<Form.Control
|
||||
onChange={[Function]}
|
||||
placeholder="Feedback message"
|
||||
value="some feedback"
|
||||
/>
|
||||
</Form.Group>
|
||||
</div>
|
||||
</Body>
|
||||
</Col>
|
||||
<Col
|
||||
className="d-inline-flex mt-1"
|
||||
xs={1}
|
||||
>
|
||||
<Trigger>
|
||||
<IconButton
|
||||
alt="Toggle feedback"
|
||||
iconAs="Icon"
|
||||
variant="primary"
|
||||
/>
|
||||
</Trigger>
|
||||
</Body>
|
||||
</div>
|
||||
<div
|
||||
className="answer-option-flex-item-3 d-flex flex-row flex-nowrap"
|
||||
>
|
||||
<Trigger>
|
||||
<IconButton
|
||||
alt="Delete answer"
|
||||
alt="Toggle feedback"
|
||||
className="feedback-icon-button"
|
||||
iconAs="Icon"
|
||||
onClick={[Function]}
|
||||
variant="primary"
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</Trigger>
|
||||
<IconButton
|
||||
alt="Delete answer"
|
||||
iconAs="Icon"
|
||||
onClick={[Function]}
|
||||
variant="primary"
|
||||
/>
|
||||
</div>
|
||||
</Advanced>
|
||||
`;
|
||||
|
||||
exports[`AnswerOption render snapshot: renders correct option with selected unselected feedback 1`] = `
|
||||
<Advanced
|
||||
className="collapsible-card"
|
||||
className="answer-option d-flex flex-row justify-content-between flex-nowrap pb-2 pt-2"
|
||||
onToggle={[Function]}
|
||||
open={false}
|
||||
>
|
||||
<Row
|
||||
className="my-2"
|
||||
<div
|
||||
className="answer-option-flex-item-1 mr-1 d-flex align-items-center"
|
||||
>
|
||||
<Col
|
||||
xs={1}
|
||||
>
|
||||
<Checker
|
||||
<Checker
|
||||
answer={
|
||||
Object {
|
||||
"correct": true,
|
||||
"id": "A",
|
||||
"selectedFeedback": "selected feedback",
|
||||
"title": "Answer 1",
|
||||
"unselectedFeedback": "unselected feedback",
|
||||
}
|
||||
}
|
||||
hasSingleAnswer={false}
|
||||
setAnswer={[Function]}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="answer-option-flex-item-2 ml-1"
|
||||
>
|
||||
<Form.Control
|
||||
as="textarea"
|
||||
autoResize={true}
|
||||
className="answer-option-textarea text-gray-500 small"
|
||||
onChange={[Function]}
|
||||
placeholder="Enter an answer"
|
||||
rows={1}
|
||||
value="Answer 1"
|
||||
/>
|
||||
<Body>
|
||||
<injectIntl(ShimmedIntlComponent)
|
||||
answer={
|
||||
Object {
|
||||
"correct": true,
|
||||
@@ -118,109 +121,33 @@ exports[`AnswerOption render snapshot: renders correct option with selected unse
|
||||
"unselectedFeedback": "unselected feedback",
|
||||
}
|
||||
}
|
||||
hasSingleAnswer={false}
|
||||
intl={
|
||||
Object {
|
||||
"formatMessage": [Function],
|
||||
}
|
||||
}
|
||||
problemType="choiceresponse"
|
||||
setAnswer={[Function]}
|
||||
/>
|
||||
</Col>
|
||||
<Col
|
||||
xs={10}
|
||||
>
|
||||
<Form.Control
|
||||
as="textarea"
|
||||
onChange={[Function]}
|
||||
placeholder="Enter an answer"
|
||||
rows={1}
|
||||
value="Answer 1"
|
||||
/>
|
||||
<Body>
|
||||
<div
|
||||
className="bg-dark-100 p-4 mt-3"
|
||||
>
|
||||
<Form.Group
|
||||
key="selectedfeedback-A"
|
||||
>
|
||||
<Form.Label
|
||||
className="mb-3"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Show following feedback when {answerId} {boldunderline}:"
|
||||
description="Label text for feedback if option is selected"
|
||||
id="authoring.answerwidget.feedback.selected.label"
|
||||
values={
|
||||
Object {
|
||||
"answerId": "A",
|
||||
"boldunderline": <b>
|
||||
<u>
|
||||
<FormattedMessage
|
||||
defaultMessage="is selected"
|
||||
description="Bold & underlined text for feedback if option is selected"
|
||||
id="authoring.answerwidget.feedback.selected.label.boldunderline"
|
||||
/>
|
||||
</u>
|
||||
</b>,
|
||||
}
|
||||
}
|
||||
/>
|
||||
</Form.Label>
|
||||
<Form.Control
|
||||
onChange={[Function]}
|
||||
placeholder="Feedback message"
|
||||
value="selected feedback"
|
||||
/>
|
||||
</Form.Group>
|
||||
<Form.Group
|
||||
key="unselectedfeedback-A"
|
||||
>
|
||||
<Form.Label
|
||||
className="mb-3"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Show following feedback when {answerId} {boldunderline}:"
|
||||
description="Label text for feedback if option is not selected"
|
||||
id="authoring.answerwidget.feedback.unselected.label"
|
||||
values={
|
||||
Object {
|
||||
"answerId": "A",
|
||||
"boldunderline": <b>
|
||||
<u>
|
||||
<FormattedMessage
|
||||
defaultMessage="is not selected"
|
||||
description="Bold & underlined text for feedback if option is not selected"
|
||||
id="authoring.answerwidget.feedback.unselected.label.boldunderline"
|
||||
/>
|
||||
</u>
|
||||
</b>,
|
||||
}
|
||||
}
|
||||
/>
|
||||
</Form.Label>
|
||||
<Form.Control
|
||||
onChange={[Function]}
|
||||
placeholder="Feedback message"
|
||||
value="unselected feedback"
|
||||
/>
|
||||
</Form.Group>
|
||||
</div>
|
||||
</Body>
|
||||
</Col>
|
||||
<Col
|
||||
className="d-inline-flex mt-1"
|
||||
xs={1}
|
||||
>
|
||||
<Trigger>
|
||||
<IconButton
|
||||
alt="Toggle feedback"
|
||||
iconAs="Icon"
|
||||
variant="primary"
|
||||
/>
|
||||
</Trigger>
|
||||
</Body>
|
||||
</div>
|
||||
<div
|
||||
className="answer-option-flex-item-3 d-flex flex-row flex-nowrap"
|
||||
>
|
||||
<Trigger>
|
||||
<IconButton
|
||||
alt="Delete answer"
|
||||
alt="Toggle feedback"
|
||||
className="feedback-icon-button"
|
||||
iconAs="Icon"
|
||||
onClick={[Function]}
|
||||
variant="primary"
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</Trigger>
|
||||
<IconButton
|
||||
alt="Delete answer"
|
||||
iconAs="Icon"
|
||||
onClick={[Function]}
|
||||
variant="primary"
|
||||
/>
|
||||
</div>
|
||||
</Advanced>
|
||||
`;
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Checker component with multiple answers 1`] = `
|
||||
<Form.Checkbox
|
||||
checked={true}
|
||||
className="pl-4"
|
||||
onChange={[Function]}
|
||||
value="A"
|
||||
>
|
||||
A
|
||||
</Form.Checkbox>
|
||||
`;
|
||||
|
||||
exports[`Checker component with single answer 1`] = `
|
||||
<Radio
|
||||
checked={true}
|
||||
className="pl-4"
|
||||
onChange={[Function]}
|
||||
value="A"
|
||||
>
|
||||
A
|
||||
</Radio>
|
||||
`;
|
||||
@@ -0,0 +1,32 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Form } from '@edx/paragon';
|
||||
|
||||
const Checker = ({
|
||||
hasSingleAnswer, answer, setAnswer,
|
||||
}) => {
|
||||
let CheckerType = Form.Checkbox;
|
||||
if (hasSingleAnswer) {
|
||||
CheckerType = Form.Radio;
|
||||
}
|
||||
return (
|
||||
<CheckerType
|
||||
className="pl-4"
|
||||
value={answer.id}
|
||||
onChange={(e) => setAnswer({ correct: e.target.checked })}
|
||||
checked={answer.correct}
|
||||
>
|
||||
{answer.id}
|
||||
</CheckerType>
|
||||
);
|
||||
};
|
||||
Checker.propTypes = {
|
||||
hasSingleAnswer: PropTypes.bool.isRequired,
|
||||
answer: PropTypes.shape({
|
||||
correct: PropTypes.bool,
|
||||
id: PropTypes.number,
|
||||
}).isRequired,
|
||||
setAnswer: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default Checker;
|
||||
@@ -0,0 +1,22 @@
|
||||
import { shallow } from 'enzyme';
|
||||
import Checker from '.';
|
||||
|
||||
const props = {
|
||||
hasSingleAnswer: true,
|
||||
answer: {
|
||||
id: 'A',
|
||||
title: 'Answer 1',
|
||||
correct: true,
|
||||
selectedFeedback: 'some feedback',
|
||||
},
|
||||
setAnswer: jest.fn(),
|
||||
};
|
||||
describe('Checker component', () => {
|
||||
test('with single answer', () => {
|
||||
expect(shallow(<Checker {...props} />)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('with multiple answers', () => {
|
||||
expect(shallow(<Checker {...props} hasSingleAnswer={false} />)).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,43 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
|
||||
import { answerOptionProps } from '../../../../../../../data/services/cms/types';
|
||||
import FeedbackControl from './FeedbackControl';
|
||||
import { messages } from './messages';
|
||||
|
||||
export const FeedbackBox = ({
|
||||
answer, setAnswer, intl,
|
||||
}) => {
|
||||
const props = {
|
||||
onChange: (e) => setAnswer({ selectedFeedback: e.target.value }),
|
||||
answer,
|
||||
intl,
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="bg-light-300 p-4 mt-3">
|
||||
<FeedbackControl
|
||||
key={`selectedfeedback-${answer.id}`}
|
||||
feedback={answer.selectedFeedback}
|
||||
labelMessage={messages.selectedFeedbackLabel}
|
||||
labelMessageBoldUnderline={messages.selectedFeedbackLabelBoldUnderlineText}
|
||||
{...props}
|
||||
/>
|
||||
<FeedbackControl
|
||||
key={`unselectedfeedback-${answer.id}`}
|
||||
feedback={answer.unselectedFeedback}
|
||||
labelMessage={messages.unSelectedFeedbackLabel}
|
||||
labelMessageBoldUnderline={messages.unSelectedFeedbackLabelBoldUnderlineText}
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
FeedbackBox.propTypes = {
|
||||
answer: answerOptionProps.isRequired,
|
||||
setAnswer: PropTypes.func.isRequired,
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
export default injectIntl(FeedbackBox);
|
||||
@@ -0,0 +1,22 @@
|
||||
import { shallow } from 'enzyme';
|
||||
import { FeedbackBox } from './FeedbackBox';
|
||||
|
||||
const answerWithFeedback = {
|
||||
id: 'A',
|
||||
title: 'Answer 1',
|
||||
correct: true,
|
||||
selectedFeedback: 'some feedback',
|
||||
unselectedFeedback: 'unselectedFeedback',
|
||||
};
|
||||
|
||||
const props = {
|
||||
answer: answerWithFeedback,
|
||||
intl: {},
|
||||
setAnswer: jest.fn(),
|
||||
};
|
||||
|
||||
describe('FeedbackBox component', () => {
|
||||
test('renders', () => {
|
||||
expect(shallow(<FeedbackBox {...props} />)).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,38 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FormattedMessage, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import { Form } from '@edx/paragon';
|
||||
|
||||
import { answerOptionProps } from '../../../../../../../data/services/cms/types';
|
||||
import messages from './messages';
|
||||
|
||||
const FeedbackControl = ({
|
||||
feedback, onChange, labelMessage, labelMessageBoldUnderline, answer, intl,
|
||||
}) => (
|
||||
<Form.Group>
|
||||
<Form.Label className="mb-3">
|
||||
<FormattedMessage
|
||||
{...labelMessage}
|
||||
values={{
|
||||
answerId: answer.id,
|
||||
boldunderline: <b><u><FormattedMessage {...labelMessageBoldUnderline} /></u></b>,
|
||||
}}
|
||||
/>
|
||||
</Form.Label>
|
||||
<Form.Control
|
||||
placeholder={intl.formatMessage(messages.feedbackPlaceholder)}
|
||||
value={feedback}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</Form.Group>
|
||||
);
|
||||
FeedbackControl.propTypes = {
|
||||
feedback: PropTypes.string.isRequired,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
labelMessage: PropTypes.string.isRequired,
|
||||
labelMessageBoldUnderline: PropTypes.string.isRequired,
|
||||
answer: answerOptionProps.isRequired,
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
export default FeedbackControl;
|
||||
@@ -0,0 +1,26 @@
|
||||
import { shallow } from 'enzyme';
|
||||
import FeedbackControl from './FeedbackControl';
|
||||
|
||||
const answerWithFeedback = {
|
||||
id: 'A',
|
||||
title: 'Answer 1',
|
||||
correct: true,
|
||||
selectedFeedback: 'some feedback',
|
||||
unselectedFeedback: 'unselectedFeedback',
|
||||
};
|
||||
|
||||
const props = {
|
||||
answer: answerWithFeedback,
|
||||
intl: { formatMessage: jest.fn() },
|
||||
setAnswer: jest.fn(),
|
||||
feedback: 'feedback',
|
||||
onChange: jest.fn(),
|
||||
labelMessage: 'msg',
|
||||
labelMessageBoldUnderline: 'msg',
|
||||
};
|
||||
|
||||
describe('FeedbackControl component', () => {
|
||||
test('renders', () => {
|
||||
expect(shallow(<FeedbackControl {...props} />)).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,66 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`FeedbackBox component renders 1`] = `
|
||||
<div
|
||||
className="bg-light-300 p-4 mt-3"
|
||||
>
|
||||
<FeedbackControl
|
||||
answer={
|
||||
Object {
|
||||
"correct": true,
|
||||
"id": "A",
|
||||
"selectedFeedback": "some feedback",
|
||||
"title": "Answer 1",
|
||||
"unselectedFeedback": "unselectedFeedback",
|
||||
}
|
||||
}
|
||||
feedback="some feedback"
|
||||
intl={Object {}}
|
||||
key="selectedfeedback-A"
|
||||
labelMessage={
|
||||
Object {
|
||||
"defaultMessage": "Show following feedback when {answerId} {boldunderline}:",
|
||||
"description": "Label text for feedback if option is selected",
|
||||
"id": "authoring.answerwidget.feedback.selected.label",
|
||||
}
|
||||
}
|
||||
labelMessageBoldUnderline={
|
||||
Object {
|
||||
"defaultMessage": "is selected",
|
||||
"description": "Bold & underlined text for feedback if option is selected",
|
||||
"id": "authoring.answerwidget.feedback.selected.label.boldunderline",
|
||||
}
|
||||
}
|
||||
onChange={[Function]}
|
||||
/>
|
||||
<FeedbackControl
|
||||
answer={
|
||||
Object {
|
||||
"correct": true,
|
||||
"id": "A",
|
||||
"selectedFeedback": "some feedback",
|
||||
"title": "Answer 1",
|
||||
"unselectedFeedback": "unselectedFeedback",
|
||||
}
|
||||
}
|
||||
feedback="unselectedFeedback"
|
||||
intl={Object {}}
|
||||
key="unselectedfeedback-A"
|
||||
labelMessage={
|
||||
Object {
|
||||
"defaultMessage": "Show following feedback when {answerId} {boldunderline}:",
|
||||
"description": "Label text for feedback if option is not selected",
|
||||
"id": "authoring.answerwidget.feedback.unselected.label",
|
||||
}
|
||||
}
|
||||
labelMessageBoldUnderline={
|
||||
Object {
|
||||
"defaultMessage": "is not selected",
|
||||
"description": "Bold & underlined text for feedback if option is not selected",
|
||||
"id": "authoring.answerwidget.feedback.unselected.label.boldunderline",
|
||||
}
|
||||
}
|
||||
onChange={[Function]}
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
@@ -0,0 +1,33 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`FeedbackControl component renders 1`] = `
|
||||
<Form.Group>
|
||||
<Form.Label
|
||||
className="mb-3"
|
||||
>
|
||||
<FormattedMessage
|
||||
0="m"
|
||||
1="s"
|
||||
2="g"
|
||||
values={
|
||||
Object {
|
||||
"answerId": "A",
|
||||
"boldunderline": <b>
|
||||
<u>
|
||||
<FormattedMessage
|
||||
0="m"
|
||||
1="s"
|
||||
2="g"
|
||||
/>
|
||||
</u>
|
||||
</b>,
|
||||
}
|
||||
}
|
||||
/>
|
||||
</Form.Label>
|
||||
<Form.Control
|
||||
onChange={[MockFunction]}
|
||||
value="feedback"
|
||||
/>
|
||||
</Form.Group>
|
||||
`;
|
||||
@@ -0,0 +1,2 @@
|
||||
export { default as FeedbackBox } from './FeedbackBox';
|
||||
export { default as FeedbackControl } from './FeedbackControl';
|
||||
@@ -0,0 +1,34 @@
|
||||
export const messages = {
|
||||
feedbackPlaceholder: {
|
||||
id: 'authoring.answerwidget.feedback.placeholder',
|
||||
defaultMessage: 'Feedback message',
|
||||
description: 'Placeholder text for feedback text',
|
||||
},
|
||||
feedbackToggleIconAltText: {
|
||||
id: 'authoring.answerwidget.feedback.icon.alt',
|
||||
defaultMessage: 'Toggle feedback',
|
||||
description: 'Alt text for feedback toggle icon',
|
||||
},
|
||||
selectedFeedbackLabel: {
|
||||
id: 'authoring.answerwidget.feedback.selected.label',
|
||||
defaultMessage: 'Show following feedback when {answerId} {boldunderline}:',
|
||||
description: 'Label text for feedback if option is selected',
|
||||
},
|
||||
selectedFeedbackLabelBoldUnderlineText: {
|
||||
id: 'authoring.answerwidget.feedback.selected.label.boldunderline',
|
||||
defaultMessage: 'is selected',
|
||||
description: 'Bold & underlined text for feedback if option is selected',
|
||||
},
|
||||
unSelectedFeedbackLabel: {
|
||||
id: 'authoring.answerwidget.feedback.unselected.label',
|
||||
defaultMessage: 'Show following feedback when {answerId} {boldunderline}:',
|
||||
description: 'Label text for feedback if option is not selected',
|
||||
},
|
||||
unSelectedFeedbackLabelBoldUnderlineText: {
|
||||
id: 'authoring.answerwidget.feedback.unselected.label.boldunderline',
|
||||
defaultMessage: 'is not selected',
|
||||
description: 'Bold & underlined text for feedback if option is not selected',
|
||||
},
|
||||
};
|
||||
|
||||
export default messages;
|
||||
@@ -1,11 +1,59 @@
|
||||
.problem-answer {
|
||||
padding: 12px;
|
||||
color: #00262B;
|
||||
.problem-answer-title {
|
||||
font-weight: bold;
|
||||
}
|
||||
@import '../../../../../../colors.scss';
|
||||
|
||||
.problem-answer-description {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
.problem-answer {
|
||||
padding: 12px;
|
||||
|
||||
.problem-answer-title {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.problem-answer-description {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
}
|
||||
|
||||
.answer-option {
|
||||
// The paragon icon path is missing "fill: currentColor"
|
||||
.feedback-icon-button {
|
||||
&:hover {
|
||||
&:not(:focus) {
|
||||
svg {
|
||||
path {
|
||||
fill: $white;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.answer-option-flex-item-1 {
|
||||
flex-basis: 8.33%;
|
||||
}
|
||||
|
||||
.answer-option-flex-item-2 {
|
||||
flex-basis: 83.34%;
|
||||
}
|
||||
|
||||
.answer-option-flex-item-3 {
|
||||
flex-basis: 8.33%;
|
||||
}
|
||||
|
||||
.answer-option-textarea {
|
||||
textarea {
|
||||
border: none;
|
||||
resize: none;
|
||||
|
||||
&:active, &:hover, &:focus, &:focus-visible {
|
||||
border: none;
|
||||
border-right: none;
|
||||
border-left: none;
|
||||
border-radius: 0;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
&:focus, &:hover {
|
||||
border-bottom: 1px solid $black;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1 +1 @@
|
||||
/* styles go here */
|
||||
@import './colors.scss';
|
||||
Reference in New Issue
Block a user