├── .DS_Store ├── test ├── mocha.opts └── example │ ├── index.styl │ ├── index.jade │ └── index.js ├── README.md ├── pagination ├── test │ ├── test.jade │ ├── total_count.coffee │ └── template.coffee ├── total_count.coffee ├── total_count_with_access_token.coffee ├── index.styl └── paginator.jade ├── stylus_lib ├── colors.json └── typography.styl ├── .gitignore ├── spinners └── index.styl ├── LICENSE └── package.json /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artsy/artsy-ezel-components/master/.DS_Store -------------------------------------------------------------------------------- /test/mocha.opts: -------------------------------------------------------------------------------- 1 | --require should 2 | --ui bdd 3 | --timeout 10000 4 | --compilers coffee:coffee-script/register -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | artsy-ezel-components 2 | ===================== 3 | 4 | A library of UI components shared across Artsy's Ezel-based apps. 5 | -------------------------------------------------------------------------------- /test/example/index.styl: -------------------------------------------------------------------------------- 1 | @import '../../stylus_lib/typography' 2 | 3 | h1 4 | garamond(l-headline) 5 | 6 | h2 7 | avant-garde(body) -------------------------------------------------------------------------------- /test/example/index.jade: -------------------------------------------------------------------------------- 1 | doctype html 2 | html(lang="en") 3 | head 4 | style 5 | include:stylus index.styl 6 | body 7 | h1 Hello World 8 | h2 Foo bar -------------------------------------------------------------------------------- /pagination/test/test.jade: -------------------------------------------------------------------------------- 1 | include ../paginator 2 | 3 | #cases 4 | #case-1: +paginate(5, 10) 5 | #case-2: +paginate(1, 1) 6 | #case-3: +paginate(1, 10, '?foo=bar&') 7 | #case-4: +paginate(10, 10) 8 | #case-5: +paginateHead(1, 2) 9 | #case-6: +paginateHead(2, 2) 10 | #case-7: +paginateHead(2, 3) -------------------------------------------------------------------------------- /test/example/index.js: -------------------------------------------------------------------------------- 1 | express = require('express'); 2 | app = module.exports = express(); 3 | 4 | app.set('views', __dirname); 5 | app.set('view engine', 'jade'); 6 | 7 | app.get('/', function(req, res, next) { 8 | res.render('index'); 9 | }); 10 | 11 | app.listen(7000, function() { 12 | console.log('listening'); 13 | }); 14 | -------------------------------------------------------------------------------- /stylus_lib/colors.json: -------------------------------------------------------------------------------- 1 | { 2 | "gray-lightest-color": "#F8F8F8", 3 | "gray-lighter-color": "#E5E5E5", 4 | "gray-color": "#CCCCCC", 5 | "gray-dark-color": "#999999", 6 | "gray-darker-color": "#666666", 7 | "gray-darkest-color": "#333333", 8 | "purple-light-color": "#E2D2FF", 9 | "purple-color": "#6E1FFF", 10 | "red-color": "#F7625A", 11 | "yellow-light-color": "#FDEFD1", 12 | "yellow-color": "#FCE1A8", 13 | "yellow-bold-color": "#F1AF1B", 14 | "green-color": "#0EDA83" 15 | } -------------------------------------------------------------------------------- /pagination/total_count.coffee: -------------------------------------------------------------------------------- 1 | Q = require 'bluebird-q' 2 | request = require 'superagent' 3 | 4 | # Makes a HEAD request, 5 | # ensures the total_count param is set, 6 | # overrides the page and size params, 7 | # and resolves a promise with the total count 8 | module.exports = (token, url) -> 9 | dfd = Q.defer() 10 | request. 11 | head(url). 12 | set('X-XAPP-TOKEN': token). 13 | query( 14 | size: 1 15 | page: 1 16 | total_count: 1 17 | ). 18 | end (err, res) -> 19 | dfd.resolve res.header['x-total-count'] 20 | dfd.promise 21 | -------------------------------------------------------------------------------- /pagination/test/total_count.coffee: -------------------------------------------------------------------------------- 1 | express = require 'express' 2 | totalCount = require '../total_count' 3 | 4 | describe 'total count', -> 5 | 6 | beforeEach (done) -> 7 | app = express() 8 | app.get '*', (req, res, next) -> 9 | res.set('x-total-count': 500).send('hi') 10 | @server = app.listen 6000, -> done() 11 | 12 | afterEach -> 13 | @server.close() 14 | 15 | it 'makes a HEAD request for the total count from the API', (done) -> 16 | totalCount('foo', 'http://localhost:6000').then (count) -> 17 | count.should.equal '500' 18 | done() -------------------------------------------------------------------------------- /pagination/total_count_with_access_token.coffee: -------------------------------------------------------------------------------- 1 | Q = require 'bluebird-q' 2 | request = require 'superagent' 3 | 4 | # Makes a HEAD request, 5 | # ensures the total_count param is set, 6 | # overrides the page and size params, 7 | # and resolves a promise with the total count 8 | module.exports = (token, url) -> 9 | dfd = Q.defer() 10 | request. 11 | head(url). 12 | set('X-ACCESS-TOKEN': token). 13 | query( 14 | size: 1 15 | page: 1 16 | total_count: 1 17 | ). 18 | end (err, res) -> 19 | dfd.resolve res.header['x-total-count'] 20 | dfd.promise 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # Compiled binary addons (http://nodejs.org/api/addons.html) 20 | build/Release 21 | 22 | # Dependency directory 23 | # Commenting this out is preferred by some people, see 24 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git- 25 | node_modules 26 | 27 | # Users Environment Variables 28 | .lock-wscript 29 | -------------------------------------------------------------------------------- /pagination/index.styl: -------------------------------------------------------------------------------- 1 | json('../stylus_lib/colors.json') 2 | 3 | .bordered-pagination 4 | margin 20px 0 5 | > ul 6 | display inline-block 7 | margin 0 10px 8 | list-style none 9 | border 2px solid gray-color 10 | &:first-child 11 | margin-left 0 12 | &:last-child 13 | margin-right 0 14 | > li 15 | display inline-block 16 | border-left 1px solid gray-lighter-color 17 | &:first-child 18 | border none 19 | > a 20 | display block 21 | padding 8px 15px 6px 22 | text-decoration none 23 | &.is-active 24 | background-color gray-lightest-color 25 | cursor default 26 | > span 27 | color gray-dark-color -------------------------------------------------------------------------------- /spinners/index.styl: -------------------------------------------------------------------------------- 1 | // 2 | // Styles for loading spinners of all varieties. 3 | // 4 | 5 | @keyframes spin 6 | 100% 7 | transform rotate(360deg) 8 | 9 | spinner(width=25px, height=6px, color=black) 10 | background color 11 | width width 12 | height height 13 | position absolute 14 | top "calc(50% - %s / 2)" % height 15 | left "calc(50% - %s / 2)" % width 16 | animation spin 1s infinite linear 17 | 18 | .loading-spinner 19 | spinner(25px, 6px) 20 | 21 | .loading-spinner-small 22 | spinner(20px, 4px) 23 | 24 | .loading-spinner-white 25 | @extends .loading-spinner 26 | background white 27 | 28 | .loading-spinner-small-white 29 | @extends .loading-spinner-small 30 | background white 31 | 32 | @keyframes fade 33 | 0% 34 | opacity 0 35 | 50% 36 | opacity 1 37 | 100% 38 | opacity 0 39 | 40 | .loading-ellipsis > span 41 | letter-spacing 2px 42 | for i in 1 2 3 43 | &:nth-child({i}) 44 | animation fade 1s infinite 45 | animation-delay i * 0.2s 46 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2014-2019 Art.sy Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "artsy-ezel-components", 3 | "version": "0.0.6", 4 | "description": "A library of UI components shared across Artsy's Ezel-based apps.", 5 | "keywords": [ 6 | "ezel", 7 | "components", 8 | "stylus", 9 | "jade" 10 | ], 11 | "scripts": { 12 | "test": "mocha **/test/*.coffee" 13 | }, 14 | "author": { 15 | "name": "Craig Spaeth", 16 | "email": "craigspaeth@gmail.com", 17 | "url": "http://craigspaeth.com" 18 | }, 19 | "contributors": [ 20 | { 21 | "name": "Brennan Moore", 22 | "email": "brennanmoore@gmail.com", 23 | "url": "http://brennanmoore.com" 24 | }, 25 | { 26 | "name": "Damon Zucconi", 27 | "url": "http://damonzucconi.com" 28 | } 29 | ], 30 | "devDependencies": { 31 | "mocha": "*", 32 | "should": "*", 33 | "jade": "*", 34 | "benv": "*", 35 | "cheerio": "*", 36 | "coffee-script": "*", 37 | "underscore": "*", 38 | "jquery": "*", 39 | "express": "*", 40 | "stylus": "*", 41 | "q": "*", 42 | "superagent": "*" 43 | }, 44 | "repository": { 45 | "type": "git", 46 | "url": "https://github.com/craigspaeth/artsy-ezel-components.git" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /stylus_lib/typography.styl: -------------------------------------------------------------------------------- 1 | default-avant-garde-font-size = 13px 2 | default-garamond-font-size = 17px 3 | 4 | foo() 5 | color red 6 | 7 | avant-garde(fsize = 'body') 8 | font-family 'ITC Avant Garde Gothic W04', 'AvantGardeGothicITCW01D 731075', 'AvantGardeGothicITCW01Dm', 'Helvetica', 'sans-serif' 9 | font-smoothing antialiased 10 | text-transform uppercase 11 | letter-spacing 1px 12 | if fsize == 's-headline' 13 | font-size 11px 14 | line-height 15px 15 | else if fsize == 'body' 16 | font-size 13px 17 | line-height 17px 18 | else if fsize == 'm-headline' 19 | font-size 15px 20 | line-height 20px 21 | else if fsize == 'l-headline' 22 | font-size 17px 23 | line-height 22px 24 | else if fsize 25 | font-size fsize 26 | 27 | garamond(fsize = 's-body') 28 | font-family 'Adobe Garamond W08', 'adobe-garamond-pro', 'AGaramondPro-Regular', 'Times New Roman', 'Times', 'serif' 29 | font-smoothing antialiased 30 | if fsize == 's-caption' 31 | font-size 11px 32 | line-height 16px 33 | else if fsize == 'l-caption' 34 | font-size 15px 35 | line-height 19px 36 | else if fsize == 's-body' 37 | font-size 17px 38 | line-height 26px 39 | else if fsize == 'l-body' 40 | font-size 20px 41 | line-height 27px 42 | else if fsize == 'headline' 43 | font-size 30px 44 | line-height 37px 45 | else if fsize == 'l-headline' 46 | font-size 37px 47 | line-height 44px 48 | else if fsize == 'xl-headline' 49 | font-size 50px 50 | line-height 50px 51 | else if fsize == 'xxl-headline' 52 | font-size 72px 53 | line-height 72px 54 | else if fsize 55 | font-size fsize 56 | 57 | faux-underline(color=#333333, x=1px, y=5px) 58 | text-decoration none 59 | background-image linear-gradient(top, transparent 0, color 1px, transparent 0) 60 | background-size x y 61 | background-repeat repeat-x 62 | background-position bottom 63 | -------------------------------------------------------------------------------- /pagination/paginator.jade: -------------------------------------------------------------------------------- 1 | mixin paginateHead(current, total, urlBase) 2 | - var urlBase = urlBase || '?' 3 | if total && total !== 1 4 | if (current !== 1) 5 | link( rel='prev', href="#{urlBase}page=#{current - 1}" ) 6 | if (current !== total) 7 | link( rel='next', href="#{urlBase}page=#{current + 1}" ) 8 | 9 | mixin paginate(current, total, urlBase, pagesInterval) 10 | - var urlBase = urlBase || '?' 11 | - var pagesInterval = pagesInterval || 3 12 | 13 | if total && total !== 1 14 | nav.bordered-pagination 15 | //- Previous button 16 | - if (current !== 1) 17 | ul 18 | li 19 | a.paginator-previous( data-value=(current - 1), href="#{urlBase}page=#{current - 1}" ) 20 | span <  21 | | Previous 22 | 23 | //- First page 24 | - if (((current - pagesInterval) - 1) > 0) 25 | ul: li: a.paginator-first( data-value=1, href='#{urlBase}page=1' ) 1 26 | 27 | //- Numbered pages 28 | ul 29 | //- Left surrounding pages 30 | - for (var i = pagesInterval; i > 0; i--) { 31 | - if (current > i) 32 | li: a.paginator-page( data-value=(current - i), href="#{urlBase}page=#{current - i}" ) #{current - i} 33 | - } 34 | 35 | //- Current page 36 | li: a.paginator-current.is-active( data-value=current )= current 37 | 38 | //- Right surrounding pages 39 | - for (var i = 1; i <= pagesInterval; i++) { 40 | - if (((total - current) + 1) > i) 41 | li: a( data-value=(current + i), href="#{urlBase}page=#{current + i}" ) #{current + i} 42 | - } 43 | 44 | //- End page 45 | - if ((current !== total) && (((total - current) + 1) > i)) 46 | ul: li: a.paginator-last( data-value=total, href="#{urlBase}page=#{total}" )= total 47 | 48 | //- Next button 49 | - if (current !== total) 50 | ul 51 | li 52 | a.paginator-next( data-value=(current + 1), href="#{urlBase}page=#{current + 1}" ) 53 | | Next 54 | span  > 55 | -------------------------------------------------------------------------------- /pagination/test/template.coffee: -------------------------------------------------------------------------------- 1 | _ = require 'underscore' 2 | jade = require 'jade' 3 | path = require 'path' 4 | fs = require 'fs' 5 | cheerio = require 'cheerio' 6 | 7 | render = (templateName) -> 8 | filename = path.resolve __dirname, "#{templateName}.jade" 9 | jade.compile( 10 | fs.readFileSync(filename), 11 | { filename: filename } 12 | ) 13 | 14 | describe 'Paginator template', -> 15 | before -> 16 | $ = cheerio.load(render('test')()) 17 | @$cases = $('#cases') 18 | 19 | after -> 20 | @$cases.remove() 21 | 22 | describe '#paginate', -> 23 | describe '10 pages, @ page 5', -> 24 | beforeEach -> @$template = @$cases.find('#case-1') 25 | it 'sets the previous button to be page 4', -> 26 | @$template.find('.paginator-previous').data('value').should.equal 4 27 | it 'sets the next button to be page 6', -> 28 | @$template.find('.paginator-next').data('value').should.equal 6 29 | it 'sets the first button to be page 1', -> 30 | @$template.find('.paginator-first').data('value').should.equal 1 31 | it 'sets the last button to be page 10', -> 32 | @$template.find('.paginator-last').data('value').should.equal 10 33 | it 'sets the current button to be page 5', -> 34 | @$template.find('.paginator-current').data('value').should.equal 5 35 | it 'includes the appropriate amount of surrounding links', -> 36 | @$template.find('.paginator-current').parent().nextAll('li').length.should.equal 3 37 | @$template.find('.paginator-current').parent().prevAll('li').length.should.equal 3 38 | 39 | describe '1 page, @ page 1', -> 40 | beforeEach -> @$template = @$cases.find('#case-2') 41 | it 'should not render anything', -> 42 | @$template.html().should.equal '' 43 | 44 | describe '10 pages, @ page 1, has a base path', -> 45 | beforeEach -> @$template = @$cases.find('#case-3') 46 | it 'ensures the previous button is not displayed', -> 47 | @$template.find('.paginator-previous').length.should.not.be.ok 48 | it 'sets the next button to be page 2', -> 49 | @$template.find('.paginator-next').data('value').should.equal 2 50 | it 'ensures the first page button is not displayed', -> 51 | @$template.find('.paginator-first').length.should.not.be.ok 52 | it 'sets the last button to be page 10', -> 53 | @$template.find('.paginator-last').data('value').should.equal 10 54 | it 'sets the current button to be page 1', -> 55 | @$template.find('.paginator-current').data('value').should.equal 1 56 | it 'sets the base path of the page links', -> 57 | @$template.find('.paginator-next').attr('href').should.equal '?foo=bar&page=2' 58 | it 'includes the appropriate amount of surrounding links', -> 59 | @$template.find('.paginator-current').parent().nextAll('li').length.should.equal 3 60 | @$template.find('.paginator-current').parent().prevAll('li').length.should.equal 0 61 | 62 | 63 | describe '#paginateHead', -> 64 | describe '2 pages, @ page 1', -> 65 | beforeEach -> @$template = @$cases.find('#case-5') 66 | it 'should not have a prev link', -> 67 | @$template.find('link[rel="prev"]').length.should.not.be.ok 68 | it 'should have a next link', -> 69 | next = @$template.find('link[rel="next"]') 70 | next.length.should.equal 1 71 | next.attr('href').should.equal '?page=2' 72 | 73 | describe '2 pages, @ page 2', -> 74 | beforeEach -> @$template = @$cases.find('#case-6') 75 | it 'should not have a next link', -> 76 | @$template.find('link[rel="next"]').length.should.not.be.ok 77 | it 'should have a prev link', -> 78 | prev = @$template.find('link[rel="prev"]') 79 | prev.length.should.equal 1 80 | prev.attr('href').should.equal '?page=1' 81 | 82 | describe '3 pages, @ page 2', -> 83 | beforeEach -> @$template = @$cases.find('#case-7') 84 | it 'should have a prev link', -> 85 | prev = @$template.find('link[rel="prev"]') 86 | prev.length.should.equal 1 87 | prev.attr('href').should.equal '?page=1' 88 | it 'should have have a next link', -> 89 | next = @$template.find('link[rel="next"]') 90 | next.length.should.equal 1 91 | next.attr('href').should.equal '?page=3' 92 | --------------------------------------------------------------------------------