├── .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://travis-ci.org/benjycui/react-github-button)
4 | [](https://www.npmjs.org/package/react-github-button)
5 | [](https://npmjs.org/package/react-github-button)
6 | [](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 |
--------------------------------------------------------------------------------