├── .gitignore ├── LICENSE ├── README.md ├── example ├── .npmignore ├── data.ts ├── index.html ├── index.tsx ├── package.json ├── tsconfig.json └── yarn.lock ├── package.json ├── src ├── ignore.d.ts ├── index.tsx └── utils │ ├── ToolTip.tsx │ └── darkenColor.ts ├── test └── blah.test.tsx ├── tsconfig.json └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | .DS_Store 3 | node_modules 4 | .rts2_cache_cjs 5 | .rts2_cache_es 6 | .rts2_cache_umd 7 | dist 8 | .cache 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Harshit Pant 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 | # Recontrib 2 | 3 | React Component that implements GiHub's commit graph UI. https://pantharshit00.github.io/recontrib 4 | 5 | ![image](https://user-images.githubusercontent.com/22195362/57941632-17990f80-78ed-11e9-86b3-939f7ac1209b.png) 6 | 7 | ## Usage 8 | 9 | 1. Add the Reach UI tooltip CSS 10 | 11 | ```js 12 | import '@reach/tooltip/style.css'; 13 | ``` 14 | 15 | 2. Use the component and pass the data 16 | 17 | ```js 18 | const App = () => { 19 | return ( 20 |
21 | 22 |
23 | ); 24 | }; 25 | ``` 26 | 27 | Data is structured as following. You can directly pass the data from the GitHub API(https://api.github.com/repos/:owner/:repo/stats/commit_activity): 28 | 29 | ```ts 30 | interface WeekData { 31 | days: number[]; // commits in individual days in the week 32 | total: number; // total number commits in the week 33 | week: number; // Timestamp in seconds of the seconds of the starting of the week 34 | } 35 | ``` 36 | 37 | ## Props 38 | 39 | ```ts 40 | interface Props { 41 | data: Array; // data 42 | gridSize?: number; // size of the tile 43 | fontSize?: string; // fontSize of months and weekdays 44 | } 45 | ``` 46 | 47 | --- 48 | 49 | MIT️ ©️ [Harshit Pant](https://twitter.com/pantharshit00) 2019 50 | -------------------------------------------------------------------------------- /example/.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .cache 3 | dist -------------------------------------------------------------------------------- /example/data.ts: -------------------------------------------------------------------------------- 1 | export const data = [ 2 | { 3 | days: [1, 0, 57, 28, 30, 18, 1], 4 | total: 135, 5 | week: 1526774400, 6 | }, 7 | { 8 | days: [0, 18, 11, 11, 27, 50, 3], 9 | total: 120, 10 | week: 1527379200, 11 | }, 12 | { 13 | days: [1, 20, 21, 14, 13, 16, 0], 14 | total: 85, 15 | week: 1527984000, 16 | }, 17 | { 18 | days: [2, 21, 29, 12, 7, 0, 2], 19 | total: 73, 20 | week: 1528588800, 21 | }, 22 | { 23 | days: [0, 18, 24, 21, 17, 10, 1], 24 | total: 91, 25 | week: 1529193600, 26 | }, 27 | { 28 | days: [0, 20, 32, 21, 32, 41, 1], 29 | total: 147, 30 | week: 1529798400, 31 | }, 32 | { 33 | days: [0, 19, 12, 15, 11, 4, 0], 34 | total: 61, 35 | week: 1530403200, 36 | }, 37 | { 38 | days: [1, 6, 0, 1, 5, 4, 0], 39 | total: 17, 40 | week: 1531008000, 41 | }, 42 | { 43 | days: [1, 6, 26, 14, 5, 7, 1], 44 | total: 60, 45 | week: 1531612800, 46 | }, 47 | { 48 | days: [1, 4, 7, 11, 16, 3, 2], 49 | total: 44, 50 | week: 1532217600, 51 | }, 52 | { 53 | days: [21, 25, 13, 26, 10, 5, 0], 54 | total: 100, 55 | week: 1532822400, 56 | }, 57 | { 58 | days: [0, 6, 5, 25, 15, 3, 1], 59 | total: 55, 60 | week: 1533427200, 61 | }, 62 | { 63 | days: [2, 9, 15, 9, 4, 16, 0], 64 | total: 55, 65 | week: 1534032000, 66 | }, 67 | { 68 | days: [4, 12, 4, 1, 6, 17, 2], 69 | total: 46, 70 | week: 1534636800, 71 | }, 72 | { 73 | days: [5, 17, 22, 15, 32, 31, 12], 74 | total: 134, 75 | week: 1535241600, 76 | }, 77 | { 78 | days: [0, 37, 51, 48, 1, 0, 0], 79 | total: 137, 80 | week: 1535846400, 81 | }, 82 | { 83 | days: [9, 113, 74, 30, 21, 5, 0], 84 | total: 252, 85 | week: 1536451200, 86 | }, 87 | { 88 | days: [0, 35, 14, 8, 26, 9, 2], 89 | total: 94, 90 | week: 1537056000, 91 | }, 92 | { 93 | days: [11, 20, 26, 32, 29, 17, 1], 94 | total: 136, 95 | week: 1537660800, 96 | }, 97 | { 98 | days: [5, 12, 17, 16, 38, 23, 7], 99 | total: 118, 100 | week: 1538265600, 101 | }, 102 | { 103 | days: [1, 20, 15, 14, 10, 1, 2], 104 | total: 63, 105 | week: 1538870400, 106 | }, 107 | { 108 | days: [2, 13, 17, 6, 12, 8, 1], 109 | total: 59, 110 | week: 1539475200, 111 | }, 112 | { 113 | days: [1, 12, 22, 19, 7, 4, 2], 114 | total: 67, 115 | week: 1540080000, 116 | }, 117 | { 118 | days: [0, 11, 15, 22, 10, 10, 0], 119 | total: 68, 120 | week: 1540684800, 121 | }, 122 | { 123 | days: [0, 13, 33, 18, 66, 35, 18], 124 | total: 183, 125 | week: 1541289600, 126 | }, 127 | { 128 | days: [1, 7, 33, 37, 25, 13, 0], 129 | total: 116, 130 | week: 1541894400, 131 | }, 132 | { 133 | days: [6, 31, 25, 27, 27, 25, 1], 134 | total: 142, 135 | week: 1542499200, 136 | }, 137 | { 138 | days: [1, 7, 23, 9, 9, 11, 3], 139 | total: 63, 140 | week: 1543104000, 141 | }, 142 | { 143 | days: [0, 13, 40, 29, 38, 13, 1], 144 | total: 134, 145 | week: 1543708800, 146 | }, 147 | { 148 | days: [4, 29, 20, 8, 25, 1, 3], 149 | total: 90, 150 | week: 1544313600, 151 | }, 152 | { 153 | days: [1, 20, 17, 31, 10, 15, 2], 154 | total: 96, 155 | week: 1544918400, 156 | }, 157 | { 158 | days: [0, 0, 1, 1, 3, 5, 1], 159 | total: 11, 160 | week: 1545523200, 161 | }, 162 | { 163 | days: [0, 0, 0, 3, 17, 12, 0], 164 | total: 32, 165 | week: 1546128000, 166 | }, 167 | { 168 | days: [0, 11, 25, 27, 20, 21, 0], 169 | total: 104, 170 | week: 1546732800, 171 | }, 172 | { 173 | days: [2, 17, 22, 17, 17, 19, 6], 174 | total: 100, 175 | week: 1547337600, 176 | }, 177 | { 178 | days: [0, 32, 34, 31, 22, 13, 3], 179 | total: 135, 180 | week: 1547942400, 181 | }, 182 | { 183 | days: [3, 7, 14, 13, 24, 27, 1], 184 | total: 89, 185 | week: 1548547200, 186 | }, 187 | { 188 | days: [1, 25, 22, 32, 20, 16, 0], 189 | total: 116, 190 | week: 1549152000, 191 | }, 192 | { 193 | days: [0, 28, 12, 22, 19, 13, 0], 194 | total: 94, 195 | week: 1549756800, 196 | }, 197 | { 198 | days: [0, 3, 13, 9, 8, 4, 4], 199 | total: 41, 200 | week: 1550361600, 201 | }, 202 | { 203 | days: [2, 12, 11, 31, 29, 28, 14], 204 | total: 127, 205 | week: 1550966400, 206 | }, 207 | { 208 | days: [0, 25, 36, 31, 19, 0, 4], 209 | total: 115, 210 | week: 1551571200, 211 | }, 212 | { 213 | days: [2, 31, 22, 42, 21, 22, 0], 214 | total: 140, 215 | week: 1552176000, 216 | }, 217 | { 218 | days: [0, 16, 20, 18, 14, 28, 0], 219 | total: 96, 220 | week: 1552780800, 221 | }, 222 | { 223 | days: [0, 15, 17, 31, 4, 24, 2], 224 | total: 93, 225 | week: 1553385600, 226 | }, 227 | { 228 | days: [0, 16, 12, 46, 31, 15, 0], 229 | total: 120, 230 | week: 1553990400, 231 | }, 232 | { 233 | days: [1, 31, 17, 25, 10, 20, 0], 234 | total: 104, 235 | week: 1554595200, 236 | }, 237 | { 238 | days: [0, 26, 29, 35, 14, 0, 0], 239 | total: 104, 240 | week: 1555200000, 241 | }, 242 | { 243 | days: [0, 0, 12, 3, 18, 8, 0], 244 | total: 41, 245 | week: 1555804800, 246 | }, 247 | { 248 | days: [0, 17, 12, 3, 19, 4, 0], 249 | total: 55, 250 | week: 1556409600, 251 | }, 252 | { 253 | days: [0, 6, 2, 2, 0, 8, 0], 254 | total: 18, 255 | week: 1557014400, 256 | }, 257 | { 258 | days: [0, 0, 4, 3, 0, 1, 0], 259 | total: 8, 260 | week: 1557619200, 261 | }, 262 | ]; 263 | -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Recontrib - React Component that implements GitHub commit UI 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /example/index.tsx: -------------------------------------------------------------------------------- 1 | import 'react-app-polyfill/ie11'; 2 | import * as React from 'react'; 3 | import * as ReactDOM from 'react-dom'; 4 | import '@reach/tooltip/styles.css'; 5 | import { Recontrib } from '../.'; 6 | import { Container, Table, Input, Form, FormGroup } from 'reactstrap'; 7 | import 'prismjs'; 8 | import 'prismjs/components/prism-bash'; 9 | import 'prismjs/components/prism-javascript'; 10 | import 'prismjs/components/prism-typescript'; 11 | import 'prismjs/plugins/line-numbers/prism-line-numbers'; 12 | import 'prismjs/themes/prism-tomorrow.css'; 13 | import 'prismjs/plugins/line-numbers/prism-line-numbers.css'; 14 | 15 | import 'bootstrap/dist/css/bootstrap.min.css'; 16 | 17 | const INSTALL_CODE = `npm install recontrib @reach/tooltip 18 | # or yarn add recontrib @reach/tooltip 19 | `; 20 | 21 | const CSS_CODE = `import '@reach/tooltip/style.css`; 22 | 23 | const APP_CODE = `import React, { useEffect, useState } from 'react'; 24 | import { Recontrib } from 'recontrib'; 25 | 26 | const App = () => { 27 | let [data, setData] = useState([]); 28 | useEffect(() => { 29 | fetch('https://api.github.com/repos/zeit/next.js/stats/commit_activity') 30 | .then(res => res.json()) 31 | .then(res => { setData(res) }) 32 | },[]); 33 | 34 | return ( 35 |
36 | 41 |
42 | ) 43 | } 44 | `; 45 | 46 | const WEEK_DATA = `interface WeekData { 47 | days: number[]; // commits in individual days in the week 48 | total: number; // total number commits in the week 49 | week: number; // Timestamp in seconds of the seconds of the starting of the week 50 | }`; 51 | 52 | const PROP = `interface Props { 53 | data: Array; 54 | gridSize?: number; 55 | fontSize?: string; 56 | }`; 57 | 58 | const App = () => { 59 | let [repoData, setData] = React.useState({ error: null, data: [] }); 60 | const [{ repo, owner }, setForm] = React.useState({ 61 | repo: 'prisma', 62 | owner: 'prisma', 63 | }); 64 | let [url, setUrl] = React.useState( 65 | `https://api.github.com/repos/${owner}/${repo}/stats/commit_activity` 66 | ); 67 | React.useEffect(() => { 68 | fetch(url) 69 | .then(res => { 70 | if (res.status === 404) { 71 | throw new Error('Not found'); 72 | } 73 | return res.json(); 74 | }) 75 | .then(res => { 76 | setData({ error: null, data: res }); 77 | }) 78 | .catch(err => { 79 | setData({ error: err.message, data: [] }); 80 | }); 81 | }, [url]); 82 | return ( 83 | 84 | 85 |

Recontrib

86 |

React Component that implements GitHub Commit Graph

87 |
88 | 89 |
{ 91 | e.preventDefault(); 92 | setUrl( 93 | `https://api.github.com/repos/${owner}/${repo}/stats/commit_activity` 94 | ); 95 | }} 96 | inline 97 | > 98 | { 100 | setForm({ repo, owner: e.currentTarget.value }); 101 | }} 102 | value={owner} 103 | className="mr-2" 104 | type="text" 105 | id="repo" 106 | placeholder="owner" 107 | />{' '} 108 | / 109 | { 113 | setForm({ owner, repo: e.currentTarget.value }); 114 | }} 115 | className="ml-2" 116 | type="text" 117 | placeholder="repo" 118 | /> 119 | 120 |
121 |
122 | {repoData.data && repoData.data.length ? ( 123 | 124 | ) : repoData.error ? ( 125 |

{repoData.error}

126 | ) : ( 127 | 'Loading....' 128 | )} 129 |
130 |
131 |
132 | 133 |

Usuage

134 |
1. Install the dependencies
135 |
136 |           {INSTALL_CODE}
137 |         
138 |
2. Add the Reach UI tooltip CSS
139 |

140 | This component use Reach UI's Tooltip component. Install it as a peer 141 | dependency 142 |

143 |
144 |           {CSS_CODE}
145 |         
146 |
2. Use the component in your code
147 |
148 |           {APP_CODE}
149 |         
150 |
151 |
152 | 153 |

API

154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 168 | 169 | 170 | 171 | 172 | 175 | 176 | 177 | 178 | 179 | 182 | 183 | 184 | 185 |
PropTypeoptional
data 166 | WeekData 167 | no
gridSize 173 | number 174 | yes - default 10
fontSize 180 | string 181 | yes - default 9px
186 |
187 | 188 |
Interfaces
189 |
WeekData
190 |
191 |           {WEEK_DATA}
192 |         
193 |
Prop
194 |
195 |           {PROP}
196 |         
197 |
198 |

199 | MIT ©️ Harshit Pant{' '} 200 | 2019 201 |

202 |
203 |
204 | ); 205 | }; 206 | 207 | ReactDOM.render(, document.getElementById('root')); 208 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "license": "MIT", 6 | "homepage": "https://pantharshit00.github.io/recontrib", 7 | "scripts": { 8 | "start": "parcel index.html", 9 | "build": "parcel build index.html --public-url /recontrib/" 10 | }, 11 | "dependencies": { 12 | "@reach/tooltip": "^0.2.0", 13 | "bootstrap": "^4.3.1", 14 | "prismjs": "^1.16.0", 15 | "react": "^16.8.0", 16 | "react-app-polyfill": "^1.0.0", 17 | "react-dom": "^16.8.0", 18 | "reactstrap": "^8.0.0" 19 | }, 20 | "alias": { 21 | "react": "../node_modules/react", 22 | "react-dom": "../node_modules/react-dom" 23 | }, 24 | "devDependencies": { 25 | "@babel/core": "^7.4.4", 26 | "@types/prismjs": "^1.16.0", 27 | "@types/react": "^16.8.15", 28 | "@types/react-dom": "^16.8.4", 29 | "@types/reactstrap": "^8.0.1", 30 | "parcel": "^1.12.3", 31 | "typescript": "^3.4.5" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /example/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowSyntheticDefaultImports": false, 4 | "target": "es5", 5 | "module": "commonjs", 6 | "jsx": "react", 7 | "moduleResolution": "node", 8 | "noImplicitAny": false, 9 | "noUnusedLocals": false, 10 | "noUnusedParameters": false, 11 | "removeComments": true, 12 | "strictNullChecks": true, 13 | "preserveConstEnums": true, 14 | "sourceMap": true, 15 | "lib": ["es2015", "es2016", "dom"], 16 | "baseUrl": ".", 17 | "types": ["node"] 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "recontrib", 3 | "version": "0.3.1", 4 | "license": "MIT", 5 | "homepage": "https://pantharshit00.github.io/recontrib", 6 | "author": "Harshit Pant ", 7 | "main": "dist/index.js", 8 | "umd:main": "dist/recontrib.umd.production.js", 9 | "module": "dist/recontrib.es.production.js", 10 | "typings": "dist/index.d.ts", 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/pantharshit00/recontrib" 14 | }, 15 | "files": [ 16 | "dist" 17 | ], 18 | "scripts": { 19 | "start": "tsdx watch", 20 | "build": "tsdx build", 21 | "test": "tsdx test --env=jsdom" 22 | }, 23 | "peerDependencies": { 24 | "@reach/tooltip": "~0.2.0", 25 | "react": ">=16" 26 | }, 27 | "husky": { 28 | "hooks": { 29 | "pre-commit": "pretty-quick --staged" 30 | } 31 | }, 32 | "prettier": { 33 | "printWidth": 80, 34 | "semi": true, 35 | "singleQuote": true, 36 | "trailingComma": "es5" 37 | }, 38 | "devDependencies": { 39 | "@types/jest": "^24.0.13", 40 | "@types/react": "^16.8.17", 41 | "@types/react-dom": "^16.8.4", 42 | "husky": "^2.3.0", 43 | "prettier": "^1.17.1", 44 | "pretty-quick": "^1.10.0", 45 | "prop-types": "^15.7.2", 46 | "react": "^16.8.0", 47 | "react-dom": "^16.8.0", 48 | "tsdx": "^0.5.11", 49 | "tslib": "^1.9.3", 50 | "typescript": "^3.4.5" 51 | }, 52 | "dependencies": { 53 | "@reach/portal": "^0.2.1", 54 | "@reach/tooltip": "^0.2.0", 55 | "dayjs": "^1.8.14" 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/ignore.d.ts: -------------------------------------------------------------------------------- 1 | declare module '@reach/tooltip'; 2 | declare module '@reach/portal'; 3 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import dayjs from 'dayjs'; 3 | import { TriangleTooltip } from './utils/ToolTip'; 4 | 5 | interface WeekData { 6 | days: number[]; 7 | total: number; 8 | week: number; 9 | } 10 | 11 | interface Data { 12 | month: string; 13 | id: string; 14 | weeks: Array; 15 | } 16 | 17 | const MONTHS = [ 18 | 'Jan', 19 | 'Feb', 20 | 'Mar', 21 | 'Apr', 22 | 'May', 23 | 'Jun', 24 | 'Jul', 25 | 'Aug', 26 | 'Sep', 27 | 'Oct', 28 | 'Nov', 29 | 'Dec', 30 | ]; 31 | 32 | const COLORS = ['#ebedf0', '#c6e48b', '#7bc96f', '#239a3b', '#196127']; 33 | 34 | interface Props { 35 | data: Array; 36 | gridSize?: number; 37 | fontSize?: string; 38 | } 39 | 40 | export const Recontrib: React.FC = ({ 41 | data, 42 | gridSize = 10, 43 | fontSize = '9px', 44 | }) => { 45 | const finalData = React.useMemo(() => { 46 | let massagedData: Array = []; 47 | 48 | data.forEach(history => { 49 | const month = dayjs.unix(history.week).get('month'); 50 | const year = dayjs.unix(history.week).get('year'); 51 | const monthIndex = massagedData.findIndex( 52 | d => d.id === `${MONTHS[month]}${year}` 53 | ); 54 | if (monthIndex === -1) { 55 | massagedData.push({ 56 | month: MONTHS[month], 57 | weeks: [history], 58 | id: `${MONTHS[month]}${year}`, 59 | }); 60 | } else { 61 | massagedData[monthIndex].weeks.push(history); 62 | } 63 | }); 64 | return massagedData; 65 | }, [data]); 66 | 67 | let weekCount = 1; 68 | return ( 69 | 74 | 75 | {finalData.map(val => { 76 | return ( 77 | 78 | 2 ? 'visible' : 'hidden'} 82 | style={{ 83 | fill: '#767676', 84 | fontSize, 85 | fontFamily: 'sans-serif', 86 | }} 87 | > 88 | {val.month} 89 | 90 | {val.weeks.map(week => { 91 | return ( 92 | 96 | {week.days.map((val, idx) => ( 97 | 108 | 122 | 123 | ))} 124 | 125 | ); 126 | })} 127 | 128 | ); 129 | })} 130 | 135 | Mon 136 | 137 | 142 | Wed 143 | 144 | 149 | Fri 150 | 151 | 152 | 153 | ); 154 | }; 155 | -------------------------------------------------------------------------------- /src/utils/ToolTip.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { useTooltip, TooltipPopup } from '@reach/tooltip'; 3 | import Portal from '@reach/portal'; 4 | 5 | interface Rects { 6 | top: number; 7 | right: number; 8 | bottom: number; 9 | left: number; 10 | width: number; 11 | } 12 | 13 | const centered = (triggerRect: Rects, tooltipRect: Rects) => { 14 | const triggerCenter = triggerRect.left + triggerRect.width / 2; 15 | const left = triggerCenter - tooltipRect.width / 2; 16 | const maxLeft = window.innerWidth - tooltipRect.width - 2; 17 | return { 18 | left: Math.min(Math.max(2, left), maxLeft) + window.scrollX, 19 | top: triggerRect.bottom + 8 + window.scrollY, 20 | }; 21 | }; 22 | 23 | interface Props { 24 | label: string; 25 | ariaLabel?: string; 26 | } 27 | 28 | export const TriangleTooltip: React.FC = ({ 29 | children, 30 | label, 31 | ariaLabel, 32 | }) => { 33 | // get the props from useTooltip 34 | const [trigger, tooltip] = useTooltip(); 35 | 36 | // destructure off what we need to position the triangle 37 | const { isVisible, triggerRect } = tooltip; 38 | 39 | return ( 40 | 41 | {React.isValidElement(children) 42 | ? React.cloneElement(children, trigger) 43 | : ''} 44 | 45 | {isVisible && ( 46 | // The Triangle. We position it relative to the trigger, not the popup 47 | // so that collisions don't have a triangle pointing off to nowhere. 48 | // Using a Portal may seem a little extreme, but we can keep the 49 | // positioning logic simpler here instead of needing to consider 50 | // the popup's position relative to the trigger and collisions 51 | 52 |
65 | 66 | )} 67 | 81 | 82 | ); 83 | }; 84 | -------------------------------------------------------------------------------- /src/utils/darkenColor.ts: -------------------------------------------------------------------------------- 1 | export function LightenDarkenColor(col: string, amt: number) { 2 | var usePound = false; 3 | 4 | if (col[0] == '#') { 5 | col = col.slice(1); 6 | usePound = true; 7 | } 8 | 9 | var num = parseInt(col, 16); 10 | 11 | var r = (num >> 16) + amt; 12 | 13 | if (r > 255) r = 255; 14 | else if (r < 0) r = 0; 15 | 16 | var b = ((num >> 8) & 0x00ff) + amt; 17 | 18 | if (b > 255) b = 255; 19 | else if (b < 0) b = 0; 20 | 21 | var g = (num & 0x0000ff) + amt; 22 | 23 | if (g > 255) g = 255; 24 | else if (g < 0) g = 0; 25 | 26 | return (usePound ? '#' : '') + (g | (b << 8) | (r << 16)).toString(16); 27 | } 28 | -------------------------------------------------------------------------------- /test/blah.test.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import * as ReactDOM from 'react-dom'; 3 | import { Thing } from '../src'; 4 | 5 | describe('it', () => { 6 | it('renders without crashing', () => { 7 | const div = document.createElement('div'); 8 | ReactDOM.render(, div); 9 | ReactDOM.unmountComponentAtNode(div); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["src", "types", "example/data.ts"], 3 | "compilerOptions": { 4 | "target": "es5", 5 | "module": "esnext", 6 | "lib": ["dom", "esnext"], 7 | "importHelpers": true, 8 | "declaration": true, 9 | "sourceMap": true, 10 | "rootDir": "./", 11 | "strict": true, 12 | "noImplicitAny": true, 13 | "strictNullChecks": true, 14 | "strictFunctionTypes": true, 15 | "strictPropertyInitialization": true, 16 | "noImplicitThis": true, 17 | "alwaysStrict": true, 18 | "noUnusedLocals": true, 19 | "noUnusedParameters": true, 20 | "noImplicitReturns": true, 21 | "noFallthroughCasesInSwitch": true, 22 | "moduleResolution": "node", 23 | "baseUrl": "./", 24 | "paths": { 25 | "*": ["src/*", "node_modules/*"] 26 | }, 27 | "jsx": "react", 28 | "esModuleInterop": true 29 | } 30 | } 31 | --------------------------------------------------------------------------------