From 4273e2820baaa47ad79860ac50cfa7bdbd1e19fe Mon Sep 17 00:00:00 2001 From: Abhishek-Punhani Date: Sat, 30 May 2026 17:59:33 +0530 Subject: [PATCH 1/4] refactor: update AnswersEditor to use checkbox inputs and improve answer row styling and layout logic Signed-off-by: Abhishek-Punhani --- .../AnswersEditor/AnswersEditor.spec.js | 118 +++- .../AnswersEditor/AnswersEditor.vue | 541 ++++++++++-------- 2 files changed, 404 insertions(+), 255 deletions(-) diff --git a/contentcuration/contentcuration/frontend/channelEdit/components/AnswersEditor/AnswersEditor.spec.js b/contentcuration/contentcuration/frontend/channelEdit/components/AnswersEditor/AnswersEditor.spec.js index fe0ec93a82..dc1ac59033 100644 --- a/contentcuration/contentcuration/frontend/channelEdit/components/AnswersEditor/AnswersEditor.spec.js +++ b/contentcuration/contentcuration/frontend/channelEdit/components/AnswersEditor/AnswersEditor.spec.js @@ -7,6 +7,12 @@ import TipTapEditor from 'shared/views/TipTapEditor/TipTapEditor/TipTapEditor.vu jest.mock('shared/views/TipTapEditor/TipTapEditor/TipTapEditor.vue'); +jest.mock('kolibri-design-system/lib/composables/useKResponsiveWindow', () => { + return function useKResponsiveWindow() { + return { windowBreakpoint: { value: 4 } }; + }; +}); + const clickNewAnswerBtn = async wrapper => { await wrapper.findComponent('[data-test="newAnswerBtn"]').trigger('click'); }; @@ -16,7 +22,7 @@ const rendersNewAnswerBtn = wrapper => { }; const clickAnswer = async (wrapper, answerIdx) => { - await wrapper.findAllComponents('[data-test="answer"]').at(answerIdx).trigger('click'); + await wrapper.findAll('[data-test="answer"]').at(answerIdx).trigger('click'); }; const clickMoveAnswerUp = async (wrapper, answerIdx) => { @@ -59,6 +65,24 @@ describe('AnswersEditor', () => { expect(wrapper.html()).toContain('Question has no answer options'); }); + describe('answers label', () => { + it.each([ + [AssessmentItemTypes.SINGLE_SELECTION, AnswersEditor.$trs.answersLabelSingleChoice], + [AssessmentItemTypes.TRUE_FALSE, AnswersEditor.$trs.answersLabelSingleChoice], + [AssessmentItemTypes.MULTIPLE_SELECTION, AnswersEditor.$trs.answersLabelMultipleChoice], + [AssessmentItemTypes.INPUT_QUESTION, AnswersEditor.$trs.answersLabelNumeric], + ])('renders the correct label for %s questions', (questionKind, expectedLabel) => { + wrapper = shallowMount(AnswersEditor, { + propsData: { + questionKind, + answers: [], + }, + }); + + expect(wrapper.text()).toContain(expectedLabel); + }); + }); + describe('for a single selection question', () => { beforeEach(() => { wrapper = mount(AnswersEditor, { @@ -72,12 +96,12 @@ describe('AnswersEditor', () => { }); }); - it('renders answers as radio selects', () => { + it('renders answers as checkbox controls', () => { const inputs = wrapper.findAll('input'); expect(inputs.length).toBe(2); for (const n in [0, 1]) { - expect(inputs.at(n).attributes()['type']).toBe('radio'); + expect(inputs.at(n).attributes()['type']).toBe('checkbox'); } }); @@ -88,8 +112,36 @@ describe('AnswersEditor', () => { expect(inputs.at(1).element.checked).toBe(false); }); + it('marks correct answer rows with the selected visual state', () => { + const answerRows = wrapper.findAll('[data-test="answer"]'); + + expect(answerRows.at(0).attributes('style')).toContain('border-color'); + expect(answerRows.at(0).attributes('style')).toContain('background-color'); + expect(answerRows.at(1).attributes('style')).toContain('border-color'); + expect(answerRows.at(1).attributes('style')).toContain('transparent'); + }); + + it('renders all possible answers', () => { + // First answer is open by default (openAnswerIdx=0) — edit mode TipTapEditor + // Second answer is closed — view mode TipTapEditor + const editors = wrapper.findAllComponents(TipTapEditor); + + // Closed answer uses view mode to safely render rich text + const viewEditor = editors.filter(e => e.props('mode') === 'view').at(0); + expect(viewEditor.exists()).toBe(true); + expect(viewEditor.props('value')).toBe('Peanut butter'); + + // Open answer uses edit mode + const editEditor = editors.filter(e => e.props('mode') === 'edit').at(0); + expect(editEditor.exists()).toBe(true); + expect(editEditor.props('value')).toBe('Mayonnaise (I mean you can, but...)'); + }); + it('renders new answer button', () => { expect(rendersNewAnswerBtn(wrapper)).toBe(true); + expect(wrapper.findComponent('[data-test="newAnswerBtn"]').text()).toContain( + AnswersEditor.$trs.newAnswerBtnLabel, + ); }); describe('on new answer button click', () => { @@ -140,6 +192,21 @@ describe('AnswersEditor', () => { expect(inputs.at(2).element.checked).toBe(true); }); + it('renders all possible answers', () => { + // First answer is open by default (openAnswerIdx=0) — edit mode TipTapEditor + // Remaining answers are closed — each gets a view mode TipTapEditor + const editors = wrapper.findAllComponents(TipTapEditor); + + const viewEditors = editors.filter(e => e.props('mode') === 'view'); + expect(viewEditors.length).toBe(2); + expect(viewEditors.at(0).props('value')).toBe('Peanut butter'); + expect(viewEditors.at(1).props('value')).toBe('Jelly'); + + const editEditor = editors.filter(e => e.props('mode') === 'edit').at(0); + expect(editEditor.exists()).toBe(true); + expect(editEditor.props('value')).toBe('Mayonnaise (I mean you can, but...)'); + }); + it('renders new answer button', () => { expect(rendersNewAnswerBtn(wrapper)).toBe(true); }); @@ -175,12 +242,12 @@ describe('AnswersEditor', () => { }); }); - it('renders answers as radio selects', () => { + it('renders answers as checkbox controls', () => { const inputs = wrapper.findAll('input'); expect(inputs.length).toBe(2); for (const n in [0, 1]) { - expect(inputs.at(n).attributes()['type']).toBe('radio'); + expect(inputs.at(n).attributes()['type']).toBe('checkbox'); } }); @@ -202,27 +269,17 @@ describe('AnswersEditor', () => { propsData: { questionKind: AssessmentItemTypes.INPUT_QUESTION, answers: [ - { answer: 'Mayonnaise (I mean you can, but...)', correct: true, order: 1 }, - { answer: 'Peanut butter', correct: true, order: 2 }, + { answer: '1.5', correct: true, order: 1 }, + { answer: '2', correct: true, order: 2 }, ], }, }); }); - it('renders all possible answers', () => { - // The answer text won't render when `mount()` is used so we override - // and use shallowMount here - wrapper = shallowMount(AnswersEditor, { - propsData: { - questionKind: AssessmentItemTypes.INPUT_QUESTION, - answers: [ - { answer: 'Mayonnaise (I mean you can, but...)', correct: true, order: 1 }, - { answer: 'Peanut butter', correct: true, order: 2 }, - ], - }, - }); - expect(wrapper.html()).toContain('Mayonnaise (I mean you can, but...)'); - expect(wrapper.html()).toContain('Peanut butter'); + it('renders open answer as a number input and closed answer as plain text', () => { + expect(wrapper.find('input[type="number"]').element.value).toBe('1.5'); + + expect(wrapper.html()).toContain('2'); }); it('renders new answer button', () => { @@ -238,8 +295,8 @@ describe('AnswersEditor', () => { expect(wrapper.emitted().update).toBeTruthy(); expect(wrapper.emitted().update.length).toBe(1); expect(wrapper.emitted().update[0][0]).toEqual([ - { answer: 'Mayonnaise (I mean you can, but...)', correct: true, order: 1 }, - { answer: 'Peanut butter', correct: true, order: 2 }, + { answer: '1.5', correct: true, order: 1 }, + { answer: '2', correct: true, order: 2 }, { answer: '', correct: true, order: 3 }, ]); }); @@ -260,14 +317,13 @@ describe('AnswersEditor', () => { }); }); - it('passes autofocus=true to the open answer editor', () => { - const editors = wrapper.findAllComponents(TipTapEditor); - expect(editors.at(1).props('autofocus')).toBe(true); - }); - - it('passes autofocus=false to closed answer editors', () => { + it('passes autofocus=true to the open (edit-mode) answer editor', () => { + // In the new architecture, only the open answer mounts an edit-mode + // TipTapEditor with autofocus. Closed answers use a separate view-mode + // TipTapEditor. The edit-mode editor is the only one with autofocus=true. const editors = wrapper.findAllComponents(TipTapEditor); - expect(editors.at(0).props('autofocus')).toBe(false); + const editModeEditor = editors.filter(e => e.props('mode') === 'edit').at(0); + expect(editModeEditor.props('autofocus')).toBe(true); }); }); @@ -373,7 +429,7 @@ describe('AnswersEditor', () => { }); await wrapper.vm.$nextTick(); - await wrapper.findAll('input[type="radio"]').at(1).setChecked(); + await wrapper.findAll('.answer-selection input[type="checkbox"]').at(1).trigger('click'); }); it('emits update event with a payload containing updated answers', () => { diff --git a/contentcuration/contentcuration/frontend/channelEdit/components/AnswersEditor/AnswersEditor.vue b/contentcuration/contentcuration/frontend/channelEdit/components/AnswersEditor/AnswersEditor.vue index d87f205fb8..0e625a0a56 100644 --- a/contentcuration/contentcuration/frontend/channelEdit/components/AnswersEditor/AnswersEditor.vue +++ b/contentcuration/contentcuration/frontend/channelEdit/components/AnswersEditor/AnswersEditor.vue @@ -1,187 +1,128 @@ @@ -213,14 +163,12 @@ import useKResponsiveWindow from 'kolibri-design-system/lib/composables/useKResponsiveWindow'; import AssessmentItemToolbar from '../AssessmentItemToolbar'; import { AssessmentItemToolbarActions } from '../../constants'; - import { floatOrIntRegex, getCorrectAnswersIndices, mapCorrectAnswers } from '../../utils'; + import { getCorrectAnswersIndices, mapCorrectAnswers } from '../../utils'; import { AssessmentItemTypes } from 'shared/constants'; import { swapElements } from 'shared/utils/helpers'; - import Checkbox from 'shared/views/form/Checkbox'; import EditorImageProcessor from 'shared/views/TipTapEditor/TipTapEditor/services/imageService'; import TipTapEditor from 'shared/views/TipTapEditor/TipTapEditor/TipTapEditor.vue'; - import { isTouchDevice } from 'shared/utils/browserInfo'; const updateAnswersOrder = answers => { return answers.map((answer, idx) => { @@ -235,7 +183,6 @@ name: 'AnswersEditor', components: { AssessmentItemToolbar, - Checkbox, TipTapEditor, }, model: { @@ -263,8 +210,6 @@ return { EditorImageProcessor, // Make it available in the template correctAnswersIndices: getCorrectAnswersIndices(this.questionKind, this.answers), - numericRule: val => floatOrIntRegex.test(val) || this.$tr('numberFieldErrorLabel'), - isTouchDevice, }; }, computed: { @@ -286,17 +231,33 @@ isEditingAllowed() { return !this.isTrueFalse; }, + answersLabel() { + if (this.isSingleSelection || this.isTrueFalse) { + return this.$tr('answersLabelSingleChoice'); + } else if (this.isMultipleSelection) { + return this.$tr('answersLabelMultipleChoice'); + } else if (this.isInputQuestion) { + return this.$tr('answersLabelNumeric'); + } + return this.$tr('answersLabel'); + }, toolbarIconActions() { + // On small screens, collapse ALL actions into the ⋮ menu so only + // one icon renders, giving the answer text room to breathe. + const deleteAction = this.isSmallScreen + ? [AssessmentItemToolbarActions.DELETE_ITEM, { collapse: true }] + : AssessmentItemToolbarActions.DELETE_ITEM; + if (this.isSingleSelection || this.isMultipleSelection) { return [ - AssessmentItemToolbarActions.MOVE_ITEM_UP, - AssessmentItemToolbarActions.MOVE_ITEM_DOWN, - AssessmentItemToolbarActions.DELETE_ITEM, + [AssessmentItemToolbarActions.MOVE_ITEM_UP, { collapse: true }], + [AssessmentItemToolbarActions.MOVE_ITEM_DOWN, { collapse: true }], + deleteAction, ]; } if (this.isInputQuestion) { - return [AssessmentItemToolbarActions.DELETE_ITEM]; + return [deleteAction]; } return []; @@ -305,6 +266,31 @@ const { windowBreakpoint } = useKResponsiveWindow(); return windowBreakpoint.value ?? 0; }, + isSmallScreen() { + return this.screenSizeLevel <= 1; + }, + buttonAppearanceOverrides() { + return { + backgroundColor: this.$themeBrand.primary.v_50, + border: `1px dashed ${this.$themeBrand.primary.v_200}`, + color: `${this.$themeTokens.primary} !important`, + fontSize: '14px', + fontWeight: '600', + textTransform: 'none', + ':hover': { + backgroundColor: this.$themeBrand.primary.v_100, + }, + }; + }, + answerNumberClasses() { + return this.$computedClass({ + backgroundColor: this.$themePalette.white, + border: `1px solid ${this.$themePalette.grey.v_300}`, + ':focus': { + borderColor: this.$themePalette.grey.v_500, + }, + }); + }, }, watch: { answers() { @@ -324,32 +310,35 @@ answerClasses(answerIdx) { const classes = ['answer']; - if (this.isEditingAllowed) { - classes.push('editable'); - } - if (!this.isAnswerOpen(answerIdx)) { classes.push('closed'); - } - - if (this.answers[answerIdx].correct) { - classes.push('answer-correct'); - } else { - classes.push('answer-wrong'); + classes.push( + this.$computedClass({ + cursor: 'pointer', + ':hover': { + backgroundColor: this.$themePalette.grey.v_100, + }, + }), + ); } return classes; }, - indicatorClasses(answer) { - const classes = ['indicator']; - - if (answer.correct) { - classes.push('correct'); + toggleCorrectAnswer(answerIdx, isChecked) { + if (this.shouldHaveOneCorrectAnswer) { + if (isChecked) { + this.onCorrectAnswersIndicesUpdate(answerIdx); + } } else { - classes.push('wrong'); + // multiple selection + let indices = [...this.correctAnswersIndices]; + if (isChecked && !indices.includes(answerIdx)) { + indices.push(answerIdx); + } else if (!isChecked && indices.includes(answerIdx)) { + indices = indices.filter(idx => idx !== answerIdx); + } + this.onCorrectAnswersIndicesUpdate(indices); } - - return classes; }, onCorrectAnswersIndicesUpdate(newIndices) { // indices can be an array or a single value - depends on question type @@ -441,10 +430,10 @@ return; } - // do not open on checkbox or radio click + // do not open on checkbox click if ( - event.target.classList.contains('v-label') || - event.target.classList.contains('v-input--selection-controls__ripple') + event.target.tagName.toLowerCase() === 'input' || + event.target.closest('.answer-selection') !== null ) { return; } @@ -495,9 +484,12 @@ }, $trs: { answersLabel: 'Answers', + answersLabelSingleChoice: 'Answer options — select one correct answer', + answersLabelMultipleChoice: 'Answer options — select all correct answers', + answersLabelNumeric: 'Answer options — enter all acceptable spellings or formats', noAnswersPlaceholder: 'Question has no answer options', - newAnswerBtnLabel: 'New answer', - numberFieldErrorLabel: 'Answer must be a number', + newAnswerBtnLabel: 'Add option', + enterNumberPlaceholder: 'Enter number', }, }; @@ -506,57 +498,158 @@ From dc73590fd8c50424566c58777d6482b71fc3f0d7 Mon Sep 17 00:00:00 2001 From: Abhishek-Punhani Date: Sat, 30 May 2026 17:59:44 +0530 Subject: [PATCH 2/4] refactor: update HintsEditor styling to primary theme tokens and remove Edit action functionality Signed-off-by: Abhishek-Punhani --- .../components/HintsEditor/HintsEditor.vue | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/contentcuration/contentcuration/frontend/channelEdit/components/HintsEditor/HintsEditor.vue b/contentcuration/contentcuration/frontend/channelEdit/components/HintsEditor/HintsEditor.vue index e6df363478..4081ddb197 100644 --- a/contentcuration/contentcuration/frontend/channelEdit/components/HintsEditor/HintsEditor.vue +++ b/contentcuration/contentcuration/frontend/channelEdit/components/HintsEditor/HintsEditor.vue @@ -126,7 +126,7 @@
{{ $tr('newHintBtnLabel') }}
@@ -179,7 +179,6 @@ return { sectionOpen: false, toolbarIconActions: [ - [AssessmentItemToolbarActions.EDIT_ITEM, { collapse: true }], [AssessmentItemToolbarActions.MOVE_ITEM_UP, { collapse: true }], [AssessmentItemToolbarActions.MOVE_ITEM_DOWN, { collapse: true }], AssessmentItemToolbarActions.DELETE_ITEM, @@ -205,14 +204,14 @@ }, buttonAppearanceOverrides() { return { - backgroundColor: this.$themePalette.grey.v_50, - border: `1px dashed ${this.$themeTokens.fineLine}`, - color: `${this.$themePalette.grey.v_700} !important`, + backgroundColor: this.$themeBrand.primary.v_50, + border: `1px dashed ${this.$themeBrand.primary.v_200}`, + color: `${this.$themeTokens.primary} !important`, fontSize: '14px', fontWeight: '600', textTransform: 'none', ':hover': { - backgroundColor: this.$themePalette.grey.v_100, + backgroundColor: this.$themeBrand.primary.v_100, }, }; }, @@ -254,6 +253,13 @@ if (!this.isHintOpen(hintIdx)) { classes.push('closed'); + classes.push( + this.$computedClass({ + ':hover': { + backgroundColor: this.$themePalette.grey.v_100, + }, + }), + ); } return classes; @@ -326,10 +332,6 @@ }, onToolbarClick(action, hintIdx) { switch (action) { - case AssessmentItemToolbarActions.EDIT_ITEM: - this.emitOpen(hintIdx); - break; - case AssessmentItemToolbarActions.MOVE_ITEM_UP: this.moveHintUp(hintIdx); break; @@ -493,8 +495,6 @@ &.closed:hover { cursor: pointer; - /* stylelint-disable-next-line custom-property-pattern */ - background-color: var(--v-greyBackground-lighten1); } } @@ -502,7 +502,7 @@ display: flex; align-items: center; width: 100%; - min-height: 52px; + min-height: 42px; padding: 0 4px; overflow: hidden; border-radius: 4px; From 5f630993eee9c9b1d4bef2f01570f86f3a75e794 Mon Sep 17 00:00:00 2001 From: Abhishek-Punhani Date: Tue, 2 Jun 2026 15:21:49 +0530 Subject: [PATCH 3/4] refactor: simplify AnswersEditor layout, conditional hover states, and label strings Signed-off-by: Abhishek-Punhani --- .../AnswersEditor/AnswersEditor.vue | 204 +++++++++--------- 1 file changed, 104 insertions(+), 100 deletions(-) diff --git a/contentcuration/contentcuration/frontend/channelEdit/components/AnswersEditor/AnswersEditor.vue b/contentcuration/contentcuration/frontend/channelEdit/components/AnswersEditor/AnswersEditor.vue index 0e625a0a56..a021f888ef 100644 --- a/contentcuration/contentcuration/frontend/channelEdit/components/AnswersEditor/AnswersEditor.vue +++ b/contentcuration/contentcuration/frontend/channelEdit/components/AnswersEditor/AnswersEditor.vue @@ -22,6 +22,7 @@ v-for="(answer, answerIdx) in answers" :key="answerIdx" class="answer-border" + :class="answerClasses(answerIdx)" :style="{ borderColor: answer.correct ? $themeTokens.correct : $themeTokens.fineLine, backgroundColor: answer.correct ? $themePalette.green.v_100 : 'transparent', @@ -29,108 +30,110 @@ data-test="answer" @click="onAnswerClick($event, answerIdx)" > -
+
+
- -
- -
+ +
- -
- -
- -
-
- - {{ $tr('enterNumberPlaceholder') }} - - -
+ +
+ +
+ +
+
+ + {{ $tr('enterNumberPlaceholder') }} + +
+
-
- -
-
- -
-
- - +
+ +
+
- +
+ + + +
+
-
- -
+
+
@@ -139,7 +142,7 @@ Date: Thu, 4 Jun 2026 00:21:42 +0530 Subject: [PATCH 4/4] refactor: update AnswersEditor and HintsEditor UI, styling, and responsive layout logic Signed-off-by: Abhishek-Punhani --- .../AnswersEditor/AnswersEditor.spec.js | 11 ++- .../AnswersEditor/AnswersEditor.vue | 95 ++++++++++--------- .../HintsEditor/HintsEditor.spec.js | 3 +- .../components/HintsEditor/HintsEditor.vue | 13 +-- 4 files changed, 70 insertions(+), 52 deletions(-) diff --git a/contentcuration/contentcuration/frontend/channelEdit/components/AnswersEditor/AnswersEditor.spec.js b/contentcuration/contentcuration/frontend/channelEdit/components/AnswersEditor/AnswersEditor.spec.js index dc1ac59033..320b88aed1 100644 --- a/contentcuration/contentcuration/frontend/channelEdit/components/AnswersEditor/AnswersEditor.spec.js +++ b/contentcuration/contentcuration/frontend/channelEdit/components/AnswersEditor/AnswersEditor.spec.js @@ -9,7 +9,8 @@ jest.mock('shared/views/TipTapEditor/TipTapEditor/TipTapEditor.vue'); jest.mock('kolibri-design-system/lib/composables/useKResponsiveWindow', () => { return function useKResponsiveWindow() { - return { windowBreakpoint: { value: 4 } }; + const { ref } = require('vue'); + return { windowIsSmall: ref(false) }; }; }); @@ -140,7 +141,7 @@ describe('AnswersEditor', () => { it('renders new answer button', () => { expect(rendersNewAnswerBtn(wrapper)).toBe(true); expect(wrapper.findComponent('[data-test="newAnswerBtn"]').text()).toContain( - AnswersEditor.$trs.newAnswerBtnLabel, + AnswersEditor.$trs.addOptionBtnLabel, ); }); @@ -209,6 +210,9 @@ describe('AnswersEditor', () => { it('renders new answer button', () => { expect(rendersNewAnswerBtn(wrapper)).toBe(true); + expect(wrapper.findComponent('[data-test="newAnswerBtn"]').text()).toContain( + AnswersEditor.$trs.addOptionBtnLabel, + ); }); describe('on new answer button click', () => { @@ -284,6 +288,9 @@ describe('AnswersEditor', () => { it('renders new answer button', () => { expect(rendersNewAnswerBtn(wrapper)).toBe(true); + expect(wrapper.findComponent('[data-test="newAnswerBtn"]').text()).toContain( + AnswersEditor.$trs.newAnswerBtnLabel, + ); }); describe('on new answer button click', () => { diff --git a/contentcuration/contentcuration/frontend/channelEdit/components/AnswersEditor/AnswersEditor.vue b/contentcuration/contentcuration/frontend/channelEdit/components/AnswersEditor/AnswersEditor.vue index a021f888ef..199d033b97 100644 --- a/contentcuration/contentcuration/frontend/channelEdit/components/AnswersEditor/AnswersEditor.vue +++ b/contentcuration/contentcuration/frontend/channelEdit/components/AnswersEditor/AnswersEditor.vue @@ -24,7 +24,7 @@ class="answer-border" :class="answerClasses(answerIdx)" :style="{ - borderColor: answer.correct ? $themeTokens.correct : $themeTokens.fineLine, + borderColor: answer.correct ? $themePalette.green.v_500 : $themeTokens.fineLine, backgroundColor: answer.correct ? $themePalette.green.v_100 : 'transparent', }" data-test="answer" @@ -49,7 +49,7 @@ marginTop: !isAnswerOpen(answerIdx) ? 0 : '', marginBottom: !isAnswerOpen(answerIdx) ? 0 : '', }" - @change="toggleCorrectAnswer(answerIdx, $event)" + @change="setCorrectAnswer(answerIdx, $event)" />
@@ -73,7 +73,7 @@
{{ $tr('enterNumberPlaceholder') }} @@ -109,7 +109,7 @@ :style="{ backgroundColor: $themePalette.white }" mode="edit" minHeight="80px" - :autofocus="true" + autofocus :image-processor="EditorImageProcessor" @update="updateAnswerText($event, answerIdx)" @minimize="emitClose" @@ -151,9 +151,9 @@
- {{ $tr('newAnswerBtnLabel') }} + {{ isInputQuestion ? $tr('newAnswerBtnLabel') : $tr('addOptionBtnLabel') }}
@@ -188,6 +188,12 @@ AssessmentItemToolbar, TipTapEditor, }, + setup() { + const { windowIsSmall } = useKResponsiveWindow(); + return { + windowIsSmall, + }; + }, model: { prop: 'answers', event: 'update', @@ -263,23 +269,19 @@ return []; }, - screenSizeLevel() { - const { windowBreakpoint } = useKResponsiveWindow(); - return windowBreakpoint.value ?? 0; - }, isSmallScreen() { - return this.screenSizeLevel <= 1; + return this.windowIsSmall; }, buttonAppearanceOverrides() { return { - backgroundColor: this.$themeBrand.primary.v_50, - border: `1px dashed ${this.$themeBrand.primary.v_200}`, - color: `${this.$themeTokens.primary} !important`, + backgroundColor: this.$themePalette.blue.v_50, + border: `1px dashed ${this.$themePalette.blue.v_200}`, + color: `${this.$themePalette.blue.v_500} !important`, fontSize: '14px', fontWeight: '600', textTransform: 'none', ':hover': { - backgroundColor: this.$themeBrand.primary.v_100, + backgroundColor: this.$themePalette.blue.v_100, }, }; }, @@ -314,22 +316,21 @@ if (!this.isAnswerOpen(answerIdx)) { classes.push('closed'); - const hoverConfig = { - cursor: 'pointer', - }; - - if (!this.answers[answerIdx].correct) { - hoverConfig[':hover'] = { - backgroundColor: this.$themePalette.grey.v_100, - }; - } + const hoverStyles = this.answers[answerIdx].correct + ? {} + : { ':hover': { backgroundColor: this.$themePalette.grey.v_100 } }; - classes.push(this.$computedClass(hoverConfig)); + classes.push( + this.$computedClass({ + cursor: 'pointer', + ...hoverStyles, + }), + ); } return classes; }, - toggleCorrectAnswer(answerIdx, isChecked) { + setCorrectAnswer(answerIdx, isChecked) { if (this.shouldHaveOneCorrectAnswer) { if (isChecked) { this.onCorrectAnswersIndicesUpdate(answerIdx); @@ -492,7 +493,8 @@ answersLabelMultipleChoice: 'Answer options — select all correct answers', answersLabelNumeric: 'Answer options — enter all acceptable spellings or formats', noAnswersPlaceholder: 'Question has no answer options', - newAnswerBtnLabel: 'Add option', + newAnswerBtnLabel: 'Add answer', + addOptionBtnLabel: 'Add option', enterNumberPlaceholder: 'Enter number', }, }; @@ -527,12 +529,6 @@ .answer-card-text { padding: 7.5px; - &.small-screen { - &.is-closed { - font-size: 10px; - } - } - &.is-closed { padding-top: 0; padding-bottom: 0; @@ -549,19 +545,27 @@ } &.small-screen { - .answer-actions { - margin-left: 4px; - } - &.is-open { - flex-direction: column-reverse; + flex-wrap: wrap; + align-items: center; + + .answer-selection { + flex: 0 0 auto; + order: 0; + margin-bottom: 4px; + } .answer-actions { - display: flex; - justify-content: flex-end; - width: 100%; - margin-bottom: 8px; - margin-left: 0; + flex: 0 0 auto; + order: 1; + margin-bottom: 4px; + margin-left: auto; + } + + .answer-content { + flex: 0 0 100%; + order: 2; + min-width: 0; } } } @@ -596,6 +600,11 @@ } } + .editor.is-open { + // Fills parent so the toolbar's ResizeObserver measures the correct width. + width: 100%; + } + .answer-view-editor { flex: 1; min-width: 0; diff --git a/contentcuration/contentcuration/frontend/channelEdit/components/HintsEditor/HintsEditor.spec.js b/contentcuration/contentcuration/frontend/channelEdit/components/HintsEditor/HintsEditor.spec.js index cb733247cc..3efedcb4c8 100644 --- a/contentcuration/contentcuration/frontend/channelEdit/components/HintsEditor/HintsEditor.spec.js +++ b/contentcuration/contentcuration/frontend/channelEdit/components/HintsEditor/HintsEditor.spec.js @@ -7,7 +7,8 @@ import HintsEditor from './HintsEditor'; jest.mock('shared/views/TipTapEditor/TipTapEditor/TipTapEditor.vue'); jest.mock('kolibri-design-system/lib/composables/useKResponsiveWindow', () => { return function useKResponsiveWindow() { - return { windowBreakpoint: { value: 4 } }; + const { ref } = require('vue'); + return { windowIsSmall: ref(false) }; }; }); diff --git a/contentcuration/contentcuration/frontend/channelEdit/components/HintsEditor/HintsEditor.vue b/contentcuration/contentcuration/frontend/channelEdit/components/HintsEditor/HintsEditor.vue index 4081ddb197..2acdb20eba 100644 --- a/contentcuration/contentcuration/frontend/channelEdit/components/HintsEditor/HintsEditor.vue +++ b/contentcuration/contentcuration/frontend/channelEdit/components/HintsEditor/HintsEditor.vue @@ -102,7 +102,6 @@ :iconActionsConfig="toolbarIconActions" :collapse="isSmallScreen" :displayMenu="isSmallScreen" - :canEdit="!isHintOpen(hintIdx)" :canMoveUp="!isHintFirst(hintIdx)" :canMoveDown="!isHintLast(hintIdx)" class="toolbar" @@ -161,6 +160,12 @@ AssessmentItemToolbar, TipTapEditor, }, + setup() { + const { windowIsSmall } = useKResponsiveWindow(); + return { + windowIsSmall, + }; + }, model: { prop: 'hints', event: 'update', @@ -195,12 +200,8 @@ }, }; }, - screenSizeLevel() { - const { windowBreakpoint } = useKResponsiveWindow(); - return windowBreakpoint.value ?? 0; - }, isSmallScreen() { - return this.screenSizeLevel <= 1; + return this.windowIsSmall; }, buttonAppearanceOverrides() { return {