diff --git a/.gitignore b/.gitignore index ee81e8959..7553fe246 100644 --- a/.gitignore +++ b/.gitignore @@ -29,3 +29,4 @@ dist .content-collections test-results +.claude/CLAUDE.md diff --git a/src/components/DocsLayout.tsx b/src/components/DocsLayout.tsx index 6eadd3e1f..616e05833 100644 --- a/src/components/DocsLayout.tsx +++ b/src/components/DocsLayout.tsx @@ -378,6 +378,10 @@ const useMenuConfig = ({ label: 'Contributors', to: '/$libraryId/$version/docs/contributors', }, + { + label: 'NPM Stats', + to: '/$libraryId/$version/docs/npm-stats', + }, ...(config.sections.find((d) => d.label === 'Community Resources') ? [ { diff --git a/src/components/NpmStatsSummaryBar.tsx b/src/components/NpmStatsSummaryBar.tsx new file mode 100644 index 000000000..955045857 --- /dev/null +++ b/src/components/NpmStatsSummaryBar.tsx @@ -0,0 +1,121 @@ +import { Suspense } from 'react' +import { useSuspenseQuery } from '@tanstack/react-query' +import { BlankErrorBoundary } from './BlankErrorBoundary' +import type { LibrarySlim } from '~/libraries' +import { ossStatsQuery, recentDownloadStatsQuery } from '~/queries/stats' +import { useNpmDownloadCounter } from '~/hooks/useNpmDownloadCounter' + +function formatNumber(num: number): string { + if (num >= 1000000) { + return (num / 1000000).toFixed(1) + 'M' + } else if (num >= 1000) { + return (num / 1000).toFixed(1) + 'K' + } + return num.toLocaleString() +} + +function isValidMetric(value: number | undefined | null): boolean { + return ( + value !== undefined && + value !== null && + !Number.isNaN(value) && + value >= 0 && + Number.isFinite(value) + ) +} + +function NpmStatsSummaryContent({ library }: { library: LibrarySlim }) { + const { data: stats } = useSuspenseQuery(ossStatsQuery({ library })) + const { data: recentStats } = useSuspenseQuery( + recentDownloadStatsQuery({ library }), + ) + + const npmDownloads = stats.npm?.totalDownloads ?? 0 + const hasNpmDownloads = isValidMetric(npmDownloads) + + // Use actual data from the API + const dailyDownloads = recentStats?.dailyDownloads ?? 0 + const weeklyDownloads = recentStats?.weeklyDownloads ?? 0 + const monthlyDownloads = recentStats?.monthlyDownloads ?? 0 + + // IMPORTANT: useNpmDownloadCounter returns a ref callback, not state + // Must be applied to a DOM element + const counterRef = useNpmDownloadCounter(stats.npm) + + return ( +
+ View download statistics for {library.name} packages. Compare + different time periods and track usage trends. +
++ *These top summary stats account for core packages, legacy package + names, and all framework adapters. +
+| + Package Name + | ++ Total Period Downloads + | ++ Downloads last {binningOptionsByType[binType].single} + | +
|---|---|---|
|
+
+
+
+ {firstPackage.name}
+
+
+ |
+ + {formatNumber(totalDownloads)} + | ++ {lastBin ? formatNumber(lastBin[1]) : '-'} + | +