Skip to content

Commit d589735

Browse files
committed
chore: Move components into sub-folders
Some components were in folders and others not, move them all for consistent project structure
1 parent 1c783e5 commit d589735

File tree

11 files changed

+144
-144
lines changed

11 files changed

+144
-144
lines changed
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React, { useState, useEffect } from 'react';
2-
import styles from './Contributors.module.css';
2+
import styles from './styles.module.css';
33

44
const REPOS = [
55
'sparkison/m3u-editor',
File renamed without changes.

src/components/DownloadBadge.js

Lines changed: 0 additions & 40 deletions
This file was deleted.
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import React, { useEffect, useState } from 'react';
2+
import styles from './styles.module.css';
3+
4+
export default function DownloadBadge() {
5+
const [downloadsText, setDownloadsText] = useState('Loading...');
6+
7+
useEffect(() => {
8+
// Fetch from shields.io JSON endpoint (no CORS issues)
9+
fetch('https://img.shields.io/docker/pulls/sparkison/m3u-editor.json')
10+
.then((r) => {
11+
if (!r.ok) throw new Error('Failed to fetch');
12+
return r.json();
13+
})
14+
.then((data) => {
15+
if (data && data.value) {
16+
// shields.io returns the value already formatted (e.g., "178k")
17+
// If you want to show the exact number with "+", you can parse it
18+
setDownloadsText(`${data.value}+`);
19+
}
20+
})
21+
.catch(() => {
22+
// Fallback to hardcoded value (update periodically)
23+
setDownloadsText('100,000+');
24+
});
25+
}, []);
26+
27+
return (
28+
<a
29+
href="https://hub.docker.com/r/sparkison/m3u-editor"
30+
target="_blank"
31+
rel="noopener noreferrer"
32+
className={styles.downloadBadge}
33+
role="status"
34+
aria-live="polite"
35+
>
36+
<span className={styles.emoji} aria-hidden="true">🚀</span>
37+
{downloadsText} Downloads
38+
</a>
39+
);
40+
}
File renamed without changes.

src/components/DownloadStat.js

Lines changed: 0 additions & 15 deletions
This file was deleted.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import React from 'react';
2+
import styles from './styles.module.css';
3+
4+
// You can update this number or fetch dynamically if needed
5+
const DOWNLOADS = '120,000+';
6+
7+
// Removed, replaced by CornerStat design in hero
8+
return (
9+
<div className={styles.statContainer}>
10+
<span className={styles.emoji} aria-hidden="true">🚀</span>
11+
<span className={styles.number}>{DOWNLOADS}</span>
12+
<span className={styles.label}>Downloads</span>
13+
</div>
14+
);
15+
}
File renamed without changes.

src/components/ScreenshotsCarousel.js

Lines changed: 0 additions & 88 deletions
This file was deleted.
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import React, { useState, useEffect } from 'react';
2+
import { createPortal } from 'react-dom';
3+
import { Swiper, SwiperSlide } from 'swiper/react';
4+
import { Navigation, Pagination, A11y } from 'swiper/modules';
5+
import 'swiper/css';
6+
import 'swiper/css/navigation';
7+
import 'swiper/css/pagination';
8+
import styles from './styles.module.css';
9+
import useBaseUrl from '@docusaurus/useBaseUrl';
10+
11+
import screenshots from '../../data/screenshots';
12+
13+
export default function ScreenshotsCarousel() {
14+
const [lightbox, setLightbox] = useState({ open: false, src: '', alt: '' });
15+
const [visibleScreenshots, setVisibleScreenshots] = useState(screenshots || []);
16+
const baseUrl = useBaseUrl('');
17+
18+
useEffect(() => {
19+
setVisibleScreenshots(screenshots || []);
20+
}, [screenshots]);
21+
22+
useEffect(() => {
23+
// Prevent body scroll when lightbox is open
24+
if (lightbox.open) {
25+
document.body.style.overflow = 'hidden';
26+
} else {
27+
document.body.style.overflow = '';
28+
}
29+
return () => {
30+
document.body.style.overflow = '';
31+
};
32+
}, [lightbox.open]);
33+
34+
const openLightbox = (src, alt) => setLightbox({ open: true, src, alt });
35+
const closeLightbox = () => setLightbox({ open: false, src: '', alt: '' });
36+
37+
const handleImageError = (src) => {
38+
setVisibleScreenshots((prev) => prev.filter((s) => s.src !== src));
39+
};
40+
41+
return (
42+
<>
43+
{visibleScreenshots.length > 0 ? (
44+
<Swiper
45+
modules={[Navigation, Pagination, A11y]}
46+
spaceBetween={16}
47+
slidesPerView={1}
48+
navigation
49+
pagination={{ clickable: true }}
50+
breakpoints={{
51+
600: { slidesPerView: 2 },
52+
900: { slidesPerView: 3 },
53+
}}
54+
style={{ paddingBottom: 32 }}
55+
className={styles.carousel}
56+
>
57+
{visibleScreenshots.map((img) => {
58+
const src = `${baseUrl.replace(/\/$/, '')}${img.src}`;
59+
return (
60+
<SwiperSlide key={src}>
61+
<img
62+
src={src}
63+
alt={img.alt}
64+
className={styles.screenshotImg}
65+
onError={() => handleImageError(img.src)}
66+
onClick={() => openLightbox(src, img.alt)}
67+
tabIndex={0}
68+
role="button"
69+
aria-label={`Expand ${img.alt}`}
70+
onKeyDown={(e) => { if (e.key === 'Enter') openLightbox(src, img.alt); }}
71+
/>
72+
</SwiperSlide>
73+
);
74+
})}
75+
</Swiper>
76+
) : (
77+
<div style={{ padding: '2rem 1rem', textAlign: 'center', color: '#cbd5e1' }}>No screenshots available.</div>
78+
)}
79+
{lightbox.open && createPortal(
80+
<div className={styles.lightboxOverlay} onClick={closeLightbox} role="dialog" aria-modal="true">
81+
<button className={styles.lightboxClose} onClick={closeLightbox} aria-label="Close">&times;</button>
82+
<img src={lightbox.src} alt={lightbox.alt} className={styles.lightboxImg} />
83+
</div>,
84+
document.body
85+
)}
86+
</>
87+
);
88+
}

0 commit comments

Comments
 (0)