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
f9c0e0bff6. 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 <rick.staa@outlook.com>
This commit is contained in:
Artem Kostiuk 2022-09-16 06:50:30 -04:00 committed by GitHub
parent 192170c111
commit b0bb994ad3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 108 additions and 32 deletions

View File

@ -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,

View File

@ -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)_

View File

@ -36,10 +36,10 @@ const createTextNode = ({
<g class="stagger" style="animation-delay: ${staggerDelay}ms" transform="translate(25, 0)">
${iconSvg}
<text class="stat bold" ${labelOffset} y="12.5">${label}:</text>
<text
class="stat"
x="${(showIcons ? 140 : 120) + shiftValuePos}"
y="12.5"
<text
class="stat"
x="${(showIcons ? 140 : 120) + shiftValuePos}"
y="12.5"
data-testid="${id}"
>${kValue}</text>
</g>
@ -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
? ""
: `<g data-testid="rank-circle"
transform="translate(400, ${height / 2 - 50})">
<circle class="rank-circle-rim" cx="-10" cy="8" r="40" />
<circle class="rank-circle" cx="-10" cy="8" r="40" />
<g class="rank-text">
<text
x="-5"
y="3"
alignment-baseline="central"
dominant-baseline="central"
text-anchor="middle"
>
${rank.level}
</text>
</g>
</g>`;
// 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
? ""
: `<g data-testid="rank-circle"
transform="translate(${calculateRankXTranslation()}, ${
height / 2 - 50
})">
<circle class="rank-circle-rim" cx="-10" cy="8" r="40" />
<circle class="rank-circle" cx="-10" cy="8" r="40" />
<g class="rank-text">
<text
x="-5"
y="3"
alignment-baseline="central"
dominant-baseline="central"
text-anchor="middle"
>
${rank.level}
</text>
</g>
</g>`;
// 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("")}
</svg>
</svg>
`);
};

View File

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