From 9d078418e6e0b85e6586a5ce56594ed7ac194dad Mon Sep 17 00:00:00 2001
From: Thomas JUND <tjund@entrouvert.com>
Date: Thu, 25 Jun 2020 18:41:18 +0200
Subject: [PATCH] templates: add back-top component (#45145)

---
 help/fr/misc-scss.page             |  35 ++++++++
 static/includes/_back-top.scss     | 140 +++++++++++++++++++++++++++++
 static/includes/_publik.scss       |   1 +
 static/montpellier/style.scss      |   1 +
 templates/combo/page_template.html |   4 +
 templates/includes/back-top.html   |  37 ++++++++
 templates/theme.html               |   6 +-
 7 files changed, 222 insertions(+), 2 deletions(-)
 create mode 100644 static/includes/_back-top.scss
 create mode 100644 templates/includes/back-top.html

diff --git a/help/fr/misc-scss.page b/help/fr/misc-scss.page
index 01d654e0..989a4c18 100644
--- a/help/fr/misc-scss.page
+++ b/help/fr/misc-scss.page
@@ -914,7 +914,42 @@ paramètre, la deuxième sa description et la troisième la valeur par défaut.
   <td><p><var>$title-transform</var></p></td>
  </tr>
 </table>
+</section>
+
+<section>
+  <title>Retour en haut</title>
 
+  <p>
+    Bouton permettant de remonter en haut de page. Élément masqué par défaut. Si affiché, il le sera par défaut sous la forme d'un lien en fin de pied de page
+  </p>
+
+  <table shade="rows">
+    <tr>
+      <td><p><code>$back-top-display</code></p></td>
+      <td><p>Condition d'affichage du bouton. Valeurs possibles: <var>mobile-only</var>, <var>block</var>, <var>inline</var>, <var>none</var>.</p></td>
+      <td><p><var>none</var></p></td>
+    </tr>
+    <tr>
+      <td><p><code>$back-top-icon-character</code></p></td>
+      <td><p>Caractère UTF-8 de l'icône du bouton.</p></td>
+      <td><p><var>"\f102"</var>(icone "angle-double-top")</p></td>
+    </tr>
+    <tr>
+      <td><p><code>$back-top-icon-size</code></p></td>
+      <td><p>Taille de l'icône.</p></td>
+      <td><p><var>2.5em</var></p></td>
+    </tr>
+    <tr>
+      <td><p><code>$back-top-icon-label-space</code></p></td>
+      <td><p>Espace entre l'icône et le label du bouton</p></td>
+      <td><p><var>0.33em</var></p></td>
+    </tr>
+    <tr>
+      <td><p><code>$back-top-layout-direction</code></p></td>
+      <td><p>Position de l'icône par rapport au label. Soit <var>vertical</var> : icône au dessus du label, soit <var>horizontal</var>: icône à côté du label</p></td>
+      <td><p><var>vertical</var></p></td>
+    </tr>
+  </table>
 
 </section>
 
diff --git a/static/includes/_back-top.scss b/static/includes/_back-top.scss
new file mode 100644
index 00000000..3a0bce35
--- /dev/null
+++ b/static/includes/_back-top.scss
@@ -0,0 +1,140 @@
+// Back to top link
+$back-top-display: none !default; // `mobile-only || block || inline || none`
+$back-top-icon-character: "\f102" !default; // any content CSS property values, default: fontawesome angle-double-up
+$back-top-icon-size: 2.5em !default;
+$back-top-icon-label-space: .33em;
+$back-top-layout-direction: vertical !default; // horizontal || vertical
+// layout position
+// 	values: `static (options) || fixed (options)`
+// 	static options values: `left: length || right: length`
+//	fixed options values: one or more of
+//		`left: length || right: length || top: length || bottom: length`
+//		separated by comma
+//	examples: `static (left: 2em)`, `fixed (right: 0, bottom: 20px)`
+$back-top-layout-position: static (right) !default;
+
+// display options
+.back-top {
+	@if $back-top-display == none {
+		@include sr-only();
+	} @else if $back-top-display == mobile-only {
+		@extend .mobile-only;
+	} @else {
+		display: $back-top-display;
+	}
+}
+
+// Default link styles
+.back-top--link {
+	display: inline-flex;
+	align-items: center;
+	// icon
+	&::before {
+		content: $back-top-icon-character;
+		font-family: fontawesome, sans-serif;
+		font-size: $back-top-icon-size;
+	}
+}
+
+//
+// Layout options
+//
+
+// Layout direction
+@if ($back-top-layout-direction == vertical) {
+	.back-top--link-label {
+		margin-left: $back-top-icon-label-space;
+		margin-right: $back-top-icon-label-space;
+	}
+} @else if ($back-top-layout-direction == horizontal) {
+	.back-top--link-label {
+		margin-top: $back-top-icon-label-space;
+		margin-bottom: $back-top-icon-label-space;
+	}
+	.back-top--link {
+		flex-direction: column;
+	}
+}
+
+// Layout Position
+// refere to CSS position property (static or fixed)
+$back-top-layout-position-type: nth($back-top-layout-position, 1);
+$back-top-layout-position-datas: false;
+@if (length($back-top-layout-position) > 1) {
+	$back-top-layout-position-datas: nth($back-top-layout-position, 2);
+}
+
+.back-top--link {
+	position: $back-top-layout-position-type;
+	z-index: 1000;
+}
+
+@if ($back-top-layout-position-type == static) {
+	.back-top {
+		@if (type-of($back-top-layout-position-datas) == map) {
+			@each $text-align, $padding-value in $back-top-layout-position-datas {
+				text-align: $text-align;
+				padding-#{$text-align}: $padding-value;
+			}
+		} @else if {
+			text-align: $back-top-layout-position-datas;
+		}
+	}
+}
+
+@if ($back-top-layout-position-type == fixed) {
+	.back-top--link {
+		@if (type-of($back-top-layout-position-datas) == map) {
+			@each $property, $value in $back-top-layout-position-datas {
+				#{$property}: $value;
+			}
+		} @else if (
+			type-of($back-top-layout-position-datas) == list or
+			type-of($back-top-layout-position-datas) == string
+		) {
+			@each $property in $back-top-layout-position-datas {
+				#{$property}: 0;
+			}
+		}
+	}
+}
+
+// Show/hide animation
+// $back-top-layout-position-type == fixed, JS add .back-top-scroll-Y class
+// and a .[below | above]-scroll-position to component (when scroll goes a custom limit)
+@keyframes back-top-show {
+	0% {
+		opacity: 0;
+		z-index: -1;
+	}
+	1% {
+		z-index: 1000;
+	}
+	100% {
+		opacity: 1;
+		z-index: 1000;
+	}
+}
+@keyframes back-top-hide {
+	0% {
+		opacity: 1;
+		z-index: 1000;
+	}
+	99% {
+		z-index: -1;
+	}
+	100% {
+		opacity: 0;
+		z-index: -1;
+	}
+}
+.back-top-scrollY {
+	animation-fill-mode: both;
+	animation-duration: 600ms;
+	&.below-scroll-limit  {
+		animation-name: back-top-show;
+	}
+	&.above-scroll-limit {
+		animation-name: back-top-hide;
+	}
+}
\ No newline at end of file
diff --git a/static/includes/_publik.scss b/static/includes/_publik.scss
index 560a4d69..4776d297 100644
--- a/static/includes/_publik.scss
+++ b/static/includes/_publik.scss
@@ -12,4 +12,5 @@
 @import 'misc';
 @import 'a11y';
 @import 'carrousel';
+@import 'back-top';
 @import 'library';
diff --git a/static/montpellier/style.scss b/static/montpellier/style.scss
index 55418491..ff1adb51 100644
--- a/static/montpellier/style.scss
+++ b/static/montpellier/style.scss
@@ -13,4 +13,5 @@
 @import '../includes/misc';
 @import '../includes/a11y';
 @import '../includes/transitions/_ball_running_dots';
+@import '../includes/back-top';
 @import 'custom';
diff --git a/templates/combo/page_template.html b/templates/combo/page_template.html
index 97a34c57..af48db8e 100644
--- a/templates/combo/page_template.html
+++ b/templates/combo/page_template.html
@@ -143,6 +143,10 @@ page-{{ page.slug }} page-template-{{ page.template_name }} {% with pages.0.slug
 {% end_skeleton_extra_placeholder %}
 {% endblock %}
 
+{% block back-top %}
+  {% include 'includes/back-top.html' %}
+{% endblock %}
+
 {% block body-bottom %}
 {% skeleton_extra_placeholder service-worker %}
 {% if page.pk %}  {# limit this to concrete pages on combo (ex: not 404) #}
diff --git a/templates/includes/back-top.html b/templates/includes/back-top.html
new file mode 100644
index 00000000..878af1ae
--- /dev/null
+++ b/templates/includes/back-top.html
@@ -0,0 +1,37 @@
+<div class="back-top">
+  <a class="back-top--link" href="#nav-skip" accesskey="t" aria-label="Retour en haut de page">
+    <span class="back-top--link-label">
+      {% block back-top-label %}
+        Retour
+      {% endblock %}
+    </span>
+  </a>
+</div>
+
+{% block back-top-script %}
+<script>
+  var back_top_scrollY;
+  $(function(){
+    var back_top_link = document.querySelector('.back-top--link');
+    var is_fixed = getComputedStyle(back_top_link, null).getPropertyValue("position") === "fixed";
+    if (is_fixed) {
+      back_top_link.classList.add('back-top-scrollY');
+      back_top_scroll_Y = new ComboScrollY({
+        limit: {% block back-top-scrollY-limit %}200{% endblock %},
+        below: function(){
+          {% block back-top-scrollY-below-callback %}
+          back_top_link.classList.add('below-scroll-limit');
+          back_top_link.classList.remove('above-scroll-limit');
+          {% endblock %}
+        },
+        above: function(){
+          {% block back-top-scrollY-above-callback %}
+          back_top_link.classList.add('above-scroll-limit');
+          back_top_link.classList.remove('below-scroll-limit');
+          {% endblock %}
+        }
+      });
+    }
+  });
+</script>
+{% endblock %}
\ No newline at end of file
diff --git a/templates/theme.html b/templates/theme.html
index 368bcbec..4f611da1 100644
--- a/templates/theme.html
+++ b/templates/theme.html
@@ -148,14 +148,16 @@
       {% block footer %}
       <p id="legal">Copyright © 2005-2016 Entr'ouvert</p>
       {% endblock %}
-      {% block footer-bottom %}{% endblock %}
+      {% block footer-bottom %}
+          {% block back-top %}
+          {% endblock %}
+      {% endblock %}
     </div>
     </div>
     {% block footer-post %}{% endblock %}
    </footer>
    </div> <!-- #page -->
    {% if environment_label %}<span id="environment-label">{{ environment_label }}</span>{% endif %}
-   <a style="display: none" href="#" accesskey="t">Retour en haut de page</a>
    {% include "includes/tracking.html" %}
    {% block body-bottom %}
    {% endblock %}
-- 
2.20.1

