Skip to content

Commit

Permalink
[FEATURE] Ajouter l'auto-scroll au Stepper (PIX-13201)
Browse files Browse the repository at this point in the history
  • Loading branch information
pix-service-auto-merge committed Jul 10, 2024
2 parents 71c25d9 + 79ac227 commit 01ea3ba
Show file tree
Hide file tree
Showing 12 changed files with 242 additions and 32 deletions.
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

0 comments on commit 01ea3ba

Please sign in to comment.