Skip to content

Commit 5a1c4bc

Browse files
authored
Merge branch 'master' into feat/improve-ticket-card-theme
2 parents 16bc909 + 5e9c297 commit 5a1c4bc

8 files changed

Lines changed: 228 additions & 67 deletions

File tree

.github/workflows/welcome-first-time-contrib.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@
44
name: Welcome first time contributors
55

66
on:
7-
pull_request:
8-
types:
9-
- opened
7+
pull_request_target: # Needed to write welcome comments on fork PRs; script only uses the GitHub API.
8+
types:
9+
- opened # zizmor: ignore[dangerous-triggers]
1010
issues:
1111
types:
1212
- opened
@@ -18,7 +18,7 @@ permissions:
1818
jobs:
1919
welcome:
2020
name: Post welcome message
21-
if: ${{ !contains(fromJson('["asyncapi-bot", "dependabot[bot]", "dependabot-preview[bot]", "allcontributors[bot]"]'), github.actor) }} # zizmor: ignore[obfuscation]
21+
if: ${{ !contains(fromJson('["asyncapi-bot", "dependabot[bot]", "dependabot-preview[bot]", "allcontributors[bot]"]'), github.event.issue.user.login || github.event.pull_request.user.login) }} # zizmor: ignore[obfuscation]
2222
runs-on: ubuntu-latest
2323
permissions:
2424
contents: read # Required to read repository data for checking if it's the user's first contribution

app/meetups/components/VenueSelector.tsx

Lines changed: 44 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,11 @@ function VenueSelector({
4343
</Paragraph>
4444
</motion.div>
4545

46-
<AnimatePresence>
46+
<AnimatePresence mode="wait">
4747
{!selectedVenue && (
4848
<motion.div
49-
className="mt-20"
49+
key="venue-list"
50+
className="mt-20 min-h-[220px]"
5051
variants={containerVariants}
5152
initial="hidden"
5253
animate="visible"
@@ -68,43 +69,50 @@ function VenueSelector({
6869
</div>
6970
</motion.div>
7071
)}
71-
</AnimatePresence>
7272

73-
{selectedVenue && (
74-
<div className="mt-20 text-left sm:mt-12">
75-
<Button
76-
type="button"
77-
className="mb-10 flex items-center sm:mb-6"
78-
onClick={() => onVenueSelect(null)}
79-
outline
80-
icon={<Arrow className="w-4 rotate-180" />}
81-
iconPosition="left"
73+
{selectedVenue && (
74+
<motion.div
75+
key="venue-details"
76+
className="mt-20 text-left sm:mt-12 min-h-[220px]"
77+
variants={containerVariants}
78+
initial="hidden"
79+
animate="visible"
80+
exit="hidden"
8281
>
83-
<div className="ml-2 text-white text-md">
84-
{selectedVenue.country}
85-
</div>
86-
</Button>
82+
<Button
83+
type="button"
84+
className="mb-10 flex items-center sm:mb-6"
85+
onClick={() => onVenueSelect(null)}
86+
outline
87+
icon={<Arrow className="w-4 rotate-180" />}
88+
iconPosition="left"
89+
>
90+
<div className="ml-2 text-white text-md">
91+
{selectedVenue.country}
92+
</div>
93+
</Button>
8794

88-
{selectedVenue.cities.length > 0 ? (
89-
<div className="grid grid-cols-3 gap-4 lg:grid-cols-2 sm:grid-cols-1">
90-
{selectedVenue.cities.map((city) => (
91-
<Button
92-
type="button"
93-
outline
94-
key={city.name}
95-
className="text-white border border-white/20 px-6 py-3 rounded-full text-center"
96-
>
97-
{city.name}
98-
</Button>
99-
))}
100-
</div>
101-
) : (
102-
<Paragraph className="text-gray-300 text-center">
103-
No meetups in this country yet. Stay tuned!
104-
</Paragraph>
105-
)}
106-
</div>
107-
)}
95+
{selectedVenue.cities.length > 0 ? (
96+
<div className="grid grid-cols-3 gap-4 lg:grid-cols-2 sm:grid-cols-1">
97+
{selectedVenue.cities.map((city) => (
98+
<Button
99+
type="button"
100+
outline
101+
key={city.name}
102+
className="text-white border border-white/20 px-6 py-3 rounded-full text-center"
103+
>
104+
{city.name}
105+
</Button>
106+
))}
107+
</div>
108+
) : (
109+
<Paragraph className="text-gray-300 text-center">
110+
No meetups in this country yet. Stay tuned!
111+
</Paragraph>
112+
)}
113+
</motion.div>
114+
)}
115+
</AnimatePresence>
108116
<motion.div variants={itemVariants}>
109117
<Paragraph className="mt-10" typeStyle="body-md">
110118
Please be sure to read and understand our Terms & Conditions for{' '}

components/Cards/PastEditionCard/PastEditionCard.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@ interface PastEditonCardProp {
99
const PastEditonCard = ({ url }: PastEditonCardProp): JSX.Element => {
1010
const year = url.split('.')[1];
1111
return (
12-
<div className="bg-white bg-opacity-10 backdrop-blur-lg bg-clip-padding shadow-lg p-4 w-full mx-auto border-[1.66px] border-[#FFFFFF66] rounded-xl">
12+
<div className="bg-white bg-opacity-10 backdrop-blur-lg bg-clip-padding shadow-lg p-4 w-full mx-auto border-[1.66px] border-[#FFFFFF66] rounded-xl transition-all hover:scale-[1.03]">
1313
<div className="flex items-center flex-wrap justify-between">
1414
<h1 className="text-2xl text-white font-semibold">{year}</h1>
15-
<div>
15+
<div className='transition'>
1616
<a
1717
href={url}
1818
rel="noreferrer"

components/Navbar/navDrop.tsx

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import Dropdown from '../illustration/dropdown';
1010
import { LinkItem } from '../../types/types';
1111
import { isExternalUrl, resolveCfpUrl } from '../../utils/pretalx';
1212
import { links } from '../../config/navigation';
13+
import { usePathname } from 'next/navigation';
14+
import { useSectionTracker } from '../../hooks/useSectionTracker';
1315

1416
interface INavDropProp {
1517
setDrop: Dispatch<SetStateAction<boolean>>;
@@ -18,6 +20,9 @@ interface INavDropProp {
1820
const NavDrop = forwardRef<HTMLDivElement, INavDropProp>(
1921
({ setDrop }, ref): JSX.Element => {
2022
const [show, setShow] = useState<string | null>(null);
23+
const pathname = usePathname();
24+
const { isActive } = useSectionTracker(pathname);
25+
2126
return (
2227
<div
2328
ref={ref}
@@ -55,7 +60,11 @@ const NavDrop = forwardRef<HTMLDivElement, INavDropProp>(
5560
className="flex items-center"
5661
onClick={(e) => e.preventDefault()}
5762
>
58-
<div className="text-white">{link.title}</div>
63+
<div
64+
className={`text-white ${isActive(link) ? 'font-semibold border-l-2 border-[#C6BED9] pl-2 text-[#C6BED9]' : ''}`}
65+
>
66+
{link.title}
67+
</div>
5968
<Dropdown
6069
fill="white"
6170
className={`ml-2 transition-transform duration-500 ${
@@ -70,7 +79,11 @@ const NavDrop = forwardRef<HTMLDivElement, INavDropProp>(
7079
<div
7180
data-test={`nav-sub-${sub.title}`}
7281
onClick={() => setDrop(false)}
73-
className="h-[40px] flex navbg items-center p-6 hover:text-black text-white cursor-pointer"
82+
className={`h-[40px] flex navbg items-center p-6 hover:text-black text-white cursor-pointer ${
83+
pathname === sub.ref
84+
? 'font-semibold bg-white/10 text-[#C6BED9]'
85+
: ''
86+
}`}
7487
>
7588
{sub.title}
7689
</div>
@@ -80,7 +93,14 @@ const NavDrop = forwardRef<HTMLDivElement, INavDropProp>(
8093
)}
8194
</div>
8295
) : (
83-
<div className="text-white" onClick={() => setDrop(false)}>
96+
<div
97+
className={`text-white ${
98+
isActive(link)
99+
? 'font-semibold border-l-2 border-[#C6BED9] pl-2 text-[#C6BED9]'
100+
: ''
101+
}`}
102+
onClick={() => setDrop(false)}
103+
>
84104
{link.title}
85105
</div>
86106
)}

components/Navbar/navbar.tsx

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
'use client';
2+
13
import Link from 'next/link';
24
import Dropdown from '../illustration/dropdown';
35
import { useState, useEffect, useRef, useCallback, JSX } from 'react';
@@ -9,9 +11,13 @@ import Image from 'next/image';
911
import { LinkItem } from '../../types/types';
1012
import { isExternalUrl, resolveCfpUrl } from '../../utils/pretalx';
1113
import { links } from '../../config/navigation';
14+
import { usePathname } from 'next/navigation';
15+
import { useSectionTracker } from '../../hooks/useSectionTracker';
1216

1317
function Navbar(): JSX.Element {
1418
const isTablet = useMediaQuery({ maxWidth: '1118px' });
19+
const pathname = usePathname();
20+
const { isActive } = useSectionTracker(pathname);
1521
const [drop, setDrop] = useState<boolean>(false);
1622
const [show, setShow] = useState<string | null>(null);
1723
const [isSubMenuHovered, setIsSubMenuHovered] = useState<boolean>(false);
@@ -210,8 +216,20 @@ function Navbar(): JSX.Element {
210216
</Link>
211217
)}
212218
</div>
213-
<span className="after:absolute after:-bottom-1 after:left-1/2 after:w-0 after:transition-all after:h-0.5 after:bg-white after:group-hover:w-3/6 "></span>
214-
<span className="after:absolute after:-bottom-1 after:right-1/2 after:w-0 after:transition-all after:h-0.5 after:bg-white after:group-hover:w-3/6"></span>
219+
<span
220+
className={`after:absolute after:-bottom-1 after:left-1/2 after:transition-all after:h-0.5 after:bg-white ${
221+
isActive(link)
222+
? 'after:w-3/6'
223+
: 'after:w-0 after:group-hover:w-3/6'
224+
}`}
225+
></span>
226+
<span
227+
className={`after:absolute after:-bottom-1 after:right-1/2 after:transition-all after:h-0.5 after:bg-white ${
228+
isActive(link)
229+
? 'after:w-3/6'
230+
: 'after:w-0 after:group-hover:w-3/6'
231+
}`}
232+
></span>
215233
{show === link.title && link.subMenu && (
216234
<div
217235
className="subMenu absolute z-[9] mt-8 min-w-[150px] whitespace-nowrap rounded-md left-[-15px] gradient-bg px-2 py-1 flex flex-col justify-center space-y-0"
@@ -226,7 +244,11 @@ function Navbar(): JSX.Element {
226244
ref={(el) => {
227245
subMenuRefs.current[index] = el;
228246
}}
229-
className={`flex items-center ${link.subMenu!.length === 1 ? 'justify-center' : 'justify-start'} min-h-[32px] text-[16px] hover:scale-95 hover:translate-x-1 transition-all focus:outline-none focus:bg-white focus:bg-opacity-20 focus:scale-95 focus:translate-x-1 rounded px-2 py-1`}
247+
className={`flex items-center ${link.subMenu!.length === 1 ? 'justify-center' : 'justify-start'} min-h-[32px] text-[16px] hover:scale-95 hover:translate-x-1 transition-all focus:outline-none focus:bg-white focus:bg-opacity-20 focus:scale-95 focus:translate-x-1 rounded px-2 py-1 ${
248+
isActive(subL)
249+
? 'bg-white bg-opacity-10 font-semibold text-[#C6BED9]'
250+
: ''
251+
}`}
230252
data-test={`nav-sub-${subL.title}`}
231253
onKeyDown={(e) => {
232254
const currentIndex = index;

hooks/useSectionTracker.ts

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import { useState, useEffect } from 'react';
2+
import { LinkItem } from '../types/types';
3+
4+
/**
5+
* Tracks the currently active navbar section based on scroll position and URL hash.
6+
* Uses IntersectionObserver for smooth scroll-spy on the home page and listens to
7+
* hashchange/click events for Next.js client-side transitions.
8+
*
9+
* @returns `activeSection` - the ID of the section in view, and `isActive` - a helper
10+
* that checks whether a given nav link should be highlighted.
11+
*/
12+
export function useSectionTracker(pathname: string) {
13+
const [activeSection, setActiveSection] = useState<string>('');
14+
15+
useEffect(() => {
16+
// 1. Initialize active hash from window.location.hash
17+
const updateHash = () => {
18+
const hash = window.location.hash.replace('#', '');
19+
if (hash) {
20+
setActiveSection(hash);
21+
}
22+
};
23+
updateHash();
24+
25+
// 2. Click listener for Next.js soft transitions
26+
const handleDocumentClick = () => {
27+
setTimeout(updateHash, 50);
28+
};
29+
30+
// 3. Scroll spy listener using IntersectionObserver on home page
31+
let observer: IntersectionObserver | null = null;
32+
if (pathname === '/') {
33+
const sections = ['about', 'speakers', 'tickets', 'sponsors'];
34+
const elements = sections
35+
.map((id) => document.getElementById(id))
36+
.filter(Boolean) as HTMLElement[];
37+
38+
if (elements.length > 0) {
39+
observer = new IntersectionObserver(
40+
(entries) => {
41+
entries.forEach((entry) => {
42+
if (entry.isIntersecting) {
43+
setActiveSection(entry.target.id);
44+
}
45+
});
46+
},
47+
{
48+
root: null,
49+
rootMargin: '-30% 0px -50% 0px',
50+
threshold: 0,
51+
}
52+
);
53+
elements.forEach((el) => observer?.observe(el));
54+
}
55+
} else {
56+
setActiveSection('');
57+
}
58+
59+
// 4. Scroll position tracking: if scrolled to the top, reset active state
60+
const handleScroll = () => {
61+
if (window.scrollY < 100 && pathname === '/') {
62+
setActiveSection('');
63+
}
64+
};
65+
66+
window.addEventListener('hashchange', updateHash);
67+
document.addEventListener('click', handleDocumentClick);
68+
window.addEventListener('scroll', handleScroll, { passive: true });
69+
70+
return () => {
71+
window.removeEventListener('hashchange', updateHash);
72+
document.removeEventListener('click', handleDocumentClick);
73+
window.removeEventListener('scroll', handleScroll);
74+
if (observer) {
75+
observer.disconnect();
76+
}
77+
};
78+
}, [pathname]);
79+
80+
const isActive = (link: LinkItem): boolean => {
81+
if (link.ref.includes('#')) {
82+
const [path, hash] = link.ref.split('#');
83+
const isCorrectPath = pathname === (path || '/');
84+
const isCorrectHash = activeSection === hash;
85+
return isCorrectPath && isCorrectHash;
86+
}
87+
if (link.ref === pathname) return true;
88+
if (link.subMenu) {
89+
return link.subMenu.some((sub) => {
90+
if (sub.ref.includes('#')) {
91+
const [path, hash] = sub.ref.split('#');
92+
return pathname === (path || '/') && activeSection === hash;
93+
}
94+
return sub.ref === pathname;
95+
});
96+
}
97+
return false;
98+
};
99+
100+
return { activeSection, isActive };
101+
}

0 commit comments

Comments
 (0)