├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── assets └── style.less ├── examples ├── simple.html └── simple.js ├── index.js ├── package.json ├── src ├── ajaxGet.js ├── index.jsx └── utils.js └── test └── index.test.js /.gitignore: -------------------------------------------------------------------------------- 1 | /npm-debug.log 2 | /node_modules 3 | /lib 4 | /build 5 | /assets/style.css 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "6" 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Benjy Cui 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-github-button 2 | 3 | [![](https://img.shields.io/travis/benjycui/react-github-button.svg?style=flat-square)](https://travis-ci.org/benjycui/react-github-button) 4 | [![npm package](https://img.shields.io/npm/v/react-github-button.svg?style=flat-square)](https://www.npmjs.org/package/react-github-button) 5 | [![NPM downloads](http://img.shields.io/npm/dm/react-github-button.svg?style=flat-square)](https://npmjs.org/package/react-github-button) 6 | [![Dependency Status](https://david-dm.org/benjycui/react-github-button.svg?style=flat-square)](https://david-dm.org/benjycui/react-github-button) 7 | 8 | Unofficial GitHub buttons in React. 9 | 10 | ## Installation 11 | 12 | ```bash 13 | npm install --save react-github-button 14 | ``` 15 | 16 | ## Usage 17 | 18 | ```jsx 19 | import GitHubButton from 'react-github-button'; 20 | 21 | ReactDOM.render( 22 | 23 | , mountNode 24 | ); 25 | ``` 26 | 27 | ## API 28 | 29 | ### type 30 | 31 | > Enum{ 'stargazers', 'watchers', 'forks' } 32 | 33 | ### size 34 | 35 | > Enum{ 'default', 'large' } 36 | 37 | ### namespace 38 | 39 | > String 40 | 41 | Your GitHub id or organization name. 42 | 43 | ### repo 44 | 45 | > String 46 | 47 | The name of your repository. 48 | 49 | ## License 50 | 51 | MIT 52 | -------------------------------------------------------------------------------- /assets/style.less: -------------------------------------------------------------------------------- 1 | .github-btn { 2 | font: bold 11px/14px 'Helvetica Neue', Helvetica, Arial, sans-serif; 3 | height: 20px; 4 | overflow: hidden; 5 | } 6 | .gh-btn, 7 | .gh-count, 8 | .gh-ico { 9 | float: left; 10 | } 11 | .gh-btn, 12 | .gh-count { 13 | padding: 2px 5px 2px 4px; 14 | color: #333; 15 | text-decoration: none; 16 | white-space: nowrap; 17 | cursor: pointer; 18 | border-radius: 3px; 19 | } 20 | .gh-btn { 21 | background-color: #eee; 22 | background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, #fcfcfc), color-stop(100%, #eee)); 23 | background-image: -webkit-linear-gradient(top, #fcfcfc 0, #eee 100%); 24 | background-image: -moz-linear-gradient(top, #fcfcfc 0, #eee 100%); 25 | background-image: -ms-linear-gradient(top, #fcfcfc 0, #eee 100%); 26 | background-image: -o-linear-gradient(top, #fcfcfc 0, #eee 100%); 27 | background-image: linear-gradient(to bottom, #fcfcfc 0, #eee 100%); 28 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fcfcfc', endColorstr='#eeeeee', GradientType=0); 29 | background-repeat: no-repeat; 30 | border: 1px solid #d5d5d5; 31 | } 32 | .gh-btn:hover, 33 | .gh-btn:focus { 34 | text-decoration: none; 35 | background-color: #ddd; 36 | background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, #eee), color-stop(100%, #ddd)); 37 | background-image: -webkit-linear-gradient(top, #eee 0, #ddd 100%); 38 | background-image: -moz-linear-gradient(top, #eee 0, #ddd 100%); 39 | background-image: -ms-linear-gradient(top, #eee 0, #ddd 100%); 40 | background-image: -o-linear-gradient(top, #eee 0, #ddd 100%); 41 | background-image: linear-gradient(to bottom, #eee 0, #ddd 100%); 42 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#eeeeee', endColorstr='#dddddd', GradientType=0); 43 | border-color: #ccc; 44 | } 45 | .gh-btn:active { 46 | background-image: none; 47 | background-color: #dcdcdc; 48 | border-color: #b5b5b5; 49 | box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15); 50 | } 51 | .gh-ico { 52 | width: 14px; 53 | height: 14px; 54 | margin-right: 4px; 55 | background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2ZXJzaW9uPSIxLjEiIGlkPSJMYXllcl8xIiB4PSIwcHgiIHk9IjBweCIgd2lkdGg9IjQwcHgiIGhlaWdodD0iNDBweCIgdmlld0JveD0iMTIgMTIgNDAgNDAiIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMTIgMTIgNDAgNDAiIHhtbDpzcGFjZT0icHJlc2VydmUiPjxwYXRoIGZpbGw9IiMzMzMzMzMiIGQ9Ik0zMiAxMy40Yy0xMC41IDAtMTkgOC41LTE5IDE5YzAgOC40IDUuNSAxNS41IDEzIDE4YzEgMC4yIDEuMy0wLjQgMS4zLTAuOWMwLTAuNSAwLTEuNyAwLTMuMiBjLTUuMyAxLjEtNi40LTIuNi02LjQtMi42QzIwIDQxLjYgMTguOCA0MSAxOC44IDQxYy0xLjctMS4yIDAuMS0xLjEgMC4xLTEuMWMxLjkgMC4xIDIuOSAyIDIuOSAyYzEuNyAyLjkgNC41IDIuMSA1LjUgMS42IGMwLjItMS4yIDAuNy0yLjEgMS4yLTIuNmMtNC4yLTAuNS04LjctMi4xLTguNy05LjRjMC0yLjEgMC43LTMuNyAyLTUuMWMtMC4yLTAuNS0wLjgtMi40IDAuMi01YzAgMCAxLjYtMC41IDUuMiAyIGMxLjUtMC40IDMuMS0wLjcgNC44LTAuN2MxLjYgMCAzLjMgMC4yIDQuNyAwLjdjMy42LTIuNCA1LjItMiA1LjItMmMxIDIuNiAwLjQgNC42IDAuMiA1YzEuMiAxLjMgMiAzIDIgNS4xYzAgNy4zLTQuNSA4LjktOC43IDkuNCBjMC43IDAuNiAxLjMgMS43IDEuMyAzLjVjMCAyLjYgMCA0LjYgMCA1LjJjMCAwLjUgMC40IDEuMSAxLjMgMC45YzcuNS0yLjYgMTMtOS43IDEzLTE4LjFDNTEgMjEuOSA0Mi41IDEzLjQgMzIgMTMuNHoiLz48L3N2Zz4='); 56 | background-size: 100% 100%; 57 | background-repeat: no-repeat; 58 | } 59 | .gh-count { 60 | position: relative; 61 | display: none; /* hidden to start */ 62 | margin-left: 4px; 63 | background-color: #fafafa; 64 | border: 1px solid #d4d4d4; 65 | } 66 | .gh-count:hover, 67 | .gh-count:focus { 68 | color: #4183C4; 69 | } 70 | .gh-count:before, 71 | .gh-count:after { 72 | content: ''; 73 | position: absolute; 74 | display: inline-block; 75 | width: 0; 76 | height: 0; 77 | border-color: transparent; 78 | border-style: solid; 79 | } 80 | .gh-count:before { 81 | top: 50%; 82 | left: -3px; 83 | margin-top: -4px; 84 | border-width: 4px 4px 4px 0; 85 | border-right-color: #fafafa; 86 | } 87 | .gh-count:after { 88 | top: 50%; 89 | left: -4px; 90 | z-index: -1; 91 | margin-top: -5px; 92 | border-width: 5px 5px 5px 0; 93 | border-right-color: #d4d4d4; 94 | } 95 | .github-btn-large { 96 | height: 30px; 97 | } 98 | .github-btn-large .gh-btn, 99 | .github-btn-large .gh-count { 100 | padding: 3px 10px 3px 8px; 101 | font-size: 16px; 102 | line-height: 22px; 103 | border-radius: 4px; 104 | } 105 | .github-btn-large .gh-ico { 106 | width: 20px; 107 | height: 20px; 108 | } 109 | .github-btn-large .gh-count { 110 | margin-left: 6px; 111 | } 112 | .github-btn-large .gh-count:before { 113 | left: -5px; 114 | margin-top: -6px; 115 | border-width: 6px 6px 6px 0; 116 | } 117 | .github-btn-large .gh-count:after { 118 | left: -6px; 119 | margin-top: -7px; 120 | border-width: 7px 7px 7px 0; 121 | } -------------------------------------------------------------------------------- /examples/simple.html: -------------------------------------------------------------------------------- 1 | placeholder 2 | -------------------------------------------------------------------------------- /examples/simple.js: -------------------------------------------------------------------------------- 1 | require('react-github-button/assets/style.less'); 2 | 3 | const React = require('react'); 4 | const ReactDOM = require('react-dom'); 5 | const GitHubButton = require('react-github-button'); 6 | 7 | ReactDOM.render( 8 |
9 | 12 |

13 | 16 |

17 | 20 |

21 |
, 22 | document.getElementById('__react-content') 23 | ); 24 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = require('./src'); 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-github-button", 3 | "version": "0.1.11", 4 | "description": "Unofficial GitHub buttons in React.", 5 | "main": "./lib/index.js", 6 | "files": [ 7 | "lib", 8 | "assets" 9 | ], 10 | "scripts": { 11 | "start": "rc-tools run server", 12 | "pub": "rc-tools run pub", 13 | "lint": "rc-tools run lint", 14 | "test": "rc-tools run compile && mocha" 15 | }, 16 | "config": { 17 | "port": 8000 18 | }, 19 | "repository": { 20 | "type": "git", 21 | "url": "git+https://github.com/benjycui/react-github-button.git" 22 | }, 23 | "keywords": [ 24 | "react", 25 | "github", 26 | "button" 27 | ], 28 | "author": "Benjy Cui", 29 | "license": "MIT", 30 | "bugs": { 31 | "url": "https://github.com/benjycui/react-github-button/issues" 32 | }, 33 | "homepage": "https://github.com/benjycui/react-github-button#readme", 34 | "devDependencies": { 35 | "mocha": "^2.5.3", 36 | "rc-tools": "^5.5.11", 37 | "react": "^15.2.0", 38 | "react-dom": "^15.2.0", 39 | "react-probe": "~0.0.0" 40 | }, 41 | "dependencies": { 42 | "prop-types": "^15.5.10" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/ajaxGet.js: -------------------------------------------------------------------------------- 1 | export default function ajaxGet(url, callback) { 2 | if (typeof XDomainRequest !== 'undefined') { 3 | callback(null); 4 | return null; 5 | } 6 | 7 | const xhr = new XMLHttpRequest(); 8 | xhr.onreadystatechange = () => { 9 | if (xhr.readyState === XMLHttpRequest.DONE && 10 | xhr.status === 200) { 11 | callback(JSON.parse(xhr.responseText)); 12 | } 13 | }; 14 | xhr.open('GET', url, true); 15 | xhr.send(); 16 | return xhr; 17 | } 18 | -------------------------------------------------------------------------------- /src/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import ajaxGet from './ajaxGet'; 4 | import * as utils from './utils'; 5 | 6 | const typeToLabel = { 7 | stargazers: 'Star', 8 | watchers: 'Watch', 9 | forks: 'Fork', 10 | }; 11 | 12 | const typeToPath = { 13 | forks: 'network', 14 | }; 15 | 16 | export default class GitHubButton extends React.Component { 17 | static displayName = 'GitHubButton'; 18 | static propTypes = { 19 | className: PropTypes.string, 20 | type: PropTypes.oneOf([ 21 | 'stargazers', 22 | 'watchers', 23 | 'forks', 24 | ]).isRequired, 25 | namespace: PropTypes.string.isRequired, 26 | repo: PropTypes.string.isRequired, 27 | size: PropTypes.oneOf([ 28 | 'large', 29 | ]), 30 | }; 31 | state = { 32 | count: null, 33 | }; 34 | componentDidMount() { 35 | this.xhr = ajaxGet(this.getRequestUrl(), (response) => { 36 | this.setCount(response); 37 | }); 38 | } 39 | componentWillUnmount() { 40 | if (this.xhr) { 41 | this.xhr.abort(); 42 | } 43 | } 44 | setCount(data) { 45 | if (!data) return; 46 | const count = data[`${this.props.type}_count`]; 47 | this.setState({ count }); 48 | } 49 | getRequestUrl() { 50 | const { namespace, repo } = this.props; 51 | return `//api.github.com/repos/${namespace}/${repo}`; 52 | } 53 | getRepoUrl() { 54 | const { namespace, repo } = this.props; 55 | return `//github.com/${namespace}/${repo}/`; 56 | } 57 | getCountUrl() { 58 | const { namespace, repo, type } = this.props; 59 | return `//github.com/${namespace}/${repo}/${typeToPath[type] || type}/`; 60 | } 61 | getCountStyle() { 62 | const count = this.state.count; 63 | if (count !== null) { 64 | return { 65 | display: 'block', 66 | }; 67 | } 68 | return null; 69 | } 70 | render() { 71 | const { className, type, size, ...rest } = this.props; 72 | delete rest.namespace; 73 | delete rest.repo; 74 | 75 | const count = this.state.count; 76 | 77 | const buttonClassName = utils.classNames({ 78 | 'github-btn': true, 79 | 'github-btn-large': size === 'large', 80 | [className]: className, 81 | }); 82 | 83 | return ( 84 | 85 | 86 | 87 | { typeToLabel[type] } 88 | 89 | 93 | { count } 94 | 95 | 96 | ); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | export function classNames(classSet) { 2 | return Object.keys(classSet) 3 | .filter((key) => classSet[key]).join(' '); 4 | } 5 | -------------------------------------------------------------------------------- /test/index.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | const probe = require('react-probe'); 5 | const GitHubButtonProto = require('../lib').prototype; 6 | 7 | describe('GitHubButton', () => { 8 | it('should set `state.count` correctly', () => { 9 | const self = probe.instance({ 10 | props: { 11 | type: 'stargazers', 12 | }, 13 | state: { 14 | count: null, 15 | } 16 | }); 17 | 18 | GitHubButtonProto.setCount.call(self, { 19 | 'stargazers_count': 10, 20 | }); 21 | assert.strictEqual(self.state.count, 10); 22 | }); 23 | }); 24 | --------------------------------------------------------------------------------