Skip to content

Commit d414e3e

Browse files
committed
fix: keep homepage free of chart and replay code
1 parent 53d0b17 commit d414e3e

17 files changed

Lines changed: 403 additions & 326 deletions
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
export function SkillSparklineFallback({ height = 40 }: { height?: number }) {
2+
return (
3+
<div
4+
className="animate-pulse rounded bg-gray-100 dark:bg-gray-800/40"
5+
style={{ width: '100%', height }}
6+
/>
7+
)
8+
}

src/components/npm-stats/ChartControls.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import {
1717
timeRanges,
1818
binningOptions,
1919
isBinningOptionValidForRange,
20-
} from './NPMStatsChart'
20+
} from './shared'
2121

2222
const dropdownButtonStyles = {
2323
base: 'bg-gray-500/10 rounded-md px-2 py-1 text-sm flex items-center gap-1',

src/components/npm-stats/NPMStatsChart.tsx

Lines changed: 11 additions & 159 deletions
Original file line numberDiff line numberDiff line change
@@ -1,166 +1,18 @@
11
import * as React from 'react'
2-
import * as v from 'valibot'
32
import * as Plot from '@observablehq/plot'
43
import * as d3 from 'd3'
54
import { ParentSize } from '~/components/ParentSize'
6-
import { packageGroupSchema } from '~/routes/stats/npm/-comparisons'
7-
import { defaultColors } from '~/utils/npm-packages'
8-
9-
// Types
10-
export type PackageGroup = v.InferOutput<typeof packageGroupSchema>
11-
12-
export const binTypeSchema = v.picklist([
13-
'yearly',
14-
'monthly',
15-
'weekly',
16-
'daily',
17-
])
18-
export type BinType = v.InferOutput<typeof binTypeSchema>
19-
20-
export const transformModeSchema = v.picklist(['none', 'normalize-y'])
21-
export type TransformMode = v.InferOutput<typeof transformModeSchema>
22-
23-
export const showDataModeSchema = v.picklist(['all', 'complete'])
24-
export type ShowDataMode = v.InferOutput<typeof showDataModeSchema>
25-
26-
export type TimeRange =
27-
| '7-days'
28-
| '30-days'
29-
| '90-days'
30-
| '180-days'
31-
| '365-days'
32-
| '730-days'
33-
| '1825-days'
34-
| 'all-time'
35-
36-
export type FacetValue = 'name'
37-
38-
// Type for query data returned from npm stats API
39-
export type NpmQueryData = Array<{
40-
packages: Array<{
41-
name?: string
42-
hidden?: boolean
43-
downloads: Array<{ day: string; downloads: number }>
44-
}>
45-
start: string
46-
end: string
47-
error?: string
48-
actualStartDate?: Date
49-
}>
50-
51-
// Binning options configuration
52-
export const binningOptions = [
53-
{
54-
label: 'Yearly',
55-
value: 'yearly',
56-
single: 'year',
57-
bin: d3.utcYear,
58-
},
59-
{
60-
label: 'Monthly',
61-
value: 'monthly',
62-
single: 'month',
63-
bin: d3.utcMonth,
64-
},
65-
{
66-
label: 'Weekly',
67-
value: 'weekly',
68-
single: 'week',
69-
bin: d3.utcWeek,
70-
},
71-
{
72-
label: 'Daily',
73-
value: 'daily',
74-
single: 'day',
75-
bin: d3.utcDay,
76-
},
77-
] as const
78-
79-
export const binningOptionsByType = binningOptions.reduce(
80-
(acc, option) => {
81-
acc[option.value] = option
82-
return acc
83-
},
84-
{} as Record<BinType, (typeof binningOptions)[number]>,
85-
)
86-
87-
export const timeRanges = [
88-
{ value: '7-days', label: '7 Days' },
89-
{ value: '30-days', label: '30 Days' },
90-
{ value: '90-days', label: '90 Days' },
91-
{ value: '180-days', label: '6 Months' },
92-
{ value: '365-days', label: '1 Year' },
93-
{ value: '730-days', label: '2 Years' },
94-
{ value: '1825-days', label: '5 Years' },
95-
{ value: 'all-time', label: 'All Time' },
96-
] as const
97-
98-
export const defaultRangeBinTypes: Record<TimeRange, BinType> = {
99-
'7-days': 'daily',
100-
'30-days': 'daily',
101-
'90-days': 'weekly',
102-
'180-days': 'weekly',
103-
'365-days': 'weekly',
104-
'730-days': 'monthly',
105-
'1825-days': 'monthly',
106-
'all-time': 'monthly',
107-
}
108-
109-
// Get or assign colors for packages
110-
export function getPackageColor(
111-
packageName: string,
112-
packages: PackageGroup[],
113-
): string {
114-
// Find the package group that contains this package
115-
const packageInfo = packages.find((pkg) =>
116-
pkg.packages.some((p) => p.name === packageName),
117-
)
118-
if (packageInfo?.color) {
119-
return packageInfo.color
120-
}
121-
122-
// Otherwise, assign a default color based on the package's position
123-
const packageIndex = packages.findIndex((pkg) =>
124-
pkg.packages.some((p) => p.name === packageName),
125-
)
126-
return defaultColors[packageIndex % defaultColors.length]
127-
}
128-
129-
// Custom number formatter for more precise control
130-
export const formatNumber = (num: number) => {
131-
if (num >= 1_000_000) {
132-
return `${(num / 1_000_000).toFixed(1)}M`
133-
}
134-
if (num >= 1_000) {
135-
return `${(num / 1_000).toFixed(1)}k`
136-
}
137-
return num.toString()
138-
}
139-
140-
// Check if a binning option is valid for a time range
141-
export function isBinningOptionValidForRange(
142-
range: TimeRange,
143-
binType: BinType,
144-
): boolean {
145-
switch (range) {
146-
case '7-days':
147-
case '30-days':
148-
return binType === 'daily'
149-
case '90-days':
150-
case '180-days':
151-
return (
152-
binType === 'daily' || binType === 'weekly' || binType === 'monthly'
153-
)
154-
case '365-days':
155-
return (
156-
binType === 'daily' || binType === 'weekly' || binType === 'monthly'
157-
)
158-
case '730-days':
159-
case '1825-days':
160-
case 'all-time':
161-
return true
162-
}
163-
}
5+
import { binningOptionsByType } from './binning'
6+
import type {
7+
BinType,
8+
FacetValue,
9+
NpmQueryData,
10+
PackageGroup,
11+
ShowDataMode,
12+
TimeRange,
13+
TransformMode,
14+
} from './shared'
15+
import { getPackageColor } from './shared'
16416

16517
// Plot figure component
16618
function PlotFigure({ options }: { options: Parameters<typeof Plot.plot>[0] }) {

src/components/npm-stats/PackagePills.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
DropdownMenuTrigger,
99
} from '@radix-ui/react-dropdown-menu'
1010
import { Tooltip } from '~/components/Tooltip'
11-
import { type PackageGroup, getPackageColor } from './NPMStatsChart'
11+
import { type PackageGroup, getPackageColor } from './shared'
1212

1313
export type PackagePillProps = {
1414
packageGroup: PackageGroup

src/components/npm-stats/PopularComparisons.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import * as React from 'react'
22
import { Link } from '@tanstack/react-router'
3-
import type { PackageGroup } from './NPMStatsChart'
3+
import type { PackageGroup } from './shared'
44
import { defaultColors } from '~/utils/npm-packages'
55

66
export type ComparisonGroup = {

src/components/npm-stats/StatsTable.tsx

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,20 @@ import * as React from 'react'
22
import * as d3 from 'd3'
33
import { X, EyeOff } from 'lucide-react'
44
import { Tooltip } from '~/components/Tooltip'
5+
import { binningOptionsByType } from './binning'
56
import {
67
type PackageGroup,
78
type TransformMode,
89
type NpmQueryData,
910
type BinType,
10-
binningOptionsByType,
1111
getPackageColor,
1212
formatNumber,
13-
} from './NPMStatsChart'
13+
} from './shared'
1414

1515
export interface StatsTableProps {
1616
queryData: NpmQueryData | undefined
1717
packageGroups: PackageGroup[]
18-
binOption: (typeof binningOptionsByType)[BinType]
18+
binType: BinType
1919
transform: TransformMode
2020
onColorClick: (packageName: string, event: React.MouseEvent) => void
2121
onToggleVisibility: (index: number, packageName: string) => void
@@ -39,10 +39,12 @@ interface PackageStat {
3939
function calculateStats(
4040
queryData: NpmQueryData | undefined,
4141
packageGroups: PackageGroup[],
42-
binOption: StatsTableProps['binOption'],
42+
binType: BinType,
4343
): PackageStat[] {
4444
if (!queryData) return []
4545

46+
const binOption = binningOptionsByType[binType]
47+
4648
return queryData
4749
.map((packageGroupDownloads, index) => {
4850
if (!packageGroupDownloads.packages.some((p) => p.downloads.length)) {
@@ -108,13 +110,14 @@ function calculateStats(
108110
export function StatsTable({
109111
queryData,
110112
packageGroups,
111-
binOption,
113+
binType,
112114
transform,
113115
onColorClick,
114116
onToggleVisibility,
115117
onRemove,
116118
}: StatsTableProps) {
117-
const stats = calculateStats(queryData, packageGroups, binOption)
119+
const binOption = binningOptionsByType[binType]
120+
const stats = calculateStats(queryData, packageGroups, binType)
118121
const sortedStats = [...stats].sort((a, b) =>
119122
transform === 'normalize-y'
120123
? b.growth - a.growth
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import * as d3 from 'd3'
2+
3+
import type { BinType } from './shared'
4+
5+
export const binningOptionsByType = {
6+
yearly: {
7+
label: 'Yearly',
8+
value: 'yearly',
9+
single: 'year',
10+
bin: d3.utcYear,
11+
},
12+
monthly: {
13+
label: 'Monthly',
14+
value: 'monthly',
15+
single: 'month',
16+
bin: d3.utcMonth,
17+
},
18+
weekly: {
19+
label: 'Weekly',
20+
value: 'weekly',
21+
single: 'week',
22+
bin: d3.utcWeek,
23+
},
24+
daily: {
25+
label: 'Daily',
26+
value: 'daily',
27+
single: 'day',
28+
bin: d3.utcDay,
29+
},
30+
} as const satisfies Record<
31+
BinType,
32+
{
33+
label: string
34+
value: BinType
35+
single: string
36+
bin: (date: Date) => Date
37+
}
38+
>

src/components/npm-stats/index.ts

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,28 @@
1+
export { NPMStatsChart, type NPMStatsChartProps } from './NPMStatsChart'
2+
3+
export type {
4+
NpmQueryData,
5+
PackageGroup,
6+
BinType,
7+
TransformMode,
8+
ShowDataMode,
9+
TimeRange,
10+
FacetValue,
11+
} from './shared'
12+
113
export {
2-
NPMStatsChart,
3-
type NPMStatsChartProps,
4-
type NpmQueryData,
5-
type PackageGroup,
6-
type BinType,
7-
type TransformMode,
8-
type ShowDataMode,
9-
type TimeRange,
10-
type FacetValue,
1114
binTypeSchema,
1215
transformModeSchema,
1316
showDataModeSchema,
1417
binningOptions,
15-
binningOptionsByType,
1618
timeRanges,
1719
defaultRangeBinTypes,
1820
getPackageColor,
1921
formatNumber,
2022
isBinningOptionValidForRange,
21-
} from './NPMStatsChart'
23+
} from './shared'
24+
25+
export { binningOptionsByType } from './binning'
2226

2327
export { PopularComparisons, type ComparisonGroup } from './PopularComparisons'
2428

src/components/npm-stats/npmQueryOptions.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import * as d3 from 'd3'
22
import { keepPreviousData, queryOptions } from '@tanstack/react-query'
3-
import type { PackageGroup, TimeRange, NpmQueryData } from './NPMStatsChart'
3+
import type { NpmQueryData, PackageGroup, TimeRange } from './shared'
44
import { fetchNpmDownloadsBulk } from '~/utils/stats-queries.functions'
55

66
/**

0 commit comments

Comments
 (0)