Skip to content

Commit 965c3e8

Browse files
committed
Component proposal.
1 parent 2986d4c commit 965c3e8

File tree

8 files changed

+290
-10
lines changed

8 files changed

+290
-10
lines changed

public/carousel/1.jpg

125 KB
Loading

public/carousel/2.jpg

131 KB
Loading

public/carousel/3.jpg

122 KB
Loading

public/carousel/4.jpg

146 KB
Loading

public/carousel/5.jpg

111 KB
Loading

src/components/Carousel.astro

+245
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,245 @@
1+
---
2+
interface CarouselItem {
3+
src: string;
4+
alt: string;
5+
width?: number;
6+
height?: number;
7+
}
8+
9+
interface Props {
10+
items: CarouselItem[];
11+
autoplay?: boolean;
12+
interval?: number; // milliseconds
13+
showControls?: boolean;
14+
showIndicators?: boolean;
15+
className?: string;
16+
}
17+
18+
// Default props
19+
const {
20+
items = [],
21+
autoplay = true,
22+
interval = 3000,
23+
showControls = true,
24+
showIndicators = true,
25+
className = "w-full h-full",
26+
} = Astro.props;
27+
28+
// Generate unique ID for this carousel instance
29+
const carouselId = `carousel-${Math.random().toString(36).substring(2, 11)}`;
30+
---
31+
32+
<div id={carouselId} class={`carousel relative ${className}`}>
33+
<div class="carousel-container overflow-hidden relative w-full h-full rounded-2xl shadow-xl">
34+
<div class="carousel-track flex transition-transform duration-500 ease-in-out h-full">
35+
{items.map((item, index) => (
36+
<div class="carousel-item flex-shrink-0 w-full h-full flex items-center justify-center" data-index={index}>
37+
<img
38+
src={item.src}
39+
alt={item.alt}
40+
width={item.width}
41+
height={item.height}
42+
class="object-cover h-full block md:w-full"
43+
/>
44+
</div>
45+
))}
46+
</div>
47+
</div>
48+
49+
{showControls && (
50+
<div class="carousel-controls">
51+
<button
52+
class="carousel-control prev absolute top-1/2 left-2 transform -translate-y-1/2 bg-white/80 rounded-full p-2 shadow-md hover:bg-white"
53+
aria-label="Previous slide"
54+
>
55+
<i class="fas fa-chevron-left"></i>
56+
</button>
57+
58+
<button
59+
class="carousel-control next absolute top-1/2 right-2 transform -translate-y-1/2 bg-white/80 rounded-full p-2 shadow-md hover:bg-white"
60+
aria-label="Next slide"
61+
>
62+
<i class="fas fa-chevron-right"></i>
63+
</button>
64+
</div>
65+
)}
66+
67+
{showIndicators && (
68+
<div class="carousel-indicators absolute bottom-2 left-1/2 transform -translate-x-1/2 flex space-x-2">
69+
{items.map((_, index) => (
70+
<button
71+
class="carousel-indicator px-1 focus:outline-none"
72+
data-index={index}
73+
aria-label={`Go to slide ${index + 1}`}
74+
>
75+
<i class="far fa-circle"></i>
76+
</button>
77+
))}
78+
</div>
79+
)}
80+
</div>
81+
82+
<script define:vars={{ carouselId, autoplay, interval }}>
83+
document.addEventListener('DOMContentLoaded', () => {
84+
const carousel = document.getElementById(carouselId);
85+
if (!carousel) return;
86+
87+
const track = carousel.querySelector('.carousel-track');
88+
const items = carousel.querySelectorAll('.carousel-item');
89+
const prevBtn = carousel.querySelector('.carousel-control.prev');
90+
const nextBtn = carousel.querySelector('.carousel-control.next');
91+
const indicators = carousel.querySelectorAll('.carousel-indicator');
92+
93+
let currentIndex = 0;
94+
const itemCount = items.length;
95+
let autoplayInterval;
96+
97+
// Set the initial active state
98+
updateActiveState();
99+
100+
// Set up autoplay
101+
function startAutoplay() {
102+
if (autoplay && itemCount > 1) {
103+
autoplayInterval = setInterval(() => {
104+
goToSlide((currentIndex + 1) % itemCount);
105+
}, interval);
106+
}
107+
}
108+
109+
function stopAutoplay() {
110+
if (autoplayInterval) {
111+
clearInterval(autoplayInterval);
112+
}
113+
}
114+
115+
// Go to specific slide
116+
function goToSlide(index) {
117+
currentIndex = index;
118+
track.style.transform = `translateX(-${currentIndex * 100}%)`;
119+
updateActiveState();
120+
}
121+
122+
// Update active states
123+
function updateActiveState() {
124+
// Update indicators
125+
indicators.forEach((indicator, index) => {
126+
const icon = indicator.querySelector('i');
127+
if (index === currentIndex) {
128+
// Change to solid circle for active indicator
129+
icon.classList.remove('far', 'fa-circle');
130+
icon.classList.add('fas', 'fa-circle');
131+
} else {
132+
// Change to outline circle for inactive indicators
133+
icon.classList.remove('fas', 'fa-circle');
134+
icon.classList.add('far', 'fa-circle');
135+
}
136+
});
137+
138+
// Update aria attributes on items
139+
items.forEach((item, index) => {
140+
if (index === currentIndex) {
141+
item.setAttribute('aria-hidden', 'false');
142+
} else {
143+
item.setAttribute('aria-hidden', 'true');
144+
}
145+
});
146+
}
147+
148+
// Event listeners
149+
if (prevBtn) {
150+
prevBtn.addEventListener('click', () => {
151+
stopAutoplay();
152+
const newIndex = (currentIndex - 1 + itemCount) % itemCount;
153+
goToSlide(newIndex);
154+
startAutoplay();
155+
});
156+
}
157+
158+
if (nextBtn) {
159+
nextBtn.addEventListener('click', () => {
160+
stopAutoplay();
161+
const newIndex = (currentIndex + 1) % itemCount;
162+
goToSlide(newIndex);
163+
startAutoplay();
164+
});
165+
}
166+
167+
// Indicator clicks
168+
indicators.forEach((indicator) => {
169+
indicator.addEventListener('click', () => {
170+
stopAutoplay();
171+
const index = parseInt(indicator.getAttribute('data-index'));
172+
goToSlide(index);
173+
startAutoplay();
174+
});
175+
});
176+
177+
// Pause autoplay on hover or focus
178+
carousel.addEventListener('mouseenter', stopAutoplay);
179+
carousel.addEventListener('mouseleave', startAutoplay);
180+
carousel.addEventListener('focusin', stopAutoplay);
181+
carousel.addEventListener('focusout', startAutoplay);
182+
183+
// Touch support
184+
let touchStartX = 0;
185+
let touchEndX = 0;
186+
187+
carousel.addEventListener('touchstart', (e) => {
188+
touchStartX = e.changedTouches[0].screenX;
189+
stopAutoplay();
190+
}, { passive: true });
191+
192+
carousel.addEventListener('touchend', (e) => {
193+
touchEndX = e.changedTouches[0].screenX;
194+
handleSwipe();
195+
startAutoplay();
196+
}, { passive: true });
197+
198+
function handleSwipe() {
199+
const difference = touchStartX - touchEndX;
200+
if (difference > 50) {
201+
// Swipe left, go to next
202+
const newIndex = (currentIndex + 1) % itemCount;
203+
goToSlide(newIndex);
204+
} else if (difference < -50) {
205+
// Swipe right, go to previous
206+
const newIndex = (currentIndex - 1 + itemCount) % itemCount;
207+
goToSlide(newIndex);
208+
}
209+
}
210+
211+
// Start autoplay on load
212+
startAutoplay();
213+
});
214+
</script>
215+
216+
<style>
217+
.carousel-track {
218+
width: 100%;
219+
height: 100%;
220+
}
221+
222+
.carousel-item {
223+
width: 100%;
224+
flex: 0 0 100%;
225+
}
226+
227+
.carousel-control i {
228+
font-size: 16px;
229+
display: block;
230+
width: 20px;
231+
height: 20px;
232+
line-height: 20px;
233+
text-align: center;
234+
}
235+
236+
/* Indicator icon styling */
237+
.carousel-indicator i {
238+
font-size: 12px;
239+
color: #333;
240+
}
241+
242+
.carousel-indicator i.fas {
243+
color: #EB714D;
244+
}
245+
</style>

src/components/hero2/hero.astro

+45-5
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,43 @@ import heroImage from "./conference_photo.jpg";
66
import IconWithLabel from "./icon-label.astro";
77
import Button from "@ui/Button.astro";
88
9+
import Carousel from "@components/Carousel.astro";
10+
11+
// Define your carousel items
12+
const carouselItems = [
13+
{
14+
src: '/carousel/1.jpg',
15+
alt: 'Logo 1',
16+
width: 800,
17+
height: 600
18+
},
19+
{
20+
src: '/carousel/2.jpg',
21+
alt: 'Logo 2',
22+
width: 800,
23+
height: 600
24+
},
25+
{
26+
src: '/carousel/3.jpg',
27+
alt: 'Logo 3',
28+
width: 800,
29+
height: 600
30+
},
31+
{
32+
src: '/carousel/4.jpg',
33+
alt: 'Logo 4',
34+
width: 800,
35+
height: 600
36+
},
37+
{
38+
src: '/carousel/5.jpg',
39+
alt: 'Logo 5',
40+
width: 800,
41+
height: 600
42+
},
43+
];
44+
45+
946
const action1 = "/tickets";
1047
const action2 = "/sponsorship/sponsor/";
1148
---
@@ -124,11 +161,14 @@ const action2 = "/sponsorship/sponsor/";
124161
<div class="hero-image overflow-hidden -mt-12">
125162
<!-- Image with Rounded Corners and Shadow -->
126163
<div class="px-5 md:px-10 md:m-10">
127-
<Image
128-
src={heroImage}
129-
alt="EuroPython 2025 Hero Image"
130-
class="w-full max-w-5xl lg:max-w-full h-auto lg:h-full rounded-2xl shadow-xl"
131-
/>
164+
165+
<Carousel items={carouselItems}
166+
className="w-[1000px] h-[500px] flex items-center justify-center text-center m-auto"
167+
autoplay={true}
168+
interval={5000}
169+
showControls={true}
170+
showIndicators={true}
171+
/>
132172
</div>
133173
</div>
134174
<!-- 3x1 Grid with Conference Stats -->

src/pages/index.astro

-5
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,6 @@ import Prague from "@sections/prague.astro";
99
import Sponsors from "@components/sponsors/sponsors.astro";
1010
import Subscribe from "@sections/subscribe.astro";
1111
12-
let deadlines = await getCollection("deadlines");
13-
deadlines = deadlines
14-
.sort((a, b) => a.slug.localeCompare(b.slug))
15-
.reverse()
16-
.slice(0, 3);
1712
---
1813

1914
<Layout

0 commit comments

Comments
 (0)