Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FEATURE] Ajouter l'auto-scroll au Stepper (PIX-13201) #9455

Merged
merged 4 commits into from
Jul 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 5 additions & 11 deletions mon-pix/app/components/module/grain.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { action } from '@ember/object';
import { service } from '@ember/service';
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';

import ModulePassage from './passage';

export default class ModuleGrain extends Component {
@service modulixAutoScroll;

grain = this.args.grain;

static AVAILABLE_ELEMENT_TYPES = ['text', 'image', 'video', 'qcu', 'qcm', 'qrocm'];
Expand Down Expand Up @@ -129,19 +130,12 @@ export default class ModuleGrain extends Component {
}

@action
focusAndScroll(element) {
focusAndScroll(htmlElement) {
if (!this.args.hasJustAppeared) {
return;
}

element.focus({ preventScroll: true });

const newGrainY = element.getBoundingClientRect().top + window.scrollY;
const userPrefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)');
window.scroll({
top: newGrainY - ModulePassage.SCROLL_OFFSET_PX,
behavior: userPrefersReducedMotion.matches ? 'instant' : 'smooth',
});
this.modulixAutoScroll.focusAndScroll(htmlElement);
}

@action
Expand Down
6 changes: 5 additions & 1 deletion mon-pix/app/components/module/passage.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@
<h1>{{@module.title}}</h1>
</div>

<div class="module-passage__content" aria-live="assertive" {{did-insert this.setGrainScrollOffsetCssProperty}}>
<div
class="module-passage__content"
aria-live="assertive"
{{did-insert this.modulixAutoScroll.setHTMLElementScrollOffsetCssProperty}}
>
{{#each this.grainsToDisplay as |grain index|}}
<Module::Grain
@grain={{grain}}
Expand Down
20 changes: 7 additions & 13 deletions mon-pix/app/components/module/passage.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,18 @@ export default class ModulePassage extends Component {
@service router;
@service metrics;
@service store;
@service modulixAutoScroll;

displayableGrains = this.args.module.grains.filter((grain) => ModuleGrain.getSupportedComponents(grain).length > 0);
@tracked grainsToDisplay = this.displayableGrains.length > 0 ? [this.displayableGrains[0]] : [];

static SCROLL_OFFSET_PX = 70;

@action
setGrainScrollOffsetCssProperty(element) {
element.style.setProperty('--grain-scroll-offset', `${ModulePassage.SCROLL_OFFSET_PX}px`);
hasGrainJustAppeared(index) {
if (this.grainsToDisplay.length === 1) {
return false;
}

return this.grainsToDisplay.length - 1 === index;
}

get hasNextGrain() {
Expand Down Expand Up @@ -92,15 +95,6 @@ export default class ModulePassage extends Component {
return this.args.module.transitionTexts.find((transition) => transition.grainId === grainId);
}

@action
hasGrainJustAppeared(index) {
if (this.grainsToDisplay.length === 1) {
return false;
}

return this.grainsToDisplay.length - 1 === index;
}

@action
terminateModule() {
this.args.passage.terminate();
Expand Down
17 changes: 16 additions & 1 deletion mon-pix/app/components/module/step.gjs
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import { action } from '@ember/object';
import { service } from '@ember/service';
import Component from '@glimmer/component';
import { t } from 'ember-intl';
import Element from 'mon-pix/components/module/element';
import ModuleGrain from 'mon-pix/components/module/grain';

import didInsert from '../../modifiers/modifier-did-insert';

export default class ModulixStep extends Component {
@service modulixAutoScroll;

get displayableElements() {
return this.args.step.elements.filter((element) => ModuleGrain.AVAILABLE_ELEMENT_TYPES.includes(element.type));
}
Expand All @@ -12,9 +18,18 @@ export default class ModulixStep extends Component {
return this.displayableElements.length > 0;
}

@action
focusAndScroll(htmlElement) {
if (!this.args.hasJustAppeared) {
return;
}

this.modulixAutoScroll.focusAndScroll(htmlElement);
}

<template>
{{#if this.hasDisplayableElements}}
<section class="stepper__step">
<section class="stepper__step" tabindex="-1" {{didInsert this.focusAndScroll}}>
<h3
class="stepper-step__position"
aria-label="{{t 'pages.modulix.stepper.step.position' currentStep=@currentStep totalSteps=@totalSteps}}"
Expand Down
21 changes: 20 additions & 1 deletion mon-pix/app/components/module/stepper.gjs
Original file line number Diff line number Diff line change
@@ -1,20 +1,34 @@
import PixButton from '@1024pix/pix-ui/components/pix-button';
import { action } from '@ember/object';
import { service } from '@ember/service';
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { t } from 'ember-intl';
import ModuleGrain from 'mon-pix/components/module/grain';
import Step from 'mon-pix/components/module/step';
import { inc } from 'mon-pix/helpers/inc';

import didInsert from '../../modifiers/modifier-did-insert';

export default class ModulixStepper extends Component {
@service modulixAutoScroll;

displayableSteps = this.args.steps.filter((step) =>
step.elements.some((element) => ModuleGrain.AVAILABLE_ELEMENT_TYPES.includes(element.type)),
);

@tracked
stepsToDisplay = [this.displayableSteps[0]];

@action
hasStepJustAppeared(index) {
if (this.stepsToDisplay.length === 1) {
return false;
}

return this.stepsToDisplay.length - 1 === index;
}

get hasDisplayableSteps() {
return this.displayableSteps.length > 0;
}
Expand Down Expand Up @@ -56,7 +70,11 @@ export default class ModulixStepper extends Component {
}

<template>
<div class="stepper">
<div
class="stepper"
aria-live="assertive"
{{didInsert this.modulixAutoScroll.setHTMLElementScrollOffsetCssProperty}}
>
{{#if this.hasDisplayableSteps}}
{{#each this.stepsToDisplay as |step index|}}
<Step
Expand All @@ -66,6 +84,7 @@ export default class ModulixStepper extends Component {
@submitAnswer={{@submitAnswer}}
@retryElement={{@retryElement}}
@getLastCorrectionForElement={{@getLastCorrectionForElement}}
@hasJustAppeared={{this.hasStepJustAppeared index}}
/>
{{/each}}
{{#if this.shouldDisplayNextButton}}
Expand Down
5 changes: 5 additions & 0 deletions mon-pix/app/modifiers/modifier-did-insert.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { modifier } from 'ember-modifier';

export default modifier(function didInsert(htmlElement, [action]) {
action(htmlElement);
});
41 changes: 41 additions & 0 deletions mon-pix/app/services/modulix-auto-scroll.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { action } from '@ember/object';
import Service from '@ember/service';

export default class ModulixAutoScroll extends Service {
#SCROLL_OFFSET_PX = 70;

@action
setHTMLElementScrollOffsetCssProperty(htmlElement) {
htmlElement.style.setProperty('--scroll-offset', `${this.#SCROLL_OFFSET_PX}px`);
}

focusAndScroll(
htmlElement,
{ scroll, userPrefersReducedMotion, getWindowScrollY } = {
scroll: this.#scroll,
userPrefersReducedMotion: this.#userPrefersReducedMotion,
getWindowScrollY: this.#getWindowScrollY,
},
) {
htmlElement.focus({ preventScroll: true });

const elementY = htmlElement.getBoundingClientRect().top + getWindowScrollY();
scroll({
top: elementY - this.#SCROLL_OFFSET_PX,
behavior: userPrefersReducedMotion() ? 'instant' : 'smooth',
});
}

#scroll(args) {
window.scroll(args);
}

#userPrefersReducedMotion() {
const userPrefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)');
return userPrefersReducedMotion.matches;
}

#getWindowScrollY() {
return window.scrollY;
}
}
7 changes: 2 additions & 5 deletions mon-pix/app/styles/components/module/_grain.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,9 @@
width: 100%;

&--active {
min-height: calc(100vh - var(--grain-scroll-offset));
outline: none;
@extend .auto-scroll;

@supports (min-height: 100dvh) {
min-height: calc(100dvh - var(--grain-scroll-offset));
}
outline: none;
}

&:last-child {
Expand Down
9 changes: 9 additions & 0 deletions mon-pix/app/styles/components/module/_passage.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@
max-width: var(--modulix-max-content-width);
margin: 0 auto;

.auto-scroll {
min-height: calc(100vh - var(--scroll-offset));

@supports (min-height: 100dvh) {
min-height: calc(100dvh - var(--scroll-offset));
}
}

h3,
h4,
p {
Expand Down Expand Up @@ -92,3 +100,4 @@
justify-content: center;
}
}

6 changes: 6 additions & 0 deletions mon-pix/app/styles/components/module/_stepper.scss
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@
&__step {
margin-bottom: var(--pix-spacing-6x);

&--active {
@extend .auto-scroll;

outline: none;
}

&:last-child {
margin-bottom: 0;
}
Expand Down
22 changes: 22 additions & 0 deletions mon-pix/tests/integration/modifiers/modifier-did-insert_test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { render } from '@1024pix/ember-testing-library';
import { hbs } from 'ember-cli-htmlbars';
import { setupRenderingTest } from 'ember-qunit';
import { module, test } from 'qunit';
import sinon from 'sinon';

module('Integration | Modifier | did-insert', function (hooks) {
setupRenderingTest(hooks);

test('should call the given action', async function (assert) {
// given
const actionStub = sinon.stub();
this.set('action', actionStub);

// when
await render(hbs`<div {{modifier-did-insert this.action}}></div>`);

// then
sinon.assert.called(actionStub);
assert.ok(true);
});
});
Loading