Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,5 @@
npm-debug.log*
yarn-debug.log*
yarn-error.log*

package-lock.json
222 changes: 116 additions & 106 deletions src/pages/index.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { TbBrandTwitter, TbShare, TbDownload, TbCopy } from "react-icons/tb";
import { TbBrandTwitter, TbShare, TbDownload, TbCopy, TbChevronDown } from "react-icons/tb";
import React, { useRef, useState, useEffect } from "react";
import {
download,
fetchData,
downloadJSON,
downloadSVG,
cleanUsername,
share,
copyToClipboard
Expand All @@ -19,17 +20,26 @@ const App = () => {
const [theme, setTheme] = useState("standard");
const [data, setData] = useState(null);
const [error, setError] = useState(null);
const [showDownloadMenu, setShowDownloadMenu] = useState(false);
const downloadMenuRef = useRef();

useEffect(() => {
if (!data) {
return;
}
if (!data) return;
draw();
}, [data, theme]);

useEffect(() => {
const handleClickOutside = (e) => {
if (downloadMenuRef.current && !downloadMenuRef.current.contains(e.target)) {
setShowDownloadMenu(false);
}
};
document.addEventListener("mousedown", handleClickOutside);
return () => document.removeEventListener("mousedown", handleClickOutside);
}, []);

const handleSubmit = (e) => {
e.preventDefault();

setUsername(cleanUsername(username));
setLoading(true);
setError(null);
Expand All @@ -52,9 +62,16 @@ const App = () => {
});
};

const onDownload = (e) => {
const onDownloadPNG = (e) => {
e.preventDefault();
download(canvasRef.current);
setShowDownloadMenu(false);
};

const onDownloadSVG = (e) => {
e.preventDefault();
downloadSVG(canvasRef.current);
setShowDownloadMenu(false);
};

const onCopy = (e) => {
Expand All @@ -64,9 +81,7 @@ const App = () => {

const onDownloadJson = (e) => {
e.preventDefault();
if (data != null) {
downloadJSON(data);
}
if (data != null) downloadJSON(data);
};

const onShare = (e) => {
Expand All @@ -79,131 +94,127 @@ const App = () => {
setError("Something went wrong... Check back later.");
return;
}

const { drawContributions } = await import("github-contributions-canvas");

drawContributions(canvasRef.current, {
data,
username: username,
themeName: theme,
footerText: "Made by @sallar & friends - github-contributions.vercel.app"
});
contentRef.current.scrollIntoView({
behavior: "smooth"
});
contentRef.current.scrollIntoView({ behavior: "smooth" });
};

const _renderGithubButton = () => {
return (
<div className="App-github-button">
<a
className="github-button"
href="https://github.com/sallar/github-contributions-chart"
data-size="large"
data-show-count="true"
aria-label="Star sallar/github-contribution-chart on GitHub"
>
Star
</a>
</div>
);
};
const _renderGithubButton = () => (
<div className="App-github-button">
<a
className="github-button"
href="https://github.com/sallar/github-contributions-chart"
data-size="large"
data-show-count="true"
aria-label="Star sallar/github-contribution-chart on GitHub"
>
Star
</a>
</div>
);

const _renderLoading = () => {
return (
<div className="App-centered">
<div className="App-loading">
<img src={"/loading.gif"} alt="Loading..." width={202} height={125} />
<p>Please wait, I’m visiting your profile...</p>
</div>
const _renderLoading = () => (
<div className="App-centered">
<div className="App-loading">
<img src={"/loading.gif"} alt="Loading..." width={202} height={125} />
<p>Please wait, I'm visiting your profile...</p>
</div>
);
};
</div>
);

const _renderGraphs = () => {
return (
<div
className="App-result"
style={{ display: data !== null && !loading ? "block" : "none" }}
>
<p>Your chart is ready!</p>
const _renderGraphs = () => (
<div
className="App-result"
style={{ display: data !== null && !loading ? "block" : "none" }}
>
<p>Your chart is ready!</p>
{data !== null && (
<>
<div className="App-buttons">
<button className="App-download-button" onClick={onCopy} type="button">
<TbCopy size={18} />
Copy
</button>

{data !== null && (
<>
<div className="App-buttons">
<div className="App-download-split" ref={downloadMenuRef}>
<button
className="App-download-button"
onClick={onCopy}
className="App-download-button App-download-button--main"
onClick={onDownloadPNG}
type="button"
>
<TbCopy size={18} />
Copy
<TbDownload size={18} />
Download PNG
</button>
<button
className="App-download-button"
onClick={onDownload}
className="App-download-button App-download-button--caret"
onClick={() => setShowDownloadMenu((v) => !v)}
type="button"
aria-label="More download options"
>
<TbDownload size={18} />
Download
<TbChevronDown size={16} />
</button>
{global.navigator && "share" in navigator && (
<button
className="App-download-button"
onClick={onShare}
type="button"
>
<TbShare size={18} />
Share
</button>
{showDownloadMenu && (
<div className="App-download-menu">
<button onClick={onDownloadPNG} type="button">
<TbDownload size={15} />
Download as PNG
</button>
<button onClick={onDownloadSVG} type="button">
<TbDownload size={15} />
Download as SVG
</button>
</div>
)}
</div>

<canvas ref={canvasRef} />
</>
)}
</div>
);
};
{global.navigator && "share" in navigator && (
<button className="App-download-button" onClick={onShare} type="button">
<TbShare size={18} />
Share
</button>
)}
</div>
<canvas ref={canvasRef} />
</>
)}
</div>
);

const _renderForm = () => {
return (
<form onSubmit={handleSubmit}>
<input
ref={inputRef}
placeholder="Your GitHub Username"
onChange={(e) => setUsername(e.target.value)}
value={username}
id="username"
autoCorrect="off"
autoCapitalize="none"
autoFocus
/>
<button type="submit" disabled={username.length <= 0 || loading}>
<span role="img" aria-label="Stars">
</span>{" "}
{loading ? "Generating..." : "Generate!"}
</button>
</form>
);
};
const _renderForm = () => (
<form onSubmit={handleSubmit}>
<input
ref={inputRef}
placeholder="Your GitHub Username"
onChange={(e) => setUsername(e.target.value)}
value={username}
id="username"
autoCorrect="off"
autoCapitalize="none"
autoFocus
/>
<button type="submit" disabled={username.length <= 0 || loading}>
<span role="img" aria-label="Stars">✨</span>{" "}
{loading ? "Generating..." : "Generate!"}
</button>
</form>
);

const _renderError = () => {
return (
<div className="App-error App-centered">
<p>{error}</p>
</div>
);
};
const _renderError = () => (
<div className="App-error App-centered">
<p>{error}</p>
</div>
);

const _renderDownloadAsJSON = () => {
if (data === null) return;
return (
<a href="#" onClick={onDownloadJson}>
<span role="img" aria-label="Bar Chart">
📊
</span>{" "}
<span role="img" aria-label="Bar Chart">📊</span>{" "}
Download data as JSON for your own visualizations
</a>
);
Expand All @@ -228,8 +239,7 @@ const App = () => {
Not affiliated with GitHub Inc. Octocat illustration from{" "}
<a href="https://octodex.github.com/topguntocat/" target="_blank">
GitHub Octodex
</a>
.
</a>.
</p>
{_renderDownloadAsJSON()}
<div className="App-powered">
Expand All @@ -251,4 +261,4 @@ const App = () => {
);
};

export default App;
export default App;
56 changes: 54 additions & 2 deletions src/styles/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
--input-border: rgb(55, 68, 75);
--button-disabled: rgb(42, 52, 57);
--button-disabled-border: var(--input-border);
--loading-text: #rgb(164, 157, 145);
--loading-text: rgb(164, 157, 145);
--input-text: #fff;
}
}
Expand Down Expand Up @@ -91,6 +91,58 @@ html {
height: 3rem;
}

.App-download-split {
position: relative;
display: inline-flex;
margin: 0 1rem;
}

.App-download-split .App-download-button {
margin: 0;
}

.App-download-button--main {
border-radius: 6px 0 0 6px;
border-right: 1px solid rgba(255, 255, 255, 0.2);
}

.App-download-button--caret {
border-radius: 0 6px 6px 0;
padding: 0 8px;
}

.App-download-menu {
position: absolute;
top: calc(100% + 4px);
left: 0;
background: #1e1e2e;
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 6px;
overflow: hidden;
z-index: 100;
min-width: 170px;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3);
}

.App-download-menu button {
display: flex;
align-items: center;
gap: 8px;
width: 100%;
margin: 0;
padding: 10px 14px;
background: none;
border: none;
color: inherit;
cursor: pointer;
font-size: 14px;
text-align: left;
}

.App-download-menu button:hover {
background: rgba(255, 255, 255, 0.08);
}

h1,
h4 {
font-weight: 300;
Expand Down Expand Up @@ -284,4 +336,4 @@ footer a {
display: flex;
flex-wrap: wrap;
}
}
}
Loading