Responsive design baby!

Mobile responsive design with a very native feel, all in pure CSS (no
templating differences between versions — even though some things are
in very different positions.)

I’ve been working on this whole thing in my head for a while now,
planning out how certain components will be laid out on the mobile
version, and how to reason about them in the templates so that a
substantially different layout can still be achieved by only using CSS.
Today I finally wrote the CSS and it’s come together pretty damn
perfectly.

Still to come:
- Swiping left or right on discussions to reveal controls
- Tablet version
This commit is contained in:
Toby Zerner 2015-03-03 20:30:52 +10:30
parent fb6080e488
commit 595364f419
35 changed files with 1568 additions and 875 deletions

View File

@ -5,7 +5,7 @@ import Ember from 'ember';
*/
export default Ember.Component.extend({
classNames: ['back-button'],
classNameBindings: ['active'],
classNameBindings: ['active', 'className'],
active: Ember.computed.or('target.paneIsShowing', 'target.paneIsPinned'),
@ -25,6 +25,10 @@ export default Ember.Component.extend({
togglePinned: function() {
this.get('target').send('togglePinned');
},
toggleDrawer: function() {
this.sendAction('toggleDrawer');
}
}
});

View File

@ -28,8 +28,8 @@ export default Ember.Component.extend({
setTimeout(function() { component.$('.permalink').select(); }, 1);
});
// Prevent clicking on the dropdown menu from closing it.
this.$('.dropdown-menu').click(function(e) {
// Prevent clicking on the input from closing it.
this.$('.permalink').click(function(e) {
e.stopPropagation();
});
}

View File

@ -8,7 +8,7 @@ var $ = Ember.$;
*/
export default Ember.Component.extend({
layoutName: 'components/discussion/stream-scrubber',
classNames: ['scrubber', 'stream-scrubber'],
classNames: ['stream-scrubber', 'dropdown'],
classNameBindings: ['disabled'],
// The stream-content component to which this scrubber is linked.

View File

@ -7,7 +7,7 @@ import Ember from 'ember';
export default Ember.Component.extend({
layoutName: 'components/ui/dropdown-select',
classNames: ['dropdown', 'dropdown-select', 'btn-group'],
classNameBindings: ['itemCountClass', 'class'],
classNameBindings: ['itemCountClass', 'className'],
buttonClass: 'btn btn-default',
menuClass: '',

View File

@ -14,7 +14,10 @@ export default Ember.Component.extend({
return [];
}
items.forEach(function(item) {
item.reopenClass({isListItem: item.proto().tagName === 'li'});
item.reopenClass({
isListItem: item.proto().tagName === 'li',
listItemClass: item.proto().listItemClass
});
});
return items;
})

View File

@ -22,7 +22,7 @@ export default Ember.Component.extend(HasItemLists, {
},
populateControls: function(items) {
this.addActionItem(items, 'submit', this.get('submitLabel')).reopen({className: 'btn btn-primary'});
this.addActionItem(items, 'submit', this.get('submitLabel'), 'check').reopen({className: 'btn btn-primary', listItemClass: 'primary-control'});
},
actions: {

View File

@ -1,7 +1,6 @@
import Ember from 'ember';
export default Ember.Controller.extend({
// The title of the forum.
// TODO: Preload this value in the index.html payload from Laravel config.
forumTitle: 'Flarum Demo Forum',
@ -18,6 +17,9 @@ export default Ember.Controller.extend({
actions: {
search: function(query) {
this.transitionToRoute('index', {queryParams: {searchQuery: query, sort: query ? 'relevance' : 'recent'}});
},
toggleDrawer: function() {
this.toggleProperty('drawerShowing');
}
}
});

View File

@ -120,6 +120,11 @@ export default Ember.Route.extend({
.set('paned', true)
.set('paneShowing', false);
this.controllerFor('composer').send('minimize');
var application = this.controllerFor('application');
if (!application.get('backButtonTarget')) {
application.set('backButtonTarget', this.controllerFor('index'));
}
}
}
});

View File

@ -18,6 +18,11 @@
// Finally, with our vendor CSS loaded, we can import Flarum-specific stuff.
@import "@{flarum-base}components.less";
@import "@{flarum-base}buttons.less";
@import "@{flarum-base}dropdowns.less";
@import "@{flarum-base}avatars.less";
@import "@{flarum-base}forms.less";
@import "@{flarum-base}hero.less";
@import "@{flarum-base}alerts.less";
@import "@{flarum-base}modals.less";
@import "@{flarum-base}layout.less";

View File

@ -35,6 +35,15 @@
@fl-body-control-bg: @fl-body-secondary-color;
@fl-body-control-color: @fl-body-muted-color;
// ---------------------------------
// DRAWER
@fl-drawer-bg: @fl-body-primary-color;
@fl-drawer-color: #fff;
@fl-drawer-muted-color: fade(#fff, 50%);
@fl-drawer-control-bg: fade(#000, 10%);
@fl-drawer-control-color: #fff;
// ---------------------------------
// HEADER
@ -50,16 +59,22 @@
@fl-body-hero-color: #fff;
}
.define-hdr-variables(true) {
@fl-hdr-bg: @fl-body-primary-color;
@fl-hdr-color: #fff;
@fl-hdr-muted-color: fade(#fff, 50%);
@fl-hdr-control-bg: fade(#000, 10%);
@fl-hdr-control-color: #fff;
@fl-hdr-bg: @fl-drawer-bg;
@fl-hdr-color: @fl-drawer-color;
@fl-hdr-muted-color: @fl-drawer-muted-color;
@fl-hdr-control-bg: @fl-drawer-control-bg;
@fl-hdr-control-color: @fl-drawer-control-color;
@fl-body-hero-bg: @fl-body-control-bg;
@fl-body-hero-color: @fl-body-control-color;
}
// ---------------------------------
// LAYOUT
@drawer-width: 270px;
@index-pane-width: 400px;
// ---------------------------------
// BOOTSTRAP
@ -88,3 +103,11 @@
@zindex-alerts: @zindex-modal + 10;
@link-hover-color: @link-color;
// ---------------------------------
// MEDIA SHORTCODES
@phone: ~"(max-width: @{screen-xs-max})";
@tablet: ~"(min-width: @{screen-sm-min}) and (max-width: @{screen-sm-max})";
@desktop: ~"(min-width: @{screen-md-min}) and (max-width: @{screen-md-max})";
@desktop-hd: ~"(min-width: @{screen-lg-min})";

View File

@ -0,0 +1,24 @@
.avatar-size(@size) {
width: @size;
height: @size;
border-radius: @size / 2;
font-size: @size / 2;
line-height: @size;
}
.avatar {
display: inline-block;
color: @fl-body-bg;
font-weight: 300;
text-align: center;
vertical-align: top;
background-color: @fl-body-control-bg;
.avatar-size(48px);
& img {
display: inline-block;
width: 100%;
height: 100%;
border-radius: 100%;
vertical-align: top;
}
}

View File

@ -0,0 +1,85 @@
.btn {
border: 0;
.box-shadow(none);
line-height: 20px;
& .fa {
font-size: 14px;
}
}
.btn-group .btn + .btn {
margin-left: 1px;
}
.btn-icon {
padding-left: 9px;
padding-right: 9px;
}
.btn-link {
color: @fl-body-muted-color;
&:hover,
&:focus {
text-decoration: none;
}
}
.btn-primary {
font-weight: bold;
& .icon-glyph {
display: none;
}
}
.btn-user {
& .avatar {
margin: -2px 5px -2px -5px;
.avatar-size(24px);
}
}
.btn-more {
padding: 1px 3px;
border-radius: 2px;
line-height: 1;
}
// Redefine Bootstrap's mixin to make some general changes
.button-variant(@color; @background; @border) {
&:hover,
&:focus,
&.focus,
&:active,
&.active,
.open > .dropdown-toggle& {
background-color: darken(@background, 5%);
}
&.active {
.box-shadow(none);
}
}
// Little round icon buttons
.btn-icon.btn-sm {
border-radius: 12px;
height: 24px;
width: 24px;
text-align: center;
padding: 3px 0;
& .label, & .icon-caret {
display: none;
}
& .fa-ellipsis-v {
font-size: 17px;
vertical-align: middle;
}
}
// Buttons that blend into the background
.btn-naked {
background: transparent;
&:hover {
background: @fl-body-control-bg;
}
}
.btn-rounded {
border-radius: 18px;
}

View File

@ -1,247 +0,0 @@
// ------------------------------------
// Buttons
.btn {
border: 0;
.box-shadow(none);
line-height: 20px;
& .fa {
font-size: 14px;
}
}
.btn-group .btn + .btn {
margin-left: 1px;
}
.btn-icon {
padding-left: 9px;
padding-right: 9px;
}
.btn-link {
color: @fl-body-muted-color;
&:hover,
&:focus {
text-decoration: none;
}
}
.btn-primary {
font-weight: bold;
& .icon-glyph {
display: none;
}
}
.btn-user {
& .avatar {
margin: -2px 5px -2px -5px;
.avatar-size(24px);
}
}
.btn-more {
padding: 1px 3px;
border-radius: 2px;
line-height: 1;
}
// Redefine Bootstrap's mixin to make some general changes
.button-variant(@color; @background; @border) {
&:hover,
&:focus,
&.focus,
&:active,
&.active,
.open > .dropdown-toggle& {
background-color: darken(@background, 5%);
}
&.active {
.box-shadow(none);
}
}
// Little round icon buttons
.btn-icon.btn-sm {
border-radius: 12px;
height: 24px;
width: 24px;
text-align: center;
padding: 3px 0;
& .label, & .icon-caret {
display: none;
}
& .fa-ellipsis-v {
font-size: 17px;
vertical-align: middle;
}
}
// Buttons that blend into the background
.btn-naked {
background: transparent;
&:hover {
background: @fl-body-control-bg;
}
}
.btn-rounded {
border-radius: 18px;
}
// ------------------------------------
// Form Controls
.form-group {
margin-bottom: 12px;
}
.form-control {
.box-shadow(none);
&:focus,
&.focus {
background-color: #fff;
color: @fl-body-color;
.box-shadow(none);
}
}
// Search inputs
// @todo Extract some of this into header-specific definitions
.search-input {
margin-right: 10px;
&:before {
.fa();
content: @fa-var-search;
float: left;
margin-right: -36px;
width: 36px;
font-size: 14px;
text-align: center;
color: @fl-body-muted-color;
position: relative;
padding: @padding-base-vertical - 1 0;
line-height: @line-height-base;
pointer-events: none;
}
}
.search-input .form-control {
float: left;
width: 225px;
padding-left: 36px;
padding-right: 36px;
.transition(~"all 0.4s");
&:focus {
width: 400px;
}
}
.search-input .clear {
float: left;
margin-left: -36px;
vertical-align: top;
opacity: 0;
.rotate(-180deg);
.transition(~"transform 0.2s, opacity 0.2s");
}
.search-input.clearable .clear {
opacity: 1;
.rotate(0deg);
}
// Select inputs
.select-input {
display: inline-block;
vertical-align: middle;
}
.select-input select {
display: inline-block;
width: auto;
-webkit-appearance: none;
padding-right: @padding-base-horizontal + 16;
cursor: pointer;
}
.select-input .fa {
margin-left: -@padding-base-horizontal - 16;
pointer-events: none;
color: @fl-body-muted-color;
}
// ------------------------------------
// Dropdown Menus
.dropdown-menu {
border: 0;
padding: 8px 0;
margin-top: 7px;
.box-shadow(0 2px 6px @fl-shadow-color);
background: @fl-body-bg;
& > li > a {
padding: 8px 15px;
color: @fl-body-color;
&:hover, &:focus {
color: @fl-body-color;
background-color: @fl-body-control-bg;
}
& .fa {
margin-right: 5px;
font-size: 14px;
}
}
& .divider {
margin: 10px 0;
background-color: @fl-body-control-bg;
}
}
.dropdown-split.item-count-1 {
& .btn {
border-radius: @border-radius-base !important;
}
& .dropdown-toggle {
display: none;
}
}
// ------------------------------------
// Tooltips
.tooltip-inner {
padding: 5px 10px;
}
// ------------------------------------
// Loading Indicators
.loading-indicator {
position: relative;
color: @fl-body-primary-color;
}
.loading-indicator-block {
height: 100px;
}
// ------------------------------------
// Avatars
.avatar-size(@size) {
width: @size;
height: @size;
border-radius: @size / 2;
font-size: @size / 2;
line-height: @size;
}
.avatar {
display: inline-block;
color: @fl-body-bg;
font-weight: 300;
text-align: center;
vertical-align: top;
background-color: @fl-body-control-bg;
.avatar-size(48px);
& img {
display: inline-block;
width: 100%;
height: 100%;
border-radius: 100%;
vertical-align: top;
}
}

View File

@ -1,104 +1,24 @@
// ------------------------------------
// Composer
.composer-container {
position: fixed;
bottom: 0;
left: 0;
right: 0;
z-index: @zindex-composer;
pointer-events: none;
.transition(left 0.2s);
.with-pane & {
left: @index-pane-width;
}
}
.composer {
pointer-events: auto;
margin-left: -20px;
margin-right: 180px;
.box-shadow(0 2px 6px @fl-shadow-color);
border-radius: @border-radius-base @border-radius-base 0 0;
background: fade(@fl-body-bg, 95%);
transform: translateZ(0); // Fix for Chrome bug where a transparent white background is actually gray
position: relative;
height: 300px;
.transition(~"background 0.2s");
.index-index & {
margin-left: 205px;
margin-right: -20px;
}
&.active, &.fullscreen {
background: @fl-body-bg;
}
&.minimized {
height: 50px;
cursor: pointer;
}
&.fullscreen {
position: fixed;
left: 0;
top: 0;
bottom: 0;
right: 0;
margin: 0;
height: auto;
}
}
.composer-content {
padding: 20px 20px 15px;
.minimized & {
padding: 10px 20px;
}
.fullscreen & {
max-width: 900px;
margin: 0 auto;
padding: 30px;
}
}
.composer-handle {
height: 20px;
margin-bottom: -20px;
position: relative;
.minimized &, .fullscreen & {
display: none;
}
}
.composer-controls {
position: absolute;
right: 10px;
top: 10px;
list-style-type: none;
list-style: none;
padding: 0;
margin: 0;
}
.composer-header {
list-style: none;
padding: 0;
margin: 0;
& li {
display: inline-block;
}
.minimized & {
top: 7px;
}
}
.fa-minus.minimize {
vertical-align: -5px;
}
.composer-avatar {
float: left;
.avatar-size(64px);
.minimized & {
display: none;
}
}
.composer-body {
margin-left: 90px;
& h3 {
margin: 5px 0 10px;
margin: 0 0 10px;
line-height: 1.5em;
&, & input {
color: @fl-body-muted-color;
font-size: 16px;
@ -111,16 +31,183 @@
height: auto;
}
}
}
.composer-loading {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(255, 255, 255, 0.9);
opacity: 0;
pointer-events: none;
border-radius: @border-radius-base @border-radius-base 0 0;
.transition(opacity 0.2s);
.minimized & {
margin-left: 0;
&.active {
opacity: 1;
pointer-events: auto;
}
}
.composer-editor {
.minimized & {
visibility: hidden;
// On phones, show the composer as a fixed overlay that covers the whole
// screen. The controls are hidden (except for the 'x', which is the back-
// control), and the avatar hidden.
@media @phone {
.composer-open {
overflow: hidden;
}
.composer {
position: fixed;
bottom: 0;
left: 0;
right: 0;
z-index: @zindex-composer;
background: @fl-body-bg;
height: 100vh !important;
padding-top: 56px;
&:before {
content: " ";
.toolbar();
opacity: 0;
.transition(opacity 0.5s);
.visible& {
opacity: 1;
}
}
}
.composer-content {
padding: 15px;
}
.composer-controls {
& li:not(.back-control) {
display: none;
}
}
.composer-avatar {
display: none;
}
.composer-body {
& h3 input {
width: 100% !important;
}
}
}
// On larger screens, show the composer as a window at the bottom of the
// content area. We hide a lot of the content when the composer is minimized.
@media @tablet, @desktop, @desktop-hd {
.composer-container {
position: fixed;
bottom: 0;
left: 0;
right: 0;
z-index: @zindex-composer;
pointer-events: none;
.transition(left 0.2s);
.with-pane & {
left: @index-pane-width;
}
}
.composer {
margin-left: -20px;
margin-right: 180px;
border-radius: @border-radius-base @border-radius-base 0 0;
background: fade(@fl-body-bg, 95%);
transform: translateZ(0); // Fix for Chrome bug where a transparent white background is actually gray
position: relative;
height: 300px;
.transition(~"background 0.2s");
.index-index & {
margin-left: 205px;
margin-right: -20px;
}
&.active, &.fullscreen {
background: @fl-body-bg;
}
&.minimized {
height: 50px;
cursor: pointer;
}
&.fullscreen {
position: fixed;
left: 0;
top: 0;
bottom: 0;
right: 0;
margin: 0;
height: auto;
}
}
.composer-content {
padding: 20px 20px 15px;
.minimized & {
padding: 10px 20px;
}
.fullscreen & {
max-width: 900px;
margin: 0 auto;
padding: 30px;
}
}
.composer-handle {
height: 20px;
margin-bottom: -20px;
position: relative;
.minimized &, .fullscreen & {
display: none;
}
}
.composer-controls {
position: absolute;
right: 10px;
top: 10px;
& li {
display: inline-block;
}
.minimized & {
top: 7px;
}
}
.fa-minus.minimize {
vertical-align: -5px;
}
.composer-avatar {
float: left;
.avatar-size(64px);
.minimized & {
display: none;
}
}
.composer-body {
margin-left: 90px;
.minimized & {
margin-left: 0;
}
}
.composer-header h3 {
margin-top: 5px;
}
.composer-editor {
.minimized & {
visibility: hidden;
}
}
}
// ------------------------------------
// Text Editor
.text-editor {
& textarea {
border-radius: 0;
@ -145,20 +232,10 @@
}
}
.composer-loading {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(255, 255, 255, 0.9);
opacity: 0;
pointer-events: none;
border-radius: @border-radius-base @border-radius-base 0 0;
.transition(opacity 0.2s);
&.active {
opacity: 1;
pointer-events: auto;
// On phones, since one of the text editor controls will probably be the
// primary-control, we shouldn't hide it completely when it's "disabled".
@media @phone {
.text-editor-controls {
opacity: 0.5;
}
}

View File

@ -2,30 +2,38 @@
// Sidebar
.discussion-nav {
float: right;
&, & > ul {
width: 150px;
}
& > ul {
position: fixed;
margin: 30px 0 0;
padding: 0;
list-style-type: none;
margin: 0;
list-style: none;
}
}
& > li {
margin-bottom: 10px;
@media @tablet, @desktop, @desktop-hd {
.discussion-nav {
float: right;
&, & > ul {
width: 150px;
}
}
& .btn-group, & .btn {
width: 100%;
}
& .btn-group:not(.item-count-1) {
& .btn {
width: 77%;
& > ul {
position: fixed;
margin-top: 30px;
& > li {
margin-bottom: 10px;
}
}
& .dropdown-toggle {
width: 22%;
& .btn-group, & .btn {
width: 100%;
}
& .btn-group:not(.item-count-1) {
& .btn {
width: 77%;
}
& .dropdown-toggle {
width: 22%;
}
}
}
}
@ -35,7 +43,7 @@
.discussion-posts {
margin-top: 40px;
margin-right: 200px;
& .item {
margin-bottom: 40px;
}
@ -56,6 +64,7 @@
&:hover, &.loading, &.active {
padding: 50px 0;
&.up:before, &.down:after {
opacity: 1;
}
@ -91,30 +100,39 @@
}
}
@media @phone {
.gap {
margin-left: -15px;
margin-right: -15px;
border-left: 0;
border-right: 0;
}
}
@media @tablet @desktop @desktop-hd {
.discussion-posts {
margin-right: 200px;
}
}
// ------------------------------------
// Posts
.post {
padding-left: 90px;
padding-bottom: 1px;
transition: 0.2s box-shadow;
& .contextual-controls {
float: right;
margin: -2px 0 0 10px;
visibility: hidden;
}
&:hover .contextual-controls, & .contextual-controls.open {
visibility: visible;
margin-left: 10px;
}
}
.item.highlight .post {
border: 8px solid rgba(255, 255, 0, 0.2);
border-radius: 8px;
padding: 15px 15px 1px 105px;
margin: -23px -23px -8px -23px;
padding: 15px;
margin: -23px;
}
.post-header {
margin-bottom: 10px;
color: @fl-body-muted-color;
@ -123,39 +141,29 @@
list-style-type: none;
padding: 0;
margin: 0;
&, & a {
color: @fl-body-muted-color;
}
& > li {
display: inline;
margin-right: 10px;
}
&, & a {
color: @fl-body-muted-color;
}
}
& .post-user {
margin: 0;
display: inline;
font-weight: bold;
font-size: 15px;
&, & a {
color: @fl-body-heading-color;
}
}
& .avatar {
margin-left: -90px;
float: left;
.avatar-size(64px);
}
}
.post-body {
font-size: 14px;
line-height: 1.6;
}
.post-icon {
float: left;
margin-left: -90px;
width: 64px;
text-align: right;
font-size: 22px;
line-height: 1.6em;
}
.post.is-hidden {
& .post-user, & .post-header > ul, & .post-header > ul a:not(.btn) {
@ -193,6 +201,9 @@
}
}
.post-icon {
float: left;
}
.post-activity {
&, & a {
color: @fl-body-muted-color;
@ -249,84 +260,146 @@
}
}
@media @phone {
.post-header {
& .avatar {
.avatar-size(32px);
vertical-align: middle;
margin-right: 5px;
}
}
.post-activity {
padding-left: 30px;
}
.post-icon {
font-size: 18px;
margin-left: -30px;
margin-top: 2px;
}
}
@media @tablet @desktop @desktop-hd {
.post {
padding-left: 90px;
& .contextual-controls {
visibility: hidden;
}
&:hover .contextual-controls, & .contextual-controls.open {
visibility: visible;
}
}
.item.highlight .post {
padding-left: 90px + 15px;
}
.post-header {
& .avatar {
margin-left: -90px;
float: left;
.avatar-size(64px);
}
}
.post-icon {
text-align: right;
margin-left: -90px;
width: 64px;
font-size: 22px;
}
}
// ------------------------------------
// Scrubber
@media (min-width: @screen-md-min) {
.stream-scrubber {
margin: 30px 0 0 0;
}
.scrubber {
& a {
margin-left: -5px;
color: @fl-body-muted-color;
& .fa {
font-size: 14px;
margin-right: 2px;
}
&:hover, &:focus {
text-decoration: none;
color: @link-hover-color;
}
.scrubber {
& a {
margin-left: -5px;
color: @fl-body-muted-color;
& .fa {
font-size: 14px;
margin-right: 2px;
}
}
.scrubber-scrollbar {
margin: 8px 0 8px 3px;
height: 300px;
min-height: 50px; // JavaScript sets a max-height
position: relative;
}
.scrubber-before, .scrubber-after {
border-left: 1px solid @fl-body-secondary-color;
}
.scrubber-slider {
position: relative;
width: 100%;
padding: 5px 0;
}
.scrubber-handle {
height: 100%;
width: 5px;
background: @fl-body-primary-color;
border-radius: 4px;
float: left;
margin-left: -2px;
transition: background 0.2s;
.disabled & {
background: @fl-body-secondary-color;
&:hover, &:focus {
text-decoration: none;
color: @link-hover-color;
}
}
.scrubber-info {
height: (2em * @line-height-base);
margin-top: (-1em * @line-height-base);
position: absolute;
top: 50%;
width: 100%;
left: 15px;
& strong {
display: block;
}
& .description {
color: @fl-body-muted-color;
}
}
.scrubber-highlights {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
list-style-type: none;
pointer-events: none;
}
.scrubber-highlights li {
position: absolute;
right: -6px;
background: #fc0;
height: 8px;
width: 13px;
border-radius: 4px;
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.15), inset 0 0 0 1px rgba(255, 255, 255, 0.5);
opacity: 0.99;
}
}
.scrubber-scrollbar {
margin: 8px 0 8px 3px;
height: 300px;
min-height: 50px; // JavaScript sets a max-height
position: relative;
}
.scrubber-before, .scrubber-after {
border-left: 1px solid @fl-body-secondary-color;
}
.scrubber-slider {
position: relative;
width: 100%;
padding: 5px 0;
}
.scrubber-handle {
height: 100%;
width: 5px;
background: @fl-body-primary-color;
border-radius: 4px;
float: left;
margin-left: -2px;
transition: background 0.2s;
.disabled & {
background: @fl-body-secondary-color;
}
}
.scrubber-info {
height: (2em * @line-height-base);
margin-top: (-1em * @line-height-base);
position: absolute;
top: 50%;
width: 100%;
left: 15px;
& strong {
display: block;
}
& .description {
color: @fl-body-muted-color;
}
}
.scrubber-highlights {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
list-style-type: none;
pointer-events: none;
}
.scrubber-highlights li {
position: absolute;
right: -6px;
background: #fc0;
height: 8px;
width: 13px;
border-radius: 4px;
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.15), inset 0 0 0 1px rgba(255, 255, 255, 0.5);
opacity: 0.99;
}
@media @phone {
.stream-scrubber {
& .dropdown-toggle {
font-size: 14px;
}
& .dropdown-menu {
padding: 30px;
font-size: 14px;
}
}
}
@media @tablet, @desktop, @desktop-hd {
.stream-scrubber {
margin: 30px 0 0 0;
.expand-dropdown();
}
}

View File

@ -0,0 +1,112 @@
.dropdown-menu {
border: 0;
padding: 8px 0;
margin-top: 7px;
background: @fl-body-bg;
.box-shadow(0 2px 6px @fl-shadow-color);
& > li > a {
padding: 8px 15px;
color: @fl-body-color;
&:hover, &:focus {
color: @fl-body-color;
background-color: @fl-body-control-bg;
}
& .fa {
margin-right: 5px;
font-size: 14px;
}
}
& .divider {
margin: 10px 0;
background-color: @fl-body-control-bg;
}
}
.dropdown-split.item-count-1 {
& .btn {
border-radius: @border-radius-base !important;
}
& .dropdown-toggle {
display: none;
}
}
// PHONES
@media (max-width: @screen-xs-max) {
.dropdown-open {
overflow: hidden;
}
.dropdown-menu {
margin: 0;
position: fixed;
left: 0 !important;
right: 0 !important;
width: auto !important;
bottom: -100vh;
top: auto;
padding: 0;
z-index: @zindex-modal;
display: block;
max-height: 100vh;
border-radius: 0;
.box-shadow(none);
.transition(bottom 0.3s);
&:before {
content: " ";
position: absolute;
left: 0;
right: 0;
bottom: 100%;
height: 200vh;
z-index: -1;
background: fade(@fl-body-primary-color, 90%);
pointer-events: none;
opacity: 0;
.transition(opacity 0.3s);
}
& > li > a {
background: #fff;
font-size: 16px;
padding: 15px 20px;
& .fa {
font-size: 16px;
margin-right: 10px;
}
}
& .divider {
margin: 0;
}
.open & {
bottom: 0;
&:before {
opacity: 1;
pointer-events: auto;
}
}
}
}
// ------------------------------------
// Mixins
.expand-dropdown() {
& .dropdown-toggle {
display: none;
}
& .dropdown-menu {
display: block;
border: 0;
width: auto;
margin: 0;
padding: 0;
min-width: 0;
float: none;
position: static;
background: none;
.box-shadow(none);
}
}

View File

@ -0,0 +1,71 @@
.form-group {
margin-bottom: 12px;
}
.form-control {
.box-shadow(none);
&:focus,
&.focus {
background-color: #fff;
color: @fl-body-color;
.box-shadow(none);
}
}
// Search inputs
// @todo Extract some of this into header-specific definitions
.search-input {
overflow: hidden;
&:before {
.fa();
content: @fa-var-search;
float: left;
margin-right: -36px;
width: 36px;
font-size: 14px;
text-align: center;
color: @fl-body-muted-color;
position: relative;
padding: @padding-base-vertical - 1 0;
line-height: @line-height-base;
pointer-events: none;
}
& .form-control {
float: left;
width: 225px;
padding-left: 36px;
padding-right: 36px;
.transition(~"all 0.4s");
}
& .clear {
float: left;
margin-left: -36px;
vertical-align: top;
opacity: 0;
width: 36px !important;
.rotate(-180deg);
.transition(~"transform 0.2s, opacity 0.2s");
}
&.clearable .clear {
opacity: 1;
.rotate(0deg);
}
}
// Select inputs
.select-input {
display: inline-block;
vertical-align: middle;
}
.select-input select {
display: inline-block;
width: auto;
-webkit-appearance: none;
padding-right: @padding-base-horizontal + 16;
cursor: pointer;
}
.select-input .fa {
margin-left: -@padding-base-horizontal - 16;
pointer-events: none;
color: @fl-body-muted-color;
}

View File

@ -0,0 +1,42 @@
.hero {
background: @fl-body-hero-bg;
text-align: center;
padding: 20px 0;
&, & a, & .close {
color: @fl-body-hero-color;
}
& a, & .close {
opacity: 0.5;
}
& .close {
float: right;
margin-top: -10px;
}
& h2 {
margin: 0;
font-size: 16px;
font-weight: normal;
line-height: 1.5em;
}
& p {
margin: 5px 0 0;
}
}
@media @phone {
.hero {
& .close {
margin-right: -10px;
}
}
}
@media @tablet, @desktop, @desktop-hd {
.hero {
padding: 30px 0;
font-size: 14px;
& h2 {
font-size: 22px;
}
}
}

View File

@ -1,64 +1,66 @@
// ------------------------------------
// Sidebar
.index-nav {
float: left;
.index-nav > ul {
margin: 0;
padding: 0;
list-style: none;
}
&, & > ul {
width: 175px;
}
& > ul {
margin: 30px 0 0;
padding: 0;
list-style-type: none;
// On phones, the index sidebar will pretty much take care of itself. The
// navigation list is a .dropdown-select and will be shown as the title-
// control; the new discussion button is the primary-control. On anything
// larger than a phone, however, we need to affix the sidebar and expand the
// .dropdown-select into a plain list.
@media @tablet, @desktop, @desktop-hd {
.index-nav {
float: left;
&.affix {
top: 56px;
&, & > ul {
width: 175px;
}
& > li {
margin-bottom: 10px;
}
}
& .new-discussion {
display: block;
margin-bottom: 20px;
}
& > ul {
margin-top: 30px;
// Expand the dropdown-select component into a normal nav list
// @todo Extract this into a mixin as we'll need to do it elsewhere.
& .dropdown-select {
display: block;
& .dropdown-toggle {
display: none;
}
& .dropdown-menu {
display: block;
border: 0;
width: auto;
margin: 0;
padding: 0;
min-width: 0;
float: none;
position: static;
background: none;
.box-shadow(none);
& > li > a {
padding: 8px 0;
color: @fl-body-muted-color;
& .fa {
margin-right: 8px;
font-size: 15px;
}
&:hover {
background: none;
color: @link-hover-color;
}
&.affix {
top: 56px;
}
& > li.active > a {
background: none;
color: @fl-body-primary-color;
font-weight: bold;
& > li {
margin-bottom: 10px;
}
}
& .new-discussion {
display: block;
margin-bottom: 20px;
}
// Expand the dropdown-select component into a normal nav list
// @todo Extract this into a mixin as we'll probably need to do it elsewhere.
& .dropdown-select {
display: block;
.expand-dropdown();
& .dropdown-menu {
& > li > a {
padding: 8px 0;
color: @fl-body-muted-color;
&:hover {
background: none;
color: @link-hover-color;
}
& .fa {
margin-right: 8px;
font-size: 15px;
}
}
& > li.active > a {
background: none;
color: @fl-body-primary-color;
font-weight: bold;
}
}
}
}
@ -67,18 +69,12 @@
// ------------------------------------
// Results
.index-results {
margin-top: 30px;
margin-left: 225px;
& .loading-indicator {
height: 46px;
}
}
.index-toolbar {
margin-bottom: 15px;
}
.index-toolbar-view {
display: inline-block;
& .control-show {
margin-right: 10px;
}
@ -86,78 +82,98 @@
.index-toolbar-action {
float: right;
}
.index-results .loading-indicator {
height: 46px;
}
@media @phone {
.index-results {
margin-top: 15px;
}
}
@media @tablet, @desktop, @desktop-hd {
.index-results {
margin-top: 30px;
margin-left: 225px;
}
}
// ------------------------------------
// Discussions Pane
@index-pane-width: 400px;
.index-area {
left: -@index-pane-width;
width: 100%;
&.paned {
position: fixed;
z-index: @zindex-pane;
overflow: auto;
top: 56px;
bottom: 0;
width: @index-pane-width;
background: @fl-body-bg;
padding-bottom: 200px;
.box-shadow(2px 2px 6px -2px @fl-shadow-color);
.transition(left 0.2s);
&.showing, .with-pane & {
left: 0;
}
.with-pane & {
z-index: @zindex-composer - 1;
.transition(none);
}
& .container {
width: auto;
margin: 0;
padding: 0 !important;
}
& .index-results {
margin: 0;
}
& .hero, & .index-nav, & .index-toolbar {
display: none;
}
& .discussions-list > li {
margin: 0;
padding-left: 65px + 15px;
padding-right: 65px + 15px;
&.active {
background: @fl-body-control-bg;
}
}
& .discussion-summary {
& .title {
font-size: 14px;
}
& .count strong {
font-size: 18px;
}
}
}
.index-area.paned {
display: none;
}
// When the pane is pinned, move the other page content inwards
.global-main, .global-footer {
.with-pane & {
margin-left: @index-pane-width;
& .container {
max-width: 100%;
padding: 0 30px;
}
}
}
.global-header .container {
.with-pane & {
@media @tablet, @desktop, @desktop-hd {
.index-area {
left: -@index-pane-width;
width: 100%;
&.paned {
position: fixed;
z-index: @zindex-pane;
overflow: auto;
top: 56px;
bottom: 0;
width: @index-pane-width;
background: @fl-body-bg;
padding-bottom: 200px;
.box-shadow(2px 2px 6px -2px @fl-shadow-color);
.transition(left 0.2s);
&.showing, .with-pane & {
left: 0;
}
.with-pane & {
z-index: @zindex-composer - 1;
.transition(none);
}
& .container {
width: auto;
margin: 0;
padding: 0 !important;
}
& .index-results {
margin: 0;
}
& .hero, & .index-nav, & .index-toolbar {
display: none;
}
& .discussions-list > li {
margin: 0;
padding-left: 65px + 15px;
padding-right: 65px + 15px;
&.active {
background: @fl-body-control-bg;
}
}
& .discussion-summary {
& .title {
font-size: 14px;
}
& .count strong {
font-size: 18px;
}
}
}
}
// When the pane is pinned, move the other page content inwards
.global-main, .global-footer {
.with-pane & {
margin-left: @index-pane-width;
& .container {
max-width: 100%;
padding: 0 30px;
}
}
}
.global-header .container {
.with-pane & {
width: 100%;
}
}
}
@ -169,24 +185,38 @@
padding: 0;
list-style-type: none;
position: relative;
}
@media @phone {
.discussions-list > li {
padding-right: 45px;
& > li {
margin-right: -25px;
padding-right: 65px + 25px;
& .contextual-controls {
position: absolute;
right: 0;
top: 18px;
visibility: hidden;
}
&:hover .contextual-controls, & .contextual-controls.open {
visibility: visible;
display: none;
}
}
}
@media @tablet, @desktop, @desktop-hd {
.discussions-list {
& > li {
margin-right: -25px;
padding-right: 65px + 25px;
& .contextual-controls {
position: absolute;
right: 0;
top: 18px;
visibility: hidden;
}
&:hover .contextual-controls, & .contextual-controls.open {
visibility: visible;
}
}
}
}
.discussion-summary {
padding-left: 65px;
padding-right: 65px;
position: relative;
&, & a {
@ -195,25 +225,21 @@
}
& .author {
float: left;
margin-left: -65px;
margin-top: 18px;
}
& .main {
display: inline-block;
width: 100%;
padding: 20px 0;
margin-right: -65px;
&.active {
text-decoration: none;
}
&:hover .title {
text-decoration: underline;
}
}
&.active {
text-decoration: none;
}
&:hover .title {
text-decoration: underline;
}
& .title {
margin: 0 0 5px;
font-size: 15px;
line-height: 1.3;
}
&.unread .title {
@ -229,33 +255,94 @@
display: inline-block;
}
}
& .username {
font-weight: bold;
}
& .count {
float: right;
margin-top: 18px;
margin-right: -65px;
width: 60px;
text-align: center;
text-transform: uppercase;
color: @fl-body-muted-color;
font-size: 11px;
text-decoration: none;
& strong {
font-size: 20px;
display: block;
font-weight: 300;
}
.unread& {
cursor: pointer;
}
.unread&, .unread& strong {
color: @fl-body-heading-color;
font-weight: bold;
}
}
}
.load-more {
text-align: center;
margin-top: 10px;
}
@media @phone {
.discussion-summary {
padding-left: 45px;
padding-right: 45px;
& .author {
margin-left: -45px;
}
& .avatar {
.avatar-size(32px);
}
& .main {
margin-right: -45px;
}
& .title {
font-size: 14px;
}
& .count {
margin-right: -45px;
background: @fl-body-control-bg;
color: @fl-body-control-color;
border-radius: @border-radius-base;
font-size: 12px;
padding: 1px 6px;
.unread& {
background: @fl-body-primary-color;
color: #fff;
font-weight: bold;
}
& .label {
display: none;
}
}
}
}
@media @tablet, @desktop, @desktop-hd {
.discussion-summary {
padding-left: 65px;
padding-right: 65px;
& .author {
margin-left: -65px;
}
& .main {
margin-right: -65px;
}
& .title {
font-size: 15px;
}
& .count {
margin-right: -65px;
width: 60px;
text-align: center;
text-transform: uppercase;
color: @fl-body-muted-color;
font-size: 20px;
font-weight: 300;
& .label {
font-size: 11px;
display: block;
font-weight: normal;
}
.unread& {
color: @fl-body-heading-color;
font-weight: bold;
}
}
}
}

View File

@ -1,194 +1,426 @@
body {
background: @fl-body-bg;
color: @fl-body-color;
padding-top: 56px;
}
.container-narrow {
max-width: 500px;
margin: 0 auto;
}
.global-page {
overflow: hidden;
}
// ------------------------------------
// Header
// Page Toolbar
.global-header {
.toolbar() {
background: fade(@fl-hdr-bg, 98%);
transform: translateZ(0); // Fix for Chrome bug where a transparent white background is actually gray
padding: 10px;
height: 56px;
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: @zindex-navbar-fixed;
.clearfix();
.transition(box-shadow 0.2s);
border-bottom: 1px solid @fl-body-control-bg;
.transition(~"box-shadow 0.2s, left 0.2s");
}
// Fix a solid white box to the top of the viewport. This toolbar's contents
// will differ depending on the device: on phones it will be content
// controls, whereas on desktops it will be the header. We will overlay
// these things on top of it later.
.global-page:before {
content: " ";
.toolbar();
.scrolled & {
border-bottom: 0;
.box-shadow(0 2px 6px @fl-shadow-color);
}
& when (@fl-colored-hdr = true) {
&, & .btn-link {
color: @fl-hdr-control-color;
// PHONES: Push the toolbar to the right when the drawer is open.
@media @phone {
.drawer-open & {
left: @drawer-width;
}
& .form-control {
background: @fl-hdr-control-bg;
border: 0;
color: @fl-hdr-control-color;
.placeholder(@fl-hdr-control-color);
}
}
&:focus {
background: fadein(@fl-hdr-control-bg, 5%);
// PHONES: Somewhere on the page there will be a .back-button, a .primary-
// control, and a .title-control. We will position these on the left, right,
// and center of the header respectively.
@media @phone {
.primary-control, .title-control, .back-control {
position: fixed;
z-index: @zindex-navbar-fixed + 1;
top: 10px;
margin: 0;
& .btn {
float: none;
background: transparent !important;
.box-shadow(~"none !important");
&:active {
opacity: 0.5;
}
}
& .search-input:before {
color: @fl-hdr-control-color;
}
& .btn-default, & .btn-default:hover {
background: @fl-hdr-control-bg;
color: @fl-hdr-control-color;
}
& .btn-default.active, .open > .dropdown-toggle.btn-default {
background: fadein(@fl-hdr-control-bg, 5%);
}
& .btn-naked {
background: transparent;
}
}
}
.header-controls {
margin: 0;
padding: 0;
list-style-type: none;
&, & > li {
display: inline-block;
vertical-align: top;
}
}
.header-primary {
float: left;
& h1 {
display: inline-block;
vertical-align: top;
}
}
.header-title {
font-size: 18px;
font-weight: normal;
margin: 0;
line-height: 36px;
&, & a {
color: @fl-hdr-color;
}
}
.header-secondary {
float: right;
}
.primary-control {
width: auto;
right: 10px;
.transition(right 0.2s);
// Back button
// @todo Lots of !importants in here, could we be more specific?
.back-button {
float: left;
margin-right: 25px;
.drawer-open .global-page & {
right: -@drawer-width;
}
& .back {
z-index: 3 !important; // z-index of an active .btn-group .btn is 2
border-radius: @border-radius-base !important;
.transition(border-radius 0.2s);
}
& .pin {
opacity: 0;
margin-left: -36px !important;
.transition(~"opacity 0.2s, margin-left 0.2s");
&:not(.active) .fa {
.rotate(45deg);
& .dropdown-split {
& .btn, & .icon-caret {
display: none;
}
& .dropdown-toggle {
display: block;
}
}
}
&.active {
& .back {
border-radius: @border-radius-base 0 0 @border-radius-base !important;
.primary-control, .back-control {
& .btn {
color: @fl-hdr-control-color !important;
padding-left: 5px;
padding-right: 5px;
& .icon-glyph {
display: block;
font-size: 18px;
}
& .label {
display: none;
}
}
}
.title-control {
width: 200px;
left: 50%;
margin-left: -100px;
text-align: center;
.transition(margin-left 0.2s);
.drawer-open .global-page & {
margin-left: -100px + @drawer-width;
}
&, & .btn {
color: @fl-hdr-color;
font-size: 16px;
}
}
.back-control {
left: 10px;
.transition(left 0.2s);
.drawer-open .global-page & {
left: @drawer-width + 10px;
}
& .pin {
opacity: 1;
margin-left: 1px !important;
display: none;
}
}
}
// ------------------------------------
// Main
// Drawer
.global-main, .paned {
border-top: 1px solid @fl-body-control-bg;
}
// Hero
.hero {
background: @fl-body-hero-bg;
margin-top: -1px;
text-align: center;
padding: 30px 0;
font-size: 14px;
&, & a {
color: @fl-body-hero-color;
// This is a mixin which styles components (buttons, inputs, etc.) for use in
// the drawer. We define it as a mixin because it is also pulled in when
// styling a "colored header".
.drawer-components() {
.header-title {
&, & a {
color: @fl-drawer-color;
}
}
& a {
opacity: 0.5;
&, & a, & .btn-link {
color: @fl-drawer-control-color;
}
& .form-control {
background: @fl-drawer-control-bg;
border: 0;
color: @fl-drawer-control-color;
.placeholder(@fl-drawer-control-color);
&:focus {
background: fadein(@fl-drawer-control-bg, 5%);
}
}
& .search-input:before {
color: @fl-drawer-control-color;
}
& .btn-default, & .btn-default:hover {
background: @fl-drawer-control-bg;
color: @fl-drawer-control-color;
}
& .btn-default.active, .open > .dropdown-toggle.btn-default {
background: fadein(@fl-drawer-control-bg, 5%);
}
& .btn-naked {
background: transparent;
}
}
.hero .close {
float: right;
margin-top: -10px;
color: #fff;
opacity: 0.5;
// PHONES: On phones, the drawer is displayed in its semantic sense: as a
// drawer on the left side of the screen. On other devices, the drawer has no
// specific appearance.
@media @phone {
.drawer-open {
overflow: hidden;
}
.global-drawer {
background: @fl-drawer-bg;
color: @fl-drawer-color;
width: @drawer-width;
position: fixed;
left: 0;
top: 0;
bottom: 0;
.drawer-components();
}
}
.hero h2 {
// ------------------------------------
// Header
.header-controls {
margin: 0;
font-size: 22px;
font-weight: normal;
padding: 0;
list-style: none;
}
.hero p {
margin: 10px 0 0;
// On phones, the header is displayed inside of the drawer. We lay its
// contents out vertically.
@media @phone {
.global-header .container {
padding: 0;
}
.header-title {
border-bottom: 1px solid @fl-drawer-control-bg;
font-size: 16px;
font-weight: normal;
margin: 0;
line-height: 56px;
white-space: nowrap;
text-align: center;
}
.header-controls {
& > li {
padding: 10px 10px 0;
}
& .form-control, & .btn-group, & .btn {
width: 100%;
text-align: left;
}
}
}
// On other devices, we stick the header up the top of the page, overlaying
// the page toolbar that we styled earlier. We lay its contents out
// horizontally.
@media @tablet, @desktop, @desktop-hd {
.global-header {
padding: 10px;
height: 56px;
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: @zindex-navbar-fixed;
.clearfix();
& when (@fl-colored-hdr = true) {
.drawer-components();
}
}
.header-controls {
&, & > li {
display: inline-block;
vertical-align: top;
}
}
.header-primary {
float: left;
}
.header-title {
display: inline-block;
vertical-align: top;
font-size: 18px;
font-weight: normal;
margin: 0;
line-height: 36px;
&, & a {
color: @fl-hdr-color;
}
}
.header-secondary {
float: right;
& .search-input {
margin-right: 10px;
&:focus {
width: 400px;
}
}
}
.back-control {
float: left;
margin-right: 25px;
& .back {
z-index: 3 !important; // z-index of an active .btn-group .btn is 2
border-radius: @border-radius-base !important;
.transition(border-radius 0.2s);
}
& .pin {
opacity: 0;
margin-left: -36px !important;
.transition(~"opacity 0.2s, margin-left 0.2s");
&:not(.active) .fa {
.rotate(45deg);
}
}
&.active {
& .back {
border-radius: @border-radius-base 0 0 @border-radius-base !important;
}
& .pin {
opacity: 1;
margin-left: 1px !important;
}
}
}
}
// ------------------------------------
// Content Area
.global-content {
padding-top: 56px;
}
// On phones, the content area overlays the drawer, so we must give it a
// background and min-height so it cannot be seen through. When the drawer is
// meant to be open, we slide the content to the right to reveal the drawer.
@media @phone {
.global-content {
background: @fl-body-bg;
position: relative;
width: 100%;
min-height: 100vh;
padding-bottom: 15px;
.transition(margin-left 0.2s);
.box-shadow(0 0 6px @fl-shadow-color);
.drawer-open & {
margin-left: @drawer-width;
}
}
}
// ------------------------------------
// Footer
.global-footer {
margin: 100px 0 20px;
color: @fl-body-muted-more-color;
.clearfix();
}
.footer-primary, .footer-secondary {
margin: 0;
padding: 0;
list-style-type: none;
list-style: none;
& > li {
display: inline-block;
vertical-align: middle;
}
& a {
color: @fl-body-muted-more-color;
&:hover,
&:focus {
text-decoration: none;
color: @link-hover-color;
}
// On phones, the footer is displayed at the bottom of the drawer. The
// footer's primary controls don't display, but the secondary ones do.
// @todo Maybe we should reverse the naming of primary/secondary then?
@media @phone {
.global-footer {
position: fixed;
left: 15px;
bottom: 15px;
width: @drawer-width;
margin: 0;
z-index: 1;
& .container {
padding: 0;
}
}
.footer-primary {
display: none;
}
.footer-secondary {
float: none;
& > li {
margin-right: 15px;
}
}
}
.footer-primary {
display: inline-block;
& > li {
margin-right: 15px;
// On other devices, we put the footer at the bottom of the page by absolutely
// positioning it, relative to the page which we pad out at the bottom. We
// show the primary controls on the left, and the secondary controls on the
// right.
@media @tablet, @desktop, @desktop-hd {
.global-page {
padding-bottom: 150px;
position: relative;
}
.global-footer {
position: absolute;
bottom: 20px;
left: 0;
right: 0;
&, & a {
color: @fl-body-muted-more-color;
}
& a {
&:hover,
&:focus {
text-decoration: none;
color: @link-hover-color;
}
}
}
.footer-primary {
display: inline-block;
& > li {
margin-right: 15px;
}
}
.footer-secondary {
float: right;
& > li {
margin-left: 15px;
}
}
}
.footer-secondary {
float: right;
& > li {
margin-left: 15px;
// ------------------------------------
// Miscellaneous
// On phones, we disregard "affixed" elements and make them static.
@media @phone {
.affix {
position: static;
}
}

View File

@ -7,34 +7,6 @@
opacity: 0.9;
}
}
.modal-dialog {
margin: 120px auto;
& .close {
position: absolute;
right: 5px;
top: 5px;
}
}
.modal-content {
border: 0;
border-radius: @border-radius-base;
.box-shadow(0 7px 15px @fl-shadow-color);
}
.modal-sm {
width: 375px;
}
.modal-header {
text-align: center;
border: 0;
padding: 25px;
& h3 {
font-size: 22px;
margin: 0;
}
}
.modal-body {
background-color: @fl-body-secondary-color;
padding: 25px;
@ -49,7 +21,6 @@
text-align: center;
color: @fl-body-muted-color;
}
.modal-loading {
position: absolute;
top: 0;
@ -67,7 +38,6 @@
pointer-events: auto;
}
}
.form-centered {
text-align: center;
@ -77,3 +47,83 @@
text-align: center;
}
}
@media @phone {
.modal.fade {
opacity: 1;
}
.modal-backdrop.in {
opacity: 0;
}
.modal-dialog {
position: fixed;
left: 0;
right: 0;
bottom: 0;
top: 0;
}
.modal-dialog {
margin: 0;
.modal.fade & {
.transition(top 0.3s);
top: 100%;
.translate(0, 0);
}
.modal.in & {
top: 0;
}
&:before {
content: " ";
.toolbar();
}
}
.modal-content {
border-radius: 0;
border: 0;
min-height: 100vh;
padding-top: 56px;
.box-shadow(none);
}
.modal-header {
padding: 0;
border: 0;
min-height: 0;
& h3 {
line-height: 36px;
}
}
}
@media @tablet, @desktop, @desktop-hd {
.modal-dialog {
margin: 120px auto;
& .close {
position: absolute;
right: 5px;
top: 5px;
}
}
.modal-content {
border: 0;
border-radius: @border-radius-base;
.box-shadow(0 7px 15px @fl-shadow-color);
}
.modal-sm {
width: 375px;
}
.modal-header {
text-align: center;
border: 0;
padding: 25px;
& h3 {
font-size: 22px;
margin: 0;
}
}
}

View File

@ -21,3 +21,9 @@
color: #fff;
}
}
@media @phone {
.signup-welcome {
padding-top: 56px + 60px;
}
}

View File

@ -1,34 +1,40 @@
<div id="page" {{bind-attr class=":page backButtonTarget.paneIsPinned:with-pane"}}>
<div id="page" {{bind-attr class=":global-page backButtonTarget.paneIsPinned:with-pane"}}>
<header id="header" class="global-header">
{{application/back-button target=backButtonTarget className="back-control" toggleDrawer="toggleDrawer"}}
{{application/back-button target=backButtonTarget}}
<div id="drawer" class="global-drawer">
<header id="header" class="global-header">
<div class="container">
<div class="container">
<div class="header-primary">
<h1 class="header-title">
{{#link-to "index" (query-params searchQuery="" sort="recent" show="discussions")}}
{{#if view.image}}
<img {{bind-attr src=view.image alt=view.title}}>
{{else}}
{{view.title}}
{{/if}}
{{/link-to}}
</h1>
{{ui/item-list items=view.headerPrimary class="header-controls"}}
</div>
<div class="header-primary">
<h1 class="header-title">
{{#link-to "index" (query-params searchQuery="" sort="recent" show="discussions")}}
{{#if view.image}}
<img {{bind-attr src=view.image alt=view.title}}>
{{else}}
{{view.title}}
{{/if}}
{{/link-to}}
</h1>
<div class="header-secondary">
{{ui/item-list items=view.headerSecondary class="header-controls"}}
</div>
{{ui/item-list items=view.headerPrimary class="header-controls"}}
</div>
</header>
<div class="header-secondary">
{{ui/item-list items=view.headerSecondary class="header-controls"}}
<footer id="footer" class="global-footer">
<div class="container">
{{ui/item-list items=view.footerPrimary class="footer-primary"}}
{{ui/item-list items=view.footerSecondary class="footer-secondary"}}
</div>
</footer>
</div>
</div>
</header>
<main id="main" class="global-main">
<main id="content" class="global-content">
{{outlet}}
<div class="composer-container">
@ -38,13 +44,6 @@
</div>
</main>
<footer id="footer" class="global-footer">
<div class="container">
{{ui/item-list items=view.footerPrimary class="footer-primary"}}
{{ui/item-list items=view.footerSecondary class="footer-secondary"}}
</div>
</footer>
</div>
<div id="modal" class="modal fade">

View File

@ -1,8 +1,10 @@
{{#if target}}
<div class="btn-group">
<button class="btn btn-default btn-icon back" {{action "back"}}>{{fa-icon "chevron-left"}}</button>
<button class="btn btn-default btn-icon back" {{action "back"}}>{{fa-icon "chevron-left" class="icon-glyph"}}</button>
{{#if target.paned}}
<button {{bind-attr class=":btn :btn-default :btn-icon :pin target.panePinned:active"}} {{action "togglePinned"}}>{{fa-icon "thumb-tack"}}</button>
<button {{bind-attr class=":btn :btn-default :btn-icon :pin target.panePinned:active"}} {{action "togglePinned"}}>{{fa-icon "thumb-tack" class="icon-glyph"}}</button>
{{/if}}
</div>
{{else}}
<button class="btn btn-default btn-icon drawer-toggle" {{action "toggleDrawer"}}>{{fa-icon "reorder" class="icon-glyph"}}</button>
{{/if}}

View File

@ -1,7 +1,7 @@
{{user-avatar user class="composer-avatar"}}
<div class="composer-body">
{{ui/item-list items=controls class="composer-header list-inline"}}
{{ui/item-list items=controls class="composer-header"}}
<div class="composer-editor">
{{ui/text-editor submit="submit" value=content placeholder=placeholder submitLabel=submitLabel disabled=loading}}

View File

@ -1,20 +1,28 @@
<a href="#" class="scrubber-first" {{action "first"}}>{{fa-icon "angle-double-up"}} Original Post</a>
<div class="scrubber-scrollbar">
<div class="scrubber-before"></div>
<div class="scrubber-slider">
<div class="scrubber-handle"></div>
{{#if loaded}}
<div class="scrubber-info">
<strong><span class="index">{{visibleIndex}}</span> of <span class="count">{{count}}</span> posts</strong>
<span class="description">{{description}}</span>
<a href="#" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
<span class="index">{{visibleIndex}}</span> of <span class="count">{{count}}</span> posts
{{fa-icon "sort" class="icon-glyph"}}
</a>
<div class="dropdown-menu">
<div class="scrubber">
<a href="#" class="scrubber-first" {{action "first"}}>{{fa-icon "angle-double-up"}} Original Post</a>
<div class="scrubber-scrollbar">
<div class="scrubber-before"></div>
<div class="scrubber-slider">
<div class="scrubber-handle"></div>
{{#if loaded}}
<div class="scrubber-info">
<strong><span class="index">{{visibleIndex}}</span> of <span class="count">{{count}}</span> posts</strong>
<span class="description">{{description}}</span>
</div>
{{/if}}
</div>
{{/if}}
<div class="scrubber-after"></div>
<ul class="scrubber-highlights">
{{#each index in relevantPostIndexes}}
<li {{bind-attr style=index}}></li>
{{/each}}
</ul>
</div>
<a href="#" class="scrubber-last" {{action "last"}}>{{fa-icon "angle-double-down"}} Now</a>
</div>
<div class="scrubber-after"></div>
<ul class="scrubber-highlights">
{{#each index in relevantPostIndexes}}
<li {{bind-attr style=index}}></li>
{{/each}}
</ul>
</div>
<a href="#" class="scrubber-last" {{action "last"}}>{{fa-icon "angle-double-down"}} Now</a>

View File

@ -22,9 +22,9 @@
<span class="count" {{action "markAsRead"}} {{bind-attr title=countTitle}}>
{{#if displayUnread}}
<strong>{{abbreviate-number discussion.unreadCount}}</strong> unread
{{abbreviate-number discussion.unreadCount}} <span class="label">unread</span>
{{else}}
<strong>{{abbreviate-number discussion.repliesCount}}</strong> replies
{{abbreviate-number discussion.repliesCount}} <span class="label">replies</span>
{{/if}}
</span>

View File

@ -2,6 +2,6 @@
{{#if item.isListItem}}
{{view item}}
{{else}}
<li>{{view item}}</li>
<li class="{{item.listItemClass}}">{{view item}}</li>
{{/if}}
{{/each}}

View File

@ -1,8 +1,8 @@
<div class="modal-content">
<button class="close btn btn-icon btn-link" {{action "closeModal"}}>{{fa-icon "times"}}</button>
<button class="close btn btn-icon btn-link back-control" {{action "closeModal"}}>{{fa-icon "times"}}</button>
<form {{action "authenticate" on="submit"}}>
<div class="modal-header">
<h3>Log In</h3>
<h3 class="title-control">Log In</h3>
</div>
<div class="modal-body">
<div class="form-centered">

View File

@ -1,8 +1,8 @@
<div class="modal-content">
<button class="close btn btn-icon btn-link" {{action "closeModal"}}>{{fa-icon "times"}}</button>
<button class="close btn btn-icon btn-link back-control" {{action "closeModal"}}>{{fa-icon "times"}}</button>
<form {{action "submit" on="submit"}}>
<div class="modal-header">
<h3>Sign Up</h3>
<h3 class="title-control">Sign Up</h3>
</div>
<div class="modal-body">
<div class="form-centered">

View File

@ -30,6 +30,12 @@ export default Ember.View.extend(HasItemLists, {
});
}),
drawerShowingChanged: Ember.observer('controller.drawerShowing', function() {
Ember.run.scheduleOnce('afterRender', this, function() {
$('body').toggleClass('drawer-open', this.get('controller.drawerShowing'));
});
}),
didInsertElement: function() {
// Add a class to the body when the window is scrolled down.
$(window).scroll(function() {
@ -38,9 +44,9 @@ export default Ember.View.extend(HasItemLists, {
// Resize the main content area so that the footer sticks to the
// bottom of the viewport.
$(window).resize(function() {
$('#main').css('min-height', $(window).height() - $('#header').outerHeight() - $('#footer').outerHeight(true));
}).resize();
// $(window).resize(function() {
// $('#main').css('min-height', $(window).height() - $('#header').outerHeight() - $('#footer').outerHeight(true));
// }).resize();
var view = this;
this.$('#modal').on('hide.bs.modal', function() {
@ -50,6 +56,19 @@ export default Ember.View.extend(HasItemLists, {
}).on('shown.bs.modal', function() {
view.get('controller.modalController').send('focus');
});
this.$().on('show.bs.dropdown', function() {
$('body').addClass('dropdown-open');
}).on('hide.bs.dropdown', function() {
$('body').removeClass('dropdown-open');
});
this.$('.global-content').click(function(e) {
if (view.get('controller.drawerShowing')) {
e.preventDefault();
view.set('controller.drawerShowing', false);
}
});
},
switchHeader: Ember.observer('controller.session.user', function() {

View File

@ -7,7 +7,7 @@ var $ = Ember.$;
export default Ember.View.extend(HasItemLists, {
classNames: ['composer'],
classNameBindings: ['minimized', 'fullscreen', 'active'],
classNameBindings: ['visible', 'minimized', 'fullscreen', 'active'],
itemLists: ['controls'],
position: Ember.computed.alias('controller.position'),
@ -142,6 +142,12 @@ export default Ember.View.extend(HasItemLists, {
});
}),
updateBody: Ember.observer('visible', function() {
Ember.run.scheduleOnce('afterRender', this, function() {
$('body').toggleClass('composer-open', this.get('visible'));
});
}),
// Whenever the composer's display state changes, update the DOM to slide
// it in or out.
positionDidChange: Ember.observer('position', function() {
@ -181,6 +187,8 @@ export default Ember.View.extend(HasItemLists, {
break;
}
$composer.css('overflow', '');
if (this.get('position') !== PositionEnum.FULLSCREEN) {
this.updateBodyPadding(true);
}
@ -233,7 +241,7 @@ export default Ember.View.extend(HasItemLists, {
populateControls: function(items) {
var view = this;
var addControl = function(tag, title, icon) {
view.addActionItem(items, tag, null, icon).reopen({className: 'btn btn-icon btn-link', title: title});
return view.addActionItem(items, tag, null, icon).reopen({className: 'btn btn-icon btn-link', title: title});
};
if (this.get('fullscreen')) {
@ -243,7 +251,7 @@ export default Ember.View.extend(HasItemLists, {
addControl('minimize', 'Minimize', 'minus minimize');
addControl('fullscreen', 'Full Screen', 'expand');
}
addControl('close', 'Close', 'times');
addControl('close', 'Close', 'times').reopen({listItemClass: 'back-control'});
}
},

View File

@ -51,11 +51,13 @@ export default Ember.View.extend(HasItemLists, {
items.pushObjectWithTag(DropdownSplit.extend({
items: this.populateItemList('controls'),
icon: 'reply',
buttonClass: 'btn-primary'
buttonClass: 'btn-primary',
listItemClass: 'primary-control',
}), 'controls');
items.pushObjectWithTag(StreamScrubber.extend({
streamContent: this.get('streamContent')
streamContent: this.get('streamContent'),
listItemClass: 'title-control'
}), 'scrubber');
},

View File

@ -74,10 +74,11 @@ export default Ember.View.extend(HasItemLists, {
}),
populateSidebar: function(items) {
this.addActionItem(items, 'newDiscussion', 'Start a Discussion', 'edit').reopen({className: 'btn btn-primary new-discussion'});
this.addActionItem(items, 'newDiscussion', 'Start a Discussion', 'edit')
.reopen({className: 'btn btn-primary new-discussion', listItemClass: 'primary-control'});
var nav = this.populateItemList('nav');
items.pushObjectWithTag(DropdownSelect.extend({items: nav}), 'nav');
items.pushObjectWithTag(DropdownSelect.extend({items: nav, listItemClass: 'title-control'}), 'nav');
},
populateNav: function(items) {

View File

@ -5,7 +5,7 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>{{ $title }}</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1">
<meta name="flarum/config/environment" content="{{ rawurlencode(json_encode($config)) }}">
<base href="/">
{!! $styles !!}