Skip to content

Commit a7ecc18

Browse files
committed
adapted team cards
1 parent 1b4aa8c commit a7ecc18

File tree

8 files changed

+924
-44
lines changed

8 files changed

+924
-44
lines changed

package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@
1111
"dependencies": {
1212
"@astrojs/mdx": "^4.2.3",
1313
"@astrojs/react": "^4.2.3",
14+
"@radix-ui/react-avatar": "^1.1.4",
15+
"@radix-ui/react-dialog": "^1.1.7",
16+
"@radix-ui/react-separator": "^1.1.3",
17+
"@radix-ui/react-slot": "^1.2.0",
1418
"@tailwindcss/vite": "^4.1.3",
1519
"@types/canvas-confetti": "^1.9.0",
1620
"@types/react": "^19.1.0",

pnpm-lock.yaml

Lines changed: 468 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/components/TeamMemberCard.tsx

Lines changed: 160 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,34 @@
1-
import { Mail, Globe } from 'lucide-react';
1+
"use client"
2+
3+
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
4+
import {
5+
Dialog,
6+
DialogContent,
7+
DialogDescription,
8+
DialogHeader,
9+
DialogTitle,
10+
DialogTrigger,
11+
} from "@/components/ui/dialog"
12+
import { Separator } from "@/components/ui/separator"
13+
import { Calendar, Mail, User } from "lucide-react"
14+
import { useState } from "react"
215

316
interface TeamMemberProps {
4-
name: string;
5-
role: string;
6-
image: string;
7-
bio: string;
8-
email?: string;
9-
website?: string;
17+
name: string
18+
role: string
19+
image: string
20+
bio: string
21+
email?: string
22+
website?: string
23+
research_interests?: string
24+
professional_career?: Array<{
25+
period: string
26+
position: string
27+
}>
28+
education?: Array<{
29+
period: string
30+
degree: string
31+
}>
1032
}
1133

1234
export default function TeamMemberCard({
@@ -16,41 +38,137 @@ export default function TeamMemberCard({
1638
bio,
1739
email,
1840
website,
41+
research_interests,
42+
professional_career,
43+
education,
1944
}: TeamMemberProps) {
45+
const [isOpen, setIsOpen] = useState(false)
46+
2047
return (
21-
<div className="flex flex-col items-center space-y-4 rounded-lg border p-6 text-center">
22-
<div className="relative h-32 w-32 overflow-hidden rounded-full">
23-
<img
24-
src={image}
25-
alt={name}
26-
className="h-full w-full object-cover"
27-
/>
28-
</div>
29-
<div className="space-y-2">
30-
<h3 className="text-xl font-bold">{name}</h3>
31-
<p className="text-sm text-muted-foreground">{role}</p>
32-
<p className="text-sm">{bio}</p>
33-
</div>
34-
<div className="flex space-x-4">
35-
{email && (
36-
<a
37-
href={`mailto:${email}`}
38-
className="text-muted-foreground hover:text-foreground"
39-
>
40-
<Mail className="h-5 w-5" />
41-
</a>
42-
)}
43-
{website && (
44-
<a
45-
href={website}
46-
target="_blank"
47-
rel="noopener noreferrer"
48-
className="text-muted-foreground hover:text-foreground"
49-
>
50-
<Globe className="h-5 w-5" />
51-
</a>
52-
)}
53-
</div>
54-
</div>
55-
);
48+
<Dialog open={isOpen} onOpenChange={setIsOpen}>
49+
<DialogTrigger asChild>
50+
<div className="flex flex-col items-center space-y-4 rounded-lg border p-6 text-center cursor-pointer hover:bg-accent/50 transition-colors">
51+
<Avatar className="h-24 w-24">
52+
<AvatarImage src={`/saezlab.org-draft/team_images/${image}`} alt={name} />
53+
<AvatarFallback>
54+
{name
55+
.split(" ")
56+
.map((n) => n[0])
57+
.join("")}
58+
</AvatarFallback>
59+
</Avatar>
60+
<div className="space-y-2">
61+
<h3 className="text-xl font-bold">{name}</h3>
62+
<p className="text-sm text-muted-foreground">{role}</p>
63+
</div>
64+
</div>
65+
</DialogTrigger>
66+
<DialogContent className="sm:max-w-[800px] max-h-[85vh] overflow-y-auto p-6">
67+
<DialogHeader className="flex flex-col sm:flex-row sm:items-start gap-4 mb-6">
68+
<Avatar className="w-28 h-28 border">
69+
<AvatarImage src={`/saezlab.org-draft/team_images/${image}`} alt={name} />
70+
<AvatarFallback>
71+
{name
72+
.split(" ")
73+
.map((n) => n[0])
74+
.join("")}
75+
</AvatarFallback>
76+
</Avatar>
77+
<div className="space-y-2">
78+
<DialogTitle className="text-2xl">{name}</DialogTitle>
79+
<DialogDescription className="text-lg font-medium">{role}</DialogDescription>
80+
</div>
81+
</DialogHeader>
82+
83+
<div className="space-y-8">
84+
{/* Biography Section */}
85+
<section>
86+
<h3 className="font-semibold text-xl mb-3">Biography</h3>
87+
<p className="text-muted-foreground">{bio}</p>
88+
</section>
89+
90+
{research_interests && (
91+
<>
92+
<Separator />
93+
<section>
94+
<h3 className="font-semibold text-xl mb-3">Research Interests</h3>
95+
<p className="text-muted-foreground">{research_interests}</p>
96+
</section>
97+
</>
98+
)}
99+
100+
{professional_career && professional_career.length > 0 && (
101+
<>
102+
<Separator />
103+
<section>
104+
<h3 className="font-semibold text-xl mb-3">Professional Career</h3>
105+
<div className="space-y-5">
106+
{professional_career.map((item, index) => (
107+
<div key={index} className="flex gap-4">
108+
<div className="flex-shrink-0 mt-1">
109+
<Calendar className="h-5 w-5 text-muted-foreground" />
110+
</div>
111+
<div>
112+
<p className="font-medium">{item.period}</p>
113+
<p className="text-muted-foreground">{item.position}</p>
114+
</div>
115+
</div>
116+
))}
117+
</div>
118+
</section>
119+
</>
120+
)}
121+
122+
{education && education.length > 0 && (
123+
<>
124+
<Separator />
125+
<section>
126+
<h3 className="font-semibold text-xl mb-3">Education</h3>
127+
<div className="space-y-5">
128+
{education.map((item, index) => (
129+
<div key={index} className="flex gap-4">
130+
<div className="flex-shrink-0 mt-1">
131+
<Calendar className="h-5 w-5 text-muted-foreground" />
132+
</div>
133+
<div>
134+
<p className="font-medium">{item.period}</p>
135+
<p className="text-muted-foreground">{item.degree}</p>
136+
</div>
137+
</div>
138+
))}
139+
</div>
140+
</section>
141+
</>
142+
)}
143+
144+
{(email || website) && (
145+
<>
146+
<Separator />
147+
<section>
148+
<h3 className="font-semibold text-xl mb-3">Contact Information</h3>
149+
<div className="space-y-3">
150+
{email && (
151+
<div className="flex items-center gap-2">
152+
<Mail className="h-5 w-5 text-muted-foreground" />
153+
<a href={`mailto:${email}`} className="text-blue-600 hover:underline">
154+
{email}
155+
</a>
156+
</div>
157+
)}
158+
{website && (
159+
<div className="flex items-center gap-2">
160+
<User className="h-5 w-5 text-muted-foreground" />
161+
<a href={website} target="_blank" rel="noopener noreferrer" className="text-blue-600 hover:underline">
162+
Website
163+
</a>
164+
</div>
165+
)}
166+
</div>
167+
</section>
168+
</>
169+
)}
170+
</div>
171+
</DialogContent>
172+
</Dialog>
173+
)
56174
}

src/components/ui/avatar.tsx

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import * as React from "react"
2+
import * as AvatarPrimitive from "@radix-ui/react-avatar"
3+
4+
import { cn } from "@/lib/utils"
5+
6+
function Avatar({
7+
className,
8+
...props
9+
}: React.ComponentProps<typeof AvatarPrimitive.Root>) {
10+
return (
11+
<AvatarPrimitive.Root
12+
data-slot="avatar"
13+
className={cn(
14+
"relative flex size-8 shrink-0 overflow-hidden rounded-full",
15+
className
16+
)}
17+
{...props}
18+
/>
19+
)
20+
}
21+
22+
function AvatarImage({
23+
className,
24+
...props
25+
}: React.ComponentProps<typeof AvatarPrimitive.Image>) {
26+
return (
27+
<AvatarPrimitive.Image
28+
data-slot="avatar-image"
29+
className={cn("aspect-square size-full", className)}
30+
{...props}
31+
/>
32+
)
33+
}
34+
35+
function AvatarFallback({
36+
className,
37+
...props
38+
}: React.ComponentProps<typeof AvatarPrimitive.Fallback>) {
39+
return (
40+
<AvatarPrimitive.Fallback
41+
data-slot="avatar-fallback"
42+
className={cn(
43+
"bg-muted flex size-full items-center justify-center rounded-full",
44+
className
45+
)}
46+
{...props}
47+
/>
48+
)
49+
}
50+
51+
export { Avatar, AvatarImage, AvatarFallback }

src/components/ui/button.tsx

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import * as React from "react"
2+
import { Slot } from "@radix-ui/react-slot"
3+
import { cva, type VariantProps } from "class-variance-authority"
4+
5+
import { cn } from "@/lib/utils"
6+
7+
const buttonVariants = cva(
8+
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
9+
{
10+
variants: {
11+
variant: {
12+
default:
13+
"bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
14+
destructive:
15+
"bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
16+
outline:
17+
"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
18+
secondary:
19+
"bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
20+
ghost:
21+
"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
22+
link: "text-primary underline-offset-4 hover:underline",
23+
},
24+
size: {
25+
default: "h-9 px-4 py-2 has-[>svg]:px-3",
26+
sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
27+
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
28+
icon: "size-9",
29+
},
30+
},
31+
defaultVariants: {
32+
variant: "default",
33+
size: "default",
34+
},
35+
}
36+
)
37+
38+
function Button({
39+
className,
40+
variant,
41+
size,
42+
asChild = false,
43+
...props
44+
}: React.ComponentProps<"button"> &
45+
VariantProps<typeof buttonVariants> & {
46+
asChild?: boolean
47+
}) {
48+
const Comp = asChild ? Slot : "button"
49+
50+
return (
51+
<Comp
52+
data-slot="button"
53+
className={cn(buttonVariants({ variant, size, className }))}
54+
{...props}
55+
/>
56+
)
57+
}
58+
59+
export { Button, buttonVariants }

0 commit comments

Comments
 (0)