mirror of
https://github.com/discourse/discourse.git
synced 2025-03-26 07:40:08 +08:00
DEV: replace list control nav dropdown with DMenuMobile (#28324)
Co-authored-by: Joffrey JAFFEUX <j.jaffeux@gmail.com> Co-authored-by: Renato Atilio <renato@discourse.org> Co-authored-by: David Taylor <david@taylorhq.com>
This commit is contained in:
parent
427f473e1b
commit
931485b7c1
@ -0,0 +1,108 @@
|
|||||||
|
import Component from "@glimmer/component";
|
||||||
|
import { concat, hash } from "@ember/helper";
|
||||||
|
import { on } from "@ember/modifier";
|
||||||
|
import { action } from "@ember/object";
|
||||||
|
import { service } from "@ember/service";
|
||||||
|
import DropdownMenu from "discourse/components/dropdown-menu";
|
||||||
|
import NavigationItem from "discourse/components/navigation-item";
|
||||||
|
import PluginOutlet from "discourse/components/plugin-outlet";
|
||||||
|
import { filterTypeForMode } from "discourse/lib/filter-mode";
|
||||||
|
import icon from "discourse-common/helpers/d-icon";
|
||||||
|
import DMenu from "float-kit/components/d-menu";
|
||||||
|
|
||||||
|
export default class NavigationBarComponent extends Component {
|
||||||
|
@service site;
|
||||||
|
|
||||||
|
get filterType() {
|
||||||
|
return filterTypeForMode(this.args.filterMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
get selectedNavItem() {
|
||||||
|
const { navItems } = this.args;
|
||||||
|
let item = navItems.find((i) => i.active === true);
|
||||||
|
|
||||||
|
item = item || navItems.find((i) => i.filterType === this.filterType);
|
||||||
|
|
||||||
|
return item || navItems[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
onRegisterApi(api) {
|
||||||
|
this.dMenu = api;
|
||||||
|
}
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<ul id="navigation-bar" class="nav nav-pills">
|
||||||
|
{{#if this.site.mobileView}}
|
||||||
|
<li>
|
||||||
|
<DMenu
|
||||||
|
@modalForMobile={{true}}
|
||||||
|
@autofocus={{true}}
|
||||||
|
@identifier="list-control-toggle-link"
|
||||||
|
@onRegisterApi={{this.onRegisterApi}}
|
||||||
|
>
|
||||||
|
<:trigger>
|
||||||
|
<span
|
||||||
|
class="list-control-toggle-link__text"
|
||||||
|
>{{this.selectedNavItem.displayName}}</span>
|
||||||
|
{{icon "discourse-chevron-expand"}}
|
||||||
|
</:trigger>
|
||||||
|
|
||||||
|
<:content>
|
||||||
|
<DropdownMenu {{on "click" this.dMenu.close}} as |dropdown|>
|
||||||
|
{{#each @navItems as |navItem|}}
|
||||||
|
<dropdown.item>
|
||||||
|
<NavigationItem
|
||||||
|
@tagName="div"
|
||||||
|
@content={{navItem}}
|
||||||
|
@filterMode={{@filterMode}}
|
||||||
|
@category={{@category}}
|
||||||
|
class={{concat "nav-item_" navItem.name}}
|
||||||
|
/>
|
||||||
|
</dropdown.item>
|
||||||
|
{{/each}}
|
||||||
|
<dropdown.item>
|
||||||
|
<PluginOutlet
|
||||||
|
@name="extra-nav-item"
|
||||||
|
@connectorTagName="span"
|
||||||
|
@outletArgs={{hash
|
||||||
|
category=@category
|
||||||
|
tag=@tag
|
||||||
|
filterMode=@filterMode
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</dropdown.item>
|
||||||
|
</DropdownMenu>
|
||||||
|
</:content>
|
||||||
|
</DMenu>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<PluginOutlet
|
||||||
|
@name="inline-extra-nav-item"
|
||||||
|
@connectorTagName="span"
|
||||||
|
@outletArgs={{hash category=@category filterMode=@filterMode}}
|
||||||
|
/>
|
||||||
|
</li>
|
||||||
|
{{else}}
|
||||||
|
{{#each @navItems as |navItem|}}
|
||||||
|
<NavigationItem
|
||||||
|
@content={{navItem}}
|
||||||
|
@filterMode={{@filterMode}}
|
||||||
|
@category={{@category}}
|
||||||
|
class={{concat "nav-item_" navItem.name}}
|
||||||
|
/>
|
||||||
|
{{/each}}
|
||||||
|
<PluginOutlet
|
||||||
|
@name="extra-nav-item"
|
||||||
|
@connectorTagName="li"
|
||||||
|
@outletArgs={{hash
|
||||||
|
category=@category
|
||||||
|
tag=@tag
|
||||||
|
filterMode=@filterMode
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{{/if}}
|
||||||
|
</ul>
|
||||||
|
</template>
|
||||||
|
}
|
@ -1,55 +0,0 @@
|
|||||||
{{#if this.site.mobileView}}
|
|
||||||
<li class="navigation-toggle">
|
|
||||||
<a href {{on "click" this.toggleDrop}} class="toggle-link">
|
|
||||||
<span
|
|
||||||
class="toggle-link__text"
|
|
||||||
>{{this.selectedNavItem.displayName}}</span>
|
|
||||||
{{d-icon "discourse-chevron-expand"}}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
{{#if this.expanded}}
|
|
||||||
<ul class="drop">
|
|
||||||
{{#each this.navItems as |navItem|}}
|
|
||||||
<NavigationItem
|
|
||||||
@content={{navItem}}
|
|
||||||
@filterMode={{this.filterMode}}
|
|
||||||
@category={{this.category}}
|
|
||||||
class={{concat "nav-item_" navItem.name}}
|
|
||||||
/>
|
|
||||||
{{/each}}
|
|
||||||
<PluginOutlet
|
|
||||||
@name="extra-nav-item"
|
|
||||||
@connectorTagName="li"
|
|
||||||
@outletArgs={{hash
|
|
||||||
category=this.category
|
|
||||||
tag=this.tag
|
|
||||||
filterMode=this.filterMode
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</ul>
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
<PluginOutlet
|
|
||||||
@name="inline-extra-nav-item"
|
|
||||||
@connectorTagName="li"
|
|
||||||
@outletArgs={{hash category=this.category filterMode=this.filterMode}}
|
|
||||||
/>
|
|
||||||
{{else}}
|
|
||||||
{{#each this.navItems as |navItem|}}
|
|
||||||
<NavigationItem
|
|
||||||
@content={{navItem}}
|
|
||||||
@filterMode={{this.filterMode}}
|
|
||||||
@category={{this.category}}
|
|
||||||
class={{concat "nav-item_" navItem.name}}
|
|
||||||
/>
|
|
||||||
{{/each}}
|
|
||||||
<PluginOutlet
|
|
||||||
@name="extra-nav-item"
|
|
||||||
@connectorTagName="li"
|
|
||||||
@outletArgs={{hash
|
|
||||||
category=this.category
|
|
||||||
tag=this.tag
|
|
||||||
filterMode=this.filterMode
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
{{/if}}
|
|
@ -1,103 +0,0 @@
|
|||||||
import { tracked } from "@glimmer/tracking";
|
|
||||||
import Component from "@ember/component";
|
|
||||||
import { action } from "@ember/object";
|
|
||||||
import { dependentKeyCompat } from "@ember/object/compat";
|
|
||||||
import { next } from "@ember/runloop";
|
|
||||||
import $ from "jquery";
|
|
||||||
import { filterTypeForMode } from "discourse/lib/filter-mode";
|
|
||||||
import DiscourseURL from "discourse/lib/url";
|
|
||||||
import discourseComputed, { observes } from "discourse-common/utils/decorators";
|
|
||||||
|
|
||||||
export default Component.extend({
|
|
||||||
tagName: "ul",
|
|
||||||
classNameBindings: [":nav", ":nav-pills"],
|
|
||||||
elementId: "navigation-bar",
|
|
||||||
filterMode: tracked(),
|
|
||||||
|
|
||||||
@dependentKeyCompat
|
|
||||||
get filterType() {
|
|
||||||
return filterTypeForMode(this.filterMode);
|
|
||||||
},
|
|
||||||
|
|
||||||
init() {
|
|
||||||
this._super(...arguments);
|
|
||||||
},
|
|
||||||
|
|
||||||
@discourseComputed("filterType", "navItems")
|
|
||||||
selectedNavItem(filterType, navItems) {
|
|
||||||
let item = navItems.find((i) => i.active === true);
|
|
||||||
|
|
||||||
item = item || navItems.find((i) => i.get("filterType") === filterType);
|
|
||||||
|
|
||||||
if (!item) {
|
|
||||||
let connectors = this.connectors;
|
|
||||||
let category = this.category;
|
|
||||||
if (connectors && category) {
|
|
||||||
connectors.forEach((c) => {
|
|
||||||
if (
|
|
||||||
c.connectorClass &&
|
|
||||||
typeof c.connectorClass.path === "function" &&
|
|
||||||
typeof c.connectorClass.displayName === "function"
|
|
||||||
) {
|
|
||||||
let path = c.connectorClass.path(category);
|
|
||||||
if (path.indexOf(filterType) > 0) {
|
|
||||||
item = {
|
|
||||||
displayName: c.connectorClass.displayName(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return item || navItems[0];
|
|
||||||
},
|
|
||||||
|
|
||||||
@observes("expanded")
|
|
||||||
closedNav() {
|
|
||||||
if (!this.expanded) {
|
|
||||||
this.ensureDropClosed();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
ensureDropClosed() {
|
|
||||||
if (!this.element || this.isDestroying || this.isDestroyed) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.expanded) {
|
|
||||||
this.set("expanded", false);
|
|
||||||
}
|
|
||||||
$(window).off("click.navigation-bar");
|
|
||||||
DiscourseURL.appEvents.off("dom:clean", this, this.ensureDropClosed);
|
|
||||||
},
|
|
||||||
|
|
||||||
@action
|
|
||||||
toggleDrop(event) {
|
|
||||||
event?.preventDefault();
|
|
||||||
this.set("expanded", !this.expanded);
|
|
||||||
|
|
||||||
if (this.expanded) {
|
|
||||||
DiscourseURL.appEvents.on("dom:clean", this, this.ensureDropClosed);
|
|
||||||
|
|
||||||
next(() => {
|
|
||||||
if (!this.expanded) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$(this.element.querySelector(".drop a")).on("click", () => {
|
|
||||||
this.element.querySelector(".drop").style.display = "none";
|
|
||||||
|
|
||||||
next(() => {
|
|
||||||
this.ensureDropClosed();
|
|
||||||
});
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
|
|
||||||
$(window).on("click.navigation-bar", () => {
|
|
||||||
this.ensureDropClosed();
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
@ -0,0 +1,35 @@
|
|||||||
|
import EmberObject from "@ember/object";
|
||||||
|
import { click, render } from "@ember/test-helpers";
|
||||||
|
import { module, test } from "qunit";
|
||||||
|
import NavigationBar from "discourse/components/navigation-bar";
|
||||||
|
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
|
||||||
|
|
||||||
|
const navItems = [
|
||||||
|
EmberObject.create({ name: "new", displayName: "New" }),
|
||||||
|
EmberObject.create({ name: "unread", displayName: "Unread" }),
|
||||||
|
];
|
||||||
|
|
||||||
|
module("Integration | Component | navigation-bar", function (hooks) {
|
||||||
|
setupRenderingTest(hooks);
|
||||||
|
|
||||||
|
test("display navigation bar items", async function (assert) {
|
||||||
|
await render(<template><NavigationBar @navItems={{navItems}} /></template>);
|
||||||
|
|
||||||
|
assert.dom(".nav .nav-item_new").includesText("New");
|
||||||
|
assert.dom(".nav .nav-item_unread").includesText("Unread");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("display navigation bar items behind a dropdown on mobile", async function (assert) {
|
||||||
|
this.site.mobileView = true;
|
||||||
|
|
||||||
|
await render(<template><NavigationBar @navItems={{navItems}} /></template>);
|
||||||
|
|
||||||
|
assert.dom(".nav .nav-item_new").doesNotExist();
|
||||||
|
assert.dom(".nav .nav-item_unread").doesNotExist();
|
||||||
|
|
||||||
|
await click(".list-control-toggle-link-trigger");
|
||||||
|
|
||||||
|
assert.dom(".nav .nav-item_new").includesText("New");
|
||||||
|
assert.dom(".nav .nav-item_unread").includesText("Unread");
|
||||||
|
});
|
||||||
|
});
|
@ -100,7 +100,7 @@ body:not(.archetype-private_message) {
|
|||||||
overflow: auto;
|
overflow: auto;
|
||||||
align-items: start;
|
align-items: start;
|
||||||
overscroll-behavior: contain;
|
overscroll-behavior: contain;
|
||||||
padding: 1.5em;
|
padding: 1rem 1.5rem;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
.desktop-view & {
|
.desktop-view & {
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
@import "invite-signup";
|
@import "invite-signup";
|
||||||
@import "lightbox";
|
@import "lightbox";
|
||||||
@import "menu-panel";
|
@import "menu-panel";
|
||||||
|
@import "list-controls";
|
||||||
@import "login-modal";
|
@import "login-modal";
|
||||||
@import "modal";
|
@import "modal";
|
||||||
@import "modal-overrides";
|
@import "modal-overrides";
|
||||||
|
87
app/assets/stylesheets/mobile/list-controls.scss
Normal file
87
app/assets/stylesheets/mobile/list-controls.scss
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
.list-controls {
|
||||||
|
.container {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
#create-topic {
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
align-self: stretch;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.navigation-container {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-pills {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
margin: 0 3px 5px 3px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.list-control-toggle-link-trigger {
|
||||||
|
font-size: var(--font-up-1-rem);
|
||||||
|
font-weight: bold;
|
||||||
|
color: var(--primary-high);
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:focus {
|
||||||
|
background: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.d-icon {
|
||||||
|
color: inherit;
|
||||||
|
font-size: var(--font-down-2);
|
||||||
|
margin-left: 0.5em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
> li {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
> li > a {
|
||||||
|
line-height: var(--line-height-large);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.d-icon {
|
||||||
|
margin-left: 5px;
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-container .full-width {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fk-d-menu-modal.list-control-toggle-link-content {
|
||||||
|
.d-modal {
|
||||||
|
&__body {
|
||||||
|
padding-block: 0.75rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-menu {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
&__item {
|
||||||
|
a {
|
||||||
|
display: block;
|
||||||
|
color: var(--primary);
|
||||||
|
padding: 0.5em 1rem;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background: var(--d-selected);
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -2,132 +2,6 @@
|
|||||||
// Topic lists
|
// Topic lists
|
||||||
// --------------------------------------------------
|
// --------------------------------------------------
|
||||||
|
|
||||||
// List controls
|
|
||||||
// --------------------------------------------------
|
|
||||||
|
|
||||||
.list-controls {
|
|
||||||
.container {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
align-items: center;
|
|
||||||
#create-topic {
|
|
||||||
box-sizing: border-box;
|
|
||||||
display: flex;
|
|
||||||
align-self: stretch;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropdown-select-box-header {
|
|
||||||
display: flex;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.navigation-container {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
width: 100%;
|
|
||||||
margin: 0;
|
|
||||||
|
|
||||||
button {
|
|
||||||
&.select-kit-header {
|
|
||||||
display: flex;
|
|
||||||
height: 100%;
|
|
||||||
flex: 1 1 auto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-pills {
|
|
||||||
display: flex;
|
|
||||||
flex: 1 1 auto;
|
|
||||||
margin: 0 3px 5px 3px;
|
|
||||||
position: relative;
|
|
||||||
.navigation-toggle {
|
|
||||||
flex: 0 1 auto;
|
|
||||||
border: 0;
|
|
||||||
|
|
||||||
.toggle-link {
|
|
||||||
font-size: var(--font-up-1-rem);
|
|
||||||
font-weight: bold;
|
|
||||||
color: var(--primary-high);
|
|
||||||
padding: 0;
|
|
||||||
|
|
||||||
&:hover,
|
|
||||||
&:focus {
|
|
||||||
background: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.d-icon {
|
|
||||||
color: inherit;
|
|
||||||
font-size: var(--font-down-2);
|
|
||||||
margin-left: 0.5em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
> li {
|
|
||||||
margin-right: 0;
|
|
||||||
font-size: var(--font-down-1);
|
|
||||||
border: 1px solid var(--primary-medium);
|
|
||||||
border-radius: var(--d-input-border-radius);
|
|
||||||
}
|
|
||||||
> li > a {
|
|
||||||
line-height: var(--line-height-large);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
border-radius: var(--d-input-border-radius);
|
|
||||||
.d-icon {
|
|
||||||
margin-left: 5px;
|
|
||||||
margin-right: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.drop {
|
|
||||||
border: 1px solid var(--primary-low);
|
|
||||||
position: absolute;
|
|
||||||
z-index: z("dropdown") - 1;
|
|
||||||
background-color: var(--secondary);
|
|
||||||
padding: 0 10px 10px 10px;
|
|
||||||
width: 150px;
|
|
||||||
top: 100%;
|
|
||||||
margin: 0;
|
|
||||||
left: 0; // iOS6 alignment
|
|
||||||
li {
|
|
||||||
list-style-type: none;
|
|
||||||
margin-left: 0;
|
|
||||||
margin-top: 5px;
|
|
||||||
padding-top: 10px;
|
|
||||||
a {
|
|
||||||
width: 100%;
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.list-container .full-width {
|
|
||||||
margin-left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.category-breadcrumb {
|
|
||||||
width: 100%;
|
|
||||||
gap: 0.5em;
|
|
||||||
li {
|
|
||||||
flex: 1 1 33%;
|
|
||||||
margin: 0;
|
|
||||||
details {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
.name,
|
|
||||||
.category-name {
|
|
||||||
@include ellipsis;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Base list
|
|
||||||
// --------------------------------------------------
|
|
||||||
|
|
||||||
.topic-list {
|
.topic-list {
|
||||||
.right {
|
.right {
|
||||||
margin-left: 55px;
|
margin-left: 55px;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user