Skip to content

Commit e935330

Browse files
authored
Merge pull request #25 from sshdopey/drips-contributions
feat(common): add FollowerCountPill, BackHeader, and TruncatedAddress components
2 parents 7b0ece9 + c1756ad commit e935330

3 files changed

Lines changed: 138 additions & 0 deletions

File tree

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { useNavigate } from 'react-router';
2+
import { ArrowLeft } from 'lucide-react';
3+
import { cn } from '@/lib/utils';
4+
5+
interface BackHeaderProps {
6+
title: string;
7+
subtitle?: string;
8+
onBack?: () => void;
9+
className?: string;
10+
}
11+
12+
const BackHeader: React.FC<BackHeaderProps> = ({ title, subtitle, onBack, className }) => {
13+
const navigate = useNavigate();
14+
15+
const handleBack = () => {
16+
if (onBack) {
17+
onBack();
18+
} else {
19+
navigate(-1);
20+
}
21+
};
22+
23+
return (
24+
<div className={cn('flex items-center gap-3', className)}>
25+
<button
26+
onClick={handleBack}
27+
aria-label="Go back"
28+
className="flex items-center justify-center rounded-xl border border-white/10 bg-white/5 p-2 text-white/70 transition-all hover:border-amber-500/30 hover:bg-amber-500/10 hover:text-white"
29+
>
30+
<ArrowLeft className="size-4" />
31+
</button>
32+
<div className="min-w-0">
33+
<h1 className="font-grotesque text-xl font-black tracking-tight text-white truncate">
34+
{title}
35+
</h1>
36+
{subtitle && (
37+
<p className="font-jakarta text-sm text-white/50 truncate">{subtitle}</p>
38+
)}
39+
</div>
40+
</div>
41+
);
42+
};
43+
44+
export default BackHeader;
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { Users } from 'lucide-react';
2+
import { cn } from '@/lib/utils';
3+
4+
interface FollowerCountPillProps {
5+
count?: number | null;
6+
className?: string;
7+
}
8+
9+
function formatCount(count: number): string {
10+
if (count >= 1_000_000) {
11+
return `${(count / 1_000_000).toFixed(1).replace(/\.0$/, '')}M`;
12+
}
13+
if (count >= 1_000) {
14+
return `${(count / 1_000).toFixed(1).replace(/\.0$/, '')}K`;
15+
}
16+
return count.toString();
17+
}
18+
19+
const FollowerCountPill: React.FC<FollowerCountPillProps> = ({ count, className }) => {
20+
if (count == null || count < 0) {
21+
return null;
22+
}
23+
24+
return (
25+
<span
26+
className={cn(
27+
'inline-flex items-center gap-1 rounded-full border border-white/10 bg-white/5 px-2 py-0.5 text-xs font-medium text-white/70',
28+
className
29+
)}
30+
>
31+
<Users className="size-3 text-amber-500/70" />
32+
{formatCount(count)}
33+
</span>
34+
);
35+
};
36+
37+
export default FollowerCountPill;
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { useState } from 'react';
2+
import { Copy, Check } from 'lucide-react';
3+
import { cn } from '@/lib/utils';
4+
5+
interface TruncatedAddressProps {
6+
address: string;
7+
prefixChars?: number;
8+
suffixChars?: number;
9+
copyable?: boolean;
10+
className?: string;
11+
}
12+
13+
function truncate(address: string, prefix: number, suffix: number): string {
14+
if (address.length <= prefix + suffix + 3) {
15+
return address;
16+
}
17+
return `${address.slice(0, prefix)}...${address.slice(-suffix)}`;
18+
}
19+
20+
const TruncatedAddress: React.FC<TruncatedAddressProps> = ({
21+
address,
22+
prefixChars = 6,
23+
suffixChars = 4,
24+
copyable = false,
25+
className,
26+
}) => {
27+
const [copied, setCopied] = useState(false);
28+
29+
const handleCopy = async () => {
30+
await navigator.clipboard.writeText(address);
31+
setCopied(true);
32+
setTimeout(() => setCopied(false), 2000);
33+
};
34+
35+
return (
36+
<span
37+
className={cn(
38+
'inline-flex items-center gap-1 font-mono text-sm text-white/70',
39+
className
40+
)}
41+
title={address}
42+
>
43+
<span>{truncate(address, prefixChars, suffixChars)}</span>
44+
{copyable && (
45+
<button
46+
onClick={handleCopy}
47+
aria-label="Copy address"
48+
className="text-white/40 transition-colors hover:text-amber-500"
49+
>
50+
{copied ? <Check className="size-3" /> : <Copy className="size-3" />}
51+
</button>
52+
)}
53+
</span>
54+
);
55+
};
56+
57+
export default TruncatedAddress;

0 commit comments

Comments
 (0)