├── .gitignore ├── .travis.yml ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── dist ├── css │ ├── astro.css │ └── astro.min.css └── js │ ├── astro.js │ └── astro.min.js ├── docs ├── dist │ ├── css │ │ ├── astro.css │ │ └── astro.min.css │ └── js │ │ ├── astro.js │ │ └── astro.min.js └── index.html ├── gulpfile.js ├── package.json └── src ├── docs ├── _templates │ ├── _footer.html │ └── _header.html └── index.md ├── js └── astro.js └── sass ├── _config.scss ├── _mixins.scss ├── astro.scss └── components └── _astro.scss /.gitignore: -------------------------------------------------------------------------------- 1 | # Node 2 | node_modules 3 | test/results 4 | test/coverage 5 | 6 | ## OS X 7 | .DS_Store 8 | ._* 9 | .Spotlight-V100 10 | .Trashes -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.10" 4 | before_script: 5 | - npm install -g gulp 6 | script: gulp -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to contribute 2 | 3 | I'm delighted that you're helping make this open source project better. Here are a few quick guidelines to make this an easier and better process for everyone. 4 | 5 | 6 | 7 | ## Reporting a bug 8 | 9 | First, **make sure the bug hasn't already been reported** by searching GitHub's issues section. 10 | 11 | If no existing issue exists, go ahead and create one. **Please be sure to include all of the following:** 12 | 13 | 1. A clear, descriptive title (ie. "A bug" is *not* a good title). 14 | 2. [A reduced test case.](https://css-tricks.com/reduced-test-cases/) In order to debug code issues, I need to see actual code in a browser. 15 | 3. The browser and OS that you're using. 16 | 17 | 18 | 19 | ## Asking a question 20 | 21 | Before asking, please **make sure the question hasn't already been asked** by searching GitHub's issues section. 22 | 23 | 24 | 25 | ## Submitting a Pull Request 26 | 27 | Please make sure your code meets the following code standards: 28 | 29 | - Camel case for JavaScript variables. 30 | - [Object-Oriented CSS](http://www.slideshare.net/stubbornella/object-oriented-css) for CSS selectors. 31 | - Favor readable code over brevity. The build process will reduce size, so opt for readability. (ex. `var navigation` is better than `var n`) 32 | - Order CSS properties alphabetically. 33 | - Hard tabs. 34 | 35 | Before submitting, make sure that you've: 36 | 37 | - Updated the version number using semantic versioning. 38 | - Made your changes in the `src` folder. 39 | - Run the Gulp build to compile, minify, and update version numbers into the `dist` folder. If you cannot do this, please note this in the Pull Request. -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # The MIT License (MIT) 2 | 3 | Copyright (c) Go Make Things, LLC 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Astro [![Build Status](https://travis-ci.org/cferdinandi/astro.svg)](https://travis-ci.org/cferdinandi/astro) 2 | A mobile-first navigation pattern, with an optional expand-and-collapse menu on small screens. 3 | 4 | [Download Astro](https://github.com/cferdinandi/astro/archive/master.zip) / [View the demo](http://cferdinandi.github.io/astro/) 5 | 6 | 7 |
8 | 9 | ### Want to learn how to write your own vanilla JS plugins? Check out ["The Vanilla JS Guidebook"](https://gomakethings.com/vanilla-js-guidebook/) and level-up as a web developer. 🚀 10 | 11 |
12 | 13 | 14 | 15 | ## Getting Started 16 | 17 | Compiled and production-ready code can be found in the `dist` directory. The `src` directory contains development code. 18 | 19 | ### 1. Include Astro on your site. 20 | 21 | ```html 22 | 23 | 24 | ``` 25 | 26 | The optional expand-and-collapse menu on smaller screens requires `astro.js`. Basic versions can omit this file. 27 | 28 | ### 2. Add the markup to your HTML. 29 | 30 | Make sure that the `[data-nav-toggle]` value matches the ID of the `.nav-menu` section. To activate expand-and-collapse functionality, add the `.nav-collapse` class to the `.nav-wrap` element. 31 | 32 | ```html 33 | 43 | ``` 44 | 45 | 46 | ### 3. Initialize Astro. 47 | 48 | If you're using the expand-and-collapse menu for smaller screens, initialize Astro in the footer of your page, after the content. And that's it, you're done. Nice work! 49 | 50 | ```html 51 | 54 | ``` 55 | 56 | 57 | 58 | ## Installing with Package Managers 59 | 60 | You can install Astro with your favorite package manager. 61 | 62 | * **NPM:** `npm install cferdinandi/astro` 63 | * **Bower:** `bower install https://github.com/cferdinandi/astro.git` 64 | * **Component:** `component install cferdinandi/astro` 65 | 66 | 67 | 68 | ## Working with the Source Files 69 | 70 | If you would prefer, you can work with the development code in the `src` directory using the included [Gulp build system](http://gulpjs.com/). This compiles, lints, and minifies code. 71 | 72 | ### Dependencies 73 | Make sure these are installed first. 74 | 75 | * [Node.js](http://nodejs.org) 76 | * [Gulp](http://gulpjs.com) `sudo npm install -g gulp` 77 | 78 | ### Quick Start 79 | 80 | 1. In bash/terminal/command line, `cd` into your project directory. 81 | 2. Run `npm install` to install required files. 82 | 3. When it's done installing, run one of the task runners to get going: 83 | * `gulp` manually compiles files. 84 | * `gulp watch` automatically compiles files and applies changes using [LiveReload](http://livereload.com/). 85 | 86 | 87 | 88 | ## Active Link Styling 89 | 90 | There's a placeholder in the CSS to add styling to the current page in the navigation menu. 91 | 92 | ```css 93 | /* Placeholder for active link styling */ 94 | /* .nav > li.active > a { */ 95 | /* Add your styles here */ 96 | /* } */ 97 | ``` 98 | 99 | ***Note:*** *If you're using WordPress, check out [this great tutorial by Todd Motto](http://www.toddmotto.com/highlight-your-current-page-with-wordpress-conditionals) on how to add the `.active` class using a simple PHP function.* 100 | 101 | 102 | 103 | ## Options and Settings 104 | 105 | Astro includes smart defaults and works right out of the box. But if you want to customize things, it also has a robust API that provides multiple ways for you to adjust the default options and settings. 106 | 107 | ### Global Settings 108 | 109 | You can pass options and callbacks into Astro through the `init()` function: 110 | 111 | ```javascript 112 | astro.init({ 113 | selector: '[data-nav-toggle]', // Navigation toggle selector 114 | toggleActiveClass: 'active', // Class added to active dropdown toggles on small screens 115 | navActiveClass: 'active', // Class added to active dropdown content areas on small screens 116 | initClass: 'js-astro', // Class added to `` element when initiated 117 | callback: function ( toggle, navID ) {} // Function that's run after a dropdown is toggled 118 | }); 119 | ``` 120 | 121 | ***Note:*** *If you change the `selector`, you still need to include the `[data-nav-toggle]` attribute in order to pass in the selector for the navigation menu.* 122 | 123 | ### Use Astro events in your own scripts 124 | 125 | You can also call Astro's navigation toggle event in your own scripts. 126 | 127 | #### toggleNav() 128 | Expand or collapse a navigation menu. 129 | 130 | ```javascript 131 | astro.toggleNav( 132 | toggle, // Node that toggles the dropdown action. ex. document.querySelector('#toggle') 133 | navID, // ID of the navigation content wrapper. ex. '#nav-menu' 134 | options, // Classes and callbacks. Same options as those passed into the init() function. 135 | event // Optional, if a DOM event was triggered. 136 | ); 137 | ``` 138 | 139 | **Example** 140 | 141 | ```javascript 142 | astro.toggleNav( null, '#nav-menu' ); 143 | ``` 144 | 145 | #### destroy() 146 | Destroy the current `astro.init()`. This is called automatically during the init function to remove any existing initializations. 147 | 148 | ```javascript 149 | astro.destroy(); 150 | ``` 151 | 152 | 153 | ## Browser Compatibility 154 | 155 | Astro works in all modern browsers, and IE 10 and above. You can extend browser support back to IE 9 with the [classList.js polyfill](https://github.com/eligrey/classList.js/). 156 | 157 | Astro is built with modern JavaScript APIs, and uses progressive enhancement. If the JavaScript file fails to load, or if your site is viewed on older and less capable browsers, the Basic navigation patterns will be displayed instead of the Plus versions. 158 | 159 | 160 | 161 | ## How to Contribute 162 | 163 | In lieu of a formal style guide, take care to maintain the existing coding style. Please apply fixes to both the development and production code. Don't forget to update the version number, and when applicable, the documentation. 164 | 165 | 166 | 167 | ## License 168 | 169 | The code is available under the [MIT License](LICENSE.md). -------------------------------------------------------------------------------- /dist/css/astro.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Astro v10.2.0: Mobile-first navigation patterns 3 | * (c) 2016 Chris Ferdinandi 4 | * MIT License 5 | * http://github.com/cferdinandi/astro 6 | */ 7 | 8 | /** 9 | * Text alignment 10 | */ 11 | /* line 4, /Users/cferdinandi/Sites/astro/src/sass/components/_astro.scss */ 12 | .nav-wrap { 13 | text-align: center; 14 | } 15 | 16 | @media (max-width: 40em) { 17 | /* line 8, /Users/cferdinandi/Sites/astro/src/sass/components/_astro.scss */ 18 | .js-astro .nav-wrap.nav-collapse { 19 | text-align: left; 20 | } 21 | } 22 | 23 | @media (min-width: 40em) { 24 | /* line 4, /Users/cferdinandi/Sites/astro/src/sass/components/_astro.scss */ 25 | .nav-wrap { 26 | text-align: left; 27 | } 28 | } 29 | 30 | /** 31 | * Logo 32 | */ 33 | /* line 22, /Users/cferdinandi/Sites/astro/src/sass/components/_astro.scss */ 34 | .logo { 35 | color: #272727; 36 | display: inline-block; 37 | font-size: 1.2em; 38 | line-height: 1.2; 39 | margin-bottom: 0.5em; 40 | text-decoration: none; 41 | } 42 | 43 | /* line 30, /Users/cferdinandi/Sites/astro/src/sass/components/_astro.scss */ 44 | .logo:hover { 45 | color: #272727; 46 | text-decoration: none; 47 | } 48 | 49 | @media (min-width: 40em) { 50 | /* line 36, /Users/cferdinandi/Sites/astro/src/sass/components/_astro.scss */ 51 | .js-astro .nav-collapse .logo { 52 | float: left; 53 | } 54 | } 55 | 56 | @media (min-width: 40em) { 57 | /* line 22, /Users/cferdinandi/Sites/astro/src/sass/components/_astro.scss */ 58 | .logo { 59 | float: left; 60 | } 61 | } 62 | 63 | /** 64 | * Navigation Menu Container 65 | */ 66 | @media (max-width: 40em) { 67 | /* line 53, /Users/cferdinandi/Sites/astro/src/sass/components/_astro.scss */ 68 | .js-astro .nav-collapse .nav-menu { 69 | box-sizing: border-box; 70 | display: none; 71 | width: 100%; 72 | } 73 | /* line 58, /Users/cferdinandi/Sites/astro/src/sass/components/_astro.scss */ 74 | .js-astro .nav-collapse .nav-menu.active { 75 | display: block; 76 | } 77 | /* line 62, /Users/cferdinandi/Sites/astro/src/sass/components/_astro.scss */ 78 | .js-astro .nav-collapse .nav-menu li { 79 | display: block; 80 | width: 100%; 81 | padding-top: 0.25em; 82 | padding-bottom: 0.25em; 83 | box-sizing: border-box; 84 | } 85 | } 86 | 87 | /** 88 | * Navigation Menu 89 | */ 90 | /* line 77, /Users/cferdinandi/Sites/astro/src/sass/components/_astro.scss */ 91 | .nav { 92 | list-style: none; 93 | margin: 0 -0.5em; 94 | padding: 0; 95 | } 96 | 97 | /* line 82, /Users/cferdinandi/Sites/astro/src/sass/components/_astro.scss */ 98 | .nav > li { 99 | display: inline-block; 100 | float: none; 101 | margin-left: 0.5em; 102 | margin-right: 0.5em; 103 | } 104 | 105 | @media (max-width: 40em) { 106 | /* line 93, /Users/cferdinandi/Sites/astro/src/sass/components/_astro.scss */ 107 | .js-astro .nav-collapse .nav { 108 | text-align: left; 109 | } 110 | } 111 | 112 | @media (min-width: 40em) { 113 | /* line 77, /Users/cferdinandi/Sites/astro/src/sass/components/_astro.scss */ 114 | .nav { 115 | text-align: right; 116 | } 117 | } 118 | 119 | /** 120 | * Navigation Toggle 121 | */ 122 | /* line 107, /Users/cferdinandi/Sites/astro/src/sass/components/_astro.scss */ 123 | .nav-toggle { 124 | display: none; 125 | visibility: hidden; 126 | } 127 | 128 | @media (max-width: 40em) { 129 | /* line 112, /Users/cferdinandi/Sites/astro/src/sass/components/_astro.scss */ 130 | .js-astro .nav-collapse .nav-toggle { 131 | display: block; 132 | float: right; 133 | visibility: visible; 134 | } 135 | } 136 | 137 | /** 138 | * Clearfix 139 | */ 140 | /* line 125, /Users/cferdinandi/Sites/astro/src/sass/components/_astro.scss */ 141 | .nav-wrap:before, 142 | .nav-wrap:after { 143 | display: table; 144 | content: ""; 145 | } 146 | 147 | /* line 131, /Users/cferdinandi/Sites/astro/src/sass/components/_astro.scss */ 148 | .nav-wrap:after { 149 | clear: both; 150 | } 151 | -------------------------------------------------------------------------------- /dist/css/astro.min.css: -------------------------------------------------------------------------------- 1 | /*! Astro v10.2.0 | (c) 2016 Chris Ferdinandi | MIT License | http://github.com/cferdinandi/astro */ 2 | .nav-wrap{text-align:center}@media (max-width:40em){.js-astro .nav-wrap.nav-collapse{text-align:left}}@media (min-width:40em){.nav-wrap{text-align:left}}.logo{display:inline-block;font-size:1.2em;line-height:1.2;margin-bottom:.5em}.logo,.logo:hover{color:#272727;text-decoration:none}@media (min-width:40em){.js-astro .nav-collapse .logo,.logo{float:left}}@media (max-width:40em){.js-astro .nav-collapse .nav-menu{box-sizing:border-box;display:none;width:100%}.js-astro .nav-collapse .nav-menu.active{display:block}.js-astro .nav-collapse .nav-menu li{display:block;width:100%;padding-top:.25em;padding-bottom:.25em;box-sizing:border-box}}.nav{list-style:none;margin:0 -.5em;padding:0}.nav>li{display:inline-block;float:none;margin-left:.5em;margin-right:.5em}@media (max-width:40em){.js-astro .nav-collapse .nav{text-align:left}}@media (min-width:40em){.nav{text-align:right}}.nav-toggle{display:none;visibility:hidden}@media (max-width:40em){.js-astro .nav-collapse .nav-toggle{display:block;float:right;visibility:visible}}.nav-wrap:after,.nav-wrap:before{display:table;content:""}.nav-wrap:after{clear:both} -------------------------------------------------------------------------------- /dist/js/astro.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Astro v10.2.0: Mobile-first navigation patterns 3 | * (c) 2016 Chris Ferdinandi 4 | * MIT License 5 | * http://github.com/cferdinandi/astro 6 | */ 7 | 8 | (function (root, factory) { 9 | if ( typeof define === 'function' && define.amd ) { 10 | define([], factory(root)); 11 | } else if ( typeof exports === 'object' ) { 12 | module.exports = factory(root); 13 | } else { 14 | root.astro = factory(root); 15 | } 16 | })(typeof global !== 'undefined' ? global : this.window || this.global, (function (root) { 17 | 18 | 'use strict'; 19 | 20 | // 21 | // Variables 22 | // 23 | 24 | var astro = {}; // Object for public APIs 25 | var supports = 'querySelector' in document && 'addEventListener' in root && 'classList' in document.createElement('_'); // Feature test 26 | var settings; 27 | 28 | // Default settings 29 | var defaults = { 30 | selector: '[data-nav-toggle]', 31 | toggleActiveClass: 'active', 32 | navActiveClass: 'active', 33 | initClass: 'js-astro', 34 | callback: function () {} 35 | }; 36 | 37 | 38 | // 39 | // Methods 40 | // 41 | 42 | /** 43 | * Merge defaults with user options 44 | * @private 45 | * @param {Object} defaults Default settings 46 | * @param {Object} options User options 47 | * @returns {Object} Merged values of defaults and options 48 | */ 49 | var extend = function () { 50 | 51 | // Variables 52 | var extended = {}; 53 | var deep = false; 54 | var i = 0; 55 | var length = arguments.length; 56 | 57 | // Check if a deep merge 58 | if ( Object.prototype.toString.call( arguments[0] ) === '[object Boolean]' ) { 59 | deep = arguments[0]; 60 | i++; 61 | } 62 | 63 | // Merge the object into the extended object 64 | var merge = function (obj) { 65 | for ( var prop in obj ) { 66 | if ( Object.prototype.hasOwnProperty.call( obj, prop ) ) { 67 | // If deep merge and property is an object, merge properties 68 | if ( deep && Object.prototype.toString.call(obj[prop]) === '[object Object]' ) { 69 | extended[prop] = buoy.extend( true, extended[prop], obj[prop] ); 70 | } else { 71 | extended[prop] = obj[prop]; 72 | } 73 | } 74 | } 75 | }; 76 | 77 | // Loop through each object and conduct a merge 78 | for ( ; i < length; i++ ) { 79 | var obj = arguments[i]; 80 | merge(obj); 81 | } 82 | 83 | return extended; 84 | 85 | }; 86 | 87 | /** 88 | * Get the closest matching element up the DOM tree. 89 | * @private 90 | * @param {Element} elem Starting element 91 | * @param {String} selector Selector to match against 92 | * @return {Boolean|Element} Returns null if not match found 93 | */ 94 | var getClosest = function ( elem, selector ) { 95 | 96 | // Element.matches() polyfill 97 | if (!Element.prototype.matches) { 98 | Element.prototype.matches = 99 | Element.prototype.matchesSelector || 100 | Element.prototype.mozMatchesSelector || 101 | Element.prototype.msMatchesSelector || 102 | Element.prototype.oMatchesSelector || 103 | Element.prototype.webkitMatchesSelector || 104 | function(s) { 105 | var matches = (this.document || this.ownerDocument).querySelectorAll(s), 106 | i = matches.length; 107 | while (--i >= 0 && matches.item(i) !== this) {} 108 | return i > -1; 109 | }; 110 | } 111 | 112 | // Get closest match 113 | for ( ; elem && elem !== document; elem = elem.parentNode ) { 114 | if ( elem.matches( selector ) ) return elem; 115 | } 116 | 117 | return null; 118 | 119 | }; 120 | 121 | /** 122 | * Show and hide navigation menu 123 | * @public 124 | * @param {Element} toggle Element that triggered the toggle 125 | * @param {String} navID The ID of the navigation element to toggle 126 | * @param {Object} settings 127 | * @param {Event} event 128 | */ 129 | astro.toggleNav = function ( toggle, navID, options, event ) { 130 | 131 | // Selectors and variables 132 | var settings = extend( settings || defaults, options || {} ); // Merge user options with defaults 133 | var nav = document.querySelector(navID); 134 | 135 | toggle.classList.toggle( settings.toggleActiveClass ); // Toggle the '.active' class on the toggle element 136 | nav.classList.toggle( settings.navActiveClass ); // Toggle the '.active' class on the menu 137 | settings.callback( toggle, navID ); // Run callbacks after toggling nav 138 | 139 | }; 140 | 141 | /** 142 | * Handle click event methods 143 | * @private 144 | */ 145 | var eventHandler = function (event) { 146 | var toggle = getClosest(event.target, settings.selector); 147 | if ( toggle ) { 148 | // Prevent default click event 149 | if ( toggle.tagName.toLowerCase() === 'a') { 150 | event.preventDefault(); 151 | } 152 | // Toggle nav 153 | astro.toggleNav( toggle, toggle.getAttribute('data-nav-toggle'), settings ); 154 | } 155 | }; 156 | 157 | /** 158 | * Destroy the current initialization. 159 | * @public 160 | */ 161 | astro.destroy = function () { 162 | if ( !settings ) return; 163 | document.documentElement.classList.remove( settings.initClass ); 164 | document.removeEventListener('click', eventHandler, false); 165 | settings = null; 166 | }; 167 | 168 | /** 169 | * Initialize Astro 170 | * @public 171 | * @param {Object} options User settings 172 | */ 173 | astro.init = function ( options ) { 174 | 175 | // feature test 176 | if ( !supports ) return; 177 | 178 | // Destroy any existing initializations 179 | astro.destroy(); 180 | 181 | // Selectors and variables 182 | settings = extend( defaults, options || {} ); // Merge user options with defaults 183 | 184 | // Listeners and methods 185 | document.documentElement.classList.add( settings.initClass ); // Add class to HTML element to activate conditional CSS 186 | document.addEventListener('click', eventHandler, false); // Listen for click events and run event handler 187 | 188 | }; 189 | 190 | 191 | // 192 | // Public APIs 193 | // 194 | 195 | return astro; 196 | 197 | })); -------------------------------------------------------------------------------- /dist/js/astro.min.js: -------------------------------------------------------------------------------- 1 | /*! Astro v10.2.0 | (c) 2016 Chris Ferdinandi | MIT License | http://github.com/cferdinandi/astro */ 2 | !(function(t,e){"function"==typeof define&&define.amd?define([],e(t)):"object"==typeof exports?module.exports=e(t):t.astro=e(t)})("undefined"!=typeof global?global:this.window||this.global,(function(t){"use strict";var e,o={},n="querySelector"in document&&"addEventListener"in t&&"classList"in document.createElement("_"),c={selector:"[data-nav-toggle]",toggleActiveClass:"active",navActiveClass:"active",initClass:"js-astro",callback:function(){}},l=function(){var t={},e=!1,o=0,n=arguments.length;"[object Boolean]"===Object.prototype.toString.call(arguments[0])&&(e=arguments[0],o++);for(var c=function(o){for(var n in o)Object.prototype.hasOwnProperty.call(o,n)&&(e&&"[object Object]"===Object.prototype.toString.call(o[n])?t[n]=buoy.extend(!0,t[n],o[n]):t[n]=o[n])};o=0&&e.item(o)!==this;);return o>-1});t&&t!==document;t=t.parentNode)if(t.matches(e))return t;return null};o.toggleNav=function(t,e,o,n){var r=l(r||c,o||{}),a=document.querySelector(e);t.classList.toggle(r.toggleActiveClass),a.classList.toggle(r.navActiveClass),r.callback(t,e)};var a=function(t){var n=r(t.target,e.selector);n&&("a"===n.tagName.toLowerCase()&&t.preventDefault(),o.toggleNav(n,n.getAttribute("data-nav-toggle"),e))};return o.destroy=function(){e&&(document.documentElement.classList.remove(e.initClass),document.removeEventListener("click",a,!1),e=null)},o.init=function(t){n&&(o.destroy(),e=l(c,t||{}),document.documentElement.classList.add(e.initClass),document.addEventListener("click",a,!1))},o})); -------------------------------------------------------------------------------- /docs/dist/css/astro.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Astro v10.2.0: Mobile-first navigation patterns 3 | * (c) 2016 Chris Ferdinandi 4 | * MIT License 5 | * http://github.com/cferdinandi/astro 6 | */ 7 | 8 | /** 9 | * Text alignment 10 | */ 11 | /* line 4, /Users/cferdinandi/Sites/astro/src/sass/components/_astro.scss */ 12 | .nav-wrap { 13 | text-align: center; 14 | } 15 | 16 | @media (max-width: 40em) { 17 | /* line 8, /Users/cferdinandi/Sites/astro/src/sass/components/_astro.scss */ 18 | .js-astro .nav-wrap.nav-collapse { 19 | text-align: left; 20 | } 21 | } 22 | 23 | @media (min-width: 40em) { 24 | /* line 4, /Users/cferdinandi/Sites/astro/src/sass/components/_astro.scss */ 25 | .nav-wrap { 26 | text-align: left; 27 | } 28 | } 29 | 30 | /** 31 | * Logo 32 | */ 33 | /* line 22, /Users/cferdinandi/Sites/astro/src/sass/components/_astro.scss */ 34 | .logo { 35 | color: #272727; 36 | display: inline-block; 37 | font-size: 1.2em; 38 | line-height: 1.2; 39 | margin-bottom: 0.5em; 40 | text-decoration: none; 41 | } 42 | 43 | /* line 30, /Users/cferdinandi/Sites/astro/src/sass/components/_astro.scss */ 44 | .logo:hover { 45 | color: #272727; 46 | text-decoration: none; 47 | } 48 | 49 | @media (min-width: 40em) { 50 | /* line 36, /Users/cferdinandi/Sites/astro/src/sass/components/_astro.scss */ 51 | .js-astro .nav-collapse .logo { 52 | float: left; 53 | } 54 | } 55 | 56 | @media (min-width: 40em) { 57 | /* line 22, /Users/cferdinandi/Sites/astro/src/sass/components/_astro.scss */ 58 | .logo { 59 | float: left; 60 | } 61 | } 62 | 63 | /** 64 | * Navigation Menu Container 65 | */ 66 | @media (max-width: 40em) { 67 | /* line 53, /Users/cferdinandi/Sites/astro/src/sass/components/_astro.scss */ 68 | .js-astro .nav-collapse .nav-menu { 69 | box-sizing: border-box; 70 | display: none; 71 | width: 100%; 72 | } 73 | /* line 58, /Users/cferdinandi/Sites/astro/src/sass/components/_astro.scss */ 74 | .js-astro .nav-collapse .nav-menu.active { 75 | display: block; 76 | } 77 | /* line 62, /Users/cferdinandi/Sites/astro/src/sass/components/_astro.scss */ 78 | .js-astro .nav-collapse .nav-menu li { 79 | display: block; 80 | width: 100%; 81 | padding-top: 0.25em; 82 | padding-bottom: 0.25em; 83 | box-sizing: border-box; 84 | } 85 | } 86 | 87 | /** 88 | * Navigation Menu 89 | */ 90 | /* line 77, /Users/cferdinandi/Sites/astro/src/sass/components/_astro.scss */ 91 | .nav { 92 | list-style: none; 93 | margin: 0 -0.5em; 94 | padding: 0; 95 | } 96 | 97 | /* line 82, /Users/cferdinandi/Sites/astro/src/sass/components/_astro.scss */ 98 | .nav > li { 99 | display: inline-block; 100 | float: none; 101 | margin-left: 0.5em; 102 | margin-right: 0.5em; 103 | } 104 | 105 | @media (max-width: 40em) { 106 | /* line 93, /Users/cferdinandi/Sites/astro/src/sass/components/_astro.scss */ 107 | .js-astro .nav-collapse .nav { 108 | text-align: left; 109 | } 110 | } 111 | 112 | @media (min-width: 40em) { 113 | /* line 77, /Users/cferdinandi/Sites/astro/src/sass/components/_astro.scss */ 114 | .nav { 115 | text-align: right; 116 | } 117 | } 118 | 119 | /** 120 | * Navigation Toggle 121 | */ 122 | /* line 107, /Users/cferdinandi/Sites/astro/src/sass/components/_astro.scss */ 123 | .nav-toggle { 124 | display: none; 125 | visibility: hidden; 126 | } 127 | 128 | @media (max-width: 40em) { 129 | /* line 112, /Users/cferdinandi/Sites/astro/src/sass/components/_astro.scss */ 130 | .js-astro .nav-collapse .nav-toggle { 131 | display: block; 132 | float: right; 133 | visibility: visible; 134 | } 135 | } 136 | 137 | /** 138 | * Clearfix 139 | */ 140 | /* line 125, /Users/cferdinandi/Sites/astro/src/sass/components/_astro.scss */ 141 | .nav-wrap:before, 142 | .nav-wrap:after { 143 | display: table; 144 | content: ""; 145 | } 146 | 147 | /* line 131, /Users/cferdinandi/Sites/astro/src/sass/components/_astro.scss */ 148 | .nav-wrap:after { 149 | clear: both; 150 | } 151 | -------------------------------------------------------------------------------- /docs/dist/css/astro.min.css: -------------------------------------------------------------------------------- 1 | /*! Astro v10.2.0 | (c) 2016 Chris Ferdinandi | MIT License | http://github.com/cferdinandi/astro */ 2 | .nav-wrap{text-align:center}@media (max-width:40em){.js-astro .nav-wrap.nav-collapse{text-align:left}}@media (min-width:40em){.nav-wrap{text-align:left}}.logo{display:inline-block;font-size:1.2em;line-height:1.2;margin-bottom:.5em}.logo,.logo:hover{color:#272727;text-decoration:none}@media (min-width:40em){.js-astro .nav-collapse .logo,.logo{float:left}}@media (max-width:40em){.js-astro .nav-collapse .nav-menu{box-sizing:border-box;display:none;width:100%}.js-astro .nav-collapse .nav-menu.active{display:block}.js-astro .nav-collapse .nav-menu li{display:block;width:100%;padding-top:.25em;padding-bottom:.25em;box-sizing:border-box}}.nav{list-style:none;margin:0 -.5em;padding:0}.nav>li{display:inline-block;float:none;margin-left:.5em;margin-right:.5em}@media (max-width:40em){.js-astro .nav-collapse .nav{text-align:left}}@media (min-width:40em){.nav{text-align:right}}.nav-toggle{display:none;visibility:hidden}@media (max-width:40em){.js-astro .nav-collapse .nav-toggle{display:block;float:right;visibility:visible}}.nav-wrap:after,.nav-wrap:before{display:table;content:""}.nav-wrap:after{clear:both} -------------------------------------------------------------------------------- /docs/dist/js/astro.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Astro v10.2.0: Mobile-first navigation patterns 3 | * (c) 2016 Chris Ferdinandi 4 | * MIT License 5 | * http://github.com/cferdinandi/astro 6 | */ 7 | 8 | (function (root, factory) { 9 | if ( typeof define === 'function' && define.amd ) { 10 | define([], factory(root)); 11 | } else if ( typeof exports === 'object' ) { 12 | module.exports = factory(root); 13 | } else { 14 | root.astro = factory(root); 15 | } 16 | })(typeof global !== 'undefined' ? global : this.window || this.global, (function (root) { 17 | 18 | 'use strict'; 19 | 20 | // 21 | // Variables 22 | // 23 | 24 | var astro = {}; // Object for public APIs 25 | var supports = 'querySelector' in document && 'addEventListener' in root && 'classList' in document.createElement('_'); // Feature test 26 | var settings; 27 | 28 | // Default settings 29 | var defaults = { 30 | selector: '[data-nav-toggle]', 31 | toggleActiveClass: 'active', 32 | navActiveClass: 'active', 33 | initClass: 'js-astro', 34 | callback: function () {} 35 | }; 36 | 37 | 38 | // 39 | // Methods 40 | // 41 | 42 | /** 43 | * Merge defaults with user options 44 | * @private 45 | * @param {Object} defaults Default settings 46 | * @param {Object} options User options 47 | * @returns {Object} Merged values of defaults and options 48 | */ 49 | var extend = function () { 50 | 51 | // Variables 52 | var extended = {}; 53 | var deep = false; 54 | var i = 0; 55 | var length = arguments.length; 56 | 57 | // Check if a deep merge 58 | if ( Object.prototype.toString.call( arguments[0] ) === '[object Boolean]' ) { 59 | deep = arguments[0]; 60 | i++; 61 | } 62 | 63 | // Merge the object into the extended object 64 | var merge = function (obj) { 65 | for ( var prop in obj ) { 66 | if ( Object.prototype.hasOwnProperty.call( obj, prop ) ) { 67 | // If deep merge and property is an object, merge properties 68 | if ( deep && Object.prototype.toString.call(obj[prop]) === '[object Object]' ) { 69 | extended[prop] = buoy.extend( true, extended[prop], obj[prop] ); 70 | } else { 71 | extended[prop] = obj[prop]; 72 | } 73 | } 74 | } 75 | }; 76 | 77 | // Loop through each object and conduct a merge 78 | for ( ; i < length; i++ ) { 79 | var obj = arguments[i]; 80 | merge(obj); 81 | } 82 | 83 | return extended; 84 | 85 | }; 86 | 87 | /** 88 | * Get the closest matching element up the DOM tree. 89 | * @private 90 | * @param {Element} elem Starting element 91 | * @param {String} selector Selector to match against 92 | * @return {Boolean|Element} Returns null if not match found 93 | */ 94 | var getClosest = function ( elem, selector ) { 95 | 96 | // Element.matches() polyfill 97 | if (!Element.prototype.matches) { 98 | Element.prototype.matches = 99 | Element.prototype.matchesSelector || 100 | Element.prototype.mozMatchesSelector || 101 | Element.prototype.msMatchesSelector || 102 | Element.prototype.oMatchesSelector || 103 | Element.prototype.webkitMatchesSelector || 104 | function(s) { 105 | var matches = (this.document || this.ownerDocument).querySelectorAll(s), 106 | i = matches.length; 107 | while (--i >= 0 && matches.item(i) !== this) {} 108 | return i > -1; 109 | }; 110 | } 111 | 112 | // Get closest match 113 | for ( ; elem && elem !== document; elem = elem.parentNode ) { 114 | if ( elem.matches( selector ) ) return elem; 115 | } 116 | 117 | return null; 118 | 119 | }; 120 | 121 | /** 122 | * Show and hide navigation menu 123 | * @public 124 | * @param {Element} toggle Element that triggered the toggle 125 | * @param {String} navID The ID of the navigation element to toggle 126 | * @param {Object} settings 127 | * @param {Event} event 128 | */ 129 | astro.toggleNav = function ( toggle, navID, options, event ) { 130 | 131 | // Selectors and variables 132 | var settings = extend( settings || defaults, options || {} ); // Merge user options with defaults 133 | var nav = document.querySelector(navID); 134 | 135 | toggle.classList.toggle( settings.toggleActiveClass ); // Toggle the '.active' class on the toggle element 136 | nav.classList.toggle( settings.navActiveClass ); // Toggle the '.active' class on the menu 137 | settings.callback( toggle, navID ); // Run callbacks after toggling nav 138 | 139 | }; 140 | 141 | /** 142 | * Handle click event methods 143 | * @private 144 | */ 145 | var eventHandler = function (event) { 146 | var toggle = getClosest(event.target, settings.selector); 147 | if ( toggle ) { 148 | // Prevent default click event 149 | if ( toggle.tagName.toLowerCase() === 'a') { 150 | event.preventDefault(); 151 | } 152 | // Toggle nav 153 | astro.toggleNav( toggle, toggle.getAttribute('data-nav-toggle'), settings ); 154 | } 155 | }; 156 | 157 | /** 158 | * Destroy the current initialization. 159 | * @public 160 | */ 161 | astro.destroy = function () { 162 | if ( !settings ) return; 163 | document.documentElement.classList.remove( settings.initClass ); 164 | document.removeEventListener('click', eventHandler, false); 165 | settings = null; 166 | }; 167 | 168 | /** 169 | * Initialize Astro 170 | * @public 171 | * @param {Object} options User settings 172 | */ 173 | astro.init = function ( options ) { 174 | 175 | // feature test 176 | if ( !supports ) return; 177 | 178 | // Destroy any existing initializations 179 | astro.destroy(); 180 | 181 | // Selectors and variables 182 | settings = extend( defaults, options || {} ); // Merge user options with defaults 183 | 184 | // Listeners and methods 185 | document.documentElement.classList.add( settings.initClass ); // Add class to HTML element to activate conditional CSS 186 | document.addEventListener('click', eventHandler, false); // Listen for click events and run event handler 187 | 188 | }; 189 | 190 | 191 | // 192 | // Public APIs 193 | // 194 | 195 | return astro; 196 | 197 | })); -------------------------------------------------------------------------------- /docs/dist/js/astro.min.js: -------------------------------------------------------------------------------- 1 | /*! Astro v10.2.0 | (c) 2016 Chris Ferdinandi | MIT License | http://github.com/cferdinandi/astro */ 2 | !(function(t,e){"function"==typeof define&&define.amd?define([],e(t)):"object"==typeof exports?module.exports=e(t):t.astro=e(t)})("undefined"!=typeof global?global:this.window||this.global,(function(t){"use strict";var e,o={},n="querySelector"in document&&"addEventListener"in t&&"classList"in document.createElement("_"),c={selector:"[data-nav-toggle]",toggleActiveClass:"active",navActiveClass:"active",initClass:"js-astro",callback:function(){}},l=function(){var t={},e=!1,o=0,n=arguments.length;"[object Boolean]"===Object.prototype.toString.call(arguments[0])&&(e=arguments[0],o++);for(var c=function(o){for(var n in o)Object.prototype.hasOwnProperty.call(o,n)&&(e&&"[object Object]"===Object.prototype.toString.call(o[n])?t[n]=buoy.extend(!0,t[n],o[n]):t[n]=o[n])};o=0&&e.item(o)!==this;);return o>-1});t&&t!==document;t=t.parentNode)if(t.matches(e))return t;return null};o.toggleNav=function(t,e,o,n){var r=l(r||c,o||{}),a=document.querySelector(e);t.classList.toggle(r.toggleActiveClass),a.classList.toggle(r.navActiveClass),r.callback(t,e)};var a=function(t){var n=r(t.target,e.selector);n&&("a"===n.tagName.toLowerCase()&&t.preventDefault(),o.toggleNav(n,n.getAttribute("data-nav-toggle"),e))};return o.destroy=function(){e&&(document.documentElement.classList.remove(e.initClass),document.removeEventListener("click",a,!1),e=null)},o.init=function(t){n&&(o.destroy(),e=l(c,t||{}),document.documentElement.classList.add(e.initClass),document.addEventListener("click",a,!1))},o})); -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Astro 7 | 8 | 9 | 10 | 11 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 30 | 31 |
32 |



33 |

Simple

34 | 43 | 44 |



45 |

Expand-and-Collapse

46 | 56 | 57 |




58 | 59 |
60 |
61 | 62 | 63 | 64 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Gulp Packages 3 | */ 4 | 5 | // General 6 | var gulp = require('gulp'); 7 | var fs = require('fs'); 8 | var del = require('del'); 9 | var lazypipe = require('lazypipe'); 10 | var plumber = require('gulp-plumber'); 11 | var flatten = require('gulp-flatten'); 12 | var tap = require('gulp-tap'); 13 | var rename = require('gulp-rename'); 14 | var header = require('gulp-header'); 15 | var footer = require('gulp-footer'); 16 | var watch = require('gulp-watch'); 17 | var livereload = require('gulp-livereload'); 18 | var package = require('./package.json'); 19 | 20 | // Scripts 21 | var jshint = require('gulp-jshint'); 22 | var stylish = require('jshint-stylish'); 23 | var concat = require('gulp-concat'); 24 | var uglify = require('gulp-uglify'); 25 | var optimizejs = require('gulp-optimize-js'); 26 | 27 | // Styles 28 | var sass = require('gulp-sass'); 29 | var prefix = require('gulp-autoprefixer'); 30 | var minify = require('gulp-cssnano'); 31 | 32 | // Docs 33 | var markdown = require('gulp-markdown'); 34 | var fileinclude = require('gulp-file-include'); 35 | 36 | 37 | /** 38 | * Paths to project folders 39 | */ 40 | 41 | var paths = { 42 | input: 'src/**/*', 43 | output: 'dist/', 44 | scripts: { 45 | input: 'src/js/*', 46 | output: 'dist/js/' 47 | }, 48 | styles: { 49 | input: 'src/sass/**/*.{scss,sass}', 50 | output: 'dist/css/' 51 | }, 52 | docs: { 53 | input: 'src/docs/*.{html,md,markdown}', 54 | output: 'docs/', 55 | templates: 'src/docs/_templates/', 56 | assets: 'src/docs/assets/**' 57 | } 58 | }; 59 | 60 | 61 | /** 62 | * Template for banner to add to file headers 63 | */ 64 | 65 | var banner = { 66 | full : 67 | '/*!\n' + 68 | ' * <%= package.name %> v<%= package.version %>: <%= package.description %>\n' + 69 | ' * (c) ' + new Date().getFullYear() + ' <%= package.author.name %>\n' + 70 | ' * MIT License\n' + 71 | ' * <%= package.repository.url %>\n' + 72 | ' */\n\n', 73 | min : 74 | '/*!' + 75 | ' <%= package.name %> v<%= package.version %>' + 76 | ' | (c) ' + new Date().getFullYear() + ' <%= package.author.name %>' + 77 | ' | MIT License' + 78 | ' | <%= package.repository.url %>' + 79 | ' */\n' 80 | }; 81 | 82 | 83 | /** 84 | * Gulp Taks 85 | */ 86 | 87 | // Lint, minify, and concatenate scripts 88 | gulp.task('build:scripts', ['clean:dist'], function() { 89 | var jsTasks = lazypipe() 90 | .pipe(header, banner.full, { package : package }) 91 | .pipe(optimizejs) 92 | .pipe(gulp.dest, paths.scripts.output) 93 | .pipe(rename, { suffix: '.min' }) 94 | .pipe(uglify) 95 | .pipe(optimizejs) 96 | .pipe(header, banner.min, { package : package }) 97 | .pipe(gulp.dest, paths.scripts.output); 98 | 99 | return gulp.src(paths.scripts.input) 100 | .pipe(plumber()) 101 | .pipe(tap(function (file, t) { 102 | if ( file.isDirectory() ) { 103 | var name = file.relative + '.js'; 104 | return gulp.src(file.path + '/*.js') 105 | .pipe(concat(name)) 106 | .pipe(jsTasks()); 107 | } 108 | })) 109 | .pipe(jsTasks()); 110 | }); 111 | 112 | // Process, lint, and minify Sass files 113 | gulp.task('build:styles', ['clean:dist'], function() { 114 | return gulp.src(paths.styles.input) 115 | .pipe(plumber()) 116 | .pipe(sass({ 117 | outputStyle: 'expanded', 118 | sourceComments: true 119 | })) 120 | .pipe(flatten()) 121 | .pipe(prefix({ 122 | browsers: ['last 2 version', '> 1%'], 123 | cascade: true, 124 | remove: true 125 | })) 126 | .pipe(header(banner.full, { package : package })) 127 | .pipe(gulp.dest(paths.styles.output)) 128 | .pipe(rename({ suffix: '.min' })) 129 | .pipe(minify({ 130 | discardComments: { 131 | removeAll: true 132 | } 133 | })) 134 | .pipe(header(banner.min, { package : package })) 135 | .pipe(gulp.dest(paths.styles.output)); 136 | }); 137 | 138 | // Lint scripts 139 | gulp.task('lint:scripts', function () { 140 | return gulp.src(paths.scripts.input) 141 | .pipe(plumber()) 142 | .pipe(jshint()) 143 | .pipe(jshint.reporter('jshint-stylish')); 144 | }); 145 | 146 | // Remove pre-existing content from output folder 147 | gulp.task('clean:dist', function () { 148 | del.sync([ 149 | paths.output 150 | ]); 151 | }); 152 | 153 | // Generate documentation 154 | gulp.task('build:docs', ['compile', 'clean:docs'], function() { 155 | return gulp.src(paths.docs.input) 156 | .pipe(plumber()) 157 | .pipe(fileinclude({ 158 | prefix: '@@', 159 | basepath: '@file' 160 | })) 161 | .pipe(tap(function (file, t) { 162 | if ( /\.md|\.markdown/.test(file.path) ) { 163 | return t.through(markdown); 164 | } 165 | })) 166 | .pipe(header(fs.readFileSync(paths.docs.templates + '/_header.html', 'utf8'))) 167 | .pipe(footer(fs.readFileSync(paths.docs.templates + '/_footer.html', 'utf8'))) 168 | .pipe(gulp.dest(paths.docs.output)); 169 | }); 170 | 171 | // Copy distribution files to docs 172 | gulp.task('copy:dist', ['compile', 'clean:docs'], function() { 173 | return gulp.src(paths.output + '/**') 174 | .pipe(plumber()) 175 | .pipe(gulp.dest(paths.docs.output + '/dist')); 176 | }); 177 | 178 | // Copy documentation assets to docs 179 | gulp.task('copy:assets', ['clean:docs'], function() { 180 | return gulp.src(paths.docs.assets) 181 | .pipe(plumber()) 182 | .pipe(gulp.dest(paths.docs.output + '/assets')); 183 | }); 184 | 185 | // Remove prexisting content from docs folder 186 | gulp.task('clean:docs', function () { 187 | return del.sync(paths.docs.output); 188 | }); 189 | 190 | // Spin up livereload server and listen for file changes 191 | gulp.task('listen', function () { 192 | livereload.listen(); 193 | gulp.watch(paths.input).on('change', function(file) { 194 | gulp.start('default'); 195 | gulp.start('refresh'); 196 | }); 197 | }); 198 | 199 | // Run livereload after file change 200 | gulp.task('refresh', ['compile', 'docs'], function () { 201 | livereload.changed(); 202 | }); 203 | 204 | 205 | /** 206 | * Task Runners 207 | */ 208 | 209 | // Compile files 210 | gulp.task('compile', [ 211 | 'lint:scripts', 212 | 'clean:dist', 213 | 'build:scripts', 214 | 'build:styles' 215 | ]); 216 | 217 | // Generate documentation 218 | gulp.task('docs', [ 219 | 'clean:docs', 220 | 'build:docs', 221 | 'copy:dist', 222 | 'copy:assets' 223 | ]); 224 | 225 | // Compile files and generate docs (default) 226 | gulp.task('default', [ 227 | 'compile', 228 | 'docs' 229 | ]); 230 | 231 | // Compile files and generate docs when something changes 232 | gulp.task('watch', [ 233 | 'listen', 234 | 'default' 235 | ]); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Astro", 3 | "version": "10.2.0", 4 | "description": "Mobile-first navigation patterns", 5 | "main": "./dist/js/astro.min.js", 6 | "author": { 7 | "name": "Chris Ferdinandi", 8 | "url": "http://gomakethings.com" 9 | }, 10 | "license": "MIT", 11 | "repository": { 12 | "type": "git", 13 | "url": "http://github.com/cferdinandi/astro" 14 | }, 15 | "devDependencies": { 16 | "gulp": "^3.9.1", 17 | "node-fs": "^0.1.7", 18 | "del": "^2.2.0", 19 | "lazypipe": "^1.0.1", 20 | "gulp-plumber": "^1.1.0", 21 | "gulp-flatten": "^0.3.1", 22 | "gulp-tap": "^0.1.3", 23 | "gulp-rename": "^1.2.2", 24 | "gulp-header": "^1.8.8", 25 | "gulp-footer": "^1.0.5", 26 | "gulp-watch": "^4.3.9", 27 | "gulp-livereload": "^3.8.1", 28 | "gulp-jshint": "^2.0.1", 29 | "jshint-stylish": "^2.2.1", 30 | "gulp-concat": "^2.6.0", 31 | "gulp-uglify": "^2.0.0", 32 | "gulp-optimize-js": "^1.0.2", 33 | "gulp-sass": "^2.3.2", 34 | "gulp-cssnano": "^2.1.2", 35 | "gulp-autoprefixer": "^3.1.1", 36 | "gulp-svgmin": "^1.2.3", 37 | "gulp-markdown": "^1.2.0", 38 | "gulp-file-include": "^0.14.0" 39 | } 40 | } -------------------------------------------------------------------------------- /src/docs/_templates/_footer.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/docs/_templates/_header.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Astro 7 | 8 | 9 | 10 | 11 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 30 | 31 |
32 | -------------------------------------------------------------------------------- /src/docs/index.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

Simple

4 | 13 | 14 |

15 | 16 |

Expand-and-Collapse

17 | 27 | 28 |


-------------------------------------------------------------------------------- /src/js/astro.js: -------------------------------------------------------------------------------- 1 | (function (root, factory) { 2 | if ( typeof define === 'function' && define.amd ) { 3 | define([], factory(root)); 4 | } else if ( typeof exports === 'object' ) { 5 | module.exports = factory(root); 6 | } else { 7 | root.astro = factory(root); 8 | } 9 | })(typeof global !== 'undefined' ? global : this.window || this.global, function (root) { 10 | 11 | 'use strict'; 12 | 13 | // 14 | // Variables 15 | // 16 | 17 | var astro = {}; // Object for public APIs 18 | var supports = 'querySelector' in document && 'addEventListener' in root && 'classList' in document.createElement('_'); // Feature test 19 | var settings; 20 | 21 | // Default settings 22 | var defaults = { 23 | selector: '[data-nav-toggle]', 24 | toggleActiveClass: 'active', 25 | navActiveClass: 'active', 26 | initClass: 'js-astro', 27 | callback: function () {} 28 | }; 29 | 30 | 31 | // 32 | // Methods 33 | // 34 | 35 | /** 36 | * Merge defaults with user options 37 | * @private 38 | * @param {Object} defaults Default settings 39 | * @param {Object} options User options 40 | * @returns {Object} Merged values of defaults and options 41 | */ 42 | var extend = function () { 43 | 44 | // Variables 45 | var extended = {}; 46 | var deep = false; 47 | var i = 0; 48 | var length = arguments.length; 49 | 50 | // Check if a deep merge 51 | if ( Object.prototype.toString.call( arguments[0] ) === '[object Boolean]' ) { 52 | deep = arguments[0]; 53 | i++; 54 | } 55 | 56 | // Merge the object into the extended object 57 | var merge = function (obj) { 58 | for ( var prop in obj ) { 59 | if ( Object.prototype.hasOwnProperty.call( obj, prop ) ) { 60 | // If deep merge and property is an object, merge properties 61 | if ( deep && Object.prototype.toString.call(obj[prop]) === '[object Object]' ) { 62 | extended[prop] = buoy.extend( true, extended[prop], obj[prop] ); 63 | } else { 64 | extended[prop] = obj[prop]; 65 | } 66 | } 67 | } 68 | }; 69 | 70 | // Loop through each object and conduct a merge 71 | for ( ; i < length; i++ ) { 72 | var obj = arguments[i]; 73 | merge(obj); 74 | } 75 | 76 | return extended; 77 | 78 | }; 79 | 80 | /** 81 | * Get the closest matching element up the DOM tree. 82 | * @private 83 | * @param {Element} elem Starting element 84 | * @param {String} selector Selector to match against 85 | * @return {Boolean|Element} Returns null if not match found 86 | */ 87 | var getClosest = function ( elem, selector ) { 88 | 89 | // Element.matches() polyfill 90 | if (!Element.prototype.matches) { 91 | Element.prototype.matches = 92 | Element.prototype.matchesSelector || 93 | Element.prototype.mozMatchesSelector || 94 | Element.prototype.msMatchesSelector || 95 | Element.prototype.oMatchesSelector || 96 | Element.prototype.webkitMatchesSelector || 97 | function(s) { 98 | var matches = (this.document || this.ownerDocument).querySelectorAll(s), 99 | i = matches.length; 100 | while (--i >= 0 && matches.item(i) !== this) {} 101 | return i > -1; 102 | }; 103 | } 104 | 105 | // Get closest match 106 | for ( ; elem && elem !== document; elem = elem.parentNode ) { 107 | if ( elem.matches( selector ) ) return elem; 108 | } 109 | 110 | return null; 111 | 112 | }; 113 | 114 | /** 115 | * Show and hide navigation menu 116 | * @public 117 | * @param {Element} toggle Element that triggered the toggle 118 | * @param {String} navID The ID of the navigation element to toggle 119 | * @param {Object} settings 120 | * @param {Event} event 121 | */ 122 | astro.toggleNav = function ( toggle, navID, options, event ) { 123 | 124 | // Selectors and variables 125 | var settings = extend( settings || defaults, options || {} ); // Merge user options with defaults 126 | var nav = document.querySelector(navID); 127 | 128 | toggle.classList.toggle( settings.toggleActiveClass ); // Toggle the '.active' class on the toggle element 129 | nav.classList.toggle( settings.navActiveClass ); // Toggle the '.active' class on the menu 130 | settings.callback( toggle, navID ); // Run callbacks after toggling nav 131 | 132 | }; 133 | 134 | /** 135 | * Handle click event methods 136 | * @private 137 | */ 138 | var eventHandler = function (event) { 139 | var toggle = getClosest(event.target, settings.selector); 140 | if ( toggle ) { 141 | // Prevent default click event 142 | if ( toggle.tagName.toLowerCase() === 'a') { 143 | event.preventDefault(); 144 | } 145 | // Toggle nav 146 | astro.toggleNav( toggle, toggle.getAttribute('data-nav-toggle'), settings ); 147 | } 148 | }; 149 | 150 | /** 151 | * Destroy the current initialization. 152 | * @public 153 | */ 154 | astro.destroy = function () { 155 | if ( !settings ) return; 156 | document.documentElement.classList.remove( settings.initClass ); 157 | document.removeEventListener('click', eventHandler, false); 158 | settings = null; 159 | }; 160 | 161 | /** 162 | * Initialize Astro 163 | * @public 164 | * @param {Object} options User settings 165 | */ 166 | astro.init = function ( options ) { 167 | 168 | // feature test 169 | if ( !supports ) return; 170 | 171 | // Destroy any existing initializations 172 | astro.destroy(); 173 | 174 | // Selectors and variables 175 | settings = extend( defaults, options || {} ); // Merge user options with defaults 176 | 177 | // Listeners and methods 178 | document.documentElement.classList.add( settings.initClass ); // Add class to HTML element to activate conditional CSS 179 | document.addEventListener('click', eventHandler, false); // Listen for click events and run event handler 180 | 181 | }; 182 | 183 | 184 | // 185 | // Public APIs 186 | // 187 | 188 | return astro; 189 | 190 | }); -------------------------------------------------------------------------------- /src/sass/_config.scss: -------------------------------------------------------------------------------- 1 | // @section Configuration Settings 2 | // Adjust colors, font stacks, breakpoints and sizing. 3 | // 4 | // Typographic Scale 5 | // (For math purposes. Actual font sizes in ems.) 6 | // 1px, 4px, 5px, 8px, 9px, 11px, 12px, 13px, 15px, 16px, 19px, 21px, 24px, 28px, 32px, 48px, 64px, 80px, 96px 7 | // line height: 1.5em on small screens, 1.5625em on big screens 8 | 9 | 10 | // Colors 11 | $color-primary: #0088cc; 12 | $color-black: #272727; 13 | $color-white: #ffffff; 14 | 15 | $color-info: #0088cc; // Blue 16 | $color-success: #377f31; // Green 17 | $color-danger: #880e14; // Red 18 | $color-warning: #dba909; // Yellow 19 | $color-code: #dd1144; 20 | 21 | $color-gray-dark: #808080; 22 | $color-gray-light: #e5e5e5; 23 | 24 | 25 | // Font Stacks 26 | $font-primary: "Helvetica Neue", Arial, sans-serif; 27 | $font-secondary: Georgia, Times, serif; 28 | $font-monospace: Menlo, Monaco, "Courier New", monospace; 29 | 30 | 31 | // Breakpoints 32 | $bp-xsmall: 20em; 33 | $bp-small: 30em; 34 | $bp-medium: 40em; 35 | $bp-large: 60em; 36 | $bp-xlarge: 80em; 37 | 38 | 39 | // Sizing 40 | $font-size-base: 100%; 41 | $font-size-xlarge-screens: 125%; 42 | $spacing: 1.5625em; 43 | $container-width: 88%; 44 | $container-max-width: 80em; 45 | 46 | 47 | // Grid 48 | $grid-margins: 1.4%; 49 | $grid-sizes: ( 50 | // Grid width options 51 | // Add/remove grid's as needed 52 | // $name: $width 53 | // $name - {string} class suffix 54 | // $width - {string} width of the grid 55 | fourth: 25%, 56 | third: 33.33333333333%, 57 | half: 50%, 58 | two-thirds: 66.666666666667%, 59 | three-fourths: 75%, 60 | full: 100% 61 | ); 62 | $grid-breakpoints: ( 63 | // Breakpoints at which to activate grid 64 | // Add/remove breakpoints as needed 65 | // ($breakpoint, $prefix-class, $include-offsets) 66 | // $breakpoint - {string|variable} the breakpoint 67 | // $prefix-class - {string|optional} class to be used with `.row` to activate grid 68 | // $include-offsets - {boolean} if true, include offset classes at this breakpoint 69 | ($bp-xsmall, ".row-start-xsmall", false), 70 | ($bp-small, ".row-start-small", false), 71 | ($bp-medium, null, true), 72 | ); 73 | $grid-dynamic: ( 74 | // Create grid classes that vary in size at different breakpoints 75 | // Add/remove classes, breakpoints, and sizes as needed 76 | // ($class, $breakpoint, $width) 77 | // $class - {string} the grid class 78 | // $breakpoint - {string|variable} the breakpoint 79 | // $width - {string|variable} width of the grid at the breakpoint 80 | (".grid-dynamic", $bp-xsmall, map-get($grid-sizes, half)) 81 | (".grid-dynamic", $bp-small, map-get($grid-sizes, third)) 82 | (".grid-dynamic", $bp-medium, map-get($grid-sizes, fourth)) 83 | ); 84 | 85 | 86 | // Icons 87 | $icons: ( 88 | // Generate icon font classes 89 | // Add/remove icons as needed 90 | // $name, $character, $color 91 | // $name - {string} the name of the icon (will be prefixed by `.icon-`) 92 | // $character - {string} the unicode character for the icon 93 | // $color - {string|variable|optional} the hex or named color of the icon 94 | ("logo", "\e600", null), 95 | ); -------------------------------------------------------------------------------- /src/sass/_mixins.scss: -------------------------------------------------------------------------------- 1 | // 2 | // MIXINS & FUNCTIONS 3 | // A few simple Sass helpers. 4 | // 5 | 6 | // @font-face mixin 7 | // Order of the includes matters, and it is: normal, bold, italic, bold+italic. 8 | // Forked from Bourbon. https://github.com/thoughtbot/bourbon/ 9 | @mixin font-face($font-family, $file-path, $weight: normal, $style: normal) { 10 | @font-face { 11 | font-family: $font-family; 12 | font-weight: $weight; 13 | font-style: $style; 14 | 15 | src: url('#{$file-path}.eot'); 16 | src: url('#{$file-path}.eot?#iefix') format('embedded-opentype'), 17 | url('#{$file-path}.woff') format('woff'), 18 | url('#{$file-path}.ttf') format('truetype'), 19 | url('#{$file-path}.svg##{$font-family}') format('svg'); 20 | } 21 | } 22 | 23 | // Strip units from values 24 | @function strip-unit($num) { 25 | @return $num / ($num * 0 + 1); 26 | } 27 | 28 | // Calculate ems from pixels 29 | @function calc-em($px, $base: 16) { 30 | $px: strip-unit($px); 31 | $base: strip-unit($base); 32 | @if $px == 1 { 33 | @return 0.0725em; 34 | } 35 | @return ($px / $base) * 1em; 36 | } -------------------------------------------------------------------------------- /src/sass/astro.scss: -------------------------------------------------------------------------------- 1 | // Import the table styles for compiling 2 | @import "config"; 3 | @import "mixins"; 4 | @import "components/astro"; 5 | -------------------------------------------------------------------------------- /src/sass/components/_astro.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Text alignment 3 | */ 4 | .nav-wrap { 5 | text-align: center; 6 | 7 | @media (max-width: $bp-medium) { 8 | .js-astro &.nav-collapse { 9 | text-align: left; 10 | } 11 | } 12 | 13 | @media (min-width: $bp-medium) { 14 | text-align: left; 15 | } 16 | } 17 | 18 | 19 | /** 20 | * Logo 21 | */ 22 | .logo { 23 | color: $color-black; 24 | display: inline-block; 25 | font-size: 1.2em; 26 | line-height: 1.2; 27 | margin-bottom: calc-em(8px); 28 | text-decoration: none; 29 | 30 | &:hover { 31 | color: $color-black; 32 | text-decoration: none; 33 | } 34 | 35 | @media (min-width: $bp-medium) { 36 | .js-astro .nav-collapse & { 37 | float: left; 38 | } 39 | } 40 | 41 | @media (min-width: $bp-medium) { 42 | float: left; 43 | } 44 | } 45 | 46 | 47 | /** 48 | * Navigation Menu Container 49 | */ 50 | .nav-menu { 51 | 52 | @media (max-width: $bp-medium) { 53 | .js-astro .nav-collapse & { 54 | box-sizing: border-box; 55 | display: none; 56 | width: 100%; 57 | 58 | &.active { 59 | display: block; 60 | } 61 | 62 | li { 63 | display: block; 64 | width: 100%; 65 | padding-top: calc-em(4px); 66 | padding-bottom: calc-em(4px); 67 | box-sizing: border-box; 68 | } 69 | } 70 | } 71 | } 72 | 73 | 74 | /** 75 | * Navigation Menu 76 | */ 77 | .nav { 78 | list-style: none; 79 | margin: 0 calc-em(-8px); 80 | padding: 0; 81 | 82 | & > li { 83 | display: inline-block; 84 | float: none; 85 | margin-left: calc-em(8px); 86 | margin-right: calc-em(8px); 87 | 88 | // Active Link Styling 89 | &.active > a {} 90 | } 91 | 92 | @media (max-width: $bp-medium) { 93 | .js-astro .nav-collapse & { 94 | text-align: left; 95 | } 96 | } 97 | 98 | @media (min-width: $bp-medium) { 99 | text-align: right; 100 | } 101 | } 102 | 103 | 104 | /** 105 | * Navigation Toggle 106 | */ 107 | .nav-toggle { 108 | display: none; 109 | visibility: hidden; 110 | 111 | @media (max-width: $bp-medium) { 112 | .js-astro .nav-collapse & { 113 | display: block; 114 | float: right; 115 | visibility: visible; 116 | } 117 | } 118 | } 119 | 120 | 121 | /** 122 | * Clearfix 123 | */ 124 | 125 | .nav-wrap:before, 126 | .nav-wrap:after { 127 | display: table; 128 | content: ""; 129 | } 130 | 131 | .nav-wrap:after { 132 | clear: both; 133 | } --------------------------------------------------------------------------------