Files
hanzo.ai/components/oss/HanzoOpenSourceShowcase.tsx
zeekay 24e67d965e landing: what we ship is counted, and drawn from the page's own parts
The commits section read numbers that were typed into scripts/sync-oss.ts
(5,420 repositories, 214 orgs, 784 pushes a day, 245,417 commits, each
contributor's count), a day's count scaled by a made-up factor, and a star
count that began at 1,420. The forge's database holds 1,150 repositories
and 46 orgs; none of that was read. Its panels were tinted rgba over a
stylesheet, DualStarButton showed its guess before GitHub answered, and
BulkStarModal said "successfully starred" over a forge PUT.

scripts/sync-oss.ts (was .mjs) now counts GitHub's public activity for the
orgs we publish: commits a day, the year, the week, active repositories and
the newest commits, written at each build with the moment it was read. A
figure the snapshot does not hold is not drawn. The grid, bars and
repository card are @hanzo/ui/marketing (8.29.19); the showcase stylesheet
and DayActivityModal are deleted. The org and repo pages lose their typed
"velocity" banners.

Design lint is zero in components/oss, home/OpenSource and ForgeBetaBanner.
e2e/gates/activity.spec.ts holds each figure to the snapshot, the cards to
one fill and edge in both schemes, a card's name, line and foot to their own
rows, and the phone to no sideways scroll.

Claude-Session: https://claude.ai/code/session_017vrVus6PPBgP5YRzKV1iqW
2026-10-26 09:57:01 -07:00

234 lines
11 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client'
import { useMemo, useRef, useState } from 'react'
import Link from 'next/link'
import { ArrowRight, ArrowUpRight, Search } from 'lucide-react'
import { useI18n } from '@hanzo/i18n/react'
import { Input, Text, View, XStack, YStack } from '@hanzo/ui'
import { Action, Repo } from '@hanzo/ui/marketing'
import stats from '@/lib/data/oss-stats.json'
import { Caption, Card, CardBody, CardTitle, Mono } from '@/components/home/plate'
import { catalog, type Organization } from './showcase-catalog'
import Activity from './Activity'
import DualStarButton from './DualStarButton'
/**
* The open-source directory: what we ship, counted (./Activity), the repositories
* to start from, and every organization, searchable by name and filtered by kind.
* Drawn from the landing's own parts, so a card here is the card everywhere.
*/
type Scope = 'hanzo' | 'related'
/** A card that is a link: one hairline, one radius, one inset, the page's own fill. */
const FRAME = { rounded: 'var(--radius-xl)', borderWidth: 1, borderColor: '$edge', bg: '$panel', p: 20, gap: 8, minW: 0 } as const
export interface HanzoOpenSourceShowcaseProps {
/** When true, renders only the activity panel (for embedding at the top of an existing repos section). */
activityOnly?: boolean
/** Start in the related ecosystem rather than the Hanzo-branded organizations. */
initialScope?: Scope
}
export function HanzoOpenSourceShowcase({ activityOnly = false, initialScope = 'hanzo' }: HanzoOpenSourceShowcaseProps) {
const { t } = useI18n()
const [scope, setScope] = useState<Scope>(initialScope)
const [category, setCategory] = useState('All')
const [query, setQuery] = useState('')
const [expanded, setExpanded] = useState(false)
const inputRef = useRef<HTMLInputElement>(null)
const organizations = scope === 'hanzo' ? catalog.primary : catalog.related
const categories = useMemo(() => ['All', ...Array.from(new Set(organizations.map((o) => o.category)))], [organizations])
const filtered = useMemo(
() => organizations.filter((o) => (category === 'All' || o.category === category) && `${o.slug} ${o.title} ${o.description}`.toLowerCase().includes(query.toLowerCase())),
[organizations, category, query],
)
const visible = expanded || category !== 'All' || query ? filtered : filtered.slice(0, 9)
const choose = (value: Scope) => {
setScope(value)
setCategory('All')
setQuery('')
setExpanded(false)
}
if (activityOnly) return <Activity />
return (
<YStack gap={48} width="100%" data-slot="oss">
<Activity />
<YStack gap={20} render="section" aria-labelledby="oss-repos">
<XStack justify="space-between" items="flex-end" flexWrap="wrap" gap={12}>
<YStack gap={6} maxW={640}>
<Text render="h2" id="oss-repos" fontSize="$7" lineHeight="$7" fontWeight="600" color="var(--foreground)">
{t('Start with the foundations.')}
</Text>
<Caption>{t('Selected open projects with direct paths to their source code.')}</Caption>
</YStack>
<Action href="https://forge.hanzo.ai" target="_blank" rel="noopener noreferrer">
{t('All repositories on the forge')}
<ArrowUpRight size={14} aria-hidden data-i18n-mirror="" />
</Action>
</XStack>
<View display="grid" gridTemplateColumns="minmax(0, 1fr)" gap={12} $sm={{ gridTemplateColumns: 'repeat(2, minmax(0, 1fr))' }} $lg={{ gridTemplateColumns: 'repeat(4, minmax(0, 1fr))' }}>
{catalog.repos.map((repo) => (
<Repo
key={`${repo.org}/${repo.name}`}
name={`${repo.org}/${repo.name}`}
description={repo.desc}
href={`/oss/${repo.org}/${repo.name}`}
tag={repo.tag}
actions={<DualStarButton owner={repo.org} repo={repo.name} size="sm" />}
/>
))}
</View>
</YStack>
<YStack gap={20} render="section" id="hanzo-oss-directory" aria-labelledby="oss-orgs">
<XStack justify="space-between" items="flex-end" flexWrap="wrap" gap={12}>
<YStack gap={6} maxW={640}>
<Text render="h2" id="oss-orgs" fontSize="$7" lineHeight="$7" fontWeight="600" color="var(--foreground)">
{t('One ecosystem. Many layers.')}
</Text>
<Caption>{t('{n} Hanzo-branded organizations, plus related ecosystem projects in one directory.', { n: catalog.primary.length })}</Caption>
</YStack>
<Mono color="var(--muted-foreground)">{t('{n} organizations', { n: filtered.length })}</Mono>
</XStack>
<XStack justify="space-between" items="center" flexWrap="wrap" gap={12}>
<XStack role="group" aria-label={t('Organization scope')} gap={2} p={3} rounded={9} borderWidth={1} borderColor="var(--border)" $platform-web={{ borderRadius: '9px' }}>
{(['hanzo', 'related'] as const).map((s) => (
<XStack
key={s}
render={<button type="button" aria-pressed={scope === s} onClick={() => choose(s)} />}
px={12}
py={6}
rounded={6}
gap={8}
items="center"
bg={scope === s ? 'var(--foreground)' : 'transparent'}
cursor="pointer"
$platform-web={{ borderRadius: '6px', border: 'none' }}
>
<Text fontSize="$2" fontWeight="500" color={scope === s ? 'var(--background)' : 'var(--muted-foreground)'}>
{s === 'hanzo' ? t('Hanzo organizations') : t('Related ecosystem')}
</Text>
<Mono color={scope === s ? 'var(--background)' : 'var(--muted-foreground)'}>{s === 'hanzo' ? catalog.primary.length : catalog.related.length}</Mono>
</XStack>
))}
</XStack>
<XStack items="center" gap={8} px={10} height={36} rounded={8} borderWidth={1} borderColor="var(--border)" minW={235} $platform-web={{ borderRadius: '8px' }}>
<Search size={14} color="var(--muted-foreground)" aria-hidden />
<Input
ref={inputRef}
type="search"
value={query}
onChange={(e) => setQuery(e.target.value)}
onKeyDown={(e) => {
if (e.key === 'Escape') setQuery('')
}}
placeholder={t('Search organizations')}
aria-label={t('Search organizations')}
flex={1}
minW={0}
borderWidth={0}
bg="transparent"
/>
</XStack>
</XStack>
<XStack gap={8} flexWrap="wrap" role="group" aria-label={t('Filter organizations by category')}>
{categories.map((c) => (
<XStack
key={c}
render={
<button
type="button"
aria-pressed={category === c}
onClick={() => {
setCategory(c)
setExpanded(false)
}}
/>
}
px={12}
py={5}
rounded={9999}
borderWidth={1}
borderColor={category === c ? 'var(--foreground)' : 'var(--border)'}
bg={category === c ? 'var(--secondary)' : 'transparent'}
cursor="pointer"
$platform-web={{ borderRadius: '9999px' }}
>
<Text fontSize="$2" color={category === c ? 'var(--foreground)' : 'var(--muted-foreground)'}>
{c === 'All' ? `${t('All')} (${organizations.length})` : c}
</Text>
</XStack>
))}
</XStack>
<View display="grid" gridTemplateColumns="minmax(0, 1fr)" gap={12} $sm={{ gridTemplateColumns: 'repeat(2, minmax(0, 1fr))' }} $lg={{ gridTemplateColumns: 'repeat(3, minmax(0, 1fr))' }} aria-live="polite">
{visible.length ? (
visible.map((org: Organization) => (
<YStack key={org.slug} render={<Link href={`/oss/${org.slug}`} />} {...FRAME} hoverStyle={{ borderColor: 'var(--border-strong)' }} $platform-web={{ textDecoration: 'none' }}>
<XStack justify="space-between" items="center" gap={8}>
<CardTitle render="div">{org.title}</CardTitle>
<ArrowRight size={14} color="var(--muted-foreground)" aria-hidden data-i18n-mirror="" />
</XStack>
<Mono color="var(--muted-foreground)">/oss/{org.slug}</Mono>
<CardBody>
{org.description}
{org.visibility === 'mixed' ? ` ${t('Private work excluded.')}` : ''}
</CardBody>
</YStack>
))
) : (
<YStack gridColumn="1 / -1" p={32} items="center" rounded="var(--radius-xl)" borderWidth={1} borderColor="var(--border)" $platform-web={{ borderStyle: 'dashed' }}>
<CardBody>{t('No organizations match this filter. Try a broader search.')}</CardBody>
</YStack>
)}
</View>
<XStack justify="space-between" items="center" flexWrap="wrap" gap={12}>
{filtered.length > 9 && category === 'All' && !query ? (
<Action render="button" type="button" aria-expanded={expanded} onClick={() => setExpanded(!expanded)}>
{expanded ? t('Show fewer organizations') : t('View all organizations')}
</Action>
) : (
<View />
)}
<Caption>{t('Showing {shown} of {total} matched organizations', { shown: visible.length, total: filtered.length })}</Caption>
</XStack>
</YStack>
<Card
title={t('Counted from GitHub')}
body={t('{repos} public repositories across {orgs} organizations, with the source on git.hanzo.ai and mirrored on GitHub.', { repos: stats.repos.toLocaleString('en-US'), orgs: stats.orgs })}
/>
<XStack justify="space-between" items="center" flexWrap="wrap" gap={20} pt={24} borderTopWidth={1} borderColor="var(--border)">
<YStack gap={6} maxW={560}>
<Text render="h2" fontSize="$8" lineHeight="$8" fontWeight="600" color="var(--foreground)">
{t('See what we’re building.')}
</Text>
<Caption>{t('Read the source. Explore the stack. Build something better.')}</Caption>
</YStack>
<XStack gap={10} flexWrap="wrap">
<Action href="https://forge.hanzo.ai" fill target="_blank" rel="noopener noreferrer">
{t('Explore the forge')}
<ArrowUpRight size={14} aria-hidden data-i18n-mirror="" />
</Action>
<Action href="https://github.com/hanzoai" target="_blank" rel="noopener noreferrer">
{t('Explore GitHub')}
<ArrowUpRight size={14} aria-hidden data-i18n-mirror="" />
</Action>
</XStack>
</XStack>
</YStack>
)
}
export default HanzoOpenSourceShowcase