-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feat/organizer carousel #535
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
import { defineType, defineField, defineArrayMember } from "sanity"; | ||
import { Users } from "lucide-react"; | ||
|
||
export default defineType({ | ||
name: "organizers", | ||
title: "Organizers", | ||
icon: Users, | ||
type: "document", | ||
fields: [ | ||
defineField({ | ||
name: "organizers", | ||
title: "Organizers", | ||
type: "array", | ||
of: [ | ||
defineArrayMember({ | ||
type: "object", | ||
name: "organizer", | ||
fields: [ | ||
defineField({ | ||
name: "name", | ||
title: "Name", | ||
type: "string", | ||
validation: (Rule) => Rule.required(), | ||
}), | ||
defineField({ | ||
name: "department", | ||
title: "Department", | ||
type: "string", | ||
validation: (Rule) => Rule.required(), | ||
options: { | ||
list: [ | ||
{ title: "Tech", value: "Tech" }, | ||
{ title: "Marketing", value: "Marketing" }, | ||
{ title: "Logistics", value: "Logistics" }, | ||
{ title: "Corporate", value: "Corporate" }, | ||
{ title: "Graphics", value: "Graphics" }, | ||
], | ||
}, | ||
}), | ||
defineField({ | ||
name: "role", | ||
title: "Role", | ||
type: "string", | ||
validation: (Rule) => Rule.required(), | ||
options: { | ||
list: [ | ||
{ title: "Director", value: "Director" }, | ||
{ title: "Organizer", value: "Organizer" }, | ||
{ title: "Intern", value: "Intern" }, | ||
{ title: "Advisor", value: "Advisor" }, | ||
], | ||
}, | ||
}), | ||
defineField({ | ||
name: "image", | ||
title: "Profile Image", | ||
type: "image", | ||
options: { | ||
hotspot: true, | ||
}, | ||
}), | ||
defineField({ | ||
name: "link", | ||
title: "Social Link", | ||
type: "url", | ||
description: "LinkedIn or other social media profile link", | ||
}), | ||
], | ||
}), | ||
], | ||
}), | ||
], | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
"use client"; | ||
|
||
import React, { useEffect, useState } from "react"; | ||
import Image from "next/image"; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
import box from "@/assets/images/center_chat_box.svg"; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
import boxBG from "@/assets/images/center_chat_box_bg.svg"; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
import Link from "next/link"; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
||
export const InfiniteMovingCards = ({ | ||
items, | ||
direction = "left", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
speed = "fast", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
pauseOnHover = true, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
}: { | ||
items: { | ||
quote: string; | ||
name: string; | ||
title: string; | ||
image: string; | ||
link: string; | ||
}[]; | ||
direction?: "left" | "right"; | ||
speed?: "fast" | "normal" | "slow"; | ||
pauseOnHover?: boolean; | ||
className?: string; | ||
}) => { | ||
const containerRef = React.useRef<HTMLDivElement>(null); | ||
const scrollerRef = React.useRef<HTMLUListElement>(null); | ||
const [start, setStart] = useState(false); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
||
useEffect(() => { | ||
if (!containerRef.current || !scrollerRef.current) return; | ||
|
||
// Clear existing clones | ||
while (scrollerRef.current.children.length > items.length) { | ||
scrollerRef.current.lastChild && | ||
scrollerRef.current.removeChild(scrollerRef.current.lastChild); | ||
Comment on lines
+36
to
+37
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
} | ||
|
||
// Clone items just once for infinite scroll | ||
const originalItems = Array.from(scrollerRef.current.children).slice( | ||
0, | ||
items.length, | ||
); | ||
originalItems.forEach((item) => { | ||
const duplicatedItem = item.cloneNode(true); | ||
if (scrollerRef.current) { | ||
scrollerRef.current.appendChild(duplicatedItem); | ||
} | ||
}); | ||
|
||
const observer = new IntersectionObserver( | ||
(entries) => { | ||
entries.forEach((entry) => { | ||
if (entry.isIntersecting) { | ||
setStart(true); | ||
} | ||
}); | ||
}, | ||
{ threshold: 0.1 }, | ||
); | ||
|
||
observer.observe(containerRef.current); | ||
|
||
return () => { | ||
observer.disconnect(); | ||
}; | ||
}, [items]); | ||
|
||
// ... rest of the component stays the same ... | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,155 @@ | ||
"use client"; | ||
|
||
import React, { useEffect, useState } from "react"; | ||
import Image from "next/image"; | ||
import box from "@/assets/images/center_chat_box.svg"; | ||
import boxBG from "@/assets/images/center_chat_box_bg.svg"; | ||
import Link from "next/link"; | ||
|
||
export const InfiniteMovingCards = ({ | ||
items, | ||
direction = "left", | ||
speed = "fast", | ||
pauseOnHover = true, | ||
}: { | ||
items: { | ||
quote: string; | ||
name: string; | ||
title: string; | ||
image: string; | ||
link: string; | ||
}[]; | ||
direction?: "left" | "right"; | ||
speed?: "fast" | "normal" | "slow"; | ||
pauseOnHover?: boolean; | ||
className?: string; | ||
}) => { | ||
const containerRef = React.useRef<HTMLDivElement>(null); | ||
const scrollerRef = React.useRef<HTMLUListElement>(null); | ||
const [start, setStart] = useState(false); | ||
|
||
useEffect(() => { | ||
if (!containerRef.current || !scrollerRef.current) return; | ||
|
||
// Clone items multiple times to ensure smooth infinite scroll | ||
const scrollerContent = Array.from(scrollerRef.current.children); | ||
// Clone enough times to ensure continuous scroll | ||
scrollerContent.forEach((item) => { | ||
const duplicatedItem = item.cloneNode(true); | ||
if (scrollerRef.current) { | ||
scrollerRef.current.appendChild(duplicatedItem); | ||
} | ||
}); | ||
|
||
const observer = new IntersectionObserver( | ||
(entries) => { | ||
entries.forEach((entry) => { | ||
if (entry.isIntersecting) { | ||
setStart(true); | ||
} | ||
}); | ||
}, | ||
{ threshold: 0.1 }, | ||
); | ||
|
||
observer.observe(containerRef.current); | ||
|
||
return () => { | ||
observer.disconnect(); | ||
}; | ||
}, [items]); | ||
|
||
const duration = | ||
speed === "fast" ? "30s" : speed === "normal" ? "45s" : "270s"; | ||
|
||
return ( | ||
<div | ||
ref={containerRef} | ||
className="scroller relative z-[20] max-w-3xl overflow-hidden [mask-image:linear-gradient(to_right,transparent,white_20%,white_80%,transparent)] pt-28" | ||
style={ | ||
{ | ||
"--duration": duration, | ||
} as React.CSSProperties | ||
} | ||
> | ||
<ul | ||
ref={scrollerRef} | ||
className={`flex min-w-full shrink-0 gap-2 py-4 w-max flex-nowrap items-center justify-center ${ | ||
start ? "animate-scroll" : "" | ||
} ${pauseOnHover ? "hover:[animation-play-state:paused]" : ""} ${ | ||
direction === "right" ? "[animation-direction:reverse]" : "" | ||
}`} | ||
> | ||
{items.map((item, idx) => ( | ||
<li | ||
key={`${item.name}-${idx}`} | ||
className="relative flex-shrink-0 group" | ||
style={{ transform: "skew(-20deg)" }} | ||
> | ||
{/* Info popup on hover */} | ||
<div className="absolute -top-[110px] left-[12%] -translate-x-1/2 opacity-0 group-hover:opacity-100 z-[300] pointer-events-none"> | ||
<div | ||
className="relative w-[200px] h-[100px]" | ||
style={{ | ||
transform: "skew(20deg)", | ||
}} | ||
> | ||
{/* Background chat box - positioned slightly offset */} | ||
<div className="absolute -right-1 -bottom-1 w-full h-full"> | ||
<Image | ||
src={boxBG} | ||
alt="Box Background" | ||
fill | ||
sizes="200px" | ||
loading="lazy" | ||
className="object-contain" | ||
/> | ||
</div> | ||
|
||
{/* Main chat box */} | ||
<Image | ||
src={box} | ||
alt="Box" | ||
fill | ||
sizes="200px" | ||
loading="lazy" | ||
className="object-contain" | ||
/> | ||
<div className="absolute w-full h-full flex flex-col items-center justify-center gap-0"> | ||
<p className="text-sm font-medium text-gray-200 leading-tight"> | ||
{item.name} | ||
</p> | ||
<p className="text-xs text-gray-400 leading-tight"> | ||
{item.title} | ||
</p> | ||
</div> | ||
</div> | ||
</div> | ||
|
||
{/* Card */} | ||
<div className="relative w-16 h-20 bg-black border-2 border-white overflow-hidden transition-transform group-hover:scale-105 group-hover:bg-[#006FB2]"> | ||
<div | ||
className="absolute inset-0 bg-black group-hover:bg-[#006FB2]" | ||
style={{ transform: "skew(20deg) scale(1.2)" }} | ||
> | ||
<Link | ||
href={item.link} | ||
target="_blank" | ||
className="relative block w-full h-full" | ||
> | ||
<Image | ||
src={item.image} | ||
alt={item.name} | ||
fill | ||
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw" | ||
className="object-cover opacity-75 group-hover:opacity-100" | ||
/> | ||
</Link> | ||
</div> | ||
</div> | ||
</li> | ||
))} | ||
</ul> | ||
</div> | ||
); | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
@keyframes scroll { | ||
from { | ||
transform: translateX(0); | ||
} | ||
to { | ||
transform: translateX(calc(-50%)); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[prettier] reported by reviewdog 🐶