feat: replace carousel using alpine with javascript

This commit is contained in:
Troy 2025-02-15 23:08:31 +00:00
parent 74b9460ea1
commit 700095d8af
Signed by: troy
GPG key ID: DFC06C02ED3B4711
9 changed files with 165 additions and 261 deletions

View file

@ -1,97 +0,0 @@
---
interface Props {
data: string;
}
const { data } = Astro.props;
---
<div
data-slides={data}
x-data="{
// Sets the time between each slides in milliseconds
autoplayIntervalTime: 5000,
init() {
this.slides = JSON.parse(this.$el.dataset.slides);
},
currentSlideIndex: 1,
isPaused: false,
autoplayInterval: null,
previous() {
if (this.currentSlideIndex > 1) {
this.currentSlideIndex = this.currentSlideIndex - 1
} else {
// If it's the first slide, go to the last slide
this.currentSlideIndex = this.slides.length
}
},
next() {
if (this.currentSlideIndex < this.slides.length) {
this.currentSlideIndex = this.currentSlideIndex + 1
} else {
// If it's the last slide, go to the first slide
this.currentSlideIndex = 1
}
},
autoplay() {
this.autoplayInterval = setInterval(() => {
if (! this.isPaused) {
this.next()
}
}, this.autoplayIntervalTime)
},
// Updates interval time
setAutoplayInterval(newIntervalTime) {
clearInterval(this.autoplayInterval)
this.autoplayIntervalTime = newIntervalTime
this.autoplay()
},
}"
x-init="autoplay"
class="relative w-full overflow-hidden"
>
<div class="relative min-h-[50svh] w-full">
<template x-for="(slide, index) in slides">
<div
x-cloak
x-show="currentSlideIndex == index + 1"
class="absolute inset-0"
x-transition.opacity.duration.1000ms
>
<img
class="absolute inset-0 h-full w-full rounded-sm object-cover"
x-bind:src="slide.data.image.url.src"
x-bind:alt="slide.data.image.alt"
/>
<a
class="absolute inset-0 z-20"
x-bind:aria-label="slide.data.title"
x-bind:href="'/' + slide.collection + '/' + slide.slug"></a>
</div>
</template>
</div>
<div class="relative min-h-18">
<template x-for="(slide, index) in slides">
<div
x-show="currentSlideIndex == index + 1"
class="text-secondary absolute top-0 left-0 flex w-full justify-end pt-1 text-right font-serif text-lg"
x-transition.opacity.duration.1000ms.delay.100ms
>
<div class="flex w-full flex-col items-end">
<h3
x-text="slide.data.title"
x-bind:aria-describedby="'slide' + (index + 1) + 'Description'"
class="italic"
>
</h3>
<p
class="text-tertiary w-fit text-sm text-pretty lg:w-1/2"
x-text="slide.data.description"
x-bind:id="'slide' + (index + 1) + 'Description'"
>
</p>
</div>
</div>
</template>
</div>
</div>

View file

@ -1,29 +1,12 @@
---
import { SITE } from "@consts";
import Link from "@components/Link.astro";
import { Icon } from "astro-icon/components";
---
<footer class="mx-auto mt-auto w-full">
<div class="flex flex-col items-center justify-between gap-2 md:gap-4">
<span class="text-tertiary text-center text-sm"
>&copy; {new Date().getFullYear()}
<a href="/" class="hover:text-secondary transition-colors duration-300"
>{SITE.TITLE}</a
>. All rights reserved.
</span>
<div class="flex gap-3 sm:justify-center">
{
SITE.LINKS.map((i) => (
<Link href={i.href}>
<Icon
name={i.icon}
title={i.name}
class="text-tertiary hover:text-secondary h-4 w-auto transition-colors duration-300"
/>
</Link>
))
}
</div>
</div>
<footer class="w-full">
<span class="text-tertiary"
>&copy; {new Date().getFullYear()}
<a href="/" class="hover:text-secondary transition-colors duration-300"
>{SITE.TITLE}</a
>. All rights reserved.
</span>
</footer>

View file

@ -1,62 +1,30 @@
---
import { SITE } from "@consts";
import { Icon } from "astro-icon/components";
const pathname = new URL(Astro.request.url).pathname;
const currentPath = pathname.replace(/\/$/, "");
---
<header class="mx-auto w-full">
<nav
class="md:flex-no-wrap flex w-full flex-wrap justify-between"
x-data="{ open: false }"
>
<div class="flex w-full items-center justify-between md:w-fit">
<a href="/" title={SITE.TITLE}>
<Icon
name="icon"
title={SITE.TITLE}
class="hover:text-tertiary h-8 w-auto transition-colors duration-500 ease-in-out"
/>
<nav class="flex w-full justify-between">
<div class="flex w-fit items-center justify-between">
<a href="/" aria-label={SITE.TITLE} title={SITE.TITLE} class="group">
<div
class="ring-secondary/10 size-8 rounded-full bg-linear-45 from-[#FF5907] from-50% to-[#FF8548] ring-1 transition-transform ease-in-out ring-inset group-hover:scale-105"
>
</div>
</a>
<button
x-on:click="open = !open"
x-bind:aria-expanded="open"
type="button"
class="text-secondary flex md:hidden"
aria-label="mobile menu"
aria-controls="mobileMenu"
>
<Icon
name="mdi:menu"
aria-hidden="true"
class="size-6"
x-cloak
x-show="!open"
/>
<Icon
name="mdi:window-close"
aria-hidden="true"
class="size-6"
x-cloak
x-show="open"
/>
</button>
</div>
<ul
class="my-4 flex w-full flex-col items-center gap-4 text-right md:my-0 md:flex md:w-fit md:flex-row md:text-left"
x-bind:class="{ 'hidden': !open }"
x-cloak
>
<ul class="flex w-fit flex-row items-center gap-4">
{
SITE.NAVLINKS.map((link) => {
let linkHref = link.href.replace(/\/$/, "");
const isActive = currentPath.startsWith(linkHref);
return (
<li class="text-tertiary hover:text-secondary focus:text-secondary text-2xl font-medium capitalize transition-colors duration-300 focus:outline-hidden md:text-base">
<li class="text-tertiary hover:text-secondary focus:text-secondary font-medium capitalize transition-colors duration-300 focus:outline-hidden">
<a
href={link.href}
class:list={[isActive ? "text-secondary" : ""]}
class:list={[isActive ? "text-secondary" : null]}
>
{link.name}
</a>

View file

@ -0,0 +1,60 @@
---
import { Image } from "astro:assets";
interface Props {
interval?: number;
images: any;
}
const { interval = 3000, images } = Astro.props;
---
<div
class="relative h-64 w-full overflow-hidden md:h-96"
data-interval={interval}
>
{
images.map((image: any, index: number) => (
<div
class="absolute top-0 left-0 h-full w-full transition-opacity duration-500"
style={`opacity: ${index === 0 ? 1 : 0}; z-index: ${
index === 0 ? 10 : 1
}`}
data-slide-index={index}
>
<a href={`/${image.collection}/${image.slug}`}>
<Image
src={image.data.image.url}
alt={`Slide ${index + 1}`}
title={image.data.title}
class="h-full w-full rounded-sm object-cover"
loading="eager"
/>
</a>
</div>
))
}
</div>
<script>
const images = document.querySelectorAll<HTMLElement>("[data-slide-index]");
let currentImageIndex = 0;
const interval =
Number(
(
document
.querySelector<HTMLElement>("[data-slide-index]")
?.closest(".relative") as HTMLElement
)?.dataset?.interval,
) || 3000;
function showNextImage() {
images[currentImageIndex].style.opacity = "0";
images[currentImageIndex].style.zIndex = "1";
currentImageIndex = (currentImageIndex + 1) % images.length;
images[currentImageIndex].style.opacity = "1";
images[currentImageIndex].style.zIndex = "10";
}
setInterval(showNextImage, interval);
</script>

View file

@ -3,24 +3,21 @@ import Layout from "@layouts/Layout.astro";
import { HOME } from "@consts";
import { getCollection } from "astro:content";
import Hero from "@components/Hero.astro";
import Carousel from "@components/Carousel.astro";
import Slideshow from "@components/Slideshow.astro";
const allProjects = await getCollection("projects");
const projects = allProjects
.filter((project) => !project.data.draft && project.data.featured)
.sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf())
.slice(0, HOME.HOMESETTINGS?.NUM_PROJECTS_ON_HOMEPAGE);
const projectsJSON = JSON.stringify(projects);
---
<Layout title={HOME.TITLE} description={HOME.DESCRIPTION}>
<div class="block w-full">
<Hero />
<section
id="featured-projects"
class="animate-reveal opacity-0 [animation-delay:0.1s]"
>
<Carousel data={projectsJSON} />
</section>
</div>
<Hero />
<section
id="featured-projects"
class="animate-reveal opacity-0 [animation-delay:0.1s]"
>
<Slideshow images={projects} />
</section>
</Layout>

View file

@ -79,7 +79,3 @@
display: none;
}
}
[x-cloak] {
display: none !important;
}