Team Profiles
The TeamProfiles component is a comprehensive, customizable team showcase section that displays team members in a professional grid layout. It features multiple card styles, flexible grid configurations, theme variants, and advanced features like modal views and animations.
Basic Usage
- Preview
- Code
Our Core Team
Meet the talented people behind our success
Alex Chen
CEO & Co-Founder
Former tech lead with 10+ years of experience in building scalable platforms. Passionate about innovation and team culture.
Sarah Johnson
CTO
Full-stack architect specializing in cloud infrastructure and AI/ML implementations. Led engineering teams at Fortune 500 companies.
Michael Rodriguez
Head of Design
Award-winning designer with a passion for creating intuitive and beautiful user experiences. Previously led design at major tech companies.
import {
TeamProfiles,
TeamHeader,
TeamGrid,
MemberCard,
MemberPhoto,
MemberContent,
MemberName,
MemberRole,
MemberBio,
MemberSocialLinks,
} from '@ignix-ui/teamprofiles';
const teamMembers = [
{
id: "1",
name: "Alex Chen",
role: "CEO & Co-Founder",
bio: "Former tech lead with 10+ years of experience...",
photo: "https://images.unsplash.com/photo-1560250097-0b93528c311a",
socialLinks: [
{ platform: "linkedin", url: "https://linkedin.com/in/alexchen" },
{ platform: "twitter", url: "https://twitter.com/alexchen" },
],
},
// ... more members
];
<TeamProfiles
variant="dark"
cardVariant="default"
enableHover={true}
>
<TeamHeader
title="Our Core Team"
subtitle="Meet the talented people behind our success"
/>
<TeamGrid columns={{ mobile: 1, tablet: 2, desktop: 3 }}>
{teamMembers.slice(0, 3).map((member) => (
<MemberCard key={member.id} member={member}>
<MemberPhoto
src={member.photo}
initials={member.name.split(' ').map(n => n[0]).join('')}
/>
<MemberContent>
<MemberName>{member.name}</MemberName>
<MemberRole>{member.role}</MemberRole>
<MemberBio>{member.bio}</MemberBio>
<MemberSocialLinks links={member.socialLinks} />
</MemberContent>
</MemberCard>
))}
</TeamGrid>
</TeamProfiles>
Advanced Variants
- Preview
- Code
Our Leadership Team
Experienced professionals driving our vision forward
Alex Chen
CEO & Co-Founder
Former tech lead with 10+ years of experience in building scalable platforms. Passionate about innovation and team culture.
Sarah Johnson
CTO
Full-stack architect specializing in cloud infrastructure and AI/ML implementations. Led engineering teams at Fortune 500 companies.
Michael Rodriguez
Head of Design
Award-winning designer with a passion for creating intuitive and beautiful user experiences. Previously led design at major tech companies.
Emily Zhang
Lead Product Manager
Strategic product leader with expertise in B2B and B2C products. MBA graduate with a focus on product-market fit.
import {
TeamProfiles,
TeamHeader,
TeamGrid,
MemberCard,
MemberPhoto,
MemberContent,
MemberName,
MemberRole,
MemberDepartment,
MemberLocation,
MemberBio,
MemberJoinDate,
MemberExpertise,
MemberAwards,
MemberSocialLinks,
MemberCardOverlay,
TeamFooter,
} from '@ignix-ui/teamprofiles';
import { UserCircle } from 'lucide-react';
import { Typography } from '@ignix-ui/typography';
import { Button } from '@ignix-ui/button';
<TeamProfiles
variant="dark"
cardVariant="elevated"
enableModal={true}
enableHover={true}
animate={true}
animationType="stagger"
>
<TeamHeader
title="Our Leadership Team"
subtitle="Experienced professionals driving our vision forward"
/>
<TeamGrid columns={{ mobile: 1, tablet: 2, desktop: 3 }}>
{teamMembers.map((member) => (
<MemberCard key={member.id} member={member}>
<MemberPhoto src={member.photo} initials={member.name.split(' ').map(n => n[0]).join('')} />
<MemberContent>
<MemberName>{member.name}</MemberName>
<MemberRole>{member.role}</MemberRole>
<MemberBio>{member.bio}</MemberBio>
<MemberSocialLinks links={member.socialLinks} />
</MemberContent>
<MemberCardOverlay>
<Button variant="ghost" size="sm" className="text-white border-white hover:bg-white/20">
View Full Profile
</Button>
</MemberCardOverlay>
</MemberCard>
))}
</TeamGrid>
<TeamFooter>
<div className="text-center mt-8">
<Button variant="primary" size="lg">
Meet the Full Team
</Button>
</div>
</TeamFooter>
</TeamProfiles>
Installation
- CLI
- Manual
ignix add component teamprofiles
import React, { useState } from 'react';
import { motion } from 'framer-motion';
import { cva, type VariantProps } from 'class-variance-authority';
import {
Linkedin,
Github,
Twitter,
Globe,
Mail,
MapPin,
Award,
ChevronRight,
X
} from 'lucide-react';
import { cn } from '../../../utils/cn';
import { Typography } from '@ignix-ui/typography';
import { Button } from '@ignix-ui/button';
/* ============================================
TYPES & INTERFACES
============================================ */
export interface SocialLink {
platform: 'linkedin' | 'github' | 'twitter' | 'website' | 'email' | 'other';
url: string;
label?: string;
}
export interface TeamMember {
id: string;
name: string;
role: string;
bio: string;
photo?: string;
photoAlt?: string;
socialLinks?: SocialLink[];
department?: string;
location?: string;
expertise?: string[];
awards?: string[];
joinDate?: string;
email?: string;
}
interface TeamContextType {
theme: 'light' | 'dark';
cardVariant: 'default' | 'minimal' | 'elevated' | 'bordered';
showBio: boolean;
showSocialLinks: boolean;
showDepartment: boolean;
showLocation: boolean;
showExpertise: boolean;
showAwards: boolean;
showJoinDate: boolean;
enableHover: boolean;
isVisible: boolean;
animationType: 'fade' | 'slide' | 'scale' | 'stagger';
animationDelay: number;
onSocialClick?: (member: TeamMember, platform: string, url: string) => void;
onMemberClick?: (member: TeamMember) => void;
setSelectedMember?: (member: TeamMember | null) => void;
}
const TeamContext = React.createContext<TeamContextType | undefined>(undefined);
const useTeam = () => {
const context = React.useContext(TeamContext);
if (!context) {
throw new Error('Team components must be used within TeamProfiles');
}
return context;
};
/* ============================================
SOCIAL ICON COMPONENT
============================================ */
/**
* Renders the appropriate social media icon based on platform name.
*
* @component
* @example
* ```tsx
* <SocialIcon platform="linkedin" className="w-5 h-5" />
* <SocialIcon platform="github" />
* ```
*
* @param {Object} props - Component props
* @param {string} props.platform - Social platform name (linkedin, github, twitter, website, email)
* @param {string} [props.className] - Additional CSS classes for the icon
* @returns {JSX.Element} The corresponding social icon component
*/
export const SocialIcon: React.FC<{ platform: string; className?: string }> = ({
platform,
className
}) => {
const iconProps = { className: cn("w-4 h-4 cursor-pointer", className) };
switch (platform.toLowerCase()) {
case 'linkedin':
return <Linkedin {...iconProps} />;
case 'github':
return <Github {...iconProps} />;
case 'twitter':
return <Twitter {...iconProps} />;
case 'website':
return <Globe {...iconProps} />;
case 'email':
return <Mail {...iconProps} />;
default:
return <Globe {...iconProps} />;
}
};
/* ============================================
MEMBER CARD COMPONENTS (Compound)
============================================ */
/**
* Container component for individual team member cards.
* Handles animations, hover effects, and click interactions.
* Must be used within TeamProfiles component.
*
* @component
* @example
* ```tsx
* <MemberCard member={teamMember} onClick={() => console.log('card clicked')}>
* <MemberPhoto src="/photo.jpg" />
* <MemberContent>
* <MemberName>John Doe</MemberName>
* <MemberRole>Software Engineer</MemberRole>
* </MemberContent>
* </MemberCard>
* ```
*
* @param {Object} props - Component props
* @param {TeamMember} props.member - Team member data object
* @param {React.ReactNode} [props.children] - Child components to render inside the card
* @param {string} [props.className] - Additional CSS classes
* @param {() => void} [props.onClick] - Optional click handler
* @returns {JSX.Element} Animated team member card
*/
export const MemberCard: React.FC<{
member: TeamMember;
children?: React.ReactNode;
className?: string;
onClick?: () => void;
}> = ({ member, children, className, onClick }) => {
const {
theme,
cardVariant,
enableHover,
isVisible,
// animationType,
animationDelay,
onMemberClick,
setSelectedMember
} = useTeam();
const getAnimationProps = () => {
// In a real scenario, index would come from context or parent
// Using a simple fade animation as default
return {
initial: { opacity: 0, y: 20 },
animate: isVisible ? { opacity: 1, y: 0 } : { opacity: 0, y: 20 },
transition: { duration: 0.5, delay: animationDelay }
};
};
const cardVariants = cva(
"group relative overflow-hidden transition-all duration-300 cursor-pointer",
{
variants: {
variant: {
default: "rounded-xl shadow-sm hover:shadow-md",
minimal: "rounded-lg",
elevated: "rounded-2xl shadow-lg hover:shadow-xl",
bordered: "rounded-xl border-2 hover:border-primary",
},
theme: {
light: "bg-white text-gray-900",
dark: "bg-gray-800 text-gray-50",
},
},
compoundVariants: [
// Light theme variants
{ theme: "light", variant: "default", className: "bg-white" },
{ theme: "light", variant: "minimal", className: "bg-transparent" },
{ theme: "light", variant: "elevated", className: "bg-white" },
{ theme: "light", variant: "bordered", className: "bg-white border-gray-200" },
// Dark theme variants
{ theme: "dark", variant: "default", className: "bg-gray-800" },
{ theme: "dark", variant: "minimal", className: "bg-transparent" },
{ theme: "dark", variant: "elevated", className: "bg-gray-800" },
{ theme: "dark", variant: "bordered", className: "bg-gray-800 border-gray-700" },
],
defaultVariants: {
variant: "default",
theme: "light",
},
}
);
const handleClick = () => {
if (onClick) {
onClick();
}
if (onMemberClick) {
onMemberClick(member);
}
if (setSelectedMember) {
setSelectedMember(member);
}
};
return (
<motion.div
{...getAnimationProps()}
className={cn(
cardVariants({ variant: cardVariant, theme }),
enableHover && "hover:scale-101 hover:-translate-y-1",
className
)}
onClick={handleClick}
role="article"
aria-label={`Team member: ${member.name}`}
>
{children}
</motion.div>
);
};
/**
* Displays a team member's photo with fallback to initials if image fails to load.
* Includes hover zoom effect and lazy loading.
*
* @component
* @example
* ```tsx
* <MemberPhoto src="/team/john.jpg" alt="John Doe" />
* <MemberPhoto initials="JD" /> // Fallback with initials
* ```
*
* @param {Object} props - Component props
* @param {string} [props.src] - Image source URL
* @param {string} [props.alt] - Alt text for the image
* @param {string} [props.initials] - Initials to display if image is not available
* @param {string} [props.className] - Additional CSS classes
* @returns {JSX.Element} Member photo with fallback
*/
export const MemberPhoto: React.FC<{
src?: string;
alt?: string;
initials?: string;
className?: string;
}> = ({ src, alt, initials, className }) => {
const [imageError, setImageError] = useState(false);
if (src && !imageError) {
return (
<div className={cn("relative aspect-square overflow-hidden bg-gray-100 dark:bg-gray-700", className)}>
<img
src={src}
alt={alt || "Team member"}
className="w-full h-full object-cover transition-transform duration-500 group-hover:scale-110"
onError={() => setImageError(true)}
loading="lazy"
/>
</div>
);
}
return (
<div className={cn(
"relative aspect-square overflow-hidden flex items-center justify-center",
"bg-gradient-to-br from-primary/20 to-secondary/20",
className
)}>
<Typography variant="h2" weight="bold" className="text-primary">
{initials || "TM"}
</Typography>
</div>
);
};
/**
* Content wrapper for team member card information.
* Provides consistent padding and layout structure.
*
* @component
* @example
* ```tsx
* <MemberContent>
* <MemberName>John Doe</MemberName>
* <MemberRole>Developer</MemberRole>
* </MemberContent>
* ```
*
* @param {Object} props - Component props
* @param {React.ReactNode} props.children - Content to wrap
* @param {string} [props.className] - Additional CSS classes
* @returns {JSX.Element} Wrapped content with padding
*/
export const MemberContent: React.FC<{
children: React.ReactNode;
className?: string;
}> = ({ children, className }) => {
return (
<div className={cn("p-5", className)}>
{children}
</div>
);
};
/**
* Displays the team member's name with appropriate typography and theme styling.
*
* @component
* @example
* ```tsx
* <MemberName>John Doe</MemberName>
* ```
*
* @param {Object} props - Component props
* @param {React.ReactNode} props.children - Member name text
* @param {string} [props.className] - Additional CSS classes
* @returns {JSX.Element} Styled member name
*/
export const MemberName: React.FC<{
children: React.ReactNode;
className?: string;
}> = ({ children, className }) => {
const { theme } = useTeam();
return (
<Typography
variant="h4"
weight="semibold"
className={cn(
"mb-1",
theme === 'dark' ? 'text-white' : 'text-gray-900',
className
)}
>
{children}
</Typography>
);
};
/**
* Displays the team member's job title or role.
*
* @component
* @example
* ```tsx
* <MemberRole>Senior Software Engineer</MemberRole>
* ```
*
* @param {Object} props - Component props
* @param {React.ReactNode} props.children - Role text
* @param {string} [props.className] - Additional CSS classes
* @returns {JSX.Element} Styled member role
*/
export const MemberRole: React.FC<{
children: React.ReactNode;
className?: string;
}> = ({ children, className }) => {
const { theme } = useTeam();
return (
<Typography
variant="body"
className={cn(
"mb-2 text-sm",
theme === 'dark' ? 'text-gray-300' : 'text-gray-600',
className
)}
>
{children}
</Typography>
);
};
/**
* Displays the team member's department with an award icon.
*
* @component
* @example
* ```tsx
* <MemberDepartment>Engineering</MemberDepartment>
* ```
*
* @param {Object} props - Component props
* @param {React.ReactNode} props.children - Department name
* @param {string} [props.className] - Additional CSS classes
* @returns {JSX.Element} Department with icon
*/
export const MemberDepartment: React.FC<{
children: React.ReactNode;
className?: string;
}> = ({ children, className }) => {
const { theme } = useTeam();
return (
<div className={cn("flex items-center gap-1 mb-2", className)}>
<Award className={cn(
"w-3 h-3",
theme === 'dark' ? 'text-gray-400' : 'text-gray-500'
)} />
<Typography
variant="small"
className={theme === 'dark' ? 'text-gray-300' : 'text-gray-600'}
>
{children}
</Typography>
</div>
);
};
/**
* Displays the team member's location with a map pin icon.
*
* @component
* @example
* ```tsx
* <MemberLocation>San Francisco, CA</MemberLocation>
* ```
*
* @param {Object} props - Component props
* @param {React.ReactNode} props.children - Location text
* @param {string} [props.className] - Additional CSS classes
* @returns {JSX.Element} Location with icon
*/
export const MemberLocation: React.FC<{
children: React.ReactNode;
className?: string;
}> = ({ children, className }) => {
const { theme } = useTeam();
return (
<div className={cn("flex items-center gap-1 mb-2", className)}>
<MapPin className={cn(
"w-3 h-3",
theme === 'dark' ? 'text-gray-400' : 'text-gray-500'
)} />
<Typography
variant="small"
className={theme === 'dark' ? 'text-gray-300' : 'text-gray-600'}
>
{children}
</Typography>
</div>
);
};
/**
* Displays a truncated version of the team member's bio.
* Supports line clamping for consistent height.
*
* @component
* @example
* ```tsx
* <MemberBio lines={3}>
* John has over 10 years of experience in software development...
* </MemberBio>
* ```
*
* @param {Object} props - Component props
* @param {React.ReactNode} props.children - Bio text
* @param {string} [props.className] - Additional CSS classes
* @param {number} [props.lines=2] - Number of lines to display before truncating
* @returns {JSX.Element} Truncated bio
*/
export const MemberBio: React.FC<{
children: React.ReactNode;
className?: string;
lines?: number;
}> = ({ children, className, lines = 2 }) => {
const { theme } = useTeam();
return (
<Typography
variant="small"
className={cn(
`line-clamp-${lines}`,
theme === 'dark' ? 'text-gray-300' : 'text-gray-600',
className
)}
>
{children}
</Typography>
);
};
/**
* Displays the date when the team member joined the organization.
*
* @component
* @example
* ```tsx
* <MemberJoinDate>January 2020</MemberJoinDate>
* ```
*
* @param {Object} props - Component props
* @param {React.ReactNode} props.children - Join date text
* @param {string} [props.className] - Additional CSS classes
* @returns {JSX.Element} Formatted join date
*/
export const MemberJoinDate: React.FC<{
children: React.ReactNode;
className?: string;
}> = ({ children, className }) => {
const { theme } = useTeam();
return (
<Typography
variant="small"
className={cn(
"mt-2 text-xs",
theme === 'dark' ? 'text-gray-400' : 'text-gray-500',
className
)}
>
Joined {children}
</Typography>
);
};
/**
* Displays the team member's areas of expertise as tag-like chips.
* Supports limiting the number of displayed items with a "+X more" indicator.
*
* @component
* @example
* ```tsx
* <MemberExpertise
* items={["React", "TypeScript", "Node.js", "GraphQL"]}
* limit={3}
* />
* ```
*
* @param {Object} props - Component props
* @param {string[]} props.items - Array of expertise/skill strings
* @param {number} [props.limit=3] - Maximum number of items to display
* @param {string} [props.className] - Additional CSS classes
* @returns {JSX.Element|null} Expertise tags or null if no items
*/
export const MemberExpertise: React.FC<{
items: string[];
limit?: number;
className?: string;
}> = ({ items, limit = 3, className }) => {
const { theme } = useTeam();
const displayItems = items.slice(0, limit);
const remaining = items.length - limit;
if (!items.length) return null;
return (
<div className={cn("flex flex-wrap gap-1 mt-3", className)}>
{displayItems.map((skill, idx) => (
<span
key={idx}
className={cn(
"px-2 py-1 text-xs rounded-full",
theme === 'dark'
? 'bg-gray-700 text-gray-200'
: 'bg-gray-100 text-gray-700'
)}
>
{skill}
</span>
))}
{remaining > 0 && (
<span className={cn(
"px-2 py-1 text-xs rounded-full",
theme === 'dark'
? 'bg-gray-700 text-gray-200'
: 'bg-gray-100 text-gray-700'
)}>
+{remaining}
</span>
)}
</div>
);
};
/**
* Displays the team member's awards and recognitions.
* Supports limiting the number of displayed items.
*
* @component
* @example
* ```tsx
* <MemberAwards
* items={["Employee of the Year 2023", "Innovation Award"]}
* limit={1}
* />
* ```
*
* @param {Object} props - Component props
* @param {string[]} props.items - Array of award strings
* @param {number} [props.limit=1] - Maximum number of awards to display
* @param {string} [props.className] - Additional CSS classes
* @returns {JSX.Element|null} Awards section or null if no items
*/
export const MemberAwards: React.FC<{
items: string[];
limit?: number;
className?: string;
}> = ({ items, limit = 1, className }) => {
const { theme } = useTeam();
const displayItems = items.slice(0, limit);
const remaining = items.length - limit;
if (!items.length) return null;
return (
<div className={cn("mt-2", className)}>
<Typography
variant="small"
weight="medium"
className={cn(
"text-xs uppercase tracking-wider",
theme === 'dark' ? 'text-gray-400' : 'text-gray-500'
)}
>
Awards
</Typography>
<div className="mt-1 space-y-1">
{displayItems.map((award, idx) => (
<Typography
key={idx}
variant="small"
className={cn(
"text-xs line-clamp-1",
theme === 'dark' ? 'text-gray-300' : 'text-gray-600'
)}
>
🏆 {award}
</Typography>
))}
{remaining > 0 && (
<Typography
variant="small"
className={cn(
"text-xs",
theme === 'dark' ? 'text-gray-300' : 'text-gray-600'
)}
>
+{remaining} more
</Typography>
)}
</div>
</div>
);
};
/**
* Displays social media links for a team member with clickable icons.
* Handles external link opening and optional click tracking.
*
* @component
* @example
* ```tsx
* <MemberSocialLinks
* links={[
* { platform: 'linkedin', url: 'https://linkedin.com/in/johndoe' },
* { platform: 'github', url: 'https://github.com/johndoe' }
* ]}
* memberId="123"
* memberName="John Doe"
* />
* ```
*
* @param {Object} props - Component props
* @param {SocialLink[]} props.links - Array of social link objects
* @param {string} [props.memberId] - Member ID for tracking purposes
* @param {string} [props.memberName] - Member name for tracking purposes
* @param {string} [props.className] - Additional CSS classes
* @returns {JSX.Element|null} Social links or null if no links
*/
export const MemberSocialLinks: React.FC<{
links: SocialLink[];
memberId?: string;
memberName?: string;
className?: string;
}> = ({ links, memberId, memberName, className }) => {
const { theme, onSocialClick } = useTeam();
if (!links.length) return null;
const handleClick = (link: SocialLink, e: React.MouseEvent) => {
e.stopPropagation();
if (onSocialClick && memberId && memberName) {
onSocialClick({ id: memberId, name: memberName } as TeamMember, link.platform, link.url);
}
window.open(link.url, '_blank', 'noopener,noreferrer');
};
return (
<div className={cn(
"flex items-center gap-2 mt-4 pt-3 border-t",
theme === 'dark' ? 'border-gray-700' : 'border-gray-200',
className
)}>
{links.map((link, idx) => (
<button
key={idx}
onClick={(e) => handleClick(link, e)}
className={cn(
"p-2 rounded-full transition-all duration-200",
theme === 'dark'
? 'hover:bg-gray-600 text-gray-400 hover:text-white'
: 'hover:bg-gray-200 text-gray-500 hover:text-gray-900'
)}
aria-label={link.label || `Social link`}
>
<SocialIcon platform={link.platform} />
</button>
))}
</div>
);
};
/**
* Hover overlay component for member cards that appears on hover.
* Can contain custom content or default "View Profile" button.
*
* @component
* @example
* ```tsx
* <MemberCardOverlay>
* <Button variant="primary">Quick View</Button>
* </MemberCardOverlay>
* ```
*
* @param {Object} props - Component props
* @param {React.ReactNode} [props.children] - Custom overlay content
* @param {string} [props.className] - Additional CSS classes
* @returns {JSX.Element} Hover overlay
*/
export const MemberCardOverlay: React.FC<{
children?: React.ReactNode;
className?: string;
}> = ({ children, className }) => {
return (
<div className={cn(
"absolute inset-0 bg-black/50 opacity-0 group-hover:opacity-100 transition-opacity duration-300 flex items-center justify-center",
className
)}>
{children || (
<Button
variant="ghost"
size="sm"
className="text-white border-white hover:bg-white/20"
>
View Profile
<ChevronRight className="w-4 h-4 ml-1" />
</Button>
)}
</div>
);
};
/* ============================================
GRID COMPONENTS
============================================ */
/**
* Responsive grid layout for organizing team member cards.
* Provides configurable columns for different screen sizes and consistent gap spacing.
*
* @component
* @example
* ```tsx
* <TeamGrid
* columns={{ mobile: 1, tablet: 2, desktop: 3, wide: 4 }}
* gap="lg"
* >
* <MemberCard member={member1} />
* <MemberCard member={member2} />
* </TeamGrid>
* ```
*
* @param {Object} props - Component props
* @param {React.ReactNode} props.children - Grid items (typically MemberCard components)
* @param {Object} [props.columns] - Column configuration for different breakpoints
* @param {number} [props.columns.mobile=1] - Columns on mobile devices
* @param {number} [props.columns.tablet] - Columns on tablets (sm breakpoint)
* @param {number} [props.columns.desktop] - Columns on desktops (lg breakpoint)
* @param {number} [props.columns.wide] - Columns on wide screens (xl breakpoint)
* @param {('sm'|'md'|'lg'|'xl')} [props.gap='md'] - Gap size between grid items
* @param {string} [props.className] - Additional CSS classes
* @returns {JSX.Element} Responsive grid layout
*/
export const TeamGrid: React.FC<{
children: React.ReactNode;
columns?: {
mobile?: number;
tablet?: number;
desktop?: number;
wide?: number;
};
gap?: 'sm' | 'md' | 'lg' | 'xl';
className?: string;
}> = ({ children, columns = { mobile: 1, tablet: 2, desktop: 3, wide: 4 }, gap = 'md', className }) => {
const gridVariants = cva(
"grid",
{
variants: {
gap: {
sm: "gap-3 md:gap-4",
md: "gap-4 md:gap-6",
lg: "gap-6 md:gap-8",
xl: "gap-8 md:gap-10",
},
},
defaultVariants: {
gap: 'md',
},
}
);
const getColumnsClass = () => {
return cn(
`grid-cols-${columns.mobile || 1}`,
columns.tablet && `sm:grid-cols-${columns.tablet}`,
columns.desktop && `lg:grid-cols-${columns.desktop}`,
columns.wide && `xl:grid-cols-${columns.wide}`
);
};
return (
<div className={cn(getColumnsClass(), gridVariants({ gap }), className)}>
{React.Children.map(children, (child, index) => {
if (React.isValidElement<Record<string, unknown>>(child)) {
return React.cloneElement(child, {
...child.props,
// Pass index for animation if needed
['data-index']: index
});
}
return child;
})}
</div>
);
};
/* ============================================
HEADER COMPONENTS
============================================ */
/**
* Header section for team profiles with title and subtitle support.
* Can accept custom children or use built-in title/subtitle props.
*
* @component
* @example
* ```tsx
* // With children
* <TeamHeader>
* <CustomHeading>Our Team</CustomHeading>
* </TeamHeader>
*
* // With props
* <TeamHeader
* title="Meet Our Team"
* subtitle="Passionate experts dedicated to your success"
* />
* ```
*
* @param {Object} props - Component props
* @param {React.ReactNode} [props.children] - Custom header content
* @param {string} [props.title] - Main heading text
* @param {string} [props.subtitle] - Subheading text
* @param {string} [props.className] - Additional CSS classes
* @returns {JSX.Element} Team header section
*/
export const TeamHeader: React.FC<{
children?: React.ReactNode;
title?: string;
subtitle?: string;
className?: string;
}> = ({ children, title, subtitle, className }) => {
const { theme } = useTeam();
if (children) {
return <div className={cn("mb-8 md:mb-12 text-center", className)}>{children}</div>;
}
return (
<div className={cn("mb-8 md:mb-12 text-center", className)}>
{title && (
<Typography
variant="h2"
weight="bold"
className={cn(
"mb-3",
theme === 'dark' ? 'text-white' : 'text-gray-900'
)}
>
{title}
</Typography>
)}
{subtitle && (
<Typography
variant="lead"
className={cn(
"max-w-2xl mx-auto",
theme === 'dark' ? 'text-gray-300' : 'text-gray-600'
)}
>
{subtitle}
</Typography>
)}
</div>
);
};
/**
* Footer section for team profiles that can contain additional content
* such as pagination, view all links, or load more buttons.
*
* @component
* @example
* ```tsx
* <TeamFooter>
* <Button variant="outline">View All Team Members</Button>
* </TeamFooter>
* ```
*
* @param {Object} props - Component props
* @param {React.ReactNode} props.children - Footer content
* @param {string} [props.className] - Additional CSS classes
* @returns {JSX.Element} Team footer section
*/
export const TeamFooter: React.FC<{
children: React.ReactNode;
className?: string;
}> = ({ children, className }) => {
return (
<div className={cn("mt-8 md:mt-12", className)}>
{children}
</div>
);
};
/* ============================================
MODAL COMPONENTS
============================================ */
/**
* Modal dialog for displaying detailed team member information.
* Features responsive layout with image and content sections.
* Includes backdrop click and close button functionality.
*
* @component
* @example
* ```tsx
* <TeamModal
* isOpen={isModalOpen}
* onClose={() => setIsModalOpen(false)}
* member={selectedMember}
* >
* <CustomModalContent />
* </TeamModal>
* ```
*
* @param {Object} props - Component props
* @param {boolean} props.isOpen - Controls modal visibility
* @param {() => void} props.onClose - Function to close the modal
* @param {TeamMember | null} props.member - Team member data to display
* @param {React.ReactNode} [props.children] - Custom modal content
* @returns {JSX.Element|null} Modal component or null if closed
*/
export const TeamModal: React.FC<{
isOpen: boolean;
onClose: () => void;
member: TeamMember | null;
children?: React.ReactNode;
}> = ({ isOpen, onClose, member, children }) => {
const { theme } = useTeam();
if (!isOpen || !member) return null;
const handleBackdropClick = (e: React.MouseEvent) => {
if (e.target === e.currentTarget) {
onClose();
}
};
const getInitials = () => {
return member.name
.split(' ')
.map(n => n[0])
.join('')
.toUpperCase()
.slice(0, 2);
};
return (
<div
className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/50 backdrop-blur-sm"
onClick={handleBackdropClick}
role="dialog"
aria-modal="true"
aria-label={`Profile: ${member?.name}`}
>
<motion.div
initial={{ opacity: 0, scale: 0.9, y: 20 }}
animate={{ opacity: 1, scale: 1, y: 0 }}
exit={{ opacity: 0, scale: 0.9, y: 20 }}
className={cn(
"relative max-w-2xl w-full max-h-[90vh] overflow-y-auto rounded-2xl",
theme === 'dark' ? 'bg-gray-800 text-white' : 'bg-white text-gray-900'
)}
>
<button
onClick={onClose}
className={cn(
"absolute top-4 right-4 p-2 rounded-full z-10",
theme === 'dark'
? 'hover:bg-gray-600 text-gray-400 hover:text-white'
: 'hover:bg-gray-200 text-gray-600 hover:text-gray-900'
)}
aria-label="Close modal"
>
<X className="w-5 h-5" />
</button>
{children || (
<div className="flex flex-col md:flex-row">
<div className="md:w-2/5">
{member.photo ? (
<img
src={member.photo}
alt={member.photoAlt || member.name}
className="w-full h-full object-cover"
/>
) : (
<div className="w-full h-full flex items-center justify-center bg-gradient-to-br from-primary/20 to-secondary/20">
<Typography variant="h1" weight="bold" className="text-primary">
{getInitials()}
</Typography>
</div>
)}
</div>
<div className="md:w-3/5 p-6">
<Typography variant="h2" weight="bold" className="mb-1">
{member.name}
</Typography>
<Typography variant="h4" className="mb-4 text-gray-600 dark:text-gray-300">
{member.role}
</Typography>
{member.department && (
<div className="flex items-center gap-2 mb-4">
<Award className="w-4 h-4 text-gray-400" />
<Typography variant="body">{member.department}</Typography>
</div>
)}
{member.location && (
<div className="flex items-center gap-2 mb-4">
<MapPin className="w-4 h-4 text-gray-400" />
<Typography variant="body">{member.location}</Typography>
</div>
)}
<Typography variant="body" className="mb-6 leading-relaxed">
{member.bio}
</Typography>
{member.expertise && member.expertise.length > 0 && (
<div className="mb-6">
<Typography variant="small" weight="bold" className="mb-2">
Expertise
</Typography>
<div className="flex flex-wrap gap-2">
{member.expertise.map((skill, idx) => (
<span
key={idx}
className={cn(
"px-3 py-1 text-sm rounded-full",
theme === 'dark'
? 'bg-gray-700 text-gray-200'
: 'bg-gray-100 text-gray-700'
)}
>
{skill}
</span>
))}
</div>
</div>
)}
{member.socialLinks && member.socialLinks.length > 0 && (
<div className="flex items-center gap-3">
{member.socialLinks.map((link, idx) => (
<button
key={idx}
onClick={() => window.open(link.url, '_blank')}
className={cn(
"p-3 rounded-full transition-colors",
theme === 'dark'
? 'hover:bg-gray-600 text-gray-400 hover:text-white'
: 'hover:bg-gray-200 text-gray-600 hover:text-gray-900'
)}
>
<SocialIcon platform={link.platform} className="w-5 h-5" />
</button>
))}
</div>
)}
</div>
</div>
)}
</motion.div>
</div>
);
};
/* ============================================
VARIANT DEFINITIONS
============================================ */
const sectionVariants = cva(
"w-full transition-all duration-300",
{
variants: {
variant: {
default: "bg-background text-foreground",
primary: "bg-primary text-primary-foreground",
secondary: "bg-secondary text-secondary-foreground",
accent: "bg-accent text-accent-foreground",
muted: "bg-muted text-muted-foreground",
gradient: "bg-gradient-to-r from-primary/90 to-accent/90 text-primary-foreground",
glass: "bg-background/80 backdrop-blur-md border border-border",
dark: "bg-gray-950 text-gray-50",
light: "bg-gray-50 text-gray-900",
},
padding: {
none: "py-0",
sm: "py-8 md:py-12",
md: "py-12 md:py-16",
lg: "py-16 md:py-20",
xl: "py-20 md:py-24",
'2xl': "py-24 md:py-32",
},
},
defaultVariants: {
variant: "default",
padding: "lg",
},
}
);
/* ============================================
MAIN COMPONENT
============================================ */
export interface TeamProfilesProps {
// Layout & Variants
variant?: VariantProps<typeof sectionVariants>['variant'];
cardVariant?: 'default' | 'minimal' | 'elevated' | 'bordered';
// Features
showBio?: boolean;
showSocialLinks?: boolean;
showDepartment?: boolean;
showLocation?: boolean;
showExpertise?: boolean;
showAwards?: boolean;
showJoinDate?: boolean;
enableModal?: boolean;
enableHover?: boolean;
// Styling
theme?: 'light' | 'dark';
forceTheme?: boolean;
backgroundType?: 'solid' | 'gradient' | 'image';
backgroundColor?: string;
gradientFrom?: string;
gradientTo?: string;
backgroundImage?: string;
// Animation
animate?: boolean;
animationDelay?: number;
animationType?: 'fade' | 'slide' | 'scale' | 'stagger';
// Spacing
padding?: 'sm' | 'md' | 'lg' | 'xl' | '2xl' | 'none';
// Actions
onMemberClick?: (member: TeamMember) => void;
onSocialClick?: (member: TeamMember, platform: string, url: string) => void;
// Accessibility
ariaLabel?: string;
// Children
children: React.ReactNode;
}
/**
* TeamProfiles - A comprehensive and composable team profiles component system.
*
* This component serves as the main wrapper for displaying team member information.
* It uses a compound component pattern where child components like MemberCard,
* MemberPhoto, MemberName, etc., must be used inside it. The system provides extensive
* customization options including themes, card variants, animations, and modal support.
*
* Features:
* - Multiple card variants: default, minimal, elevated, bordered
* - Theme support: light/dark modes with forced theme capability
* - Configurable information display (bio, social links, department, etc.)
* - Modal support for detailed member profiles
* - Hover effects and animations
* - Responsive grid layout with TeamGrid component
* - Accessibility-focused with ARIA labels and semantic HTML
* - Compound component pattern for flexible composition
*
* @component
* @example
* ```tsx
* // Basic usage
* <TeamProfiles
* variant="default"
* cardVariant="elevated"
* showBio={true}
* showSocialLinks={true}
* enableModal={true}
* theme="light"
* >
* <TeamHeader
* title="Meet Our Team"
* subtitle="Passionate experts dedicated to your success"
* />
*
* <TeamGrid
* columns={{ mobile: 1, tablet: 2, desktop: 3 }}
* gap="lg"
* >
* {teamMembers.map((member) => (
* <MemberCard key={member.id} member={member}>
* <MemberPhoto src={member.photo} initials={getInitials(member.name)} />
* <MemberContent>
* <MemberName>{member.name}</MemberName>
* <MemberRole>{member.role}</MemberRole>
* {member.department && <MemberDepartment>{member.department}</MemberDepartment>}
* {member.location && <MemberLocation>{member.location}</MemberLocation>}
* {showBio && <MemberBio lines={2}>{member.bio}</MemberBio>}
* {member.expertise && <MemberExpertise items={member.expertise} limit={3} />}
* {showSocialLinks && member.socialLinks && (
* <MemberSocialLinks
* links={member.socialLinks}
* memberId={member.id}
* memberName={member.name}
* />
* )}
* </MemberContent>
* {enableModal && <MemberCardOverlay />}
* </MemberCard>
* ))}
* </TeamGrid>
*
* <TeamFooter>
* <Button variant="outline">View All Team Members</Button>
* </TeamFooter>
* </TeamProfiles>
*
* // Minimal variant with limited information
* <TeamProfiles
* variant="light"
* cardVariant="minimal"
* showBio={false}
* showSocialLinks={false}
* enableModal={false}
* enableHover={false}
* >
* <TeamGrid columns={{ mobile: 2, tablet: 3, desktop: 4 }} gap="sm">
* {teamMembers.map((member) => (
* <MemberCard key={member.id} member={member}>
* <MemberPhoto src={member.photo} />
* <MemberContent>
* <MemberName>{member.name}</MemberName>
* <MemberRole>{member.role}</MemberRole>
* </MemberContent>
* </MemberCard>
* ))}
* </TeamGrid>
* </TeamProfiles>
*
* // Dark theme with gradient background and modal support
* <TeamProfiles
* variant="gradient"
* cardVariant="bordered"
* theme="dark"
* backgroundType="gradient"
* gradientFrom="#4F46E5"
* gradientTo="#7C3AED"
* enableModal={true}
* onMemberClick={handleMemberClick}
* onSocialClick={handleSocialClick}
* >
* <TeamHeader
* title="Our Leadership Team"
* subtitle="Meet the people shaping our company's future"
* />
*
* <TeamGrid columns={{ mobile: 1, tablet: 2, desktop: 2 }} gap="xl">
* {leadershipTeam.map((leader) => (
* <MemberCard key={leader.id} member={leader}>
* <MemberPhoto src={leader.photo} />
* <MemberContent>
* <MemberName>{leader.name}</MemberName>
* <MemberRole>{leader.role}</MemberRole>
* <MemberDepartment>{leader.department}</MemberDepartment>
* <MemberLocation>{leader.location}</MemberLocation>
* <MemberBio lines={3}>{leader.bio}</MemberBio>
* <MemberExpertise items={leader.expertise} limit={4} />
* <MemberAwards items={leader.awards} limit={2} />
* <MemberSocialLinks
* links={leader.socialLinks}
* memberId={leader.id}
* memberName={leader.name}
* />
* <MemberJoinDate>{leader.joinDate}</MemberJoinDate>
* </MemberContent>
* <MemberCardOverlay />
* </MemberCard>
* ))}
* </TeamGrid>
* </TeamProfiles>
* ```
*
* @param {Object} props - Component props
* @param {('default'|'primary'|'secondary'|'accent'|'muted'|'gradient'|'glass'|'dark'|'light')} [props.variant='default'] - Visual style variant for the section
* @param {('default'|'minimal'|'elevated'|'bordered')} [props.cardVariant='default'] - Visual style variant for member cards
* @param {boolean} [props.showBio=true] - Whether to display member bios
* @param {boolean} [props.showSocialLinks=true] - Whether to display social media links
* @param {boolean} [props.showDepartment=false] - Whether to display department information
* @param {boolean} [props.showLocation=false] - Whether to display location information
* @param {boolean} [props.showExpertise=false] - Whether to display expertise/skills
* @param {boolean} [props.showAwards=false] - Whether to display awards
* @param {boolean} [props.showJoinDate=false] - Whether to display join date
* @param {boolean} [props.enableModal=false] - Whether to enable modal for detailed profiles
* @param {boolean} [props.enableHover=true] - Whether to enable hover effects on cards
* @param {('light'|'dark')} [props.theme] - Color theme (auto-detected from variant if not provided)
* @param {boolean} [props.forceTheme=false] - Force theme application regardless of parent
* @param {('solid'|'gradient'|'image')} [props.backgroundType='solid'] - Background type
* @param {string} [props.backgroundColor] - Custom background color (hex/rgb/hsl)
* @param {string} [props.gradientFrom] - Start color for gradient backgrounds
* @param {string} [props.gradientTo] - End color for gradient backgrounds
* @param {string} [props.backgroundImage] - URL for image backgrounds
* @param {boolean} [props.animate=true] - Enable/disable animations
* @param {number} [props.animationDelay=0] - Delay before animation starts (seconds)
* @param {('fade'|'slide'|'scale'|'stagger')} [props.animationType='fade'] - Animation type
* @param {('sm'|'md'|'lg'|'xl'|'2xl'|'none')} [props.padding='lg'] - Padding size
* @param {(member: TeamMember) => void} [props.onMemberClick] - Callback when a member card is clicked
* @param {(member: TeamMember, platform: string, url: string) => void} [props.onSocialClick] - Callback when a social link is clicked
* @param {string} [props.ariaLabel='Team profiles section'] - ARIA label for accessibility
* @param {React.ReactNode} props.children - Child components (must be Team components)
* @returns {JSX.Element} Rendered team profiles section with all child components
*
* @see {@link MemberCard} - For individual team member cards
* @see {@link MemberPhoto} - For member photos with fallback
* @see {@link MemberName} - For member names
* @see {@link MemberRole} - For member roles
* @see {@link MemberBio} - For truncated bios
* @see {@link MemberSocialLinks} - For social media links
* @see {@link MemberExpertise} - For expertise tags
* @see {@link TeamGrid} - For responsive grid layout
* @see {@link TeamHeader} - For section header
* @see {@link TeamFooter} - For section footer
* @see {@link TeamModal} - For detailed profile modal
*/
export const TeamProfiles: React.FC<TeamProfilesProps> = ({
// Layout & Variants
variant = "default",
cardVariant = "default",
// Features
showBio = true,
showSocialLinks = true,
showDepartment = false,
showLocation = false,
showExpertise = false,
showAwards = false,
showJoinDate = false,
enableModal = false,
enableHover = true,
// Styling
theme,
forceTheme = false,
backgroundType = "solid",
backgroundColor,
gradientFrom,
gradientTo,
backgroundImage,
// Animation
animate = true,
animationDelay = 0,
animationType = "fade",
// Spacing
padding = "lg",
// Actions
onMemberClick,
onSocialClick,
// Accessibility
ariaLabel = "Team profiles section",
// Children
children,
}) => {
const [isVisible, setIsVisible] = useState(false);
const [selectedMember, setSelectedMember] = useState<TeamMember | null>(null);
// Determine theme
const resolvedTheme = theme ||
(variant === 'dark' ? 'dark' :
variant === 'light' ? 'light' :
variant === 'gradient' ? 'dark' : 'light');
// Handle background styling
const backgroundStyle: React.CSSProperties = {};
if (backgroundType === 'gradient' && gradientFrom && gradientTo) {
backgroundStyle.background = `linear-gradient(135deg, ${gradientFrom}, ${gradientTo})`;
} else if (backgroundType === 'solid' && backgroundColor) {
backgroundStyle.backgroundColor = backgroundColor;
} else if (backgroundType === 'image' && backgroundImage) {
backgroundStyle.backgroundImage = `url(${backgroundImage})`;
backgroundStyle.backgroundSize = 'cover';
backgroundStyle.backgroundPosition = 'center';
}
// Trigger animation
React.useEffect(() => {
if (animate) {
const timer = setTimeout(() => {
setIsVisible(true);
}, 100);
return () => clearTimeout(timer);
} else {
setIsVisible(true);
}
}, [animate]);
// Close modal
const handleCloseModal = () => {
setSelectedMember(null);
};
// Context value
const contextValue: TeamContextType = {
theme: resolvedTheme,
cardVariant,
showBio,
showSocialLinks,
showDepartment,
showLocation,
showExpertise,
showAwards,
showJoinDate,
enableHover,
isVisible,
animationType,
animationDelay,
onSocialClick,
onMemberClick,
setSelectedMember: enableModal ? setSelectedMember : undefined,
};
return (
<TeamContext.Provider value={contextValue}>
<section
className={cn(
sectionVariants({ variant, padding }),
forceTheme && resolvedTheme === 'dark' && 'dark',
"relative"
)}
style={backgroundStyle}
aria-label={ariaLabel}
>
{/* Background overlay for image/gradient */}
{backgroundType === 'image' && (
<div className="absolute inset-0 bg-black/40" />
)}
<div className="container mx-auto px-4 sm:px-6 lg:px-8 relative z-10">
{children}
</div>
</section>
{/* Modal */}
{enableModal && selectedMember && (
<TeamModal
isOpen={!!selectedMember}
onClose={handleCloseModal}
member={selectedMember}
/>
)}
</TeamContext.Provider>
);
};
Props
TeamProfiles Props
| Prop | Type | Default | Description |
|---|---|---|---|
variant | 'default' | 'primary' | 'secondary' | 'accent' | 'muted' | 'gradient' | 'glass' | 'dark' | 'light' | 'default' | Visual variant of the section background |
cardVariant | 'default' | 'minimal' | 'elevated' | 'bordered' | 'default' | Visual style of team cards |
showBio | boolean | true | Show member biography |
showSocialLinks | boolean | true | Show social media links |
showDepartment | boolean | false | Show member department |
showLocation | boolean | false | Show member location |
showExpertise | boolean | false | Show expertise tags |
showAwards | boolean | false | Show awards and recognition |
showJoinDate | boolean | false | Show join date |
enableModal | boolean | false | Enable modal popup for member details |
enableHover | boolean | true | Enable hover effects on cards |
theme | 'light' | 'dark' | - | Force a specific theme (overrides variant-based theme) |
forceTheme | boolean | false | Force theme CSS classes |
backgroundType | 'solid' | 'gradient' | 'image' | 'solid' | Type of background styling |
backgroundColor | string | - | Custom background color (for solid background) |
gradientFrom | string | - | Start color for gradient background |
gradientTo | string | - | End color for gradient background |
backgroundImage | string | - | URL for background image |
animate | boolean | true | Enable/disable entrance animations |
animationDelay | number | 0 | Initial animation delay in seconds |
animationType | 'fade' | 'slide' | 'scale' | 'stagger' | 'fade' | Type of entrance animation |
padding | 'none' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | 'lg' | Vertical padding size |
onMemberClick | (member: TeamMember) => void | - | Callback when a member card is clicked |
onSocialClick | (member: TeamMember, platform: string, url: string) => void | - | Callback when a social link is clicked |
ariaLabel | string | 'Team profiles section' | Accessibility label |
children | React.ReactNode | required | Team components (TeamHeader, TeamGrid, TeamFooter) |
TeamHeader Props
| Prop | Type | Default | Description |
|---|---|---|---|
children | React.ReactNode | - | Custom header content (overrides title/subtitle) |
title | string | - | Section title |
subtitle | string | - | Section subtitle |
className | string | - | Additional CSS classes |
TeamGrid Props
| Prop | Type | Default | Description |
|---|---|---|---|
children | React.ReactNode | required | MemberCard components |
columns | { mobile?: number; tablet?: number; desktop?: number; wide?: number; } | { mobile: 1, tablet: 2, desktop: 3, wide: 4 } | Responsive column configuration |
gap | 'sm' | 'md' | 'lg' | 'xl' | 'md' | Gap between cards |
className | string | - | Additional CSS classes |
MemberCard Props
| Prop | Type | Default | Description |
|---|---|---|---|
member | TeamMember | required | Team member data object |
children | React.ReactNode | required | Member photo and content components |
className | string | - | Additional CSS classes |
MemberPhoto Props
| Prop | Type | Default | Description |
|---|---|---|---|
src | string | - | Image source URL |
alt | string | - | Image alt text |
initials | string | - | Initials to display when no image is provided |
className | string | - | Additional CSS classes |
MemberContent Props
| Prop | Type | Default | Description |
|---|---|---|---|
children | React.ReactNode | required | Member information components |
className | string | - | Additional CSS classes |
MemberName Props
| Prop | Type | Default | Description |
|---|---|---|---|
children | React.ReactNode | required | Member name text |
className | string | - | Additional CSS classes |
MemberRole Props
| Prop | Type | Default | Description |
|---|---|---|---|
children | React.ReactNode | required | Member role/title text |
className | string | - | Additional CSS classes |
MemberDepartment Props
| Prop | Type | Default | Description |
|---|---|---|---|
children | React.ReactNode | required | Department name |
className | string | - | Additional CSS classes |
MemberLocation Props
| Prop | Type | Default | Description |
|---|---|---|---|
children | React.ReactNode | required | Location text |
className | string | - | Additional CSS classes |
MemberBio Props
| Prop | Type | Default | Description |
|---|---|---|---|
children | React.ReactNode | required | Biography text |
lines | number | 2 | Number of lines to show before truncating |
className | string | - | Additional CSS classes |
MemberJoinDate Props
| Prop | Type | Default | Description |
|---|---|---|---|
children | React.ReactNode | required | Join date text |
className | string | - | Additional CSS classes |
MemberExpertise Props
| Prop | Type | Default | Description |
|---|---|---|---|
items | string[] | required | Array of expertise/skills |
limit | number | 3 | Maximum number of items to show |
className | string | - | Additional CSS classes |
MemberAwards Props
| Prop | Type | Default | Description |
|---|---|---|---|
items | string[] | required | Array of awards |
limit | number | 1 | Maximum number of items to show |
className | string | - | Additional CSS classes |
MemberSocialLinks Props
| Prop | Type | Default | Description |
|---|---|---|---|
links | SocialLink[] | required | Array of social links |
memberId | string | - | Member ID for tracking |
memberName | string | - | Member name for tracking |
className | string | - | Additional CSS classes |
MemberCardOverlay Props
| Prop | Type | Default | Description |
|---|---|---|---|
children | React.ReactNode | - | Custom overlay content |
className | string | - | Additional CSS classes |
TeamFooter Props
| Prop | Type | Default | Description |
|---|---|---|---|
children | React.ReactNode | required | Footer content |
className | string | - | Additional CSS classes |
TeamModal Props
| Prop | Type | Default | Description |
|---|---|---|---|
isOpen | boolean | required | Modal open state |
onClose | () => void | required | Close handler |
member | TeamMember | null | required | Selected team member |
children | React.ReactNode | - | Custom modal content |
Types
interface SocialLink {
platform: 'linkedin' | 'github' | 'twitter' | 'website' | 'email' | 'other';
url: string;
label?: string;
}
interface TeamMember {
id: string; // Unique identifier
name: string; // Member's full name
role: string; // Job title/role
bio: string; // Biography/description
photo?: string; // Photo URL
photoAlt?: string; // Alt text for photo
socialLinks?: SocialLink[]; // Social media links
department?: string; // Department name
location?: string; // Location (city, country)
expertise?: string[]; // Areas of expertise
awards?: string[]; // Awards and recognition
joinDate?: string; // Join date (any format)
email?: string; // Email address
}
Base Usage
import {
TeamProfiles,
TeamHeader,
TeamGrid,
MemberCard,
MemberPhoto,
MemberContent,
MemberName,
MemberRole,
MemberBio,
MemberSocialLinks
} from '@ignix-ui/teamprofiles';
const teamMembers = [
{
id: '1',
name: 'Alex Chen',
role: 'CEO & Co-Founder',
bio: 'Former tech lead with 10+ years of experience.',
photo: '/images/alex.jpg',
socialLinks: [
{ platform: 'linkedin', url: 'https://linkedin.com/in/alexchen' },
{ platform: 'twitter', url: 'https://twitter.com/alexchen' }
]
},
// ... more members
];
<TeamProfiles variant="light" cardVariant="default">
<TeamHeader
title="Our Team"
subtitle="Meet the experts behind our success"
/>
<TeamGrid columns={{ mobile: 1, tablet: 2, desktop: 3 }}>
{teamMembers.map((member) => (
<MemberCard key={member.id} member={member}>
<MemberPhoto
src={member.photo}
initials={member.name.split(' ').map(n => n[0]).join('')}
/>
<MemberContent>
<MemberName>{member.name}</MemberName>
<MemberRole>{member.role}</MemberRole>
<MemberBio>{member.bio}</MemberBio>
<MemberSocialLinks links={member.socialLinks} />
</MemberContent>
</MemberCard>
))}
</TeamGrid>
</TeamProfiles>
With all features
<TeamProfiles
variant="light"
cardVariant="elevated"
enableModal={true}
enableHover={true}
animate={true}
animationType="stagger"
>
<TeamHeader>
<div className="flex items-center gap-2 mb-4">
<Users className="w-8 h-8 text-primary" />
<Typography variant="h2">Our Leadership Team</Typography>
</div>
<Typography variant="lead">Experienced professionals driving our vision</Typography>
</TeamHeader>
<TeamGrid columns={{ mobile: 1, tablet: 2, desktop: 3, wide: 4 }}>
{teamMembers.map((member) => (
<MemberCard key={member.id} member={member}>
<MemberPhoto
src={member.photo}
initials={member.name.split(' ').map(n => n[0]).join('')}
/>
<MemberContent>
<MemberName>{member.name}</MemberName>
<MemberRole>{member.role}</MemberRole>
<MemberDepartment>{member.department}</MemberDepartment>
<MemberLocation>{member.location}</MemberLocation>
<MemberBio>{member.bio}</MemberBio>
<MemberExpertise items={member.expertise} />
<MemberAwards items={member.awards} />
<MemberJoinDate>{member.joinDate}</MemberJoinDate>
<MemberSocialLinks links={member.socialLinks} />
</MemberContent>
<MemberCardOverlay />
</MemberCard>
))}
</TeamGrid>
<TeamFooter>
<Button variant="primary" size="lg">
View All Team Members
</Button>
</TeamFooter>
</TeamProfiles>
Accessibility
The Team Profiles component follows WAI-ARIA guidelines:
- Each MemberCard has role="article" and appropriate aria-label
- Social links have aria-label for screen readers
- The TeamModal has proper ARIA attributes (role="dialog", aria-modal="true", aria-label)
- Images have alt text support
- Focus management is handled for modal interactions
- Color contrast meets WCAG standards in all variants