Split `hamburger-menu` out into `menu-panel`

This commit is contained in:
Robin Ward 2015-08-26 15:51:56 -04:00
parent 05adcda1fc
commit 5457684975
14 changed files with 316 additions and 216 deletions

View File

@ -1,67 +1,6 @@
import { default as computed, on, observes } from 'ember-addons/ember-computed-decorators';
import computed from 'ember-addons/ember-computed-decorators';
export default Ember.Component.extend({
classNameBindings: ['visible::hidden', 'viewMode'],
attributeBindings: ['style'],
elementId: 'hamburger-menu',
viewMode: 'dropDown',
showClose: Ember.computed.equal('viewMode', 'slide-in'),
@computed('viewMode')
style(viewMode) {
if (viewMode === 'drop-down') {
const $buttonPanel = $('header ul.icons');
const buttonPanelPos = $buttonPanel.offset();
const myWidth = this.$().width();
const posTop = parseInt(buttonPanelPos.top + $buttonPanel.height());
const posLeft = parseInt(buttonPanelPos.left + $buttonPanel.width() - myWidth);
return `left: ${posLeft}px; top: ${posTop}px`.htmlSafe();
} else {
const headerHeight = parseInt($('header.d-header').height() + 3);
return `top: ${headerHeight}px`.htmlSafe();
}
},
@computed('viewMode')
bodyStyle(viewMode) {
if (viewMode === 'drop-down') {
const height = parseInt($(window).height() * 0.8)
return `height: ${height}px`.htmlSafe();
}
},
@observes('visible')
_visibleChanged() {
const isDropdown = (this.get('viewMode') === 'drop-down');
if (this.get('visible')) {
if (isDropdown) {
$('.hamburger-dropdown').addClass('active');
}
if ($(window).width() < 1024) {
this.set('viewMode', 'slide-in');
} else {
this.set('viewMode', 'drop-down');
}
$('html').on('click.close-hamburger', (e) => {
const $target = $(e.target);
if ($target.closest('.hamburger-dropdown').length > 0) { return; }
if ($target.closest('#hamburger-menu').length > 0) { return; }
this.hide();
});
} else {
$('.hamburger-dropdown').removeClass('active');
$('html').off('click.close-hamburger');
}
},
classNames: ['hamburger-panel'],
@computed()
showKeyboardShortcuts() {
@ -83,29 +22,6 @@ export default Ember.Component.extend({
return this.siteSettings.faq_url ? this.siteSettings.faq_url : Discourse.getURL('/faq');
},
@on('didInsertElement')
_bindEvents() {
this.$().on('click.discourse-hamburger', 'a', () => {
this.hide();
});
this.appEvents.on('dropdowns:closeAll', this, this.hide);
$('body').on('keydown.discourse-hambuger', (e) => {
if (e.which === 27) {
this.hide();
}
});
},
@on('willDestroyElement')
_removeEvents() {
this.appEvents.off('dropdowns:closeAll', this, this.hide);
this.$().off('click.discourse-hamburger');
$('body').off('keydown.discourse-hambuger');
$('html').off('click.close-hamburger');
},
@computed()
categories() {
const hideUncategorized = !this.siteSettings.allow_uncategorized_topics;
@ -119,14 +35,7 @@ export default Ember.Component.extend({
});
},
hide() {
this.set('visible', false);
},
actions: {
close() {
this.hide();
},
keyboardShortcuts() {
this.sendAction('showKeyboardAction');
},

View File

@ -0,0 +1,124 @@
import { default as computed, on, observes } from 'ember-addons/ember-computed-decorators';
export default Ember.Component.extend({
classNameBindings: [':menu-panel', 'visible::hidden', 'viewMode'],
attributeBindings: ['style'],
viewMode: 'dropDown',
showClose: Ember.computed.equal('viewMode', 'slide-in'),
@computed('viewMode')
style(viewMode) {
if (viewMode === 'drop-down') {
const $buttonPanel = $('header ul.icons');
if ($buttonPanel.length === 0) { return; }
const buttonPanelPos = $buttonPanel.offset();
const myWidth = this.$().width();
const posTop = parseInt(buttonPanelPos.top + $buttonPanel.height()) - $(window).scrollTop();
const posLeft = parseInt(buttonPanelPos.left + $buttonPanel.width() - myWidth);
return `left: ${posLeft}px; top: ${posTop}px`.htmlSafe();
} else {
const headerHeight = parseInt($('header.d-header').height() + 3);
return `top: ${headerHeight}px`.htmlSafe();
}
},
@computed('viewMode')
bodyStyle(viewMode) {
if (viewMode === 'drop-down') {
const height = parseInt($(window).height() * 0.8)
return `height: ${height}px`.htmlSafe();
}
},
@observes('visible')
_visibleChanged() {
const force = this.get('force');
if (force) {
this.set('viewMode', force);
} else if ($(window).width() < 1024) {
this.set('viewMode', 'slide-in');
} else {
this.set('viewMode', 'drop-down');
}
const isDropdown = (this.get('viewMode') === 'drop-down');
const markActive = this.get('markActive');
if (this.get('visible')) {
if (isDropdown && markActive) {
$(markActive).addClass('active');
}
$('html').on('click.close-menu-panel', (e) => {
const $target = $(e.target);
if ($target.closest(markActive).length > 0) { return; }
if ($target.closest('.menu-panel').length > 0) { return; }
this.hide();
});
} else {
if (markActive) {
$(markActive).removeClass('active');
}
$('html').off('click.close-menu-panel');
}
},
@computed()
showKeyboardShortcuts() {
return !Discourse.Mobile.mobileView && !this.capabilities.touch;
},
@computed()
showMobileToggle() {
return Discourse.Mobile.mobileView || (this.siteSettings.enable_mobile_theme && this.capabilities.touch);
},
@computed()
mobileViewLinkTextKey() {
return Discourse.Mobile.mobileView ? "desktop_view" : "mobile_view";
},
@computed()
faqUrl() {
return this.siteSettings.faq_url ? this.siteSettings.faq_url : Discourse.getURL('/faq');
},
@on('didInsertElement')
_bindEvents() {
this.$().on('click.discourse-menu-panel', 'a', () => {
this.hide();
});
this.appEvents.on('dropdowns:closeAll', this, this.hide);
$('body').on('keydown.discourse-menu-panel', (e) => {
if (e.which === 27) {
this.hide();
}
});
},
@on('willDestroyElement')
_removeEvents() {
this.appEvents.off('dropdowns:closeAll', this, this.hide);
this.$().off('click.discourse-menu-panel');
$('body').off('keydown.discourse-menu-panel');
$('html').off('click.close-menu-panel');
},
hide() {
this.set('visible', false);
},
actions: {
close() {
this.hide();
}
}
});

View File

@ -1,5 +1,5 @@
{{render "header"}}
{{hamburger-menu visible=hamburgerVisible showKeyboardAction="showKeyboardShortcutsHelp"}}
{{hamburger-menu hamburgerVisible=hamburgerVisible showKeyboardAction="showKeyboardShortcutsHelp"}}
<div id="main-outlet" class="wrap">
<div class="container">

View File

@ -1,82 +1,73 @@
{{#if visible}}
{{#if showClose}}
<div class='hamburger-header clearfix'>
<a href {{action "close"}} class='close-hamburger'>{{fa-icon 'times'}}</a>
</div>
{{/if}}
<div class='hamburger-body' style={{bodyStyle}}>
<ul class="location-links">
{{#if currentUser.staff}}
<li>
{{#link-to "admin" class="admin-link"}}
{{fa-icon "wrench"}} {{i18n 'admin_title'}}
{{/link-to}}
</li>
<li>
{{#link-to "adminFlags" class="flagged-posts-link"}}
{{fa-icon "flag"}} {{i18n 'flags_title'}}
{{#if currentUser.site_flagged_posts_count}}
<span title={{i18n 'notifications.total_flagged'}} class='badge-notification flagged-posts'>{{currentUser.site_flagged_posts_count}}</span>
{{/if}}
{{/link-to}}
</li>
{{/if}}
{{#menu-panel visible=hamburgerVisible markActive=".hamburger-dropdown"}}
<ul class="location-links">
{{#if currentUser.staff}}
<li>
{{#link-to "discovery.latest" class="latest-topics-link"}}
{{i18n 'filters.latest.title.zero'}}
{{#link-to "admin" class="admin-link"}}
{{fa-icon "wrench"}} {{i18n 'admin_title'}}
{{/link-to}}
</li>
{{#if siteSettings.enable_badges}}
<li>
{{#link-to 'badges' class="badge-link"}}{{i18n 'badges.title'}}{{/link-to}}
</li>
{{/if}}
{{#if siteSettings.enable_user_directory}}
<li>{{#link-to 'users' class="user-directory-link"}}{{i18n "directory.title"}}{{/link-to}}</li>
{{/if}}
{{#if currentUser.show_queued_posts}}
<li>
{{#link-to 'queued-posts'}}
{{i18n "queue.title"}}
{{#if currentUser.post_queue_new_count}}
<span class='badge-notification flagged-posts'>{{currentUser.post_queue_new_count}}</span>
{{/if}}
{{/link-to}}
</li>
{{/if}}
{{plugin-outlet "site-map-links"}}
{{#if showKeyboardShortcuts}}
<li><a href {{action "keyboardShortcuts"}} class="keyboard-shortcuts-link">{{i18n 'keyboard_shortcuts_help.title'}}</a></li>
{{/if}}
<li>
<a href={{faqUrl}} class="faq-link">{{i18n 'faq'}}</a>
{{#link-to "adminFlags" class="flagged-posts-link"}}
{{fa-icon "flag"}} {{i18n 'flags_title'}}
{{#if currentUser.site_flagged_posts_count}}
<span title={{i18n 'notifications.total_flagged'}} class='badge-notification flagged-posts'>{{currentUser.site_flagged_posts_count}}</span>
{{/if}}
{{/link-to}}
</li>
{{/if}}
<li>
{{#link-to "discovery.latest" class="latest-topics-link"}}
{{i18n 'filters.latest.title.zero'}}
{{/link-to}}
</li>
{{#if siteSettings.enable_badges}}
<li>
{{#link-to 'badges' class="badge-link"}}{{i18n 'badges.title'}}{{/link-to}}
</li>
<li>
{{#link-to 'about' class="about-link"}}{{i18n 'about.simple_title'}}{{/link-to}}
</li>
{{#if showMobileToggle}}
<li><a href class="mobile-toggle-link" {{action "toggleMobileView"}}>{{boundI18n mobileViewLinkTextKey}}</a></li>
{{/if}}
{{plugin-outlet "site-map-links-last"}}
</ul>
{{#if categories}}
<ul class="category-links clearfix">
<li class="heading" title={{i18n 'filters.categories.help'}}>
{{#link-to "discovery.categories" class="categories-link"}}{{i18n 'filters.categories.title'}}{{/link-to}}
</li>
{{#each categories as |c|}}
{{hamburger-category category=c}}
{{/each}}
</ul>
{{/if}}
</div>
{{/if}}
{{#if siteSettings.enable_user_directory}}
<li>{{#link-to 'users' class="user-directory-link"}}{{i18n "directory.title"}}{{/link-to}}</li>
{{/if}}
{{#if currentUser.show_queued_posts}}
<li>
{{#link-to 'queued-posts'}}
{{i18n "queue.title"}}
{{#if currentUser.post_queue_new_count}}
<span class='badge-notification flagged-posts'>{{currentUser.post_queue_new_count}}</span>
{{/if}}
{{/link-to}}
</li>
{{/if}}
{{plugin-outlet "site-map-links"}}
{{#if showKeyboardShortcuts}}
<li><a href {{action "keyboardShortcuts"}} class="keyboard-shortcuts-link">{{i18n 'keyboard_shortcuts_help.title'}}</a></li>
{{/if}}
<li>
<a href={{faqUrl}} class="faq-link">{{i18n 'faq'}}</a>
</li>
<li>
{{#link-to 'about' class="about-link"}}{{i18n 'about.simple_title'}}{{/link-to}}
</li>
{{#if showMobileToggle}}
<li><a href class="mobile-toggle-link" {{action "toggleMobileView"}}>{{boundI18n mobileViewLinkTextKey}}</a></li>
{{/if}}
{{plugin-outlet "site-map-links-last"}}
</ul>
{{#if categories}}
<ul class="category-links clearfix">
<li class="heading" title={{i18n 'filters.categories.help'}}>
{{#link-to "discovery.categories" class="categories-link"}}{{i18n 'filters.categories.title'}}{{/link-to}}
</li>
{{#each categories as |c|}}
{{hamburger-category category=c}}
{{/each}}
</ul>
{{/if}}
{{/menu-panel}}

View File

@ -0,0 +1,10 @@
{{#if visible}}
{{#if showClose}}
<div class='panel-header clearfix'>
<a href {{action "close"}} class='close-panel'>{{fa-icon 'times'}}</a>
</div>
{{/if}}
<div class='panel-body' style={{bodyStyle}}>
{{yield}}
</div>
{{/if}}

View File

@ -1,6 +1,5 @@
<div class='wrap'>
<div class='contents clearfix'>
{{home-logo minimized=showExtraInfo}}
<div class='panel clearfix'>
@ -52,7 +51,7 @@
{{fa-icon "bars"}}
</a>
{{else}}
<a {{action "toggleHamburgerMenu"}} class='icon' href
<a {{action "toggleHamburgerMenu"}} class='icon' href
title={{i18n 'hamburger_menu'}}
aria-label={{i18n 'hamburger_menu'}}
id="toggle-hamburger-menu">

View File

@ -1,20 +1,20 @@
#hamburger-menu.slide-in {
.menu-panel.slide-in {
position: fixed;
right: 0;
top: 0;
height: 100%;
.hamburger-body {
.panel-body {
position: absolute;
top: 40px;
bottom: 37px;
}
}
#hamburger-menu.drop-down {
position: absolute;
.menu-panel.drop-down {
position: fixed;
}
#hamburger-menu {
.menu-panel {
border: 1px solid dark-light-diff($primary, $secondary, 90%, -60%);
box-shadow: 0 2px 2px rgba(0,0,0, .25);
background-color: $secondary;
@ -23,7 +23,7 @@
padding: 0.5em 0.5em 0.5em 0.5em;
width: 300px;
.close-hamburger {
.close-panel {
float: right;
color: dark-light-choose(scale-color($header_primary, $lightness: 50%), $header_primary);
font-size: 1.5em;
@ -33,19 +33,24 @@
z-index: 9999;
}
.hamburger-header {
.panel-header {
position: absolute;
right: 20px;
}
ul {
list-style: none;
margin: 0;
padding: 0;
}
.panel-body {
overflow-y: auto;
overflow-x: hidden;
}
}
.hamburger-panel {
ul.location-links li, li.heading {
a {
padding: 0.5em;
@ -76,10 +81,5 @@
font-weight: normal;
font-size: 11px;
}
.hamburger-body {
overflow-y: auto;
overflow-x: hidden;
}
}

View File

@ -17,7 +17,7 @@
@import "desktop/user";
@import "desktop/history";
@import "desktop/queued-posts";
@import "desktop/hamburger";
@import "desktop/menu-panel";
/* These files doesn't actually exist, they are injected by DiscourseSassImporter. */

View File

@ -6,8 +6,8 @@ test("Menu Items", (assert) => {
visit("/");
click("#toggle-hamburger-menu");
andThen(() => {
assert.ok(exists("#hamburger-menu .admin-link"));
assert.ok(exists("#hamburger-menu .flagged-posts-link"));
assert.ok(exists("#hamburger-menu .flagged-posts.badge-notification"), "it displays flag notifications");
assert.ok(exists(".hamburger-panel .admin-link"));
assert.ok(exists(".hamburger-panel .flagged-posts-link"));
assert.ok(exists(".hamburger-panel .flagged-posts.badge-notification"), "it displays flag notifications");
});
});

View File

@ -2,38 +2,20 @@ import { acceptance } from "helpers/qunit-helpers";
acceptance("Hamburger Menu");
test("Toggle Menu", (assert) => {
visit("/");
andThen(() => {
assert.ok(exists("#hamburger-menu.hidden"), "hidden by default");
});
click("#toggle-hamburger-menu");
andThen(() => {
assert.ok(!exists("#hamburger-menu.hidden"), "a click makes it appear");
});
click('#main-outlet')
andThen(() => {
assert.ok(exists("#hamburger-menu.hidden"), "clicking the body hides the menu");
});
});
test("Menu Items", (assert) => {
visit("/");
click("#toggle-hamburger-menu");
andThen(() => {
assert.ok(!exists("#hamburger-menu .admin-link"), 'does not have admin link');
assert.ok(!exists("#hamburger-menu .flagged-posts-link"), 'does not have flagged posts link');
assert.ok(!exists(".hamburger-panel .admin-link"), 'does not have admin link');
assert.ok(!exists(".hamburger-panel .flagged-posts-link"), 'does not have flagged posts link');
assert.ok(exists("#hamburger-menu .latest-topics-link"), 'last link to latest');
assert.ok(exists("#hamburger-menu .badge-link"), 'has link to badges');
assert.ok(exists("#hamburger-menu .user-directory-link"), 'has user directory link');
assert.ok(exists("#hamburger-menu .faq-link"), 'has faq link');
assert.ok(exists("#hamburger-menu .about-link"), 'has about link');
assert.ok(exists("#hamburger-menu .categories-link"), 'has categories link');
assert.ok(exists(".hamburger-panel .latest-topics-link"), 'last link to latest');
assert.ok(exists(".hamburger-panel .badge-link"), 'has link to badges');
assert.ok(exists(".hamburger-panel .user-directory-link"), 'has user directory link');
assert.ok(exists(".hamburger-panel .faq-link"), 'has faq link');
assert.ok(exists(".hamburger-panel .about-link"), 'has about link');
assert.ok(exists(".hamburger-panel .categories-link"), 'has categories link');
assert.ok(exists('#hamburger-menu .category-link'), 'has at least one category');
assert.ok(exists('.hamburger-panel .category-link'), 'has at least one category');
});
});

View File

@ -0,0 +1,80 @@
import componentTest from 'helpers/component-test';
moduleForComponent('menu-panel', {integration: true});
componentTest('as a dropdown', {
template: `
<div id='outside-area'>click me</div>
<div class='menu-selected'></div>
{{#menu-panel visible=panelVisible markActive=".menu-selected" force="drop-down"}}
Some content
{{/menu-panel}}
`,
setup() {
this.set('panelVisible', false);
},
test(assert) {
assert.ok(exists(".menu-panel.hidden"), "hidden by default");
assert.ok(!exists(".menu-selected.active"), "does not mark anything as active");
this.set('panelVisible', true);
andThen(() => {
assert.ok(!exists('.menu-panel .close-panel'), "the close X is not shown");
assert.ok(!exists(".menu-panel.hidden"), "toggling visible makes it appear");
assert.ok(exists(".menu-selected.active"), "marks the panel as active");
});
click('#outside-area')
andThen(() => {
assert.ok(exists(".menu-panel.hidden"), "clicking the body hides the menu");
assert.ok(!exists(".menu-selected.active"), "removes the active class");
assert.equal(this.get('panelVisible'), false, 'it updates the bound variable');
});
}
});
componentTest('as a slide-in', {
template: `
<div id='outside-area'>click me</div>
<div class='menu-selected'></div>
{{#menu-panel visible=panelVisible markActive=".menu-selected" force="slide-in"}}
Some content
{{/menu-panel}}
`,
setup() {
this.set('panelVisible', false);
},
test(assert) {
assert.ok(exists(".menu-panel.hidden"), "hidden by default");
assert.ok(!exists(".menu-selected.active"), "does not mark anything as active");
this.set('panelVisible', true);
andThen(() => {
assert.ok(!exists(".menu-panel.hidden"), "toggling visible makes it appear");
assert.ok(!exists(".menu-selected.active"), "slide ins don't mark as active");
});
click('#outside-area')
andThen(() => {
assert.ok(exists(".menu-panel.hidden"), "clicking the body hides the menu");
assert.equal(this.get('panelVisible'), false, 'it updates the bound variable');
this.set('panelVisible', true);
});
andThen(() => {
assert.ok(exists('.menu-panel .close-panel'), "the close X is shown");
});
click('.close-panel');
andThen(() => {
assert.ok(exists(".menu-panel.hidden"), "clicking the close button closes it");
assert.equal(this.get('panelVisible'), false, 'it updates the bound variable');
});
}
});

View File

@ -1,3 +1,4 @@
import AppEvents from 'discourse/lib/app-events';
import createStore from 'helpers/create-store';
export default function(name, opts) {
@ -8,8 +9,12 @@ export default function(name, opts) {
const store = createStore();
opts.setup.call(this, store);
}
const appEvents = AppEvents.create();
this.container.register('site-settings:main', Discourse.SiteSettings, { instantiate: false });
this.container.register('app-events:main', appEvents, { instantiate: false });
this.container.injection('component', 'siteSettings', 'site-settings:main');
this.container.injection('component', 'appEvents', 'app-events:main');
andThen(() => {
this.render(opts.template);