From 96d3fedde381728edf01169c52b07b67c8f384a2 Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Sun, 15 Jan 2017 13:07:05 +0100 Subject: [PATCH] Smart toolbar that scales down to a button and popup menu on small screen --- skins/elastic/README | 12 ----- skins/elastic/styles.css | 30 +++++++++-- skins/elastic/ui.js | 104 +++++++++++++++++++++++++++++---------- 3 files changed, 104 insertions(+), 42 deletions(-) diff --git a/skins/elastic/README b/skins/elastic/README index 0dfed3f82..21e73c48e 100644 --- a/skins/elastic/README +++ b/skins/elastic/README @@ -37,17 +37,5 @@ INSTALLATION: IDEAS/TODOs: ------------ - Planned use of a css framework, e.g. semantic-ui, bootstrap (to be decided) -- Minimum supported screen width: 320px (or we should support 240px?) -- As we use flexbox, we develop on most recent Chrome/Firefox, we'll - see later what we can do for older browser versions -- use Less - build custom semantic-ui style/js, we don't really want 800kB (already minified) file - use semantic-ui/loader when loading content to content-frames (on phones only)? -- make scrollbars pretty (impossible in Firefox?) -- check supported browser versions, looks like flexbox is not supported on Android <= 4, - how old versions we should support? -- popups/modal dialogs -- consider using a dialog popup for Contacts import instead of a separate page. Actually consider - this a UI rule: no full page for functionality that can be in a dialog. - Note: This is for consistency reasons, mail import uses dialog, calendar events import too. - Note: the same rule would apply to contacts advanced search. diff --git a/skins/elastic/styles.css b/skins/elastic/styles.css index d41fb6101..6f60f1f37 100644 --- a/skins/elastic/styles.css +++ b/skins/elastic/styles.css @@ -106,6 +106,7 @@ body > #layout > div > .header > a.menu-button { } @media screen and (max-width: 768px) { /* tablet */ + #layout > div > .header > .toolbar:not(.searchbar), body > #layout > div:not(.selected) { display: none; } @@ -115,6 +116,7 @@ body > #layout > div > .header > a.menu-button { body > #layout > div.list { max-width: 100%; } + body > #layout > div > .header > a.menu-button, body > #layout > div > .header > .buttons { display: block; float: right; @@ -207,6 +209,9 @@ body { border-top: 1px solid #ddd; } + + + /*** Login form ***/ .task-login #content { @@ -392,6 +397,10 @@ ul.listing li input[type=checkbox] { width: 1.2em; } +.toolbar a.button span.inner { + display: none; +} + .searchbar { display: table; text-align: right; @@ -457,6 +466,21 @@ ul.listing li input[type=checkbox] { } } +@media screen and (min-width: 769px) { + ul.toolbar { + margin: 0; +} + ul.toolbar > li { + display: inline-block; + } +} + +@media screen and (max-width: 768px) { + ul.toolbar a.button .inner { + display: inline; + } +} + @media screen and (max-width: 480px) { #messagestack { left: 0.5em; @@ -464,8 +488,8 @@ ul.listing li input[type=checkbox] { } } -/* font-icons */ +/* font-icons */ button.ui.button.icon:before, .toolbar a.button:before, @@ -508,10 +532,6 @@ button.ui.button.icon:before { float: initial; } -.toolbar a.button span.inner { - display: none; -} - .toolbar a.button { padding: 0 0.3em; } diff --git a/skins/elastic/ui.js b/skins/elastic/ui.js index 8b41f59fe..66f8424a7 100644 --- a/skins/elastic/ui.js +++ b/skins/elastic/ui.js @@ -101,28 +101,7 @@ function rcube_elastic_ui() }); // Initialize menu dropdowns - $('*[data-popup]').each(function() { - var popup_id = $(this).data('popup'); - - $('#' + popup_id).data('button', this); - - $(this).attr({ - 'aria-haspopup': 'true', - 'aria-expanded': 'false', - 'aria-owns': popup_id + '-menu' - }) - .popup({ - popup: '#' + popup_id, - exclusive: true, - on: 'click', - position: $(this).data('popup-pos') || 'bottom left', - lastResort: true - }); - - // TODO: Set aria attributes on menu show/hide - // TODO: Set popup height so it is less that window height - }); - $('.ui.popup').attr('aria-hidden', 'true'); + $('*[data-popup]').each(function() { popup_init(this); }); // close popups on click in an iframe on the page var close_all_popups = function() { @@ -134,11 +113,9 @@ function rcube_elastic_ui() rcube_webmail.set_iframe_events({mousedown: close_all_popups}); // Move form buttons from the content frame into the frame header (on parent window) - // TODO: There really should be only one button in most forms, Cancel buttons - // should not be used at all, other buttons will need to be verified // TODO: Active button state var form_buttons = []; - $('.formbuttons > .primary').each(function() { + $('.formbuttons').children(':not(.cancel)').each(function() { var target = this, button = $(this).clone(); form_buttons.push( @@ -183,6 +160,9 @@ function rcube_elastic_ui() // Initialize search forms (in list headers) $('.header > .searchbar').each(function() { searchbar_init(this); }); + // Initialize responsive toolbars + toolbar_init(); + // Make login form pretty if (rcmail.env.task == 'login') { var inputs = [], @@ -287,6 +267,8 @@ function rcube_elastic_ui() buttons.back_list.show(); } + $('.header > ul.toolbar', layout.content).addClass('hidden ui popup'); + $.each(content_buttons, function() { $(this).hide(); }); }; @@ -307,6 +289,7 @@ function rcube_elastic_ui() $(layout.menu).css('display', 'flex'); buttons.back_list.hide(); $.each(content_buttons, function() { $(this).show(); }); + $('ul.toolbar.ui.popup').removeClass('hidden ui popup'); }; function screen_resize_wide() @@ -314,6 +297,7 @@ function rcube_elastic_ui() $.each(layout, function(name, item) { $(item).css('display', 'flex'); }); buttons.back_list.hide(); $.each(content_buttons, function() { $(this).show(); }); + $('ul.toolbar.ui.popup').removeClass('hidden ui popup'); }; function show_sidebar() @@ -449,6 +433,76 @@ function rcube_elastic_ui() } }); }; + + /** + * Converts toolbar menu into popup-menu for small screens + */ + function toolbar_init() + { + if (env.got_smart_toolbar) { + return; + } + + env.got_smart_toolbar = true; + + // TODO: if the toolbar contains "global or list only" buttons + // another popup menu with these options should be created + // on the list (or sidebar if there's no list element). + // TODO: spacer item + // TODO: dropbutton item + // TODO: a way to inject buttons to the menu from content iframe + + var items = []; + + // convert toolbar to a popup list + $('.header > .toolbar', layout.content).each(function() { + var toolbar = $(this); + + toolbar.children().each(function() { + var button = $(this).detach(); + items.push($('
  • ').append(button)); + }); + }); + + // append the new toolbar and menu button + if (items.length) { + var menu_button = $('') + .html('') + .data('popup', 'toolbar-menu') + .data('popup-pos', 'bottom right'); + + $(layout.content).children('.header') + // TODO: copy original toolbar attributes (class, role, aria-*) + .append($('
      ').attr({'class': 'toolbar ui popup', id: 'toolbar-menu'}).append(items)) + .append(menu_button); + + popup_init(menu_button); + } + } + + function popup_init(item) + { + var popup_id = $(item).data('popup'); + + $('#' + popup_id).data('button', item); + + $(item).attr({ + 'aria-haspopup': 'true', + 'aria-expanded': 'false', + 'aria-owns': popup_id + '-menu' + }) + .popup({ + popup: '#' + popup_id, + exclusive: true, + on: 'click', + position: $(item).data('popup-pos') || 'bottom left', + lastResort: true + }); + + // TODO: Set aria attributes on menu show/hide + // TODO: Set popup height so it is less that window height + $('#' + popup_id).attr('aria-hidden', 'true'); + } } var UI = new rcube_elastic_ui();