Skip to content

Commit a479c96

Browse files
committed
Merge branch 'main' of https://github.com/alex-key/npmx.dev into feat/kbd-shortcuts-switcher
2 parents 77bcb72 + b6eb04f commit a479c96

22 files changed

Lines changed: 802 additions & 60 deletions

.github/workflows/chromatic.yml

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,14 @@ jobs:
1717
chromatic:
1818
name: 📚 Chromatic
1919
runs-on: ubuntu-24.04-arm
20-
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }}
2120

2221
steps:
2322
- name: ☑️ Checkout
2423
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
2524
with:
2625
fetch-depth: 0
27-
ref: ${{ github.event.pull_request.head.ref || github.ref }}
26+
repository: ${{ github.event.pull_request.head.repo.full_name || github.repository }}
27+
ref: ${{ github.event.pull_request.head.sha || github.sha }}
2828

2929
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
3030
with:
@@ -38,10 +38,8 @@ jobs:
3838
- name: 📦 Install dependencies
3939
run: pnpm install
4040

41-
- name: 🧪 Run Chromatic Visual Tests
41+
- name: 🧪 Run Chromatic Visual and Accessibility Tests
4242
uses: chromaui/action@a8ce9c58f59be5cc7090cadfc8f130fb08fcf0c3 # v15.1.0
43-
with:
44-
projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
4543
env:
4644
CHROMATIC_BRANCH: ${{ github.event.pull_request.head.ref || github.ref_name }}
4745
CHROMATIC_SHA: ${{ github.event.pull_request.head.sha || github.sha }}

app/assets/logos/sponsors/index.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,24 @@ import LogoVercel from './vercel.svg'
22
import LogoVercelLight from './vercel-light.svg'
33
import LogoVoidZero from './void-zero.svg'
44
import LogoVoidZeroLight from './void-zero-light.svg'
5+
import LogoVlt from './vlt.svg'
6+
import LogoVltLight from './vlt-light.svg'
57

68
// The list is used on the about page. To add, simply upload the logos nearby and add an entry here. Prefer SVGs.
79
// For logo src, specify a string or object with the light and dark theme variants.
810
// Prefer original assets from partner sites to keep their brand identity.
911
//
1012
// If there are no original assets and the logo is not universal, you can add only the dark theme variant
1113
// and specify 'auto' for the light one - this will grayscale the logo and invert it in light mode.
14+
// The normalisingIndent is the Y-axis space to visually stabilize favicon-only logos with logotypes that contain long name.
1215
export const SPONSORS = [
1316
{
1417
name: 'Vercel',
1518
logo: {
1619
dark: LogoVercel,
1720
light: LogoVercelLight,
1821
},
22+
normalisingIndent: '0.875rem',
1923
url: 'https://vercel.com/',
2024
},
2125
{
@@ -24,6 +28,16 @@ export const SPONSORS = [
2428
dark: LogoVoidZero,
2529
light: LogoVoidZeroLight,
2630
},
31+
normalisingIndent: '0.875rem',
2732
url: 'https://voidzero.dev/',
2833
},
34+
{
35+
name: 'vlt',
36+
logo: {
37+
dark: LogoVlt,
38+
light: LogoVltLight,
39+
},
40+
normalisingIndent: '0.25rem',
41+
url: 'https://vlt.sh/',
42+
},
2943
]
Lines changed: 1 addition & 0 deletions
Loading

app/assets/logos/sponsors/vlt.svg

Lines changed: 1 addition & 0 deletions
Loading

app/components/About/LogoImg.vue

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,30 +11,30 @@ const props = defineProps<{
1111
</script>
1212
<template>
1313
<div v-if="typeof src === 'string'">
14-
<img :src="src" loading="lazy" height="36" class="w-auto block h-9" :alt="alt" />
14+
<img :src="src" loading="lazy" height="36" class="w-auto block h-full" :alt="alt" />
1515
</div>
16-
<div v-else-if="src.light === 'auto'">
16+
<div v-else-if="src.light === 'auto'" class="h-full">
1717
<img
1818
:src="src.dark"
1919
loading="lazy"
2020
height="36"
21-
class="w-auto block light:invert light:grayscale h-9"
21+
class="w-auto block light:invert light:grayscale h-full"
2222
:alt="alt"
2323
/>
2424
</div>
25-
<div v-else>
25+
<div v-else class="h-full">
2626
<img
2727
:src="src.dark"
2828
loading="lazy"
2929
height="36"
30-
class="w-auto block light:hidden h-9"
30+
class="w-auto block light:hidden h-full"
3131
:alt="alt"
3232
/>
3333
<img
3434
:src="src.light"
3535
loading="lazy"
3636
height="36"
37-
class="w-auto block hidden light:block h-9"
37+
class="w-auto block hidden light:block h-full"
3838
:alt="alt"
3939
/>
4040
</div>

app/components/About/LogoList.vue

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
type BaseItem = {
33
name: string
44
url: string
5+
normalisingIndent?: string
56
logo:
67
| string
78
| {
@@ -21,14 +22,15 @@ const props = defineProps<{
2122
</script>
2223

2324
<template>
24-
<ul class="flex items-center flex-wrap gap-4 md:gap-x-6 md:gap-y-4 list-none">
25+
<ul class="flex flex-wrap gap-4 md:gap-x-6 md:gap-y-4 list-none">
2526
<li v-for="item in list" :key="item.name">
2627
<a
2728
v-if="'logo' in item"
2829
:href="item.url"
2930
target="_blank"
3031
rel="noopener noreferrer"
31-
class="flex items-center justify-center h-full min-w-11 rounded-md hover:bg-fg/10 transition-colors p-1"
32+
class="relative flex items-center justify-center h-14 min-w-14 rounded-md hover:bg-fg/10 transition-colors p-1"
33+
:style="{ paddingBlock: item.normalisingIndent }"
3234
:aria-label="item.name"
3335
>
3436
<AboutLogoImg :src="item.logo" :alt="item.name" />
@@ -53,7 +55,8 @@ const props = defineProps<{
5355
:href="groupItem.url"
5456
target="_blank"
5557
rel="noopener noreferrer"
56-
class="flex items-center justify-center h-full min-w-10 rounded-md hover:bg-fg/10 transition-colors p-0.5"
58+
class="relative flex items-center justify-center h-10 min-w-10 rounded-md hover:bg-fg/10 transition-colors p-0.5"
59+
:style="{ paddingBlock: groupItem.normalisingIndent }"
5760
:aria-label="groupItem.name"
5861
>
5962
<AboutLogoImg :src="groupItem.logo" :alt="groupItem.name" />

app/components/Package/TrendsChart.vue

Lines changed: 165 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ import type {
1818
YearlyDataPoint,
1919
} from '~/types/chart'
2020
import { DATE_INPUT_MAX } from '~/utils/input'
21+
import { applyDataCorrection } from '~/utils/chart-data-correction'
22+
import { applyBlocklistCorrection, getAnomaliesForPackages } from '~/utils/download-anomalies'
2123
import { copyAltTextForTrendLineChart } from '~/utils/charts'
2224
2325
const props = withDefaults(
@@ -51,6 +53,7 @@ const props = withDefaults(
5153
5254
const { locale } = useI18n()
5355
const { accentColors, selectedAccentColor } = useAccentColor()
56+
const { settings } = useSettings()
5457
const { copy, copied } = useClipboard()
5558
5659
const colorMode = useColorMode()
@@ -929,15 +932,35 @@ watch(
929932
930933
const effectiveDataSingle = computed<EvolutionData>(() => {
931934
const state = activeMetricState.value
935+
let data: EvolutionData
932936
if (
933937
selectedMetric.value === DEFAULT_METRIC_ID &&
934938
displayedGranularity.value === DEFAULT_GRANULARITY &&
935939
props.weeklyDownloads?.length
936940
) {
937-
if (isWeeklyDataset(state.evolution) && state.evolution.length) return state.evolution
938-
return props.weeklyDownloads
941+
data =
942+
isWeeklyDataset(state.evolution) && state.evolution.length
943+
? state.evolution
944+
: props.weeklyDownloads
945+
} else {
946+
data = state.evolution
939947
}
940-
return state.evolution
948+
949+
if (isDownloadsMetric.value && data.length) {
950+
const pkg = effectivePackageNames.value[0] ?? props.packageName ?? ''
951+
if (settings.value.chartFilter.anomaliesFixed) {
952+
data = applyBlocklistCorrection({
953+
data,
954+
packageName: pkg,
955+
granularity: displayedGranularity.value,
956+
})
957+
}
958+
return applyDataCorrection(
959+
data as Array<{ value: number }>,
960+
settings.value.chartFilter,
961+
) as EvolutionData
962+
}
963+
return data
941964
})
942965
943966
/**
@@ -971,7 +994,16 @@ const chartData = computed<{
971994
const pointsByPackage = new Map<string, Array<{ timestamp: number; value: number }>>()
972995
973996
for (const pkg of names) {
974-
const data = state.evolutionsByPackage[pkg] ?? []
997+
let data = state.evolutionsByPackage[pkg] ?? []
998+
if (isDownloadsMetric.value && data.length) {
999+
if (settings.value.chartFilter.anomaliesFixed) {
1000+
data = applyBlocklistCorrection({ data, packageName: pkg, granularity })
1001+
}
1002+
data = applyDataCorrection(
1003+
data as Array<{ value: number }>,
1004+
settings.value.chartFilter,
1005+
) as EvolutionData
1006+
}
9751007
const points = extractSeriesPoints(granularity, data)
9761008
pointsByPackage.set(pkg, points)
9771009
for (const p of points) timestampSet.add(p.timestamp)
@@ -1428,6 +1460,7 @@ const chartConfig = computed<VueUiXyConfig>(() => {
14281460
img: $t('package.trends.download_file', { fileType: 'PNG' }),
14291461
svg: $t('package.trends.download_file', { fileType: 'SVG' }),
14301462
annotator: $t('package.trends.toggle_annotator'),
1463+
stack: $t('package.trends.toggle_stack_mode'),
14311464
altCopy: $t('package.trends.copy_alt.button_label'), // Do not make this text dependant on the `copied` variable, since this would re-render the component, which is undesirable if the minimap was used to select a time frame.
14321465
},
14331466
callbacks: {
@@ -1598,6 +1631,23 @@ const chartConfig = computed<VueUiXyConfig>(() => {
15981631
}
15991632
})
16001633
1634+
const isDownloadsMetric = computed(() => selectedMetric.value === 'downloads')
1635+
const showCorrectionControls = shallowRef(false)
1636+
1637+
const packageAnomalies = computed(() => getAnomaliesForPackages(effectivePackageNames.value))
1638+
const hasAnomalies = computed(() => packageAnomalies.value.length > 0)
1639+
1640+
function formatAnomalyDate(dateStr: string) {
1641+
const [y, m, d] = dateStr.split('-').map(Number)
1642+
if (!y || !m || !d) return dateStr
1643+
return new Intl.DateTimeFormat(locale.value, {
1644+
year: 'numeric',
1645+
month: 'short',
1646+
day: 'numeric',
1647+
timeZone: 'UTC',
1648+
}).format(new Date(Date.UTC(y, m - 1, d)))
1649+
}
1650+
16011651
// Trigger data loading when the metric is switched
16021652
watch(selectedMetric, value => {
16031653
if (!isMounted.value) return
@@ -1686,6 +1736,117 @@ watch(selectedMetric, value => {
16861736
</button>
16871737
</div>
16881738

1739+
<!-- Download filter controls -->
1740+
<div v-if="isDownloadsMetric" class="flex flex-col gap-2">
1741+
<button
1742+
type="button"
1743+
class="self-start flex items-center gap-1 text-2xs font-mono text-fg-subtle hover:text-fg transition-colors"
1744+
@click="showCorrectionControls = !showCorrectionControls"
1745+
>
1746+
<span
1747+
class="w-3.5 h-3.5 transition-transform"
1748+
:class="showCorrectionControls ? 'i-lucide:chevron-down' : 'i-lucide:chevron-right'"
1749+
aria-hidden="true"
1750+
/>
1751+
{{ $t('package.trends.data_correction') }}
1752+
</button>
1753+
<div v-if="showCorrectionControls" class="flex items-end gap-3">
1754+
<label class="flex flex-col gap-1 flex-1">
1755+
<span class="text-2xs font-mono text-fg-subtle tracking-wide uppercase">
1756+
{{ $t('package.trends.average_window') }}
1757+
<span class="text-fg-muted">({{ settings.chartFilter.averageWindow }})</span>
1758+
</span>
1759+
<input
1760+
v-model.number="settings.chartFilter.averageWindow"
1761+
type="range"
1762+
min="0"
1763+
max="20"
1764+
step="1"
1765+
class="accent-[var(--accent-color,var(--fg-subtle))]"
1766+
/>
1767+
</label>
1768+
<label class="flex flex-col gap-1 flex-1">
1769+
<span class="text-2xs font-mono text-fg-subtle tracking-wide uppercase">
1770+
{{ $t('package.trends.smoothing') }}
1771+
<span class="text-fg-muted">({{ settings.chartFilter.smoothingTau }})</span>
1772+
</span>
1773+
<input
1774+
v-model.number="settings.chartFilter.smoothingTau"
1775+
type="range"
1776+
min="0"
1777+
max="20"
1778+
step="1"
1779+
class="accent-[var(--accent-color,var(--fg-subtle))]"
1780+
/>
1781+
</label>
1782+
<div class="flex flex-col gap-1 shrink-0">
1783+
<span
1784+
class="text-2xs font-mono text-fg-subtle tracking-wide uppercase flex items-center justify-between"
1785+
>
1786+
{{ $t('package.trends.known_anomalies') }}
1787+
<TooltipApp interactive :to="inModal ? '#chart-modal' : undefined">
1788+
<button
1789+
type="button"
1790+
class="i-lucide:info w-3.5 h-3.5 text-fg-muted cursor-help"
1791+
:aria-label="$t('package.trends.known_anomalies')"
1792+
/>
1793+
<template #content>
1794+
<div class="flex flex-col gap-3">
1795+
<p class="text-xs text-fg-muted">
1796+
{{ $t('package.trends.known_anomalies_description') }}
1797+
</p>
1798+
<div v-if="hasAnomalies">
1799+
<p class="text-xs text-fg-subtle font-medium">
1800+
{{ $t('package.trends.known_anomalies_ranges') }}
1801+
</p>
1802+
<ul class="text-xs text-fg-subtle list-disc list-inside">
1803+
<li v-for="a in packageAnomalies" :key="`${a.packageName}-${a.start}`">
1804+
{{
1805+
isMultiPackageMode
1806+
? $t('package.trends.known_anomalies_range_named', {
1807+
packageName: a.packageName,
1808+
start: formatAnomalyDate(a.start),
1809+
end: formatAnomalyDate(a.end),
1810+
})
1811+
: $t('package.trends.known_anomalies_range', {
1812+
start: formatAnomalyDate(a.start),
1813+
end: formatAnomalyDate(a.end),
1814+
})
1815+
}}
1816+
</li>
1817+
</ul>
1818+
</div>
1819+
<p v-else class="text-xs text-fg-muted">
1820+
{{ $t('package.trends.known_anomalies_none', effectivePackageNames.length) }}
1821+
</p>
1822+
<div class="flex justify-end">
1823+
<LinkBase
1824+
to="https://github.com/npmx-dev/npmx.dev/edit/main/app/utils/download-anomalies.data.ts"
1825+
class="text-xs text-accent"
1826+
>
1827+
{{ $t('package.trends.known_anomalies_contribute') }}
1828+
</LinkBase>
1829+
</div>
1830+
</div>
1831+
</template>
1832+
</TooltipApp>
1833+
</span>
1834+
<label
1835+
class="flex items-center gap-1.5 text-2xs font-mono text-fg-subtle cursor-pointer"
1836+
:class="{ 'opacity-50 pointer-events-none': !hasAnomalies }"
1837+
>
1838+
<input
1839+
v-model="settings.chartFilter.anomaliesFixed"
1840+
type="checkbox"
1841+
:disabled="!hasAnomalies"
1842+
class="accent-[var(--accent-color,var(--fg-subtle))]"
1843+
/>
1844+
{{ $t('package.trends.apply_correction') }}
1845+
</label>
1846+
</div>
1847+
</div>
1848+
</div>
1849+
16891850
<p v-if="skippedPackagesWithoutGitHub.length > 0" class="text-2xs font-mono text-fg-subtle">
16901851
{{ $t('package.trends.contributors_skip', { count: skippedPackagesWithoutGitHub.length }) }}
16911852
{{ skippedPackagesWithoutGitHub.join(', ') }}

0 commit comments

Comments
 (0)