UX: mobile experimental sidebar improvement (#17302)
First pass at the mobile experimental sidebar improvement. Co-authored-by: Alan Guo Xiang Tan <gxtan1990@gmail.com>
This commit is contained in:
parent
408ce1312b
commit
56c0d8cf92
|
@ -1,3 +1,39 @@
|
|||
import GlimmerComponent from "discourse/components/glimmer";
|
||||
import { bind } from "discourse-common/utils/decorators";
|
||||
|
||||
export default class Sidebar extends GlimmerComponent {}
|
||||
export default class Sidebar extends GlimmerComponent {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
|
||||
if (this.site.mobileView) {
|
||||
document.addEventListener("click", this.collapseSidebar);
|
||||
}
|
||||
}
|
||||
|
||||
@bind
|
||||
collapseSidebar(event) {
|
||||
let shouldCollapseSidebar = false;
|
||||
|
||||
const isClickWithinSidebar = event.composedPath().some((element) => {
|
||||
if (
|
||||
element?.className !== "sidebar-section-header-caret" &&
|
||||
["A", "BUTTON"].includes(element.nodeName)
|
||||
) {
|
||||
shouldCollapseSidebar = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
return element.className && element.className === "sidebar-wrapper";
|
||||
});
|
||||
|
||||
if (shouldCollapseSidebar || !isClickWithinSidebar) {
|
||||
this.args.toggleSidebar();
|
||||
}
|
||||
}
|
||||
|
||||
willDestroy() {
|
||||
if (this.site.mobileView) {
|
||||
document.removeEventListener("click", this.collapseSidebar);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,23 @@
|
|||
import Controller from "@ember/controller";
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
import discourseDebounce from "discourse-common/lib/debounce";
|
||||
import { inject as service } from "@ember/service";
|
||||
import { action } from "@ember/object";
|
||||
|
||||
export default Controller.extend({
|
||||
showTop: true,
|
||||
showFooter: false,
|
||||
router: service(),
|
||||
showSidebar: true,
|
||||
showSidebar: null,
|
||||
hideSidebarKey: "sidebar-hidden",
|
||||
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
|
||||
this.showSidebar = this.site.mobileView
|
||||
? false
|
||||
: this.currentUser && !this.keyValueStore.getItem(this.hideSidebarKey);
|
||||
},
|
||||
|
||||
@discourseComputed
|
||||
canSignUp() {
|
||||
|
@ -26,4 +37,31 @@ export default Controller.extend({
|
|||
showFooterNav() {
|
||||
return this.capabilities.isAppWebview || this.capabilities.isiOSPWA;
|
||||
},
|
||||
|
||||
_mainOutletAnimate() {
|
||||
document
|
||||
.querySelector("#main-outlet")
|
||||
.classList.remove("main-outlet-animate");
|
||||
},
|
||||
|
||||
@action
|
||||
toggleSidebar() {
|
||||
// enables CSS transitions, but not on did-insert
|
||||
document.querySelector("body").classList.add("sidebar-animate");
|
||||
|
||||
// reduces CSS transition jank
|
||||
document.querySelector("#main-outlet").classList.add("main-outlet-animate");
|
||||
|
||||
discourseDebounce(this, this._mainOutletAnimate, 250);
|
||||
|
||||
this.toggleProperty("showSidebar");
|
||||
|
||||
if (!this.site.mobileView) {
|
||||
if (this.showSidebar) {
|
||||
this.keyValueStore.removeItem(this.hideSidebarKey);
|
||||
} else {
|
||||
this.keyValueStore.setItem(this.hideSidebarKey);
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
|
@ -47,13 +47,6 @@ const ApplicationRoute = DiscourseRoute.extend(OpenComposer, {
|
|||
});
|
||||
},
|
||||
|
||||
toggleSidebar() {
|
||||
// enables CSS transitions, but not on did-insert
|
||||
document.querySelector("body").classList.add("sidebar-animate");
|
||||
|
||||
this.controllerFor("application").toggleProperty("showSidebar");
|
||||
},
|
||||
|
||||
toggleMobileView() {
|
||||
mobile.toggleMobileView();
|
||||
},
|
||||
|
|
|
@ -2,15 +2,19 @@
|
|||
<a href="#main-container" id="skip-link">{{i18n "skip_to_main_content"}}</a>
|
||||
<DDocument />
|
||||
<PluginOutlet @name="above-site-header" @connectorTagName="div" />
|
||||
<SiteHeader @canSignUp={{canSignUp}} @showCreateAccount={{route-action "showCreateAccount"}} @showLogin={{route-action "showLogin"}} @showKeyboard={{route-action "showKeyboardShortcutsHelp"}} @toggleMobileView={{route-action "toggleMobileView"}} @toggleAnonymous={{route-action "toggleAnonymous"}} @logout={{route-action "logout"}} @toggleSidebar={{route-action "toggleSidebar"}} />
|
||||
<SiteHeader @canSignUp={{canSignUp}} @showCreateAccount={{route-action "showCreateAccount"}} @showLogin={{route-action "showLogin"}} @showKeyboard={{route-action "showKeyboardShortcutsHelp"}} @toggleMobileView={{route-action "toggleMobileView"}} @toggleAnonymous={{route-action "toggleAnonymous"}} @logout={{route-action "logout"}} @toggleSidebar={{action "toggleSidebar"}} />
|
||||
<SoftwareUpdatePrompt />
|
||||
|
||||
<PluginOutlet @name="below-site-header" @connectorTagName="div" @args={{hash currentPath=router._router.currentPath}} />
|
||||
|
||||
<div id="main-outlet-wrapper" class="wrap" role="main">
|
||||
|
||||
<div class="sidebar-wrapper">
|
||||
{{!-- empty div allows for animation --}}
|
||||
{{#if (and currentUser.experimental_sidebar_enabled showSidebar)}}
|
||||
<Sidebar />
|
||||
<Sidebar @toggleSidebar={{action "toggleSidebar"}}/>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
<div id="main-outlet">
|
||||
<PluginOutlet @name="above-main-container" @connectorTagName="div" />
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
<DSection @pageClass="has-sidebar" @class="sidebar-wrapper">
|
||||
<div class="sidebar-container">
|
||||
<DSection @pageClass="has-sidebar" @class="sidebar-container">
|
||||
<div class="sidebar-scroll-wrap">
|
||||
<Sidebar::TopicsSection />
|
||||
<Sidebar::CategoriesSection />
|
||||
|
@ -12,5 +11,4 @@
|
|||
<Sidebar::MessagesSection />
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
</DSection>
|
||||
|
|
|
@ -9,7 +9,7 @@ export default createWidget("sidebar-toggle", {
|
|||
title: "",
|
||||
icon: "bars",
|
||||
action: "toggleSidebar",
|
||||
className: "btn btn-flat",
|
||||
className: "btn btn-flat btn-sidebar-toggle",
|
||||
}),
|
||||
];
|
||||
},
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
import { test } from "qunit";
|
||||
|
||||
import { click, visit } from "@ember/test-helpers";
|
||||
import { acceptance, exists } from "discourse/tests/helpers/qunit-helpers";
|
||||
|
||||
acceptance("Sidebar - Mobile - User with sidebar enabled", function (needs) {
|
||||
needs.user({ experimental_sidebar_enabled: true });
|
||||
needs.mobileView();
|
||||
|
||||
test("hidden by default", async function (assert) {
|
||||
await visit("/");
|
||||
|
||||
assert.ok(!exists(".sidebar-container"), "sidebar is not displayed");
|
||||
});
|
||||
|
||||
test("clicking outside sidebar collapses it", async function (assert) {
|
||||
await visit("/");
|
||||
|
||||
await click(".btn-sidebar-toggle");
|
||||
|
||||
assert.ok(exists(".sidebar-container"), "sidebar is displayed");
|
||||
|
||||
await click("#main-outlet");
|
||||
|
||||
assert.ok(!exists(".sidebar-container"), "sidebar is collapsed");
|
||||
});
|
||||
|
||||
test("clicking on a link or button in sidebar collapses it", async function (assert) {
|
||||
await visit("/");
|
||||
|
||||
await click(".btn-sidebar-toggle");
|
||||
await click(".sidebar-section-link-tracked");
|
||||
|
||||
assert.ok(
|
||||
!exists(".sidebar-container"),
|
||||
"sidebar is collapsed when a button in sidebar is clicked"
|
||||
);
|
||||
|
||||
await click(".btn-sidebar-toggle");
|
||||
await click(".sidebar-section-header-link");
|
||||
|
||||
assert.ok(
|
||||
!exists(".sidebar-container"),
|
||||
"sidebar is collapsed when a link in sidebar is clicked"
|
||||
);
|
||||
});
|
||||
|
||||
test("collpasing sidebar sections does not collapse sidebar", async function (assert) {
|
||||
await visit("/");
|
||||
|
||||
await click(".btn-sidebar-toggle");
|
||||
await click(".sidebar-section-header-caret");
|
||||
|
||||
assert.ok(
|
||||
!exists(".sidebar-section-topics .sidebar-section-content"),
|
||||
"topics section is collapsed"
|
||||
);
|
||||
|
||||
assert.ok(
|
||||
exists(".sidebar-container"),
|
||||
"sidebar is not collapsed when clicking on caret to collapse a section in sidebar"
|
||||
);
|
||||
});
|
||||
});
|
|
@ -14,7 +14,7 @@ acceptance("Sidebar - Anon User", function () {
|
|||
"does not add sidebar utility class to body"
|
||||
);
|
||||
|
||||
assert.ok(!exists(".sidebar-wrapper"));
|
||||
assert.ok(!exists(".sidebar-container"));
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -30,7 +30,7 @@ acceptance("Sidebar - User with sidebar disabled", function (needs) {
|
|||
"does not add sidebar utility class to body"
|
||||
);
|
||||
|
||||
assert.ok(!exists(".sidebar-wrapper"));
|
||||
assert.ok(!exists(".sidebar-container"));
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -46,7 +46,7 @@ acceptance("Sidebar - User with sidebar enabled", function (needs) {
|
|||
"adds sidebar utility class to body"
|
||||
);
|
||||
|
||||
assert.ok(exists(".sidebar-wrapper"), "displays the sidebar by default");
|
||||
assert.ok(exists(".sidebar-container"), "displays the sidebar by default");
|
||||
|
||||
await click(".header-sidebar-toggle .btn");
|
||||
|
||||
|
@ -56,10 +56,10 @@ acceptance("Sidebar - User with sidebar enabled", function (needs) {
|
|||
"removes sidebar utility class to body"
|
||||
);
|
||||
|
||||
assert.ok(!exists(".sidebar-wrapper"), "hides the sidebar");
|
||||
assert.ok(!exists(".sidebar-container"), "hides the sidebar");
|
||||
|
||||
await click(".header-sidebar-toggle .btn");
|
||||
|
||||
assert.ok(exists(".sidebar-wrapper"), "displays the sidebar");
|
||||
assert.ok(exists(".sidebar-container"), "displays the sidebar");
|
||||
});
|
||||
});
|
||||
|
|
|
@ -671,6 +671,22 @@ table {
|
|||
|
||||
// Special elements
|
||||
|
||||
#main-outlet-wrapper {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
display: grid;
|
||||
// we can CSS transition the sidebar grid width change
|
||||
// as long as we keep the column count consistent
|
||||
// grid transitions are only supported in Firefox at the moment, but coming summer 2022 to Chrome
|
||||
grid-template-areas: "sidebar content";
|
||||
grid-template-columns: min-content minmax(0, 1fr);
|
||||
gap: 0;
|
||||
|
||||
#main-outlet {
|
||||
grid-area: content;
|
||||
}
|
||||
}
|
||||
|
||||
#main-outlet {
|
||||
padding-top: 2.5em;
|
||||
}
|
||||
|
|
|
@ -131,7 +131,7 @@
|
|||
.sidebar-section-link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0.25em 0.5em;
|
||||
padding: 0.35em 0.5em;
|
||||
color: var(--primary-high);
|
||||
font-size: var(--font-down-1);
|
||||
|
||||
|
|
|
@ -187,22 +187,6 @@ input {
|
|||
}
|
||||
}
|
||||
|
||||
#main-outlet-wrapper {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
display: grid;
|
||||
// we can CSS transition the sidebar grid width change
|
||||
// as long as we keep the column count consistent
|
||||
// grid transitions are only supported in Firefox at the moment, but coming summer 2022 to Chrome
|
||||
grid-template-areas: "sidebar content";
|
||||
grid-template-columns: 0 minmax(0, 1fr);
|
||||
gap: 0;
|
||||
|
||||
#main-outlet {
|
||||
grid-area: content;
|
||||
}
|
||||
}
|
||||
|
||||
body.has-sidebar-page {
|
||||
.wrap {
|
||||
// increase page max-width to accommodate sidebar width
|
||||
|
|
|
@ -134,3 +134,57 @@ blockquote {
|
|||
#simple-container {
|
||||
width: 90%;
|
||||
}
|
||||
|
||||
#main-outlet-wrapper {
|
||||
width: 100%;
|
||||
|
||||
#main-outlet {
|
||||
&.main-outlet-animate {
|
||||
width: 95vw; // prevents content width shift during animation
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-wrapper {
|
||||
width: 0;
|
||||
transition: width 0.25s ease-in-out;
|
||||
}
|
||||
|
||||
body.has-sidebar-page {
|
||||
position: fixed;
|
||||
height: calc(100vh - var(--header-offset));
|
||||
|
||||
#main {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.sidebar-wrapper {
|
||||
width: var(--d-sidebar-width);
|
||||
}
|
||||
|
||||
#main-outlet {
|
||||
position: relative;
|
||||
width: 95vw; // prevents content width shift during animation
|
||||
&:after {
|
||||
content: "";
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -2em; // compensate for gap
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: z("dropdown");
|
||||
}
|
||||
}
|
||||
|
||||
#main-outlet-wrapper {
|
||||
grid-template-columns: min-content minmax(0, 100vw);
|
||||
gap: 0 2em;
|
||||
padding-left: 0;
|
||||
|
||||
.sidebar-container {
|
||||
padding-bottom: 6.6em;
|
||||
transition: width 0.25s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue