diff --git a/package-lock.json b/package-lock.json index edffa32..130e796 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,9 @@ "@fortawesome/free-solid-svg-icons": "^6.5.1", "@fortawesome/react-fontawesome": "^0.2.0", "@next/third-parties": "^14.1.0", + "embla-carousel-auto-scroll": "^8.0.0", + "embla-carousel-autoplay": "^8.0.0", + "embla-carousel-react": "^8.0.0", "html-react-parser": "^5.1.2", "next": "^14.1.0", "react": "^18", @@ -1526,6 +1529,47 @@ "url": "https://github.com/fb55/domutils?sponsor=1" } }, + "node_modules/embla-carousel": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/embla-carousel/-/embla-carousel-8.0.0.tgz", + "integrity": "sha512-ecixcyqS6oKD2nh5Nj5MObcgoSILWNI/GtBxkidn5ytFaCCmwVHo2SecksaQZHcARMMpIR2dWOlSIdA1LkZFUA==" + }, + "node_modules/embla-carousel-auto-scroll": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/embla-carousel-auto-scroll/-/embla-carousel-auto-scroll-8.0.0.tgz", + "integrity": "sha512-+bG79hg/BhI4enRevR6fuQdrJevfu3gfNg/S+1kGszXHUJHOOwDQne/dXOnQ12+o74XvDD5t/LG45rAOplehhQ==", + "peerDependencies": { + "embla-carousel": "8.0.0" + } + }, + "node_modules/embla-carousel-autoplay": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/embla-carousel-autoplay/-/embla-carousel-autoplay-8.0.0.tgz", + "integrity": "sha512-FWHhZULH5+ydg7fiabwQppCDoTMi8pbMC20lmVytoXn7hH2KAhXHc/8yCUb3yToqMduCN6xPKUONtgzBqz3RZg==", + "peerDependencies": { + "embla-carousel": "8.0.0" + } + }, + "node_modules/embla-carousel-react": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/embla-carousel-react/-/embla-carousel-react-8.0.0.tgz", + "integrity": "sha512-qT0dii8ZwoCtEIBE6ogjqU2+5IwnGfdt2teKjCzW88JRErflhlCpz8KjWnW8xoRZOP8g0clRtsMEFoAgS/elfA==", + "dependencies": { + "embla-carousel": "8.0.0", + "embla-carousel-reactive-utils": "8.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.1 || ^18.0.0" + } + }, + "node_modules/embla-carousel-reactive-utils": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/embla-carousel-reactive-utils/-/embla-carousel-reactive-utils-8.0.0.tgz", + "integrity": "sha512-JCw0CqCXI7tbHDRogBb9PoeMLyjEC1vpN0lDOzUjmlfVgtfF+ffLaOK8bVtXVUEbNs/3guGe3NSzA5J5aYzLzw==", + "peerDependencies": { + "embla-carousel": "8.0.0" + } + }, "node_modules/emoji-regex": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", diff --git a/package.json b/package.json index e08dbfc..889cc12 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,9 @@ "@fortawesome/free-solid-svg-icons": "^6.5.1", "@fortawesome/react-fontawesome": "^0.2.0", "@next/third-parties": "^14.1.0", + "embla-carousel-auto-scroll": "^8.0.0", + "embla-carousel-autoplay": "^8.0.0", + "embla-carousel-react": "^8.0.0", "html-react-parser": "^5.1.2", "next": "^14.1.0", "react": "^18", diff --git a/src/app/components/ThroughTheYears.tsx b/src/app/components/ThroughTheYears.tsx deleted file mode 100644 index 025c6c2..0000000 --- a/src/app/components/ThroughTheYears.tsx +++ /dev/null @@ -1,16 +0,0 @@ -export default function ThroughTheYears() { - return ( -
- {" "} - Coming soon...{" "} -
- ); -} diff --git a/src/app/components/ThroughTheYears/EmblaCarousel.tsx b/src/app/components/ThroughTheYears/EmblaCarousel.tsx new file mode 100644 index 0000000..737ca0f --- /dev/null +++ b/src/app/components/ThroughTheYears/EmblaCarousel.tsx @@ -0,0 +1,231 @@ +import { React, useState, useEffect, useCallback } from "react"; +import { EmblaOptionsType } from "embla-carousel"; +import { DotButton, useDotButton } from "./EmblaCarouselDotButton"; +import useEmblaCarousel from "embla-carousel-react"; +import Image from "next/image"; +import Link from "next/link"; +import AutoScroll from "embla-carousel-auto-scroll"; + +import styled from "styled-components"; + +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faPlay, faStop } from "@fortawesome/free-solid-svg-icons"; + +type PropType = { + slides: number[]; + options?: EmblaOptionsType; +}; + +const Embla = styled.section` + margin: auto; + max-width: 100%; + --slide-spacing: 1rem; + --slide-size: 100%; + --slide-spacing-sm: 10rem; + --slide-size-sm: 20%; + --slide-spacing-lg: 2rem; + --slide-size-lg: calc(100% / 4); +`; + +const EmblaViewport = styled.div` + overflow: hidden; +`; + +const EmblaContainer = styled.div` + backface-visibility: hidden; + display: flex; + touch-action: pan-y; + margin-left: calc(var(--slide-spacing) * -1); + + @media (min-width: 300px) { + margin-left: calc(var(--slide-spacing-sm) * -1); + } + + @media (min-width: 750px) { + margin-left: calc(var(--slide-spacing-lg) * -1); + } +`; + +const EmblaSlide = styled.div` + min-width: 0; + flex: 0 0 var(--slide-size); + padding-left: var(--slide-spacing); + display: flex; + justify-content: center; + align-items: center; + text-align: center; + + @media (min-width: 300px) { + flex: 0 0 var(--slide-size-sm); + padding-left: var(--slide-spacing-sm); + } + + @media (min-width: 750px) { + padding-left: var(--slide-spacing-sm); + } + + @media (min-width: 1200px) { + flex: 0 0 var(--slide-size-lg); + padding-left: var(--slide-spacing-lg); + } +`; + +const EmblaSlideNumber = styled.div` + border-radius: 1.8rem; + font-size: 4rem; + font-weight: 600; + display: flex; + align-items: center; + justify-content: center; + height: var(--slide-height); +`; + +const EmblaControls = styled.div` + display: flex; + justify-content: center; + align-items: center; + margin-top: 1.8rem; + + @media (max-width: 767px) { + display: none; + } +`; + +const EmblaButtons = styled.div` + display: flex; + gap: 0.6rem; + align-items: center; +`; + +const EmblaButton = styled.button` + -webkit-appearance: none; + appearance: none; + background-color: transparent; + touch-action: manipulation; + display: inline-flex; + text-decoration: none; + cursor: pointer; + border: 0; + padding: 0; + margin: 0; + width: 3.6rem; + height: 3.6rem; + z-index: 1; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; +`; + +const EmblaButtonSvg = styled.svg` + width: 35%; + height: 35%; +`; + +const EmblaDots = styled.div` + display: flex; + flex-wrap: wrap; + justify-content: center; + align-items: center; +`; + +const EmblaPlay = styled.button` + -webkit-appearance: none; + text-decoration: none; + cursor: url("/rocket-fire.png"), auto; + display: flex; + font-size: 1.5rem; + background-color: black; + border: none; + margin: 0px 0px 0px 10px; +`; + +const EmblaCarousel: React.FC = (props) => { + const { slides, options } = props; + const [emblaRef, emblaApi] = useEmblaCarousel(options, [ + AutoScroll({ playOnInit: true }), + ]); + const [isPlaying, setIsPlaying] = useState(false); + + const { selectedIndex, scrollSnaps, onDotButtonClick } = + useDotButton(emblaApi); + + const onButtonAutoplayClick = useCallback( + (callback: () => void) => { + const autoScroll = emblaApi?.plugins()?.autoScroll; + if (!autoScroll) return; + + const resetOrStop = + autoScroll.options.stopOnInteraction === false + ? autoScroll.reset + : autoScroll.stop; + + resetOrStop(); + callback(); + }, + [emblaApi], + ); + + const toggleAutoplay = useCallback(() => { + const autoScroll = emblaApi?.plugins()?.autoScroll; + if (!autoScroll) return; + + const playOrStop = autoScroll.isPlaying() + ? autoScroll.stop + : autoScroll.play; + playOrStop(); + }, [emblaApi]); + + useEffect(() => { + const autoScroll = emblaApi?.plugins()?.autoScroll; + if (!autoScroll) return; + + setIsPlaying(autoScroll.isPlaying()); + emblaApi + .on("autoScroll:play", () => setIsPlaying(true)) + .on("autoScroll:stop", () => setIsPlaying(false)) + .on("reInit", () => setIsPlaying(false)); + }, [emblaApi]); + + return ( + + + + {slides.map((edition, index, currentIndex) => ( + + + altText +

{edition.name}

+

{edition.date}

+ +
+ ))} +
+
+ + + {scrollSnaps.map((_, index) => ( + onDotButtonClick(index)} + selected={selectedIndex === index} + /> + ))} + + + + + +
+ ); +}; + +export default EmblaCarousel; diff --git a/src/app/components/ThroughTheYears/EmblaCarouselDotButton.tsx b/src/app/components/ThroughTheYears/EmblaCarouselDotButton.tsx new file mode 100644 index 0000000..e00b8ba --- /dev/null +++ b/src/app/components/ThroughTheYears/EmblaCarouselDotButton.tsx @@ -0,0 +1,106 @@ +import React, { + PropsWithChildren, + useCallback, + useEffect, + useState, +} from "react"; +// eslint-disable-next-line import/no-extraneous-dependencies +import { EmblaCarouselType } from "embla-carousel"; +import styled from "styled-components"; + +type UseDotButtonType = { + selectedIndex: number; + scrollSnaps: number[]; + onDotButtonClick: (index: number) => void; +}; + +export const useDotButton = ( + emblaApi: EmblaCarouselType | undefined, +): UseDotButtonType => { + const [selectedIndex, setSelectedIndex] = useState(0); + const [scrollSnaps, setScrollSnaps] = useState([]); + + const onDotButtonClick = useCallback( + (index: number) => { + if (!emblaApi) return; + emblaApi.scrollTo(index); + }, + [emblaApi], + ); + + const onInit = useCallback((emblaApi: EmblaCarouselType) => { + setScrollSnaps(emblaApi.scrollSnapList()); + }, []); + + const onSelect = useCallback((emblaApi: EmblaCarouselType) => { + setSelectedIndex(emblaApi.selectedScrollSnap()); + }, []); + + useEffect(() => { + if (!emblaApi) return; + + onInit(emblaApi); + onSelect(emblaApi); + emblaApi.on("reInit", onInit); + emblaApi.on("reInit", onSelect); + emblaApi.on("select", onSelect); + }, [emblaApi, onInit, onSelect]); + + return { + selectedIndex, + scrollSnaps, + onDotButtonClick, + }; +}; + +type PropType = PropsWithChildren< + React.DetailedHTMLProps< + React.ButtonHTMLAttributes, + HTMLButtonElement + > +>; + +const EmblaDot = styled.button` + appearance: none; + touch-action: manipulation; + display: inline-flex; + text-decoration: none; + cursor: url("/rocket-fire.png"), auto; + border: 0; + padding: 0; + margin: 0.1rem; + width: 1rem; + height: 1rem; + display: flex; + align-items: center; + justify-content: center; + border-radius: 50%; + position: relative; + background-color: ${(props) => + props.selected + ? "orangered" + : "slategray"}; // Change 'blue' to your desired color + + + &::after { + width: 1rem; + height: 1rem; + border-radius: 50%; + display: flex; + align-items: center; + content: ""; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); +`; + +export const DotButton: React.FC = (props) => { + const { children, ...restProps } = props; + + return ( + + {children} + + ); +}; diff --git a/src/app/components/ThroughTheYears/ThroughTheYears.tsx b/src/app/components/ThroughTheYears/ThroughTheYears.tsx new file mode 100644 index 0000000..9200815 --- /dev/null +++ b/src/app/components/ThroughTheYears/ThroughTheYears.tsx @@ -0,0 +1,88 @@ +import React from "react"; + +import styled, { keyframes } from "styled-components"; +import { Section } from "@/app/genericComponents/General"; +import { SectionTitle } from "@/app/genericComponents/Typography"; +import useEmblaCarousel from "embla-carousel-react"; +import EmblaCarousel from "./EmblaCarousel"; +// eslint-disable-next-line import/no-extraneous-dependencies +import { EmblaOptionsType } from "embla-carousel"; + +export default function ThroughTheYears() { + const PREVIOUS_EDITIONS = [ + { + name: "Fall 2016", + date: "October 23-25", + img: "/Planets/planetPartner.svg", + url: "", + }, + { + name: "Winter 2016", + date: "May 8-12", + img: "/Planets/planetSponsor.svg", + url: "", + }, + { + name: "Fall 2017", + date: "May 3-5", + img: "/Planets/planetMentor.svg", + url: "", + }, + { + name: "Winter 2017", + date: "May 3-5", + img: "/Planets/planetVolunteer.svg", + url: "", + }, + { + name: "2018", + date: "May 3-5", + img: "/Planets/planetPartner.svg", + url: "https://2018.hackupc.com/", + }, + { + name: "2019", + date: "May 3-5", + img: "/Planets/planetSponsor.svg", + url: "https://2019.hackupc.com/", + }, + { + name: "2020", + date: "CANCELLED", + img: "/Planets/planetMentor.svg", + url: "", + }, + { + name: "2021", + date: "May 3-5", + img: "/Planets/planetVolunteer.svg", + url: "https://2021.hackupc.com/", + }, + { + name: "2022", + date: "May 3-5", + img: "/Planets/planetVolunteer.svg", + url: "https://2022.hackupc.com/", + }, + { + name: "2023", + date: "May 3-5", + img: "/Planets/planetVolunteer.svg", + url: "https://2023.hackupc.com/", + }, + ]; + + const [emblaRef] = useEmblaCarousel(); + const OPTIONS: EmblaOptionsType = { + align: "start", + loop: true, + slidesToScroll: 2, + }; + + return ( +
+ Through The Years + +
+ ); +} diff --git a/src/app/page.tsx b/src/app/page.tsx index 1e7df11..a130cac 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -10,11 +10,10 @@ import FAQs from "@/app/components/FAQs"; import Socials from "@/app/components/Socials"; import SponsorsAndPartners from "@/app/components/SponsorsAndPartners"; import Hackers from "@/app/components/Hackers"; -// TODO: import ThroughTheYears from "@/app/components/ThroughTheYears"; import Hero from "./components/Hero"; import Header from "@/app/components/Header"; import Footer from "@/app/components/Footer"; -import ThroughTheYears from "@/app/components/ThroughTheYears"; +import ThroughTheYears from "@/app/components/ThroughTheYears/ThroughTheYears"; import { Background } from "@/app/genericComponents/General"; export default function Home() {