Skip to content

feat: add AnswerSettings and QuestionSettingsHeader components - #6059

Open
Abhishek-Punhani wants to merge 2 commits into
learningequality:unstablefrom
Abhishek-Punhani:question-selector
Open

feat: add AnswerSettings and QuestionSettingsHeader components #6059
Abhishek-Punhani wants to merge 2 commits into
learningequality:unstablefrom
Abhishek-Punhani:question-selector

Conversation

@Abhishek-Punhani

Copy link
Copy Markdown
Member

Summary

Added QuestionSettingsHeader and AnswerSettings components to the QTI editor using portals.

This PR adds:

  • QuestionSettingsHeader/index.vue — UI component for selecting interaction types.
  • AnswerSettings/index.vue — UI component for interaction-specific configurations.

References

Closes #6033

Reviewer guidance

  1. Navigate to the QTI demo page.
  2. Verify the Question Type selector is rendered correctly.
  3. Verify the Answer Settings panel is displayed and updates as expected for different interaction types.

AI usage

Used Antigravity for a final review and minor code/style nitpicks. I reviewed all suggested changes, kept only the relevant improvements, and verified that the implementation worked as intended after applying them.

…iceInteractionEditor

Signed-off-by: Abhishek-Punhani <punhani.manavabhi@gmail.com>
…eaders

Signed-off-by: Abhishek-Punhani <punhani.manavabhi@gmail.com>
@learning-equality-bot

Copy link
Copy Markdown

👋 Hi @Abhishek-Punhani, thanks for contributing!

For the review process to begin, please verify that the following is satisfied:

  • Contribution is aligned with our contributing guidelines

  • Pull request description has correctly filled AI usage section & follows our AI guidance:

    AI guidance

    State explicitly whether you didn't use or used AI & how.

    If you used it, ensure that the PR is aligned with Using AI as well as our DEEP framework. DEEP asks you:

    • Disclose — Be open about when you've used AI for support.
    • Engage critically — Question what is generated. Review code for correctness and unnecessary complexity.
    • Edit — Review and refine AI output. Remove unnecessary code and verify it still works after your edits.
    • Process sharing — Explain how you used the AI so others can learn.

    Examples of good disclosures:

    "I used Claude Code to implement the component, prompting it to follow the pattern in ComponentX. I reviewed the generated code, removed unnecessary error handling, and verified the tests pass."

    "I brainstormed the approach with Gemini, then had it write failing tests for the feature. After reviewing the tests, I used Claude Code to generate the implementation. I refactored the output to reduce verbosity and ran the full test suite."

Also check that issue requirements are satisfied & you ran pre-commit locally.

Pull requests that don't follow the guidelines will be closed.

Reviewer assignment can take up to 2 weeks.

@Abhishek-Punhani

Copy link
Copy Markdown
Member Author

@AlexVelezLl, Vue 2.7 doesn't support the built-in Teleport component, so I've added a custom component to mirror its behaviour. Let me know if you have a better implementation in mind!

@AlexVelezLl

Copy link
Copy Markdown
Member

Oh, apologies @Abhishek-Punhani 😅, I just knew we had already used it in our ecosystem and didn't recall it was a dependency package. In KDS, we use vue2-teleport. Could you install that same version on Studio, please? I read it has some memory optimizations that'd be good to have!

@AlexVelezLl AlexVelezLl left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @Abhishek-Punhani! I think there is a better way to do this to not remove the type selector from the DOM when we change the question type. Please let us know if there is any questions!

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, given that this will only be used in the editor, could we move this component to the choice folder instead?

Comment on lines +125 to +129
settings: {
type: Array,
required: true,
validator: arr => arr.every(setting => ['shuffle', 'showAnswerCount'].includes(setting)),
},

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that this will only be rendered for choice interactions, I think it's fine to let it infer when to display each based on the questionType instead of this settings prop.

:title="showAnswerCountInfoTitle$()"
@cancel="showAnswerCountModal = false"
>
<p :style="{ color: $themeTokens.annotation }">

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can leave the normal text color here, instead of this annotation.

Comment on lines +75 to +80
<template #actions>
<KButton
:text="closeBtnLabel$()"
@click="showAnswerCountModal = false"
/>
</template>

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can also just use the cancelText prop instead of this actions slot. Usually the actions slot is used for more complex button layouts.

:title="shuffleAnswersInfoTitle$()"
@cancel="showShuffleModal = false"
>
<p :style="{ color: $themeTokens.annotation }">

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Idem

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, the most important responsibility of this component is the type selector, so could we reference "type selector" in the name instead of "SettingsHeader"? (Similarly for class names like question-settings-header, etc)

<template>

<div
v-if="mode === 'edit'"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We sometimes have issues because of rendering conditions on the root element of a component, and a lint rule will soon be added to avoid this, so could we move forward with this condition and leave this responsibility to the parent component instead? i.e. let the parent do <QuestionTypeSelector v-if="mode==='edit'" instead

Comment on lines +27 to +34
<div class="select-display-row">
<KIcon
icon="language"
class="select-globe-icon"
:style="{ color: $themePalette.grey.v_700 }"
/>
<span class="select-value-text">{{ selectedOption.label }}</span>
</div>

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!

@update:showAnswerCount="setShowAnswerCount"
/>
</template>
</QuestionSettingsHeader>

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, some comments: the interaction editors should not be the ones responsible for rendering this "settings header," which is actually a type selector, mainly because this will cause the DOM to remove these nodes when the component is unmounted. The type selector (and therefore, the whole header row) should be shared across all interaction editors and rendered independently of them, so that if we change the question type, the type selector component is not removed from the DOM (which would cause some accessibility issues).

So, what we can do instead is:

  • Have the InteractionSection component be the one that renders the QuestionTypeSelector component. This way, each interaction has its own type selector independent of the editor being rendered.
  • Let the QuestionTypeSelector be the one responsible for rendering the div with the proper ID so that editor components can target it (instead of using a slot).
  • We can take advantage of the fact that we can only have one item being edited at a time, and because of this, there will always only be one question type selector rendered at a time, and use a constant as id so that we don't have to keep track of any identifiers yet.

With this, the idea would be:

// QuestionTypeSelector
<div class="type-selector>
  ...
  <div id="qti-interaction-settings" />
...
// InteractionSection
<QuestionTypeSelector ... />
<component :is="descriptor.editorComponent" ... />
// ChoiceInteractionEditor
<div class="choice-editor">
  <Teleport to="qti-interaction-settings">
    <AnswerSettings ... />
  </Teleport>
  ....
</div>

This way only the interaction editors that actually wants to add settings are the only ones that needs to teleport anything.

Comment on lines +284 to +295
const questionTypeOptions = computed(() => [
{
value: QuestionType.SINGLE_SELECT,
label: singleSelectLabel$(),
description: singleChoiceDescription$(),
},
{
value: QuestionType.MULTI_SELECT,
label: multiSelectLabel$(),
description: multipleSelectionDescription$(),
},
]);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another little problem with this is that the questionType selector should render all question types for all interactions, not only the ones they are related to.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[QTI] Question Type Selector and Answer Settings

2 participants