Skip to content

Commit e5c31da

Browse files
committed
Display socials in consistent order
1 parent 4c3ae62 commit e5c31da

File tree

6 files changed

+195
-109
lines changed

6 files changed

+195
-109
lines changed
Lines changed: 50 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,15 @@
11
import clsx from "clsx"
22
import Image from "next-image-export-optimizer"
3-
import type { StaticImageData } from "next/image"
43

5-
import TwitterXIcon from "@/icons/twitter.svg?svgr"
6-
import LinkedInIcon from "@/icons/linkedin.svg?svgr"
74
import { eventsColors } from "../utils"
85

96
import { Anchor } from "../../_design-system/anchor"
107
import { Tag } from "../../_design-system/tag"
118
import { SchedSpeaker } from "../../2023/types"
12-
import {
13-
SocialMediaIcon,
14-
SocialMediaIconServiceType,
15-
} from "../../_components/speakers/social-media"
9+
import { StripesDecoration } from "../../_design-system/stripes-decoration"
10+
import { SocialIcon, SocialIconType } from "../../_design-system/social-icon"
1611

1712
export interface SpeakerCardProps extends React.HTMLAttributes<HTMLDivElement> {
18-
imageUrl?: string | StaticImageData
1913
tags?: string[]
2014
isReturning?: boolean
2115
stripes?: string
@@ -33,13 +27,12 @@ function Stripes({ mask }: { mask?: string }) {
3327
WebkitMaskImage: mask,
3428
}}
3529
>
36-
<div className="absolute inset-0 bg-gradient-to-b from-sec-dark/50 to-sec-light/50" />
30+
<StripesDecoration oddClassName="absolute inset-0 bg-gradient-to-b from-sec-dark to-sec-light" />
3731
</div>
3832
)
3933
}
4034

4135
export function SpeakerCard({
42-
imageUrl,
4336
tags = [],
4437
className,
4538
speaker,
@@ -55,31 +48,29 @@ export function SpeakerCard({
5548
{...props}
5649
>
5750
<div className="flex gap-6 p-6">
58-
{imageUrl && (
59-
<div className="relative aspect-square size-[236px] shrink-0 overflow-hidden">
60-
<div className="absolute inset-0 z-[1] bg-sec-light opacity-90 mix-blend-multiply" />
51+
<SpeakerLinks speaker={speaker} className="absolute right-6 top-6" />
52+
{speaker.avatar && (
53+
<div className="relative aspect-square shrink-0 overflow-hidden">
54+
<div className="absolute inset-0 z-[1] bg-sec-light mix-blend-multiply" />
6155
<Image
62-
src={imageUrl}
56+
src={speaker.avatar}
6357
alt=""
64-
width={312}
65-
height={312}
66-
className="size-full object-cover saturate-[0.1] transition-transform"
67-
/>
68-
<Stripes
69-
// TODO!
70-
mask={""}
58+
width={176}
59+
height={176}
60+
className="size-full object-cover saturate-[.1] transition-transform"
7161
/>
62+
<Stripes mask="radial-gradient(ellipse at top left, hsl(var(--color-pri-base)) 0%, hsl(var(--color-pri-base)) 5%, transparent 40%, transparent, transparent 85%, black 100%)" />
7263
</div>
7364
)}
7465
<div className="flex flex-1 flex-col gap-2">
7566
<div className="flex flex-col gap-1">
7667
<h3 className="typography-body-lg">{speaker.name}</h3>
77-
<p className="text-neu-700 typography-body-sm">
68+
<p className="typography-body-sm text-neu-800">
7869
{[speaker.position, speaker.company].filter(Boolean).join(", ")}
7970
</p>
8071
</div>
8172
{speaker.about && (
82-
<p className="text-neu-700 typography-body-sm">{speaker.about}</p>
73+
<p className="typography-body-sm text-neu-800">{speaker.about}</p>
8374
)}
8475
{/* TODO: We'll have to collect it when fetching all sessions. */}
8576
{tags.length > 0 && (
@@ -91,36 +82,48 @@ export function SpeakerCard({
9182
))}
9283
</div>
9384
)}
94-
<div className="flex gap-4">
95-
{speaker.socialurls?.length ? (
96-
<div className="mt-0 text-[#765e5e]">
97-
<div className="flex space-x-2">
98-
{speaker.socialurls.map(social => (
99-
<a
100-
key={social.url}
101-
href={social.url}
102-
target="_blank"
103-
rel="noreferrer"
104-
className="flex items-center text-blk"
105-
>
106-
<SocialMediaIcon
107-
service={
108-
social.service.toLowerCase() as SocialMediaIconServiceType
109-
}
110-
/>
111-
</a>
112-
))}
113-
</div>
114-
</div>
115-
) : null}
116-
</div>
11785
</div>
11886
</div>
11987
<Anchor
12088
href={`/conf/${year}/speakers/${speaker.username}`}
121-
className="absolute inset-0"
89+
className="absolute inset-0 z-[1] ring-inset ring-neu-400 hover:ring-1 dark:ring-neu-100"
12290
title={`See ${speaker.name.split(" ")[0]}'s sessions`}
12391
/>
12492
</article>
12593
)
12694
}
95+
96+
function SpeakerLinks({
97+
speaker,
98+
className,
99+
}: {
100+
speaker: SchedSpeaker
101+
className?: string
102+
}) {
103+
const speakerUrls = SocialIconType.all
104+
.map(social => speaker.socialurls.find(x => x.service === social))
105+
.concat([{ service: "website", url: speaker.url || "" }])
106+
.filter((x): x is Exclude<typeof x, undefined> => !!x?.url)
107+
.slice(-3)
108+
109+
return (
110+
<div
111+
className={clsx(
112+
"z-[3] flex divide-x divide-neu-200 border border-neu-200",
113+
className,
114+
)}
115+
>
116+
{speakerUrls.map(social => (
117+
<a
118+
key={social.url}
119+
href={social.url}
120+
target="_blank"
121+
rel="noreferrer"
122+
className="flex items-center p-2 text-neu-900"
123+
>
124+
<SocialIcon type={social.service.toLowerCase()} className="size-5" />
125+
</a>
126+
))}
127+
</div>
128+
)
129+
}

src/app/conf/2025/components/top-minds/index.tsx

Lines changed: 54 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -4,44 +4,61 @@ import Image from "next-image-export-optimizer"
44
import type { StaticImageData } from "next/image"
55

66
import TwitterIcon from "@/icons/twitter.svg"
7+
import LinkedInIcon from "@/icons/linkedin-filled.svg"
8+
79
import { Button } from "@/app/conf/_design-system/button"
810
import { BECOME_A_SPEAKER_LINK } from "../../links"
911
import { StripesDecoration } from "@/app/conf/_design-system/stripes-decoration"
12+
import {
13+
SocialIconType,
14+
SocialIcon,
15+
urlForUser,
16+
} from "@/app/conf/_design-system/social-icon"
1017

1118
const previousConfSpeakers = {
1219
benjie: {
1320
name: "Benjie Gillam",
1421
title: "GraphQL TSC & Spec",
1522
src: "https://avatars.sched.co/b/99/18743846/avatar.jpg.320x320px.jpg",
16-
twitter: "benjie",
17-
linkedin: "benjiegillam",
23+
socials: {
24+
twitter: "benjie",
25+
linkedin: "benjiegillam",
26+
},
1827
},
1928
kewei: {
2029
name: "Kewei Qu",
2130
title: "Meta, Senior Staff Engineer",
2231
src: "https://avatars.sched.co/9/1a/18743864/avatar.jpg.320x320px.jpg",
23-
twitter: "kewei_qu",
24-
linkedin: "keweiqu",
32+
socials: {
33+
twitter: "kewei_qu",
34+
linkedin: "keweiqu",
35+
},
2536
},
2637
donna: {
2738
name: "Donna Zhou",
2839
title: "Atlassian, GraphQL Java",
2940
src: "https://avatars.sched.co/0/1d/18743879/avatar.jpg.320x320px.jpg?e1f",
30-
linkedin: "donnazhou",
41+
socials: {
42+
linkedin: "donnazhou",
43+
},
3144
},
3245
uri: {
3346
name: "Uri Goldshtein",
3447
title: "The Guild, Founder",
3548
src: "https://avatars.sched.co/8/2b/14900013/avatar.jpg.320x320px.jpg?9f1",
36-
twitter: "UriGoldshtein",
37-
linkedin: "urigo",
49+
socials: {
50+
twitter: "UriGoldshtein",
51+
linkedin: "urigo",
52+
},
3853
},
3954
alessia: {
4055
name: "Alessia Bellisario",
4156
title: "Apollo, Staff Engineer",
4257
src: "https://avatars.sched.co/a/c6/18743837/avatar.jpg.320x320px.jpg?847",
43-
twitter: "alessbell",
44-
linkedin: "alessiabellisario",
58+
socials: {
59+
twitter: "alessbell",
60+
linkedin: "alessiabellisario",
61+
},
4562
},
4663
}
4764

@@ -63,30 +80,30 @@ export default function TopMindsSection({
6380
{...rest}
6481
>
6582
<div className="flex grid-cols-2 flex-wrap [@media(444px<width<743px)]:grid [@media(width<=444px)]:flex-col [@media(width<=743px)]:justify-center [@media(width>=970px)]:*:border-b-0">
66-
<h3 className="mr-auto flex w-full grow text-pretty pb-6 pr-6 typography-h2 [@media(width>857px)]:basis-0">
83+
<h3 className="typography-h2 mr-auto flex w-full grow text-pretty pb-6 pr-6 [@media(width>857px)]:basis-0">
6784
Meet the top industry minds
6885
</h3>
69-
<SpeakerCard
86+
<TopMindCard
7087
{...previousConfSpeakers.benjie}
7188
stripes="linear-gradient(80deg, hsl(var(--color-pri-base)) 0%, hsl(var(--color-pri-base)) 5%, transparent 40%, transparent)"
7289
/>
73-
<SpeakerCard
90+
<TopMindCard
7491
{...previousConfSpeakers.kewei}
7592
className="[@media(width<=742px)]:border-l"
7693
stripes="radial-gradient(circle at bottom right, hsl(var(--color-pri-base)) 0%, hsl(var(--color-pri-base)) 10%, transparent 40%, transparent)"
7794
/>
7895
<div className="flex grow border-sec-dark [@media(width<970px)]:contents [@media(width>=970px)]:border-t [@media(width>=970px)]:*:border-t-0">
79-
<SpeakerCard
96+
<TopMindCard
8097
{...previousConfSpeakers.donna}
8198
className="[@media(744px<=width<=970px)]:first-of-type:border-l-0"
8299
stripes="radial-gradient(ellipse at top left, hsl(var(--color-pri-base)) 0%, hsl(var(--color-pri-base)) 5%, transparent 40%, transparent, transparent 85%, black 100%)"
83100
/>
84-
<SpeakerCard
101+
<TopMindCard
85102
{...previousConfSpeakers.uri}
86103
className="[@media(639px<=width<=970px)]:border-l"
87104
stripes="linear-gradient(-40deg, hsl(var(--color-pri-base)) 0%, hsl(var(--color-pri-base)) 5%, transparent 40%, transparent)"
88105
/>
89-
<SpeakerCard
106+
<TopMindCard
90107
{...previousConfSpeakers.alessia}
91108
className="[@media(width<744px)]:border-l"
92109
stripes="radial-gradient(circle at top left, transparent 0%, transparent 65%, black 90%)"
@@ -108,23 +125,23 @@ export default function TopMindsSection({
108125
)
109126
}
110127

111-
function SpeakerCard({
112-
name,
113-
title,
114-
src,
115-
twitter,
116-
linkedin,
117-
className,
118-
stripes,
119-
}: {
128+
export interface TopMindCardProps {
120129
name: string
121130
title: string
122131
src: string | StaticImageData
123-
twitter?: string
124-
linkedin?: string
125132
className?: string
126133
stripes?: string
127-
}) {
134+
socials: Partial<Record<SocialIconType, string>>
135+
}
136+
137+
function TopMindCard({
138+
name,
139+
title,
140+
src,
141+
className,
142+
stripes,
143+
socials,
144+
}: TopMindCardProps) {
128145
return (
129146
<article
130147
className={clsx(
@@ -146,51 +163,28 @@ function SpeakerCard({
146163
<div className="flex flex-1 items-stretch border-t border-sec-dark">
147164
<div className="flex grow flex-col justify-center gap-1 p-3 sm:h-[80px]">
148165
<h4 className="typography-body-md">{name}</h4>
149-
<p className="text-neu-700 typography-body-xs">{title}</p>
166+
<p className="typography-body-xs text-neu-700">{title}</p>
150167
</div>
151-
{(linkedin || twitter) && (
152-
<div className="flex border-l border-sec-dark max-sm:divide-x sm:flex-col sm:items-center sm:divide-y sm:border-l">
153-
{linkedin && (
168+
<div className="flex border-l border-sec-dark max-sm:divide-x sm:flex-col sm:items-center sm:divide-y sm:border-l">
169+
{SocialIconType.all.map(type => {
170+
if (!socials[type]) return null
171+
return (
154172
<a
155-
href={`https://www.linkedin.com/in/${linkedin}`}
156-
target="_blank"
157-
rel="noopener noreferrer"
158-
className="flex grow items-center justify-center p-4 transition-colors hover:bg-neu-900/10 hover:text-neu-700 sm:p-2"
159-
>
160-
<LinkedInIcon />
161-
</a>
162-
)}
163-
{twitter && (
164-
<a
165-
href={`https://x.com/${twitter}`}
173+
href={urlForUser(type, socials[type])}
166174
target="_blank"
167175
rel="noopener noreferrer"
168176
className="flex grow items-center justify-center border-sec-dark p-4 transition-colors hover:bg-neu-900/10 hover:text-neu-700 sm:p-2"
169177
>
170-
<TwitterIcon className="size-6" />
178+
<SocialIcon type={type} className="size-6" />
171179
</a>
172-
)}
173-
</div>
174-
)}
180+
)
181+
})}
182+
</div>
175183
</div>
176184
</article>
177185
)
178186
}
179187

180-
function LinkedInIcon(props: HTMLAttributes<SVGElement>) {
181-
return (
182-
<svg
183-
width="24"
184-
height="24"
185-
viewBox="0 0 24 24"
186-
fill="currentColor"
187-
{...props}
188-
>
189-
<path d="M19 3C19.5304 3 20.0391 3.21071 20.4142 3.58579C20.7893 3.96086 21 4.46957 21 5V19C21 19.5304 20.7893 20.0391 20.4142 20.4142C20.0391 20.7893 19.5304 21 19 21H5C4.46957 21 3.96086 20.7893 3.58579 20.4142C3.21071 20.0391 3 19.5304 3 19V5C3 4.46957 3.21071 3.96086 3.58579 3.58579C3.96086 3.21071 4.46957 3 5 3H19ZM18.5 18.5V13.2C18.5 12.3354 18.1565 11.5062 17.5452 10.8948C16.9338 10.2835 16.1046 9.94 15.24 9.94C14.39 9.94 13.4 10.46 12.92 11.24V10.13H10.13V18.5H12.92V13.57C12.92 12.8 13.54 12.17 14.31 12.17C14.6813 12.17 15.0374 12.3175 15.2999 12.5801C15.5625 12.8426 15.71 13.1987 15.71 13.57V18.5H18.5ZM6.88 8.56C7.32556 8.56 7.75288 8.383 8.06794 8.06794C8.383 7.75288 8.56 7.32556 8.56 6.88C8.56 5.95 7.81 5.19 6.88 5.19C6.43178 5.19 6.00193 5.36805 5.68499 5.68499C5.36805 6.00193 5.19 6.43178 5.19 6.88C5.19 7.81 5.95 8.56 6.88 8.56ZM8.27 18.5V10.13H5.5V18.5H8.27Z" />
190-
</svg>
191-
)
192-
}
193-
194188
function Stripes({ mask }: { mask?: string }) {
195189
return (
196190
<div

src/app/conf/_components/speakers/social-media.tsx

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,27 @@
1-
import { TwitterIcon, FacebookIcon, LinkedInIcon, InstagramIcon } from "@/icons"
1+
import {
2+
TwitterIcon,
3+
FacebookIcon,
4+
LinkedInIcon,
5+
InstagramIcon,
6+
GlobeIcon,
7+
} from "@/icons"
8+
import clsx from "clsx"
29

310
export type SocialMediaIconServiceType =
411
| "twitter"
512
| "linkedin"
613
| "facebook"
714
| "instagram"
15+
| "website"
816

917
export const SocialMediaIcon = ({
1018
service,
19+
className,
1120
}: {
1221
service: SocialMediaIconServiceType
22+
className?: string
1323
}) => {
14-
const classes = "h-7 hover:text-primary transition-colors"
24+
const classes = clsx("h-7 hover:text-primary transition-colors", className)
1525

1626
switch (service) {
1727
case "twitter":
@@ -22,6 +32,8 @@ export const SocialMediaIcon = ({
2232
return <FacebookIcon className={classes} />
2333
case "instagram":
2434
return <InstagramIcon className={classes} />
35+
case "website":
36+
return <GlobeIcon className={classes} />
2537
default:
2638
throw new Error(`Can't found social icon for "${service}" service.`)
2739
}

0 commit comments

Comments
 (0)