11---
2- import { getCollection } from " astro:content" ;
2+ import { getCollection , type CollectionEntry } from " astro:content" ;
33import Layout from " ../layouts/Layout.astro" ;
44import Prose from " ../components/prose/prose.astro" ;
55import { Separator } from " ../components/separator/separator" ;
6+ import { Image } from " astro:assets" ;
7+
8+ type Speaker = CollectionEntry <" speakers" >;
69
7- // Fetch all speaker entries
810const speakersCollection = await getCollection (" speakers" );
911
10- // Define the type for the groups object
11- type Speaker = {
12- id: string ;
13- data: {
14- name: string ;
15- };
16- };
17-
18- type Groups = {
19- [key : string ]: Speaker [];
20- };
21-
22- // Group speakers by the first letter of their name
23- const groups: Groups = speakersCollection
24- .filter ((speaker : Speaker ) => !! speaker .data .name )
25- .reduce ((acc : Groups , speaker : Speaker ) => {
26- const letter = speaker .data .name [0 ].toUpperCase ();
27- if (! acc [letter ]) {
28- acc [letter ] = [];
12+ const speakersWithNames = speakersCollection .filter ((speaker ) =>
13+ !! speaker .data ?.name
14+ );
15+
16+ const getFirstLetter = (name : string ): string => name .charAt (0 ).toUpperCase ();
17+
18+ function createOptimalGroups(speakers : Speaker []): Record <string , Speaker []> {
19+ const sortedSpeakers = [... speakers ].sort ((a , b ) =>
20+ a .data .name .localeCompare (b .data .name )
21+ );
22+
23+ const groups: Record <string , Speaker []> = {};
24+ let currentGroup: Speaker [] = [];
25+ let currentLetter = ' ' ;
26+ let groupKey = ' ' ;
27+
28+ sortedSpeakers .forEach ((speaker ) => {
29+ const letter = getFirstLetter (speaker .data .name );
30+
31+ if (currentGroup .length === 0 ) {
32+ currentLetter = letter ;
33+ groupKey = letter ;
34+ currentGroup .push (speaker );
35+ }
36+ else if (letter === currentLetter && currentGroup .length < 15 ) {
37+ currentGroup .push (speaker );
2938 }
30- acc [letter ].push (speaker );
31- return acc ;
32- }, {} as Groups );
39+ else {
40+ if (currentGroup .length < 10 && currentGroup .length + 1 <= 15 ) {
41+ currentGroup .push (speaker );
42+ groupKey = ` ${currentLetter }-${letter } ` ;
43+ } else {
44+ groups [groupKey ] = currentGroup ;
45+ currentLetter = letter ;
46+ groupKey = letter ;
47+ currentGroup = [speaker ];
48+ }
49+ }
50+ });
3351
34- const letters = Object .keys (groups ).sort ((a , b ) => a .localeCompare (b ));
52+ if (currentGroup .length > 0 ) {
53+ groups [groupKey ] = currentGroup ;
54+ }
3555
36- const title = " Speakers" ;
56+ return groups ;
57+ }
3758
38- const description =
39- " Alphabetical list of all confirmed speakers for the conference" ;
59+ const speakerGroups = createOptimalGroups (speakersWithNames );
60+ const groupKeys = Object .keys (speakerGroups );
61+
62+ const title = " Speakers" ;
63+ const description = " Our conference speakers organized alphabetically" ;
4064---
4165
4266<Layout title ={ title } description ={ description } >
@@ -45,49 +69,68 @@ const description =
4569 <h1 >Speakers</h1 >
4670 </Prose >
4771
48- <div class =" flex text-3xl font-bold flex-wrap my-10 " >
72+ <div class =" flex text-xl md:text-2xl font-bold flex-wrap my-8 justify-center " >
4973 {
50- letters .map ((letter ) => (
51- <h3 class = " mr-2" >
52- <a href = { ` #letter-${letter } ` } >{ letter } </a >
53- </h3 >
74+ groupKeys .map ((key ) => (
75+ <a
76+ href = { ` #group-${key } ` }
77+ class = " mx-2 my-1 px-2 py-1 hover:text-primary hover:bg-gray-100 rounded transition-colors"
78+ >
79+ { key }
80+ </a >
5481 ))
5582 }
5683 </div >
5784
58- <ol class =" speakers" >
59- {
60- letters .map ((letter , index ) => (
61- <>
62- <div id = { ` letter-${letter } ` } >
63- <h2 class = " relative font-title text-primary font-bold mb-[0.6em] [&>a]:border-0 [&>a]:text-inherit text-4xl" >
64- { letter }
65- </h2 >
66-
67- <ul class = " pl-4" >
68- { groups [letter ]
69- .sort ((a , b ) => a .data .name .localeCompare (b .data .name ))
70- .map ((speaker ) => (
71- <li { ... { key: speaker .id }} class = " mb-1" >
72- <a
73- class = " underline hover:text-primary-hover"
74- href = { ` /speaker/${speaker .id } ` }
75- >
76- { speaker .data .name }
77- </a >
78- </li >
79- ))}
80- </ul >
85+ {
86+ groupKeys .map ((key , index ) => (
87+ <>
88+ <div id = { ` group-${key } ` } class = " pt-6" >
89+ <h2 class = " text-3xl md:text-4xl font-bold text-primary mb-6" >{ key } </h2 >
90+
91+ <div class = " grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-6 mb-10" >
92+ { speakerGroups [key ].map ((speaker ) => (
93+ <a
94+ href = { ` /speaker/${speaker .id } ` }
95+ class = " flex flex-col items-center group"
96+ >
97+ <div class = " w-full aspect-square rounded-full overflow-hidden mb-3 border-2 border-transparent group-hover:border-primary transition-all" >
98+ { speaker .data .avatar ? (
99+
100+ <Image
101+ src = { speaker .data .avatar }
102+ alt = { ` ${speaker .data .name }'s profile picture ` }
103+ height = { 200 }
104+ width = { 200 }
105+ class = " w-full h-full object-cover"
106+ />
107+ ) : (
108+ <div class = " w-full h-full bg-[#f7efe6] flex items-center justify-center text-gray-500 text-xl" >
109+ { speaker .data .name .charAt (0 )}
110+ </div >
111+ )}
112+ </div >
113+ <h3 class = " text-center font-medium group-hover:text-primary text-sm md:text-base" >
114+ { speaker .data .name }
115+ </h3 >
116+ </a >
117+ ))}
81118 </div >
119+ </div >
82120
83- { index !== letters .length - 1 ? (
84- <Separator />
85- ) : (
86- <div class = " mb-20" />
87- )}
88- </>
89- ))
90- }
91- </ol >
121+ { index !== groupKeys .length - 1 && <Separator />}
122+ </>
123+ ))
124+ }
92125 </div >
93126</Layout >
127+
128+ <style >
129+ a {
130+ transition: transform 0.2s ease;
131+ }
132+
133+ a.group:hover {
134+ transform: translateY(-3px);
135+ }
136+ </style >
0 commit comments