@stats-organization/github-readme-stats-core 2.1.3 → 2.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. package/build/api/gist.d.ts.map +1 -1
  2. package/build/api/gist.js +34 -1
  3. package/build/api/index.d.ts.map +1 -1
  4. package/build/api/index.js +18 -1
  5. package/build/api/pin.d.ts.map +1 -1
  6. package/build/api/pin.js +17 -1
  7. package/build/api/top-langs.d.ts.map +1 -1
  8. package/build/api/top-langs.js +36 -5
  9. package/build/api/wakatime.d.ts.map +1 -1
  10. package/build/api/wakatime.js +34 -1
  11. package/build/calculateRank.d.ts +12 -11
  12. package/build/calculateRank.d.ts.map +1 -1
  13. package/build/calculateRank.js +18 -18
  14. package/build/cards/gist.d.ts.map +1 -1
  15. package/build/cards/gist.js +0 -1
  16. package/build/cards/repo.d.ts.map +1 -1
  17. package/build/cards/repo.js +29 -21
  18. package/build/cards/stats.d.ts +4 -2
  19. package/build/cards/stats.d.ts.map +1 -1
  20. package/build/cards/stats.js +21 -9
  21. package/build/cards/top-languages.d.ts.map +1 -1
  22. package/build/cards/top-languages.js +39 -12
  23. package/build/cards/wakatime.d.ts.map +1 -1
  24. package/build/cards/wakatime.js +29 -9
  25. package/build/common/Card.d.ts +54 -60
  26. package/build/common/Card.d.ts.map +1 -1
  27. package/build/common/Card.js +88 -61
  28. package/build/common/I18n.d.ts +13 -14
  29. package/build/common/I18n.d.ts.map +1 -1
  30. package/build/common/I18n.js +13 -12
  31. package/build/common/color.d.ts +56 -32
  32. package/build/common/color.d.ts.map +1 -1
  33. package/build/common/color.js +87 -50
  34. package/build/common/config.d.ts +18 -2
  35. package/build/common/config.d.ts.map +1 -1
  36. package/build/common/config.js +17 -26
  37. package/build/common/constants.d.ts +2 -2
  38. package/build/common/constants.d.ts.map +1 -1
  39. package/build/common/constants.js +5 -3
  40. package/build/common/error.d.ts +36 -29
  41. package/build/common/error.d.ts.map +1 -1
  42. package/build/common/error.js +14 -10
  43. package/build/common/fmt.d.ts +14 -13
  44. package/build/common/fmt.d.ts.map +1 -1
  45. package/build/common/fmt.js +19 -15
  46. package/build/common/html.d.ts +2 -4
  47. package/build/common/html.d.ts.map +1 -1
  48. package/build/common/html.js +2 -6
  49. package/build/common/http.d.ts +13 -4
  50. package/build/common/http.d.ts.map +1 -1
  51. package/build/common/http.js +3 -4
  52. package/build/common/icons.d.ts +22 -21
  53. package/build/common/icons.d.ts.map +1 -1
  54. package/build/common/icons.js +4 -5
  55. package/build/common/languageColors.json +9 -1
  56. package/build/common/ops.d.ts +30 -29
  57. package/build/common/ops.d.ts.map +1 -1
  58. package/build/common/ops.js +32 -38
  59. package/build/common/render.d.ts +115 -111
  60. package/build/common/render.d.ts.map +1 -1
  61. package/build/common/render.js +138 -88
  62. package/build/common/retryer.d.ts +24 -13
  63. package/build/common/retryer.d.ts.map +1 -1
  64. package/build/common/retryer.js +22 -30
  65. package/build/fetchers/gist.d.ts +6 -21
  66. package/build/fetchers/gist.d.ts.map +1 -1
  67. package/build/fetchers/gist.js +30 -36
  68. package/build/fetchers/repo.d.ts.map +1 -1
  69. package/build/fetchers/repo.js +4 -7
  70. package/build/fetchers/stats.d.ts.map +1 -1
  71. package/build/fetchers/stats.js +3 -7
  72. package/build/fetchers/top-languages.d.ts.map +1 -1
  73. package/build/fetchers/top-languages.js +0 -1
  74. package/build/fetchers/types.d.ts +126 -0
  75. package/build/fetchers/types.d.ts.map +1 -0
  76. package/build/fetchers/types.js +1 -0
  77. package/build/fetchers/wakatime.d.ts.map +1 -1
  78. package/build/fetchers/wakatime.js +0 -1
  79. package/build/translations.d.ts +627 -24
  80. package/build/translations.d.ts.map +1 -1
  81. package/build/translations.js +7 -8
  82. package/package.json +4 -4
  83. package/src/_emoji-name-map.d.ts +10 -0
  84. package/src/api/gist.js +36 -2
  85. package/src/api/index.js +19 -2
  86. package/src/api/pin.js +18 -2
  87. package/src/api/top-langs.js +38 -6
  88. package/src/api/wakatime.js +36 -2
  89. package/src/{calculateRank.js → calculateRank.ts} +29 -19
  90. package/src/cards/gist.js +0 -2
  91. package/src/cards/repo.js +30 -22
  92. package/src/cards/stats.js +22 -10
  93. package/src/cards/top-languages.js +49 -13
  94. package/src/cards/wakatime.js +33 -10
  95. package/src/common/Card.ts +338 -0
  96. package/src/common/I18n.ts +49 -0
  97. package/src/common/color.ts +212 -0
  98. package/src/common/config.ts +83 -0
  99. package/src/common/constants.ts +10 -0
  100. package/src/common/{error.js → error.ts} +20 -14
  101. package/src/common/{fmt.js → fmt.ts} +27 -19
  102. package/src/common/{html.js → html.ts} +3 -8
  103. package/src/common/http.ts +31 -0
  104. package/src/common/{icons.js → icons.ts} +9 -7
  105. package/src/common/languageColors.json +9 -1
  106. package/src/common/{ops.js → ops.ts} +48 -52
  107. package/src/common/{render.js → render.ts} +208 -98
  108. package/src/common/retryer.ts +125 -0
  109. package/src/fetchers/gist.ts +136 -0
  110. package/src/fetchers/repo.js +4 -8
  111. package/src/fetchers/stats.js +3 -8
  112. package/src/fetchers/top-languages.js +0 -2
  113. package/src/fetchers/{types.d.ts → types.ts} +1 -1
  114. package/src/fetchers/wakatime.js +0 -2
  115. package/src/{translations.js → translations.ts} +14 -10
  116. package/src/common/Card.js +0 -275
  117. package/src/common/I18n.js +0 -42
  118. package/src/common/color.js +0 -145
  119. package/src/common/config.js +0 -78
  120. package/src/common/constants.js +0 -9
  121. package/src/common/http.js +0 -21
  122. package/src/common/retryer.js +0 -104
  123. package/src/fetchers/gist.js +0 -114
@@ -0,0 +1,125 @@
1
+ import type { AxiosResponse } from "axios";
2
+
3
+ import { getConfig } from "./config.js";
4
+ import { CustomError } from "./error.js";
5
+ import { logger } from "./log.js";
6
+
7
+ /**
8
+ * Error-detection fields the retryer inspects to detect rate-limiting and credential failures.
9
+ * Every fetcher's payload is intersected with
10
+ * this, so the retryer can read `errors`/`message` regardless of the payload's own shape.
11
+ */
12
+ interface ResponseErrors {
13
+ errors?: Array<{ type?: string; message?: string }>;
14
+ message?: string;
15
+ }
16
+
17
+ /**
18
+ * Returns a random integer from 0 (inclusive) to `max` (exclusive).
19
+ *
20
+ * The value is generated using `Math.random()` and uniformly distributed
21
+ * across the range.
22
+ *
23
+ * @param max The upper bound (exclusive). Must be a positive number.
24
+ *
25
+ * @returns A random integer `n` such that `0 <= n < max`.
26
+ */
27
+ function getRandomInt(max: number): number {
28
+ return Math.floor(Math.random() * max);
29
+ }
30
+
31
+ /**
32
+ * A fetcher's Axios response. `TData` is the shape of `response.data`,
33
+ * which is intersected with {@link ResponseErrors} so the retryer can inspect
34
+ * `errors`/`message`.
35
+ * Defaults to `unknown` (error fields only) for callers that don't care about the payload.
36
+ */
37
+ type FetcherResponse<TData = unknown> = AxiosResponse<TData & ResponseErrors>;
38
+
39
+ type FetcherFunction<TData = unknown> = (
40
+ variables: Record<string, unknown>,
41
+ token: string,
42
+ retriesForTests?: number,
43
+ ) => Promise<FetcherResponse<TData>>;
44
+
45
+ /**
46
+ * Try to execute the fetcher function until it succeeds or the max number of retries is reached.
47
+ *
48
+ * @template TData Shape of `response.data` returned by the fetcher.
49
+ * @param fetcher The fetcher function.
50
+ * @param variables Object with arguments to pass to the fetcher function.
51
+ * @param pat Optional PAT override.
52
+ * @returns The response from the fetcher function.
53
+ */
54
+ const retryer = async <TData = unknown>(
55
+ fetcher: FetcherFunction<TData>,
56
+ variables: Record<string, unknown>,
57
+ pat: string | null = null,
58
+ ): Promise<FetcherResponse<TData>> => {
59
+ const PATs = pat
60
+ ? [{ name: "user PAT from database", value: pat }]
61
+ : getConfig().pats;
62
+
63
+ if (!PATs.length) {
64
+ throw new CustomError("No GitHub API tokens found", CustomError.NO_TOKENS);
65
+ }
66
+ const startPAT = getRandomInt(PATs.length);
67
+
68
+ for (let retries = 0; retries < PATs.length; retries++) {
69
+ const currentPAT = PATs[(startPAT + retries) % PATs.length];
70
+ if (!currentPAT) {
71
+ continue;
72
+ }
73
+
74
+ try {
75
+ const response = await fetcher(
76
+ variables,
77
+ currentPAT.value,
78
+ // used in tests for faking rate limit
79
+ retries,
80
+ );
81
+
82
+ // react on both type and message-based rate-limit signals.
83
+ // https://github.com/anuraghazra/github-readme-stats/issues/4425
84
+ const errors = response.data.errors;
85
+ const errorType = errors?.[0]?.type;
86
+ const errorMsg = errors?.[0]?.message ?? "";
87
+ const isRateLimited =
88
+ (!!errors && errorType === "RATE_LIMITED") ||
89
+ /rate limit/i.test(errorMsg);
90
+
91
+ if (isRateLimited) {
92
+ logger.log(`${currentPAT.name} Failed due to rate limiting`);
93
+ } else {
94
+ return response;
95
+ }
96
+ } catch (err) {
97
+ const e = err as { response?: FetcherResponse<TData> };
98
+
99
+ // network/unexpected error → let caller treat as failure
100
+ if (!e.response) {
101
+ throw err;
102
+ }
103
+
104
+ // also checking for bad credentials if any tokens gets invalidated
105
+ const message = e.response.data.message;
106
+ const isBadCredential = message === "Bad credentials";
107
+ const isAccountSuspended =
108
+ message === "Sorry. Your account was suspended.";
109
+
110
+ if (isBadCredential || isAccountSuspended) {
111
+ logger.log(`${currentPAT.name} Failed due to bad credentials`);
112
+ } else {
113
+ // HTTP error with a response → return it for caller-side handling
114
+ return e.response;
115
+ }
116
+ }
117
+ }
118
+
119
+ throw new CustomError(
120
+ "Downtime due to GitHub API rate limiting",
121
+ CustomError.MAX_RETRY,
122
+ );
123
+ };
124
+
125
+ export { retryer };
@@ -0,0 +1,136 @@
1
+ import type { AxiosResponse } from "axios";
2
+
3
+ import { MissingParamError } from "../common/error.js";
4
+ import { request } from "../common/http.js";
5
+ import { retryer } from "../common/retryer.js";
6
+
7
+ import type { GistData } from "./types.js";
8
+
9
+ const QUERY = `
10
+ query gistInfo($gistName: String!) {
11
+ viewer {
12
+ gist(name: $gistName) {
13
+ description
14
+ owner {
15
+ login
16
+ }
17
+ stargazerCount
18
+ forks {
19
+ totalCount
20
+ }
21
+ files {
22
+ name
23
+ language {
24
+ name
25
+ }
26
+ size
27
+ }
28
+ }
29
+ }
30
+ }
31
+ `;
32
+
33
+ /**
34
+ * Gist data fetcher.
35
+ *
36
+ * @param variables Fetcher variables.
37
+ * @param token GitHub token.
38
+ * @returns The response.
39
+ */
40
+ const fetcher = (
41
+ variables: Record<string, unknown>,
42
+ token: string,
43
+ ): Promise<AxiosResponse> => {
44
+ return request(
45
+ { query: QUERY, variables },
46
+ { Authorization: `token ${token}` },
47
+ );
48
+ };
49
+
50
+ /** A single file within a gist. */
51
+ interface GistFile {
52
+ name: string;
53
+ language: { name: string } | null;
54
+ size: number;
55
+ }
56
+
57
+ /** Shape of `response.data` returned by the gist GraphQL query. */
58
+ interface GistQueryResponse {
59
+ data: {
60
+ viewer: {
61
+ gist: {
62
+ description: string | null;
63
+ owner: { login: string };
64
+ stargazerCount: number;
65
+ forks: { totalCount: number };
66
+ files: Array<GistFile>;
67
+ } | null;
68
+ };
69
+ };
70
+ }
71
+
72
+ /**
73
+ * This function calculates the primary language of a gist by files size.
74
+ *
75
+ * @param files Files.
76
+ * @returns Primary language, or `null` when no file has a language.
77
+ */
78
+ const calculatePrimaryLanguage = (files: Array<GistFile>): string | null => {
79
+ const languages: Record<string, number> = {};
80
+
81
+ for (const file of files) {
82
+ if (file.language) {
83
+ languages[file.language.name] =
84
+ (languages[file.language.name] ?? 0) + file.size;
85
+ }
86
+ }
87
+
88
+ let primaryLanguage: string | null = null;
89
+ let maxSize = -1;
90
+ for (const [language, size] of Object.entries(languages)) {
91
+ if (size > maxSize) {
92
+ maxSize = size;
93
+ primaryLanguage = language;
94
+ }
95
+ }
96
+
97
+ return primaryLanguage;
98
+ };
99
+
100
+ /**
101
+ * Fetch GitHub gist information by given username and ID.
102
+ *
103
+ * @param id GitHub gist ID.
104
+ * @param pat Optional PAT override.
105
+ * @returns Gist data.
106
+ */
107
+ const fetchGist = async (
108
+ id: string,
109
+ pat: string | null = null,
110
+ ): Promise<GistData> => {
111
+ if (!id) {
112
+ throw new MissingParamError(["id"], "/api/gist?id=GIST_ID");
113
+ }
114
+ const res = await retryer<GistQueryResponse>(fetcher, { gistName: id }, pat);
115
+ if (res.data.errors) {
116
+ throw new Error(res.data.errors[0]?.message);
117
+ }
118
+ const gist = res.data.data.viewer.gist;
119
+ if (!gist) {
120
+ throw new Error("Gist not found");
121
+ }
122
+ const firstFile = gist.files[0];
123
+ if (!firstFile) {
124
+ throw new Error("Gist has no files");
125
+ }
126
+ return {
127
+ name: firstFile.name,
128
+ nameWithOwner: `${gist.owner.login}/${firstFile.name}`,
129
+ description: gist.description,
130
+ language: calculatePrimaryLanguage(gist.files),
131
+ starsCount: gist.stargazerCount,
132
+ forksCount: gist.forks.totalCount,
133
+ };
134
+ };
135
+
136
+ export { fetchGist };
@@ -1,5 +1,3 @@
1
- // @ts-check
2
-
3
1
  import { MissingParamError } from "../common/error.js";
4
2
  import { request } from "../common/http.js";
5
3
  import { retryer } from "../common/retryer.js";
@@ -23,9 +21,7 @@ const fetcher = (variables, token) => {
23
21
  isPrivate
24
22
  isArchived
25
23
  isTemplate
26
- stargazers {
27
- totalCount
28
- }
24
+ stargazerCount
29
25
  description
30
26
  primaryLanguage {
31
27
  color
@@ -55,7 +51,7 @@ const fetcher = (variables, token) => {
55
51
  );
56
52
  };
57
53
 
58
- const urlExample = "/api/pin?username=USERNAME&amp;repo=REPO_NAME";
54
+ const urlExample = "/api/pin?username=USERNAME&repo=REPO_NAME";
59
55
 
60
56
  /**
61
57
  * @typedef {import("./types").RepositoryData} RepositoryData Repository data.
@@ -130,7 +126,7 @@ const fetchRepo = async (
130
126
  return {
131
127
  ...repoUserStats,
132
128
  ...data.user.repository,
133
- starCount: data.user.repository.stargazers.totalCount,
129
+ starCount: data.user.repository.stargazerCount,
134
130
  };
135
131
  }
136
132
 
@@ -155,7 +151,7 @@ const fetchRepo = async (
155
151
  return {
156
152
  ...repoUserStats,
157
153
  ...data.organization.repository,
158
- starCount: data.organization.repository.stargazers.totalCount,
154
+ starCount: data.organization.repository.stargazerCount,
159
155
  };
160
156
  }
161
157
 
@@ -1,5 +1,3 @@
1
- // @ts-check
2
-
3
1
  import axios from "axios";
4
2
  import githubUsernameRegex from "github-username-regex";
5
3
 
@@ -18,9 +16,7 @@ const GRAPHQL_REPOS_FIELD = `
18
16
  totalCount
19
17
  nodes {
20
18
  name
21
- stargazers {
22
- totalCount
23
- }
19
+ stargazerCount
24
20
  }
25
21
  pageInfo {
26
22
  hasNextPage
@@ -156,10 +152,9 @@ const statsFetcher = async ({
156
152
  stats = res;
157
153
  }
158
154
 
159
- // Disable multi page fetching on public Vercel instance due to rate limits.
160
155
  fetchedPages++;
161
156
  const repoNodesWithStars = repoNodes.filter(
162
- (node) => node.stargazers.totalCount !== 0,
157
+ (node) => node.stargazerCount !== 0,
163
158
  );
164
159
 
165
160
  hasNextPage =
@@ -458,7 +453,7 @@ const fetchStats = async (
458
453
  return !repoToHide.has(data.name);
459
454
  })
460
455
  .reduce((prev, curr) => {
461
- return prev + curr.stargazers.totalCount;
456
+ return prev + curr.stargazerCount;
462
457
  }, 0);
463
458
 
464
459
  stats.rank = calculateRank({
@@ -1,5 +1,3 @@
1
- // @ts-check
2
-
3
1
  import { getConfig } from "../common/config.js";
4
2
  import { CustomError, MissingParamError } from "../common/error.js";
5
3
  import { wrapTextMultiline } from "../common/fmt.js";
@@ -13,7 +13,7 @@ export interface RepositoryData {
13
13
  isPrivate: boolean;
14
14
  isArchived: boolean;
15
15
  isTemplate: boolean;
16
- stargazers: { totalCount: number };
16
+ stargazerCount: number;
17
17
  description: string;
18
18
  primaryLanguage: {
19
19
  color: string;
@@ -1,5 +1,3 @@
1
- // @ts-check
2
-
3
1
  import axios from "axios";
4
2
 
5
3
  import { CustomError, MissingParamError } from "../common/error.js";
@@ -1,18 +1,22 @@
1
- // @ts-check
2
-
3
1
  import { encodeHTML } from "./common/html.js";
4
2
 
5
3
  /**
6
4
  * Retrieves stat card labels in the available locales.
7
5
  *
8
- * @param {object} props Function arguments.
9
- * @param {string} props.name The name of the locale.
10
- * @param {string} props.apostrophe Whether to use apostrophe or not.
11
- * @returns {object} The locales object.
6
+ * @param props Function arguments.
7
+ * @param props.name The name of the locale.
8
+ * @param props.apostrophe Whether to use apostrophe or not.
9
+ * @returns The locales object.
12
10
  *
13
11
  * @see https://www.andiamo.co.uk/resources/iso-language-codes/ for language codes.
14
12
  */
15
- const statCardLocales = ({ name, apostrophe }) => {
13
+ const statCardLocales = ({
14
+ name,
15
+ apostrophe,
16
+ }: {
17
+ name: string;
18
+ apostrophe: string;
19
+ }) => {
16
20
  const encodedName = encodeHTML(name);
17
21
  return {
18
22
  "statcard.title": {
@@ -1140,10 +1144,10 @@ const availableLocales = Object.keys(repoCardLocales["repocard.archived"]);
1140
1144
  /**
1141
1145
  * Checks whether the locale is available or not.
1142
1146
  *
1143
- * @param {string} locale The locale to check.
1144
- * @returns {boolean} Boolean specifying whether the locale is available or not.
1147
+ * @param locale The locale to check.
1148
+ * @returns Boolean specifying whether the locale is available or not.
1145
1149
  */
1146
- const isLocaleAvailable = (locale) => {
1150
+ const isLocaleAvailable = (locale: string): boolean => {
1147
1151
  return availableLocales.includes(locale.toLowerCase());
1148
1152
  };
1149
1153
 
@@ -1,275 +0,0 @@
1
- // @ts-check
2
-
3
- import { encodeHTML } from "./html.js";
4
- import { flexLayout } from "./render.js";
5
-
6
- class Card {
7
- /**
8
- * Creates a new card instance.
9
- *
10
- * @param {object} args Card arguments.
11
- * @param {number=} args.width Card width.
12
- * @param {number=} args.height Card height.
13
- * @param {number=} args.border_radius Card border radius.
14
- * @param {string=} args.customTitle Card custom title.
15
- * @param {string=} args.defaultTitle Card default title.
16
- * @param {string=} args.titlePrefixIcon Card title prefix icon.
17
- * @param {object} [args.colors={}] Card colors arguments.
18
- * @param {string=} args.colors.titleColor Card title color.
19
- * @param {string=} args.colors.textColor Card text color.
20
- * @param {string=} args.colors.iconColor Card icon color.
21
- * @param {string|string[]=} args.colors.bgColor Card background color.
22
- * @param {string=} args.colors.borderColor Card border color.
23
- */
24
- constructor({
25
- width = 100,
26
- height = 100,
27
- border_radius = 4.5,
28
- colors = {},
29
- customTitle,
30
- defaultTitle = "",
31
- titlePrefixIcon,
32
- }) {
33
- this.width = width;
34
- this.height = height;
35
-
36
- this.hideBorder = false;
37
- this.hideTitle = false;
38
-
39
- this.border_radius = border_radius;
40
-
41
- // returns theme based colors with proper overrides and defaults
42
- this.colors = colors;
43
- this.title =
44
- customTitle === undefined
45
- ? encodeHTML(defaultTitle)
46
- : encodeHTML(customTitle);
47
-
48
- this.css = "";
49
-
50
- this.paddingX = 25;
51
- this.paddingY = 35;
52
- this.titlePrefixIcon = titlePrefixIcon;
53
- this.animations = true;
54
- this.a11yTitle = "";
55
- this.a11yDesc = "";
56
- }
57
-
58
- /**
59
- * @returns {void}
60
- */
61
- disableAnimations() {
62
- this.animations = false;
63
- }
64
-
65
- /**
66
- * @param {Object} props The props object.
67
- * @param {string} props.title Accessibility title.
68
- * @param {string} props.desc Accessibility description.
69
- * @returns {void}
70
- */
71
- setAccessibilityLabel({ title, desc }) {
72
- this.a11yTitle = title;
73
- this.a11yDesc = desc;
74
- }
75
-
76
- /**
77
- * @param {string} value The CSS to add to the card.
78
- * @returns {void}
79
- */
80
- setCSS(value) {
81
- this.css = value;
82
- }
83
-
84
- /**
85
- * @param {boolean} value Whether to hide the border or not.
86
- * @returns {void}
87
- */
88
- setHideBorder(value) {
89
- this.hideBorder = value;
90
- }
91
-
92
- /**
93
- * @param {boolean} value Whether to hide the title or not.
94
- * @returns {void}
95
- */
96
- setHideTitle(value) {
97
- this.hideTitle = value;
98
- if (value) {
99
- this.height -= 30;
100
- }
101
- }
102
-
103
- /**
104
- * @param {string} text The title to set.
105
- * @returns {void}
106
- */
107
- setTitle(text) {
108
- this.title = text;
109
- }
110
-
111
- /**
112
- * @returns {string} The rendered card title.
113
- */
114
- renderTitle() {
115
- const titleText = `
116
- <text
117
- x="0"
118
- y="0"
119
- class="header"
120
- data-testid="header"
121
- >${this.title}</text>
122
- `;
123
-
124
- const prefixIcon = `
125
- <svg
126
- class="icon"
127
- x="0"
128
- y="-13"
129
- viewBox="0 0 16 16"
130
- version="1.1"
131
- width="16"
132
- height="16"
133
- >
134
- ${this.titlePrefixIcon}
135
- </svg>
136
- `;
137
- return `
138
- <g
139
- data-testid="card-title"
140
- transform="translate(${this.paddingX}, ${this.paddingY})"
141
- >
142
- ${flexLayout({
143
- items: [this.titlePrefixIcon ? prefixIcon : "", titleText],
144
- gap: 25,
145
- }).join("")}
146
- </g>
147
- `;
148
- }
149
-
150
- /**
151
- * @returns {string} The rendered card gradient.
152
- */
153
- renderGradient() {
154
- if (typeof this.colors.bgColor !== "object") {
155
- return "";
156
- }
157
-
158
- const gradients = this.colors.bgColor.slice(1);
159
- return typeof this.colors.bgColor === "object"
160
- ? `
161
- <defs>
162
- <linearGradient
163
- id="gradient"
164
- gradientTransform="rotate(${this.colors.bgColor[0]})"
165
- gradientUnits="userSpaceOnUse"
166
- >
167
- ${gradients.map((grad, index) => {
168
- let offset = (index * 100) / (gradients.length - 1);
169
- return `<stop offset="${offset}%" stop-color="#${grad}" />`;
170
- })}
171
- </linearGradient>
172
- </defs>
173
- `
174
- : "";
175
- }
176
-
177
- /**
178
- * Retrieves css animations for a card.
179
- *
180
- * @returns {string} Animation css.
181
- */
182
- getAnimations = () => {
183
- return `
184
- /* Animations */
185
- @keyframes scaleInAnimation {
186
- from {
187
- transform: translate(-5px, 5px) scale(0);
188
- }
189
- to {
190
- transform: translate(-5px, 5px) scale(1);
191
- }
192
- }
193
- @keyframes fadeInAnimation {
194
- from {
195
- opacity: 0;
196
- }
197
- to {
198
- opacity: 1;
199
- }
200
- }
201
- `;
202
- };
203
-
204
- /**
205
- * @param {string} body The inner body of the card.
206
- * @returns {string} The rendered card.
207
- */
208
- render(body) {
209
- return `
210
- <svg
211
- width="${this.width}"
212
- height="${this.height}"
213
- viewBox="0 0 ${this.width} ${this.height}"
214
- fill="none"
215
- xmlns="http://www.w3.org/2000/svg"
216
- role="img"
217
- aria-labelledby="descId"
218
- >
219
- <title id="titleId">${this.a11yTitle}</title>
220
- <desc id="descId">${this.a11yDesc}</desc>
221
- <style>
222
- .header {
223
- font: 600 18px 'Segoe UI', Ubuntu, Sans-Serif;
224
- fill: ${this.colors.titleColor};
225
- animation: fadeInAnimation 0.8s ease-in-out forwards;
226
- }
227
- @supports(-moz-appearance: auto) {
228
- /* Selector detects Firefox */
229
- .header { font-size: 15.5px; }
230
- }
231
- ${this.css}
232
-
233
- ${this.getAnimations()}
234
- ${
235
- this.animations === false
236
- ? `* { animation-duration: 0s !important; animation-delay: 0s !important; }`
237
- : ""
238
- }
239
- </style>
240
-
241
- ${this.renderGradient()}
242
-
243
- <rect
244
- data-testid="card-bg"
245
- x="0.5"
246
- y="0.5"
247
- rx="${this.border_radius}"
248
- height="99%"
249
- stroke="${this.colors.borderColor}"
250
- width="${this.width - 1}"
251
- fill="${
252
- typeof this.colors.bgColor === "object"
253
- ? "url(#gradient)"
254
- : this.colors.bgColor
255
- }"
256
- stroke-opacity="${this.hideBorder ? 0 : 1}"
257
- />
258
-
259
- ${this.hideTitle ? "" : this.renderTitle()}
260
-
261
- <g
262
- data-testid="main-card-body"
263
- transform="translate(0, ${
264
- this.hideTitle ? this.paddingX : this.paddingY + 20
265
- })"
266
- >
267
- ${body}
268
- </g>
269
- </svg>
270
- `;
271
- }
272
- }
273
-
274
- export { Card };
275
- export default Card;