Skip to content

Commit 52c6558

Browse files
authored
chore: Use Tremor for stats dataviz
* chore: Use Tremor for stats dataviz * chore: Less padding on mobile * chore: Rework version adoption calculation
1 parent 66418ca commit 52c6558

19 files changed

+965
-354
lines changed

packages/docs/package.json

+3
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
},
1111
"dependencies": {
1212
"@faker-js/faker": "^8.4.0",
13+
"@headlessui/react": "^1.7.18",
14+
"@headlessui/tailwindcss": "^0.2.0",
1315
"@radix-ui/react-checkbox": "^1.0.4",
1416
"@radix-ui/react-label": "^2.0.2",
1517
"@radix-ui/react-separator": "^1.0.3",
@@ -19,6 +21,7 @@
1921
"@radix-ui/react-toggle": "^1.0.3",
2022
"@radix-ui/react-toggle-group": "^1.0.4",
2123
"@tailwindcss/container-queries": "^0.1.1",
24+
"@tremor/react": "^3.14.0",
2225
"cheerio": "1.0.0-rc.12",
2326
"class-variance-authority": "^0.7.0",
2427
"clsx": "^2.1.0",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
'use client'
2+
3+
import { LineChart } from '@tremor/react'
4+
import { formatStatNumber } from '../lib/format'
5+
import type { Datum, MultiDatum } from '../lib/npm'
6+
import { Widget, WidgetProps } from './widget'
7+
8+
type DownloadsGraphProps = WidgetProps & {
9+
data: (Datum | MultiDatum)[]
10+
dataKeys: string[]
11+
}
12+
13+
// stroke-red-500 fill-red-500 bg-red-500
14+
// stroke-zinc-500/50 fill-zinc-500/50 bg-zinc-500/50
15+
16+
export function DownloadsGraph({
17+
data,
18+
dataKeys,
19+
...props
20+
}: DownloadsGraphProps) {
21+
return (
22+
<Widget {...props}>
23+
<LineChart
24+
data={data}
25+
curveType="monotone"
26+
index="date"
27+
tickGap={40}
28+
yAxisWidth={30}
29+
categories={dataKeys}
30+
colors={['red-500', 'zinc-500/50']}
31+
valueFormatter={v => formatStatNumber(v)}
32+
/>
33+
</Widget>
34+
)
35+
}
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,83 @@
11
import { Download } from 'lucide-react'
2-
import { twMerge } from 'tailwind-merge'
3-
import { fetchNpmPackage } from '../lib/npm'
4-
import { SvgCurveGraph } from './svg-curve-graph'
2+
import { Suspense } from 'react'
3+
import { formatStatNumber } from '../lib/format'
4+
import { combineStats, fetchNpmPackage } from '../lib/npm'
5+
import { DownloadsGraph } from './downloads.client'
6+
import { BarList } from './tremor'
57

6-
type DownloadsGraphProps = React.ComponentProps<'section'> & {}
7-
8-
const formatter = new Intl.NumberFormat('en-GB', {
9-
compactDisplay: 'short',
10-
notation: 'compact'
11-
})
8+
export async function NPMStats() {
9+
const [nuqs, nextUseQueryState] = await Promise.all([
10+
fetchNpmPackage('nuqs'),
11+
fetchNpmPackage('next-usequerystate')
12+
])
13+
const both = combineStats(nuqs, nextUseQueryState)
14+
return (
15+
<>
16+
<h3 className="flex items-center gap-2 text-4xl font-bold">
17+
<Download size={32} /> {formatStatNumber(both.allTime)}
18+
</h3>
19+
<BarList
20+
data={[
21+
{
22+
name: 'next-usequerystate',
23+
value: nextUseQueryState.allTime,
24+
color: 'zinc'
25+
},
26+
{ name: 'nuqs', value: nuqs.allTime, color: 'red' }
27+
]}
28+
className="flex-1"
29+
/>
30+
</>
31+
)
32+
}
1233

13-
export async function DownloadsGraph({
14-
className,
15-
...props
16-
}: DownloadsGraphProps) {
34+
export async function NPMDownloads() {
1735
const [nuqs, nextUseQueryState] = await Promise.all([
1836
fetchNpmPackage('nuqs'),
1937
fetchNpmPackage('next-usequerystate')
2038
])
21-
const last30Days = nuqs.last30Days.reduce((acc, x, i) => {
22-
acc[i] = x + nextUseQueryState.last30Days[i]
23-
return acc
24-
}, [] as number[])
25-
const totalDownloads = nuqs.allTime + nextUseQueryState.allTime
39+
const both = combineStats(nuqs, nextUseQueryState)
2640
return (
27-
<section className={twMerge('relative', className)} {...props}>
28-
<SvgCurveGraph
29-
data={last30Days}
30-
lastDate={nuqs.lastDate}
31-
height={200}
32-
summaryValue={last30Days.reduce((sum, x) => sum + x)}
33-
className="text-red-500"
41+
<Suspense>
42+
<DownloadsGraph
43+
data={both.last90Days}
44+
dataKeys={['nuqs', 'next-usequerystate']}
45+
title={
46+
<>
47+
<Download size={20} /> Last 90 days
48+
</>
49+
}
3450
/>
35-
<p className="absolute left-2 top-2 flex items-center gap-2 text-2xl font-semibold tabular-nums md:text-6xl">
36-
<Download
37-
aria-label="NPM package downloads"
38-
className="inline-block md:h-12 md:w-12"
39-
/>{' '}
40-
{formatter.format(totalDownloads)}
41-
</p>
42-
</section>
51+
<DownloadsGraph
52+
data={both.last30Days}
53+
dataKeys={['nuqs', 'next-usequerystate']}
54+
title={
55+
<>
56+
<Download size={20} /> Last 30 days
57+
</>
58+
}
59+
/>
60+
</Suspense>
61+
// <section className={twMerge('relative', className)} {...props}>
62+
// <LineChart
63+
// data=
64+
65+
// />
66+
67+
// <SvgCurveGraph
68+
// data={last30Days}
69+
// lastDate={nuqs.lastDate}
70+
// height={200}
71+
// summaryValue={last30Days.reduce((sum, x) => sum + x)}
72+
// className="text-red-500"
73+
// />
74+
// <p className="absolute left-2 top-2 flex items-center gap-2 text-2xl font-semibold tabular-nums md:text-6xl">
75+
// <Download
76+
// aria-label="NPM package downloads"
77+
// className="inline-block md:h-12 md:w-12"
78+
// />{' '}
79+
// {formatter.format(totalDownloads)}
80+
// </p>
81+
// </section>
4382
)
4483
}

packages/docs/src/app/(pages)/stats/_components/star-history-graph.tsx

-31
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
'use client'
2+
3+
import { BarChart, List, ListItem, Tab, TabGroup, TabList } from '@tremor/react'
4+
import { Star } from 'lucide-react'
5+
import { parseAsStringLiteral, useQueryState } from 'nuqs'
6+
import { formatStatNumber } from '../lib/format'
7+
import { GitHubStarHistory } from '../lib/github'
8+
import { Widget } from './widget'
9+
10+
type StarsGraphProps = {
11+
data: GitHubStarHistory
12+
}
13+
14+
// fill-amber-500
15+
// text-amber-500
16+
17+
const starTabs = ['earned', 'gazers'] as const
18+
19+
export function StarsGraph({ data }: StarsGraphProps) {
20+
const [activeTab, setActiveTab] = useQueryState(
21+
'stars',
22+
parseAsStringLiteral(starTabs).withDefault('earned')
23+
)
24+
const stargarzers = data.bins.flatMap(b => b.stargarzers)
25+
return (
26+
<Widget
27+
title={
28+
<>
29+
<Star size={20} /> {data.count}
30+
<TabGroup
31+
className="ml-auto w-auto"
32+
index={starTabs.indexOf(activeTab)}
33+
onIndexChange={index => setActiveTab(starTabs[index])}
34+
>
35+
<TabList variant="solid">
36+
<Tab className="dark:ui-selected:text-amber-500 ui-selected:text-amber-700">
37+
Stars earned
38+
</Tab>
39+
<Tab className="dark:ui-selected:text-amber-500 ui-selected:text-amber-700">
40+
Stargazers
41+
</Tab>
42+
</TabList>
43+
</TabGroup>
44+
</>
45+
}
46+
>
47+
{activeTab === 'earned' && (
48+
<BarChart
49+
data={data.bins
50+
.toReversed()
51+
.map(b => ({ ...b, 'Stars earned': b.diff }))}
52+
index="date"
53+
categories={['Stars earned']}
54+
colors={['amber-500']}
55+
showLegend={false}
56+
tickGap={40}
57+
yAxisWidth={20}
58+
valueFormatter={v => formatStatNumber(v)}
59+
/>
60+
)}
61+
{activeTab === 'gazers' && (
62+
<List className="mt-4 max-h-72 overflow-y-auto overscroll-contain">
63+
{stargarzers.map(s => (
64+
<ListItem key={s.login + s.avatarUrl} className="flex-wrap gap-2">
65+
<a
66+
href={`https://github.com/${s.login}`}
67+
className="group flex items-center gap-2"
68+
>
69+
<img
70+
src={s.avatarUrl}
71+
alt={s.name ?? 'Unknown'}
72+
className="h-5 w-5 rounded-full"
73+
/>
74+
<span className="font-semibold text-foreground empty:hidden">
75+
{s.name}
76+
</span>
77+
<span className="text-sm text-zinc-500 group-hover:underline">
78+
{s.login}
79+
</span>
80+
</a>
81+
<span className="ml-auto flex items-center gap-2 text-zinc-500">
82+
<span className="font-semibold empty:hidden">{s.company}</span>
83+
<span>{formatStatNumber(s.followers)} followers</span>
84+
</span>
85+
</ListItem>
86+
))}
87+
</List>
88+
)}
89+
</Widget>
90+
)
91+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { Suspense } from 'react'
2+
import { getStarHistory } from '../lib/github'
3+
import { StarsGraph } from './stars.client'
4+
5+
export async function StarHistoryGraph() {
6+
const stars = await getStarHistory()
7+
return (
8+
<Suspense>
9+
<StarsGraph data={stars} />
10+
</Suspense>
11+
)
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
'use client'
2+
3+
import { Card, LineChart } from '@tremor/react'
4+
import { formatStatNumber } from '../lib/format'
5+
6+
type TremorTestProps = {
7+
data: any
8+
}
9+
10+
export function TremorTest({ data }: TremorTestProps) {
11+
return (
12+
<Card className="dark:bg-background">
13+
<LineChart
14+
className="h-80"
15+
data={data}
16+
categories={['nuqs']}
17+
index="date"
18+
tickGap={40}
19+
colors={['red-500']}
20+
valueFormatter={value => formatStatNumber(value)}
21+
curveType="natural"
22+
yAxisWidth={30}
23+
onValueChange={v => console.log(v)}
24+
/>
25+
</Card>
26+
)
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
'use client'
2+
export * from '@tremor/react'

0 commit comments

Comments
 (0)