Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 97 additions & 0 deletions src/components/common/ProfileTabPill.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import React from 'react';
import { cva, type VariantProps } from 'class-variance-authority';
import { cn } from '@/lib/utils';

const profileTabPillVariants = cva(
'inline-flex items-center gap-1.5 rounded-full border px-3.5 py-1.5 text-xs font-semibold font-jakarta tracking-wide transition-all duration-200 outline-none focus-visible:ring-2 focus-visible:ring-amber-400/50 focus-visible:ring-offset-1 focus-visible:ring-offset-transparent',
{
variants: {
state: {
active: 'border-amber-400/30 bg-amber-400/15 text-white',
inactive:
'border-white/10 bg-white/[0.06] text-white/60 hover:border-white/20 hover:bg-white/10 hover:text-white/80',
},
},
defaultVariants: {
state: 'inactive',
},
}
);

export interface ProfileTabPillProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof profileTabPillVariants> {
isActive?: boolean;
icon?: React.ReactNode;
}

const ProfileTabPill: React.FC<ProfileTabPillProps> = ({
isActive = false,
icon,
className,
children,
...props
}) => {
return (
<button
type="button"
className={cn(
profileTabPillVariants({
state: isActive ? 'active' : 'inactive',
className,
})
)}
{...props}
>
{icon && (
<span className="[&_svg]:size-3.5" aria-hidden="true">
{icon}
</span>
)}
{children}
</button>
);
};

export default ProfileTabPill;

export interface ProfileTabPillGroupTab {
label: string;
value: string;
icon?: React.ReactNode;
}

export interface ProfileTabPillGroupProps {
tabs: ProfileTabPillGroupTab[];
activeTab: string;
onTabChange: (value: string) => void;
className?: string;
}

export const ProfileTabPillGroup: React.FC<ProfileTabPillGroupProps> = ({
tabs,
activeTab,
onTabChange,
className,
}) => {
return (
<nav
role="tablist"
aria-label="Profile sections"
className={cn('flex flex-wrap items-center gap-2', className)}
>
{tabs.map(tab => (
<ProfileTabPill
key={tab.value}
role="tab"
aria-selected={activeTab === tab.value}
isActive={activeTab === tab.value}
icon={tab.icon}
onClick={() => onTabChange(tab.value)}
>
{tab.label}
</ProfileTabPill>
))}
</nav>
);
};
25 changes: 19 additions & 6 deletions src/pages/LandingPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import CompactSectionSubtitle from '@/components/common/CompactSectionSubtitle';
import CreatorProfileInfoGrid from '@/components/common/CreatorProfileInfoGrid';
import MiniStatChip from '@/components/common/MiniStatChip';
import MarketplaceSection from '@/components/common/MarketplaceSection';
import { ProfileTabPillGroup } from '@/components/common/ProfileTabPill';

const FEATURED_CREATOR_FACTS = [
{ label: 'Membership', value: 'Collectors Circle' },
Expand Down Expand Up @@ -101,6 +102,7 @@ function LandingPage() {
const [creators, setCreators] = useState<Course[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [searchQuery, setSearchQuery] = useState('');
const [activeProfileTab, setActiveProfileTab] = useState('overview');

const trimmedSearchQuery = searchQuery.trim();
const hasInvalidSearchInput = /[^a-zA-Z0-9_\s-]/.test(trimmedSearchQuery);
Expand Down Expand Up @@ -230,12 +232,23 @@ function LandingPage() {
className="grid gap-8 rounded-[2rem] border border-white/10 bg-white/[0.045] p-6 shadow-[0_24px_80px_-60px_rgba(8,17,31,0.95)] backdrop-blur-sm md:p-8 lg:grid-cols-[1.1fr_0.9fr] lg:items-start"
>
<div>
<SectionHeading
eyebrow="Profile spotlight"
title="A reusable profile facts layout for featured creators"
className="mb-4"
/>
<CompactSectionSubtitle className="max-w-xl">
<SectionHeading
eyebrow="Profile spotlight"
title="A reusable profile facts layout for featured creators"
className="mb-4"
/>
<ProfileTabPillGroup
tabs={[
{ label: 'Overview', value: 'overview' },
{ label: 'Creations', value: 'creations' },
{ label: 'Collectors', value: 'collectors' },
{ label: 'Activity', value: 'activity' },
]}
activeTab={activeProfileTab}
onTabChange={setActiveProfileTab}
className="mb-4"
/>
<CompactSectionSubtitle className="max-w-xl">
Use the same subtitle pattern beneath headings, then drop
repeated creator facts into one responsive grid that stays
tidy on mobile and desktop.
Expand Down
Loading