@yatoday/astro-ui 0.0.2

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 (56) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +2 -0
  3. package/astro.d.ts +31 -0
  4. package/astro.js +19 -0
  5. package/components/Analytics/Analytics.astro +15 -0
  6. package/components/Analytics/AnalyticsGoogle.astro +17 -0
  7. package/components/Analytics/types.ts +6 -0
  8. package/components/Background/Background.astro +9 -0
  9. package/components/Background/Background.svelte +13 -0
  10. package/components/Background/types.ts +9 -0
  11. package/components/Card0/Card0.astro +23 -0
  12. package/components/Card0/card0.ts +6 -0
  13. package/components/Card0/types.ts +6 -0
  14. package/components/ConditionalWrapper/ConditionalWrapper.astro +15 -0
  15. package/components/ConditionalWrapper/ConditionalWrapper.svelte +18 -0
  16. package/components/ConditionalWrapper/types.ts +10 -0
  17. package/components/DarkMode/DarkMode.astro +77 -0
  18. package/components/DarkMode/types.ts +7 -0
  19. package/components/Layout/Layout.astro +35 -0
  20. package/components/Layout/types.ts +13 -0
  21. package/components/Metadata/AstroSeo.astro +40 -0
  22. package/components/Metadata/Metadata.astro +60 -0
  23. package/components/Metadata/types.ts +186 -0
  24. package/components/Metadata/utils/buildTags.ts +380 -0
  25. package/components/SeoAnalytics/SeoAnalytics.astro +15 -0
  26. package/components/SeoAnalytics/types.ts +3 -0
  27. package/components/SeoAnalyticsGoogleProvider/SeoAnalyticsGoogleProvider.astro +17 -0
  28. package/components/SeoAnalyticsGoogleProvider/types.ts +4 -0
  29. package/components/SeoAstroSeo/SeoAstroSeo.astro +40 -0
  30. package/components/SeoAstroSeo/SeoAstroSeo.svelte +3 -0
  31. package/components/SeoAstroSeo/types.ts +146 -0
  32. package/components/SeoMetadata/SeoMetadata.astro +8 -0
  33. package/components/SeoMetadata/types.ts +38 -0
  34. package/components/SeoSiteVerification/SeoSiteVerification.astro +6 -0
  35. package/components/SeoSiteVerification/types.ts +3 -0
  36. package/components/SiteVerification/SiteVerification.astro +6 -0
  37. package/components/SiteVerification/types.ts +1 -0
  38. package/components/WidgetWrapper/WidgetWrapper.astro +31 -0
  39. package/components/WidgetWrapper/WidgetWrapper.ts +6 -0
  40. package/components/WidgetWrapper/types.ts +10 -0
  41. package/index.d.ts +180 -0
  42. package/index.js +6 -0
  43. package/package.json +99 -0
  44. package/svelte.d.ts +32 -0
  45. package/svelte.js +19 -0
  46. package/utils/buildTags.ts +380 -0
  47. package/utils/css.ts +39 -0
  48. package/utils/images-optimization.ts +356 -0
  49. package/utils/images.ts +138 -0
  50. package/utils/index.ts +5 -0
  51. package/utils/permalinks.ts +38 -0
  52. package/utils/utils.ts +58 -0
  53. package/vendor-config/index.ts +115 -0
  54. package/vendor-config/types.d.ts +10 -0
  55. package/vendor-config/utils/configBuilder.ts +206 -0
  56. package/vendor-config/utils/loadConfig.ts +16 -0
@@ -0,0 +1,356 @@
1
+ import type { ImageMetadata } from 'astro';
2
+ import type { HTMLAttributes } from 'astro/types';
3
+ import { getImage } from 'astro:assets';
4
+ import { parseUrl, transformUrl } from 'unpic';
5
+
6
+ type Layout = 'fixed' | 'constrained' | 'fullWidth' | 'cover' | 'responsive' | 'contained';
7
+
8
+ export interface ImageProps extends Omit<HTMLAttributes<'img'>, 'src'> {
9
+ src?: string | ImageMetadata | null;
10
+ width?: string | number | null;
11
+ height?: string | number | null;
12
+ alt?: string | null;
13
+ loading?: 'eager' | 'lazy' | null;
14
+ decoding?: 'sync' | 'async' | 'auto' | null;
15
+ style?: string;
16
+ srcset?: string | null;
17
+ sizes?: string | null;
18
+ fetchpriority?: 'high' | 'low' | 'auto' | null;
19
+
20
+ layout?: Layout;
21
+ widths?: number[] | null;
22
+ aspectRatio?: string | number | null;
23
+ objectPosition?: string;
24
+
25
+ format?: string;
26
+ }
27
+
28
+ export type ImagesOptimizer = (
29
+ image: ImageMetadata | string,
30
+ breakpoints: number[],
31
+ width?: number,
32
+ height?: number,
33
+ format?: string
34
+ ) => Promise<Array<{ src: string; width: number }>>;
35
+
36
+ /* ******* */
37
+ const config = {
38
+ // FIXME: Use this when image.width is minor than deviceSizes
39
+ imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
40
+
41
+ deviceSizes: [
42
+ 640, // older and lower-end phones
43
+ 750, // iPhone 6-8
44
+ 828, // iPhone XR/11
45
+ 960, // older horizontal phones
46
+ 1080, // iPhone 6-8 Plus
47
+ 1280, // 720p
48
+ 1668, // Various iPads
49
+ 1920, // 1080p
50
+ 2048, // QXGA
51
+ 2560, // WQXGA
52
+ 3200, // QHD+
53
+ 3840, // 4K
54
+ 4480, // 4.5K
55
+ 5120, // 5K
56
+ 6016, // 6K
57
+ ],
58
+
59
+ formats: ['image/webp'],
60
+ };
61
+
62
+ const computeHeight = (width: number, aspectRatio: number) => {
63
+ return Math.floor(width / aspectRatio);
64
+ };
65
+
66
+ const parseAspectRatio = (aspectRatio: number | string | null | undefined): number | undefined => {
67
+ if (typeof aspectRatio === 'number') {
68
+ return aspectRatio;
69
+ }
70
+
71
+ if (typeof aspectRatio === 'string') {
72
+ const match = aspectRatio.match(/(\d+)\s*[/:]\s*(\d+)/);
73
+
74
+ if (match) {
75
+ const [, num, den] = match.map(Number);
76
+ if (den && !isNaN(num)) {
77
+ return num / den;
78
+ }
79
+ } else {
80
+ const numericValue = parseFloat(aspectRatio);
81
+ if (!isNaN(numericValue)) {
82
+ return numericValue;
83
+ }
84
+ }
85
+ }
86
+
87
+ return undefined;
88
+ };
89
+
90
+ /**
91
+ * Gets the `sizes` attribute for an image, based on the layout and width
92
+ */
93
+ export const getSizes = (width?: number, layout?: Layout): string | undefined => {
94
+ if (!width || !layout) {
95
+ return undefined;
96
+ }
97
+ switch (layout) {
98
+ // If screen is wider than the max size, image width is the max size,
99
+ // otherwise it's the width of the screen
100
+ case 'constrained':
101
+ return `(min-width: ${width}px) ${width}px, 100vw`;
102
+
103
+ // Image is always the same width, whatever the size of the screen
104
+ case 'fixed':
105
+ return `${width}px`;
106
+
107
+ // Image is always the width of the screen
108
+ case 'fullWidth':
109
+ return '100vw';
110
+
111
+ default:
112
+ return undefined;
113
+ }
114
+ };
115
+
116
+ const pixelate = (value?: number) => (value || value === 0 ? `${value}px` : undefined);
117
+
118
+ const getStyle = ({
119
+ width,
120
+ height,
121
+ aspectRatio,
122
+ layout,
123
+ objectFit = 'cover',
124
+ objectPosition = 'center',
125
+ background,
126
+ }: {
127
+ width?: number;
128
+ height?: number;
129
+ aspectRatio?: number;
130
+ objectFit?: string;
131
+ objectPosition?: string;
132
+ layout?: string;
133
+ background?: string;
134
+ }) => {
135
+ const styleEntries: Array<[prop: string, value: string | undefined]> = [
136
+ ['object-fit', objectFit],
137
+ ['object-position', objectPosition],
138
+ ];
139
+
140
+ // If background is a URL, set it to cover the image and not repeat
141
+ if (background?.startsWith('https:') || background?.startsWith('http:') || background?.startsWith('data:')) {
142
+ styleEntries.push(['background-image', `url(${background})`]);
143
+ styleEntries.push(['background-size', 'cover']);
144
+ styleEntries.push(['background-repeat', 'no-repeat']);
145
+ } else {
146
+ styleEntries.push(['background', background]);
147
+ }
148
+ if (layout === 'fixed') {
149
+ styleEntries.push(['width', pixelate(width)]);
150
+ styleEntries.push(['height', pixelate(height)]);
151
+ styleEntries.push(['object-position', 'top left']);
152
+ }
153
+ if (layout === 'constrained') {
154
+ styleEntries.push(['max-width', pixelate(width)]);
155
+ styleEntries.push(['max-height', pixelate(height)]);
156
+ styleEntries.push(['aspect-ratio', aspectRatio ? `${aspectRatio}` : undefined]);
157
+ styleEntries.push(['width', '100%']);
158
+ }
159
+ if (layout === 'fullWidth') {
160
+ styleEntries.push(['width', '100%']);
161
+ styleEntries.push(['aspect-ratio', aspectRatio ? `${aspectRatio}` : undefined]);
162
+ styleEntries.push(['height', pixelate(height)]);
163
+ }
164
+ if (layout === 'responsive') {
165
+ styleEntries.push(['width', '100%']);
166
+ styleEntries.push(['height', 'auto']);
167
+ styleEntries.push(['aspect-ratio', aspectRatio ? `${aspectRatio}` : undefined]);
168
+ }
169
+ if (layout === 'contained') {
170
+ styleEntries.push(['max-width', '100%']);
171
+ styleEntries.push(['max-height', '100%']);
172
+ styleEntries.push(['object-fit', 'contain']);
173
+ styleEntries.push(['aspect-ratio', aspectRatio ? `${aspectRatio}` : undefined]);
174
+ }
175
+ if (layout === 'cover') {
176
+ styleEntries.push(['max-width', '100%']);
177
+ styleEntries.push(['max-height', '100%']);
178
+ }
179
+
180
+ const styles = Object.fromEntries(styleEntries.filter(([, value]) => value));
181
+
182
+ return Object.entries(styles)
183
+ .map(([key, value]) => `${key}: ${value};`)
184
+ .join(' ');
185
+ };
186
+
187
+ const getBreakpoints = ({
188
+ width,
189
+ breakpoints,
190
+ layout,
191
+ }: {
192
+ width?: number;
193
+ breakpoints?: number[];
194
+ layout: Layout;
195
+ }): number[] => {
196
+ if (layout === 'fullWidth' || layout === 'cover' || layout === 'responsive' || layout === 'contained') {
197
+ return breakpoints || config.deviceSizes;
198
+ }
199
+ if (!width) {
200
+ return [];
201
+ }
202
+ const doubleWidth = width * 2;
203
+ if (layout === 'fixed') {
204
+ return [width, doubleWidth];
205
+ }
206
+ if (layout === 'constrained') {
207
+ return [
208
+ // Always include the image at 1x and 2x the specified width
209
+ width,
210
+ doubleWidth,
211
+ // Filter out any resolutions that are larger than the double-res image
212
+ ...(breakpoints || config.deviceSizes).filter((w) => w < doubleWidth),
213
+ ];
214
+ }
215
+
216
+ return [];
217
+ };
218
+
219
+ /* ** */
220
+ export const astroAsseetsOptimizer: ImagesOptimizer = async (
221
+ image,
222
+ breakpoints,
223
+ _width,
224
+ _height,
225
+ format = undefined
226
+ ) => {
227
+ if (!image) {
228
+ return [];
229
+ }
230
+
231
+ return Promise.all(
232
+ breakpoints.map(async (w: number) => {
233
+ const result = await getImage({ src: image, width: w, inferSize: true, ...(format ? { format } : {}) });
234
+
235
+ return {
236
+ src: result?.src,
237
+ width: result?.attributes?.width ?? w,
238
+ height: result?.attributes?.height,
239
+ };
240
+ })
241
+ );
242
+ };
243
+
244
+ export const isUnpicCompatible = (image: string) => {
245
+ return typeof parseUrl(image) !== 'undefined';
246
+ };
247
+
248
+ /* ** */
249
+ export const unpicOptimizer: ImagesOptimizer = async (image, breakpoints, width, height, format = undefined) => {
250
+ if (!image || typeof image !== 'string') {
251
+ return [];
252
+ }
253
+
254
+ const urlParsed = parseUrl(image);
255
+ if (!urlParsed) {
256
+ return [];
257
+ }
258
+
259
+ return Promise.all(
260
+ breakpoints.map(async (w: number) => {
261
+ const _height = width && height ? computeHeight(w, width / height) : height;
262
+ const url =
263
+ transformUrl({
264
+ url: image,
265
+ width: w,
266
+ height: _height,
267
+ cdn: urlParsed.cdn,
268
+ ...(format ? { format } : {}),
269
+ }) || image;
270
+ return {
271
+ src: String(url),
272
+ width: w,
273
+ height: _height,
274
+ };
275
+ })
276
+ );
277
+ };
278
+
279
+ /* ** */
280
+ export async function getImagesOptimized(
281
+ image: ImageMetadata | string,
282
+ {
283
+ src: _,
284
+ width,
285
+ height,
286
+ sizes,
287
+ aspectRatio,
288
+ objectPosition,
289
+ widths,
290
+ layout = 'constrained',
291
+ style = '',
292
+ format,
293
+ ...rest
294
+ }: ImageProps,
295
+ transform: ImagesOptimizer = () => Promise.resolve([])
296
+ ): Promise<{ src: string; attributes: HTMLAttributes<'img'> }> {
297
+ if (typeof image !== 'string') {
298
+ width ||= Number(image.width) || undefined;
299
+ height ||= typeof width === 'number' ? computeHeight(width, image.width / image.height) : undefined;
300
+ }
301
+
302
+ width = (width && Number(width)) || undefined;
303
+ height = (height && Number(height)) || undefined;
304
+
305
+ widths ||= config.deviceSizes;
306
+ sizes ||= getSizes(Number(width) || undefined, layout);
307
+ aspectRatio = parseAspectRatio(aspectRatio);
308
+
309
+ // Calculate dimensions from aspect ratio
310
+ if (aspectRatio) {
311
+ if (width) {
312
+ if (height) {
313
+ /* empty */
314
+ } else {
315
+ height = width / aspectRatio;
316
+ }
317
+ } else if (height) {
318
+ width = Number(height * aspectRatio);
319
+ } else if (layout !== 'fullWidth') {
320
+ // Fullwidth images have 100% width, so aspectRatio is applicable
321
+ console.error('When aspectRatio is set, either width or height must also be set');
322
+ console.error('Image', image);
323
+ }
324
+ } else if (width && height) {
325
+ aspectRatio = width / height;
326
+ } else if (layout !== 'fullWidth') {
327
+ // Fullwidth images don't need dimensions
328
+ console.error('Either aspectRatio or both width and height must be set');
329
+ console.error('Image', image);
330
+ }
331
+
332
+ let breakpoints = getBreakpoints({ width, breakpoints: widths, layout });
333
+ breakpoints = [...new Set(breakpoints)].sort((a, b) => a - b);
334
+
335
+ const srcset = (await transform(image, breakpoints, Number(width) || undefined, Number(height) || undefined, format))
336
+ .map(({ src, width }) => `${src} ${width}w`)
337
+ .join(', ');
338
+
339
+ return {
340
+ src: typeof image === 'string' ? image : image.src,
341
+ attributes: {
342
+ width,
343
+ height,
344
+ srcset: srcset || undefined,
345
+ sizes,
346
+ style: `${getStyle({
347
+ width,
348
+ height,
349
+ aspectRatio,
350
+ objectPosition,
351
+ layout,
352
+ })}${style ?? ''}`,
353
+ ...rest,
354
+ },
355
+ };
356
+ }
@@ -0,0 +1,138 @@
1
+ import { astroAsseetsOptimizer, isUnpicCompatible, unpicOptimizer } from './images-optimization';
2
+
3
+ import type { OpenGraph } from '@astrolib/seo';
4
+ import type { ImageMetadata } from 'astro';
5
+
6
+ const load = async function () {
7
+ let images: Record<string, () => Promise<unknown>> | undefined;
8
+ try {
9
+ images = import.meta.glob(
10
+ '/src/assets/images/**/*.{jpeg,jpg,png,tiff,webp,gif,svg,JPEG,JPG,PNG,TIFF,WEBP,GIF,SVG}'
11
+ );
12
+ } catch (error) {
13
+ // continue regardless of error
14
+ console.error(error);
15
+ }
16
+ return images;
17
+ };
18
+
19
+ let _images: Record<string, () => Promise<unknown>> | undefined;
20
+
21
+ /** */
22
+ export const fetchLocalImages = async () => {
23
+ _images = _images || (await load());
24
+ return _images;
25
+ };
26
+
27
+ /** */
28
+ export const findImage = async (
29
+ imagePath?: string | ImageMetadata | null
30
+ ): Promise<string | ImageMetadata | undefined | null> => {
31
+ // Not string
32
+ if (typeof imagePath !== 'string') {
33
+ return imagePath;
34
+ }
35
+
36
+ // Absolute paths
37
+ if (imagePath.startsWith('http://') || imagePath.startsWith('https://') || imagePath.startsWith('/')) {
38
+ return imagePath;
39
+ }
40
+
41
+ // Relative paths or not "~/assets/"
42
+ if (!imagePath.startsWith('~/assets/images')) {
43
+ return imagePath;
44
+ }
45
+
46
+ const images = await fetchLocalImages();
47
+ const key = imagePath.replace('~/', '/src/');
48
+
49
+ return images && typeof images[key] === 'function'
50
+ ? ((await images[key]()) as { default: ImageMetadata }).default
51
+ : null;
52
+ };
53
+
54
+ /** */
55
+ export const adaptOpenGraphImages = async (
56
+ openGraph: OpenGraph = {},
57
+ astroSite: URL | undefined = new URL('')
58
+ ): Promise<OpenGraph> => {
59
+ if (!openGraph?.images?.length) {
60
+ return openGraph;
61
+ }
62
+
63
+ const images = openGraph.images;
64
+ const defaultWidth = 1200;
65
+ const defaultHeight = 626;
66
+
67
+ const adaptedImages = await Promise.all(
68
+ images.map(async (image) => {
69
+ if (image?.url) {
70
+ const resolvedImage = (await findImage(image.url)) as ImageMetadata | string | undefined;
71
+ if (!resolvedImage) {
72
+ return {
73
+ url: '',
74
+ };
75
+ }
76
+
77
+ let _image;
78
+
79
+ if (
80
+ typeof resolvedImage === 'string' &&
81
+ (resolvedImage.startsWith('http://') || resolvedImage.startsWith('https://')) &&
82
+ isUnpicCompatible(resolvedImage)
83
+ ) {
84
+ _image = (await unpicOptimizer(resolvedImage, [defaultWidth], defaultWidth, defaultHeight, 'jpg'))[0];
85
+ } else if (resolvedImage) {
86
+ const dimensions =
87
+ typeof resolvedImage !== 'string' && resolvedImage?.width <= defaultWidth
88
+ ? [resolvedImage?.width, resolvedImage?.height]
89
+ : [defaultWidth, defaultHeight];
90
+ _image = (
91
+ await astroAsseetsOptimizer(resolvedImage, [dimensions[0]], dimensions[0], dimensions[1], 'jpg')
92
+ )[0];
93
+ }
94
+
95
+ if (typeof _image === 'object') {
96
+ return {
97
+ url: 'src' in _image && typeof _image.src === 'string' ? String(new URL(_image.src, astroSite)) : '',
98
+ width: 'width' in _image && typeof _image.width === 'number' ? _image.width : undefined,
99
+ height: 'height' in _image && typeof _image.height === 'number' ? _image.height : undefined,
100
+ };
101
+ }
102
+ return {
103
+ url: '',
104
+ };
105
+ }
106
+
107
+ return {
108
+ url: '',
109
+ };
110
+ })
111
+ );
112
+
113
+ return { ...openGraph, ...(adaptedImages ? { images: adaptedImages } : {}) };
114
+ };
115
+
116
+ export const createImageSrcBySize = (src: string, size: number, format: string = ''): string | undefined => {
117
+ if (!src) {
118
+ return;
119
+ }
120
+
121
+ const imageFilename = src.split('/').pop()!;
122
+ const imageBaseName = imageFilename.split('.').slice(0, -1).join('.');
123
+ const imageExt = format || imageFilename.split('.').pop()!;
124
+
125
+ return src.replace(imageFilename, `${imageBaseName}-${size}.${imageExt}`);
126
+ };
127
+
128
+ export const getImagePathBySize = (src: string, size: number, format: string = ''): string => {
129
+ if (!src) {
130
+ return '';
131
+ }
132
+
133
+ const imageFilename = src.split('/').pop()!;
134
+ const imageBaseName = imageFilename.split('.').slice(0, -1).join('.');
135
+ const imageExt = format || imageFilename.split('.').pop()!;
136
+
137
+ return `${src.replace(imageFilename, `${imageBaseName}-${size}.${imageExt}`)}?width=${size}`;
138
+ };
package/utils/index.ts ADDED
@@ -0,0 +1,5 @@
1
+ export * from './utils.ts';
2
+ export * from './css.ts';
3
+ export * from './images.ts';
4
+ export * from './permalinks.ts';
5
+ export * from './images-optimization.ts';
@@ -0,0 +1,38 @@
1
+ import slugify from 'limax';
2
+ import {trim} from './utils';
3
+
4
+ export const trimSlash = (s: string) => trim(trim(s, '/'));
5
+ export const createPath = (...params: string[]) => {
6
+ const paths = params
7
+ .map((el) => trimSlash(el))
8
+ .filter((el) => !!el)
9
+ .join('/');
10
+ return '/' + paths + (paths ? '/' : '');
11
+ };
12
+
13
+ const BASE_PATHNAME = '/';
14
+
15
+ export const cleanSlug = (text = '') =>
16
+ trimSlash(text)
17
+ .split('/')
18
+ .map((slug: string) => slugify(slug))
19
+ .join('/');
20
+
21
+ export const getCanonical = (path = ''): string | URL => {
22
+ const url = String(new URL(path, 'https://example.com'));
23
+ if (path && url.endsWith('/')) {
24
+ return url.slice(0, -1);
25
+ }
26
+
27
+ return url;
28
+ };
29
+
30
+ export const getAsset = (path: string): string =>
31
+ '/' +
32
+ [BASE_PATHNAME, path]
33
+ .map((el) => trimSlash(el))
34
+ .filter((el) => !!el)
35
+ .join('/');
36
+
37
+ const definitivePermalink = (permalink: string): string => createPath(BASE_PATHNAME, permalink);
38
+
package/utils/utils.ts ADDED
@@ -0,0 +1,58 @@
1
+ import { I18N } from 'vendor:config';
2
+
3
+ export const formatter: Intl.DateTimeFormat = new Intl.DateTimeFormat(I18N?.language, {
4
+ year: 'numeric',
5
+ month: 'short',
6
+ day: 'numeric',
7
+ timeZone: 'UTC',
8
+ });
9
+
10
+ export const getFormattedDate = (date: Date): string => (date ? formatter.format(date) : '');
11
+
12
+ export const trim = (str = '', ch?: string) => {
13
+ let start = 0;
14
+ let end = str.length || 0;
15
+ while (start < end && str[start] === ch) {
16
+ ++start;
17
+ }
18
+ while (end > start && str[end - 1] === ch) {
19
+ --end;
20
+ }
21
+ return start > 0 || end < str.length ? str.substring(start, end) : str;
22
+ };
23
+
24
+ // Function to format a number in thousands (K) or millions (M) format depending on its value
25
+ export const toUiAmount = (amount: number) => {
26
+ if (!amount) {
27
+ return 0;
28
+ }
29
+
30
+ let value: string;
31
+
32
+ if (amount >= 1000000000) {
33
+ const formattedNumber = (amount / 1000000000).toFixed(1);
34
+ if (Number(formattedNumber) === parseInt(formattedNumber)) {
35
+ value = `${parseInt(formattedNumber)}B`;
36
+ } else {
37
+ value = `${formattedNumber}B`;
38
+ }
39
+ } else if (amount >= 1000000) {
40
+ const formattedNumber = (amount / 1000000).toFixed(1);
41
+ if (Number(formattedNumber) === parseInt(formattedNumber)) {
42
+ value = `${parseInt(formattedNumber)}M`;
43
+ } else {
44
+ value = `${formattedNumber}M`;
45
+ }
46
+ } else if (amount >= 1000) {
47
+ const formattedNumber = (amount / 1000).toFixed(1);
48
+ if (Number(formattedNumber) === parseInt(formattedNumber)) {
49
+ value = `${parseInt(formattedNumber)}K`;
50
+ } else {
51
+ value = `${formattedNumber}K`;
52
+ }
53
+ } else {
54
+ value = Number(amount).toFixed(0);
55
+ }
56
+
57
+ return value;
58
+ };