Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Add experimental off-canvas layout #6565

Closed
wants to merge 21 commits into from
@trumbitta

I don't think this is going to ever be accepted, but I hope it could be a nice beginning to play with. Just read on...

Based on:
https://github.com/bradfrost/this-is-responsive/blob/gh-pages/patterns/layout-offcanvas-right.html

TODO:

  • fix WebKit infinite loop transition bug (automagically fixed since Bootstrap 3 switched to Mobile First)
  • refactor JS into a proper Bootstrap plugin (but can be improved), with proper unit tests
  • must be tested on all the browsers supported by Bootstrap (this is a WIP and will be unchecked whenever there's some related bug being worked out)
  • add proper documentation
  • add variant with sidebar coming from the left: .row-offcanvas-left
  • delete temporary html file from examples
trumbitta added some commits
@trumbitta trumbitta Add experimental off-canvas layout
Based on:
https://github.com/bradfrost/this-is-responsive/blob/gh-pages/patterns/layout-offcanvas-right.html

It lacks a nice transition effect due to a strange bug happening
only in Chrome at certain viewport sizes.

Other gotchas: only works with a sidebar coming in from the right,
and the required javascript is ugly and written in the HTML file
because I don't feel comfortable enough to figure out how to write
a noob-level plugin for Bootstrap.
6b3cf14
@trumbitta trumbitta update description with correct activation pixel range 302f0ec
@mdo
Owner

If you can clean this up and isolate the custom CSS to the page, we can add this as an example. None of the CSS should be in the /less/ directory, the JS should be cleaned up and bugs worked out, etc. Build it up to something usable and stable, and we'll do our best to get it in there.

@blakeembrey

@trumbitta Do you have an example of the transition bug. I've been using an off-canvas layout for http://requestmaker.jit.su/ to play around with, and haven't seen any issues with the transition.

@trumbitta

@blakeembrey I'll try to put one up asap.

@mdo Thank you, I'll do my best to follow your directives.

Just a question: do you think that Bootstrap should not have an off-canvas component / option, and this is why you want this as an example with its own CSS and JS?

My first thought was to end up with an addition, maybe a JS component, and I was starting to look for help from a friend with the JS part with the aim to write a real plugin.

@mdo
Owner

@trumbitta If it's not 100% compatible and stable, it should be an example. If it's something you can write in the exact same style as our JS plugins, document, and support for IE8+, then we can consider adding it to the core. If you want to do the latter, be sure to read the Contributing doc and the JS docs.

Thanks!

@trumbitta

@mdo I'm trying to get there with the JS plugin (see the commits coming in the next minutes).

I'd like to keep updating this pull request while trying to reach a usable state, to get some feedback / hints / help / whatever while I'm working on it.
Of course, if you prefer, I could aswell work outside a pull request and come back when it's "perfect"ish.

@blakeembrey here's the transition bug: https://dl.dropbox.com/u/21966344/offcanvas-bootstrap-transition-bug/index.html

  • I'm using Chrome Version 24.0.1312.52 on Linux
  • resize your window at 767px, maybe less. Stop resizing when the blue button shows up on the top right of the jumbotron element, and do not make your window too much narrow because the bug disappears after a while.
  • please, ignore the missing icon on the button
  • push the button, behold the crazy loop
@trumbitta trumbitta Rename off-canvas to offcanvas, first steps of the JS plugin
TODO:

 * JS unit tests
 * fix the transition bug at ~767px (infinite loop)
 * remove examples/off-canvas.html, replace with proper documentation
 * refine, polish, refine
5712ddf
@blakeembrey

@trumbitta I just updated to Chrome 24 on Ubuntu and aren't seeing the bug. Don't suppose you have any more details?

@blakeembrey

@trumbitta Lol, ignore that. Just found it, I see what you mean now. Other than that though, it looks great.

@Yohn

@trumbitta that looks nice, I've been playing with it in firefox 18 on windows xp and it seems to be working correctly from what I can tll.. I couldnt see any bugs happening, it opens and closes when you click the icon-less button.. I really look forward to seeing something like this added to bootstrap!

@trumbitta

I put some hours in debugging that transition, but wasn't able to fix it.

To my tests, the issue is somewhat similar to http://stackoverflow.com/questions/12363172/how-to-avoid-infinite-loop-with-css3-transition-and-vertical-scroll-bar-in-webki

If you set two different background-color properties inside and outside the 767px threshold, and increase the transition-duration to - say - 5s, you can observe quite clearly what's going on:

When the .row-offcanvas wrapper starts its transition towards right: NNpx, Chrome gets confused about what media query is to be applied at a given time, and falls in a infinite loop.

At this point, my solution would be to remove the transition. Would anyone else like to take their chances on this juicy little bug?

In the next days, I'm going to test the pull request against IE8+ and replace the example HTML file with some proper docs.

@blakeembrey

@trumbitta I'll take a look, it should be easy enough to work around. Have you tried floats and negative margins yet? I used this approach and haven't hit this bug yet. I am using it on http://requestmaker.jit.su/ and the code is here https://github.com/blakeembrey/requestmaker/blob/master/app/public/styl/requestmaker.styl

@trumbitta

@blakeembrey Thank you!

I tried some floats and negative margins, but not extensively.
One of my goals is to respect, and use as a foundation, the core grid behaviours (i.e. no float < 767px) and the landmark viewport width values for the media queries.

@trumbitta

Thank you god and @mdo for the switch to Mobile First. That single move nuked away the transition bug!

Wait for the related commits sometimes today :)

@trumbitta

@mdo except for the HTML file in the examples (which is still there just for a quick and comfy test while working on this, and would be removed before the end), I think the first complete iteration is finished.

I'm looking forward for your thoughts / directives on further refinement or whatever.

@steffler

@trumbitta I just wanted to chime in and thank you for work on these commits. This is exactly what I've been trying get bootstrap to do for the past week after I came across Brad Frosts' work.

When it comes to writing JS, I am about as green as it gets. I'm still very new to coding so I can't imagine how much time this has saved me.

@trumbitta

@steffler thank you, this is exactly the reason why I embraced Open Source a life ago :smile_cat:

docs/assets/js/bootstrap-offcanvas.js
((27 lines not shown))
+ * ========================= */
+
+ var toggle = '[data-toggle=offcanvas]'
+ , Offcanvas = function (element) {
+ var $el = $(element).on('click.offcanvas.data-api', this.toggle)
+ }
+
+ Offcanvas.prototype = {
+
+ constructor: Offcanvas
+
+ , toggle: function (e) {
+ var $this = $(this)
+ , $parent
+
+ if ($this.is('.disabled, :disabled')) return
@fat Owner
fat added a note

don't need this – disabled property doesn't fire events

Indeed.
(Poorly executed) TDD misguided my judgement.
Commit incoming.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
docs/assets/js/bootstrap-offcanvas.js
((48 lines not shown))
+
+ return false
+ }
+
+ , keydown: function (e) {
+ var $this
+ , $parent
+
+ if (!/(38|40|27)/.test(e.keyCode)) return
+
+ $this = $(this)
+
+ e.preventDefault()
+ e.stopPropagation()
+
+ if ($this.is('.disabled, :disabled')) return
@fat Owner
fat added a note

don't need

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
docs/assets/js/bootstrap-offcanvas.js
((56 lines not shown))
+ if (!/(38|40|27)/.test(e.keyCode)) return
+
+ $this = $(this)
+
+ e.preventDefault()
+ e.stopPropagation()
+
+ if ($this.is('.disabled, :disabled')) return
+
+ $parent = $this.parents('.row-offcanvas')
+
+ $parent.hasClass('active')
+
+ if (e.keyCode == 27) {
+ if (e.which == 27) $parent.find(toggle).focus()
+ return $this.click()
@fat Owner
fat added a note

call toggle here directly, not a click event

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
docs/assets/js/bootstrap-offcanvas.js
((54 lines not shown))
+ , $parent
+
+ if (!/(38|40|27)/.test(e.keyCode)) return
+
+ $this = $(this)
+
+ e.preventDefault()
+ e.stopPropagation()
+
+ if ($this.is('.disabled, :disabled')) return
+
+ $parent = $this.parents('.row-offcanvas')
+
+ $parent.hasClass('active')
+
+ if (e.keyCode == 27) {
@fat Owner
fat added a note

confused a bit here… but shouldn't e.keyCode and e.which be the same?

the whole keydown block it's a botched attempt at being smart when using a keyboard...

On second thought, I'd go for an extremely light first version:

[...]
keydown: function (e) {
      $(this).toggle
}
[...]

This should be enough to provide the basic functionality, while proceeding further - maybe next version? - I'd like to have the following behaviour:

  • on active via keyboard, give focus to the sidebar
  • while in sidebar: ESC gives back focus to the toggler anchor/button

I don't feel skilled enough now, but I'm willing to improve myself and this plugin.

Your thoughts on my plan?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@ckluis

@trumbitta THANK YOU

Off-Canvas can be extremely useful - especially with content that could be displayed dynamically. If you look at the
basecamp iPhone app (or actually even twitter.com) - the concept of slidein content works very well.

Have you considered creating a non-menu style solution for off-canvas? I believe a great implementation of off-canvas would be allowing the off-canvas area to be built via the same grid/column structure of the base theme with a new off-canvas css/javascript designator.

"off-canvas oc-left span3"
"off-canvas oc-right span4"
"off-canvas oc-top"
"off-canvas oc-bottom"

Unfortunately, I'm not much of a developer or designer or I would attempt to code it and provide you with a sample.

http://bradfrost.github.com/this-is-responsive/patterns.html - has good off-canvas content examples - the idea of the off-canvas items being hidden until selected is enticing

@trumbitta

@ckluis actually this plugin is inspired by This Is Responsive (have a look at the description of the pull request :smile: ).
Also, you're not forced to use a menu. The plugin should work with various types of inner content.

@ckluis

@trumbitta Indeed, most excellent! I was trying to get the concept of a sizable off-canvas solution. I can imagine things like a contact form being off-canvas right - just an example.

@dharmaone

how can i use this plugin?

@trumbitta

@dhelder check the pull request, there's also proper documentation in the docs. BTW, it is a bit outdated right now.

Waiting for a reply from @fat on my plans for the JS part, to move on and keep it up-to-date with the latest changes from upstream

@breerly

Big +1 on this.

@dhelder

@trumbitta I have nothing to do with Bootstrap.

@trumbitta

Lol sorry @dhelder . I was replying to @dharmaone

@fat fat commented on the diff
docs/assets/js/bootstrap-offcanvas.js
((36 lines not shown))
+ constructor: Offcanvas
+
+ , toggle: function (e) {
+ var $this = $(this)
+ , $parent
+
+ $parent = $this.parents('.row-offcanvas')
+
+ $parent.toggleClass('active')
+ $this.toggleClass('active')
+
+ return false
+ }
+
+ , keydown: function (e) {
+ console.log($(this))
@fat Owner
fat added a note

assuming you don't want me to review this…

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@fat
Owner
fat commented

my feedback on the js is that it doesn't look like it should be a plugin… if you're jus toggling a single class.

@trumbitta

Ok, I think this means that I should have followed the very first advice by @mdo ( #6565 (comment) ) and make it an example.

Due to a bandwith issue I can't work on it at least till Monday.

Please, let me know if you guys want me to close this and open a new PR or anything else.

@trumbitta

I'm closing this, as it's now a standalone example. #7799

@trumbitta trumbitta closed this
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Jan 12, 2013
  1. @trumbitta

    Add experimental off-canvas layout

    trumbitta authored
    Based on:
    https://github.com/bradfrost/this-is-responsive/blob/gh-pages/patterns/layout-offcanvas-right.html
    
    It lacks a nice transition effect due to a strange bug happening
    only in Chrome at certain viewport sizes.
    
    Other gotchas: only works with a sidebar coming in from the right,
    and the required javascript is ugly and written in the HTML file
    because I don't feel comfortable enough to figure out how to write
    a noob-level plugin for Bootstrap.
  2. @trumbitta
Commits on Jan 14, 2013
  1. @trumbitta
  2. @trumbitta

    Rename off-canvas to offcanvas, first steps of the JS plugin

    trumbitta authored
    TODO:
    
     * JS unit tests
     * fix the transition bug at ~767px (infinite loop)
     * remove examples/off-canvas.html, replace with proper documentation
     * refine, polish, refine
Commits on Jan 16, 2013
  1. @trumbitta
Commits on Jan 17, 2013
  1. @trumbitta
  2. @trumbitta

    Adapt offcanvas to new Mobile First approach

    trumbitta authored
    Also fixes: infinite transition loop in WebKit
  3. @trumbitta
  4. @trumbitta
Commits on Jan 18, 2013
  1. @trumbitta
  2. @trumbitta
  3. @trumbitta
  4. @trumbitta
  5. @trumbitta

    Deserved kudos to @bradfrost

    trumbitta authored
Commits on Jan 19, 2013
  1. @trumbitta

    Add support for left side off canvas and simplify things for users.

    trumbitta authored
    With proper nesting, there's now -1 class selector to add to the HTML
  2. @trumbitta
  3. @trumbitta
  4. @trumbitta
Commits on Feb 8, 2013
  1. @trumbitta
Commits on Apr 18, 2013
  1. @trumbitta
  2. @trumbitta
This page is out of date. Refresh to see the latest.
View
1  docs/_includes/docs-nav.html
@@ -218,5 +218,6 @@ <h3 class="bs-docs-sidenav-heading"><a href="/">Bootstrap</a></h3>
<li><a href="#carousel">Carousel</a></li>
<li><a href="#typeahead">Typeahead</a></li>
<li><a href="#affix">Affix</a></li>
+ <li><a href="#offcanvas">Offcanvas</a></li>
</ul>
</div>
View
1  docs/_includes/footer.html
@@ -15,6 +15,7 @@
<script src="/assets/js/bootstrap-carousel.js"></script>
<script src="/assets/js/bootstrap-typeahead.js"></script>
<script src="/assets/js/bootstrap-affix.js"></script>
+<script src="/assets/js/bootstrap-offcanvas.js"></script>
<script type="text/javascript" src="http://platform.twitter.com/widgets.js"></script>
<script src="/assets/js/holder/holder.js"></script>
View
86 docs/assets/css/bootstrap.css
@@ -5266,6 +5266,92 @@ a.list-group-item.active > .badge,
}
}
+.row-offcanvas {
+ position: relative;
+ overflow: hidden;
+ -webkit-transition: all 0.25s ease-out;
+ -moz-transition: all 0.25s ease-out;
+ -o-transition: all 0.25s ease-out;
+ transition: all 0.25s ease-out;
+}
+
+.row-offcanvas.active {
+ overflow: visible;
+}
+
+.row-offcanvas-right .sidebar-offcanvas {
+ right: -210px;
+}
+
+.row-offcanvas-left .sidebar-offcanvas {
+ left: -210px;
+}
+
+.row-offcanvas-right.active {
+ right: 210px;
+}
+
+.row-offcanvas-left.active {
+ left: 210px;
+}
+
+.sidebar-offcanvas {
+ position: absolute;
+ top: 0;
+}
+
+@media screen and (max-width: 319px) {
+ .sidebar-offcanvas {
+ width: 210px;
+ }
+}
+
+@media screen and (min-width: 320px) and (max-width: 480px) {
+ .row-offcanvas-right .sidebar-offcanvas {
+ right: -270px;
+ }
+ .row-offcanvas-left .sidebar-offcanvas {
+ left: -270px;
+ }
+ .row-offcanvas-right.active {
+ right: 270px;
+ }
+ .row-offcanvas-left.active {
+ left: 270px;
+ }
+ .sidebar-offcanvas {
+ width: 270px;
+ }
+}
+
+@media screen and (min-width: 481px) and (max-width: 767px) {
+ .row-offcanvas-right .sidebar-offcanvas {
+ right: -480px;
+ }
+ .row-offcanvas-left .sidebar-offcanvas {
+ left: -480px;
+ }
+ .row-offcanvas-right.active {
+ right: 480px;
+ }
+ .row-offcanvas-left.active {
+ left: 480px;
+ }
+ .sidebar-offcanvas {
+ width: 480px;
+ }
+}
+
+@media screen and (min-width: 768px) {
+ .row-offcanvas {
+ position: static;
+ overflow: visible;
+ }
+ .sidebar-offcanvas {
+ position: static;
+ }
+}
+
.clearfix:before,
.clearfix:after {
display: table;
View
112 docs/assets/js/bootstrap-offcanvas.js
@@ -0,0 +1,112 @@
+/* ============================================================
+ * bootstrap-offcanvas.js v3.0.0
+ * http://twitter.github.com/bootstrap/javascript.html#offcanvas
+ * ============================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============================================================ */
+
+
+!function ($) {
+
+ "use strict"; // jshint ;_;
+
+
+ /* OFFCANVAS CLASS DEFINITION
+ * ========================= */
+
+ var toggle = '[data-toggle=offcanvas]'
+ , Offcanvas = function (element) {
+ var $el = $(element).on('click.offcanvas.data-api', this.toggle)
+ }
+
+ Offcanvas.prototype = {
+
+ constructor: Offcanvas
+
+ , toggle: function (e) {
+ var $this = $(this)
+ , $parent
+
+ $parent = $this.parents('.row-offcanvas')
+
+ $parent.toggleClass('active')
+ $this.toggleClass('active')
+
+ return false
+ }
+
+ , keydown: function (e) {
+ console.log($(this))
@fat Owner
fat added a note

assuming you don't want me to review this…

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ $(this).toggle
+ // var $this
+ // , $parent
+
+ // if (!/(38|40|27)/.test(e.keyCode)) return
+
+ // $this = $(this)
+
+ // e.preventDefault()
+ // e.stopPropagation()
+
+ // $parent = $this.parents('.row-offcanvas')
+
+ // console.log($parent, $parent.find(toggle))
+ // if ($parent.hasClass('active') && e.keyCode == 27) {
+ // console.log($parent, $parent.find(toggle))
+ // $parent.find(toggle).focus()
+ // }
+
+ // return false;
+
+ }
+
+ }
+
+
+
+ /* OFFCANVAS PLUGIN DEFINITION
+ * ========================== */
+
+ var old = $.fn.offcanvas
+
+ $.fn.offcanvas = function (option) {
+ return this.each(function () {
+ var $this = $(this)
+ , data = $this.data('offcanvas')
+ if (!data) $this.data('offcanvas', (data = new Offcanvas(this)))
+ if (typeof option == 'string') data[option].call($this)
+ })
+ }
+
+ $.fn.offcanvas.Constructor = Offcanvas
+
+
+ /* OFFCANVAS NO CONFLICT
+ * ==================== */
+
+ $.fn.offcanvas.noConflict = function () {
+ $.fn.offcanvas = old
+ return this
+ }
+
+
+ /* APPLY TO OFFCANVAS ELEMENTS
+ * =================================== */
+
+ $(document)
+ .on('click.offcanvas.data-api touchstart.offcanvas.data-api' , toggle, Offcanvas.prototype.toggle)
+ .on('keydown.offcanvas.data-api touchstart.offcanvas.data-api', toggle, Offcanvas.prototype.keydown)
+
+}(window.jQuery);
View
157 docs/docs.html
@@ -6695,6 +6695,163 @@ <h3 class="popover-title">Popover left</h3>
</table>
</section>
+ <!-- Off Canvas
+ ================================================== -->
+ <section id="offcanvas">
+ <div class="page-header">
+ <h1>Off Canvas <small>bootstrap-offcanvas.js</small></h1>
+ </div>
+
+ <p>Inspired by <a href="http://bradfrost.github.com/this-is-responsive/patterns.html">This is Responsive</a> by <a href="http://bradfrostweb.com/about/">Brad Frost</a>.
+
+ <h2>Examples</h2>
+ <p>Create basic off canvas layouts, as seen on the likes of the Facebook and Google+ apps for iPad.</p>
+
+ <div class="alert alert-info">
+ <strong>Heads up!</strong>
+ The off canvas layout is tailored for phone and tablet viewport widths. If you are using a Desktop browser, in order to see the examples in action you must shrink the window accordingly.
+ </div>
+
+ <h3>Sidebar coming in from the right</h3>
+ <div class="bs-docs-example" style="overflow: hidden">
+
+ <div id="offcanvas-right-example" class="row row-offcanvas row-offcanvas-right">
+ <div class="col-span-9">
+ <p class="pull-right visible-phone"><a href="#offcanvas-right-sidebar" class="btn btn-primary btn-offcanvas" data-toggle="offcanvas"><i class="glyphicon-resize-horizontal"></i></a></p>
+ <div class="row">
+ <div class="col-span-6">
+ <h2>Heading</h2>
+ <p>Donec id elit non mi porta gravida at eget metus. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Etiam porta sem malesuada magna mollis euismod. Donec sed odio dui. </p>
+ <p><a class="btn" href="#">View details &raquo;</a></p>
+ </div>
+ <div class="col-span-6">
+ <h2>Heading</h2>
+ <p>Donec id elit non mi porta gravida at eget metus. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Etiam porta sem malesuada magna mollis euismod. Donec sed odio dui. </p>
+ <p><a class="btn" href="#">View details &raquo;</a></p>
+ </div>
+ </div>
+ </div>
+ <nav class="col-span-3 sidebar-offcanvas" id="offcanvas-right-sidebar" role="navigation">
+ <div class="well sidebar-nav">
+ <ul class="nav nav-list">
+ <li class="nav-header">Sidebar</li>
+ <li class="active"><a href="#">Link</a></li>
+ <li><a href="#">Link</a></li>
+ <li><a href="#">Link</a></li>
+ <li><a href="#">Link</a></li>
+ <li class="nav-header">Sidebar</li>
+ <li><a href="#">Link</a></li>
+ <li><a href="#">Link</a></li>
+ </ul>
+ </div>
+ </nav>
+ </div> <!-- /offcanvas-right-example -->
+ </div>
+{% highlight html linenos %}
+<div id="offcanvas-right-example" class="row row-offcanvas row-offcanvas-right">
+ <div class="col-span-9">
+ <p class="pull-right visible-phone"><a href="#offcanvas-right-sidebar" class="btn btn-primary btn-offcanvas" data-toggle="offcanvas"><i class="glyphicon-resize-horizontal"></i></a></p>
+ <div class="row">
+ <div class="col-span-6">
+ <h2>Heading</h2>
+ <p>Donec id elit non mi porta gravida at eget metus. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Etiam porta sem malesuada magna mollis euismod. Donec sed odio dui. </p>
+ <p><a class="btn" href="#">View details &raquo;</a></p>
+ </div>
+ <div class="col-span-6">
+ <h2>Heading</h2>
+ <p>Donec id elit non mi porta gravida at eget metus. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Etiam porta sem malesuada magna mollis euismod. Donec sed odio dui. </p>
+ <p><a class="btn" href="#">View details &raquo;</a></p>
+ </div>
+ </div>
+ </div>
+ <nav class="col-span-3 sidebar-offcanvas" id="offcanvas-right-sidebar" role="navigation">
+ <div class="well sidebar-nav">
+ <ul class="nav nav-list">
+ <li class="nav-header">Sidebar</li>
+ <li class="active"><a href="#">Link</a></li>
+ <li><a href="#">Link</a></li>
+ <li><a href="#">Link</a></li>
+ <li><a href="#">Link</a></li>
+ <li class="nav-header">Sidebar</li>
+ <li><a href="#">Link</a></li>
+ <li><a href="#">Link</a></li>
+ </ul>
+ </div>
+ </nav>
+</div>
+{% endhighlight %}
+
+ <h3>Sidebar coming in from the left</h3>
+ <div class="bs-docs-example" style="overflow: hidden">
+
+ <div id="offcanvas-left-example" class="row row-offcanvas row-offcanvas-left">
+ <nav class="col-span-3 sidebar-offcanvas" id="offcanvas-right-sidebar" role="navigation">
+ <div class="well sidebar-nav">
+ <ul class="nav nav-list">
+ <li class="nav-header">Sidebar</li>
+ <li class="active"><a href="#">Link</a></li>
+ <li><a href="#">Link</a></li>
+ <li><a href="#">Link</a></li>
+ <li><a href="#">Link</a></li>
+ <li class="nav-header">Sidebar</li>
+ <li><a href="#">Link</a></li>
+ <li><a href="#">Link</a></li>
+ </ul>
+ </div>
+ </nav>
+ <div class="col-span-9">
+ <p class="pull-left visible-phone"><a href="#offcanvas-left-sidebar" class="btn btn-primary btn-offcanvas" data-toggle="offcanvas"><i class="glyphicon-resize-horizontal"></i></a></p>
+ <div class="row">
+ <div class="col-span-6">
+ <h2>Heading</h2>
+ <p>Donec id elit non mi porta gravida at eget metus. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Etiam porta sem malesuada magna mollis euismod. Donec sed odio dui. </p>
+ <p><a class="btn" href="#">View details &raquo;</a></p>
+ </div>
+ <div class="col-span-6">
+ <h2>Heading</h2>
+ <p>Donec id elit non mi porta gravida at eget metus. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Etiam porta sem malesuada magna mollis euismod. Donec sed odio dui. </p>
+ <p><a class="btn" href="#">View details &raquo;</a></p>
+ </div>
+ </div>
+ </div>
+ </div> <!-- /offcanvas-left-example -->
+ </div>
+
+ <hr class="bs-docs-separator">
+
+ <h2>Usage</h2>
+
+ <h3>Via data attributes</h3>
+ <p>Build a simple row with two columns, designate the content column and sidebar column, and put a button or a link with <code>class="btn-offcanvas"</code> and <code>data-toggle="offcanvas"</code> inside the content column.</p>
+<pre class="prettyprint linenums">
+&lt;div class="row row-offcanvas row-offcanvas-right"&gt;
+ &lt;div class="col-span-9"&gt;
+ &lt;p class="pull-right visible-phone"&gt;
+ &lt;a href="#offcanvas-right-sidebar" class="btn btn-offcanvas" data-toggle="offcanvas"&gt;&lt;i class="glyphicon-resize-horizontal"&gt;&lt;/i&gt;&lt;/a&gt;
+ &lt;/p&gt;
+ ...
+ &lt;/div&gt;
+ &lt;nav class="col-span-3 sidebar-offcanvas" id="offcanvas-right-sidebar" role="navigation"&gt;
+ ...
+ &lt;/nav&gt;
+&lt;/div&gt;
+</pre>
+
+ <h3>Via JavaScript</h3>
+ <p>Call the off canvas via JavaScript:</p>
+ <pre class="prettyprint linenums">$('.btn-offcanvas').offcanvas()</pre>
+
+ <h3>Options</h3>
+ <p><em>None</em></p>
+
+ <h3>Methods</h3>
+ <h4>$().offcanvas('toggle')</h4>
+ <p>A programmatic api for toggling the sidebar and making it slide in or out.</p>
+ </section>
+
+
+
+
</div><!-- /.container -->
<a href="#welcome" class="bs-docs-top">
View
147 docs/examples/off-canvas.html
@@ -0,0 +1,147 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <title>Bootstrap, from Twitter</title>
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <meta name="description" content="">
+ <meta name="author" content="">
+
+ <!-- Le styles -->
+ <link href="../assets/css/bootstrap.css" rel="stylesheet">
+
+ <style>
+ body {
+ padding-top: 80px; /* 80px to make the container go all the way to the bottom of the topbar */
+ }
+ </style>
+
+ <!-- Le HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
+ <!--[if lt IE 9]>
+ <script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
+ <script src="assets/js/respond/respond.min.js"></script>
+ <![endif]-->
+
+ <!-- Fav and touch icons -->
+ <link rel="apple-touch-icon-precomposed" sizes="144x144" href="bootstrap/ico/apple-touch-icon-144-precomposed.png">
+ <link rel="apple-touch-icon-precomposed" sizes="114x114" href="bootstrap/ico/apple-touch-icon-114-precomposed.png">
+ <link rel="apple-touch-icon-precomposed" sizes="72x72" href="bootstrap/ico/apple-touch-icon-72-precomposed.png">
+ <link rel="apple-touch-icon-precomposed" href="bootstrap/ico/apple-touch-icon-57-precomposed.png">
+ <link rel="shortcut icon" href="bootstrap/ico/favicon.png">
+ </head>
+
+ <body>
+ <div class="navbar navbar-inverse navbar-fixed-top">
+ <div class="navbar-inner">
+ <div class="container">
+ <a class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
+ <span class="icon-bar"></span>
+ <span class="icon-bar"></span>
+ <span class="icon-bar"></span>
+ </a>
+ <a class="brand" href="#">Project name</a>
+ <div class="nav-collapse collapse">
+ <ul class="nav">
+ <li class="active"><a href="#">Home</a></li>
+ <li><a href="#about">About</a></li>
+ <li><a href="#contact">Contact</a></li>
+ </ul>
+ </div><!--/.nav-collapse -->
+ </div>
+ </div>
+ </div>
+ <div class="container">
+ <div class="row row-offcanvas row-offcanvas-right">
+ <div class="span9">
+ <p class="pull-right visible-phone"><a href="#sidebar" class="btn btn-primary btn-offcanvas" data-toggle="offcanvas"><i class="glyphicon-resize-horizontal"></i></a></p>
+ <div class="jumbotron">
+ <h1>Hello, world!</h1>
+ <p>This is an example to show the potential of an offcanvas layout pattern in Bootstrap. Try some responsive-range viewport sizes to see it in action.</p>
+ </div>
+ <div class="row">
+ <div class="span4">
+ <h2>Heading</h2>
+ <p>Donec id elit non mi porta gravida at eget metus. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Etiam porta sem malesuada magna mollis euismod. Donec sed odio dui. </p>
+ <p><a class="btn" href="#">View details &raquo;</a></p>
+ </div><!--/span-->
+ <div class="span4">
+ <h2>Heading</h2>
+ <p>Donec id elit non mi porta gravida at eget metus. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Etiam porta sem malesuada magna mollis euismod. Donec sed odio dui. </p>
+ <p><a class="btn" href="#">View details &raquo;</a></p>
+ </div><!--/span-->
+ <div class="span4">
+ <h2>Heading</h2>
+ <p>Donec id elit non mi porta gravida at eget metus. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Etiam porta sem malesuada magna mollis euismod. Donec sed odio dui. </p>
+ <p><a class="btn" href="#">View details &raquo;</a></p>
+ </div><!--/span-->
+ </div><!--/row-->
+ <div class="row">
+ <div class="span4">
+ <h2>Heading</h2>
+ <p>Donec id elit non mi porta gravida at eget metus. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Etiam porta sem malesuada magna mollis euismod. Donec sed odio dui. </p>
+ <p><a class="btn" href="#">View details &raquo;</a></p>
+ </div><!--/span-->
+ <div class="span4">
+ <h2>Heading</h2>
+ <p>Donec id elit non mi porta gravida at eget metus. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Etiam porta sem malesuada magna mollis euismod. Donec sed odio dui. </p>
+ <p><a class="btn" href="#">View details &raquo;</a></p>
+ </div><!--/span-->
+ <div class="span4">
+ <h2>Heading</h2>
+ <p>Donec id elit non mi porta gravida at eget metus. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Etiam porta sem malesuada magna mollis euismod. Donec sed odio dui. </p>
+ <p><a class="btn" href="#">View details &raquo;</a></p>
+ </div><!--/span-->
+ </div><!--/row-->
+ </div><!--/span-->
+ <nav class="span3 sidebar-offcanvas" id="sidebar" role="navigation">
+ <div class="well sidebar-nav">
+ <ul class="nav nav-list">
+ <li class="nav-header">Sidebar</li>
+ <li class="active"><a href="#">Link</a></li>
+ <li><a href="#">Link</a></li>
+ <li><a href="#">Link</a></li>
+ <li><a href="#">Link</a></li>
+ <li class="nav-header">Sidebar</li>
+ <li><a href="#">Link</a></li>
+ <li><a href="#">Link</a></li>
+ <li><a href="#">Link</a></li>
+ <li><a href="#">Link</a></li>
+ <li><a href="#">Link</a></li>
+ <li><a href="#">Link</a></li>
+ <li class="nav-header">Sidebar</li>
+ <li><a href="#">Link</a></li>
+ <li><a href="#">Link</a></li>
+ <li><a href="#">Link</a></li>
+ </ul>
+ </div><!--/.well -->
+ </nav><!--/span-->
+ </div><!--/row-->
+
+ <hr>
+
+ <footer>
+ <p>&copy; Company 2012</p>
+ </footer>
+
+ </div><!--/.container-->
+
+ <!-- Le javascript
+ ================================================== -->
+ <!-- Placed at the end of the document so the pages load faster -->
+ <script src="../assets/js/jquery.js"></script>
+ <script src="../assets/js/bootstrap-transition.js"></script>
+ <script src="../assets/js/bootstrap-alert.js"></script>
+ <script src="../assets/js/bootstrap-modal.js"></script>
+ <script src="../assets/js/bootstrap-dropdown.js"></script>
+ <script src="../assets/js/bootstrap-scrollspy.js"></script>
+ <script src="../assets/js/bootstrap-tab.js"></script>
+ <script src="../assets/js/bootstrap-tooltip.js"></script>
+ <script src="../assets/js/bootstrap-popover.js"></script>
+ <script src="../assets/js/bootstrap-button.js"></script>
+ <script src="../assets/js/bootstrap-collapse.js"></script>
+ <script src="../assets/js/bootstrap-carousel.js"></script>
+ <script src="../assets/js/bootstrap-typeahead.js"></script>
+ <script src="../assets/js/bootstrap-offcanvas.js"></script>
+
+ </body>
+</html>
View
112 js/bootstrap-offcanvas.js
@@ -0,0 +1,112 @@
+/* ============================================================
+ * bootstrap-offcanvas.js v3.0.0
+ * http://twitter.github.com/bootstrap/javascript.html#offcanvas
+ * ============================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============================================================ */
+
+
+!function ($) {
+
+ "use strict"; // jshint ;_;
+
+
+ /* OFFCANVAS CLASS DEFINITION
+ * ========================= */
+
+ var toggle = '[data-toggle=offcanvas]'
+ , Offcanvas = function (element) {
+ var $el = $(element).on('click.offcanvas.data-api', this.toggle)
+ }
+
+ Offcanvas.prototype = {
+
+ constructor: Offcanvas
+
+ , toggle: function (e) {
+ var $this = $(this)
+ , $parent
+
+ $parent = $this.parents('.row-offcanvas')
+
+ $parent.toggleClass('active')
+ $this.toggleClass('active')
+
+ return false
+ }
+
+ , keydown: function (e) {
+ console.log($(this))
+ $(this).toggle
+ // var $this
+ // , $parent
+
+ // if (!/(38|40|27)/.test(e.keyCode)) return
+
+ // $this = $(this)
+
+ // e.preventDefault()
+ // e.stopPropagation()
+
+ // $parent = $this.parents('.row-offcanvas')
+
+ // console.log($parent, $parent.find(toggle))
+ // if ($parent.hasClass('active') && e.keyCode == 27) {
+ // console.log($parent, $parent.find(toggle))
+ // $parent.find(toggle).focus()
+ // }
+
+ // return false;
+
+ }
+
+ }
+
+
+
+ /* OFFCANVAS PLUGIN DEFINITION
+ * ========================== */
+
+ var old = $.fn.offcanvas
+
+ $.fn.offcanvas = function (option) {
+ return this.each(function () {
+ var $this = $(this)
+ , data = $this.data('offcanvas')
+ if (!data) $this.data('offcanvas', (data = new Offcanvas(this)))
+ if (typeof option == 'string') data[option].call($this)
+ })
+ }
+
+ $.fn.offcanvas.Constructor = Offcanvas
+
+
+ /* OFFCANVAS NO CONFLICT
+ * ==================== */
+
+ $.fn.offcanvas.noConflict = function () {
+ $.fn.offcanvas = old
+ return this
+ }
+
+
+ /* APPLY TO OFFCANVAS ELEMENTS
+ * =================================== */
+
+ $(document)
+ .on('click.offcanvas.data-api touchstart.offcanvas.data-api' , toggle, Offcanvas.prototype.toggle)
+ .on('keydown.offcanvas.data-api touchstart.offcanvas.data-api', toggle, Offcanvas.prototype.keydown)
+
+}(window.jQuery);
View
2  js/tests/index.html
@@ -28,6 +28,7 @@
<script src="../../js/bootstrap-popover.js"></script>
<script src="../../js/bootstrap-typeahead.js"></script>
<script src="../../js/bootstrap-affix.js"></script>
+ <script src="../../js/bootstrap-offcanvas.js"></script>
<!-- unit tests -->
<script src="unit/bootstrap-transition.js"></script>
@@ -43,6 +44,7 @@
<script src="unit/bootstrap-popover.js"></script>
<script src="unit/bootstrap-typeahead.js"></script>
<script src="unit/bootstrap-affix.js"></script>
+ <script src="unit/bootstrap-offcanvas.js"></script>
</head>
<body>
<div>
View
84 js/tests/unit/bootstrap-offcanvas.js
@@ -0,0 +1,84 @@
+$(function () {
+
+ module("bootstrap-offcanvas")
+
+ test("should provide no conflict", function () {
+ var offcanvas = $.fn.offcanvas.noConflict()
+ ok(!$.fn.offcanvas, 'offcanvas was set back to undefined (org value)')
+ $.fn.offcanvas = offcanvas
+ })
+
+ test("should be defined on jquery object", function () {
+ ok($(document.body).offcanvas, 'offcanvas method is defined')
+ })
+
+ test("should return element", function () {
+ var el = $("<div />")
+ ok(el.offcanvas()[0] === el[0], 'same element returned')
+ })
+
+ test("should add active class to wrapper if clicked", function () {
+ var offcanvasHTML = '<div class="row row-offcanvas row-offcanvas-right">'
+ + '<div class="span9">'
+ + '<p class="visible-phone"><a href="#sidebar" class="btn btn-offcanvas" data-toggle="offcanvas">More</a></p>'
+ + '<p>Lorem ipsum</p>'
+ + '</div>'
+ + '<nav class="span3 sidebar-offcanvas sidebar-offcanvas-right" id="sidebar" role="navigation">'
+ + '<p>Off Canvas Sidebar</p>'
+ + '</nav>'
+ + '</div>'
+ , offcanvas = $(offcanvasHTML).find('[data-toggle="offcanvas"]').offcanvas().click()
+
+ ok(offcanvas.parents('.row-offcanvas').hasClass('active'), 'active class added on click')
+ })
+
+ test("should add active class to toggler if clicked", function () {
+ var offcanvasHTML = '<div class="row row-offcanvas row-offcanvas-right">'
+ + '<div class="span9">'
+ + '<p class="visible-phone"><a href="#sidebar" class="btn btn-offcanvas" data-toggle="offcanvas">More</a></p>'
+ + '<p>Lorem ipsum</p>'
+ + '</div>'
+ + '<nav class="span3 sidebar-offcanvas sidebar-offcanvas-right" id="sidebar" role="navigation">'
+ + '<p>Off Canvas Sidebar</p>'
+ + '</nav>'
+ + '</div>'
+ , offcanvas = $(offcanvasHTML).find('[data-toggle="offcanvas"]').offcanvas().click()
+
+ ok(offcanvas.hasClass('active'), 'active class added on click')
+ })
+
+ test("should remove active class from wrapper if toggler clicked while active", function () {
+ var offcanvasHTML = '<div class="row row-offcanvas row-offcanvas-right">'
+ + '<div class="span9">'
+ + '<p class="visible-phone"><a href="#sidebar" class="btn btn-offcanvas" data-toggle="offcanvas">More</a></p>'
+ + '<p>Lorem ipsum</p>'
+ + '</div>'
+ + '<nav class="span3 sidebar-offcanvas sidebar-offcanvas-right" id="sidebar" role="navigation">'
+ + '<p>Off Canvas Sidebar</p>'
+ + '</nav>'
+ + '</div>'
+ , offcanvas = $(offcanvasHTML).find('[data-toggle="offcanvas"]').offcanvas().click()
+
+ ok(offcanvas.parents('.row-offcanvas').hasClass('active'), 'active class added on click')
+ offcanvas.click()
+ ok(!offcanvas.parents('.row-offcanvas').hasClass('active'), 'active class still present after click')
+ })
+
+ test("should remove active class from toggler if toggler clicked while active", function () {
+ var offcanvasHTML = '<div class="row row-offcanvas row-offcanvas-right">'
+ + '<div class="span9">'
+ + '<p class="visible-phone"><a href="#sidebar" class="btn btn-offcanvas" data-toggle="offcanvas">More</a></p>'
+ + '<p>Lorem ipsum</p>'
+ + '</div>'
+ + '<nav class="span3 sidebar-offcanvas sidebar-offcanvas-right" id="sidebar" role="navigation">'
+ + '<p>Off Canvas Sidebar</p>'
+ + '</nav>'
+ + '</div>'
+ , offcanvas = $(offcanvasHTML).find('[data-toggle="offcanvas"]').offcanvas().click()
+
+ ok(offcanvas.hasClass('active'), 'active class added on click')
+ offcanvas.click()
+ ok(!offcanvas.hasClass('active'), 'active class still present after click')
+ })
+
+})
View
1  less/bootstrap.less
@@ -58,6 +58,7 @@
@import "accordion.less";
@import "carousel.less";
@import "jumbotron.less";
+@import "offcanvas.less";
// Utility classes
@import "utilities.less"; // Has to be last to override when necessary
View
105 less/offcanvas.less
@@ -0,0 +1,105 @@
+//
+// Off canvas
+// --------------------------------------------------
+.row-offcanvas {
+ position: relative;
+ overflow: hidden;
+ .transition(all 0.25s ease-out);
+}
+.row-offcanvas.active {
+ overflow: visible;
+}
+.row-offcanvas-right {
+ .sidebar-offcanvas {
+ right: -@offcanvas-column-width-base;
+ }
+}
+.row-offcanvas-left {
+ .sidebar-offcanvas {
+ left: -@offcanvas-column-width-base;
+ }
+}
+.row-offcanvas-right.active {
+ right: @offcanvas-column-width-base;
+}
+.row-offcanvas-left.active {
+ left: @offcanvas-column-width-base;
+}
+.sidebar-offcanvas {
+ position: absolute;
+ top: 0;
+}
+
+@media screen and (max-width: 319px) {
+ // This is here instead of outside the media queries because when
+ // we exit "mobile land", the sidebar needs to be able to restore its
+ // originally authored ".spanX" width.
+ .sidebar-offcanvas {
+ width: @offcanvas-column-width-base;
+ }
+}
+
+@media screen and (min-width: 320px) and (max-width: 480px) {
+
+ .row-offcanvas-right {
+ .sidebar-offcanvas {
+ right: -@offcanvas-column-width-phone;
+ }
+ }
+ .row-offcanvas-left {
+ .sidebar-offcanvas {
+ left: -@offcanvas-column-width-phone;
+ }
+ }
+ .row-offcanvas-right.active {
+ right: @offcanvas-column-width-phone;
+ }
+ .row-offcanvas-left.active {
+ left: @offcanvas-column-width-phone;
+ }
+ .sidebar-offcanvas {
+ width: @offcanvas-column-width-phone;
+ }
+
+}
+
+// Landscape phone to tablets
+// --------------------------
+@media screen and (min-width: 481px) and (max-width: 767px) {
+
+ .row-offcanvas-right {
+ .sidebar-offcanvas {
+ right: -@offcanvas-column-width-tablet;
+ }
+ }
+ .row-offcanvas-left {
+ .sidebar-offcanvas {
+ left: -@offcanvas-column-width-tablet;
+ }
+ }
+ .row-offcanvas-right.active {
+ right: @offcanvas-column-width-tablet;
+ }
+ .row-offcanvas-left.active {
+ left: @offcanvas-column-width-tablet;
+ }
+ .sidebar-offcanvas {
+ width: @offcanvas-column-width-tablet;
+ }
+
+}
+
+// Tablets & above
+//----------------
+@media screen and (min-width: 768px) {
+
+ .row-offcanvas {
+ position: static;
+ overflow: visible;
+ }
+
+ .sidebar-offcanvas {
+ position: static;
+ }
+
+}
View
7 less/variables.less
@@ -261,6 +261,13 @@
@popover-arrow-outer-width: (@popover-arrow-width + 1);
@popover-arrow-outer-color: rgba(0,0,0,.25);
+// Off canvas
+// -------------------------
+// 60px is the old @grid-column-width - waiting for a better solution
+@offcanvas-column-width-base: 60px * 3.5; // very small phones like 240x320 HTC Wildfire
+@offcanvas-column-width-phone: 60px * 4.5;
+@offcanvas-column-width-tablet: 60px * 8;
+
// Labels
// -------------------------
Something went wrong with that request. Please try again.