Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,13 @@ body {
color: var(--foreground);
font-family: var(--font-outfit-sans), sans-serif;
}

/* Utility to hide scrollbars while maintaining functionality */
.scrollbar-hide {
-ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */
}

.scrollbar-hide::-webkit-scrollbar {
display: none; /* Chrome, Safari and Opera */
}
2 changes: 1 addition & 1 deletion src/app/volunteers/CarouselAssignments.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ async function AssignmentsCarousel() {
return (
<Carousel pages={1 + Math.floor((assignments.length - 1) / 6)} size={6} className="grid gap-4 grid-cols-1 md:grid-cols-3 place-items-center place-content-center">
{assignments.map((e, i) => (
<CI key={e.title} className="rounded-2xl bg-color-02 text-neutral-02 w-full p-4 m-3 contain-content" index={i}>
<CI key={e.title} className="rounded-2xl bg-color-02 text-neutral-02 w-full p-4 m-3 contain-content">
<h5 className='text-white text-2xl font-bold'>{e.title}{i}</h5>
<p>{e.description}</p>
{/*Unused code from Figma:*/} <p>{e.id}</p>
Expand Down
4 changes: 2 additions & 2 deletions src/app/volunteers/CarouselTestimony.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ async function TestimonyCarousel() {
}/**/
return (
<Carousel pages={1 + Math.floor((testimony.length - 1) / 6)} size={6} className="grid gap-4 grid-cols-1 md:grid-cols-3 place-items-center place-content-center">
{testimony.map((e, i) => (
<CI key={e.name} className="rounded-2xl w-full p-4 m-3 contain-content bg-neutral-01" index={i}>
{testimony.map((e) => (
<CI key={e.name} className="rounded-2xl w-full p-4 m-3 contain-content bg-neutral-01">
<FaQuoteLeft className="text-color-01 text-5xl" />
{/*Unused code from Figma design <h5 className='text-2xl font-bold'>{e.title}{i}</h5>*/}
<p className='text-neutral-03'>{e.quote}</p>
Expand Down
4 changes: 2 additions & 2 deletions src/app/volunteers/CarouselVolunteer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ async function VolunteerCarousel() {
}
return (
<Carousel pages={1 + Math.floor(0.125 * (volunteers.length - 1))} size={8} className="grid gap-4 grid-cols-2 md:grid-cols-4 place-items-center place-content-center">
{volunteers.map((e, i) => (
<CI key={e.name} className="group rounded-2xl w-full contain-content z-1 m-3" index={i}>
{volunteers.map((e) => (
<CI key={e.name} className="group rounded-2xl w-full contain-content z-1 m-3">
<Image alt={e.name} src={e.image} width={302} height={335} className='object-cover w-full z-2' />
<div className={`${styles.overlay} fixed bottom-0 w-full h-6/12 z-3 opacity-0 group-hover:opacity-100 text-white duration-200 bg-gradient-to-t from-black bg-opacity-75`}>
<div className='fixed bottom-0 w-full p-4 z-4'>
Expand Down
109 changes: 95 additions & 14 deletions src/components/Carousel.tsx
Original file line number Diff line number Diff line change
@@ -1,32 +1,113 @@
'use client';
import {useState, createContext, useContext} from 'react';
import {useState, createContext, useRef, useEffect} from 'react';

const PageContext = createContext({page: 1,size:1});

function Carousel({children, pages, size, className}:{children: Array<React.ReactNode>, pages:number, size:number, className?:string}) {
const [page, setPage] = useState(0);
const scrollContainerRef = useRef<HTMLDivElement>(null);

function setter (p: number) {
setPage(p);
// Scroll to the corresponding page
if (scrollContainerRef.current) {
const scrollContainer = scrollContainerRef.current;
const pageWidth = scrollContainer.offsetWidth;
scrollContainer.scrollTo({
left: pageWidth * p,
behavior: 'smooth'
});
}
}

// Handle scroll events to update active dot
useEffect(() => {
const scrollContainer = scrollContainerRef.current;
if (!scrollContainer || pages <= 1) return;

const handleScroll = () => {
const scrollLeft = scrollContainer.scrollLeft;
const pageWidth = scrollContainer.offsetWidth;
const currentPage = Math.round(scrollLeft / pageWidth);
if (currentPage !== page && currentPage >= 0 && currentPage < pages) {
setPage(currentPage);
}
};

scrollContainer.addEventListener('scroll', handleScroll);
return () => scrollContainer.removeEventListener('scroll', handleScroll);
}, [pages, page]);

// Create pages with items
const createPages = () => {
const pageElements = [];
for (let i = 0; i < pages; i++) {
const pageItems = [];
for (let j = i * size; j < Math.min((i + 1) * size, children.length); j++) {
const childElement = children[j] as React.ReactElement<{className?: string, children: React.ReactNode}>;
pageItems.push(
<CI key={j} className={childElement.props.className || ''}>
{childElement.props.children}
</CI>
);
}

pageElements.push(
<ul key={i} className={`w-full flex-shrink-0 snap-start contain-content ${className}`}>
<PageContext value={{page: i, size: size}}>
{pageItems}
</PageContext>
</ul>
);
}
return pageElements;
};

let dots;
if (pages>1){
dots = (<div className="col-span-full m-0 text-center w-52">
{[...Array(pages).keys()].map(i => (<span key={i} onClick={()=>{setter(i)}} className={`${(page==i)?'opacity-100':'opacity-50'} select-none m-0.5 hover:opacity-70`}>●</span>))}
</div>)
if (pages > 1) {
dots = (
<div className="col-span-full m-0 text-center w-full mt-4">
<div className="flex justify-center items-center gap-2">
{[...Array(pages).keys()].map(i => (
<button
key={i}
onClick={() => setter(i)}
className={`
${(page === i) ? 'opacity-100' : 'opacity-50'}
select-none hover:opacity-70 transition-opacity duration-200
w-6 h-6 flex items-center justify-center
rounded-full cursor-pointer
`}
aria-label={`Go to slide ${i + 1}`}
>
<span className="w-2 h-2 rounded-full bg-current"></span>
</button>
))}
</div>
</div>
);
}

return (
<ul className={`w-full contain-content ${className}`}>
<PageContext value={{page:page,size:size}}>{children}</PageContext>
{dots}
</ul>
);
<div className="w-full">
<div
ref={scrollContainerRef}
className="flex gap-4 overflow-x-auto scrollbar-hide snap-x snap-mandatory"
style={{
scrollbarWidth: 'none',
msOverflowStyle: 'none',
}}
>
{createPages()}
</div>
{dots}
</div>
);
}

function CI ({children, className, index}:{children: Array<React.ReactNode>, className:string, index:number}) {
const context = useContext(PageContext);
const visible = (index>=(context.size*context.page))&&(index<(context.size*(context.page+1)));
function CI ({children, className}:{children: React.ReactNode, className:string}) {
return (
<li className={`${className} ${visible?'':'hidden'}`}>
<li className={className}>
{children}
</li>
);
Expand Down