feat: Added compact layout for top languages card (#179)
* compact layout for top langs card * 🐞 FIX: most used lang should be first, apply correct colors, removed nested svgs and height set * 🐞 FIX: conditionally rendering layout compact * 🐞 FIX: border radius on lang progressbar * refactor: refactored code & fixed bugs * fix: toFixed * chore: change string interpolation * docs: updated readme Co-authored-by: anuraghazra <hazru.anurag@gmail.com>
This commit is contained in:
parent
3bdcf3d61f
commit
b8330a88e1
4 changed files with 139 additions and 19 deletions
|
@ -20,6 +20,7 @@ module.exports = async (req, res) => {
|
||||||
bg_color,
|
bg_color,
|
||||||
theme,
|
theme,
|
||||||
cache_seconds,
|
cache_seconds,
|
||||||
|
layout
|
||||||
} = req.query;
|
} = req.query;
|
||||||
let topLangs;
|
let topLangs;
|
||||||
|
|
||||||
|
@ -49,6 +50,7 @@ module.exports = async (req, res) => {
|
||||||
text_color,
|
text_color,
|
||||||
bg_color,
|
bg_color,
|
||||||
theme,
|
theme,
|
||||||
|
layout
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
13
readme.md
13
readme.md
|
@ -125,6 +125,7 @@ Customization Options:
|
||||||
| theme | string | sets inbuilt theme | 'default' | 'default_repocard' | 'default |
|
| theme | string | sets inbuilt theme | 'default' | 'default_repocard' | 'default |
|
||||||
| cache_seconds | number | manually set custom cache control | 1800 | 1800 | '1800' |
|
| cache_seconds | number | manually set custom cache control | 1800 | 1800 | '1800' |
|
||||||
| count_private | boolean | counts private contributions too if enabled | false | N/A | N/A |
|
| count_private | boolean | counts private contributions too if enabled | false | N/A | N/A |
|
||||||
|
| layout | string | choose a layout option | N/A | N/A | "default" |
|
||||||
|
|
||||||
> Note on cache: Repo cards have default cache of 30mins (1800 seconds) if the fork count & star count is less than 1k otherwise it's 2hours (7200). Also note that cache is clamped to minimum of 30min and maximum of 24hours
|
> Note on cache: Repo cards have default cache of 30mins (1800 seconds) if the fork count & star count is less than 1k otherwise it's 2hours (7200). Also note that cache is clamped to minimum of 30min and maximum of 24hours
|
||||||
|
|
||||||
|
@ -176,10 +177,22 @@ You can use `?hide=language1,language2` parameter to hide individual languages.
|
||||||
[](https://github.com/anuraghazra/github-readme-stats)
|
[](https://github.com/anuraghazra/github-readme-stats)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Compact Language Card Layout
|
||||||
|
|
||||||
|
You can use the `&layout=compact` option to change the card design.
|
||||||
|
|
||||||
|
```md
|
||||||
|
[](https://github.com/anuraghazra/github-readme-stats)
|
||||||
|
```
|
||||||
|
|
||||||
### Demo
|
### Demo
|
||||||
|
|
||||||
[](https://github.com/anuraghazra/github-readme-stats)
|
[](https://github.com/anuraghazra/github-readme-stats)
|
||||||
|
|
||||||
|
- Compact layout
|
||||||
|
|
||||||
|
[](https://github.com/anuraghazra/github-readme-stats)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### All Demos
|
### All Demos
|
||||||
|
|
|
@ -23,6 +23,41 @@ const createProgressNode = ({ width, color, name, progress }) => {
|
||||||
`;
|
`;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const createCompactLangNode = ({ lang, totalSize, x, y }) => {
|
||||||
|
const percentage = ((lang.size / totalSize) * 100).toFixed(2);
|
||||||
|
const color = lang.color || "#858585";
|
||||||
|
|
||||||
|
return `
|
||||||
|
<g transform="translate(${x}, ${y})">
|
||||||
|
<circle cx="5" cy="6" r="5" fill="${color}" />
|
||||||
|
<text data-testid="lang-name" x="15" y="10" class='lang-name'>
|
||||||
|
${lang.name} ${percentage}%
|
||||||
|
</text>
|
||||||
|
</g>
|
||||||
|
`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const createLanguageTextNode = ({ langs, totalSize, x, y }) => {
|
||||||
|
return langs.map((lang, index) => {
|
||||||
|
if (index % 2 === 0) {
|
||||||
|
return createCompactLangNode({
|
||||||
|
lang,
|
||||||
|
x,
|
||||||
|
y: 12.5 * index + y,
|
||||||
|
totalSize,
|
||||||
|
index,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return createCompactLangNode({
|
||||||
|
lang,
|
||||||
|
x: 150,
|
||||||
|
y: 12.5 + 12.5 * index,
|
||||||
|
totalSize,
|
||||||
|
index,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const lowercaseTrim = (name) => name.toLowerCase().trim();
|
const lowercaseTrim = (name) => name.toLowerCase().trim();
|
||||||
|
|
||||||
const renderTopLanguages = (topLangs, options = {}) => {
|
const renderTopLanguages = (topLangs, options = {}) => {
|
||||||
|
@ -34,6 +69,7 @@ const renderTopLanguages = (topLangs, options = {}) => {
|
||||||
bg_color,
|
bg_color,
|
||||||
hide,
|
hide,
|
||||||
theme,
|
theme,
|
||||||
|
layout,
|
||||||
} = options;
|
} = options;
|
||||||
|
|
||||||
let langs = Object.values(topLangs);
|
let langs = Object.values(topLangs);
|
||||||
|
@ -54,7 +90,7 @@ const renderTopLanguages = (topLangs, options = {}) => {
|
||||||
return !langsToHide[lowercaseTrim(lang.name)];
|
return !langsToHide[lowercaseTrim(lang.name)];
|
||||||
});
|
});
|
||||||
|
|
||||||
const totalSize = langs.reduce((acc, curr) => {
|
const totalLanguageSize = langs.reduce((acc, curr) => {
|
||||||
return acc + curr.size;
|
return acc + curr.size;
|
||||||
}, 0);
|
}, 0);
|
||||||
|
|
||||||
|
@ -66,12 +102,78 @@ const renderTopLanguages = (topLangs, options = {}) => {
|
||||||
theme,
|
theme,
|
||||||
});
|
});
|
||||||
|
|
||||||
const width = isNaN(card_width) ? 300 : card_width;
|
let width = isNaN(card_width) ? 300 : card_width;
|
||||||
let height = 45 + (langs.length + 1) * 40;
|
let height = 45 + (langs.length + 1) * 40;
|
||||||
|
|
||||||
|
let finalLayout = "";
|
||||||
|
|
||||||
|
// RENDER COMPACT LAYOUT
|
||||||
|
if (layout === "compact") {
|
||||||
|
width = width + 50;
|
||||||
|
height = 30 + (langs.length / 2 + 1) * 40;
|
||||||
|
|
||||||
|
// progressOffset holds the previous language's width and used to offset the next language
|
||||||
|
// so that we can stack them one after another, like this: [--][----][---]
|
||||||
|
let progressOffset = 0;
|
||||||
|
const compactProgressBar = langs
|
||||||
|
.map((lang) => {
|
||||||
|
const percentage = (
|
||||||
|
(lang.size / totalLanguageSize) *
|
||||||
|
(width - 50)
|
||||||
|
).toFixed(2);
|
||||||
|
|
||||||
|
const progress =
|
||||||
|
percentage < 10 ? parseFloat(percentage) + 10 : percentage;
|
||||||
|
|
||||||
|
const output = `
|
||||||
|
<rect
|
||||||
|
mask="url(#rect-mask)"
|
||||||
|
data-testid="lang-progress"
|
||||||
|
x="${progressOffset}"
|
||||||
|
y="0"
|
||||||
|
width="${progress}"
|
||||||
|
height="8"
|
||||||
|
fill="${lang.color || "#858585"}"
|
||||||
|
/>
|
||||||
|
`;
|
||||||
|
progressOffset += parseFloat(percentage);
|
||||||
|
return output;
|
||||||
|
})
|
||||||
|
.join("");
|
||||||
|
|
||||||
|
finalLayout = `
|
||||||
|
<mask id="rect-mask">
|
||||||
|
<rect x="0" y="0" width="${
|
||||||
|
width - 50
|
||||||
|
}" height="8" fill="white" rx="5" />
|
||||||
|
</mask>
|
||||||
|
${compactProgressBar}
|
||||||
|
${createLanguageTextNode({
|
||||||
|
x: 0,
|
||||||
|
y: 25,
|
||||||
|
langs,
|
||||||
|
totalSize: totalLanguageSize,
|
||||||
|
}).join("")}
|
||||||
|
`;
|
||||||
|
} else {
|
||||||
|
finalLayout = FlexLayout({
|
||||||
|
items: langs.map((lang) => {
|
||||||
|
return createProgressNode({
|
||||||
|
width: width,
|
||||||
|
name: lang.name,
|
||||||
|
color: lang.color || "#858585",
|
||||||
|
progress: ((lang.size / totalLanguageSize) * 100).toFixed(2),
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
gap: 40,
|
||||||
|
direction: "column",
|
||||||
|
}).join("");
|
||||||
|
}
|
||||||
|
|
||||||
if (hide_title) {
|
if (hide_title) {
|
||||||
height -= 30;
|
height -= 30;
|
||||||
}
|
}
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<svg width="${width}" height="${height}" viewBox="0 0 ${width} ${height}" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="${width}" height="${height}" viewBox="0 0 ${width} ${height}" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<style>
|
<style>
|
||||||
|
@ -80,7 +182,6 @@ const renderTopLanguages = (topLangs, options = {}) => {
|
||||||
</style>
|
</style>
|
||||||
<rect data-testid="card-bg" x="0.5" y="0.5" width="99.7%" height="99%" rx="4.5" fill="${bgColor}" stroke="#E4E2E2"/>
|
<rect data-testid="card-bg" x="0.5" y="0.5" width="99.7%" height="99%" rx="4.5" fill="${bgColor}" stroke="#E4E2E2"/>
|
||||||
|
|
||||||
|
|
||||||
${
|
${
|
||||||
hide_title
|
hide_title
|
||||||
? ""
|
? ""
|
||||||
|
@ -88,18 +189,7 @@ const renderTopLanguages = (topLangs, options = {}) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
<svg data-testid="lang-items" x="25" y="${hide_title ? 25 : 55}">
|
<svg data-testid="lang-items" x="25" y="${hide_title ? 25 : 55}">
|
||||||
${FlexLayout({
|
${finalLayout}
|
||||||
items: langs.map((lang) => {
|
|
||||||
return createProgressNode({
|
|
||||||
width: width,
|
|
||||||
name: lang.name,
|
|
||||||
color: lang.color || "#858585",
|
|
||||||
progress: ((lang.size / totalSize) * 100).toFixed(2),
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
gap: 40,
|
|
||||||
direction: "column",
|
|
||||||
}).join("")}
|
|
||||||
</svg>
|
</svg>
|
||||||
</svg>
|
</svg>
|
||||||
`;
|
`;
|
||||||
|
|
|
@ -207,4 +207,19 @@ describe("Test renderTopLanguages", () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should render with layout compact', () => {
|
||||||
|
document.body.innerHTML = renderTopLanguages(langs, {layout: 'compact'});
|
||||||
|
|
||||||
|
expect(queryByTestId(document.body, "header")).toHaveTextContent("Top Languages");
|
||||||
|
|
||||||
|
expect(queryAllByTestId(document.body, "lang-name")[0]).toHaveTextContent("HTML 40.00%");
|
||||||
|
expect(queryAllByTestId(document.body, "lang-progress")[0]).toHaveAttribute("width","120.00");
|
||||||
|
|
||||||
|
expect(queryAllByTestId(document.body, "lang-name")[1]).toHaveTextContent("javascript 40.00%");
|
||||||
|
expect(queryAllByTestId(document.body, "lang-progress")[1]).toHaveAttribute("width","120.00");
|
||||||
|
|
||||||
|
expect(queryAllByTestId(document.body, "lang-name")[2]).toHaveTextContent("css 20.00%");
|
||||||
|
expect(queryAllByTestId(document.body, "lang-progress")[2]).toHaveAttribute("width","60.00");
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|
Loading…
Add table
Reference in a new issue