Files
hanzo.ai/components/oss/RepoCard.tsx

216 lines
7.0 KiB
TypeScript

'use client'
import React, { useState } from 'react'
import { ArrowUpRight, Star, Eye } from 'lucide-react'
import { Text, XStack, YStack } from '@hanzo/ui'
import { useI18n } from '@hanzo/i18n/react'
export interface RepoCardProps {
repo: string
line: string
language: string
}
export function GithubMark({ size = 14, color = 'currentColor' }: { size?: number; color?: string }) {
return (
<svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke={color} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
<path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22" />
</svg>
)
}
export function ForgeMark({ size = 14, color = 'currentColor' }: { size?: number; color?: string }) {
return (
<svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke={color} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
<circle cx="12" cy="18" r="3" />
<circle cx="6" cy="6" r="3" />
<circle cx="18" cy="6" r="3" />
<path d="M18 9v2a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2V9" />
<path d="M12 12v3" />
</svg>
)
}
export default function RepoCard({ repo, line, language }: RepoCardProps) {
const { t } = useI18n()
const [starred, setStarred] = useState(false)
const [watching, setWatching] = useState(false)
const [owner, name] = repo.split('/')
const ghUrl = `https://github.com/${repo}`
const forgeUrl = `https://forge.hanzo.ai/${repo}`
const handleStar = (e: React.MouseEvent) => {
e.preventDefault()
e.stopPropagation()
const next = !starred
setStarred(next)
// Call Forge star API
try {
fetch(`https://forge.hanzo.ai/api/v1/user/starred/${owner}/${name}`, {
method: next ? 'PUT' : 'DELETE',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
}).catch(() => {})
} catch (_) {}
// Open GitHub repo in background tab if starring
if (next) {
try {
const win = window.open(ghUrl, '_blank', 'noopener,noreferrer')
if (win) win.blur()
window.focus()
} catch (_) {}
}
}
const handleWatch = (e: React.MouseEvent) => {
e.preventDefault()
e.stopPropagation()
setWatching(!watching)
try {
window.open(`${ghUrl}/subscription`, '_blank', 'noopener,noreferrer')
} catch (_) {}
}
return (
<YStack
render={<a href={ghUrl} target="_blank" rel="noopener noreferrer" />}
gap={10}
p={16}
rounded={14}
borderWidth={1}
borderColor="var(--border)"
bg="rgba(255, 255, 255, 0.01)"
hoverStyle={{ borderColor: 'var(--border-strong)', bg: 'rgba(255, 255, 255, 0.025)' }}
cursor="pointer"
$platform-web={{
borderRadius: '14px',
borderStyle: 'solid',
textDecoration: 'none',
transition: 'all 0.16s ease-in-out',
}}
>
{/* Top row: repo name + top-right arrow */}
<XStack items="center" justify="space-between" gap={8}>
<Text
fontFamily="$mono"
fontSize="$2"
fontWeight="600"
color="var(--foreground)"
numberOfLines={1}
data-i18n-ltr=""
>
{repo}
</Text>
<ArrowUpRight size={13} color="var(--muted-foreground)" aria-hidden data-i18n-mirror="" />
</XStack>
{/* Description */}
<Text
fontSize="$2"
lineHeight="$3"
color="var(--muted-foreground)"
numberOfLines={2}
minH={38}
>
{t(line)}
</Text>
{/* Bottom Action Row */}
<XStack items="center" justify="space-between" gap={8} mt="auto" pt={4}>
<XStack items="center" gap={6}>
{/* Star pill button */}
<XStack
render={<button type="button" onClick={handleStar} title={t('Star on GitHub and Hanzo Forge')} />}
items="center"
gap={5}
px={9}
py={4}
rounded={6}
borderWidth={1}
borderColor={starred ? '#39bd7d' : 'var(--border)'}
bg={starred ? 'rgba(57, 189, 125, 0.15)' : 'rgba(255, 255, 255, 0.03)'}
hoverStyle={{
borderColor: starred ? '#56e36d' : 'var(--border-strong)',
bg: starred ? 'rgba(57, 189, 125, 0.22)' : 'rgba(255, 255, 255, 0.08)',
}}
cursor="pointer"
$platform-web={{
borderRadius: '6px',
borderStyle: 'solid',
transition: 'all 0.15s ease',
}}
>
<Star
size={12}
color={starred ? '#39bd7d' : 'var(--muted-foreground)'}
fill={starred ? '#39bd7d' : 'none'}
/>
<Text
fontSize={11}
fontWeight="600"
color={starred ? '#39bd7d' : 'var(--foreground)'}
>
{starred ? t('Starred') : t('Star')}
</Text>
</XStack>
{/* Watch icon button */}
<XStack
render={<button type="button" onClick={handleWatch} title={t('Subscribe to updates')} />}
items="center"
justify="center"
p={5}
rounded={6}
borderWidth={1}
borderColor={watching ? '#39bd7d' : 'transparent'}
bg={watching ? 'rgba(57, 189, 125, 0.1)' : 'transparent'}
hoverStyle={{ bg: 'var(--white-06)' }}
cursor="pointer"
$platform-web={{ borderRadius: '6px', borderStyle: 'solid' }}
>
<Eye size={13} color={watching ? '#39bd7d' : 'var(--muted-foreground)'} />
</XStack>
</XStack>
{/* Source links: GH & Forge */}
<XStack items="center" gap={8}>
<XStack
render={<a href={ghUrl} target="_blank" rel="noopener noreferrer" title="View on GitHub" />}
$platform-web={{ textDecoration: 'none' }}
onClick={(e) => e.stopPropagation()}
>
<Text
fontFamily="$mono"
fontSize={11}
fontWeight="600"
color="var(--muted-foreground)"
hoverStyle={{ color: 'var(--foreground)' }}
>
GH
</Text>
</XStack>
<XStack
render={<a href={forgeUrl} target="_blank" rel="noopener noreferrer" title="View on Hanzo Forge" />}
$platform-web={{ textDecoration: 'none' }}
onClick={(e) => e.stopPropagation()}
>
<Text
fontFamily="$mono"
fontSize={11}
fontWeight="600"
color="#39bd7d"
hoverStyle={{ color: '#56e36d' }}
>
Forge
</Text>
</XStack>
</XStack>
</XStack>
</YStack>
)
}