Skip to content

Commit 2d7ce67

Browse files
committed
Merge branch 'main' into feat-i18n/adding-dutch
2 parents 1cd38b5 + d26e250 commit 2d7ce67

27 files changed

+1293
-400
lines changed

CODE_OF_CONDUCT.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ We agree to restrict the following behaviors in our community. Instances, threat
4343

4444
Tensions can occur between community members even when they are trying their best to collaborate. Not every conflict represents a code of conduct violation, and this Code of Conduct reinforces encouraged behaviors and norms that can help avoid conflicts and minimize harm.
4545

46-
When an incident does occur, it is important to report it promptly. To report a possible violation, contact the project stewards (@danielroe and @patak.dev) by DM in our community chat. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project stewards are obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
46+
When an incident does occur, it is important to report it promptly. To report a possible violation, contact the project stewards (@danielroe and @patak.cat) by DM in our community chat. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project stewards are obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
4747

4848
Community Moderators take reports of violations seriously and will make every effort to respond in a timely manner. They will investigate all reports of code of conduct violations, reviewing messages, logs, and recordings, or interviewing witnesses and other participants. Community Moderators will keep investigation and enforcement actions as transparent as possible while prioritizing safety and confidentiality. In order to honor these values, enforcement actions are carried out in private with the involved parties, but communicating to the whole community may be part of a mutually agreed upon resolution.
4949

GOVERNANCE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ Not every contributor will reach this level, and that's okay! Maintainers still
4141
The npmx project Stewards are currently:
4242

4343
- **Daniel Roe** ([website](https://roe.dev), [social](https://bsky.app/profile/danielroe.dev), [github](https://github.com/danielroe), [@danielroe](https://chat.npmx.dev))
44-
- **Matias Capeletto** ([website](https://patak.dev), [social](https://bsky.app/profile/patak.dev), [github](https://github.com/patak-dev), [@patak.dev](https://chat.npmx.dev))
44+
- **Matias Capeletto** ([website](https://patak.cat), [social](https://bsky.app/profile/patak.cat), [github](https://github.com/patak-cat), [@patak.cat](https://chat.npmx.dev))
4545

4646
---
4747

Lines changed: 313 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,313 @@
1+
<script setup lang="ts">
2+
import { ref, computed } from 'vue'
3+
import { VueUiHorizontalBar } from 'vue-data-ui/vue-ui-horizontal-bar'
4+
import type {
5+
VueUiHorizontalBarConfig,
6+
VueUiHorizontalBarDatapoint,
7+
VueUiHorizontalBarDatasetItem,
8+
} from 'vue-data-ui'
9+
import { getFrameworkColor, isListedFramework } from '~/utils/frameworks'
10+
import { drawSmallNpmxLogoAndTaglineWatermark } from '~/composables/useChartWatermark'
11+
import {
12+
loadFile,
13+
insertLineBreaks,
14+
sanitise,
15+
applyEllipsis,
16+
copyAltTextForCompareFacetBarChart,
17+
} from '~/utils/charts'
18+
19+
import('vue-data-ui/style.css')
20+
21+
const props = defineProps<{
22+
values: (FacetValue | null | undefined)[]
23+
packages: string[]
24+
label: string
25+
description: string
26+
facetLoading?: boolean
27+
}>()
28+
29+
const colorMode = useColorMode()
30+
const resolvedMode = shallowRef<'light' | 'dark'>('light')
31+
const rootEl = shallowRef<HTMLElement | null>(null)
32+
const { width } = useElementSize(rootEl)
33+
const { copy, copied } = useClipboard()
34+
35+
const mobileBreakpointWidth = 640
36+
const isMobile = computed(() => width.value > 0 && width.value < mobileBreakpointWidth)
37+
38+
const chartKey = ref(0)
39+
40+
const { colors } = useCssVariables(
41+
[
42+
'--bg',
43+
'--fg',
44+
'--bg-subtle',
45+
'--bg-elevated',
46+
'--fg-subtle',
47+
'--fg-muted',
48+
'--border',
49+
'--border-subtle',
50+
],
51+
{
52+
element: rootEl,
53+
watchHtmlAttributes: true,
54+
watchResize: false,
55+
},
56+
)
57+
58+
const watermarkColors = computed(() => ({
59+
fg: colors.value.fg ?? OKLCH_NEUTRAL_FALLBACK,
60+
bg: colors.value.bg ?? OKLCH_NEUTRAL_FALLBACK,
61+
fgSubtle: colors.value.fgSubtle ?? OKLCH_NEUTRAL_FALLBACK,
62+
}))
63+
64+
onMounted(async () => {
65+
rootEl.value = document.documentElement
66+
resolvedMode.value = colorMode.value === 'dark' ? 'dark' : 'light'
67+
})
68+
69+
watch(
70+
() => colorMode.value,
71+
value => {
72+
resolvedMode.value = value === 'dark' ? 'dark' : 'light'
73+
},
74+
{ flush: 'sync' },
75+
)
76+
77+
watch(
78+
() => props.packages,
79+
(newP, oldP) => {
80+
if (newP.length !== oldP.length) return
81+
chartKey.value += 1
82+
},
83+
)
84+
85+
const isDarkMode = computed(() => resolvedMode.value === 'dark')
86+
87+
const dataset = computed<VueUiHorizontalBarDatasetItem[]>(() => {
88+
if (props.facetLoading) return []
89+
return props.packages.map((name, index) => {
90+
const rawValue = props.values[index]?.raw
91+
return {
92+
name: insertLineBreaks(applyEllipsis(name)),
93+
value: typeof rawValue === 'number' ? rawValue : 0,
94+
color: isListedFramework(name) ? getFrameworkColor(name) : undefined,
95+
formattedValue: props.values[index]?.display,
96+
}
97+
})
98+
})
99+
100+
const skeletonDataset = computed(() =>
101+
props.packages.map((_pkg, i) => ({
102+
name: '_',
103+
value: i + 1,
104+
color: colors.value.border,
105+
})),
106+
)
107+
108+
function buildExportFilename(extension: string): string {
109+
const sanitizedPackages = props.packages.map(p => sanitise(p).slice(0, 10)).join('_')
110+
const comparisonLabel = sanitise($t('compare.packages.section_comparison'))
111+
const facetLabel = sanitise(props.label)
112+
return `${facetLabel}_${comparisonLabel}_${sanitizedPackages}.${extension}`
113+
}
114+
115+
const config = computed<VueUiHorizontalBarConfig>(() => {
116+
return {
117+
theme: isDarkMode.value ? 'dark' : '',
118+
userOptions: {
119+
buttons: {
120+
tooltip: false,
121+
pdf: false,
122+
fullscreen: false,
123+
sort: false,
124+
annotator: false,
125+
table: false,
126+
csv: false,
127+
altCopy: true,
128+
},
129+
buttonTitle: {
130+
img: $t('package.trends.download_file', { fileType: 'PNG' }),
131+
svg: $t('package.trends.download_file', { fileType: 'SVG' }),
132+
altCopy: $t('package.trends.copy_alt.button_label'),
133+
},
134+
callbacks: {
135+
img: args => {
136+
const imageUri = args?.imageUri
137+
if (!imageUri) return
138+
loadFile(imageUri, buildExportFilename('png'))
139+
},
140+
svg: args => {
141+
const blob = args?.blob
142+
if (!blob) return
143+
const url = URL.createObjectURL(blob)
144+
loadFile(url, buildExportFilename('svg'))
145+
URL.revokeObjectURL(url)
146+
},
147+
altCopy: ({ dataset: dst, config: cfg }) => {
148+
copyAltTextForCompareFacetBarChart({
149+
dataset: dst,
150+
config: {
151+
...cfg,
152+
facet: props.label,
153+
description: props.description,
154+
copy,
155+
$t,
156+
},
157+
})
158+
},
159+
},
160+
},
161+
skeletonDataset: skeletonDataset.value,
162+
skeletonConfig: {
163+
style: {
164+
chart: {
165+
backgroundColor: colors.value.bg,
166+
},
167+
},
168+
},
169+
style: {
170+
chart: {
171+
backgroundColor: colors.value.bg,
172+
height: 60 * props.packages.length,
173+
layout: {
174+
bars: {
175+
rowColor: isDarkMode.value ? colors.value.borderSubtle : colors.value.bgSubtle,
176+
rowRadius: 4,
177+
borderRadius: 4,
178+
dataLabels: {
179+
fontSize: isMobile.value ? 12 : 18,
180+
percentage: { show: false },
181+
offsetX: 12,
182+
bold: false,
183+
color: colors.value.fg,
184+
value: {
185+
formatter: ({ config }) => {
186+
return config?.datapoint?.formattedValue ?? '0'
187+
},
188+
},
189+
},
190+
nameLabels: {
191+
fontSize: isMobile.value ? 12 : 18,
192+
color: colors.value.fgSubtle,
193+
},
194+
underlayerColor: colors.value.bg,
195+
},
196+
highlighter: {
197+
opacity: isMobile.value ? 0 : 5,
198+
},
199+
},
200+
legend: {
201+
show: false,
202+
},
203+
title: {
204+
fontSize: 16,
205+
bold: false,
206+
text: props.label,
207+
color: colors.value.fg,
208+
subtitle: {
209+
text: props.description,
210+
fontSize: 12,
211+
color: colors.value.fgSubtle,
212+
},
213+
},
214+
tooltip: {
215+
show: !isMobile.value,
216+
borderColor: 'transparent',
217+
backdropFilter: false,
218+
backgroundColor: 'transparent',
219+
customFormat: ({ datapoint }) => {
220+
const name = datapoint?.name?.replace(/\n/g, '<br>')
221+
return `
222+
<div class="font-mono p-3 border border-border rounded-md bg-[var(--bg)]/10 backdrop-blur-md">
223+
<div class="flex items-center gap-2">
224+
<div class="w-3 h-3">
225+
<svg viewBox="0 0 2 2" class="w-full h-full">
226+
<rect x="0" y="0" width="2" height="2" rx="0.3" fill="${datapoint?.color}" />
227+
</svg>
228+
</div>
229+
<span>${name}: ${(datapoint as VueUiHorizontalBarDatapoint).formattedValue ?? 0}</span>
230+
</div>
231+
</div>
232+
`
233+
},
234+
},
235+
},
236+
},
237+
}
238+
})
239+
</script>
240+
241+
<template>
242+
<div class="font-mono facet-bar">
243+
<ClientOnly v-if="dataset.length">
244+
<VueUiHorizontalBar :key="chartKey" :dataset :config class="[direction:ltr]">
245+
<template #svg="{ svg }">
246+
<!-- Inject npmx logo & tagline during SVG and PNG print -->
247+
<g
248+
v-if="svg.isPrintingSvg || svg.isPrintingImg"
249+
v-html="
250+
drawSmallNpmxLogoAndTaglineWatermark({
251+
svg,
252+
colors: watermarkColors,
253+
translateFn: $t,
254+
})
255+
"
256+
/>
257+
</template>
258+
259+
<template #menuIcon="{ isOpen }">
260+
<span v-if="isOpen" class="i-lucide:x w-6 h-6" aria-hidden="true" />
261+
<span v-else class="i-lucide:ellipsis-vertical w-6 h-6" aria-hidden="true" />
262+
</template>
263+
<template #optionCsv>
264+
<span class="text-fg-subtle font-mono pointer-events-none">CSV</span>
265+
</template>
266+
<template #optionImg>
267+
<span class="text-fg-subtle font-mono pointer-events-none">PNG</span>
268+
</template>
269+
<template #optionSvg>
270+
<span class="text-fg-subtle font-mono pointer-events-none">SVG</span>
271+
</template>
272+
<template #optionAltCopy>
273+
<span
274+
class="w-6 h-6"
275+
:class="
276+
copied ? 'i-lucide:check text-accent' : 'i-lucide:person-standing text-fg-subtle'
277+
"
278+
style="pointer-events: none"
279+
aria-hidden="true"
280+
/>
281+
</template>
282+
</VueUiHorizontalBar>
283+
284+
<template #fallback>
285+
<div class="flex flex-col gap-2 justify-center items-center mb-2">
286+
<SkeletonInline class="h-4 w-16" />
287+
<SkeletonInline class="h-4 w-28" />
288+
</div>
289+
<div class="flex flex-col gap-1">
290+
<SkeletonInline class="h-7 w-full" v-for="pkg in packages" :key="pkg" />
291+
</div>
292+
</template>
293+
</ClientOnly>
294+
295+
<template v-else>
296+
<div class="flex flex-col gap-2 justify-center items-center mb-2">
297+
<SkeletonInline class="h-4 w-16" />
298+
<SkeletonInline class="h-4 w-28" />
299+
</div>
300+
<div class="flex flex-col gap-1">
301+
<SkeletonInline class="h-7 w-full" v-for="pkg in packages" :key="pkg" />
302+
</div>
303+
</template>
304+
</div>
305+
</template>
306+
307+
<style>
308+
.facet-bar .atom-subtitle {
309+
width: 80% !important;
310+
margin: 0 auto;
311+
height: 2rem;
312+
}
313+
</style>

app/components/Compare/PackageSelector.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,7 @@ onClickOutside(containerRef, () => {
272272
<ButtonBase
273273
v-if="showNoDependencyOption"
274274
data-navigable
275-
class="block w-full text-start"
275+
class="block w-full text-start !border-transparent"
276276
:class="highlightedIndex === 0 ? '!bg-accent/15' : ''"
277277
:aria-label="$t('compare.no_dependency.add_column')"
278278
@mouseenter="highlightedIndex = 0"
@@ -297,7 +297,7 @@ onClickOutside(containerRef, () => {
297297
v-for="(result, index) in filteredResults"
298298
:key="result.name"
299299
data-navigable
300-
class="block w-full text-start my-0.5"
300+
class="block w-full text-start my-0.5 !border-transparent"
301301
:class="highlightedIndex === index + resultIndexOffset ? '!bg-accent/15' : ''"
302302
@mouseenter="highlightedIndex = index + resultIndexOffset"
303303
@click="addPackage(result.name)"

app/components/OgImage/BlogPost.vue

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,8 @@ const formattedAuthorNames = computed(() => {
110110
v-if="author.avatar"
111111
:src="author.avatar"
112112
:alt="author.name"
113+
width="48"
114+
height="48"
113115
class="w-full h-full object-cover"
114116
/>
115117
<span v-else style="font-size: 20px; color: #666; font-weight: 500">

0 commit comments

Comments
 (0)