diff --git a/app/assets/javascripts/discourse/app/components/horizontal-overflow-nav.js b/app/assets/javascripts/discourse/app/components/horizontal-overflow-nav.js
index 79d0a065190..24d2f4292f9 100644
--- a/app/assets/javascripts/discourse/app/components/horizontal-overflow-nav.js
+++ b/app/assets/javascripts/discourse/app/components/horizontal-overflow-nav.js
@@ -12,10 +12,8 @@ export default class HorizontalOverflowNav extends Component {
scrollInterval;
@bind
- scrollToActive() {
- const activeElement = document.querySelector(
- ".user-navigation-secondary a.active"
- );
+ scrollToActive(element) {
+ const activeElement = element.querySelector("a.active");
activeElement?.scrollIntoView({
block: "nearest",
@@ -24,14 +22,14 @@ export default class HorizontalOverflowNav extends Component {
}
@bind
- checkScroll(element) {
+ checkScroll(event) {
if (this.site.mobileView) {
return;
}
- this.watchScroll(element);
+ this.watchScroll(event);
return (this.hasScroll =
- element.target.scrollWidth > element.target.offsetWidth);
+ event.target.scrollWidth > event.target.offsetWidth);
}
@bind
@@ -40,14 +38,14 @@ export default class HorizontalOverflowNav extends Component {
}
@bind
- watchScroll(element) {
+ watchScroll(event) {
if (this.site.mobileView) {
return;
}
if (
- element.target.offsetWidth + element.target.scrollLeft ===
- element.target.scrollWidth
+ event.target.offsetWidth + event.target.scrollLeft ===
+ event.target.scrollWidth
) {
this.hideRightScroll = true;
clearInterval(this.scrollInterval);
@@ -55,7 +53,7 @@ export default class HorizontalOverflowNav extends Component {
this.hideRightScroll = false;
}
- if (element.target.scrollLeft === 0) {
+ if (event.target.scrollLeft === 0) {
this.hideLeftScroll = true;
clearInterval(this.scrollInterval);
} else {
@@ -63,21 +61,57 @@ export default class HorizontalOverflowNav extends Component {
}
}
- @action
- horizScroll(element) {
- let scrollSpeed = 100;
- let siblingTarget = element.target.previousElementSibling;
+ @bind
+ scrollDrag(event) {
+ if (this.site.mobileView) {
+ return;
+ }
- if (element.target.dataset.direction === "left") {
+ event.preventDefault();
+
+ const navPills = event.target.closest(".nav-pills");
+
+ const position = {
+ left: navPills.scrollLeft, // current scroll
+ x: event.clientX, // mouse position
+ };
+
+ const mouseDragScroll = function (e) {
+ let mouseChange = e.clientX - position.x;
+ navPills.scrollLeft = position.left - mouseChange;
+
+ navPills.querySelectorAll("a").forEach((a) => {
+ a.style.cursor = "grabbing";
+ });
+ };
+
+ const removeDragScroll = function () {
+ document.removeEventListener("mousemove", mouseDragScroll);
+
+ navPills.querySelectorAll("a").forEach((a) => {
+ a.style.cursor = "pointer";
+ });
+ };
+
+ document.addEventListener("mousemove", mouseDragScroll);
+ document.addEventListener("mouseup", removeDragScroll);
+ }
+
+ @action
+ horizScroll(event) {
+ let scrollSpeed = 175;
+ let siblingTarget = event.target.previousElementSibling;
+
+ if (event.target.dataset.direction === "left") {
scrollSpeed = scrollSpeed * -1;
- siblingTarget = element.target.nextElementSibling;
+ siblingTarget = event.target.nextElementSibling;
}
this.scrollInterval = setInterval(function () {
siblingTarget.scrollLeft += scrollSpeed;
}, 50);
- element.target.addEventListener("mouseup", this.stopScroll);
- element.target.addEventListener("mouseleave", this.stopScroll);
+ event.target.addEventListener("mouseup", this.stopScroll);
+ event.target.addEventListener("mouseleave", this.stopScroll);
}
}
diff --git a/app/assets/javascripts/discourse/app/components/user-nav.hbs b/app/assets/javascripts/discourse/app/components/user-nav.hbs
index 6a832c2d89f..4caa7d2e7a6 100644
--- a/app/assets/javascripts/discourse/app/components/user-nav.hbs
+++ b/app/assets/javascripts/discourse/app/components/user-nav.hbs
@@ -1,5 +1,5 @@
-
+
{{#unless @user.profile_hidden}}
-
@@ -74,5 +74,5 @@
{{/if}}
-
+
diff --git a/app/assets/javascripts/discourse/app/templates/components/horizontal-overflow-nav.hbs b/app/assets/javascripts/discourse/app/templates/components/horizontal-overflow-nav.hbs
index d726eb8eefc..e3963b966e4 100644
--- a/app/assets/javascripts/discourse/app/templates/components/horizontal-overflow-nav.hbs
+++ b/app/assets/javascripts/discourse/app/templates/components/horizontal-overflow-nav.hbs
@@ -1,4 +1,5 @@
{{!-- template-lint-disable no-down-event-binding --}}
+{{!-- template-lint-disable no-invalid-interactive --}}
{{#if this.hasScroll}}
@@ -6,7 +7,7 @@
role="button"
{{on "mousedown" this.horizScroll}}
data-direction="left"
- class="nav-overflow__scroll-left btn-flat {{if this.hideLeftScroll "transparent"}}"
+ class="horizontal-overflow-nav__scroll-left {{if this.hideLeftScroll "disabled"}}"
>
{{d-icon "chevron-left"}}
@@ -16,7 +17,8 @@
{{on-resize this.checkScroll}}
{{on "scroll" this.watchScroll}}
{{did-insert this.scrollToActive}}
- class="nav-pills action-list"
+ {{on "mousedown" this.scrollDrag}}
+ class="nav-pills action-list {{@className}}"
>
{{yield}}
@@ -25,7 +27,7 @@
{{d-icon "chevron-right"}}
diff --git a/app/assets/stylesheets/common/base/new-user.scss b/app/assets/stylesheets/common/base/new-user.scss
index c666ce84612..85772e3fe3e 100644
--- a/app/assets/stylesheets/common/base/new-user.scss
+++ b/app/assets/stylesheets/common/base/new-user.scss
@@ -2,9 +2,8 @@
margin-top: -15px; // temp, can remove margin from sibling element after nav finalized
.user-navigation {
--user-navigation__border-width: 4px;
- &.user-navigation-primary {
- border-bottom: 1px solid var(--primary-low);
- }
+ border-bottom: 1px solid var(--primary-low);
+
.nav-pills {
width: 100%;
margin: 0;
@@ -37,7 +36,6 @@
li {
flex: 1 1 auto;
margin: 0;
- overflow: hidden;
display: flex;
a {
@@ -80,6 +78,15 @@
}
}
+ .user-navigation-primary {
+ [class*="horizontal-overflow-nav__scroll"] {
+ font-size: var(--font-up-1);
+ .d-icon {
+ margin-top: 0.15em; // minor alignment
+ }
+ }
+ }
+
.user-navigation-secondary {
--user-navigation__border-width: 2px;
position: relative;
@@ -89,12 +96,6 @@
gap: 0 0.5em;
border-bottom: 1px solid var(--primary-low);
- .horizontal-overflow-nav {
- position: relative;
- min-width: 0;
- width: 100%;
- }
-
.select-kit .select-kit-header {
height: 100%;
padding: 0.5em 1em;
@@ -116,73 +117,10 @@
}
}
- .nav-overflow__scroll-right,
- .nav-overflow__scroll-left {
- --fade-width: 20px;
- opacity: 1;
- position: absolute;
- z-index: 2;
- background-color: var(--secondary);
- top: 0;
- bottom: 0;
- display: flex;
- align-items: center;
- transition: opacity 0.25s;
- .d-icon {
- pointer-events: none;
- margin-bottom: 0.2em;
- color: var(--quaternary);
- }
- &.transparent {
- // hiding with opacity so we can transition visibility
- opacity: 0;
- pointer-events: none;
- }
- }
-
- .nav-overflow__scroll-right {
- right: 0;
- &:before {
- content: "";
- margin-left: -1.5em;
- height: 100%;
- width: 1.5em;
- background: linear-gradient(
- to left,
- rgba(var(--secondary-rgb), 1),
- rgba(var(--secondary-rgb), 0)
- );
- }
- }
-
- .nav-overflow__scroll-left {
- left: 0;
- &:after {
- content: "";
- margin-right: -1.5em;
- height: 100%;
- width: 1.5em;
- background: linear-gradient(
- to right,
- rgba(var(--secondary-rgb), 1),
- rgba(var(--secondary-rgb), 0)
- );
- }
- }
-
.nav-pills {
flex: 1 1 auto;
font-size: var(--font-down-1);
justify-content: flex-start;
- overflow: auto;
- position: relative;
- scroll-behavior: smooth;
-
- // hides scrollbars, but allows mouse scrolling
- scrollbar-width: none;
- &::-webkit-scrollbar {
- height: 0;
- }
li {
flex: 1 0 auto;
diff --git a/app/assets/stylesheets/common/components/_index.scss b/app/assets/stylesheets/common/components/_index.scss
index a054cd44783..361546e0532 100644
--- a/app/assets/stylesheets/common/components/_index.scss
+++ b/app/assets/stylesheets/common/components/_index.scss
@@ -15,6 +15,7 @@
@import "group-member-dropdown";
@import "groups-form-membership-fields";
@import "hashtag";
+@import "horizontal-overflow-nav";
@import "iframed-html";
@import "ignored-user-list";
@import "keyboard_shortcuts";
diff --git a/app/assets/stylesheets/common/components/horizontal-overflow-nav.scss b/app/assets/stylesheets/common/components/horizontal-overflow-nav.scss
new file mode 100644
index 00000000000..9f2f817c02e
--- /dev/null
+++ b/app/assets/stylesheets/common/components/horizontal-overflow-nav.scss
@@ -0,0 +1,78 @@
+.horizontal-overflow-nav {
+ position: relative;
+ min-width: 0;
+ width: 100%;
+ .nav-pills {
+ overflow: auto;
+ min-width: 0;
+ position: relative;
+
+ // avoids auto-scroll on initial load if active nav item is overflowed
+ scroll-behavior: auto;
+
+ // hides scrollbars, but allows mouse scrolling
+ scrollbar-width: none;
+ &::-webkit-scrollbar {
+ height: 0;
+ }
+ }
+ &.has-scroll {
+ .nav-pills {
+ scroll-behavior: smooth; // smooth scrolling on user interaction
+ }
+ }
+}
+
+.horizontal-overflow-nav__scroll-right,
+.horizontal-overflow-nav__scroll-left {
+ --fade-width: 1.5em;
+ opacity: 1;
+ position: absolute;
+ z-index: 2;
+ background-color: var(--secondary);
+ top: 0;
+ bottom: 0;
+ display: flex;
+ align-items: center;
+ transition: opacity 0.25s;
+ .d-icon {
+ pointer-events: none;
+ margin-bottom: 0.2em;
+ color: var(--quaternary);
+ }
+ &.disabled {
+ // hiding with opacity so we can transition visibility
+ opacity: 0;
+ pointer-events: none;
+ }
+}
+
+.horizontal-overflow-nav__scroll-right {
+ right: 0;
+ &:before {
+ content: "";
+ margin-left: calc(var(--fade-width) * -1);
+ height: 100%;
+ width: var(--fade-width);
+ background: linear-gradient(
+ to left,
+ rgba(var(--secondary-rgb), 1),
+ rgba(var(--secondary-rgb), 0)
+ );
+ }
+}
+
+.horizontal-overflow-nav__scroll-left {
+ left: 0;
+ &:after {
+ content: "";
+ margin-right: calc(var(--fade-width) * -1);
+ height: 100%;
+ width: var(--fade-width);
+ background: linear-gradient(
+ to right,
+ rgba(var(--secondary-rgb), 1),
+ rgba(var(--secondary-rgb), 0)
+ );
+ }
+}