Skip to content
Open
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
13 changes: 12 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -151,4 +151,15 @@ dist
# Vite files
vite.config.js.timestamp-*
vite.config.ts.timestamp-*
.vite/
.vite/

# JetBrains IDEA
.idea/*

# OpenCode
.opencode/*
AGENTS.md

# Playwright MCP
.playwright-mcp/*
/*.png
10 changes: 8 additions & 2 deletions full-stack/src/components/design-system/membership-card.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { cva } from 'class-variance-authority';
import { cn } from '@/lib/utils';
import { getInitials } from '@/utils/string';
import { Button } from '../ui/button';
import { Card, CardContent, CardHeader } from '../ui/card';
import { Detail, Subtitle, Text } from './typography';
Expand Down Expand Up @@ -72,13 +73,17 @@ export function MembershipCard({
variant,
groupName,
roles,
startDate = '2020 mar',
endDate,
onOpen,
}: {
className?: string;
variant: 'tier1' | 'tier2' | 'active' | 'alumni' | 'newbie';
groupName: string;
roles: string;
startDate?: string;
endDate?: string;
onOpen?: () => void;
}) {
const hasBackground =
variant === 'tier1' || variant === 'tier2' || variant === 'active';
Expand All @@ -87,6 +92,7 @@ export function MembershipCard({

return (
<Card
onClick={onOpen}
className={cn(
containerVariants({ className, variant }),
'min-h-40 w-80 gap-0'
Expand All @@ -95,7 +101,7 @@ export function MembershipCard({
<CardHeader className={headerVariants({ variant })}>
{/* Group Avatar */}
<div className={cn(avatarVariants({ variant }), 'h-14 w-14 shrink-0')}>
S
{getInitials(groupName)}
</div>
<div className='min-w-0 overflow-hidden *:truncate'>
{/* Group name */}
Expand Down Expand Up @@ -142,7 +148,7 @@ export function MembershipCard({
</Text>
<div className={accentVariants({ variant })} />
<Text muted className='tabular-nums'>
{'2020 mar'}
{startDate}
{endDate ? (
<>
<br />
Expand Down
36 changes: 18 additions & 18 deletions full-stack/src/components/design-system/user-avatar.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -246,21 +246,21 @@ export const AllSizes: Story = {
name: 'All Sizes',
render: () => (
<div className='flex flex-wrap items-end gap-8'>
<UserAvatar tier='tier-1' size='sm'>
<UserAvatar tier='tier-1' className='avatar-small'>
<UserAvatarImage
src='https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?w=200&h=200&fit=crop'
alt='Small'
/>
<UserAvatarFallback>JD</UserAvatarFallback>
</UserAvatar>
<UserAvatar tier='tier-1' size='default'>
<UserAvatar tier='tier-1' className='avatar-default'>
<UserAvatarImage
src='https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?w=200&h=200&fit=crop'
alt='Default'
/>
<UserAvatarFallback>JD</UserAvatarFallback>
</UserAvatar>
<UserAvatar tier='tier-1' size='lg'>
<UserAvatar tier='tier-1' className='avatar-large'>
<UserAvatarImage
src='https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?w=200&h=200&fit=crop'
alt='Large'
Expand All @@ -277,61 +277,61 @@ export const AllVariants: Story = {
<div className='flex flex-col gap-8'>
{/* Tier 1 row */}
<div className='flex items-end gap-4'>
<UserAvatar tier='tier-1' size='sm'>
<UserAvatar tier='tier-1' className='avatar-small'>
<UserAvatarFallback>T1</UserAvatarFallback>
</UserAvatar>
<UserAvatar tier='tier-1' size='default'>
<UserAvatar tier='tier-1' className='avatar-default'>
<UserAvatarFallback>T1</UserAvatarFallback>
</UserAvatar>
<UserAvatar tier='tier-1' size='lg'>
<UserAvatar tier='tier-1' className='avatar-large'>
<UserAvatarFallback>T1</UserAvatarFallback>
</UserAvatar>
</div>
{/* Tier 2 row */}
<div className='flex items-end gap-4'>
<UserAvatar tier='tier-2' size='sm'>
<UserAvatar tier='tier-2' className='avatar-small'>
<UserAvatarFallback>T2</UserAvatarFallback>
</UserAvatar>
<UserAvatar tier='tier-2' size='default'>
<UserAvatar tier='tier-2' className='avatar-default'>
<UserAvatarFallback>T2</UserAvatarFallback>
</UserAvatar>
<UserAvatar tier='tier-2' size='lg'>
<UserAvatar tier='tier-2' className='avatar-large'>
<UserAvatarFallback>T2</UserAvatarFallback>
</UserAvatar>
</div>
{/* Tier 3 row */}
<div className='flex items-end gap-4'>
<UserAvatar tier='active' size='sm'>
<UserAvatar tier='active' className='avatar-small'>
<UserAvatarFallback>T3</UserAvatarFallback>
</UserAvatar>
<UserAvatar tier='active' size='default'>
<UserAvatar tier='active' className='avatar-default'>
<UserAvatarFallback>T3</UserAvatarFallback>
</UserAvatar>
<UserAvatar tier='active' size='lg'>
<UserAvatar tier='active' className='avatar-large'>
<UserAvatarFallback>T3</UserAvatarFallback>
</UserAvatar>
</div>
{/* Draft row */}
<div className='flex items-end gap-4'>
<UserAvatar tier='unknown' size='sm'>
<UserAvatar tier='unknown' className='avatar-small'>
<UserAvatarFallback>DR</UserAvatarFallback>
</UserAvatar>
<UserAvatar tier='unknown' size='default'>
<UserAvatar tier='unknown' className='avatar-default'>
<UserAvatarFallback>DR</UserAvatarFallback>
</UserAvatar>
<UserAvatar tier='unknown' size='lg'>
<UserAvatar tier='unknown' className='avatar-large'>
<UserAvatarFallback>DR</UserAvatarFallback>
</UserAvatar>
</div>
{/* Inactive row */}
<div className='flex items-end gap-4'>
<UserAvatar tier='inactive' size='sm'>
<UserAvatar tier='inactive' className='avatar-small'>
<UserAvatarFallback>IN</UserAvatarFallback>
</UserAvatar>
<UserAvatar tier='inactive' size='default'>
<UserAvatar tier='inactive' className='avatar-default'>
<UserAvatarFallback>IN</UserAvatarFallback>
</UserAvatar>
<UserAvatar tier='inactive' size='lg'>
<UserAvatar tier='inactive' className='avatar-large'>
<UserAvatarFallback>IN</UserAvatarFallback>
</UserAvatar>
</div>
Expand Down
33 changes: 9 additions & 24 deletions full-stack/src/components/design-system/user-avatar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,19 @@ export type UserAvatarTier =
| 'active'
| 'inactive'
| 'unknown';
export type UserAvatarSize = 'sm' | 'default' | 'lg';
export type AvatarSizeClass =
| 'avatar-small'
| 'avatar-default'
| 'avatar-large';

export interface UserAvatarProps extends ComponentProps<typeof Avatar> {
tier?: UserAvatarTier;
size?: UserAvatarSize;
showBadge?: boolean;
children?: React.ReactNode;
}

export function UserAvatar({
tier = 'unknown',
size = 'default',
showBadge = true,
className,
children,
Expand All @@ -29,24 +30,18 @@ export function UserAvatar({
<Avatar
data-slot='user-avatar'
data-tier={tier}
data-size={size}
className={cn(
'relative rounded-full',
// Ring styling per tier
'ring-4 data-[size=lg]:ring-6 data-[size=sm]:ring-2',
'ring-transparent data-[tier=active]:ring-tier-3-start data-[tier=inactive]:ring-tier-inactive data-[tier=tier-1]:ring-tier-1-start data-[tier=tier-2]:ring-tier-2-start',
// Size styling
'data-[size=default]:size-30 data-[size=lg]:size-50 data-[size=sm]:size-10',
className
)}
{...props}
>
{children}
{showBadge &&
size !== 'sm' &&
(tier === 'tier-1' || tier === 'tier-2') && (
<UserAvatarBadge large={size === 'lg'} tier={tier} />
)}
{showBadge && (tier === 'tier-1' || tier === 'tier-2') && (
<UserAvatarBadge tier={tier} />
)}
</Avatar>
);
}
Expand Down Expand Up @@ -78,8 +73,6 @@ export function UserAvatarFallback({
className={cn(
'flex size-full items-center justify-center rounded-full',
'font-semibold text-lg',
'data-[size=sm]:text-xs',
'data-[size=lg]:text-3xl',
className
)}
{...props}
Expand All @@ -89,13 +82,7 @@ export function UserAvatarFallback({
);
}

function UserAvatarBadge({
large,
tier,
}: {
large: boolean;
tier: 'tier-1' | 'tier-2';
}) {
function UserAvatarBadge({ tier }: { tier: 'tier-1' | 'tier-2' }) {
const BadgeIcon = tier === 'tier-2' ? KBCard : ABCard;

return (
Expand All @@ -110,9 +97,7 @@ function UserAvatarBadge({
// Tier gradient background
'bg-linear-to-br',
'data-[tier=tier-1]:from-tier-1-start data-[tier=tier-1]:to-tier-1-end',
'data-[tier=tier-2]:from-tier-2-start data-[tier=tier-2]:to-tier-2-end',
// Size-based scaling
large && 'origin-bottom-left scale-[2]'
'data-[tier=tier-2]:from-tier-2-start data-[tier=tier-2]:to-tier-2-end'
)}
>
<BadgeIcon className='h-7 w-fit' />
Expand Down
16 changes: 5 additions & 11 deletions full-stack/src/components/ui/avatar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,13 @@ import { cn } from "@/lib/utils"

function Avatar({
className,
size = "default",
...props
}: React.ComponentProps<typeof AvatarPrimitive.Root> & {
size?: "default" | "sm" | "lg"
}) {
}: React.ComponentProps<typeof AvatarPrimitive.Root>) {
return (
<AvatarPrimitive.Root
data-slot="avatar"
data-size={size}
className={cn(
"size-8 rounded-full after:rounded-full data-[size=lg]:size-10 data-[size=sm]:size-6 after:border-border group/avatar relative flex shrink-0 select-none after:absolute after:inset-0 after:border after:mix-blend-darken dark:after:mix-blend-lighten",
"rounded-full after:rounded-full after:border-border group/avatar relative flex shrink-0 select-none after:absolute after:inset-0 after:border after:mix-blend-darken dark:after:mix-blend-lighten",
className
)}
{...props}
Expand Down Expand Up @@ -47,7 +43,7 @@ function AvatarFallback({
<AvatarPrimitive.Fallback
data-slot="avatar-fallback"
className={cn(
"bg-muted text-muted-foreground rounded-full flex size-full items-center justify-center text-sm group-data-[size=sm]/avatar:text-xs",
"bg-muted text-muted-foreground rounded-full flex size-full items-center justify-center text-sm",
className
)}
{...props}
Expand All @@ -61,9 +57,7 @@ function AvatarBadge({ className, ...props }: React.ComponentProps<"span">) {
data-slot="avatar-badge"
className={cn(
"bg-primary text-primary-foreground ring-background absolute right-0 bottom-0 z-10 inline-flex items-center justify-center rounded-full bg-blend-color ring-2 select-none",
"group-data-[size=sm]/avatar:size-2 group-data-[size=sm]/avatar:[&>svg]:hidden",
"group-data-[size=default]/avatar:size-2.5 group-data-[size=default]/avatar:[&>svg]:size-2",
"group-data-[size=lg]/avatar:size-3 group-data-[size=lg]/avatar:[&>svg]:size-2",

className
)}
{...props}
Expand Down Expand Up @@ -91,7 +85,7 @@ function AvatarGroupCount({
return (
<div
data-slot="avatar-group-count"
className={cn("bg-muted text-muted-foreground size-8 rounded-full text-sm group-has-data-[size=lg]/avatar-group:size-10 group-has-data-[size=sm]/avatar-group:size-6 [&>svg]:size-4 group-has-data-[size=lg]/avatar-group:[&>svg]:size-5 group-has-data-[size=sm]/avatar-group:[&>svg]:size-3 ring-background relative flex shrink-0 items-center justify-center ring-2", className)}
className={cn("bg-muted text-muted-foreground rounded-full text-sm ring-background relative flex shrink-0 items-center justify-center ring-2", className)}
{...props}
/>
)
Expand Down
2 changes: 0 additions & 2 deletions full-stack/src/routes/__root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import {
Scripts,
} from '@tanstack/react-router';
import { Devtools } from '@/devtools';
import { Header } from '../components/header';
import appCss from '../styles.css?url';

export const Route = createRootRouteWithContext<{
Expand Down Expand Up @@ -43,7 +42,6 @@ function RootDocument({ children }: { children: React.ReactNode }) {
<HeadContent />
</head>
<body>
<Header />
{children}

<Scripts />
Expand Down
Loading
Loading