├── .gitignore ├── patterns ├── scss │ ├── mixin │ │ ├── _clearfix.scss │ │ ├── _not-selectable.scss │ │ ├── _triangle.scss │ │ ├── _typography.scss │ │ └── _breakpoint.scss │ ├── app-mq.scss │ ├── function │ │ ├── _black.scss │ │ └── _white.scss │ ├── app-no-mq.scss │ ├── core │ │ ├── _reset.scss │ │ ├── _typography.scss │ │ ├── _table.scss │ │ ├── _form.scss │ │ └── _common.scss │ ├── content │ │ └── _pattern-library.scss │ ├── _app-loader.scss │ ├── vendor │ │ ├── _prism.scss │ │ └── _normalize.scss │ └── _var.scss ├── config.rb ├── js │ ├── app.js │ ├── modernizr.custom.min.js │ └── enquire.js ├── index.html ├── user-interface.html ├── typography.html └── tables.html ├── issues └── README.md ├── pivots ├── 10-comments.md ├── 04-checkout-ux.md ├── 09-retina-icons.md ├── README.md ├── 08-responsive-images.md ├── 03-dry-principles.md ├── 01-mobile-first.md ├── 07-javascript-architecture.md ├── 06-css-pre-processors.md ├── 05-css-architecture.md └── 02-atomic-templates.md ├── charter └── README.md ├── code-style ├── README.md └── phtml │ ├── minicart_original.phtml │ ├── minicart_proposed.phtml │ ├── shipping_original.phtml │ └── shipping_proposed.phtml ├── README.md └── principles └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | css/ 2 | .sass-cache/ 3 | .idea 4 | -------------------------------------------------------------------------------- /patterns/scss/mixin/_clearfix.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Gravity Department 3 | * 4 | * @author Brendan Falkowski (http://gravitydept.com) 5 | * @copyright Copyright 2014 Gravity Department 6 | * @license All rights reserved. 7 | */ 8 | 9 | 10 | // ============================================== 11 | // Mixin - Clearfix 12 | // ============================================== 13 | 14 | @mixin clearfix { 15 | content:''; 16 | display:table; 17 | clear:both; 18 | } 19 | -------------------------------------------------------------------------------- /patterns/scss/app-mq.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Gravity Department 3 | * 4 | * @author Brendan Falkowski (http://gravitydept.com) 5 | * @copyright Copyright 2014 Gravity Department 6 | * @license All rights reserved. 7 | */ 8 | 9 | 10 | // ============================================== 11 | // Import all partials with full media query support. 12 | // ============================================== 13 | 14 | $mq-support: true; 15 | $mq-fixed-value: false; 16 | 17 | @import "app-loader"; 18 | -------------------------------------------------------------------------------- /patterns/scss/function/_black.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Gravity Department 3 | * 4 | * @author Brendan Falkowski (http://gravitydept.com) 5 | * @copyright Copyright 2014 Gravity Department 6 | * @license All rights reserved. 7 | */ 8 | 9 | 10 | // ============================================== 11 | // Function - Black 12 | // ============================================== 13 | 14 | @function black ($opacity) { 15 | @return rgba(0, 0, 0, $opacity); 16 | } 17 | 18 | @function black-fallback ($opacity) { 19 | @return lighten(#000, (1 - $opacity)); 20 | } 21 | -------------------------------------------------------------------------------- /patterns/scss/function/_white.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Gravity Department 3 | * 4 | * @author Brendan Falkowski (http://gravitydept.com) 5 | * @copyright Copyright 2014 Gravity Department 6 | * @license All rights reserved. 7 | */ 8 | 9 | 10 | // ============================================== 11 | // Function - White 12 | // ============================================== 13 | 14 | @function white ($opacity) { 15 | @return rgba(255, 255, 255, $opacity); 16 | } 17 | 18 | @function white-fallback ($opacity) { 19 | @return darken(#FFF, (1 - $opacity)); 20 | } 21 | -------------------------------------------------------------------------------- /patterns/scss/mixin/_not-selectable.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Gravity Department 3 | * 4 | * @author Brendan Falkowski (http://gravitydept.com) 5 | * @copyright Copyright 2014 Gravity Department 6 | * @license All rights reserved. 7 | */ 8 | 9 | 10 | // ============================================== 11 | // Mixin - Not Selectable 12 | // ============================================== 13 | 14 | @mixin not-selectable { 15 | -moz-user-select:none; 16 | -ms-user-select:none; 17 | -webkit-user-select:none; 18 | -o-user-select:none; 19 | user-select:none; 20 | } 21 | -------------------------------------------------------------------------------- /patterns/scss/app-no-mq.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Gravity Department 3 | * 4 | * @author Brendan Falkowski (http://gravitydept.com) 5 | * @copyright Copyright 2014 Gravity Department 6 | * @license All rights reserved. 7 | */ 8 | 9 | 10 | // ============================================== 11 | // Use conditional breakpoint mixin for "lte IE8" render applicable content from media queries only. 12 | // See: /scss/mixin/_breakpoint.scss 13 | // ============================================== 14 | 15 | $mq-support: false; 16 | $mq-fixed-value: 1024px; 17 | 18 | // Temporarily disable compilation of fallback stylesheet for non-'media query' browsers. 19 | //@import "app-loader"; 20 | 21 | // IE8 and below fixes 22 | //@import "override/lte-ie8"; 23 | -------------------------------------------------------------------------------- /patterns/config.rb: -------------------------------------------------------------------------------- 1 | # Require any additional compass plugins here. 2 | 3 | # Set this to the root of your project when deployed: 4 | http_path = "/" 5 | css_dir = "css" 6 | sass_dir = "scss" 7 | images_dir = "img" 8 | javascripts_dir = "js" 9 | 10 | #output_style = :expanded 11 | output_style = :compressed 12 | 13 | #environment = :development 14 | environment = :production 15 | 16 | # To enable relative paths to assets via compass helper functions. Uncomment: 17 | relative_assets = true 18 | 19 | # To disable debugging comments that display the original location of your selectors. Uncomment: 20 | # line_comments = false 21 | color_output = false 22 | 23 | 24 | # If you prefer the indented syntax, you might want to regenerate this 25 | # project again passing --syntax sass, or you can uncomment this: 26 | # preferred_syntax = :sass 27 | # and then run: 28 | # sass-convert -R --from scss --to sass scss scss && rm -rf sass && mv scss sass 29 | -------------------------------------------------------------------------------- /issues/README.md: -------------------------------------------------------------------------------- 1 | # Issues 2 | 3 | ### See: https://docs.google.com/spreadsheet/ccc?key=0AgkHIW0ExJHqdGFydXNoQnNFR010MUx3V3V1S1dIbmc 4 | 5 | Issues were logged from a brainstorm of the contributor's nagging code-level frontend annoyances in Magento 1.x, and exploration of the current Magento 2 frontend. 6 | 7 | Rather than flood the Magento 2 repo with issues, we setup a spreadsheet to track everything. We may migrate these into GitHub Issues at some point. 8 | 9 | ### Brainstorm process 10 | 11 | 1. Brainstorm to generate as many ideas as possible (no critiquing). 12 | 1. Discuss each issue and mark duplicates. 13 | 1. Expand rationale with comments and use case (if necessary). 14 | 1. Assign value/priority, ease of implementation, and acceptance chance. 15 | 16 | ### Pull Requests 17 | 18 | Part of the OFS team started making pull requests directly on the Magento 2 repo based on these issues. These are quick wins for improving Magento at the code-level. 19 | 20 | If you have any further suggestions, you can add them to this repo's issue tracker or contribute directly to the Magento 2 repo. -------------------------------------------------------------------------------- /patterns/scss/mixin/_triangle.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Gravity Department 3 | * 4 | * @author Brendan Falkowski (http://gravitydept.com) 5 | * @copyright Copyright 2014 Gravity Department 6 | * @license All rights reserved. 7 | */ 8 | 9 | 10 | // ============================================== 11 | // Mixin - Triangle 12 | // ============================================== 13 | 14 | @mixin triangle ($direction: up, $size: 5px, $color: #000) { 15 | content:''; 16 | position:absolute; 17 | width:0; 18 | height:0; 19 | 20 | @if $direction == up { 21 | border-right: $size solid transparent; 22 | border-left: $size solid transparent; 23 | border-bottom: $size solid $color; 24 | border-top: none; 25 | } 26 | 27 | @if $direction == down { 28 | border-right: $size solid transparent; 29 | border-left: $size solid transparent; 30 | border-top: $size solid $color; 31 | border-bottom: none; 32 | } 33 | 34 | @if $direction == right { 35 | border-top: $size solid transparent; 36 | border-bottom: $size solid transparent; 37 | border-left: $size solid $color; 38 | border-right: none; 39 | } 40 | 41 | @if $direction == left { 42 | border-top: $size solid transparent; 43 | border-bottom: $size solid transparent; 44 | border-right: $size solid $color; 45 | border-left: none; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /pivots/10-comments.md: -------------------------------------------------------------------------------- 1 | # Comments 2 | 3 | ## Write comments in code, and sign comments as Magento. 4 | 5 | Magento should write comments in XML an PHTML templates to make it easier for developers to understand non-obvious things like: 6 | 7 | * Which configurations trigger different states. 8 | * Edge cases (ex: not enough inventory to fulfill order). 9 | * Dependencies from other modules or templates (ex: expecting form parent or global var). 10 | 11 | In PHTML templates, comments must always be written in PHP comments using single-line syntax so users never download them, and they can easily be spanned with multi-line comments. 12 | 13 | Signing comments by company namespace helps developers easily spot their comments from platform or third-party customizations. For example: 14 | 15 | ```php 16 | 21 | = $abc && $response !== null): ?> 22 | 27 |
Something custom here
28 | 29 | ``` 30 | 31 | Magento should never comment out code without explanation. This is fairly prevalent in Magento 1.x, and why/who/when/status of the comment is never clear. -------------------------------------------------------------------------------- /patterns/scss/core/_reset.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Gravity Department 3 | * 4 | * @author Brendan Falkowski (http://gravitydept.com) 5 | * @copyright Copyright 2014 Gravity Department 6 | * @license All rights reserved. 7 | */ 8 | 9 | 10 | // ============================================== 11 | // RESET 12 | // ============================================== 13 | 14 | *, 15 | *:before, 16 | *:after { 17 | @include box-sizing (border-box); 18 | margin:0; 19 | padding:0; 20 | outline:0; 21 | } 22 | 23 | html { 24 | -webkit-tap-highlight-color:black(0); // Prevent tap highlight on iOS/Android 25 | -webkit-text-size-adjust:100%; // Prevent automatic scaling on iOS 26 | } 27 | 28 | body { 29 | background:#FFF; 30 | color:#000; 31 | line-height:1; 32 | } 33 | 34 | html, 35 | body, 36 | img, 37 | fieldset, 38 | abbr, 39 | acronym { 40 | border:0; 41 | } 42 | 43 | h1, 44 | h2, 45 | h3, 46 | h4, 47 | h5, 48 | h6 { 49 | font-size:100%; 50 | font-weight:normal; 51 | } 52 | 53 | th, 54 | code, 55 | cite, 56 | caption { 57 | font-weight:normal; 58 | font-style:normal; 59 | text-align:left; 60 | } 61 | 62 | address { 63 | font-style:normal; 64 | } 65 | 66 | fieldset { 67 | margin:0; 68 | padding:0; 69 | border:0; 70 | } 71 | 72 | img { 73 | display:block; 74 | } 75 | 76 | ol, 77 | ul { 78 | list-style:none; 79 | } 80 | 81 | table { 82 | border-collapse:collapse; 83 | border-spacing:0; 84 | } 85 | 86 | q:before, 87 | q:after { 88 | content:''; 89 | } 90 | -------------------------------------------------------------------------------- /pivots/04-checkout-ux.md: -------------------------------------------------------------------------------- 1 | # Checkout UX 2 | 3 | ## Use (Woven Checkout) not (Accordion + Sidebar) for OnePageCheckout. 4 | 5 | Magento's OPC splits the interaction into two components: 6 | 7 | * Accordion of checkout steps 8 | * Progress sidebar 9 | 10 | As this structure is heavily tied to JS behavior, changing it requires accepting a significant maintenance burden and introduces conflicts with third-party code. 11 | 12 | Redesigning OPC from a mobile-first perspective presents a better solution: woven checkout. As each OPC step is completed its form is collapsed and replaced by the progress summary for that step. This is ideal for multiple reasons: 13 | 14 | 1. **Natural Hierarchy** — the editing interface erodes as the progress interface expands. It's a logical tide exchanging input for feedback. 15 | 1. **Proximity** — there's no interface gap between input, reviewing, and editing. For each step, all the interaction modes occupy the same physical space. This connectedness makes the interface more intuitive and consistent. 16 | 1. **Device Friendly** — Scales up from mobile to desktop without drastic layout transformations. 17 | 18 | In Magento 1.8 / 1.13 the OPC progress sidebar was rewritten to pull each progress state from a separate AJAX request. It should be possible to implement woven checkout without significantly re-architecting OPC. 19 | 20 | The pattern should be adopted in the core because third-parties building checkout extensions need to have a standard to build to. 21 | 22 | #### References 23 | 24 | * https://speakerdeck.com/brendanfalkowski/responsive-ecommerce-part-two?slide=187 -------------------------------------------------------------------------------- /patterns/scss/mixin/_typography.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Gravity Department 3 | * 4 | * @author Brendan Falkowski (http://gravitydept.com) 5 | * @copyright Copyright 2014 Gravity Department 6 | * @license All rights reserved. 7 | */ 8 | 9 | 10 | // ============================================== 11 | // Mixin - Typography 12 | // ============================================== 13 | 14 | @mixin h1 { 15 | margin-bottom:0.5em; 16 | color:$c-h1; 17 | font-family:$f-stack-sans; 18 | font-size:36px; 19 | font-weight:bold; 20 | font-style:normal; 21 | line-height:42px; 22 | text-rendering:optimizeLegibility; 23 | text-transform:none; 24 | } 25 | 26 | @mixin h2 { 27 | margin-bottom:0.5em; 28 | color:$c-h2; 29 | font-family:$f-stack-sans; 30 | font-size:20px; 31 | font-weight:bold; 32 | font-style:normal; 33 | line-height:1.4; 34 | text-rendering:optimizeLegibility; 35 | text-transform:none; 36 | } 37 | 38 | @mixin h3 { 39 | margin-bottom:0.2em; 40 | color:$c-h3; 41 | font-family:$f-stack-sans; 42 | font-size:18px; 43 | font-weight:bold; 44 | font-style:normal; 45 | line-height:1.3333; 46 | text-rendering:optimizeLegibility; 47 | text-transform:none; 48 | } 49 | 50 | @mixin h4 { 51 | margin-bottom:0.2em; 52 | color:$c-h4; 53 | font-family:$f-stack-sans; 54 | font-size:14px; 55 | font-weight:bold; 56 | font-style:normal; 57 | line-height:$line-height; 58 | text-rendering:optimizeSpeed; 59 | text-transform:uppercase; 60 | } 61 | 62 | @mixin h5 { } 63 | 64 | @mixin h6 { } 65 | -------------------------------------------------------------------------------- /pivots/09-retina-icons.md: -------------------------------------------------------------------------------- 1 | # Retina Icons 2 | 3 | ## Use (SVG Sprites + PNG Fallback) or (Retina PNG Sprites) not (Icon Fonts) for icons. 4 | 5 | Icon Fonts are used heavily in Magento 2 to render what are traditionally image-based graphics with resolution independence. 6 | 7 | At least 370 million people use (new and old) mobile browsers that don't support web fonts. Most methods for including icon fonts don't work with screen-readers or require specific markup patterns to be accessible. While it is possible to provide fallbacks and workarounds for unsupported browsers, the complicated history of this feature means many edge cases are still undetected. 8 | 9 | There are equally performant solutions for serving graphical icons at multiple resolutions, and they have more consistent and predictable fallback mechanisms. 10 | 11 | The most universal approach is generating PNG assets at multiple resolutions, and combining them into a sprite sheet which is served at the appropriate resolution using media queries. All browsers (modern + very old) can utilize this method without impacting performance. The only downside is the assets are not truly resolution independent. 12 | 13 | Going one step further, for browsers that support SVG (modern browsers, IE9+, Android 3+) we can serve resolution independent SVGs and generation of PNGs as a fallback. 14 | 15 | #### Further Reading 16 | 17 | * http://filamentgroup.com/lab/bulletproof_icon_fonts/ 18 | * http://dbushell.com/2012/04/03/svg-use-it-already/ 19 | * http://filamentgroup.com/lab/grunticon/ 20 | * http://filamentgroup.com/lab/grumpicon_workflow/ 21 | * http://css-tricks.com/svg-fallbacks/ 22 | * http://toddmotto.com/mastering-svg-use-for-a-retina-web-fallbacks-with-png-script/ -------------------------------------------------------------------------------- /patterns/scss/core/_typography.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Gravity Department 3 | * 4 | * @author Brendan Falkowski (http://gravitydept.com) 5 | * @copyright Copyright 2014 Gravity Department 6 | * @license All rights reserved. 7 | */ 8 | 9 | 10 | // ============================================== 11 | // Base Elements 12 | // ============================================== 13 | 14 | body, 15 | button, 16 | input, 17 | select, 18 | table, 19 | textarea { 20 | font-family:$f-stack-sans; 21 | color:$c-text; 22 | font-size:$f-size; 23 | line-height:$line-height; 24 | } 25 | 26 | a { 27 | color:$c-link; 28 | text-decoration:none; 29 | } 30 | 31 | a:hover { 32 | color:$c-link-hover; 33 | text-decoration:none; 34 | } 35 | 36 | a:focus, 37 | a:active { 38 | outline:0; 39 | color:$c-link-active; 40 | } 41 | 42 | p { 43 | margin:0 0 $margin-bottom; 44 | } 45 | 46 | ol, 47 | ul { 48 | list-style:none; 49 | margin:0; 50 | padding:0; 51 | } 52 | 53 | 54 | 55 | // ============================================== 56 | // Headings 57 | // ============================================== 58 | 59 | h1, 60 | h2, 61 | h3, 62 | h4, 63 | h5, 64 | h6 { 65 | margin:0; 66 | font-family:inherit; 67 | font-weight:normal; 68 | font-style:normal; 69 | } 70 | 71 | h1, 72 | .h1 { 73 | @include h1; 74 | } 75 | 76 | h2, 77 | .h2 { 78 | @include h2; 79 | } 80 | 81 | h3, 82 | .h3 { 83 | @include h3; 84 | } 85 | 86 | h4, 87 | .h4 { 88 | @include h4; 89 | } 90 | 91 | h5, 92 | .h5 { 93 | @include h5; 94 | } 95 | 96 | h6, 97 | .h6 { 98 | @include h6; 99 | } 100 | -------------------------------------------------------------------------------- /pivots/README.md: -------------------------------------------------------------------------------- 1 | # Pivots in Magento 2's Frontend Architecture 2 | 3 | Magento 2's frontend architecture is a combination of new practices and those carried from Magento 1.x. Parts of this frontend architecture / design system are harmful to the present and future needs of Magento's partners and customers. 4 | 5 | This document proposes major architectural changes that Magento must consider to be competitive today, and most importantly extensible for the future. 6 | 7 | These are not trivial corrections. Evaluating and adopting these practices requires not only deep understanding of the subject matter, but commitment from Magento's leadership to prioritize modernizing the frontend. 8 | 9 | ## [1. Mobile First](01-mobile-first.md) 10 | Adopt a mobile-first methodology across the design system. 11 | 12 | ## [2. Atomic Templates](02-atomic-templates.md) 13 | Write templates atomically to contain the smallest possible output. 14 | 15 | ## [3. DRY Principles](03-dry-principles.md) 16 | Embrace DRY (Don't Repeat Yourself) principles consistently. 17 | 18 | ## [4. Checkout UX](04-checkout-ux.md) 19 | Use (Woven Checkout) not (Accordion + Sidebar) for OnePageCheckout. 20 | 21 | ## [5. CSS Architecture](05-css-architecture.md) 22 | Write HTML classes for (OO-CSS) not (chained-classes CSS). 23 | 24 | ## [6. CSS Pre-Processors](06-css-pre-processors.md) 25 | Use (Sass + Compass) not (LESS) for CSS pre-processing. 26 | 27 | ## [7. JavaScript Architecture](07-javascript-architecture.md) 28 | Use (RequireJS) or (Browserify) not (HeadJS) for JS loading architecture. 29 | 30 | ## [8. Responsive Images](08-responsive-images.md) 31 | Use (PictureFill) and plan for the technology to change. 32 | 33 | ## [9. Retina Icons](09-retina-icons.md) 34 | Use (SVG Sprites + PNG Fallback) or (Retina PNG Sprites) not (Icon Fonts) for icons. 35 | 36 | ## [10. Comments](10-comments.md) 37 | Write comments in code, and sign comments as Magento. -------------------------------------------------------------------------------- /patterns/scss/content/_pattern-library.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Gravity Department 3 | * 4 | * @author Brendan Falkowski (http://gravitydept.com) 5 | * @copyright Copyright 2014 Gravity Department 6 | * @license All rights reserved. 7 | */ 8 | 9 | 10 | // ============================================== 11 | // Pattern Library 12 | // ============================================== 13 | 14 | .pattern-library-wrapper { } 15 | 16 | .container { 17 | 18 | } 19 | 20 | .pattern-library-header { 21 | overflow:hidden; 22 | margin-bottom:30px; 23 | padding:0 15px; 24 | background:#333; 25 | color:#FFF; 26 | font-family:'Helvetica Neue', arial, sans-serif; 27 | font-size:16px; 28 | font-weight:300; 29 | line-height:1.2; 30 | } 31 | 32 | .pattern-library-header a { 33 | float:left; 34 | padding:0 10px; 35 | color:#FFF; 36 | opacity:0.75; 37 | line-height:40px; 38 | } 39 | 40 | .pattern-library-header a:first-child { 41 | opacity:1; 42 | } 43 | 44 | .pattern-library-header a:hover { 45 | background:#555; 46 | opacity:1; 47 | } 48 | 49 | 50 | // ---------------------------------------------- 51 | // Page Title 52 | 53 | .pattern-library-page-title { 54 | margin-bottom:30px; 55 | color:#333; 56 | font-family:'Helvetica Neue', arial, sans-serif; 57 | font-size:48px; 58 | font-weight:bold; 59 | line-height:1.2; 60 | } 61 | 62 | 63 | // ---------------------------------------------- 64 | // Section 65 | 66 | .pattern-library-section { 67 | margin-bottom:70px; 68 | } 69 | 70 | 71 | // ---------------------------------------------- 72 | // Section Title 73 | 74 | .pattern-library-title { 75 | margin-bottom:30px; 76 | padding:10px 15px; 77 | background:#333; 78 | color:#FFF; 79 | font-family:'Helvetica Neue', arial, sans-serif; 80 | font-size:24px; 81 | font-weight:300; 82 | line-height:1.2; 83 | } 84 | -------------------------------------------------------------------------------- /patterns/scss/mixin/_breakpoint.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Gravity Department 3 | * 4 | * @author Brendan Falkowski (http://gravitydept.com) 5 | * @copyright Copyright 2014 Gravity Department 6 | * @license All rights reserved. 7 | */ 8 | 9 | 10 | $mq-support: true !default; 11 | $mq-fixed-value: 1024px !default; 12 | 13 | @mixin bp ($feature, $value) { 14 | // Set global device param 15 | $media: only screen; 16 | 17 | // Media queries supported 18 | @if $mq-support == true { 19 | 20 | @media #{$media} and ($feature: $value) { 21 | @content; 22 | } 23 | 24 | // Media queries not supported 25 | } @else { 26 | 27 | @if $feature == 'min-width' { 28 | @if $value <= $mq-fixed-value { 29 | @content; 30 | } 31 | } @else if $feature == 'max-width' { 32 | @if $value >= $mq-fixed-value { 33 | @content; 34 | } 35 | } 36 | 37 | } 38 | } 39 | 40 | 41 | 42 | /* 43 | // ----------------------------------------------- 44 | // Usage example: 45 | // For IE set $mq-support to false. 46 | // Set the fixed value. 47 | // Then use mixins to test whether styles should be applied. 48 | // ----------------------------------------------- 49 | 50 | $mq-support: false; 51 | $mq-fixed-value: 1024; 52 | 53 | // Renders at fixed value 54 | @include bp (min-width, 300px) { 55 | div { color:#000; } 56 | } 57 | 58 | // Doesn't render without MQ support 59 | @include bp (min-width, 1200px) { 60 | div { color:#FFF; } 61 | } 62 | 63 | // Doesn't render without MQ support 64 | @include bp (max-width, 300px) { 65 | div { color:#444; } 66 | } 67 | 68 | // Renders at fixed value 69 | @include bp (max-width, 1200px) { 70 | div { color:#888; } 71 | } 72 | 73 | // ----------------------------------------------- 74 | // End example. 75 | // ----------------------------------------------- 76 | */ 77 | 78 | -------------------------------------------------------------------------------- /patterns/scss/_app-loader.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Gravity Department 3 | * 4 | * @author Brendan Falkowski (http://gravitydept.com) 5 | * @copyright Copyright 2014 Gravity Department 6 | * @license All rights reserved. 7 | */ 8 | 9 | 10 | // ============================================== 11 | // Compass Libraries 12 | // ============================================== 13 | 14 | @import "compass"; 15 | @import "compass/css3"; 16 | 17 | 18 | 19 | // ============================================== 20 | // Functions + Vars + Mixins 21 | // ============================================== 22 | 23 | @import "function/black"; 24 | @import "function/white"; 25 | 26 | @import "var"; 27 | 28 | @import "mixin/breakpoint"; 29 | @import "mixin/clearfix"; 30 | @import "mixin/not-selectable"; 31 | @import "mixin/triangle"; 32 | @import "mixin/typography"; 33 | 34 | 35 | 36 | // ============================================== 37 | // Reset 38 | // ============================================== 39 | 40 | @import "vendor/normalize"; 41 | @import "core/reset"; 42 | 43 | 44 | 45 | // ============================================== 46 | // Core 47 | // ============================================== 48 | 49 | @import "core/common"; 50 | @import "core/form"; 51 | @import "core/table"; 52 | @import "core/typography"; 53 | 54 | 55 | 56 | // ============================================== 57 | // Layout 58 | // ============================================== 59 | 60 | // TBD 61 | 62 | 63 | 64 | // ============================================== 65 | // Content 66 | // ============================================== 67 | 68 | @import "content/pattern-library"; 69 | 70 | 71 | 72 | // ============================================== 73 | // MODULES 74 | // ============================================== 75 | 76 | // TBD 77 | 78 | 79 | 80 | // ============================================== 81 | // Vendor 82 | // ============================================== 83 | 84 | @import "vendor/prism"; 85 | 86 | 87 | -------------------------------------------------------------------------------- /pivots/08-responsive-images.md: -------------------------------------------------------------------------------- 1 | # Responsive Images 2 | 3 | ## Use (PictureFill) and plan for the technology to change. 4 | 5 | Cloud Four has already written a fantastic summary of [what a responsive images solution needs](http://blog.cloudfour.com/8-guidelines-and-1-rule-for-responsive-images/) to accomplish today: 6 | 7 | 1. Use vector-based images or font icons whenever you can. 8 | 1. Encourage people to upload the highest quality source possible. 9 | 1. Provide an automatic image resizing and compression service. 10 | 1. Images can be resized to any size with URL parameters. 11 | 1. Provide automated output of your image markup. 12 | 1. Provide a way to override resized images for art direction needs. 13 | 1. Integrate image compression best practices. 14 | 1. Bonus: Detect support for WebP image format and use it. 15 | 16 | The one and only rule for responsive images: 17 | 18 | **Plan for the fact that whatever you implement will be deprecated.** 19 | 20 | That last rule is critical to understand. Responsive images are not solvable today and nothing being used today is a long-term solution. We can get very close using plugins like PictureFill and the existing image resizing tools built into Magento though. 21 | 22 | Magento should expand its image tools in the following ways: 23 | 24 | 1. Allow every single image asset in the system (product images, CMS images, category images, category thumbnails) to be processed with output filters. 25 | 1. Every output filter (dimensions, ratio, fill or stretch to dimensions, compression, file type) must be available to every asset via a class or URL parameters. 26 | 1. Create a global “responsive image markup" template and method, which accepts an object of data and parameters. By simply passing in the characteristics, implementors can then globally change the template output as more browser-based responsive images technologies become available, while the data objects stay intact. 27 | 28 | Coupled with the Cloud Four recommendations, Magento could facilitate a very robust image output solution based on the tools it already has. 29 | 30 | #### Further Reading 31 | 32 | * http://blog.cloudfour.com/8-guidelines-and-1-rule-for-responsive-images/ 33 | * https://github.com/scottjehl/picturefill 34 | * https://speakerdeck.com/brendanfalkowski/responsive-ecommerce-part-two?slide=114 -------------------------------------------------------------------------------- /pivots/03-dry-principles.md: -------------------------------------------------------------------------------- 1 | # DRY Principles 2 | 3 | ## Embrace DRY (Don't Repeat Yourself) principles consistently. 4 | 5 | Magento 1 and 2 have many instances where template files are indiscriminately copied between modules and slightly modified to suit the needs of that module. While it's arguable this allows for more loosely coupled modules, not being DRY creates several problems: 6 | 7 | 1. Duplicating templates creates a headache for anyone implementing Magento. They need to re-write the same customizations in multiple locations and scrutinize each instance for minor differences. 8 | 1. Often the existence of duplicated templates is not obvious. Developers may mistakenly not repeat their customizations in each duplicate and only discover their mistake when an obscure feature or config edge case appears (often in production). 9 | 1. Duplicating templates in Magento's core makes long-term maintenance more difficult and error prone. 10 | 1. Each duplication instance makes merging customizations more tedious during upgrades. 11 | 1. In Magento 1.x, there are multiple instances of changes in core templates not being ported in duplicate templates by the core team. This inconsistency makes customization even more error-prone. 12 | 13 | ### Example from Magento 2 14 | 15 | The ```bundle``` and ```downloadable``` modules are a key example of this problem. They contain several modified versions of templates from the ```checkout``` module. 16 | 17 | For example, the row render templates for the cart, checkout review, and order/invoice/credit-memo/shipment are all near duplicates of the renderers in the ```checkout``` module. 18 | 19 | A comparison of these two files in ```Magento 2, dev63``` reveals a myriad of trivial changes (spacing, extra line breaks, additional classes), but there are only a few actual differences between the two files. 20 | 21 | ``` 22 | app/code/Magento/Downloadable/view/frontend/checkout/cart/item/default.phtml 23 | app/code/Magento/Checkout/view/frontend/cart/item/default.phtml 24 | ``` 25 | 26 | The trivial changes demonstrate the importance of the not having duplicate content in template files, and the actual differences should be isolated and conditionally displayed. 27 | 28 | ### Improvement 29 | 30 | If the ```bundle``` and ```downloadable``` modules need to add a few lines to the ```checkout``` cart renderer, do not duplicate the entire renderer template. 31 | 32 | Just modify the ```checkout``` cart renderer with a fragment template that conditionally renders based on the product type. 33 | 34 | For non-core modules, a third-party could make use of a generic layout container into which new blocks could be inserted. Magento should not use them though, all core templates should be explicitly inserted for clarity. -------------------------------------------------------------------------------- /charter/README.md: -------------------------------------------------------------------------------- 1 | # Project Charter 2 | 3 | Operation Frontend Steward is a four-pronged offense to improve Magento 2's frontend: 4 | 5 | 1. Define strategic overarching principles Magento 2's frontend needs to uphold (think "10 commandments"). 6 | 1. Create public documentation of frontend patterns used in Magento 2. 7 | 1. Refactor outdated, ignored, and abused frontend code with standardized, lean, extensible, and future-friendly code. 8 | 1. Propose frontend architectural changes outside the Hackaton's scope to modernize Magento 2. 9 | 10 | ## Why it matters 11 | 12 | This project is necessary because Magento's development is to this day dominated by backend engineering. This is observable in the neglect of modernizing Magento 1's frontend between 2009–2014, and the lateral-at-best frontend changes to date in Magento 2. With responsive design eating other mobile / multi-device strategies, the role of a highly engineered frontend codebase has never been more critcal. 13 | 14 | What are the odds of a product rebuilt from the group up but designed desktop-first in 2014 surviving another 5 years? Take your bets. 15 | 16 | ## Proposed schedule of events 17 | 18 | 1. Make an inventory of all issues, idiosyncrasies, gotchas, obsolete, redundant, overly prescribed, and good patterns in Magento's frontend. 19 | 1. Categorize all suggestions and prioritize them. 20 | 1. Split into three groups: documentors, refactorers, and strategizers to tackle the four objectives of Operation Frontend Steward. 21 | 22 | 23 | ## This project's audience 24 | 25 | This project is concerned with any suggestions which enable interaction designers, UX designers, frontend developers, information architects, and content strategists to do the best work possible using Magento. Those people all serve users. 26 | 27 | ## Out of Scope 28 | 29 | There are two things this project is not: 30 | 31 | 1. Building yet another theme. Magento does not need more themes, it needs a better core frontend. The core must not be slanted for Magento's demos but rather be the most neutral base for implementors. 32 | 1. Adding responsive design / mobile support. These are not "features". Doing them well **requires** consideration of the client's unique goals and constraints, and that is predicated on having an unobtrusive frontend core as a foundation. The cart goes after the horse. 33 | 34 | I am wholeheartedly concerned with both these things, but today I think the best way Magento can handle them is by addressing the foundational changes necessary to make them more approachable for new developers and easier to maintain for complex implementations. 35 | 36 | ## Project Info 37 | 38 | * Submitted by: Brendan Falkowski @ http://gravitydept.com 39 | * Project URL: https://github.com/magento-hackathon/operation-frontend-steward 40 | * Timezone: US Eastern (GMT -5) 41 | -------------------------------------------------------------------------------- /pivots/01-mobile-first.md: -------------------------------------------------------------------------------- 1 | # Mobile First 2 | 3 | ## Adopt a mobile-first methodology across the design system. 4 | 5 | The world is un-debatably multi-device. Magento cannot continue building a desktop-first product, and expect to be relevant several years from now. 6 | 7 | Magento has historically missed expectations addressing the changing device landscape through a stripped down mobile-specific site and poorly constructed native app—neither of which provided a compelling enough small screen experience to gain traction. 8 | 9 | Responsive design has overtaken mobile-specific sites and native apps, since it is the only truly multi-device strategy that retailers can adopt. It's advantages are numerous: 10 | 11 | 1. Consistent content, behavior, and branding throughout the user's journey regardless of what device they choose (not the technology provider). 12 | 1. With universal support of web technologies, responsive design is platform-agnostic. It doesn't matter if you use Apple, Android, Microsoft, Blackberry, Linux, Firefox OS, your car, or refrigerator. If connects to the internet, it will work. 13 | 1. Significantly lower development cost and timeline compared to building a comparable multi-device experience using other approaches. 14 | 1. Significantly lower cost of ownership due to a single codebase powering all device experiences. 15 | 1. Significantly more flexible and modern development workflow. No waiting for App Store approval. No synchronization or bureaucracy between web and mobile teams. Deploy once to all users at any time, and changes are effective immediately. 16 | 17 | Mobile-first thinking needs to permeate every aspect of the frontend architecture: 18 | 19 | 1. Elements cannot be defined by their position on a large screen. The design language used in Magento 2 needs to be overhauled. Elements should be named based on priority and hierarchy according to the user's context and path journeying through the site. 20 | 1. Magento 2 features like the Visual Design Editor are designed specifically for a world with absolute layout control, which has eroded today. In a responsive site, you cannot drag/drop elements around the page and maintain a semblance of hierarchy and a usable interface. Interactions are carefully choreographed across breakpoints. 21 | 1. Likewise building in tools in the Visual Design Editor so users can change image sizes to fixed values is dated. Responsive implementations require fluid dimensions on all elements (not just images) to transform and adapt to best fit the device. 22 | 23 | These are just a few points illustrating how the frontend work being done currently in Magento 2 does not match what developers or retailers need from the platform today or in the future. 24 | 25 | **Further Reading** 26 | * https://speakerdeck.com/brendanfalkowski/responsive-ecommerce-part-two 27 | * https://speakerdeck.com/brendanfalkowski/responsive-design-panel-where-why-and-how 28 | * https://speakerdeck.com/brendanfalkowski/responsive-ecommerce-design-once-sell-everywhere -------------------------------------------------------------------------------- /patterns/js/app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Gravity Department 3 | * 4 | * @author Brendan Falkowski (http://gravitydept.com) 5 | * @copyright Copyright 2014 Gravity Department 6 | * @license All rights reserved. 7 | */ 8 | 9 | 10 | 11 | // ============================================== 12 | // jQuery Init 13 | // ============================================== 14 | 15 | // Avoid PrototypeJS conflicts, assign jQuery to $jQ instead of $ 16 | var $jQ = jQuery.noConflict(); 17 | 18 | // Use $jQ(document).ready() because Magento executes Prototype inline 19 | // and breaks if jQuery executes beforehand. Use function($) to maintain 20 | // normal jQuery syntax inside 21 | 22 | $jQ(document).ready(function($){ 23 | 24 | // ============================================== 25 | // Shared Vars 26 | // ============================================== 27 | 28 | // Document 29 | var w = $(window); 30 | var d = $(document); 31 | var body = $('body'); 32 | 33 | 34 | 35 | // ============================================== 36 | // FastClick - Remove 300ms delay 37 | // See: https://github.com/ftlabs/fastclick 38 | // ============================================== 39 | 40 | FastClick.attach(document.body); 41 | 42 | 43 | 44 | // ============================================== 45 | // UI Pattern - Toggle 46 | // ============================================== 47 | 48 | // Used to trigger the display of extra content. 49 | // Default is hidden. 50 | 51 | //
52 | //
Trigger
53 | //
Toggleable content
54 | //
55 | 56 | // Examples: 57 | // - Catalog: list filter 58 | // - Catalog: product social share 59 | // - Customer: account nav 60 | // - FAQ 61 | 62 | var toggleButtons = $('.toggle').find('.toggle-button'); 63 | 64 | toggleButtons.on('click', function (e) { 65 | e.preventDefault(); 66 | 67 | $(this) 68 | .closest('.toggle') 69 | .toggleClass('toggle-active'); 70 | }); 71 | 72 | 73 | // ============================================== 74 | // UI Pattern - Linearize Table 75 | // ============================================== 76 | 77 | $('.linearize-table').each(function(){ 78 | 79 | var cellLabels = []; 80 | 81 | // Loop through all table headers to find the labels 82 | $(this).find('thead th').each(function(index){ 83 | // Only assign label if there is no data-no-label attribute on the th. Adding data-no-label will display 84 | // the value of each cell without it being prepended by a label, which can be useful for product name and 85 | // similar data types. 86 | cellLabels[index] = ($(this).is('[data-no-label]')) ? false : $(this).text().trim(); 87 | }); 88 | 89 | $(this).find('tbody tr').each(function(){ 90 | // Looping through each td inside of each row so that the index value will match that of thead > tr > th 91 | $(this).children('td').each(function(index){ 92 | var label = cellLabels[index]; 93 | // As long as the th did not contain a data-no-label attribute, add the data-label attribute 94 | if (label) { 95 | $(this).attr('data-label', label); 96 | } 97 | }); 98 | }); 99 | }); 100 | 101 | 102 | // ============================================== 103 | // END: $jQ(document).ready() 104 | }); 105 | -------------------------------------------------------------------------------- /pivots/07-javascript-architecture.md: -------------------------------------------------------------------------------- 1 | # JavaScript Architecture 2 | 3 | ## Use (RequireJS) or (Browserify) not (HeadJS) for JS loading architecture 4 | 5 | Magento 1.x has significant performance constraints because of its JavaScript architecture. This bottleneck causes the majority of perceived poor performance from the frontend. The problem is deep: 6 | 7 | 1. Magento loads all scripts in the ``````, which is blocking — meaning the page cannot begin rendering until all scripts are loaded and executed. 8 | 1. Concurrent connections are limited per domain, so there is a limit to how many assets defined inline can be downloaded in parallel. 9 | 1. Libraries were chosen as core dependencies which are bloated relative to their actual utilization with Magento. Example: all scriptaculous libraries. 10 | 1. Templates are riddled with inline JS, which require libraries execute beforehand. 11 | 1. The minification/concatenation process does not bundle intelligently. Example: adding a single script to a page requires all JS on the page to be re-downloaded. The more than triples the bandwidth need to complete a 5-page purchase path. 12 | 1. Bundled JS is too large to be cached on older and low-end devices, so it must be re-downloaded regardless. 13 | 14 | The best approach in Magento 1.x is minifying all JS and serving each file individually. This allows the browser to cache it, but the first page load has a high HTTP request burden which can be excessive on a high-latency connection. Still this approach outperforms concatenation. 15 | 16 | In Magento 2, Prototype has been replaced with jQuery and the script loader HeadJS has been implemented. This is an improvement in that only one script is loaded in the and the rest asynchronously, but it allows the continued obsolete practice of inlining JS throughout PHTML templates. HeadJS has recently veered into Modernizr's territory providing browser feature tests and rudimentary media query helpers. On the surface this seems relevant, but this violates the single responsibility principle. There are more powerful individual tools which can be assembled leaner than using a jack-of-all-trades plugin. 17 | 18 | Magento needs a platform-wide restructuring of its JS architecture. Ideally it should meet these requirements: 19 | 20 | * Develop all scripts in separate files or modules. 21 | * Asset manager script thats load before the ends. 22 | * All scripts are loaded asynchronously. 23 | * The loading order does not determine the execution order. 24 | * Each script may be defined with dependencies, and will not execute until after dependencies are loaded. 25 | * Fallback URIs are providable if a script's host is inaccessible. 26 | * Intelligent concatenation (or by configuration) for scripts that always load together or are loaded globally. 27 | * Debug easily from a browser. 28 | 29 | There are plenty of contenders in this space, but RequireJS is generally considered the most configurable and Browserify the best build tool for JS module management. Which is better depends on how opinionated the platform and diversity of build/deployment processes is. For Magento, RequireJS is a more natural fit because it runs in the browser and is very configuration heavy, which fits with how Magento uses XML for layout architecture. 30 | 31 | #### Further Reading 32 | 33 | * http://headjs.com/ 34 | * http://requirejs.org/ 35 | * http://bower.io/ 36 | * http://browserify.org/ 37 | * http://www.sitepoint.com/understanding-requirejs-for-effective-javascript-module-loading/ 38 | * http://blog.teamtreehouse.com/organize-your-code-with-requirejs 39 | * http://www.100percentjs.com/just-like-grunt-gulp-browserify-now/ 40 | * http://codeofrob.com/entries/why-i-stopped-using-amd.html 41 | * http://javascriptplayground.com/blog/2013/09/browserify/ 42 | * http://esa-matti.suuronen.org/blog/2013/03/22/journey-from-requirejs-to-browserify/ -------------------------------------------------------------------------------- /patterns/scss/vendor/_prism.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * prism.js default theme for JavaScript, CSS and HTML 3 | * Based on dabblet (http://dabblet.com) 4 | * @author Lea Verou 5 | */ 6 | 7 | code[class*="language-"], 8 | pre[class*="language-"] { 9 | color: black; 10 | text-shadow: 0 1px white; 11 | font-family: Consolas, Monaco, 'Andale Mono', monospace; 12 | direction: ltr; 13 | text-align: left; 14 | white-space: pre; 15 | word-spacing: normal; 16 | word-break: normal; 17 | font-size: 14px; 18 | 19 | 20 | -moz-tab-size: 4; 21 | -o-tab-size: 4; 22 | tab-size: 4; 23 | 24 | -webkit-hyphens: none; 25 | -moz-hyphens: none; 26 | -ms-hyphens: none; 27 | hyphens: none; 28 | } 29 | 30 | pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection, 31 | code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection { 32 | text-shadow: none; 33 | background: #b3d4fc; 34 | } 35 | 36 | pre[class*="language-"]::selection, pre[class*="language-"] ::selection, 37 | code[class*="language-"]::selection, code[class*="language-"] ::selection { 38 | text-shadow: none; 39 | background: #b3d4fc; 40 | } 41 | 42 | @media print { 43 | code[class*="language-"], 44 | pre[class*="language-"] { 45 | text-shadow: none; 46 | } 47 | } 48 | 49 | /* Code blocks */ 50 | pre[class*="language-"] { 51 | padding: 1em; 52 | margin: .5em 0; 53 | overflow: auto; 54 | } 55 | 56 | :not(pre) > code[class*="language-"], 57 | pre[class*="language-"] { 58 | background: #f5f2f0; 59 | } 60 | 61 | /* Inline code */ 62 | :not(pre) > code[class*="language-"] { 63 | padding: .1em; 64 | border-radius: .3em; 65 | } 66 | 67 | .token.comment, 68 | .token.prolog, 69 | .token.doctype, 70 | .token.cdata { 71 | color: slategray; 72 | } 73 | 74 | .token.punctuation { 75 | color: #999; 76 | } 77 | 78 | .namespace { 79 | opacity: .7; 80 | } 81 | 82 | .token.property, 83 | .token.tag, 84 | .token.boolean, 85 | .token.number, 86 | .token.constant, 87 | .token.symbol { 88 | color: #905; 89 | } 90 | 91 | .token.selector, 92 | .token.attr-name, 93 | .token.string, 94 | .token.builtin { 95 | color: #690; 96 | } 97 | 98 | .token.operator, 99 | .token.entity, 100 | .token.url, 101 | .language-css .token.string, 102 | .style .token.string, 103 | .token.variable { 104 | color: #a67f59; 105 | background: hsla(0,0%,100%,.5); 106 | } 107 | 108 | .token.atrule, 109 | .token.attr-value, 110 | .token.keyword { 111 | color: #07a; 112 | } 113 | 114 | 115 | .token.regex, 116 | .token.important { 117 | color: #e90; 118 | } 119 | 120 | .token.important { 121 | font-weight: bold; 122 | } 123 | 124 | .token.entity { 125 | cursor: help; 126 | } 127 | 128 | pre.line-numbers { 129 | position: relative; 130 | padding-left: 3.8em; 131 | counter-reset: linenumber; 132 | } 133 | 134 | pre.line-numbers > code { 135 | position: relative; 136 | } 137 | 138 | .line-numbers .line-numbers-rows { 139 | position: absolute; 140 | pointer-events: none; 141 | top: 0; 142 | font-size: 100%; 143 | left: -3.8em; 144 | width: 3em; /* works for line-numbers below 1000 lines */ 145 | letter-spacing: -1px; 146 | border-right: 1px solid #999; 147 | 148 | -webkit-user-select: none; 149 | -moz-user-select: none; 150 | -ms-user-select: none; 151 | user-select: none; 152 | 153 | } 154 | 155 | .line-numbers-rows > span { 156 | pointer-events: none; 157 | display: block; 158 | counter-increment: linenumber; 159 | } 160 | 161 | .line-numbers-rows > span:before { 162 | content: counter(linenumber); 163 | color: #999; 164 | display: block; 165 | padding-right: 0.8em; 166 | text-align: right; 167 | } 168 | -------------------------------------------------------------------------------- /code-style/README.md: -------------------------------------------------------------------------------- 1 | # Code Style 2 | 3 | Teams writing code together must create standardized style guides and conventions, so personal styles do not clash. Consistency is not *hard* but it requires every developer's commitment. 4 | 5 | This is especially critical for Magento because the product's code is primarily used externally and is frequently modified. Distributed software needs even higher standards not only for readability's sake, but because third-parties will mirror its quality or sloppiness. 6 | 7 | Magento 2 is following PSR standards for PHP classes, which is modern and great. No such standard exists for frontend code. Browsers are designed to accept loosely defined HTML, CSS, and JavaScript for maximum interoperability. You can always do something 3 or 4 ways with varying trade-offs / benefits. 8 | 9 | ### Magento 1.x ignored the frontend 10 | 11 | Magento 1.x did not set the bar high. Almost every template contains an inconsistency versus itself (let alone the whole codebase) which is pathetic. Virtually no refactoring was done during the 1.3 to 1.8 (current) lifecycle. In many cases frontend code was obviously written by backend developers lacking experience addressing frontend concerns: extensibility, re-usability, semantics, readability, and search optimization. 12 | 13 | Frontend and backend programming are very different disciplines. You would not hire a frontend developer to write your database models or caching layer. Don't hire a backend developer to architect your design system's markup patterns. 14 | 15 | ### Magento 2.x needs comprehensive frontend standards 16 | 17 | The Magento 2 wiki contains a few examples of coding practices, but they glance over fundamentals and are not comprehensive. Team members will not absorb each other's styles and self-align on a standard practice. Conventions must be discussed, documented, and enforced with code reviews. 18 | 19 | The best resource Magento has for modern, real-world frontend practices is the development community. Magento's core team is "too close" to the code to see its flaws during implementations. 20 | 21 | ### First steps 22 | 23 | Establishing standards and applying them is not a weekend project. [Gravity Department](http://gravitydept.com) has started documenting and iterating its frontend standards around Magento. This has helped immensely when collaborating with partner and client teams. 24 | 25 | #### See: http://manuals.gravitydept.com 26 | 27 | To date, there are no similar public resources in the Magento community. If you know of any, please share them. 28 | 29 | Rather than re-typing these conventions verbatim, the relevant sections will be linked to. 30 | 31 | #### HTML 32 | 33 | * http://manuals.gravitydept.com/code/html/styleguide 34 | * http://manuals.gravitydept.com/code/html/semantics 35 | * http://manuals.gravitydept.com/code/html/conventions 36 | 37 | #### CSS 38 | 39 | * http://manuals.gravitydept.com/code/css/styleguide 40 | * http://manuals.gravitydept.com/code/css/comments 41 | * http://manuals.gravitydept.com/code/css/specificity 42 | 43 | #### JavaScript 44 | 45 | * http://manuals.gravitydept.com/code/js/styleguide 46 | 47 | #### PHP 48 | 49 | * http://manuals.gravitydept.com/code/php/styleguide 50 | * http://manuals.gravitydept.com/code/php/comments 51 | * http://manuals.gravitydept.com/code/php/templating 52 | 53 | #### Magento Specifics 54 | 55 | * http://manuals.gravitydept.com/platforms/magento/theme-fallback 56 | * http://manuals.gravitydept.com/platforms/magento/phtml-templates 57 | * http://manuals.gravitydept.com/platforms/magento/layout-xml 58 | * http://manuals.gravitydept.com/platforms/magento/translation 59 | 60 | #### Example code 61 | 62 | This repo also contains two examples of refactored PHTML templates, which demonstrate the code-level changes being proposed. 63 | 64 | See: https://github.com/magento-hackathon/operation-frontend-steward/tree/master/code-style/phtml 65 | 66 | ### Next steps 67 | 68 | Ideally Magento will: 69 | 70 | 1. Adopt a community-driven perspective on which standards are valid and useful. 71 | 1. Refactor Magento 2 to remove inconsistencies and align it with standards. 72 | 1. Publicize documentation of its standards for implementors and third-parties. 73 | -------------------------------------------------------------------------------- /patterns/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | Frontend Steward - Pattern Library 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 25 | 26 | 27 | 28 | 29 |
30 | Pattern Library 31 | Forms 32 | Tables 33 | Typography 34 | User Interface 35 |
36 | 37 |
38 |
39 |
Frontend Steward - Pattern Library
40 | 41 | 42 | 43 | 44 | 45 |
46 |
Patterns
47 | 48 |
49 | 50 | 51 |

Forms

52 | 53 | 59 | 60 | 61 | 62 |

Tables

63 | 64 | 71 | 72 | 73 | 74 |

Typography

75 | 76 | 85 | 86 | 87 | 88 |

User Interface

89 | 90 | 99 |
100 |
101 | 102 | 103 | 104 | 105 | 106 |
107 |
108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /patterns/scss/core/_table.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Gravity Department 3 | * 4 | * @author Brendan Falkowski (http://gravitydept.com) 5 | * @copyright Copyright 2014 Gravity Department 6 | * @license All rights reserved. 7 | */ 8 | 9 | 10 | // ============================================== 11 | // Tables - Core 12 | // ============================================== 13 | 14 | .hide-th { 15 | text-indent:-9999px; 16 | } 17 | 18 | 19 | // ---------------------------------------------- 20 | // Responsive table priorities 21 | 22 | .data-p1 { 23 | display:table-cell; 24 | } 25 | 26 | .data-p2, 27 | .data-p3, 28 | .data-p4 { 29 | display:none; 30 | } 31 | 32 | @include bp (min-width, 480px) { 33 | 34 | .data-p2 { 35 | display:table-cell; 36 | } 37 | 38 | } 39 | 40 | @include bp (min-width, 600px) { 41 | 42 | .data-p3 { 43 | display:table-cell; 44 | } 45 | 46 | } 47 | 48 | @include bp (min-width, 768px) { 49 | 50 | .data-p4 { 51 | display:table-cell; 52 | } 53 | 54 | } 55 | 56 | 57 | // ============================================== 58 | // Data Table 59 | // ============================================== 60 | 61 | .data-table { 62 | width:100%; 63 | margin-bottom:$margin-bottom; 64 | font-size:$f-size-s; 65 | } 66 | 67 | .data-table td, 68 | .data-table th { 69 | padding:8px; 70 | border:1px solid $c-table-border; 71 | vertical-align:top; 72 | } 73 | 74 | .data-table th { 75 | background:$c-table-background; 76 | font-weight:bold; 77 | white-space:nowrap; 78 | } 79 | 80 | .data-table tfoot tr { 81 | background:$c-table-background; 82 | } 83 | 84 | // ============================================== 85 | // Linearize Table 86 | // ============================================== 87 | 88 | @include bp (max-width, 599px) { 89 | 90 | .linearize-table { 91 | // border-width:0 0 1px; 92 | border-collapse:separate; 93 | border:1px solid $c-table-border; 94 | } 95 | 96 | .linearize-table tr, 97 | .linearize-table th, 98 | .linearize-table td { 99 | display:block; 100 | } 101 | 102 | // THEAD 103 | 104 | .linearize-table thead { 105 | display:none; 106 | } 107 | 108 | // TBODY - TR 109 | 110 | .linearize-table tbody tr { 111 | position:relative; 112 | } 113 | 114 | /* 115 | .linearize-table tbody tr:nth-child(odd) { 116 | background:$c-table-zebra-odd; 117 | } 118 | 119 | .linearize-table tbody tr:nth-child(even) { 120 | background:$c-table-zebra-even; 121 | } 122 | */ 123 | 124 | // TBODY - TD 125 | 126 | .linearize-table tbody td { 127 | padding:0 10px 10px 0; 128 | border:0; 129 | } 130 | 131 | 132 | .linearize-table tbody td:first-child { 133 | padding-top:15px; 134 | padding-left:10px; 135 | } 136 | 137 | .linearize-table tbody:not(:first-of-type) td:first-child { 138 | border-top:1px solid $c-table-border; 139 | } 140 | 141 | .linearize-table tbody td:not([data-label]) { 142 | font-weight:bold; 143 | } 144 | 145 | .linearize-table tbody td[data-label] { 146 | padding-left:20px; 147 | } 148 | 149 | .linearize-table tbody td[data-label]:before { 150 | content:attr(data-label) ":"; 151 | display:inline-block; 152 | padding-right:5px; 153 | font-weight:bold; 154 | } 155 | 156 | // TFOOT - TD 157 | 158 | .linearize-table tfoot tr { 159 | display:block; 160 | border-top:1px solid $c-table-border; 161 | } 162 | 163 | .linearize-table tfoot tr:after { 164 | @include clearfix(); 165 | } 166 | 167 | // TODO: Floating the td elements removes their table-cell display type, so their borders don't collapse, causing double borders 168 | .linearize-table tfoot td { 169 | float:left; 170 | border:0px; 171 | } 172 | 173 | .linearize-table tfoot td:nth-child(odd) { 174 | clear:left; 175 | width:percentage(3/5); 176 | } 177 | 178 | .linearize-table tfoot td:nth-child(even) { 179 | text-align:left; 180 | width:percentage(2/5); 181 | } 182 | 183 | // Helpers 184 | 185 | .linearize-table .linearize-hide { 186 | display:none; 187 | } 188 | 189 | .linearize-table .linearize-unpad { 190 | padding:0; 191 | } 192 | 193 | .linearize-table .linearize-show { 194 | display:block; 195 | } 196 | 197 | // At "min-width: 600px" there is another helper ".linearize-collapse". 198 | // For holding duplicated content that is hidden in the full table. 199 | 200 | } 201 | 202 | // ---------------------------------------------- 203 | 204 | @include bp (min-width, 600px) { 205 | 206 | // Hide a region that is only visible under 599px 207 | .linearize-table .linearize-collapse { 208 | display:none; 209 | } 210 | 211 | } 212 | 213 | 214 | -------------------------------------------------------------------------------- /pivots/06-css-pre-processors.md: -------------------------------------------------------------------------------- 1 | # CSS Pre-Processors 2 | 3 | ## Use (Sass + Compass) not (LESS) for CSS pre-processing 4 | 5 | Using a CSS pre-processors is necessary in modern frontend development. While there are a plethora of CSS pre-processors on the market, Sass and LESS are the two that have risen to the top. 6 | 7 | In fact, Sass and LESS are more alike than different. They both do almost everything you need: 8 | 9 | * Partials – Modularize your source files. 10 | * Variables – Store and apply values. 11 | * Mixins – Classes for classes. 12 | * Parametric Mixins – Classes to which you can pass parameters, like functions. 13 | * Nested Rules – Classes within classes, which cut down on repetitive code. 14 | * Operations – Math within CSS. 15 | * Color Functions – Edit your colors. 16 | * Namespaces – Groups of styles that can be called by references. 17 | * Scope – Make local changes to styles. 18 | * Programmatic evaluation – Complex expressions evaluated in CSS. 19 | * Source Maps – See the partial/line in browser developer tools 20 | 21 | In Magento 2, the ```magento_plushe``` theme currently uses LESS, which is an inferior CSS pre-processor to Sass. Reasons why Sass is a better choice for Magento 22 | 23 | ### Why people choose LESS 24 | 25 | #### A. LESS is easier to install 26 | 27 | Simply add a JS file and any site can render LESS client-side. Of course, this is not pre-processing and even LESS doesn't recommend this practice anymore, but the low barrier helped the project gain significant traction. 28 | 29 | By contrast, Sass was born in Rails' asset pipeline and compiles with Ruby. In the early days, you had to install Ruby to use Sass which many frontend people were not comfortable doing. 30 | 31 | Today, both Sass and LESS can be compiled from the command-line, task runners like http://gruntjs.com/ or http://gulpjs.com/, or GUI applications like http://incident57.com/codekit/ or http://mhs.github.io/scout-app/. 32 | 33 | #### B. Bootstrap uses LESS 34 | 35 | Bootstrap is a very popular frontend framework that has helped LESS gain in popularity over the past couple of years. Many look to Bootstrap as a “best practice", but it has historically reversed course significantly on every major architectural decision: 36 | 37 | * Began fixed width ➔ rewritten responsive in v2 38 | * Responsiveness was implemented desktop-first ➔ rewritten mobile-first in v3 39 | * Long-time LESS-only ➔ now officially supports Sass https://github.com/twbs/bootstrap-sass 40 | 41 | Outside the “I only use Bootstrap" camp there is significantly more advocation for Sass. The Bootstrap project has recognized this and now also officially supports Sass as of Bootstrap 3.1. 42 | 43 | Said another way: Bootstrap is the only major frontend framework that still supports LESS. 44 | 45 | ### Why Sass Is Better 46 | 47 | 90% of what Sass and Less do is comparable, but Sass ultimately does a better job in the details. These differences are not obvious with light usage or skimming the project pages, but make a significant difference in real-world development. 48 | 49 | #### A. Sass has Compass 50 | 51 | Compass is robust mixin library for Sass that offers CSS3 mixins, Helpers, and Sprite generators. While LESS has a number of mixin libraries, they don't rival the robustness of Compass. 52 | 53 | #### B. Sass has full programming logic available 54 | 55 | If you need to write a complex mixin with multiple arguments and conditional output, Sass can do that in a construct very similar to every other scripting language. LESS has a basic looping construct, but lacks the oomph and robustness to write mixins that are truly concise to use. 56 | 57 | #### C. Sass has the best open-source mixins 58 | 59 | The Sass + Compass community produce more and better tested helper tools than LESS. There's [Susy](http://susy.oddbird.net/) for grids, [Breakpoint](http://breakpoint-sass.com/) for media queries, and dozens more catalogued at: http://www.sache.in/ 60 | 61 | #### D. Sass has can perform image processing via Ruby 62 | 63 | This crucially enables Compass to perform sprite generation and image asset optimization. Since LESS compiles CSS using JS there is no comparable functionality. From a workflow perspective, this is killer. Managing sprites and multiple retina assets by hand is near impossible to do reliably and quickly on complex sites. Sass makes this dead simple. 64 | 65 | #### E. Sass has multiple output modes 66 | 67 | Having multiple output modes with source map support make development and debugging easier. Until two months ago LESS did not support source maps and it can only output minified or un-minified CSS. This can make it harder for new developers to get used to debugging their output. 68 | 69 | ### Who uses Sass with Magento? 70 | 71 | Sass is already being used by a number of Magento partners (Blue Acorn, Classy Llama, Gravity Department, etc) and is critical to some of the highest profile responsive work being done with Magento for Angry Birds, Skinny Ties, and GSI Commerce (now eBay Enterprise). 72 | #### Further reading 73 | 74 | * http://css-tricks.com/sass-vs-less/ 75 | * http://www.hongkiat.com/blog/sass-vs-less/ 76 | * http://lukebrooker.com/presentations/why-i-moved-from-less-to-sass/ 77 | -------------------------------------------------------------------------------- /patterns/user-interface.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | Frontend Steward - Pattern Library 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 25 | 26 | 27 | 28 | 29 |
30 | Pattern Library 31 | Forms 32 | Tables 33 | Typography 34 | User Interface 35 |
36 | 37 |
38 |
39 |
User Interface
40 | 41 | 42 | 43 | 44 | 45 |
46 |
Block Module
47 | 48 |
49 |
50 |

Lorem ipsum dolor sit amet?

51 |
52 | 53 |
54 |

Nulla facilisi. Duis aliquet egestas purus in blandit. Curabitur vulputate, ligula lacinia scelerisque tempor, lacus lacus ornare ante, ac egestas est urna sit amet arcu.

55 | 56 |

Nulla facilisi. Duis aliquet egestas purus in blandit. Curabitur vulputate, ligula lacinia scelerisque tempor, lacus lacus ornare ante, ac egestas est urna sit amet arcu.

57 |
58 |
59 |
60 | 61 | 62 | 63 | 64 | 65 |
66 |
Block Module with Toggle
67 | 68 |
69 |
70 |

Lorem ipsum dolor sit amet?

71 |
72 | 73 |
74 |

Nulla facilisi. Duis aliquet egestas purus in blandit. Curabitur vulputate, ligula lacinia scelerisque tempor, lacus lacus ornare ante, ac egestas est urna sit amet arcu.

75 | 76 |

Nulla facilisi. Duis aliquet egestas purus in blandit. Curabitur vulputate, ligula lacinia scelerisque tempor, lacus lacus ornare ante, ac egestas est urna sit amet arcu.

77 |
78 |
79 |
80 | 81 | 82 | 83 | 84 | 85 |
86 |
Callout
87 | 88 |
89 |

Some Title

90 | 91 |

Nulla facilisi. Duis aliquet egestas purus in blandit. Curabitur vulputate, ligula lacinia scelerisque tempor, lacus lacus ornare ante, ac egestas est urna sit amet arcu.

92 | 93 | Button 94 |
95 |
96 | 97 | 98 | 99 | 100 | 101 |
102 |
Messages
103 | 104 |
    105 |
  • 106 |
      107 |
    • Red Skywalker Plush Toy was added to your shopping cart.
    • 108 |
    • Red Skywalker Plush Toy has been added to your wishlist. Continue Shopping
    • 109 |
    110 |
  • 111 | 112 |
  • 113 |
      114 |
    • Nulla facilisi. Duis aliquet egestas purus in blandit.
    • 115 |
    116 |
  • 117 | 118 |
  • 119 |
      120 |
    • Some of the products cannot be ordered in requested quantity.
    • 121 |
    122 |
  • 123 |
124 |
125 | 126 | 127 | 128 | 129 | 130 |
131 |
Tabs
132 | 133 |
134 | 139 | 140 |
141 |
142 |

Nulla facilisi. Duis aliquet egestas purus in blandit. Curabitur vulputate, 143 | ligula lacinia scelerisque tempor, lacus lacus ornare ante, ac egestas est 144 | urna sit amet arcu.

145 |
146 | 147 |
148 |

Nulla facilisi. Duis aliquet egestas purus in blandit. Curabitur vulputate, 149 | ligula lacinia scelerisque tempor, lacus lacus ornare ante, ac egestas est 150 | urna sit amet arcu.

151 | 152 |

Nulla facilisi. Duis aliquet egestas purus in blandit. Curabitur vulputate, 153 | ligula lacinia scelerisque tempor, lacus lacus ornare ante, ac egestas est 154 | urna sit amet arcu.

155 |
156 | 157 |
158 |

Nulla facilisi. Duis aliquet egestas purus in blandit.

159 |
160 |
161 |
162 |
163 | 164 | 165 | 166 | 167 | 168 |
169 |
170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Operation Frontend Steward 2 | 3 | *Update [2014-02-16] — Many (but not all) of the artifacts produced for Operation Frontend Steward have been migrated into this repo. We are working directly with Magento 2's product teams to ensure these concerns are addressed within the product. We are preparing a survey for partners and independent developers to provide feedback to Magento on each issue. Please read through the articles and we'll let you know when the survey is ready.* 4 | 5 | --- 6 | 7 | ### Operation Frontend Steward is a four-pronged offense to improve Magento 2's frontend 8 | 9 | 1. Define strategic overarching principles Magento 2's frontend needs to uphold (think "10 commandments"). 10 | 1. Create public documentation of frontend patterns used in Magento 2. 11 | 1. Refactor outdated, ignored, and abused frontend code with standardized, lean, extensible, and future-friendly code. 12 | 1. Propose frontend architectural changes outside the Hackaton's scope to modernize Magento 2. 13 | 14 | --- 15 | 16 | ## Table of Contents 17 | 18 | ### [Project Charter](https://github.com/magento-hackathon/operation-frontend-steward/tree/master/charter) — The Origin Story 19 | 20 | 1. [Why it matters](https://github.com/magento-hackathon/operation-frontend-steward/blob/master/charter/README.md#why-it-matters) 21 | 1. [Proposed schedule of events](https://github.com/magento-hackathon/operation-frontend-steward/blob/master/charter/README.md#proposed-schedule-of-events) 22 | 1. [This project's audience](https://github.com/magento-hackathon/operation-frontend-steward/blob/master/charter/README.md#this-projects-audience) 23 | 1. [Out of scope](https://github.com/magento-hackathon/operation-frontend-steward/blob/master/charter/README.md#out-of-scope) 24 | 1. [Project info](https://github.com/magento-hackathon/operation-frontend-steward/blob/master/charter/README.md#project-info) 25 | 26 | ### [Principles](https://github.com/magento-hackathon/operation-frontend-steward/tree/master/principles) — Overarching Frontend Goals 27 | 28 | 1. [The perfect CMS does not constrain or coerce its frontend output](https://github.com/magento-hackathon/operation-frontend-steward/blob/master/principles/README.md#1-the-perfect-cms-does-not-constrain-or-coerce-its-frontend-output) 29 | 1. [The core frontend package cannot be Magento's demo](https://github.com/magento-hackathon/operation-frontend-steward/blob/master/principles/README.md#2-the-core-frontend-package-cannot-be-magentos-demo) 30 | 1. [Invest in Magento's frontend patterns. Stay lean and framework agnostic](https://github.com/magento-hackathon/operation-frontend-steward/blob/master/principles/README.md#3-invest-in-magentos-frontend-patterns-stay-lean-and-framework-agnostic) 31 | 1. [Treat frontend performance as the bottleneck it is compared to backend performance](https://github.com/magento-hackathon/operation-frontend-steward/blob/master/principles/README.md#4-treat-frontend-performance-as-the-bottleneck-it-is-compared-to-backend-performance) 32 | 1. [Use tools to evaluate and test frontend code quality](https://github.com/magento-hackathon/operation-frontend-steward/blob/master/principles/README.md#5-use-tools-to-evaluate-and-test-frontend-code-quality) 33 | 1. [Decouple HTML, CSS and JS from PHP classes](https://github.com/magento-hackathon/operation-frontend-steward/blob/master/principles/README.md#6-decouple-html-css-and-js-from-php-classes) 34 | 1. [Decouple visual (CSS) layer from the functional (JavaScript) layer](https://github.com/magento-hackathon/operation-frontend-steward/blob/master/principles/README.md#7-decouple-visual-css-layer-from-the-functional-javascript-layer) 35 | 1. [Decouple functional (JavaScript) layer from the markup (HTML)](https://github.com/magento-hackathon/operation-frontend-steward/blob/master/principles/README.md#8-decouple-functional-javascript-layer-from-the-markup-html) 36 | 37 | ### [Pivots](https://github.com/magento-hackathon/operation-frontend-steward/tree/master/pivots) — Major Technical Shifts Needed in Magento 2 38 | 39 | 1. [Mobile First](https://github.com/magento-hackathon/operation-frontend-steward/blob/master/pivots/01-mobile-first.md) — Adopt a mobile-first methodology across the design system. 40 | 1. [Atomic Templates](https://github.com/magento-hackathon/operation-frontend-steward/blob/master/pivots/02-atomic-templates.md) — Write templates atomically to contain the smallest possible output. 41 | 1. [DRY Principles](https://github.com/magento-hackathon/operation-frontend-steward/blob/master/pivots/03-dry-principles.md) — Embrace DRY (Don't Repeat Yourself) principles consistently. 42 | 1. [Checkout UX](https://github.com/magento-hackathon/operation-frontend-steward/blob/master/pivots/04-checkout-ux.md) — Use (Woven Checkout) not (Accordion + Sidebar) for OnePageCheckout. 43 | 1. [CSS Architecture](https://github.com/magento-hackathon/operation-frontend-steward/blob/master/pivots/05-css-architecture.md) — Write HTML classes for (OO-CSS) not (chained-classes CSS). 44 | 1. [CSS Pre-Processors](https://github.com/magento-hackathon/operation-frontend-steward/blob/master/pivots/06-css-pre-processors.md) — Use (Sass + Compass) not (LESS) for CSS pre-processing. 45 | 1. [JavaScript Architecture](https://github.com/magento-hackathon/operation-frontend-steward/blob/master/pivots/07-javascript-architecture.md) 46 | Use (RequireJS) or (Browserify) not (HeadJS) for JS loading architecture. 47 | 1. [Responsive Images](https://github.com/magento-hackathon/operation-frontend-steward/blob/master/pivots/08-responsive-images.md) — Use (PictureFill) and plan for the technology to change. 48 | 1. [Retina Icons](https://github.com/magento-hackathon/operation-frontend-steward/blob/master/pivots/09-retina-icons.md) — Use (SVG Sprites + PNG Fallback) or (Retina PNG Sprites) not (Icon Fonts) for icons. 49 | 1. [Comments](https://github.com/magento-hackathon/operation-frontend-steward/blob/master/pivots/10-comments.md) — Write comments in code, and sign comments as Magento. 50 | 51 | ### [Issues](https://github.com/magento-hackathon/operation-frontend-steward/tree/master/issues) — Specific Code-Level Problems in Magento 2 52 | 53 | See: https://docs.google.com/spreadsheet/ccc?key=0AgkHIW0ExJHqdGFydXNoQnNFR010MUx3V3V1S1dIbmc 54 | 55 | ### [Patterns](https://github.com/magento-hackathon/operation-frontend-steward/tree/master/patterns) — Extensible UI Components and Markup States 56 | 57 | ### [Code Style](https://github.com/magento-hackathon/operation-frontend-steward/tree/master/code-style) — Writing Consistent, Readable Frontend Code 58 | 59 | --- 60 | 61 | ## Contributors 62 | 63 | This resource was entirely community-driven, and was created during (and after) the January 2014 Magento Hackathon: http://www.mage-hackathon.de/passed/online-hackathon-worldwide-31st-jan-1st-feb.html 64 | 65 | * David Alger @ http://www.classyllama.com 66 | * Kris Brown @ http://www.briteskies.com 67 | * Brendan Falkowski @ http://gravitydept.com (Project Lead) 68 | * Erik Hansen @ http://www.classyllama.com 69 | * Tom Robertshaw @ https://www.meanbee.com 70 | * Kimberely Thomas @ http://www.redlightblinking.com -------------------------------------------------------------------------------- /patterns/scss/core/_form.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Gravity Department 3 | * 4 | * @author Brendan Falkowski (http://gravitydept.com) 5 | * @copyright Copyright 2014 Gravity Department 6 | * @license All rights reserved. 7 | */ 8 | 9 | 10 | // ============================================== 11 | // Forms 12 | // ============================================== 13 | 14 | 15 | // ---------------------------------------------- 16 | // Controls 17 | 18 | //.controls { } 19 | 20 | .controls .controls-title { 21 | margin-bottom:8px; 22 | font-size:$f-size-xs; 23 | font-weight:bold; 24 | line-height:1.3; 25 | } 26 | 27 | .controls .control { 28 | padding-bottom:5px; 29 | } 30 | 31 | .controls .control:last-child { 32 | padding-bottom:0; 33 | } 34 | 35 | 36 | // ---------------------------------------------- 37 | // Fieldsets 38 | 39 | .fieldset { 40 | margin-bottom:$margin-bottom; 41 | } 42 | 43 | .fieldset .legend { 44 | @include h2; 45 | margin-bottom:1em; 46 | padding-bottom:0.5em; 47 | border-bottom:1px solid $c-fieldset-border; 48 | } 49 | 50 | // ---------------------------------------------- 51 | // Input Box 52 | 53 | .input-box:after { 54 | @include clearfix; 55 | } 56 | 57 | // For adjacent select inputs. 58 | // Example: credit card expiration month and year. 59 | .input-box .v-fix { 60 | float:left; 61 | margin-right:5px; 62 | } 63 | 64 | 65 | // ---------------------------------------------- 66 | // Input Hint 67 | 68 | .input-hint { 69 | margin-top:5px; 70 | color:$c-text-subtle; 71 | font-size:$f-size-xs; 72 | line-height:1.3; 73 | } 74 | 75 | 76 | // ---------------------------------------------- 77 | // Input Quantity 78 | 79 | input[type=number].qty, 80 | input[type=text].qty { 81 | width:50px; 82 | } 83 | 84 | 85 | // ---------------------------------------------- 86 | // Input Text 87 | 88 | .input-text { 89 | @include appearance (none); 90 | @include border-radius (5px); 91 | @include box-shadow (black(0.2) 0 1px 5px 0 inset); 92 | border:1px solid $c-input-border; 93 | background:#FFF; 94 | font-size:$f-size; 95 | } 96 | 97 | .input-text[readonly] { 98 | opacity:0.6; 99 | } 100 | 101 | .input-text:focus { 102 | border-color:$c-input-border-focus; 103 | } 104 | 105 | .input-text.validation-failed { 106 | border-color:$c-danger; 107 | } 108 | 109 | input[type=email], 110 | input[type=number], 111 | input[type=password], 112 | input[type=tel], 113 | input[type=text] { 114 | width:21em; 115 | max-width:100%; 116 | height:40px; 117 | padding:0 8px; 118 | } 119 | 120 | .ie8 .input-text { 121 | max-width:none; 122 | } 123 | 124 | 125 | // ---------------------------------------------- 126 | // Labels 127 | 128 | label { 129 | display:inline-block; 130 | font-size:$f-size-xs; 131 | font-weight:bold; 132 | line-height:1.3; 133 | } 134 | 135 | label.required:after, 136 | span.required:after { 137 | content:' *'; 138 | color:$c-danger; 139 | font-weight:normal; 140 | } 141 | 142 | label.required em, 143 | span.required em { 144 | display:none; 145 | } 146 | 147 | 148 | // ---------------------------------------------- 149 | // Placeholder 150 | 151 | ::-webkit-input-placeholder { 152 | color:$c-input-placeholder; 153 | font-size:$f-size; 154 | } 155 | 156 | input:-moz-placeholder { 157 | color:$c-input-placeholder; 158 | font-size:$f-size; 159 | } 160 | 161 | 162 | // ---------------------------------------------- 163 | // Select 164 | 165 | // Select menus are replaced by JS with custom controls. 166 | // See: /scss/vendor/_selecty.scss 167 | 168 | select { 169 | max-width:100%; 170 | font-size:16px; // Prevent iOS from zooming on focus 171 | } 172 | 173 | select + select { 174 | margin-left:5px; 175 | } 176 | 177 | 178 | 179 | // ---------------------------------------------- 180 | // Textarea 181 | 182 | textarea { 183 | @include box-sizing (border-box); 184 | width:100%; 185 | max-width:40em; 186 | padding:5px; 187 | } 188 | 189 | 190 | // ---------------------------------------------- 191 | // Checkbox And Radio 192 | 193 | .checkbox, 194 | .radio { 195 | position:relative; 196 | top:-2px; 197 | display:inline-block; 198 | } 199 | 200 | .checkbox + label, 201 | .radio + label { 202 | width:auto; 203 | max-width:85%; 204 | padding:0 0 0 8px; 205 | font-size:$f-size; 206 | font-weight:normal; 207 | line-height:1.3; 208 | vertical-align:top; 209 | } 210 | 211 | 212 | // ---------------------------------------------- 213 | // Validation 214 | 215 | .validation-advice { 216 | margin:5px 0 0; 217 | color:$c-danger; 218 | font-size:$f-size-xs; 219 | } 220 | 221 | p.required { 222 | display:none; 223 | } 224 | 225 | 226 | 227 | // ============================================== 228 | // Forms - Contrast Module 229 | // ============================================== 230 | 231 | .contrast-module .input-text { 232 | border-color:$c-input-border-contrast; 233 | } 234 | 235 | 236 | 237 | // ============================================== 238 | // Stack Form 239 | // ============================================== 240 | 241 | .stack-form { 242 | margin-bottom:$gap; 243 | } 244 | 245 | //.stack-form .fieldset { } 246 | 247 | //.stack-form .fieldset .legend { } 248 | 249 | .stack-form .form-list li { 250 | display:block; 251 | margin-bottom:20px; 252 | } 253 | 254 | .stack-form .form-list li:after { 255 | @include clearfix; 256 | } 257 | 258 | .stack-form .input-box { 259 | padding-top:5px; 260 | } 261 | 262 | .stack-form .form-list .fields { 263 | overflow:hidden; 264 | margin-bottom:0; 265 | } 266 | 267 | .stack-form .form-list .fields .field { 268 | margin-bottom:18px; 269 | } 270 | 271 | //.stack-form .form-list .fields .field + .field { } 272 | 273 | 274 | 275 | // ============================================== 276 | // Scaffold Form 277 | // ============================================== 278 | 279 | .scaffold-form { 280 | margin-bottom:$gap; 281 | } 282 | 283 | .scaffold-form .form-list li { 284 | display:block; 285 | padding-bottom:20px; 286 | } 287 | 288 | .scaffold-form .form-list li:after { 289 | @include clearfix; 290 | } 291 | 292 | .scaffold-form .form-list .fields { 293 | padding:0; 294 | } 295 | 296 | .scaffold-form .field { 297 | overflow:hidden; 298 | padding-bottom:18px; 299 | } 300 | 301 | .scaffold-form label:first-child { 302 | float:left; 303 | width:140px; 304 | padding:0.75em 0.75em 0 0; 305 | } 306 | 307 | .scaffold-form select { 308 | margin-top:4px; 309 | } 310 | 311 | .scaffold-form .input-box { 312 | float:left; 313 | max-width:350px; 314 | } 315 | 316 | .scaffold-form .checkbox + label, 317 | .scaffold-form .radio + label { 318 | float:none; 319 | padding:0 0 0 8px; 320 | } 321 | 322 | .scaffold-form .buttons-set { 323 | padding-top:5px; 324 | } 325 | 326 | // ---------------------------------------------- 327 | 328 | @include bp (max-width, 599px) { 329 | 330 | .scaffold-form li { 331 | padding:0 0 18px; 332 | } 333 | 334 | .scaffold-form label:first-child { 335 | float:none; 336 | width:auto; 337 | padding:0; 338 | } 339 | 340 | .scaffold-form .input-box { 341 | float:none; 342 | max-width:100%; 343 | padding-top:5px; 344 | } 345 | 346 | } 347 | 348 | 349 | 350 | -------------------------------------------------------------------------------- /patterns/typography.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | Frontend Steward - Pattern Library 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 25 | 26 | 27 | 28 | 29 |
30 | Pattern Library 31 | Forms 32 | Tables 33 | Typography 34 | User Interface 35 |
36 | 37 |
38 |
39 |
Typography
40 | 41 | 42 | 43 | 44 | 45 |
46 |
Standard Text Formatting (.std)
47 | 48 |
49 |

Paragraph with interesting links and emphasized text and strong text.

50 | 51 |

An ordered list:

52 | 53 |
    54 |
  1. List item one
  2. 55 |
  3. List item two
  4. 56 |
  5. List item three
  6. 57 |
  7. List item four
  8. 58 |
59 | 60 |

An unordered list:

61 | 62 |
    63 |
  • List item one
  • 64 |
  • List item two
  • 65 |
  • List item three
  • 66 |
  • List item four
  • 67 |
68 | 69 |

Heading Level One

70 | 71 |

72 | Nulla facilisi. Duis aliquet egestas purus in blandit. Curabitur vulputate, ligula lacinia 73 | scelerisque tempor, lacus lacus ornare ante, ac egestas est urna sit amet arcu 74 |

75 | 76 |

Heading Level Two

77 | 78 |

79 | Nulla facilisi. Duis aliquet egestas purus in blandit. Curabitur vulputate, ligula lacinia 80 | scelerisque tempor, lacus lacus ornare ante, ac egestas est urna sit amet arcu 81 |

82 | 83 |

Heading Level Three

84 | 85 |

86 | Nulla facilisi. Duis aliquet egestas purus in blandit. Curabitur vulputate, ligula lacinia 87 | scelerisque tempor, lacus lacus ornare ante, ac egestas est urna sit amet arcu 88 |

89 | 90 |

Heading Level Four

91 | 92 |

93 | Nulla facilisi. Duis aliquet egestas purus in blandit. Curabitur vulputate, ligula lacinia 94 | scelerisque tempor, lacus lacus ornare ante, ac egestas est urna sit amet arcu 95 |

96 | 97 |
Heading Level Five
98 | 99 |

100 | Nulla facilisi. Duis aliquet egestas purus in blandit. Curabitur vulputate, ligula lacinia 101 | scelerisque tempor, lacus lacus ornare ante, ac egestas est urna sit amet arcu 102 |

103 | 104 |
Heading Level Six
105 | 106 |

107 | Nulla facilisi. Duis aliquet egestas purus in blandit. Curabitur vulputate, ligula lacinia 108 | scelerisque tempor, lacus lacus ornare ante, ac egestas est urna sit amet arcu 109 |

110 |
111 |
112 | 113 | 114 | 115 | 116 | 117 |
118 |
Heading Level 1
119 | 120 |

Heading 1 - No Class

121 |

Heading 2 - Class H1

122 |

Heading 3 - Class H1

123 |

Heading 4 - Class H1

124 |
Heading 5 - Class H1
125 |
Heading 6 - Class H1
126 |
127 | 128 | 129 | 130 | 131 | 132 |
133 |
Heading Level 2
134 | 135 |

Heading 1 - Class H2

136 |

Heading 2 - No Class

137 |

Heading 3 - Class H2

138 |

Heading 4 - Class H2

139 |
Heading 5 - Class H2
140 |
Heading 6 - Class H2
141 |
142 | 143 | 144 | 145 | 146 | 147 |
148 |
Heading Level 3
149 | 150 |

Heading 1 - Class H3

151 |

Heading 2 - Class H3

152 |

Heading 3 - No Class

153 |

Heading 4 - Class H3

154 |
Heading 5 - Class H3
155 |
Heading 6 - Class H3
156 |
157 | 158 | 159 | 160 | 161 | 162 |
163 |
Heading Level 4
164 | 165 |

Heading 1 - Class H4

166 |

Heading 2 - Class H4

167 |

Heading 3 - Class H4

168 |

Heading 4 - No Class

169 |
Heading 5 - Class H4
170 |
Heading 6 - Class H4
171 |
172 | 173 | 174 | 175 | 176 | 177 |
178 |
Heading Level 5
179 | 180 |

Heading 1 - Class H5

181 |

Heading 2 - Class H5

182 |

Heading 3 - Class H5

183 |

Heading 4 - Class H5

184 |
Heading 5 - No Class
185 |
Heading 6 - Class H5
186 |
187 | 188 | 189 | 190 | 191 | 192 |
193 |
Heading Level 6
194 | 195 |

Heading 1 - Class H6

196 |

Heading 2 - Class H6

197 |

Heading 3 - Class H6

198 |

Heading 4 - Class H6

199 |
Heading 5 - Class H6
200 |
Heading 6 - No Class
201 |
202 | 203 | 204 | 205 | 206 | 207 |
208 |
209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | -------------------------------------------------------------------------------- /code-style/phtml/minicart_original.phtml: -------------------------------------------------------------------------------- 1 | 28 | getInList()): ?> 29 |
  • 30 | 31 |
    32 | 33 | getSummaryCount() ?> 34 | getIsLinkMode() || !$this->getIsNeedToDisplaySideBar()): ?> 35 | getIsNeedToDisplaySideBar()): ?>data-toggle="dropdown" class="action showcart" href="getUrl('checkout/cart'); ?>"> 36 | 37 | 38 | 39 | getIsNeedToDisplaySideBar()): ?> 40 |
    41 | 44 |
    45 | 46 | 47 | 48 | 49 | 50 | 51 |
    52 |
    53 | getRecentItems() ?> 54 | 55 | 56 |
    57 |
      58 | 59 | getItemHtml($_item) ?> 60 | 61 |
    62 |
    63 | 64 | 65 | 66 | 67 | getCartEmptyMessage()): ?> 68 |

    getCartEmptyMessage(); ?>

    69 | 70 | 71 | 72 | getAllowCartLink()): ?> 73 | isPossibleOnepageCheckout() ?> 74 | 75 |
    76 | canApplyMsrp()): ?> 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 |
    85 | helper('Magento\Checkout\Helper\Data')->formatPrice($this->getSubtotal()) ?> 86 | getSubtotalInclTax()): ?> 87 | 88 | helper('Magento\Checkout\Helper\Data')->formatPrice($_subtotalInclTax) ?> 89 | helper('Magento\Tax\Helper\Data')->getIncExcText(true) ?> 90 | 91 | 92 |
    93 | 94 |
    95 | 96 | getChildHtml('minicart_info') ?> 97 |
    98 |
    99 | 100 | getChildHtml('extra_actions') ?> 101 | 104 | 105 |
    106 |
    107 | 108 | 109 | 110 |
    111 |
    112 | 113 |
    114 |
    115 | 116 | 117 | 129 | getInList()): ?> 130 |
  • 131 | 132 | 133 | 134 | -------------------------------------------------------------------------------- /code-style/phtml/minicart_proposed.phtml: -------------------------------------------------------------------------------- 1 | 28 | getInList()): ?> 29 |
  • 30 | 31 |
    32 | 33 | 34 | getSummaryCount(); ?> 35 | 36 | getIsLinkMode() || !$this->getIsNeedToDisplaySideBar()): ?> 37 | getIsNeedToDisplaySideBar()): ?>data-toggle="dropdown" class="action showcart" href="getUrl('checkout/cart'); ?>"> 38 | 39 | 40 | 41 | 42 | getIsNeedToDisplaySideBar()): ?> 43 |
    44 | 47 | 48 |
    49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 |
    57 | 58 |
    59 | getRecentItems(); ?> 60 | 61 | 62 | 63 | 64 |
    65 |
      66 | 67 | getItemHtml($_item); ?> 68 | 69 | 70 |
    71 |
    72 | 73 | 74 | 75 | 76 | 77 | getCartEmptyMessage()): ?> 78 |

    getCartEmptyMessage(); ?>

    79 | 80 | 81 | 82 | getAllowCartLink()): ?> 83 | isPossibleOnepageCheckout(); ?> 84 | 85 |
    86 | canApplyMsrp()): ?> 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 |
    96 | helper('Magento\Checkout\Helper\Data')->formatPrice($this->getSubtotal()); ?> 97 | 98 | getSubtotalInclTax()): ?> 99 | 100 | helper('Magento\Checkout\Helper\Data')->formatPrice($_subtotalInclTax); ?> 101 | helper('Magento\Tax\Helper\Data')->getIncExcText(true); ?> 102 | 103 | 104 |
    105 | 106 | 107 |
    108 | 109 | 110 | getChildHtml('minicart_info'); ?> 111 |
    112 |
    113 | 114 | getChildHtml('extra_actions'); ?> 115 | 118 | 119 |
    120 | 121 |
    122 | 123 | 124 | 125 |
    126 | 127 |
    128 | 129 |
    130 |
    131 | 132 | 133 | 134 | 146 | 147 | getInList()): ?> 148 |
  • 149 | 150 | 151 | 152 | -------------------------------------------------------------------------------- /code-style/phtml/shipping_original.phtml: -------------------------------------------------------------------------------- 1 | 26 | 27 |
    28 |
    29 |
    30 |
    31 |
    32 |

    33 |
    34 | 35 |
    36 | getDirectoryBlock()->getCountryHtmlSelect($this->getEstimateCountryId()) ?> 37 |
    38 |
    39 | getStateActive()): ?> 40 |
    41 | 42 |
    43 | 46 | 47 |
    48 |
    49 | 50 | getCityActive()): ?> 51 |
    52 | 53 |
    54 | 55 |
    56 |
    57 | 58 |
    59 | 60 |
    61 | 62 |
    63 |
    64 |
    65 | 66 |
    67 |
    68 |
    69 | 91 | getEstimateRates())): ?> 92 |
    93 |
    94 |
    95 | $_rates): ?> 96 |
    escapeHtml($this->getCarrierName($code)) ?>
    97 |
    98 | 99 |
    100 | getErrorMessage()): ?> 101 | escapeHtml($_rate->getErrorMessage()) ?> 102 | 103 | getCode()===$this->getAddressShippingMethod()) echo ' checked="checked"' ?> class="radio" /> 104 | 113 | 114 |
    115 | 116 |
    117 | 118 |
    119 |
    120 | 121 |
    122 |
    123 |
    124 | 125 |
    126 |
    127 | -------------------------------------------------------------------------------- /pivots/05-css-architecture.md: -------------------------------------------------------------------------------- 1 | # CSS Architecture 2 | 3 | ## Write HTML classes for (OO-CSS) not (chained-classes CSS). 4 | 5 | The class naming pattern adopted in Magento 2 is not a modern or extensible practice. 6 | 7 | Magento 2 has started requires all class names on HTML elements to consist of single-words, which are then chained in CSS selectors. The rationale is “Requires stronger architecture behind all visual styling. Enforces re-use of CSS classes instead of creating custom / unique CSS classes each time." 8 | 9 | See Magento 2 Wiki, Section A.1.2 https://wiki.magento.com/display/MAGE2DOC/Magento+Code+Demarcation+Standard 10 | 11 | There are significant architectural issues with authoring classes and selectors this way in a complex design system: 12 | 13 | 1. Chaining classes is less performant. Rendering engines read selectors from right to left, so the renderer must consider applying any style matched to the “product" class to over half the elements in the markup. This rendering calculation must be completed before any dimensional calculation can take place, and that means that visually the page does not stop rendering until the operation is complete. 14 | 1. Using single-word class names to chain selectors increases likelihood of accidental collisions with other modules and especially third-party code. Example: many modules could use a selector like “.description" or “.name" because they are so broad / general. Even with specific scoping in other modules, and styles applied to that global selector will be inherited by all other uses of the class. This leads to more attribute than are necessary in scoped selectors to override the cascaded attributes. The result is harder to maintain, increased bloat, and lower extensibility. It is harmful to be excessively generic with class names. 15 | 1. Chaining classes in a selector rapidly increases the specificity of that selector. This makes variations on an element more difficult to override and contributes to specificity escalation killing the cascading nature of CSS: 16 | * Classes are chained together to override a base class. 17 | * Parent classes are added to override the chained class. 18 | * Direct descendent selectors are added, which undesirably bind the selector to the markup structure. 19 | * An ID is added to the selector to overpower all the chained and familial specificity. 20 | * Finally, to override the ID it becomes necessary to use !important or inline styles. 21 | 1. That specificity war doesn't need to start if the base classes are engineered to take advantage of CSS' natural inheritance and cascade. 22 | 1. From a workflow perspective, it's often necessary to find all uses of a particular class value within a project. With chained selectors having class values like “products list items", you can search for “products list" but not “products items". A better class value would be “products-list products-items" (although this is redundant) because it would allow you to search on either term. The classes “products" or “list" or “items" are too generic to search for globally within a project. 23 | 24 | ### Problems in Magento 2 25 | 26 | The examples presented in the Magento 2 Wiki (A.1.2) do not accurately reflect how classes are used to build extensible design systems. 27 | 28 | #### Example HTML from Magento 2 29 | 30 | *File: /app/code/Magento/Catalog/view/frontend/product/list.phtml* 31 | 32 | ```php 33 |
      34 |
    1. 35 |
      ...
      36 | 37 |
      38 |
      ...
      39 | 40 |
      41 | ... 42 | Learn More 43 |
      44 | 45 |
      46 | 47 |
      48 |
      49 |
    2. 50 |
    51 | ``` 52 | 53 | The following CSS selectors (with specificity) are the minimal chain possible to select each element without collisions between other modules based on the Magento 2 markup: 54 | 55 | ``` 56 | 20 - .products.list {} 57 | 30 - .products.list .item {} 58 | 40 - .products.list .product.photo {} 59 | 40 - .products.list .product.details {} 60 | 40 - .products.list .product.name {} 61 | 40 - .products.list .product.description {} 62 | 60 - .products.list .product.description .action.more {} 63 | 40 - .products.list .product.actions {} 64 | 40 - .products.list .action.tocart {} 65 | ``` 66 | 67 | In this very simple example, additional flaws in chaining classes are obvious: 68 | 69 | 1. There is no distinction between classes which are used as block descriptors, element descriptors, or element modifiers. A well written class value should infer the appropriate CSS selector to minimally and uniquely identify that element set. 70 | 1. Element-named classes are so broad they will never have styling applied to them globally. This makes them redundant and only only serving to scope the content-based class name (which unnecessarily raised specificity). 71 | * The “product" class will never be usable independently because of its indiscriminate use on list items, image wrappers, text wrappers, and action wrappers. 72 | * The “list" class could applies to any type of list (and therefore none). 73 | * The “action" class could applies to elements which may be styled as text links, image links, or buttons depending on the implementation's needs. 74 | 1. Developers have too many options to write selectors for this component. Four of six have identical specificity, which means that the last selector declared in the CSS source order will have precedence. Selector applicability should never depend on source order in a properly architected design system: 75 | 76 | ``` 77 | .products.list li 78 | .products.list .product 79 | .products.list .item 80 | .products.items li 81 | .products.items .product 82 | .products.items .item 83 | ``` 84 | 85 | ### Improvement rewritten with OO-CSS 86 | 87 | Using OO-CSS principles, here is the same markup: 88 | 89 | ```php 90 |
      91 |
    1. 92 |
      ...
      93 | 94 |
      95 |
      ...
      96 | 97 |
      98 | ... 99 | Learn More 100 |
      101 | 102 |
      103 | 104 |
      105 |
      106 |
    2. 107 |
    108 | ``` 109 | 110 | And the improved CSS selectors (with specificity): 111 | 112 | ``` 113 | 10 - .products-list {} 114 | 20 - .products-list .item {} 115 | 20 - .products-list .product-photo {} 116 | 20 - .products-list .product-details {} 117 | 20 - .products-list .product-name {} 118 | 20 - .products-list .product-description {} 119 | 30 - .products-list .product-description .more-link {} 120 | 20 - .products-list .product-actions {} 121 | 20 - .products-list .add-to-cart {} 122 | ``` 123 | 124 | #### Reasons the OO-CSS approach is better: 125 | 126 | 1. All class names describe the element they are applied to. This makes it possible to define base styling across modules that is inherited and easily override-able with parent selectors. 127 | 1. The average specificity is 20, and the highest specificity is 30. In every case this is half the specificity of using chained classes. 128 | 1. This markup is significantly more extensible because we can define a global style for “.product-photo" and then cleanly override it based on its component context. Taken a step further you could provide custom styles for individual items while still keeping specificity under 30 at the element level. 129 | 1. It can also inherit object-based styles for components like buttons cleanly. Note: the “.button" class is applied, but to select the button instances we don't need to use it in a selector. Capturing only the content-based class name (.add-to-cart) utilizes the cascade but allows for variation. 130 | 131 | #### Further reading 132 | 133 | * http://www.stubbornella.org/content/2011/04/28/our-best-practices-are-killing-us/ 134 | * http://coding.smashingmagazine.com/2011/12/12/an-introduction-to-object-oriented-css-oocss/ 135 | * https://developer.mozilla.org/en-US/docs/Web/CSS/Specificity 136 | * http://csswizardry.com/2012/11/code-smells-in-css/ -------------------------------------------------------------------------------- /patterns/scss/_var.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Gravity Department 3 | * 4 | * @author Brendan Falkowski (http://gravitydept.com) 5 | * @copyright Copyright 2014 Gravity Department 6 | * @license All rights reserved. 7 | */ 8 | 9 | 10 | // ============================================== 11 | // Compass Defaults 12 | // ============================================== 13 | 14 | // Border Radius 15 | 16 | $default-border-radius: 5px; 17 | 18 | // Transition 19 | 20 | $default-transition-property: all; 21 | $default-transition-duration: 200ms; 22 | $default-transition-function: linear; 23 | $default-transition-delay: 0; 24 | 25 | 26 | 27 | // ============================================== 28 | // Base Values 29 | // ============================================== 30 | 31 | 32 | // ---------------------------------------------- 33 | // Units 34 | 35 | // Hex is the percentage difference between 101010 and 202020. 36 | // Used with Sass functions darken() and lighten(). 37 | $hex: 6.1%; 38 | 39 | 40 | // ---------------------------------------------- 41 | // Spacing 42 | 43 | // Gap is the standard vertical spacing between modules. 44 | $gap: 30px; 45 | 46 | // Trim is the whitespace between the content and viewport edge. 47 | $trim: 12px; 48 | 49 | // Liner is the whitespace between the content box and content. 50 | $liner: 12px; 51 | 52 | 53 | // ---------------------------------------------- 54 | // Dimensions - Max 55 | 56 | $max-content-width: 1250px; 57 | $max-container-width: $trim + $max-content-width + $trim; 58 | $max-std-formatted-width: 50em; 59 | 60 | 61 | // ---------------------------------------------- 62 | // Dimensions - Header 63 | 64 | $header-element-height: 46px; 65 | 66 | $header-primary-small-height: (50px + 1px); 67 | $header-primary-large-height: (100px + 1px); 68 | $header-primary-large-vertical-offset: (($header-primary-large-height - 1px - $header-element-height) / 2); 69 | 70 | $header-secondary-height: (46px + 1px); 71 | 72 | 73 | 74 | // ============================================== 75 | // Brand / Site Colors 76 | // ============================================== 77 | 78 | // Usually not used directly in Sass partials. 79 | // Only used to define context-based color vars in this file. 80 | 81 | $c-blue: #0368B3; 82 | $c-blue-bright: #0080DF; 83 | $c-green: #048C49; 84 | $c-orange: #FC6D1E; 85 | $c-red: #DF3100; 86 | $c-yellow: #ECDA00; 87 | $c-yellow-light: #FFF5E3; 88 | 89 | 90 | 91 | // ============================================== 92 | // Base Colors 93 | // ============================================== 94 | 95 | 96 | // ---------------------------------------------- 97 | // Text 98 | 99 | $c-text: #505050; 100 | $c-text-subtle: #909090; 101 | 102 | $c-text-contrast: #FFF; 103 | $c-text-contrast-subtle: #C0C0C0; 104 | 105 | 106 | // ---------------------------------------------- 107 | // Interaction 108 | 109 | $c-action: $c-blue-bright; 110 | $c-stimulus: darken($c-blue-bright, 4%); 111 | $c-subtle: #A0A0A0; 112 | 113 | $c-action-secondary: $c-green; 114 | $c-stimulus-secondary: darken($c-green, 4%); 115 | 116 | 117 | // ---------------------------------------------- 118 | // Notification States 119 | 120 | $c-danger: $c-red; 121 | $c-security: #E0F2FF; 122 | $c-success: $c-green; 123 | $c-warn: $c-yellow; 124 | 125 | 126 | 127 | // ============================================== 128 | // Element Colors 129 | // ============================================== 130 | 131 | 132 | // ---------------------------------------------- 133 | // Buttons 134 | 135 | $c-button: $c-blue-bright; 136 | $c-button-hover: darken($c-blue-bright, 4%); 137 | $c-button-active: darken($c-blue-bright, $hex); 138 | 139 | $c-button-danger: $c-danger; 140 | $c-button-danger-hover: darken($c-danger, 4%); 141 | $c-button-danger-active: darken($c-danger, $hex); 142 | 143 | $c-button-disabled: #E4E4E4; 144 | $c-button-disabled-text: #A4A4A4; 145 | 146 | $c-button-priority: $c-green; 147 | $c-button-priority-hover: darken($c-green, 4%); 148 | $c-button-priority-active: darken($c-green, $hex); 149 | 150 | $c-button-subtle: #808080; 151 | $c-button-subtle-hover: darken(#808080, 4%); 152 | $c-button-subtle-active: darken(#808080, $hex); 153 | 154 | 155 | // ---------------------------------------------- 156 | // Footer 157 | 158 | //$c-footer-background: #EFEFEF; [refactor] 159 | $c-footer-background: #808080; 160 | //$c-footer-border: #DBDBDB; [refactor] 161 | $c-footer-border: #505050; 162 | 163 | 164 | // ---------------------------------------------- 165 | // Forms 166 | 167 | $c-fieldset-border: #C0C0C0; 168 | 169 | $c-input-border: #A0A0A0; 170 | $c-input-border-contrast: #202020; 171 | $c-input-border-focus: #0368B3; 172 | 173 | $c-input-placeholder: $c-text-subtle; 174 | $c-input-text: $c-text; 175 | 176 | 177 | // ---------------------------------------------- 178 | // Header 179 | 180 | $c-header-primary-background: $c-blue; 181 | 182 | //$c-header-secondary-background: #EFEFEF; [refactor] 183 | $c-header-secondary-background: #FFF; 184 | //$c-header-secondary-border: #DBDBDB; [refactor] 185 | $c-header-secondary-border: #C0C0C0; 186 | 187 | 188 | // ---------------------------------------------- 189 | // Headings 190 | 191 | $c-h1: $c-text; 192 | $c-h2: $c-text; 193 | $c-h3: $c-text; 194 | $c-h4: $c-text; 195 | $c-h5: $c-text; 196 | $c-h6: $c-text; 197 | 198 | 199 | // ---------------------------------------------- 200 | // Link 201 | 202 | $c-link: $c-blue-bright; 203 | $c-link-hover: darken($c-blue-bright, 4%); 204 | $c-link-focus: darken($c-blue-bright, 4%); 205 | $c-link-active: darken($c-blue-bright, $hex); 206 | 207 | $c-link-contrast: lighten($c-link, 20%); 208 | $c-link-contrast-hover: lighten($c-link, 10%); 209 | $c-link-contrast-focus: lighten($c-link, 10%); 210 | $c-link-contrast-active: lighten($c-link, 20%); 211 | 212 | 213 | // ---------------------------------------------- 214 | // Module 215 | 216 | $c-module-background: #F4F4F4; 217 | 218 | $c-module-border: #C0C0C0; 219 | $c-module-border-subtle: #E0E0E0; 220 | 221 | $c-module-divider: #E0E0E0; 222 | 223 | 224 | // ---------------------------------------------- 225 | // Module (contrast) 226 | 227 | $c-module-contrast-background: #4E4E4E; 228 | $c-module-contrast-border: darken($c-module-contrast-background, 10%); 229 | 230 | 231 | // ---------------------------------------------- 232 | // Module (emphasis) 233 | 234 | $c-module-emphasis-background: #E8E8E8; 235 | $c-module-emphasis-border: #C0C0C0; 236 | 237 | 238 | // ---------------------------------------------- 239 | // Module (priority) 240 | 241 | $c-module-priority-background: $c-yellow-light; 242 | 243 | 244 | // ---------------------------------------------- 245 | // Social Networks 246 | 247 | $c-facebook: #3B5998; 248 | $c-google-plus: #E04B31; 249 | $c-pinterest: #DE2108; 250 | $c-tumblr: #2C4762; 251 | $c-twitter: #00ACED; 252 | $c-vk: #567DA4; 253 | $c-weibo: #EBDBC4; 254 | 255 | 256 | // ---------------------------------------------- 257 | // Table 258 | 259 | $c-table-background: #F4F4F4; 260 | $c-table-border: #C0C0C0; 261 | 262 | $c-table-zebra-odd: #FFF; 263 | $c-table-zebra-even: #F4F4F4; 264 | 265 | 266 | // ---------------------------------------------- 267 | // Tabs 268 | 269 | $c-tabs-background: #E8E8E8; 270 | $c-tabs-border: #C0C0C0; 271 | 272 | 273 | 274 | // ============================================== 275 | // Typography 276 | // ============================================== 277 | 278 | // Web fonts are loaded from a template. 279 | // See: /template/gravdept/page/html/web-font-loader.phtml 280 | 281 | 282 | // ---------------------------------------------- 283 | // Font Stacks 284 | 285 | $f-stack-sans: 'proxima-nova', 'Helvetica Neue', arial, sans-serif; 286 | $f-stack-serif: 'Georgia', serif; 287 | $f-stack-special: 'proxima-nova', 'Helvetica Neue', arial, sans-serif; 288 | 289 | $f-stack-helvetica: 'Helvetica Neue', helvetica, arial, sans-serif; 290 | 291 | $f-stack-default: $f-stack-sans; 292 | 293 | 294 | // ---------------------------------------------- 295 | // Font Sizes 296 | 297 | $f-size-xxl: 24px; 298 | $f-size-xl: 20px; 299 | $f-size-l: 18px; 300 | 301 | $f-size: 16px; 302 | 303 | $f-size-s: 15px; 304 | $f-size-xs: 14px; 305 | $f-size-xxs: 13px; 306 | $f-size-xxxs: 12px; 307 | $f-size-xxxxs: 11px; 308 | 309 | 310 | // ---------------------------------------------- 311 | // Base Measures 312 | 313 | $line-height: 1.5; 314 | $margin-bottom: 1.5em; 315 | 316 | 317 | -------------------------------------------------------------------------------- /patterns/scss/vendor/_normalize.scss: -------------------------------------------------------------------------------- 1 | /* normalize.css v2.0.1 | MIT License | git.io/normalize */ 2 | 3 | /* ========================================================================== 4 | HTML5 display definitions 5 | ========================================================================== */ 6 | 7 | /* 8 | * Corrects `block` display not defined in IE 8/9. 9 | */ 10 | 11 | article, 12 | aside, 13 | details, 14 | figcaption, 15 | figure, 16 | footer, 17 | header, 18 | hgroup, 19 | nav, 20 | section, 21 | summary { 22 | display: block; 23 | } 24 | 25 | /* 26 | * Corrects `inline-block` display not defined in IE 8/9. 27 | */ 28 | 29 | audio, 30 | canvas, 31 | video { 32 | display: inline-block; 33 | } 34 | 35 | /* 36 | * Prevents modern browsers from displaying `audio` without controls. 37 | * Remove excess height in iOS 5 devices. 38 | */ 39 | 40 | audio:not([controls]) { 41 | display: none; 42 | height: 0; 43 | } 44 | 45 | /* 46 | * Addresses styling for `hidden` attribute not present in IE 8/9. 47 | */ 48 | 49 | [hidden] { 50 | display: none; 51 | } 52 | 53 | /* ========================================================================== 54 | Base 55 | ========================================================================== */ 56 | 57 | /* 58 | * 1. Sets default font family to sans-serif. 59 | * 2. Prevents iOS text size adjust after orientation change, without disabling 60 | * user zoom. 61 | */ 62 | 63 | html { 64 | font-family: sans-serif; /* 1 */ 65 | -webkit-text-size-adjust: 100%; /* 2 */ 66 | -ms-text-size-adjust: 100%; /* 2 */ 67 | } 68 | 69 | /* 70 | * Removes default margin. 71 | */ 72 | 73 | body { 74 | margin: 0; 75 | } 76 | 77 | /* ========================================================================== 78 | Links 79 | ========================================================================== */ 80 | 81 | /* 82 | * Addresses `outline` inconsistency between Chrome and other browsers. 83 | */ 84 | 85 | a:focus { 86 | outline: thin dotted; 87 | } 88 | 89 | /* 90 | * Improves readability when focused and also mouse hovered in all browsers. 91 | */ 92 | 93 | a:active, 94 | a:hover { 95 | outline: 0; 96 | } 97 | 98 | /* ========================================================================== 99 | Typography 100 | ========================================================================== */ 101 | 102 | /* 103 | * Addresses `h1` font sizes within `section` and `article` in Firefox 4+, 104 | * Safari 5, and Chrome. 105 | */ 106 | 107 | h1 { 108 | font-size: 2em; 109 | } 110 | 111 | /* 112 | * Addresses styling not present in IE 8/9, Safari 5, and Chrome. 113 | */ 114 | 115 | abbr[title] { 116 | border-bottom: 1px dotted; 117 | } 118 | 119 | /* 120 | * Addresses style set to `bolder` in Firefox 4+, Safari 5, and Chrome. 121 | */ 122 | 123 | b, 124 | strong { 125 | font-weight: bold; 126 | } 127 | 128 | /* 129 | * Addresses styling not present in Safari 5 and Chrome. 130 | */ 131 | 132 | dfn { 133 | font-style: italic; 134 | } 135 | 136 | /* 137 | * Addresses styling not present in IE 8/9. 138 | */ 139 | 140 | mark { 141 | background: #ff0; 142 | color: #000; 143 | } 144 | 145 | 146 | /* 147 | * Corrects font family set oddly in Safari 5 and Chrome. 148 | */ 149 | 150 | code, 151 | kbd, 152 | pre, 153 | samp { 154 | font-family: monospace, serif; 155 | font-size: 1em; 156 | } 157 | 158 | /* 159 | * Improves readability of pre-formatted text in all browsers. 160 | */ 161 | 162 | pre { 163 | white-space: pre; 164 | white-space: pre-wrap; 165 | word-wrap: break-word; 166 | } 167 | 168 | /* 169 | * Sets consistent quote types. 170 | */ 171 | 172 | q { 173 | quotes: "\201C" "\201D" "\2018" "\2019"; 174 | } 175 | 176 | /* 177 | * Addresses inconsistent and variable font size in all browsers. 178 | */ 179 | 180 | small { 181 | font-size: 80%; 182 | } 183 | 184 | /* 185 | * Prevents `sub` and `sup` affecting `line-height` in all browsers. 186 | */ 187 | 188 | sub, 189 | sup { 190 | font-size: 75%; 191 | line-height: 0; 192 | position: relative; 193 | vertical-align: baseline; 194 | } 195 | 196 | sup { 197 | top: -0.5em; 198 | } 199 | 200 | sub { 201 | bottom: -0.25em; 202 | } 203 | 204 | /* ========================================================================== 205 | Embedded content 206 | ========================================================================== */ 207 | 208 | /* 209 | * Removes border when inside `a` element in IE 8/9. 210 | */ 211 | 212 | img { 213 | border: 0; 214 | } 215 | 216 | /* 217 | * Corrects overflow displayed oddly in IE 9. 218 | */ 219 | 220 | svg:not(:root) { 221 | overflow: hidden; 222 | } 223 | 224 | /* ========================================================================== 225 | Figures 226 | ========================================================================== */ 227 | 228 | /* 229 | * Addresses margin not present in IE 8/9 and Safari 5. 230 | */ 231 | 232 | figure { 233 | margin: 0; 234 | } 235 | 236 | /* ========================================================================== 237 | Forms 238 | ========================================================================== */ 239 | 240 | /* 241 | * Define consistent border, margin, and padding. 242 | */ 243 | 244 | fieldset { 245 | border: 1px solid #c0c0c0; 246 | margin: 0 2px; 247 | padding: 0.35em 0.625em 0.75em; 248 | } 249 | 250 | /* 251 | * 1. Corrects color not being inherited in IE 8/9. 252 | * 2. Remove padding so people aren't caught out if they zero out fieldsets. 253 | */ 254 | 255 | legend { 256 | border: 0; /* 1 */ 257 | padding: 0; /* 2 */ 258 | } 259 | 260 | /* 261 | * 1. Corrects font family not being inherited in all browsers. 262 | * 2. Corrects font size not being inherited in all browsers. 263 | * 3. Addresses margins set differently in Firefox 4+, Safari 5, and Chrome 264 | */ 265 | 266 | button, 267 | input, 268 | select, 269 | textarea { 270 | font-family: inherit; /* 1 */ 271 | font-size: 100%; /* 2 */ 272 | margin: 0; /* 3 */ 273 | } 274 | 275 | /* 276 | * Addresses Firefox 4+ setting `line-height` on `input` using `!important` in 277 | * the UA stylesheet. 278 | */ 279 | 280 | button, 281 | input { 282 | line-height: normal; 283 | } 284 | 285 | /* 286 | * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` 287 | * and `video` controls. 288 | * 2. Corrects inability to style clickable `input` types in iOS. 289 | * 3. Improves usability and consistency of cursor style between image-type 290 | * `input` and others. 291 | */ 292 | 293 | button, 294 | html input[type="button"], /* 1 */ 295 | input[type="reset"], 296 | input[type="submit"] { 297 | -webkit-appearance: button; /* 2 */ 298 | cursor: pointer; /* 3 */ 299 | } 300 | 301 | /* 302 | * Re-set default cursor for disabled elements. 303 | */ 304 | 305 | button[disabled], 306 | input[disabled] { 307 | cursor: default; 308 | } 309 | 310 | /* 311 | * 1. Addresses box sizing set to `content-box` in IE 8/9. 312 | * 2. Removes excess padding in IE 8/9. 313 | */ 314 | 315 | input[type="checkbox"], 316 | input[type="radio"] { 317 | box-sizing: border-box; /* 1 */ 318 | padding: 0; /* 2 */ 319 | } 320 | 321 | /* 322 | * 1. Addresses `appearance` set to `searchfield` in Safari 5 and Chrome. 323 | * 2. Addresses `box-sizing` set to `border-box` in Safari 5 and Chrome 324 | * (include `-moz` to future-proof). 325 | */ 326 | 327 | input[type="search"] { 328 | -webkit-appearance: textfield; /* 1 */ 329 | -moz-box-sizing: content-box; 330 | -webkit-box-sizing: content-box; /* 2 */ 331 | box-sizing: content-box; 332 | } 333 | 334 | /* 335 | * Removes inner padding and search cancel button in Safari 5 and Chrome 336 | * on OS X. 337 | */ 338 | 339 | input[type="search"]::-webkit-search-cancel-button, 340 | input[type="search"]::-webkit-search-decoration { 341 | -webkit-appearance: none; 342 | } 343 | 344 | /* 345 | * Removes inner padding and border in Firefox 4+. 346 | */ 347 | 348 | button::-moz-focus-inner, 349 | input::-moz-focus-inner { 350 | border: 0; 351 | padding: 0; 352 | } 353 | 354 | /* 355 | * 1. Removes default vertical scrollbar in IE 8/9. 356 | * 2. Improves readability and alignment in all browsers. 357 | */ 358 | 359 | textarea { 360 | overflow: auto; /* 1 */ 361 | vertical-align: top; /* 2 */ 362 | } 363 | 364 | /* ========================================================================== 365 | Tables 366 | ========================================================================== */ 367 | 368 | /* 369 | * Remove most spacing between table cells. 370 | */ 371 | 372 | table { 373 | border-collapse: collapse; 374 | border-spacing: 0; 375 | } 376 | -------------------------------------------------------------------------------- /pivots/02-atomic-templates.md: -------------------------------------------------------------------------------- 1 | # Atomic Templates 2 | 3 | ## Write templates atomically to contain the smallest possible output. 4 | 5 | Magento's layout is assembled from many PHTML templates. To newcomers, this architecture is difficult to understand because not all the parts are known. It's difficult to find templates until you learn to use template path hints. 6 | 7 | For experienced developers working on complex frontend implementations, templates need to be even more aggressively atomic. This would improve maintainability, reduce overrides, and make the core frontend more extensible. 8 | 9 | This is hard to describe without examples, but I'll try to keep them relatively simple. The example patterns are taken from Magento 1.x for familiarity. 10 | 11 | ### Example A: Product View 12 | 13 | In Magento 1.x, heavily modifying the product view is a mess because the ```catalog/product/view.phtml``` template contains a significant amount of variability between product types and a great deal of logic spills out into conditionally rendering child templates. This manifests as a problem when components that should be independent like “price box(es)", “inventory status", “quantity input", or “add-to actions" render in different templates within the same parent depending on the product type. 14 | 15 | #### Original Magento Code (excerpt) 16 | 17 | *File: /app/design/frontend/base/default/template/catalog/product/view.phtml* 18 | 19 | ```php 20 | hasOptions()):?> 21 |
    22 | isSaleable()): ?> 23 | getChildHtml('addtocart') ?> 24 | helper('wishlist')->isAllow() || $_compareUrl=$this->helper('catalog/product_compare')->getAddUrl($_product)): ?> 25 | __('OR') ?> 26 | 27 | 28 | getChildHtml('addto') ?> 29 |
    30 | getChildHtml('extra_buttons') ?> 31 | isSaleable()): ?> 32 |
    33 | getChildHtml('addto') ?> 34 |
    35 | 36 | ``` 37 | 38 | *File: /app/design/frontend/base/default/template/catalog/product/view/addto.phtml* 39 | 40 | ```php 41 | 52 | ``` 53 | 54 | #### Rewritten with Atomic Templates 55 | 56 | Magento would be significantly more extensible if individual components were broken off into templates. Each child template would contain all the conditional logic to render that content depending on the product type — rather than the parent (```view.phtml```). 57 | 58 | This has two major benefits: 59 | 60 | 1. The parent template (```view.phtml```) becomes a logic-less layer used purely for markup structure. You can completely re-arrange all the child templates without writing deeply nested conditionals to span gaps between product types. 61 | 1. Maintenance is drastically decreased because you only have to maintain the child templates that required modifications and the parent template (```view.phtml```). 62 | 63 | Deconstructed into more atomic files: 64 | 65 | ``` 66 | /app/design/frontend/base/default/template/catalog/product/view.phtml 67 | /app/design/frontend/base/default/template/catalog/product/view/add-to-cart.phtml 68 | /app/design/frontend/base/default/template/catalog/product/view/add-to-wishlist.phtml 69 | /app/design/frontend/base/default/template/catalog/product/view/add-to-compare.phtml 70 | ``` 71 | 72 | In this structure you could very easily rewrite (view.phtml) without accepting maintenance burden for any of the interior code as this: 73 | 74 | ```php 75 |
    76 | getChildHtml('add-to-cart'); ?> 77 |
    78 | 79 |
    80 | getChildHtml('add-to-wishlist'); ?> 81 | getChildHtml('add-to-compare'); ?> 82 |
    83 | ``` 84 | 85 | Or this equally viable markup pattern: 86 | 87 | ```php 88 |
    89 | getChildHtml('add-to-cart'); ?> 90 | 91 | 95 |
    96 | ``` 97 | 98 | By deconstructing content into atomic templates restructuring the markup becomes trivially easy. 99 | 100 | ### Example B: Cart Items Table 101 | 102 | The cart items table is another example where a monster template creates a major maintenance burden to modify significantly. To render this table, there are two templates in play: 103 | 104 | ``` 105 | /app/design/frontend/base/default/checkout/cart.phtml 106 | /app/design/frontend/base/default/checkout/cart/item/default.phtml 107 | ``` 108 | 109 | In a responsive site, a mobile-first approach quickly breaks the assumption that a table is the appropriate structure to begin with. But the item renderer is a blob of tax conditions and pricing output, which changes frequently in core updates (not something you want to maintain). 110 | 111 | #### Rewritten with Atomic Templates 112 | 113 | Adopting the following template structure would improve extensibility and reduce maintenance: 114 | 115 | ``` 116 | /app/design/frontend/base/default/checkout/cart.phtml 117 | /app/design/frontend/base/default/checkout/cart/item/default.phtml 118 | /app/design/frontend/base/default/checkout/cart/item/fragment/name.phtml 119 | /app/design/frontend/base/default/checkout/cart/item/fragment/options.phtml 120 | /app/design/frontend/base/default/checkout/cart/item/fragment/unit-price.phtml 121 | /app/design/frontend/base/default/checkout/cart/item/fragment/remove.phtml 122 | /app/design/frontend/base/default/checkout/cart/item/fragment/subtotal.phtml 123 | /app/design/frontend/base/default/checkout/cart/item/fragment/quantity.phtml 124 | /app/design/frontend/base/default/checkout/cart/item/fragment/edit.phtml 125 | /app/design/frontend/base/default/checkout/cart/item/fragment/image.phtml 126 | ``` 127 | 128 | The item renderer is responsible solely for the structure of each “row", and the fragments contain all the content-specific logic and output. They can be re-assembled in any configuration to suit the project, whether that is a table or not. 129 | 130 | *File: /app/design/frontend/base/default/checkout/cart/item/default.phtml* 131 | 132 | ```php 133 | 134 | getChildHtml('image'); ?> 135 | 136 | 137 | getChildHtml('name'); ?> 138 | getChildHtml('options'); ?> 139 | getChildHtml('edit'); ?> 140 | 141 | 142 | getChildHtml('unit-price'); ?> 143 | 144 | getChildHtml('quantity'); ?> 145 | 146 | getChildHtml('subtotal'); ?> 147 | 148 | getChildHtml('remove'); ?> 149 | 150 | ``` 151 | 152 | Or a completely separate pattern that's easier to transform from small screens upward: 153 | 154 | ```php 155 |
  • 156 |
    157 | getChildHtml('name'); ?> 158 | getChildHtml('options'); ?> 159 | getChildHtml('image'); ?> 160 |
    161 | 162 |
    163 | getChildHtml('quantity'); ?> 164 | getChildHtml('edit'); ?> 165 | getChildHtml('remove'); ?> 166 |
    167 | 168 |
    169 | getChildHtml('unit-price'); ?> 170 | getChildHtml('subtotal'); ?> 171 |
    172 |
  • 173 | ``` 174 | 175 | From this simple example, it's obvious how much code could be put out of the maintenance scope for implementors and how much more flexible the UI can be. 176 | 177 | ### Magento must atom-icize all templates knowing that it cannot write universally appropriate markup. 178 | 179 | It must implement a design architecture which facilitates decomposition and reconstruction as efficiently as possible. This also minimizes that amount of code which is drawn into themes (and thus maintenance). 180 | 181 | ##### Further reading 182 | 183 | * http://bradfrostweb.com/blog/post/atomic-web-design/ 184 | * http://www.slideshare.net/bradfrostweb/atomic-design 185 | * http://vimeo.com/67476280 -------------------------------------------------------------------------------- /principles/README.md: -------------------------------------------------------------------------------- 1 | # Principles and Standards in Magento 2's Frontend Architecture 2 | 3 | Principles and standards are necessary to establish and maintain frontend quality in Magento 2. The lack of documentation in Magento 1.x contributed to significant variability between implementations, and constant collisions between extensions and customizations. 4 | 5 | A top-down approach is not good enough. It is critical that Magento observes the community's experience and uses it to **define, adopt, strictly conform to, and promote** a set of frontend principles / standards. 6 | 7 | This precedent cannot only exist in code. Magento must demonstrate how/why these principles are applied through documentation, examples, and written dialog. 8 | 9 | This document defines what the community thinks Magento 2's frontend architecture needs to serve. There are no technical recommendations here, these are high-level goals (more “why" than “how"). These principles ensure the technical underpinnings can adapt to diverse implementation requirements and be future-friendly. 10 | 11 | --- 12 | 13 | ## 1. The perfect CMS does not constrain or coerce its frontend output. 14 | 15 | The core frontend package must be output-agnostic. Magento must not prescribe specific visual, layout, interactive, information architecture, or content paradigms in the frontend. 16 | 17 | This is significantly more critical for Magento's frontend than backend. Whereas retailers appreciate and benefit from a very standardized backend, their frontend must be wholly unique to differentiate them and afford competitive advantages. 18 | 19 | **Magento must adopt frontend output neutrality as the ultimate goal.** It must build the underlying platform, which gives implementers and retailers the freedom to meet their unique needs. 20 | 21 | --- 22 | 23 | ## 2. The core frontend package cannot be Magento's demo. 24 | 25 | In Magento 1.x, the “base/default" package (which all themes inherited) was not neutral. There are specific frontend decisions in its code which only served Magento's “demo" themes. 26 | 27 | Overriding demo-slanted practices is repetitively annoying, wastes resources better applied elsewhere, and increases cost of ownership. Magento 2's theming architecture provides for more explicit separation of the core package and theme assets, but Magento cannot passively achieve this goal. 28 | 29 | Magento must consciously resist bending the core frontend to serve demoing Magento 2. Failing to do this weakens the whole platform, and puts the need of Magento's demo above every customer. 30 | 31 | **Magento must implement its demos as strictly separate themes** just as implementers do, and work to refactor the core frontend away from demo-slanted decisions. 32 | 33 | --- 34 | 35 | ## 3. Invest in Magento's frontend patterns. Stay lean and framework agnostic. 36 | 37 | Using any off-the-shelf frontend framework is going to be a misstep. Magento needs to expand upon and document the patterns/practices it already uses rather than adopting an external framework. 38 | 39 | Not being framework agnostic has the following consequences: 40 | 41 | **Immediate Issues** — Binding frameworks like Bootstrap or Foundation to Magento would prescribe so much that the entire frontend would need to be rewritten for their implementation to be useful. That would both swamp the Magento 2 timeline, and entrench too much to any third-party. 42 | 43 | **Short-term Issues** — Bootstrap (v3) and Foundation (v5) have both undergone sweeping major revisions in the space of a few years. They move significantly faster than Magento can. This virtually guarantees that by the time any external framework is implemented it will be outdated. Then Magento would be committed to it for a decade because forcing the deep framework changes on existing implementations alongside core updates will not be accepted. 44 | 45 | **Long-term issues** — When a framework falls completely out of use, it becomes dead weight when you have to implement something else on top of it. This is exactly what happened with Prototype and jQuery in Magento 1.x The crux of modern multi-device frontend development is that it's better to be lean than thick. 46 | 47 | #### Build Magento's Patterns 48 | 49 | The reason frontend frameworks are good isn't solely because of their patterns, it is mainly because of the deep and robust documentation. This is what helps new developers become effective with the system quickly. Magento has tons of frontend patterns, but zero frontend documentation. 50 | 51 | Frameworks do make it easier for companies to hire developers because of widespread familiarity, but this benefit is rooted in having clear, accessible documentation. 52 | 53 | You can always cherry-pick specific components from third-party frameworks (they're designed for that) but replacing the frontend architecture with Bootstrap is a lot more involved than documenting the long-standing Magento patterns, which are already a good start. 54 | 55 | Magento must document how/why/where its patterns are implemented in the core frontend, so new developers don't require the exposure of several projects before noticing the patterns exist. In doing this, Magento's thinking about its frontend architecture will shift from themes/pages to patterns/components. This ideological change is what makes a frontend framework strengthen rapidly. 56 | 57 | #### Further Reading 58 | 59 | * https://github.com/magento/magento2/issues/457 60 | * https://github.com/magento/magento2/issues/347 61 | * https://speakerdeck.com/brendanfalkowski/responsive-ecommerce-part-two?slide=114 62 | * http://daverupert.com/2013/04/responsive-deliverables/ 63 | 64 | --- 65 | 66 | ## 4. Treat frontend performance as the bottleneck it is compared to backend performance. 67 | 68 | Performance optimization in Magento 1.x was focused on backend improvements only. Scalability, concurrency, and read/write speeds are important for delivering highly accessible web applications, but they are not what makes websites slow for users. 69 | 70 | From the Alexa top 50,000 sites just 13% of page load time was on the backend. 87% of load time is on the frontend. Put that into concrete terms for site taking 10 seconds to load: 71 | 72 | * 10% backend improvement = 0.13 seconds faster load 73 | * 10% frontend improvement = 0.87 seconds faster load 74 | * Frontend improvements have 6.7x more impact on every page load 75 | 76 | **Magento must prioritize projects that improve frontend performance.** Performance is a design constraint, and a bad frontend undermines the perfect backend. There are dozens of small, simple tweaks with measurable performance gains. The biggest violators today are JavaScript architecture and images. 77 | 78 | #### Further Reading 79 | 80 | * https://speakerdeck.com/brendanfalkowski/responsive-ecommerce-part-two?slide=65 81 | 82 | --- 83 | 84 | ## 5. Use tools to evaluate and test frontend code quality. 85 | 86 | #### HTML 87 | 88 | HTML must pass the W3C Markup Validator: http://validator.w3.org/ 89 | 90 | Every frontend-accessible URL in Magento including markup after DOM manipulations must pass the validator using an HTML5 doctype. Many older browser's support of web standards degrades significantly if a markup error forces the browser's rendering engine into “quirks mode". The importance of producing valid, consistent markup is not debatable. 91 | 92 | #### HTML5 Document Outlining 93 | 94 | Although a constantly moving target, since it spans the entire core frontend package, Magento must deeply understand and consider how the HTML5 Document Outlining algorithm impacts the frontend architecture: http://html5doctor.com/outlines/ 95 | 96 | #### JavaScript 97 | 98 | JavaScript must pass the JSHint: http://jshint.com/about/ 99 | 100 | JSHint is a static code analysis tool (not functional or unit tests). It's purpose is to detect errors and potential problems in JavaScript code and to enforce your team's coding conventions. It is not a substitute for code reviews or other testing, but it can significantly improve debugging time and code quality. 101 | 102 | #### CSS 103 | 104 | CSS evaluation should not be a primary concern of Magento's for several reasons: 105 | 106 | 1. Conforming to CSS' syntax is trivial compared to most programming languages. 107 | 1. The breadth of vendor prefixes used today will not validate against the official CSS specification. 108 | 1. Use of CSS pre-processors means that authored CSS is very different than output CSS, and compilation processes have built-in quality tools. 109 | 110 | Most importantly, CSS is always the implementation's domain and not the core frontend. Highly customized implementations in Magento 1.x will not re-use any of the core CSS. Especially in responsive implementations, the developer's hand is what determines nearly all CSS quality, in which case tools like CSSLint are useful for evaluation: http://csslint.net/ 111 | 112 | --- 113 | 114 | ## 6. Decouple HTML, CSS and JS from PHP classes. 115 | 116 | TBD 117 | 118 | --- 119 | 120 | ## 7. Decouple visual (CSS) layer from the functional (JavaScript) layer. 121 | 122 | TBD 123 | 124 | --- 125 | 126 | ## 8. Decouple functional (JavaScript) layer from the markup (HTML). 127 | 128 | TBD -------------------------------------------------------------------------------- /patterns/js/modernizr.custom.min.js: -------------------------------------------------------------------------------- 1 | /* Modernizr 2.6.2 (Custom Build) | MIT & BSD 2 | * Build: http://modernizr.com/download/#-csstransforms-csstransforms3d-csstransitions-localstorage-touch-shiv-mq-cssclasses-teststyles-testprop-testallprops-prefixes-domprefixes-load 3 | */ 4 | ;window.Modernizr=function(a,b,c){function A(a){j.cssText=a}function B(a,b){return A(m.join(a+";")+(b||""))}function C(a,b){return typeof a===b}function D(a,b){return!!~(""+a).indexOf(b)}function E(a,b){for(var d in a){var e=a[d];if(!D(e,"-")&&j[e]!==c)return b=="pfx"?e:!0}return!1}function F(a,b,d){for(var e in a){var f=b[a[e]];if(f!==c)return d===!1?a[e]:C(f,"function")?f.bind(d||b):f}return!1}function G(a,b,c){var d=a.charAt(0).toUpperCase()+a.slice(1),e=(a+" "+o.join(d+" ")+d).split(" ");return C(b,"string")||C(b,"undefined")?E(e,b):(e=(a+" "+p.join(d+" ")+d).split(" "),F(e,b,c))}var d="2.6.2",e={},f=!0,g=b.documentElement,h="modernizr",i=b.createElement(h),j=i.style,k,l={}.toString,m=" -webkit- -moz- -o- -ms- ".split(" "),n="Webkit Moz O ms",o=n.split(" "),p=n.toLowerCase().split(" "),q={},r={},s={},t=[],u=t.slice,v,w=function(a,c,d,e){var f,i,j,k,l=b.createElement("div"),m=b.body,n=m||b.createElement("body");if(parseInt(d,10))while(d--)j=b.createElement("div"),j.id=e?e[d]:h+(d+1),l.appendChild(j);return f=["­",'"].join(""),l.id=h,(m?l:n).innerHTML+=f,n.appendChild(l),m||(n.style.background="",n.style.overflow="hidden",k=g.style.overflow,g.style.overflow="hidden",g.appendChild(n)),i=c(l,a),m?l.parentNode.removeChild(l):(n.parentNode.removeChild(n),g.style.overflow=k),!!i},x=function(b){var c=a.matchMedia||a.msMatchMedia;if(c)return c(b).matches;var d;return w("@media "+b+" { #"+h+" { position: absolute; } }",function(b){d=(a.getComputedStyle?getComputedStyle(b,null):b.currentStyle)["position"]=="absolute"}),d},y={}.hasOwnProperty,z;!C(y,"undefined")&&!C(y.call,"undefined")?z=function(a,b){return y.call(a,b)}:z=function(a,b){return b in a&&C(a.constructor.prototype[b],"undefined")},Function.prototype.bind||(Function.prototype.bind=function(b){var c=this;if(typeof c!="function")throw new TypeError;var d=u.call(arguments,1),e=function(){if(this instanceof e){var a=function(){};a.prototype=c.prototype;var f=new a,g=c.apply(f,d.concat(u.call(arguments)));return Object(g)===g?g:f}return c.apply(b,d.concat(u.call(arguments)))};return e}),q.touch=function(){var c;return"ontouchstart"in a||a.DocumentTouch&&b instanceof DocumentTouch?c=!0:w(["@media (",m.join("touch-enabled),("),h,")","{#modernizr{top:9px;position:absolute}}"].join(""),function(a){c=a.offsetTop===9}),c},q.csstransforms=function(){return!!G("transform")},q.csstransforms3d=function(){var a=!!G("perspective");return a&&"webkitPerspective"in g.style&&w("@media (transform-3d),(-webkit-transform-3d){#modernizr{left:9px;position:absolute;height:3px;}}",function(b,c){a=b.offsetLeft===9&&b.offsetHeight===3}),a},q.csstransitions=function(){return G("transition")},q.localstorage=function(){try{return localStorage.setItem(h,h),localStorage.removeItem(h),!0}catch(a){return!1}};for(var H in q)z(q,H)&&(v=H.toLowerCase(),e[v]=q[H](),t.push((e[v]?"":"no-")+v));return e.addTest=function(a,b){if(typeof a=="object")for(var d in a)z(a,d)&&e.addTest(d,a[d]);else{a=a.toLowerCase();if(e[a]!==c)return e;b=typeof b=="function"?b():b,typeof f!="undefined"&&f&&(g.className+=" "+(b?"":"no-")+a),e[a]=b}return e},A(""),i=k=null,function(a,b){function k(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x",d.insertBefore(c.lastChild,d.firstChild)}function l(){var a=r.elements;return typeof a=="string"?a.split(" "):a}function m(a){var b=i[a[g]];return b||(b={},h++,a[g]=h,i[h]=b),b}function n(a,c,f){c||(c=b);if(j)return c.createElement(a);f||(f=m(c));var g;return f.cache[a]?g=f.cache[a].cloneNode():e.test(a)?g=(f.cache[a]=f.createElem(a)).cloneNode():g=f.createElem(a),g.canHaveChildren&&!d.test(a)?f.frag.appendChild(g):g}function o(a,c){a||(a=b);if(j)return a.createDocumentFragment();c=c||m(a);var d=c.frag.cloneNode(),e=0,f=l(),g=f.length;for(;e",f="hidden"in a,j=a.childNodes.length==1||function(){b.createElement("a");var a=b.createDocumentFragment();return typeof a.cloneNode=="undefined"||typeof a.createDocumentFragment=="undefined"||typeof a.createElement=="undefined"}()}catch(c){f=!0,j=!0}})();var r={elements:c.elements||"abbr article aside audio bdi canvas data datalist details figcaption figure footer header hgroup mark meter nav output progress section summary time video",shivCSS:c.shivCSS!==!1,supportsUnknownElements:j,shivMethods:c.shivMethods!==!1,type:"default",shivDocument:q,createElement:n,createDocumentFragment:o};a.html5=r,q(b)}(this,b),e._version=d,e._prefixes=m,e._domPrefixes=p,e._cssomPrefixes=o,e.mq=x,e.testProp=function(a){return E([a])},e.testAllProps=G,e.testStyles=w,g.className=g.className.replace(/(^|\s)no-js(\s|$)/,"$1$2")+(f?" js "+t.join(" "):""),e}(this,this.document),function(a,b,c){function d(a){return"[object Function]"==o.call(a)}function e(a){return"string"==typeof a}function f(){}function g(a){return!a||"loaded"==a||"complete"==a||"uninitialized"==a}function h(){var a=p.shift();q=1,a?a.t?m(function(){("c"==a.t?B.injectCss:B.injectJs)(a.s,0,a.a,a.x,a.e,1)},0):(a(),h()):q=0}function i(a,c,d,e,f,i,j){function k(b){if(!o&&g(l.readyState)&&(u.r=o=1,!q&&h(),l.onload=l.onreadystatechange=null,b)){"img"!=a&&m(function(){t.removeChild(l)},50);for(var d in y[c])y[c].hasOwnProperty(d)&&y[c][d].onload()}}var j=j||B.errorTimeout,l=b.createElement(a),o=0,r=0,u={t:d,s:c,e:f,a:i,x:j};1===y[c]&&(r=1,y[c]=[]),"object"==a?l.data=c:(l.src=c,l.type=a),l.width=l.height="0",l.onerror=l.onload=l.onreadystatechange=function(){k.call(this,r)},p.splice(e,0,u),"img"!=a&&(r||2===y[c]?(t.insertBefore(l,s?null:n),m(k,j)):y[c].push(l))}function j(a,b,c,d,f){return q=0,b=b||"j",e(a)?i("c"==b?v:u,a,b,this.i++,c,d,f):(p.splice(this.i++,0,a),1==p.length&&h()),this}function k(){var a=B;return a.loader={load:j,i:0},a}var l=b.documentElement,m=a.setTimeout,n=b.getElementsByTagName("script")[0],o={}.toString,p=[],q=0,r="MozAppearance"in l.style,s=r&&!!b.createRange().compareNode,t=s?l:n.parentNode,l=a.opera&&"[object Opera]"==o.call(a.opera),l=!!b.attachEvent&&!l,u=r?"object":l?"script":"img",v=l?"script":u,w=Array.isArray||function(a){return"[object Array]"==o.call(a)},x=[],y={},z={timeout:function(a,b){return b.length&&(a.timeout=b[0]),a}},A,B;B=function(a){function b(a){var a=a.split("!"),b=x.length,c=a.pop(),d=a.length,c={url:c,origUrl:c,prefixes:a},e,f,g;for(f=0;f 26 | 27 | 28 | 29 |
    30 |
    31 |

    32 |
    33 | 34 |
    35 |
    36 |
    37 |

    38 | 39 |

    40 | 41 |
    42 | 43 | 44 |
    45 | getDirectoryBlock()->getCountryHtmlSelect($this->getEstimateCountryId()); ?> 46 |
    47 |
    48 | 49 | getStateActive()): ?> 50 |
    51 | 52 | 53 |
    54 | 57 | 58 | 59 |
    60 |
    61 | 62 | 63 | getCityActive()): ?> 64 |
    65 | 66 | 67 |
    68 | 69 |
    70 |
    71 | 72 | 73 |
    74 | 75 | 76 |
    77 | 78 |
    79 |
    80 | 81 |
    82 | 85 |
    86 |
    87 |
    88 | 89 | 111 | 112 | getEstimateRates())): ?> 113 |
    114 |
      115 | $_rates): ?> 116 |
    • 117 |
      118 | escapeHtml($this->getCarrierName($code)); ?> 119 |
      120 | 121 |
        122 | 123 |
        124 | getErrorMessage()): ?> 125 | escapeHtml($_rate->getErrorMessage()); ?> 126 | 127 | getCode() === $this->getAddressShippingMethod()) ?>checked="checked" /> 128 | 129 | 143 | 144 |
        145 | 146 |
      147 |
    • 148 | 149 |
    150 | 151 |
    152 | 155 |
    156 |
    157 | 158 |
    159 |
    160 | -------------------------------------------------------------------------------- /patterns/js/enquire.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * enquire.js v2.1.0 - Awesome Media Queries in JavaScript 3 | * Copyright (c) 2013 Nick Williams - http://wicky.nillia.ms/enquire.js 4 | * License: MIT (http://www.opensource.org/licenses/mit-license.php) 5 | */ 6 | 7 | ;(function (name, context, factory) { 8 | var matchMedia = context.matchMedia; 9 | 10 | if (typeof module !== 'undefined' && module.exports) { 11 | module.exports = factory(matchMedia); 12 | } 13 | else if (typeof define === 'function' && define.amd) { 14 | define(function() { 15 | return (context[name] = factory(matchMedia)); 16 | }); 17 | } 18 | else { 19 | context[name] = factory(matchMedia); 20 | } 21 | }('enquire', this, function (matchMedia) { 22 | 23 | 'use strict'; 24 | 25 | /*jshint unused:false */ 26 | /** 27 | * Helper function for iterating over a collection 28 | * 29 | * @param collection 30 | * @param fn 31 | */ 32 | function each(collection, fn) { 33 | var i = 0, 34 | length = collection.length, 35 | cont; 36 | 37 | for(i; i < length; i++) { 38 | cont = fn(collection[i], i); 39 | if(cont === false) { 40 | break; //allow early exit 41 | } 42 | } 43 | } 44 | 45 | /** 46 | * Helper function for determining whether target object is an array 47 | * 48 | * @param target the object under test 49 | * @return {Boolean} true if array, false otherwise 50 | */ 51 | function isArray(target) { 52 | return Object.prototype.toString.apply(target) === '[object Array]'; 53 | } 54 | 55 | /** 56 | * Helper function for determining whether target object is a function 57 | * 58 | * @param target the object under test 59 | * @return {Boolean} true if function, false otherwise 60 | */ 61 | function isFunction(target) { 62 | return typeof target === 'function'; 63 | } 64 | 65 | /** 66 | * Delegate to handle a media query being matched and unmatched. 67 | * 68 | * @param {object} options 69 | * @param {function} options.match callback for when the media query is matched 70 | * @param {function} [options.unmatch] callback for when the media query is unmatched 71 | * @param {function} [options.setup] one-time callback triggered the first time a query is matched 72 | * @param {boolean} [options.deferSetup=false] should the setup callback be run immediately, rather than first time query is matched? 73 | * @constructor 74 | */ 75 | function QueryHandler(options) { 76 | this.options = options; 77 | !options.deferSetup && this.setup(); 78 | } 79 | QueryHandler.prototype = { 80 | 81 | /** 82 | * coordinates setup of the handler 83 | * 84 | * @function 85 | */ 86 | setup : function() { 87 | if(this.options.setup) { 88 | this.options.setup(); 89 | } 90 | this.initialised = true; 91 | }, 92 | 93 | /** 94 | * coordinates setup and triggering of the handler 95 | * 96 | * @function 97 | */ 98 | on : function() { 99 | !this.initialised && this.setup(); 100 | this.options.match && this.options.match(); 101 | }, 102 | 103 | /** 104 | * coordinates the unmatch event for the handler 105 | * 106 | * @function 107 | */ 108 | off : function() { 109 | this.options.unmatch && this.options.unmatch(); 110 | }, 111 | 112 | /** 113 | * called when a handler is to be destroyed. 114 | * delegates to the destroy or unmatch callbacks, depending on availability. 115 | * 116 | * @function 117 | */ 118 | destroy : function() { 119 | this.options.destroy ? this.options.destroy() : this.off(); 120 | }, 121 | 122 | /** 123 | * determines equality by reference. 124 | * if object is supplied compare options, if function, compare match callback 125 | * 126 | * @function 127 | * @param {object || function} [target] the target for comparison 128 | */ 129 | equals : function(target) { 130 | return this.options === target || this.options.match === target; 131 | } 132 | 133 | }; 134 | /** 135 | * Represents a single media query, manages it's state and registered handlers for this query 136 | * 137 | * @constructor 138 | * @param {string} query the media query string 139 | * @param {boolean} [isUnconditional=false] whether the media query should run regardless of whether the conditions are met. Primarily for helping older browsers deal with mobile-first design 140 | */ 141 | function MediaQuery(query, isUnconditional) { 142 | this.query = query; 143 | this.isUnconditional = isUnconditional; 144 | this.handlers = []; 145 | this.mql = matchMedia(query); 146 | 147 | var self = this; 148 | this.listener = function(mql) { 149 | self.mql = mql; 150 | self.assess(); 151 | }; 152 | this.mql.addListener(this.listener); 153 | } 154 | MediaQuery.prototype = { 155 | 156 | /** 157 | * add a handler for this query, triggering if already active 158 | * 159 | * @param {object} handler 160 | * @param {function} handler.match callback for when query is activated 161 | * @param {function} [handler.unmatch] callback for when query is deactivated 162 | * @param {function} [handler.setup] callback for immediate execution when a query handler is registered 163 | * @param {boolean} [handler.deferSetup=false] should the setup callback be deferred until the first time the handler is matched? 164 | */ 165 | addHandler : function(handler) { 166 | var qh = new QueryHandler(handler); 167 | this.handlers.push(qh); 168 | 169 | this.matches() && qh.on(); 170 | }, 171 | 172 | /** 173 | * removes the given handler from the collection, and calls it's destroy methods 174 | * 175 | * @param {object || function} handler the handler to remove 176 | */ 177 | removeHandler : function(handler) { 178 | var handlers = this.handlers; 179 | each(handlers, function(h, i) { 180 | if(h.equals(handler)) { 181 | h.destroy(); 182 | return !handlers.splice(i,1); //remove from array and exit each early 183 | } 184 | }); 185 | }, 186 | 187 | /** 188 | * Determine whether the media query should be considered a match 189 | * 190 | * @return {Boolean} true if media query can be considered a match, false otherwise 191 | */ 192 | matches : function() { 193 | return this.mql.matches || this.isUnconditional; 194 | }, 195 | 196 | /** 197 | * Clears all handlers and unbinds events 198 | */ 199 | clear : function() { 200 | each(this.handlers, function(handler) { 201 | handler.destroy(); 202 | }); 203 | this.mql.removeListener(this.listener); 204 | this.handlers.length = 0; //clear array 205 | }, 206 | 207 | /* 208 | * Assesses the query, turning on all handlers if it matches, turning them off if it doesn't match 209 | */ 210 | assess : function() { 211 | var action = this.matches() ? 'on' : 'off'; 212 | 213 | each(this.handlers, function(handler) { 214 | handler[action](); 215 | }); 216 | } 217 | }; 218 | /** 219 | * Allows for registration of query handlers. 220 | * Manages the query handler's state and is responsible for wiring up browser events 221 | * 222 | * @constructor 223 | */ 224 | function MediaQueryDispatch () { 225 | if(!matchMedia) { 226 | throw new Error('matchMedia not present, legacy browsers require a polyfill'); 227 | } 228 | 229 | this.queries = {}; 230 | this.browserIsIncapable = !matchMedia('only all').matches; 231 | } 232 | 233 | MediaQueryDispatch.prototype = { 234 | 235 | /** 236 | * Registers a handler for the given media query 237 | * 238 | * @param {string} q the media query 239 | * @param {object || Array || Function} options either a single query handler object, a function, or an array of query handlers 240 | * @param {function} options.match fired when query matched 241 | * @param {function} [options.unmatch] fired when a query is no longer matched 242 | * @param {function} [options.setup] fired when handler first triggered 243 | * @param {boolean} [options.deferSetup=false] whether setup should be run immediately or deferred until query is first matched 244 | * @param {boolean} [shouldDegrade=false] whether this particular media query should always run on incapable browsers 245 | */ 246 | register : function(q, options, shouldDegrade) { 247 | var queries = this.queries, 248 | isUnconditional = shouldDegrade && this.browserIsIncapable; 249 | 250 | if(!queries[q]) { 251 | queries[q] = new MediaQuery(q, isUnconditional); 252 | } 253 | 254 | //normalise to object in an array 255 | if(isFunction(options)) { 256 | options = { match : options }; 257 | } 258 | if(!isArray(options)) { 259 | options = [options]; 260 | } 261 | each(options, function(handler) { 262 | queries[q].addHandler(handler); 263 | }); 264 | 265 | return this; 266 | }, 267 | 268 | /** 269 | * unregisters a query and all it's handlers, or a specific handler for a query 270 | * 271 | * @param {string} q the media query to target 272 | * @param {object || function} [handler] specific handler to unregister 273 | */ 274 | unregister : function(q, handler) { 275 | var query = this.queries[q]; 276 | 277 | if(query) { 278 | if(handler) { 279 | query.removeHandler(handler); 280 | } 281 | else { 282 | query.clear(); 283 | delete this.queries[q]; 284 | } 285 | } 286 | 287 | return this; 288 | } 289 | }; 290 | 291 | return new MediaQueryDispatch(); 292 | 293 | })); -------------------------------------------------------------------------------- /patterns/scss/core/_common.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Gravity Department 3 | * 4 | * @author Brendan Falkowski (http://gravitydept.com) 5 | * @copyright Copyright 2014 Gravity Department 6 | * @license All rights reserved. 7 | */ 8 | 9 | 10 | // ============================================== 11 | // Address 12 | // ============================================== 13 | 14 | .address-list address { 15 | margin-bottom:1em; 16 | } 17 | 18 | 19 | 20 | // ============================================== 21 | // Availability 22 | // ============================================== 23 | 24 | .availability { 25 | font-size:$f-size-xxs; 26 | text-transform:uppercase; 27 | } 28 | 29 | .availability.in-stock { 30 | color:$c-success; 31 | } 32 | 33 | .availability.available-soon, 34 | .availability.out-of-stock { 35 | color:$c-danger; 36 | } 37 | 38 | 39 | 40 | // ============================================== 41 | // Block Module 42 | // ============================================== 43 | 44 | .block { 45 | @include border-radius (5px); 46 | margin-bottom:$gap; 47 | border:1px solid $c-module-border; 48 | background:$c-module-background; 49 | } 50 | 51 | .block-title { 52 | position:relative; 53 | padding-top:4px; 54 | padding-bottom:10px; 55 | border-bottom:1px solid $c-module-border; 56 | } 57 | 58 | .block-title h2, 59 | .block-title h3 { 60 | @include h3; 61 | margin-bottom:0; 62 | } 63 | 64 | .block-content { 65 | padding-top:15px; 66 | background:#FFF; 67 | } 68 | 69 | .block-content.unpad { 70 | padding:0; 71 | } 72 | 73 | 74 | 75 | // ============================================== 76 | // Block Module - With Toggle 77 | // ============================================== 78 | 79 | .block.toggle { 80 | background:$c-module-background; 81 | } 82 | 83 | .block.toggle .block-title { 84 | @include not-selectable; 85 | padding:10px 14px; 86 | border-bottom:0; 87 | background:darken($c-module-background, 5%); 88 | } 89 | 90 | .block.toggle .block-title:hover { 91 | background:darken($c-module-background, 10%); 92 | cursor:pointer; 93 | } 94 | 95 | .block.toggle .block-title:before { 96 | @include triangle (down, 6px, $c-text); 97 | right:14px; 98 | top:50%; 99 | margin-top:-3px; 100 | } 101 | 102 | // Toggle - Active 103 | 104 | .block.toggle-active .block-title { 105 | background:darken($c-module-background, 10%); 106 | } 107 | 108 | .block.toggle-active .block-title:before { 109 | @include triangle (up, 6px, $c-text); 110 | } 111 | 112 | 113 | 114 | // ============================================== 115 | // Buttons 116 | // ============================================== 117 | 118 | .button { 119 | @include border-radius (4px); 120 | @include box-shadow (black(0.2) 0 0 0 1px inset); 121 | background:$c-button; 122 | display:inline-block; 123 | padding:0 15px; 124 | border:0; 125 | color:#FFF; 126 | font-size:$f-size; 127 | font-weight:normal; 128 | line-height:40px; 129 | text-align:center; 130 | vertical-align:middle; 131 | white-space:nowrap; 132 | } 133 | 134 | a.button { 135 | text-decoration:none; 136 | } 137 | 138 | .button:hover, 139 | a.button:hover { 140 | background:$c-button-hover; 141 | color:#FFF; 142 | cursor:pointer; 143 | } 144 | 145 | .button:focus, 146 | .button:active { 147 | background:$c-button-active; 148 | color:#FFF; 149 | } 150 | 151 | 152 | 153 | // ============================================== 154 | // Button + Adjacent Button 155 | // ============================================== 156 | 157 | .button + .button { 158 | margin-left:5px; 159 | } 160 | 161 | 162 | 163 | // ============================================== 164 | // Button + Adjacent Link 165 | // ============================================== 166 | 167 | .button + .alt-action { 168 | margin-left:10px; 169 | font-size:$f-size-xs; 170 | line-height:28px; 171 | } 172 | 173 | 174 | 175 | // ============================================== 176 | // Button - Sizes 177 | // ============================================== 178 | 179 | .button-huge { 180 | padding:0 24px; 181 | font-size:$f-size-xxl; 182 | font-weight:300; 183 | line-height:60px; 184 | } 185 | 186 | .button-large { 187 | padding:0 15px; 188 | font-size:$f-size-xl; 189 | line-height:48px; 190 | } 191 | 192 | .button-small { 193 | padding:0 10px; 194 | font-size:$f-size-xs; 195 | line-height:30px; 196 | } 197 | 198 | .button-micro { 199 | padding:0 10px; 200 | font-size:$f-size-xxs; 201 | line-height:24px; 202 | } 203 | 204 | 205 | 206 | // ============================================== 207 | // Button - Block Width 208 | // ============================================== 209 | 210 | .button-block { 211 | width:100%; 212 | } 213 | 214 | 215 | 216 | // ============================================== 217 | // Button State - Danger 218 | // ============================================== 219 | 220 | .button-danger { 221 | background:$c-button-danger; 222 | } 223 | 224 | .button-danger:hover, 225 | .button-danger:focus, 226 | a.button-danger:hover, 227 | a.button-danger:focus { 228 | background:$c-button-danger-hover; 229 | color:#FFF; 230 | } 231 | 232 | .button-danger:active, 233 | a.button-danger:active { 234 | background:$c-button-danger-active; 235 | color:#FFF; 236 | } 237 | 238 | 239 | 240 | // ============================================== 241 | // Button State - Disabled 242 | // Use "class" for . 243 | // Use "pseudo-selector" for form elements supporting "disabled" attribute. 244 | // ============================================== 245 | 246 | .button.disabled, 247 | .button:disabled { 248 | background:$c-button-disabled; 249 | color:$c-button-disabled-text; 250 | } 251 | 252 | .button.disabled:hover, 253 | .button.disabled:focus, 254 | .button:disabled:hover, 255 | .button:disabled:focus, 256 | a.button.disabled:hover, 257 | a.button.disabled:focus, 258 | a.button:disabled:hover, 259 | a.button:disabled:focus { 260 | background:$c-button-disabled; 261 | color:$c-button-disabled-text; 262 | cursor:not-allowed; 263 | } 264 | 265 | .button.disabled:active, 266 | .button:disabled:active, 267 | a.button.disabled:active, 268 | a.button:disabled:active { 269 | background:$c-button-disabled; 270 | color:$c-button-disabled-text; 271 | } 272 | 273 | 274 | 275 | // ============================================== 276 | // Button State - Priority 277 | // ============================================== 278 | 279 | .button-priority { 280 | background:$c-button-priority; 281 | color:#FFF; 282 | } 283 | 284 | .button-priority:hover, 285 | .button-priority:focus, 286 | a.button-priority:hover, 287 | a.button-priority:focus { 288 | background:$c-button-priority-hover; 289 | color:#FFF; 290 | } 291 | 292 | .button-priority:active, 293 | a.button-priority:active { 294 | background:$c-button-priority-active; 295 | color:#FFF; 296 | } 297 | 298 | 299 | // ============================================== 300 | // Button State - Subtle 301 | // ============================================== 302 | 303 | .button-subtle { 304 | background:$c-button-subtle; 305 | color:#FFF; 306 | } 307 | 308 | .button-subtle:hover, 309 | .button-subtle:focus, 310 | a.button-subtle:hover, 311 | a.button-subtle:focus { 312 | background:$c-button-subtle-hover; 313 | color:#FFF; 314 | } 315 | 316 | .button-subtle:active, 317 | a.button-subtle:active { 318 | background:$c-button-subtle-active; 319 | color:#FFF; 320 | } 321 | 322 | 323 | // ============================================== 324 | // Item Options 325 | // ============================================== 326 | 327 | .item.options { 328 | padding-left: 10px; 329 | } 330 | 331 | .item.options:after { 332 | @include clearfix; 333 | } 334 | 335 | .item.options dt { 336 | float: left; 337 | clear: left; 338 | font-weight: bold; 339 | font-style: italic; 340 | } 341 | 342 | .item.options dt:after { 343 | content: ': '; 344 | } 345 | 346 | .item.options dd { 347 | float: left; 348 | padding-left: 15px; 349 | margin: 0 0 6px; 350 | } 351 | 352 | 353 | // ============================================== 354 | // Breadcrumbs 355 | // ============================================== 356 | 357 | .breadcrumbs { 358 | overflow:hidden; 359 | } 360 | 361 | .breadcrumbs li { 362 | float:left; 363 | font-size:$f-size-xxs; 364 | } 365 | 366 | .breadcrumbs a { 367 | float:left; 368 | } 369 | 370 | .breadcrumbs strong { 371 | font-weight:normal; 372 | } 373 | 374 | .breadcrumbs span { 375 | float:left; 376 | padding:0 7px; 377 | } 378 | 379 | 380 | 381 | // ============================================== 382 | // Callout 383 | // ============================================== 384 | 385 | .callout { 386 | @include border-radius (5px); 387 | margin-bottom:$margin-bottom; 388 | padding:15px; 389 | border:1px solid $c-module-border; 390 | background:$c-module-background; 391 | } 392 | 393 | 394 | 395 | // ============================================== 396 | // Container 397 | // ============================================== 398 | 399 | .container { 400 | position:relative; 401 | max-width:($trim + $max-content-width + $trim); 402 | margin:0 auto; 403 | padding:15px; 404 | } 405 | 406 | .container:after { 407 | @include clearfix; 408 | } 409 | 410 | 411 | 412 | // ============================================== 413 | // Helper Classes 414 | // ============================================== 415 | 416 | 417 | // ---------------------------------------------- 418 | // Magento Helpers 419 | 420 | .a-center { 421 | text-align:center; 422 | } 423 | 424 | .a-right, 425 | .align-right { 426 | text-align:right; 427 | } 428 | 429 | .no-display { 430 | display:none !important; 431 | } 432 | 433 | .nobr, 434 | .nowrap { 435 | white-space:nowrap; 436 | } 437 | 438 | .width-full { 439 | width:100%; 440 | } 441 | 442 | 443 | // ---------------------------------------------- 444 | // Custom Helpers 445 | 446 | .hidden { 447 | display:none; 448 | } 449 | 450 | 451 | 452 | // ============================================== 453 | // Messages 454 | // ============================================== 455 | 456 | .messages { 457 | margin:0 0 15px; 458 | } 459 | 460 | .messages li li { 461 | @include border-radius (2px); 462 | position:relative; 463 | margin-bottom:5px; 464 | padding:15px; 465 | background:$c-module-background; 466 | color:#FFF; 467 | line-height:1.3; 468 | } 469 | 470 | .messages .error-msg li { 471 | background:$c-danger; 472 | } 473 | 474 | .messages .notice-msg li { 475 | background:$c-warn; 476 | color:$c-text; 477 | } 478 | 479 | .messages .success-msg li { 480 | background:$c-success; 481 | } 482 | 483 | //.messages a { } 484 | 485 | 486 | 487 | // ============================================== 488 | // Standard Formatted Text Block 489 | // ============================================== 490 | 491 | .std { 492 | max-width:$max-std-formatted-width; 493 | } 494 | 495 | .std ol { 496 | list-style:decimal outside; 497 | margin-bottom:$margin-bottom; 498 | } 499 | 500 | .std ol li { 501 | margin-left:2em; 502 | } 503 | 504 | .std ul { 505 | list-style:disc outside; 506 | margin-bottom:$margin-bottom; 507 | } 508 | 509 | .std ul li { 510 | margin-left:2em; 511 | } 512 | 513 | .std .note { 514 | color:$c-text-subtle; 515 | font-size:$f-size-xs; 516 | } 517 | 518 | 519 | 520 | // ============================================== 521 | // Toggle UI 522 | // ============================================== 523 | 524 | .no-js .toggle-button, 525 | .js .toggle-target { 526 | display:none; 527 | } 528 | 529 | .js .toggle-active .toggle-target { 530 | display:block; 531 | } 532 | 533 | 534 | -------------------------------------------------------------------------------- /patterns/tables.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | Frontend Steward - Pattern Library 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 25 | 26 | 27 | 28 | 29 | 36 | 37 |
    38 |
    39 |
    Tables
    40 | 41 | 42 | 43 | 44 | 45 |
    46 |
    Table - Data
    47 | 48 |

    49 | Core table style. 50 | Compresses fluidly for small screens. 51 | Example: my orders, checkout summary. 52 |

    53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 |
    Order #DateTotalStatusAction
    Primary Action
    1000001331/31/14$2,950.75CompleteView Order
    1000001311/31/14$80.76PendingView Order
    1000001081/31/14$62.51PendingView Order
    97 | 98 |

    Summary Tables

    99 |

    100 | In the example above, we have a "summary" table, which is a table that summarizes content and allows the user to take 101 | an action to view all the details. In a summary table, there are some columns that are more important than others. 102 | On smaller viewports, it is acceptable to hide the unessential columns, as the user can take an action to view 103 | full details. We hide unnecessary columns by using .data-p[1-4] classes to indicate the importance of 104 | a specific column. The .data-p* classes are then hidden on smaller viewports. The exact breakpoint at which a 105 | specific .data-p* class may vary, based on the context of the table. 106 |

    107 | 108 |

    Example .data-p* classes

    109 | 110 |
    .data-p1 {
    111 |     display:table-cell;
    112 | }
    113 | 
    114 | .data-p2,
    115 | .data-p3,
    116 | .data-p4 {
    117 |     display:none;
    118 | }
    119 | 
    120 | @include bp (min-width, 480px) {
    121 | 
    122 |     .data-p2 {
    123 |         display:table-cell;
    124 |     }
    125 | 
    126 | }
    127 | 
    128 | @include bp (min-width, 600px) {
    129 | 
    130 |     .data-p3 {
    131 |         display:table-cell;
    132 |     }
    133 | 
    134 | }
    135 | 
    136 | @include bp (min-width, 768px) {
    137 | 
    138 |     .data-p4 {
    139 |         display:table-cell;
    140 |     }
    141 | 
    142 | }
    143 | 144 |
    Table - Linearize
    145 | 146 |

    147 | Tables with many columns do not display well on small viewports. While hiding columns on *summary* tables is 148 | acceptable, it is not acceptable on data tables because users we should not restrict access to information 149 | based on viewport size. In order to display all of the data in this type of table without forcing the user to 150 | scroll, we use a "linearize" pattern that turns cells into rows. 151 |

    152 | 153 |

    Example table

    154 | 155 | 156 | 157 | 158 | 161 | 164 | 167 | 170 | 173 | 174 | 175 | 176 | 177 | 180 | 183 | 184 | 185 | 188 | 191 | 192 | 193 | 196 | 199 | 200 | 201 | 202 | 203 | 214 | 217 | 220 | 227 | 230 | 231 | 232 | 233 | 234 | 245 | 248 | 251 | 258 | 261 | 262 | 263 | 264 | 265 | 268 | 271 | 274 | 277 | 280 | 281 | 282 | 287 | 288 | 289 | 292 | 295 | 298 | 301 | 304 | 305 | 306 |
    159 | Product Name 160 | 162 | SKU 163 | 165 | Price 166 | 168 | Qty 169 | 171 | Subtotal 172 |
    178 | Subtotal 179 | 181 | $340.00 182 |
    186 | Shipping & Handling 187 | 189 | $15.00 190 |
    194 | Grand Total 195 | 197 | $355.00 198 |
    204 | Test Product #2 205 |
    206 |
    207 | Color 208 |
    209 |
    210 | Purple 211 |
    212 |
    213 |
    215 | Test Product-Purple 216 | 218 | $140.00 219 | 221 |
      222 |
    • 223 | Ordered2 224 |
    • 225 |
    226 |
    228 | $240.00
    229 |
    235 | Test Product 236 |
    237 |
    238 | Color 239 |
    240 |
    241 | Green 242 |
    243 |
    244 |
    246 | Test Product-Green 247 | 249 | $120.00 250 | 252 |
      253 |
    • 254 | Ordered2 255 |
    • 256 |
    257 |
    259 | $240.00
    260 |
    266 | Bundled Product 267 | 269 | Bundled Product-Test Product-Green 270 | 272 | $100.00
    273 |
    275 |   276 | 278 | $100.00
    279 |
    283 |
    284 | Laptop 285 |
    286 |
    290 | 1 x Test Product-Green $100.00 291 | 293 | Test Product-Green 294 | 296 |   297 | 299 | Ordered: 1
    300 |
    302 |   303 |
    307 | 308 |

    The markup for a linearized table follows this pattern

    309 | 310 |
    <table class="linearize-table">
    311 |     <thead>
    312 |         <tr>
    313 |             <th>Product Name</th>
    314 |             <th>Price</th>
    315 |         </tr>
    316 |     </thead>
    317 |     <tbody>
    318 |         <tr>
    319 |             <-- The data-label attribute will be added via JS -->
    320 |             <td data-label="Product Name">Mountain Bike</td>
    321 |             <td data-label="Price">$3,000</td>
    322 |         </tr>
    323 |         <tr>
    324 |             <td data-label="Product Name">Helment</td>
    325 |             <td data-label="Price">$180</td>
    326 |         </tr>
    327 |     </tbody>
    328 | </table>
    329 | 330 |

    331 | When a linearize table is displayed on a small viewport, the "data-label" attribute is used as the label for 332 | that cell 333 |

    334 |
    .linearize-table tbody td[data-label]:before {
    335 |     content:attr(data-label) ":";
    336 | }
    337 | 338 |

    Rather than adding the data-label attribute to all tbody td elements, we have a jQuery decorator that does this for us

    339 | 340 |

    341 | The decorator allows us to write linearize table markup without the data-label attributes. This is useful because 342 | Magento has multiple item render template files, and using a decorator eliminates the need to update label/th values 343 | in multiple templates. 344 |

    345 | 346 |

    347 | In some linearized tables, the first column in the table does not need to be prepended with a label, as its 348 | meaning is apparent (eg, product name). For this type of column, a "data-no-label" attribute can be added to the 349 | th. You can see an example of this in the linearized table below. 350 |

    351 | 352 |
    // ==============================================
    353 | // UI Pattern - Linearize Table
    354 | // ==============================================
    355 | 
    356 | $('.linearize-table').each(function(){
    357 | 
    358 |     var cellLabels = [];
    359 | 
    360 |     // Loop through all table headers to find the labels
    361 |     $(this).find('thead th').each(function(index){
    362 |         // Only assign label if there is no data-no-label attribute on the th. Adding data-no-label will display
    363 |         // the value of each cell without it being prepended by a label, which can be useful for product name and
    364 |         // similar data types.
    365 |         cellLabels[index] = ($(this).is('[data-no-label]')) ? false : $(this).text().trim();
    366 |     });
    367 | 
    368 |     $(this).find('tbody tr').each(function(){
    369 |         // Looping through each td inside of each row so that the index value will match that of thead > tr > th
    370 |         $(this).children('td').each(function(index){
    371 |             var label = cellLabels[index];
    372 |             // As long as the th did not contain a data-no-label attribute, add the data-label attribute
    373 |             if (label) {
    374 |                 $(this).attr('data-label', label);
    375 |             }
    376 |         });
    377 |     });
    378 | });
    379 | 380 |

    Using the JS decorator, we can now write our markup like this

    381 | 382 |
    <table class="linearize-table">
    383 |     <thead>
    384 |         <tr>
    385 |             <th data-no-label>Product Name</th>
    386 |             <th>Price</th>
    387 |             <th>Qty</th>
    388 |         </tr>
    389 |     </thead>
    390 |     <tbody>
    391 |         <tr>
    392 |             <td>Mountain Bike</td>
    393 |             <td>$3,000</td>
    394 |             <td>2</td>
    395 |         </tr>
    396 |         <tr>
    397 |             <td>Helment</td>
    398 |             <td>$180</td>
    399 | 			<td>1</td>
    400 |         </tr>
    401 |     </tbody>
    402 | </table>
    403 | 404 |
    Table - Linearize Custom
    405 | 406 |

    407 | For tables that are a more prominent part of the user experience (eg, cart items table), using a vanilla linearize 408 | pattern is not appropriate, as the screen real estate can be used in a more effective manner. For an example of 409 | this, reference the cart on the shop.angrybirds.com site. 410 |

    411 | 412 |
    413 | 414 | 415 | 416 | 417 | 418 |
    419 |
    420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | --------------------------------------------------------------------------------