diff --git a/app/assets/javascripts/discourse/app/components/sidebar.js b/app/assets/javascripts/discourse/app/components/sidebar.js new file mode 100644 index 00000000000..ea096319112 --- /dev/null +++ b/app/assets/javascripts/discourse/app/components/sidebar.js @@ -0,0 +1,3 @@ +import GlimmerComponent from "discourse/components/glimmer"; + +export default class Sidebar extends GlimmerComponent {} diff --git a/app/assets/javascripts/discourse/app/controllers/application.js b/app/assets/javascripts/discourse/app/controllers/application.js index b494f56e54d..c07ca32adc9 100644 --- a/app/assets/javascripts/discourse/app/controllers/application.js +++ b/app/assets/javascripts/discourse/app/controllers/application.js @@ -6,6 +6,12 @@ export default Controller.extend({ showTop: true, showFooter: false, router: service(), + showSidebar: true, + + @discourseComputed("showSidebar", "currentUser.experimental_sidebar_enabled") + mainOutletWrapperClasses(showSidebar, experimentalSidebarEnabled) { + return showSidebar && experimentalSidebarEnabled ? "has-sidebar" : ""; + }, @discourseComputed canSignUp() { diff --git a/app/assets/javascripts/discourse/app/routes/application.js b/app/assets/javascripts/discourse/app/routes/application.js index dbffdf2de99..ea6da0a5c76 100644 --- a/app/assets/javascripts/discourse/app/routes/application.js +++ b/app/assets/javascripts/discourse/app/routes/application.js @@ -37,6 +37,10 @@ const ApplicationRoute = DiscourseRoute.extend(OpenComposer, { }); }, + toggleSidebar() { + this.controllerFor("application").toggleProperty("showSidebar"); + }, + toggleMobileView() { mobile.toggleMobileView(); }, diff --git a/app/assets/javascripts/discourse/app/templates/application.hbs b/app/assets/javascripts/discourse/app/templates/application.hbs index 6c0944f9059..7e16b0f9106 100644 --- a/app/assets/javascripts/discourse/app/templates/application.hbs +++ b/app/assets/javascripts/discourse/app/templates/application.hbs @@ -8,26 +8,34 @@ showKeyboard=(route-action "showKeyboardShortcutsHelp") toggleMobileView=(route-action "toggleMobileView") toggleAnonymous=(route-action "toggleAnonymous") - logout=(route-action "logout")}} + logout=(route-action "logout") + toggleSidebar=(route-action "toggleSidebar") + }} {{software-update-prompt}} {{plugin-outlet name="below-site-header" connectorTagName="div" args=(hash currentPath=router._router.currentPath)}} -
- {{plugin-outlet name="above-main-container" connectorTagName="div"}} -
- {{#if showTop}} - {{custom-html name="top"}} - {{/if}} - {{notification-consent-banner}} - {{pwa-install-banner}} - {{global-notice}} - {{create-topics-notice}} - {{plugin-outlet name="top-notices" connectorTagName="div" args=(hash currentPath=router._router.currentPath)}} -
+
+ {{#if currentUser.experimental_sidebar_enabled}} + + {{/if}} - {{outlet}} - {{outlet "user-card"}} +
+ {{plugin-outlet name="above-main-container" connectorTagName="div"}} +
+ {{#if showTop}} + {{custom-html name="top"}} + {{/if}} + {{notification-consent-banner}} + {{pwa-install-banner}} + {{global-notice}} + {{create-topics-notice}} + {{plugin-outlet name="top-notices" connectorTagName="div" args=(hash currentPath=router._router.currentPath)}} +
+ + {{outlet}} + {{outlet "user-card"}} +
{{plugin-outlet name="above-footer" connectorTagName="div" args=(hash showFooter=showFooter)}} diff --git a/app/assets/javascripts/discourse/app/templates/components/sidebar.hbs b/app/assets/javascripts/discourse/app/templates/components/sidebar.hbs new file mode 100644 index 00000000000..91657f9f4c6 --- /dev/null +++ b/app/assets/javascripts/discourse/app/templates/components/sidebar.hbs @@ -0,0 +1,6 @@ +{{#if @shouldDisplay}} + +{{/if}} diff --git a/app/assets/javascripts/discourse/app/widgets/header-contents.js b/app/assets/javascripts/discourse/app/widgets/header-contents.js index 5ce69e3b2d5..7f13995929f 100644 --- a/app/assets/javascripts/discourse/app/widgets/header-contents.js +++ b/app/assets/javascripts/discourse/app/widgets/header-contents.js @@ -4,6 +4,9 @@ import hbs from "discourse/widgets/hbs-compiler"; createWidget("header-contents", { tagName: "div.contents.clearfix", template: hbs` + {{#if attrs.sidebarEnabled}} + {{sidebar-toggle attrs=attrs}} + {{/if}} {{home-logo attrs=attrs}} {{#if attrs.topic}} {{header-topic-info attrs=attrs}} diff --git a/app/assets/javascripts/discourse/app/widgets/header.js b/app/assets/javascripts/discourse/app/widgets/header.js index 4885bbf1981..9d289ae005d 100644 --- a/app/assets/javascripts/discourse/app/widgets/header.js +++ b/app/assets/javascripts/discourse/app/widgets/header.js @@ -400,7 +400,12 @@ export default createWidget("header", { return panels; }; - let contentsAttrs = { contents, minimized: !!attrs.topic }; + let contentsAttrs = { + contents, + minimized: !!attrs.topic, + sidebarEnabled: this.currentUser?.experimental_sidebar_enabled, + }; + return h( "div.wrap", this.attach("header-contents", Object.assign({}, attrs, contentsAttrs)) diff --git a/app/assets/javascripts/discourse/app/widgets/sidebar-toggle.js b/app/assets/javascripts/discourse/app/widgets/sidebar-toggle.js new file mode 100644 index 00000000000..b39163c0f32 --- /dev/null +++ b/app/assets/javascripts/discourse/app/widgets/sidebar-toggle.js @@ -0,0 +1,16 @@ +import { createWidget } from "discourse/widgets/widget"; + +export default createWidget("sidebar-toggle", { + tagName: "span.header-sidebar-toggle", + + html() { + return [ + this.attach("button", { + title: "", + icon: "bars", + action: "toggleSidebar", + className: "btn btn-flat", + }), + ]; + }, +}); diff --git a/app/assets/javascripts/discourse/tests/acceptance/sidebar-test.js b/app/assets/javascripts/discourse/tests/acceptance/sidebar-test.js new file mode 100644 index 00000000000..c173b7e4d17 --- /dev/null +++ b/app/assets/javascripts/discourse/tests/acceptance/sidebar-test.js @@ -0,0 +1,65 @@ +import { click, visit } from "@ember/test-helpers"; +import { + acceptance, + conditionalTest, + exists, +} from "discourse/tests/helpers/qunit-helpers"; +import { test } from "qunit"; +import { isLegacyEmber } from "discourse-common/config/environment"; + +acceptance("Sidebar - Anon User", function () { + // Don't show sidebar for anon user until we know what we want to display + test("sidebar is not displayed", async function (assert) { + await visit("/"); + + assert.ok(!exists("#main-outlet-wrapper.has-sidebar")); + assert.ok(!exists(".sidebar-wrapper")); + }); +}); + +acceptance("Sidebar - User with sidebar disabled", function (needs) { + needs.user({ experimental_sidebar_enabled: false }); + + conditionalTest( + "sidebar is not displayed", + !isLegacyEmber(), + async function (assert) { + await visit("/"); + + assert.ok(!exists("#main-outlet-wrapper.has-sidebar")); + assert.ok(!exists(".sidebar-wrapper")); + } + ); +}); + +acceptance("Sidebar - User with sidebar enabled", function (needs) { + needs.user({ experimental_sidebar_enabled: true }); + + conditionalTest( + "hiding and displaying sidebar", + !isLegacyEmber(), + async function (assert) { + await visit("/"); + + assert.ok( + exists("#main-outlet-wrapper.has-sidebar"), + "adds sidebar utility class on main outlet wrapper" + ); + + assert.ok(exists(".sidebar-wrapper"), "displays the sidebar by default"); + + await click(".header-sidebar-toggle .btn"); + + assert.ok( + !exists("#main-outlet-wrapper.has-sidebar"), + "removes sidebar utility class from main outlet wrapper" + ); + + assert.ok(!exists(".sidebar-wrapper"), "hides the sidebar"); + + await click(".header-sidebar-toggle .btn"); + + assert.ok(exists(".sidebar-wrapper"), "displays the sidebar"); + } + ); +}); diff --git a/app/assets/stylesheets/common/base/_index.scss b/app/assets/stylesheets/common/base/_index.scss index f6b8e49d0a0..6741fdc6bbe 100644 --- a/app/assets/stylesheets/common/base/_index.scss +++ b/app/assets/stylesheets/common/base/_index.scss @@ -44,6 +44,7 @@ @import "search"; @import "share_link"; @import "shared-drafts"; +@import "sidebar"; @import "tagging"; @import "tooltip"; @import "topic-admin-menu"; diff --git a/app/assets/stylesheets/common/base/discourse.scss b/app/assets/stylesheets/common/base/discourse.scss index 0126ae6cc55..b9cdd3cfb34 100644 --- a/app/assets/stylesheets/common/base/discourse.scss +++ b/app/assets/stylesheets/common/base/discourse.scss @@ -651,6 +651,7 @@ table { #main-outlet { padding-top: 2.5em; + grid-area: content; } #main { diff --git a/app/assets/stylesheets/common/base/header.scss b/app/assets/stylesheets/common/base/header.scss index 07cffbaf119..0cc0a38922e 100644 --- a/app/assets/stylesheets/common/base/header.scss +++ b/app/assets/stylesheets/common/base/header.scss @@ -14,7 +14,6 @@ backface-visibility: hidden; /** do magic for scrolling performance **/ > .wrap { - box-sizing: border-box; width: 100%; height: 100%; diff --git a/app/assets/stylesheets/common/base/sidebar.scss b/app/assets/stylesheets/common/base/sidebar.scss new file mode 100644 index 00000000000..d1c87b6a639 --- /dev/null +++ b/app/assets/stylesheets/common/base/sidebar.scss @@ -0,0 +1,40 @@ +.header-sidebar-toggle { + margin-right: 1em; + margin-left: -10px; + + button { + position: relative; + font-size: var(--font-up-2); + + .discourse-no-touch & { + &:hover { + background: var(--primary-low); + .d-icon { + color: var(--primary-medium); + } + } + } + } +} + +.sidebar-wrapper { + grid-area: sidebar; + position: sticky; + top: var(--header-offset); + height: calc(100vh - var(--header-offset)); + align-self: start; + overflow-y: auto; + background-color: var(--primary-very-low); +} + +.sidebar-container { + box-sizing: border-box; + height: 100%; + width: 240px; + padding: 1em; +} + +.sidebar-toggle { + display: flex; + justify-content: flex-end; +} diff --git a/app/assets/stylesheets/desktop/discourse.scss b/app/assets/stylesheets/desktop/discourse.scss index 8fb5a9dd553..18893c7aae1 100644 --- a/app/assets/stylesheets/desktop/discourse.scss +++ b/app/assets/stylesheets/desktop/discourse.scss @@ -182,3 +182,16 @@ input { min-width: 0; } } + +#main-outlet-wrapper { + display: grid; + grid-template-areas: "content"; + grid-template-columns: 1fr; + gap: 0; + + &.has-sidebar { + grid-template-areas: "sidebar content"; + grid-template-columns: 240px 1fr; + gap: 0 2em; + } +}