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);