Skip to content

Commit b0bb994

Browse files
postatumrickstaa
andauthored
Fix #1170: Allow customizable width for GitHub Stats Card (#1334)
* Change default stats card width with hide rank * Add tests for stats card with card_width * Add card_width Stats Card description to readme * fix: add icon width to stats-card min width calculation * fix: fixes rank circle padding problem This commit fixes a padding problem that was introduced in f9c0e0b. In the new code, the padding around the rank circle will be 50 when the stats card is bigger than 450. When it is smaller than 450 the left and right padding will shrink equally. * style: run prettier * tests: add extra stats 'card_width' tests This commit makes sure we also test the stats card width for the case that the 'show_icons' option is enabled. * style: run prettier Co-authored-by: rickstaa <[email protected]>
1 parent 192170c commit b0bb994

File tree

4 files changed

+108
-32
lines changed

4 files changed

+108
-32
lines changed

api/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ module.exports = async (req, res) => {
1717
hide,
1818
hide_title,
1919
hide_border,
20+
card_width,
2021
hide_rank,
2122
show_icons,
2223
count_private,
@@ -65,6 +66,7 @@ module.exports = async (req, res) => {
6566
show_icons: parseBoolean(show_icons),
6667
hide_title: parseBoolean(hide_title),
6768
hide_border: parseBoolean(hide_border),
69+
card_width: parseInt(card_width, 10),
6870
hide_rank: parseBoolean(hide_rank),
6971
include_all_commits: parseBoolean(include_all_commits),
7072
line_height,

readme.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,7 @@ You can provide multiple comma-separated values in the bg_color option to render
187187

188188
- `hide` - Hides the [specified items](#hiding-individual-stats) from stats _(Comma-separated values)_
189189
- `hide_title` - _(boolean)_
190+
- `card_width` - Set the card's width manually _(number)_
190191
- `hide_rank` - _(boolean)_ hides the rank and automatically resizes the card width
191192
- `show_icons` - _(boolean)_
192193
- `include_all_commits` - Count total commits instead of just the current year commits _(boolean)_

src/cards/stats-card.js

Lines changed: 59 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,10 @@ const createTextNode = ({
3636
<g class="stagger" style="animation-delay: ${staggerDelay}ms" transform="translate(25, 0)">
3737
${iconSvg}
3838
<text class="stat bold" ${labelOffset} y="12.5">${label}:</text>
39-
<text
40-
class="stat"
41-
x="${(showIcons ? 140 : 120) + shiftValuePos}"
42-
y="12.5"
39+
<text
40+
class="stat"
41+
x="${(showIcons ? 140 : 120) + shiftValuePos}"
42+
y="12.5"
4343
data-testid="${id}"
4444
>${kValue}</text>
4545
</g>
@@ -66,6 +66,7 @@ const renderStatsCard = (stats = {}, options = { hide: [] }) => {
6666
show_icons = false,
6767
hide_title = false,
6868
hide_border = false,
69+
card_width,
6970
hide_rank = false,
7071
include_all_commits = false,
7172
line_height = 25,
@@ -174,26 +175,6 @@ const renderStatsCard = (stats = {}, options = { hide: [] }) => {
174175
hide_rank ? 0 : 150,
175176
);
176177

177-
// Conditionally rendered elements
178-
const rankCircle = hide_rank
179-
? ""
180-
: `<g data-testid="rank-circle"
181-
transform="translate(400, ${height / 2 - 50})">
182-
<circle class="rank-circle-rim" cx="-10" cy="8" r="40" />
183-
<circle class="rank-circle" cx="-10" cy="8" r="40" />
184-
<g class="rank-text">
185-
<text
186-
x="-5"
187-
y="3"
188-
alignment-baseline="central"
189-
dominant-baseline="central"
190-
text-anchor="middle"
191-
>
192-
${rank.level}
193-
</text>
194-
</g>
195-
</g>`;
196-
197178
// the better user's score the the rank will be closer to zero so
198179
// subtracting 100 to get the progress in 100%
199180
const progress = 100 - rank.score;
@@ -209,13 +190,20 @@ const renderStatsCard = (stats = {}, options = { hide: [] }) => {
209190
return measureText(custom_title ? custom_title : i18n.t("statcard.title"));
210191
};
211192

212-
const width = hide_rank
213-
? clampValue(
214-
50 /* padding */ + calculateTextWidth() * 2,
215-
270 /* min */,
216-
Infinity,
217-
)
218-
: 495;
193+
/*
194+
When hide_rank=true, the minimum card width is 270 px + the title length and padding.
195+
When hide_rank=false, the minimum card_width is 340 px + the icon width (if show_icons=true).
196+
Numbers are picked by looking at existing dimensions on production.
197+
*/
198+
const iconWidth = show_icons ? 16 : 0;
199+
const minCardWidth = hide_rank
200+
? clampValue(50 /* padding */ + calculateTextWidth() * 2, 270, Infinity)
201+
: 340 + iconWidth;
202+
const defaultCardWidth = hide_rank ? 270 : 495;
203+
let width = isNaN(card_width) ? defaultCardWidth : card_width;
204+
if (width < minCardWidth) {
205+
width = minCardWidth;
206+
}
219207

220208
const card = new Card({
221209
customTitle: custom_title,
@@ -238,6 +226,45 @@ const renderStatsCard = (stats = {}, options = { hide: [] }) => {
238226

239227
if (disable_animations) card.disableAnimations();
240228

229+
/**
230+
* Calculates the right rank circle translation values such that the rank circle
231+
* keeps respecting the padding.
232+
*
233+
* width > 450: The default left padding of 50 px will be used.
234+
* width < 450: The left and right padding will shrink equally.
235+
*
236+
* @returns {number} - Rank circle translation value.
237+
*/
238+
const calculateRankXTranslation = () => {
239+
if (width < 450) {
240+
return width - 95 + (45 * (450 - 340)) / 110;
241+
} else {
242+
return width - 95;
243+
}
244+
};
245+
246+
// Conditionally rendered elements
247+
const rankCircle = hide_rank
248+
? ""
249+
: `<g data-testid="rank-circle"
250+
transform="translate(${calculateRankXTranslation()}, ${
251+
height / 2 - 50
252+
})">
253+
<circle class="rank-circle-rim" cx="-10" cy="8" r="40" />
254+
<circle class="rank-circle" cx="-10" cy="8" r="40" />
255+
<g class="rank-text">
256+
<text
257+
x="-5"
258+
y="3"
259+
alignment-baseline="central"
260+
dominant-baseline="central"
261+
text-anchor="middle"
262+
>
263+
${rank.level}
264+
</text>
265+
</g>
266+
</g>`;
267+
241268
// Accessibility Labels
242269
const labels = Object.keys(STATS)
243270
.filter((key) => !hide.includes(key))
@@ -264,7 +291,7 @@ const renderStatsCard = (stats = {}, options = { hide: [] }) => {
264291
gap: lheight,
265292
direction: "column",
266293
}).join("")}
267-
</svg>
294+
</svg>
268295
`);
269296
};
270297

tests/renderStatsCard.test.js

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,52 @@ describe("Test renderStatsCard", () => {
7575
expect(queryByTestId(document.body, "rank-circle")).not.toBeInTheDocument();
7676
});
7777

78+
it("should render with custom width set", () => {
79+
document.body.innerHTML = renderStatsCard(stats);
80+
expect(document.querySelector("svg")).toHaveAttribute("width", "495");
81+
82+
document.body.innerHTML = renderStatsCard(stats, { card_width: 400 });
83+
expect(document.querySelector("svg")).toHaveAttribute("width", "400");
84+
});
85+
86+
it("should render with custom width set and limit minimum width", () => {
87+
document.body.innerHTML = renderStatsCard(stats, { card_width: 1 });
88+
expect(document.querySelector("svg")).toHaveAttribute("width", "340");
89+
90+
document.body.innerHTML = renderStatsCard(stats, {
91+
card_width: 1,
92+
hide_rank: true,
93+
});
94+
expect(document.querySelector("svg")).toHaveAttribute(
95+
"width",
96+
"305.81250000000006",
97+
);
98+
99+
document.body.innerHTML = renderStatsCard(stats, {
100+
card_width: 1,
101+
hide_rank: true,
102+
show_icons: true,
103+
});
104+
expect(document.querySelector("svg")).toHaveAttribute(
105+
"width",
106+
"305.81250000000006",
107+
);
108+
109+
document.body.innerHTML = renderStatsCard(stats, {
110+
card_width: 1,
111+
hide_rank: false,
112+
show_icons: true,
113+
});
114+
expect(document.querySelector("svg")).toHaveAttribute("width", "356");
115+
116+
document.body.innerHTML = renderStatsCard(stats, {
117+
card_width: 1,
118+
hide_rank: false,
119+
show_icons: false,
120+
});
121+
expect(document.querySelector("svg")).toHaveAttribute("width", "340");
122+
});
123+
78124
it("should render default colors properly", () => {
79125
document.body.innerHTML = renderStatsCard(stats);
80126

0 commit comments

Comments
 (0)