Skip to content

Commit 4a469d2

Browse files
Cleaned up Dev Time Stats (#151)
1 parent f8d324e commit 4a469d2

14 files changed

Lines changed: 427 additions & 439 deletions
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
---
2+
import { buildInstallData, depsStats } from '../lib/collections'
3+
import { getFrameworkSlug } from '../lib/utils'
4+
import StatsTable from './StatsTable.astro'
5+
6+
const buildInstallColumns = [
7+
{
8+
key: 'name',
9+
header: 'Framework',
10+
href: (row: Record<string, unknown>) =>
11+
`/framework/${getFrameworkSlug(row.package as string)}`,
12+
},
13+
{ key: 'avgInstall', header: 'Avg Install' },
14+
{ key: 'minInstall', header: 'Min Install' },
15+
{ key: 'maxInstall', header: 'Max Install' },
16+
{ key: 'avgColdBuild', header: 'Avg Cold Build' },
17+
{ key: 'avgWarmBuild', header: 'Avg Warm Build' },
18+
{ key: 'buildOutput', header: 'Build Output' },
19+
]
20+
---
21+
22+
<StatsTable
23+
label="Build and install times by framework"
24+
columns={buildInstallColumns}
25+
data={buildInstallData}
26+
/>
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
---
2+
import { starterStats } from '../lib/collections'
3+
import { BYTES_PER_MB } from '../lib/utils'
4+
import ComparisonBarChart from './ComparisonBarChart.astro'
5+
6+
const validEntries = starterStats.filter(
7+
(f) => f?.name != null && Number.isFinite(f.buildOutputSize),
8+
)
9+
10+
const data = validEntries.map((f) => ({
11+
name: f.name,
12+
value: f.buildOutputSize / BYTES_PER_MB,
13+
}))
14+
---
15+
16+
<ComparisonBarChart title="Build size" data={data} valueFormat="mb" />
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
---
2+
import BuildSizeChart from './BuildSizeChart.astro'
3+
import BuildSizeProdChart from './BuildSizeProdChart.astro'
4+
import ChartTabs from './ChartTabs.astro'
5+
---
6+
7+
<ChartTabs
8+
sectionId="build"
9+
label="Build size graphs"
10+
tab1Label="Build size"
11+
tab2Label="Build size prod"
12+
>
13+
<BuildSizeChart slot="panel-1" />
14+
<BuildSizeProdChart slot="panel-2" />
15+
</ChartTabs>
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
---
2+
import { starterStats } from '../lib/collections'
3+
import { BYTES_PER_MB } from '../lib/utils'
4+
import ComparisonBarChart from './ComparisonBarChart.astro'
5+
6+
const validEntries = starterStats.filter(
7+
(f) => f?.name != null && Number.isFinite(f.nodeModulesSizeProdOnly),
8+
)
9+
10+
const data = validEntries.map((f) => ({
11+
name: f.name,
12+
value: f.nodeModulesSizeProdOnly / BYTES_PER_MB,
13+
}))
14+
---
15+
16+
<ComparisonBarChart title="Build size prod" data={data} valueFormat="mb" />
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
---
2+
interface Props {
3+
sectionId: string
4+
label: string
5+
tab1Label: string
6+
tab2Label: string
7+
}
8+
9+
const { sectionId, label, tab1Label, tab2Label } = Astro.props
10+
11+
const tab1Id = `${sectionId}-tab-1`
12+
const tab2Id = `${sectionId}-tab-2`
13+
const panel1Id = `${sectionId}-panel-1`
14+
const panel2Id = `${sectionId}-panel-2`
15+
---
16+
17+
<div class="chart-tabs-container" data-tab-section={sectionId}>
18+
<div class="chart-tablist" role="tablist" aria-label={label}>
19+
<button
20+
type="button"
21+
role="tab"
22+
id={tab1Id}
23+
aria-selected="true"
24+
aria-controls={panel1Id}
25+
class="tab active"
26+
>
27+
{tab1Label}
28+
</button>
29+
<button
30+
type="button"
31+
role="tab"
32+
id={tab2Id}
33+
aria-selected="false"
34+
aria-controls={panel2Id}
35+
class="tab"
36+
>
37+
{tab2Label}
38+
</button>
39+
</div>
40+
<div class="chart-tabpanels">
41+
<div
42+
role="tabpanel"
43+
id={panel1Id}
44+
aria-labelledby={tab1Id}
45+
class="chart-tabpanel active"
46+
>
47+
<slot name="panel-1" />
48+
</div>
49+
<div
50+
role="tabpanel"
51+
id={panel2Id}
52+
aria-labelledby={tab2Id}
53+
class="chart-tabpanel"
54+
hidden
55+
>
56+
<slot name="panel-2" />
57+
</div>
58+
</div>
59+
</div>
60+
61+
<style>
62+
.chart-tabs-container {
63+
margin-top: 1.5em;
64+
margin-bottom: 2em;
65+
padding: 1em 1.25em;
66+
border: 1px solid var(--ft-border);
67+
border-radius: 12px;
68+
background: var(--ft-bg-muted);
69+
}
70+
71+
.chart-tablist {
72+
display: flex;
73+
gap: 8px;
74+
margin: 0 0 1em 0;
75+
padding: 0;
76+
}
77+
78+
.tab {
79+
padding: 8px 16px;
80+
font-size: 14px;
81+
font-weight: 500;
82+
color: var(--ft-muted);
83+
background: transparent;
84+
border: none;
85+
border-radius: 8px;
86+
cursor: pointer;
87+
font-family: inherit;
88+
transition:
89+
color 0.2s,
90+
background-color 0.2s;
91+
}
92+
93+
.tab:hover {
94+
color: var(--ft-text);
95+
}
96+
97+
.tab:focus-visible {
98+
outline: 2px solid var(--ft-accent);
99+
outline-offset: 2px;
100+
}
101+
102+
.tab.active {
103+
background: var(--ft-accent);
104+
color: white;
105+
}
106+
107+
:global(html.dark) .tab.active {
108+
color: var(--ft-bg);
109+
}
110+
111+
.chart-tabpanels {
112+
position: relative;
113+
min-height: 320px;
114+
}
115+
116+
.chart-tabpanel[hidden] {
117+
display: none;
118+
}
119+
120+
@media screen and (max-width: 768px) {
121+
.tab {
122+
padding: 8px 14px;
123+
font-size: 13px;
124+
}
125+
}
126+
</style>
127+
128+
<script>
129+
function initTabs() {
130+
const sections = document.querySelectorAll(
131+
'.chart-tabs-container[data-tab-section]',
132+
)
133+
sections.forEach((section: Element) => {
134+
const tabs = section.querySelectorAll<HTMLButtonElement>('[role="tab"]')
135+
const panels = section.querySelectorAll<HTMLElement>('[role="tabpanel"]')
136+
137+
if (tabs.length !== 2 || panels.length !== 2) return
138+
139+
function setActive(index: number) {
140+
tabs.forEach((tab, i) => {
141+
const isSelected = i === index
142+
tab.setAttribute('aria-selected', String(isSelected))
143+
tab.classList.toggle('active', isSelected)
144+
})
145+
panels.forEach((panel, i) => {
146+
panel.hidden = i !== index
147+
})
148+
}
149+
150+
tabs.forEach((tab, index) => {
151+
tab.addEventListener('click', () => setActive(index))
152+
153+
tab.addEventListener('keydown', (e: KeyboardEvent) => {
154+
let newIndex = index
155+
if (e.key === 'ArrowLeft' || e.key === 'ArrowUp') {
156+
e.preventDefault()
157+
newIndex = index === 0 ? tabs.length - 1 : index - 1
158+
} else if (e.key === 'ArrowRight' || e.key === 'ArrowDown') {
159+
e.preventDefault()
160+
newIndex = index === tabs.length - 1 ? 0 : index + 1
161+
} else if (e.key === 'Home') {
162+
e.preventDefault()
163+
newIndex = 0
164+
} else if (e.key === 'End') {
165+
e.preventDefault()
166+
newIndex = tabs.length - 1
167+
} else return
168+
;(tabs[newIndex] as HTMLButtonElement).focus()
169+
setActive(newIndex)
170+
})
171+
})
172+
173+
setActive(0)
174+
})
175+
}
176+
177+
if (document.readyState === 'loading') {
178+
document.addEventListener('DOMContentLoaded', initTabs)
179+
} else {
180+
initTabs()
181+
}
182+
</script>
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
---
2+
import ChartTabs from './ChartTabs.astro'
3+
import DepsChart from './DepsChart.astro'
4+
import ProdDepsChart from './ProdDepsChart.astro'
5+
---
6+
7+
<ChartTabs
8+
sectionId="deps"
9+
label="Dependency graphs"
10+
tab1Label="Deps"
11+
tab2Label="Prod Deps"
12+
>
13+
<DepsChart slot="panel-1" />
14+
<ProdDepsChart slot="panel-2" />
15+
</ChartTabs>

0 commit comments

Comments
 (0)