mirror of
https://github.com/discourse/discourse.git
synced 2025-03-23 16:25:49 +08:00
UX: drag new user menus, scroll primary user nav (#18690)
This commit is contained in:
parent
cde2719ea1
commit
b6f5b236b3
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
<section class="user-navigation user-navigation-primary">
|
||||
<ul class="main-nav nav nav-pills user-nav">
|
||||
<HorizontalOverflowNav @className="main-nav nav user-nav">
|
||||
{{#unless @user.profile_hidden}}
|
||||
<li class="summary">
|
||||
<LinkTo @route="user.summary">
|
||||
@ -74,5 +74,5 @@
|
||||
</a>
|
||||
</li>
|
||||
{{/if}}
|
||||
</ul>
|
||||
</HorizontalOverflowNav>
|
||||
</section>
|
||||
|
@ -1,4 +1,5 @@
|
||||
{{!-- template-lint-disable no-down-event-binding --}}
|
||||
{{!-- template-lint-disable no-invalid-interactive --}}
|
||||
|
||||
<div class="horizontal-overflow-nav {{if this.hasScroll "has-scroll"}}">
|
||||
{{#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"}}
|
||||
</a>
|
||||
@ -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}}
|
||||
</ul>
|
||||
@ -25,7 +27,7 @@
|
||||
<a
|
||||
role="button"
|
||||
{{on "mousedown" this.horizScroll}}
|
||||
class="nav-overflow__scroll-right btn-flat {{if this.hideRightScroll "transparent"}}"
|
||||
class="horizontal-overflow-nav__scroll-right {{if this.hideRightScroll "disabled"}}"
|
||||
>
|
||||
{{d-icon "chevron-right"}}
|
||||
</a>
|
||||
|
@ -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;
|
||||
|
@ -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";
|
||||
|
@ -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)
|
||||
);
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user