diff --git a/api/index.js b/api/index.js index 6563dbc..b07464d 100644 --- a/api/index.js +++ b/api/index.js @@ -17,6 +17,7 @@ module.exports = async (req, res) => { hide, hide_title, hide_border, + card_width, hide_rank, show_icons, count_private, @@ -65,6 +66,7 @@ module.exports = async (req, res) => { show_icons: parseBoolean(show_icons), hide_title: parseBoolean(hide_title), hide_border: parseBoolean(hide_border), + card_width: parseInt(card_width, 10), hide_rank: parseBoolean(hide_rank), include_all_commits: parseBoolean(include_all_commits), line_height, diff --git a/readme.md b/readme.md index f657871..d8e4c7d 100644 --- a/readme.md +++ b/readme.md @@ -187,6 +187,7 @@ You can provide multiple comma-separated values in the bg_color option to render - `hide` - Hides the [specified items](#hiding-individual-stats) from stats _(Comma-separated values)_ - `hide_title` - _(boolean)_ +- `card_width` - Set the card's width manually _(number)_ - `hide_rank` - _(boolean)_ hides the rank and automatically resizes the card width - `show_icons` - _(boolean)_ - `include_all_commits` - Count total commits instead of just the current year commits _(boolean)_ diff --git a/src/cards/stats-card.js b/src/cards/stats-card.js index 9db4562..bbc48cf 100644 --- a/src/cards/stats-card.js +++ b/src/cards/stats-card.js @@ -36,10 +36,10 @@ const createTextNode = ({ ${iconSvg} ${label}: - ${kValue} @@ -66,6 +66,7 @@ const renderStatsCard = (stats = {}, options = { hide: [] }) => { show_icons = false, hide_title = false, hide_border = false, + card_width, hide_rank = false, include_all_commits = false, line_height = 25, @@ -174,26 +175,6 @@ const renderStatsCard = (stats = {}, options = { hide: [] }) => { hide_rank ? 0 : 150, ); - // Conditionally rendered elements - const rankCircle = hide_rank - ? "" - : ` - - - - - ${rank.level} - - - `; - // the better user's score the the rank will be closer to zero so // subtracting 100 to get the progress in 100% const progress = 100 - rank.score; @@ -209,13 +190,20 @@ const renderStatsCard = (stats = {}, options = { hide: [] }) => { return measureText(custom_title ? custom_title : i18n.t("statcard.title")); }; - const width = hide_rank - ? clampValue( - 50 /* padding */ + calculateTextWidth() * 2, - 270 /* min */, - Infinity, - ) - : 495; + /* + When hide_rank=true, the minimum card width is 270 px + the title length and padding. + When hide_rank=false, the minimum card_width is 340 px + the icon width (if show_icons=true). + Numbers are picked by looking at existing dimensions on production. + */ + const iconWidth = show_icons ? 16 : 0; + const minCardWidth = hide_rank + ? clampValue(50 /* padding */ + calculateTextWidth() * 2, 270, Infinity) + : 340 + iconWidth; + const defaultCardWidth = hide_rank ? 270 : 495; + let width = isNaN(card_width) ? defaultCardWidth : card_width; + if (width < minCardWidth) { + width = minCardWidth; + } const card = new Card({ customTitle: custom_title, @@ -238,6 +226,45 @@ const renderStatsCard = (stats = {}, options = { hide: [] }) => { if (disable_animations) card.disableAnimations(); + /** + * Calculates the right rank circle translation values such that the rank circle + * keeps respecting the padding. + * + * width > 450: The default left padding of 50 px will be used. + * width < 450: The left and right padding will shrink equally. + * + * @returns {number} - Rank circle translation value. + */ + const calculateRankXTranslation = () => { + if (width < 450) { + return width - 95 + (45 * (450 - 340)) / 110; + } else { + return width - 95; + } + }; + + // Conditionally rendered elements + const rankCircle = hide_rank + ? "" + : ` + + + + + ${rank.level} + + + `; + // Accessibility Labels const labels = Object.keys(STATS) .filter((key) => !hide.includes(key)) @@ -264,7 +291,7 @@ const renderStatsCard = (stats = {}, options = { hide: [] }) => { gap: lheight, direction: "column", }).join("")} - + `); }; diff --git a/tests/renderStatsCard.test.js b/tests/renderStatsCard.test.js index aabf2f6..09bd9b0 100644 --- a/tests/renderStatsCard.test.js +++ b/tests/renderStatsCard.test.js @@ -75,6 +75,52 @@ describe("Test renderStatsCard", () => { expect(queryByTestId(document.body, "rank-circle")).not.toBeInTheDocument(); }); + it("should render with custom width set", () => { + document.body.innerHTML = renderStatsCard(stats); + expect(document.querySelector("svg")).toHaveAttribute("width", "495"); + + document.body.innerHTML = renderStatsCard(stats, { card_width: 400 }); + expect(document.querySelector("svg")).toHaveAttribute("width", "400"); + }); + + it("should render with custom width set and limit minimum width", () => { + document.body.innerHTML = renderStatsCard(stats, { card_width: 1 }); + expect(document.querySelector("svg")).toHaveAttribute("width", "340"); + + document.body.innerHTML = renderStatsCard(stats, { + card_width: 1, + hide_rank: true, + }); + expect(document.querySelector("svg")).toHaveAttribute( + "width", + "305.81250000000006", + ); + + document.body.innerHTML = renderStatsCard(stats, { + card_width: 1, + hide_rank: true, + show_icons: true, + }); + expect(document.querySelector("svg")).toHaveAttribute( + "width", + "305.81250000000006", + ); + + document.body.innerHTML = renderStatsCard(stats, { + card_width: 1, + hide_rank: false, + show_icons: true, + }); + expect(document.querySelector("svg")).toHaveAttribute("width", "356"); + + document.body.innerHTML = renderStatsCard(stats, { + card_width: 1, + hide_rank: false, + show_icons: false, + }); + expect(document.querySelector("svg")).toHaveAttribute("width", "340"); + }); + it("should render default colors properly", () => { document.body.innerHTML = renderStatsCard(stats);