Skip to content

Commit 77347fd

Browse files
committed
Merge branch 'main' into fix/issue-1323
2 parents c735405 + 3712560 commit 77347fd

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

65 files changed

+2956
-1317
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

app/assets/main.css

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -275,13 +275,16 @@ dd {
275275
}
276276

277277
/* Shiki theme colors */
278-
html.light .shiki,
279-
html.light .shiki span {
278+
html.light .shiki {
280279
color: var(--shiki-light) !important;
281280
background-color: var(--shiki-light-bg) !important;
282-
font-style: var(--shiki-light-font-style) !important;
283-
font-weight: var(--shiki-light-font-weight) !important;
284-
text-decoration: var(--shiki-light-text-decoration) !important;
281+
282+
& span {
283+
color: var(--shiki-light) !important;
284+
font-style: var(--shiki-light-font-style) !important;
285+
font-weight: var(--shiki-light-font-weight) !important;
286+
text-decoration: var(--shiki-light-text-decoration) !important;
287+
}
285288
}
286289

287290
/* Inline code in package descriptions */
Lines changed: 318 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,318 @@
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="grid grid-cols-[12px_minmax(0,1fr)_max-content] items-center gap-x-3">
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 class="text-3xs uppercase tracking-wide text-[var(--fg)]/70 truncate">
230+
${name}
231+
</span>
232+
<span class="text-base text-[var(--fg)] font-mono tabular-nums text-end">
233+
${(datapoint as VueUiHorizontalBarDatapoint).formattedValue ?? 0}
234+
</span>
235+
</div>
236+
</div>
237+
`
238+
},
239+
},
240+
},
241+
},
242+
}
243+
})
244+
</script>
245+
246+
<template>
247+
<div class="font-mono facet-bar">
248+
<ClientOnly v-if="dataset.length">
249+
<VueUiHorizontalBar :key="chartKey" :dataset :config class="[direction:ltr]">
250+
<template #svg="{ svg }">
251+
<!-- Inject npmx logo & tagline during SVG and PNG print -->
252+
<g
253+
v-if="svg.isPrintingSvg || svg.isPrintingImg"
254+
v-html="
255+
drawSmallNpmxLogoAndTaglineWatermark({
256+
svg,
257+
colors: watermarkColors,
258+
translateFn: $t,
259+
})
260+
"
261+
/>
262+
</template>
263+
264+
<template #menuIcon="{ isOpen }">
265+
<span v-if="isOpen" class="i-lucide:x w-6 h-6" aria-hidden="true" />
266+
<span v-else class="i-lucide:ellipsis-vertical w-6 h-6" aria-hidden="true" />
267+
</template>
268+
<template #optionCsv>
269+
<span class="text-fg-subtle font-mono pointer-events-none">CSV</span>
270+
</template>
271+
<template #optionImg>
272+
<span class="text-fg-subtle font-mono pointer-events-none">PNG</span>
273+
</template>
274+
<template #optionSvg>
275+
<span class="text-fg-subtle font-mono pointer-events-none">SVG</span>
276+
</template>
277+
<template #optionAltCopy>
278+
<span
279+
class="w-6 h-6"
280+
:class="
281+
copied ? 'i-lucide:check text-accent' : 'i-lucide:person-standing text-fg-subtle'
282+
"
283+
style="pointer-events: none"
284+
aria-hidden="true"
285+
/>
286+
</template>
287+
</VueUiHorizontalBar>
288+
289+
<template #fallback>
290+
<div class="flex flex-col gap-2 justify-center items-center mb-2">
291+
<SkeletonInline class="h-4 w-16" />
292+
<SkeletonInline class="h-4 w-28" />
293+
</div>
294+
<div class="flex flex-col gap-1">
295+
<SkeletonInline class="h-7 w-full" v-for="pkg in packages" :key="pkg" />
296+
</div>
297+
</template>
298+
</ClientOnly>
299+
300+
<template v-else>
301+
<div class="flex flex-col gap-2 justify-center items-center mb-2">
302+
<SkeletonInline class="h-4 w-16" />
303+
<SkeletonInline class="h-4 w-28" />
304+
</div>
305+
<div class="flex flex-col gap-1">
306+
<SkeletonInline class="h-7 w-full" v-for="pkg in packages" :key="pkg" />
307+
</div>
308+
</template>
309+
</div>
310+
</template>
311+
312+
<style>
313+
.facet-bar .atom-subtitle {
314+
width: 80% !important;
315+
margin: 0 auto;
316+
height: 2rem;
317+
}
318+
</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)"

0 commit comments

Comments
 (0)