diff --git a/api/index.js b/api/index.js index 22019cd..e6684f2 100644 --- a/api/index.js +++ b/api/index.js @@ -4,7 +4,17 @@ const fetchStats = require("../src/fetchStats"); const renderStatsCard = require("../src/renderStatsCard"); module.exports = async (req, res) => { - const { username, hide, hide_border, show_icons, line_height } = req.query; + const { + username, + hide, + hide_border, + show_icons, + line_height, + title_color, + icon_color, + text_color, + bg_color, + } = req.query; let stats; res.setHeader("Content-Type", "image/svg+xml"); @@ -20,6 +30,10 @@ module.exports = async (req, res) => { show_icons, hide_border, line_height, + title_color, + icon_color, + text_color, + bg_color, }) ); }; diff --git a/api/pin.js b/api/pin.js index c509fe2..2d69c0e 100644 --- a/api/pin.js +++ b/api/pin.js @@ -4,7 +4,14 @@ const fetchRepo = require("../src/fetchRepo"); const renderRepoCard = require("../src/renderRepoCard"); module.exports = async (req, res) => { - const { username, repo } = req.query; + const { + username, + repo, + title_color, + icon_color, + text_color, + bg_color, + } = req.query; let repoData; res.setHeader("Content-Type", "image/svg+xml"); @@ -16,5 +23,12 @@ module.exports = async (req, res) => { return res.send(renderError(err.message)); } - res.send(renderRepoCard(repoData)); + res.send( + renderRepoCard(repoData, { + title_color, + icon_color, + text_color, + bg_color, + }) + ); }; diff --git a/package.json b/package.json index 1d3b262..4a95aa8 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "@testing-library/jest-dom": "^5.11.0", "axios": "^0.19.2", "axios-mock-adapter": "^1.18.1", + "css-to-object": "^1.1.0", "jest": "^26.1.0" }, "dependencies": { diff --git a/readme.md b/readme.md index a921651..e5ee897 100644 --- a/readme.md +++ b/readme.md @@ -10,6 +10,7 @@ Get dynamically generated GitHub stats on your readmes! - [Github Stats Card](#github-stats-card) - [Github Extra Pins](#github-extra-pins) +- [Customization](#customization) - [Deploy Yourself](#deploy-on-your-own-vercel-instance) # Github Stats Card @@ -45,6 +46,25 @@ Other options: - `&hide_border=true` hide the border box if you don't like it :D. - `&line_height=30` control the line-height between text. +### Customization + +You can customize the appearance of your `Stats Card` or `Repo Card` however you want with url params. + +Customization Options: + +| Option | type | Stats Card (default) | Repo Card (default) | +| ----------- | --------- | ---------------------- | ---------------------- | +| title_color | hex color | #2f80ed | #2f80ed | +| text_color | hex color | #333 | #333 | +| icon_color | hex color | #4c71f2 | #586069 | +| bg_color | hex color | rgba(255, 255, 255, 0) | rgba(255, 255, 255, 0) | + +- You can also customize the cards to be compatible with dark mode + +```md +![Anurag's github stats](https://github-readme-stats.vercel.app/api?username=anuraghazra?username=anuraghazra&repo=github-readme-stats&title_color=fff&icon_color=f9f9f9&text_color=9f9f9f&bg_color=151515]) +``` + ### Demo - Default @@ -59,6 +79,14 @@ Other options: ![Anurag's github stats](https://github-readme-stats.vercel.app/api?username=anuraghazra&hide=["issues"]&show_icons=true) +- Customizing stats card + +![Anurag's github stats](https://github-readme-stats.vercel.app/api/?username=anuraghazra&repo=github-readme-stats&show_icons=true&title_color=fff&icon_color=79ff97&text_color=9f9f9f&bg_color=151515]) + +- Customizing repo card + +![Customized Card](https://github-readme-stats.vercel.app/api/pin?username=anuraghazra&repo=github-readme-stats&title_color=fff&icon_color=f9f9f9&text_color=9f9f9f&bg_color=151515]) + # Github Extra Pins Github extra pins allow you to pin more than 6 repositories in your profile using a GitHub readme profile. diff --git a/src/renderRepoCard.js b/src/renderRepoCard.js index 97412c3..a228866 100644 --- a/src/renderRepoCard.js +++ b/src/renderRepoCard.js @@ -1,7 +1,9 @@ -const { kFormatter, encodeHTML } = require("../src/utils"); +const { kFormatter, encodeHTML, isValidHexColor } = require("../src/utils"); -const renderRepoCard = (repo) => { +const renderRepoCard = (repo, options = {}) => { const { name, description, primaryLanguage, stargazers, forkCount } = repo; + const { title_color, icon_color, text_color, bg_color } = options; + const height = 120; const shiftText = primaryLanguage.name.length > 15 ? 0 : 30; @@ -10,20 +12,26 @@ const renderRepoCard = (repo) => { desc = `${description.slice(0, 55)}..`; } + const titleColor = + (isValidHexColor(title_color) && `#${title_color}`) || "#2f80ed"; + const iconColor = + (isValidHexColor(icon_color) && `#${icon_color}`) || "#586069"; + const bgColor = + (isValidHexColor(bg_color) && `#${bg_color}`) || "rgba(255, 255, 255, 0)"; + const textColor = (isValidHexColor(text_color) && `#${text_color}`) || "#333"; + const totalStars = kFormatter(stargazers.totalCount); const totalForks = kFormatter(forkCount); return ` - - + + @@ -40,14 +48,14 @@ const renderRepoCard = (repo) => { - + ${totalStars} - + ${totalForks} diff --git a/src/renderStatsCard.js b/src/renderStatsCard.js index 98d73b3..40b03a6 100644 --- a/src/renderStatsCard.js +++ b/src/renderStatsCard.js @@ -1,11 +1,11 @@ -const { kFormatter } = require("../src/utils"); +const { kFormatter, isValidHexColor } = require("../src/utils"); const createTextNode = ({ icon, label, value, lineHeight, id }) => { const classname = icon === "★" && "star-icon"; const kValue = kFormatter(value); return ` - ${icon} ${label}: + ${icon} ${label}: ${kValue} `; }; @@ -24,10 +24,22 @@ const renderStatsCard = (stats = {}, options = { hide: [] }) => { show_icons = false, hide_border = false, line_height = 25, + title_color, + icon_color, + text_color, + bg_color, } = options; const lheight = parseInt(line_height); + const titleColor = + (isValidHexColor(title_color) && `#${title_color}`) || "#2f80ed"; + const iconColor = + (isValidHexColor(icon_color) && `#${icon_color}`) || "#4c71f2"; + const textColor = (isValidHexColor(text_color) && `#${text_color}`) || "#333"; + const bgColor = + (isValidHexColor(bg_color) && `#${bg_color}`) || "rgba(255, 255, 255, 0)"; + const STAT_MAP = { stars: createTextNode({ icon: "★", @@ -72,15 +84,16 @@ const renderStatsCard = (stats = {}, options = { hide: [] }) => { const height = 45 + (statItems.length + 1) * lheight; - const border = ``; + const border = ``; return ` diff --git a/src/utils.js b/src/utils.js index 2b7168e..6cd2b50 100644 --- a/src/utils.js +++ b/src/utils.js @@ -25,4 +25,10 @@ function kFormatter(num) { : Math.sign(num) * Math.abs(num); } -module.exports = { renderError, kFormatter, encodeHTML }; +function isValidHexColor(hexColor) { + return new RegExp( + /^([A-Fa-f0-9]{8}|[A-Fa-f0-9]{6}|[A-Fa-f0-9]{3}|[A-Fa-f0-9]{4})$/ + ).test(hexColor); +} + +module.exports = { renderError, kFormatter, encodeHTML, isValidHexColor }; diff --git a/tests/renderRepoCard.test.js b/tests/renderRepoCard.test.js index e41cb7e..aea2954 100644 --- a/tests/renderRepoCard.test.js +++ b/tests/renderRepoCard.test.js @@ -1,4 +1,5 @@ require("@testing-library/jest-dom"); +const cssToObject = require("css-to-object"); const renderRepoCard = require("../src/renderRepoCard"); const { queryByTestId } = require("@testing-library/dom"); @@ -88,4 +89,51 @@ describe("Test renderRepoCard", () => { "translate(125, 100)" ); }); + + it("should render default colors properly", () => { + document.body.innerHTML = renderRepoCard(data_repo.repository); + + const styleTag = document.querySelector("style"); + const stylesObject = cssToObject(styleTag.innerHTML); + + const headerClassStyles = stylesObject[".header"]; + const statClassStyles = stylesObject[".description"]; + const iconClassStyles = stylesObject[".icon"]; + + expect(headerClassStyles.fill).toBe("#2f80ed"); + expect(statClassStyles.fill).toBe("#333"); + expect(iconClassStyles.fill).toBe("#586069"); + expect(queryByTestId(document.body, "card-border")).toHaveAttribute( + "fill", + "rgba(255, 255, 255, 0)" + ); + }); + + it("should render custom colors properly", () => { + const customColors = { + title_color: "5a0", + icon_color: "1b998b", + text_color: "9991", + bg_color: "252525", + }; + + document.body.innerHTML = renderRepoCard(data_repo.repository, { + ...customColors, + }); + + const styleTag = document.querySelector("style"); + const stylesObject = cssToObject(styleTag.innerHTML); + + const headerClassStyles = stylesObject[".header"]; + const statClassStyles = stylesObject[".description"]; + const iconClassStyles = stylesObject[".icon"]; + + expect(headerClassStyles.fill).toBe(`#${customColors.title_color}`); + expect(statClassStyles.fill).toBe(`#${customColors.text_color}`); + expect(iconClassStyles.fill).toBe(`#${customColors.icon_color}`); + expect(queryByTestId(document.body, "card-border")).toHaveAttribute( + "fill", + "#252525" + ); + }); }); diff --git a/tests/renderStatsCard.test.js b/tests/renderStatsCard.test.js index d7bc8d2..8e3d4f4 100644 --- a/tests/renderStatsCard.test.js +++ b/tests/renderStatsCard.test.js @@ -1,4 +1,5 @@ require("@testing-library/jest-dom"); +const cssToObject = require("css-to-object"); const renderStatsCard = require("../src/renderStatsCard"); const { getByTestId, queryByTestId } = require("@testing-library/dom"); @@ -51,4 +52,49 @@ describe("Test renderStatsCard", () => { expect(queryByTestId(document.body, "card-border")).not.toBeInTheDocument(); }); + + it("should render default colors properly", () => { + document.body.innerHTML = renderStatsCard(stats); + + const styleTag = document.querySelector("style"); + const stylesObject = cssToObject(styleTag.innerHTML); + + const headerClassStyles = stylesObject[".header"]; + const statClassStyles = stylesObject[".stat"]; + const iconClassStyles = stylesObject[".icon"]; + + expect(headerClassStyles.fill).toBe("#2f80ed"); + expect(statClassStyles.fill).toBe("#333"); + expect(iconClassStyles.fill).toBe("#4c71f2"); + expect(queryByTestId(document.body, "card-border")).toHaveAttribute( + "fill", + "rgba(255, 255, 255, 0)" + ); + }); + + it("should render custom colors properly", () => { + const customColors = { + title_color: "5a0", + icon_color: "1b998b", + text_color: "9991", + bg_color: "252525", + }; + + document.body.innerHTML = renderStatsCard(stats, { ...customColors }); + + const styleTag = document.querySelector("style"); + const stylesObject = cssToObject(styleTag.innerHTML); + + const headerClassStyles = stylesObject[".header"]; + const statClassStyles = stylesObject[".stat"]; + const iconClassStyles = stylesObject[".icon"]; + + expect(headerClassStyles.fill).toBe(`#${customColors.title_color}`); + expect(statClassStyles.fill).toBe(`#${customColors.text_color}`); + expect(iconClassStyles.fill).toBe(`#${customColors.icon_color}`); + expect(queryByTestId(document.body, "card-border")).toHaveAttribute( + "fill", + "#252525" + ); + }); });