mirror of
https://github.com/discourse/discourse.git
synced 2025-02-25 18:55:32 -06:00
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 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 Controller from "@ember/controller";
|
||||||
import discourseComputed from "discourse-common/utils/decorators";
|
import discourseComputed from "discourse-common/utils/decorators";
|
||||||
|
import discourseDebounce from "discourse-common/lib/debounce";
|
||||||
import { inject as service } from "@ember/service";
|
import { inject as service } from "@ember/service";
|
||||||
|
import { action } from "@ember/object";
|
||||||
|
|
||||||
export default Controller.extend({
|
export default Controller.extend({
|
||||||
showTop: true,
|
showTop: true,
|
||||||
showFooter: false,
|
showFooter: false,
|
||||||
router: service(),
|
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
|
@discourseComputed
|
||||||
canSignUp() {
|
canSignUp() {
|
||||||
@ -26,4 +37,31 @@ export default Controller.extend({
|
|||||||
showFooterNav() {
|
showFooterNav() {
|
||||||
return this.capabilities.isAppWebview || this.capabilities.isiOSPWA;
|
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() {
|
toggleMobileView() {
|
||||||
mobile.toggleMobileView();
|
mobile.toggleMobileView();
|
||||||
},
|
},
|
||||||
|
@ -2,15 +2,19 @@
|
|||||||
<a href="#main-container" id="skip-link">{{i18n "skip_to_main_content"}}</a>
|
<a href="#main-container" id="skip-link">{{i18n "skip_to_main_content"}}</a>
|
||||||
<DDocument />
|
<DDocument />
|
||||||
<PluginOutlet @name="above-site-header" @connectorTagName="div" />
|
<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 />
|
<SoftwareUpdatePrompt />
|
||||||
|
|
||||||
<PluginOutlet @name="below-site-header" @connectorTagName="div" @args={{hash currentPath=router._router.currentPath}} />
|
<PluginOutlet @name="below-site-header" @connectorTagName="div" @args={{hash currentPath=router._router.currentPath}} />
|
||||||
|
|
||||||
<div id="main-outlet-wrapper" class="wrap" role="main">
|
<div id="main-outlet-wrapper" class="wrap" role="main">
|
||||||
{{#if (and currentUser.experimental_sidebar_enabled showSidebar)}}
|
|
||||||
<Sidebar />
|
<div class="sidebar-wrapper">
|
||||||
{{/if}}
|
{{!-- empty div allows for animation --}}
|
||||||
|
{{#if (and currentUser.experimental_sidebar_enabled showSidebar)}}
|
||||||
|
<Sidebar @toggleSidebar={{action "toggleSidebar"}}/>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div id="main-outlet">
|
<div id="main-outlet">
|
||||||
<PluginOutlet @name="above-main-container" @connectorTagName="div" />
|
<PluginOutlet @name="above-main-container" @connectorTagName="div" />
|
||||||
|
@ -1,16 +1,14 @@
|
|||||||
<DSection @pageClass="has-sidebar" @class="sidebar-wrapper">
|
<DSection @pageClass="has-sidebar" @class="sidebar-container">
|
||||||
<div class="sidebar-container">
|
<div class="sidebar-scroll-wrap">
|
||||||
<div class="sidebar-scroll-wrap">
|
<Sidebar::TopicsSection />
|
||||||
<Sidebar::TopicsSection />
|
<Sidebar::CategoriesSection />
|
||||||
<Sidebar::CategoriesSection />
|
|
||||||
|
|
||||||
{{#if this.siteSettings.tagging_enabled}}
|
{{#if this.siteSettings.tagging_enabled}}
|
||||||
<Sidebar::TagsSection />
|
<Sidebar::TagsSection />
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
{{#if this.siteSettings.enable_personal_messages}}
|
{{#if this.siteSettings.enable_personal_messages}}
|
||||||
<Sidebar::MessagesSection />
|
<Sidebar::MessagesSection />
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</DSection>
|
</DSection>
|
||||||
|
@ -9,7 +9,7 @@ export default createWidget("sidebar-toggle", {
|
|||||||
title: "",
|
title: "",
|
||||||
icon: "bars",
|
icon: "bars",
|
||||||
action: "toggleSidebar",
|
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"
|
"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"
|
"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"
|
"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");
|
await click(".header-sidebar-toggle .btn");
|
||||||
|
|
||||||
@ -56,10 +56,10 @@ acceptance("Sidebar - User with sidebar enabled", function (needs) {
|
|||||||
"removes sidebar utility class to body"
|
"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");
|
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
|
// 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 {
|
#main-outlet {
|
||||||
padding-top: 2.5em;
|
padding-top: 2.5em;
|
||||||
}
|
}
|
||||||
|
@ -131,7 +131,7 @@
|
|||||||
.sidebar-section-link {
|
.sidebar-section-link {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 0.25em 0.5em;
|
padding: 0.35em 0.5em;
|
||||||
color: var(--primary-high);
|
color: var(--primary-high);
|
||||||
font-size: var(--font-down-1);
|
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 {
|
body.has-sidebar-page {
|
||||||
.wrap {
|
.wrap {
|
||||||
// increase page max-width to accommodate sidebar width
|
// increase page max-width to accommodate sidebar width
|
||||||
|
@ -134,3 +134,57 @@ blockquote {
|
|||||||
#simple-container {
|
#simple-container {
|
||||||
width: 90%;
|
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
Block a user