Skip to content

Commit 26feb38

Browse files
committed
Merge remote-tracking branch 'origin/main' into storybook-playground-link
2 parents b38a4b5 + 4aab312 commit 26feb38

File tree

11 files changed

+203
-79
lines changed

11 files changed

+203
-79
lines changed
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
<script setup lang="ts">
2+
import type { HTMLAttributes } from 'vue'
3+
4+
defineOptions({
5+
inheritAttrs: false,
6+
})
7+
8+
const props = defineProps<{
9+
copied: boolean
10+
copyText?: string
11+
copiedText?: string
12+
ariaLabelCopy?: string
13+
ariaLabelCopied?: string
14+
buttonAttrs?: HTMLAttributes
15+
}>()
16+
17+
const buttonCopyText = computed(() => props.copyText || $t('common.copy'))
18+
const buttonCopiedText = computed(() => props.copiedText || $t('common.copied'))
19+
const buttonAriaLabelCopy = computed(() => props.ariaLabelCopy || $t('common.copy'))
20+
const buttonAriaLabelCopied = computed(() => props.ariaLabelCopied || $t('common.copied'))
21+
22+
const emit = defineEmits<{
23+
click: []
24+
}>()
25+
26+
function handleClick() {
27+
emit('click')
28+
}
29+
</script>
30+
31+
<template>
32+
<div class="group relative" v-bind="$attrs">
33+
<slot />
34+
<button
35+
type="button"
36+
@click="handleClick"
37+
class="absolute z-20 inset-is-0 top-full inline-flex items-center gap-1 px-2 py-1 rounded border text-xs font-mono whitespace-nowrap transition-all duration-150 opacity-0 -translate-y-1 pointer-events-none group-hover:opacity-100 group-hover:translate-y-0 group-hover:pointer-events-auto focus-visible:opacity-100 focus-visible:translate-y-0 focus-visible:pointer-events-auto"
38+
:class="[
39+
$style.copyButton,
40+
copied ? 'text-accent bg-accent/10' : 'text-fg-muted bg-bg border-border',
41+
]"
42+
:aria-label="copied ? buttonAriaLabelCopied : buttonAriaLabelCopy"
43+
v-bind="buttonAttrs"
44+
>
45+
<span
46+
:class="copied ? 'i-lucide:check' : 'i-lucide:copy'"
47+
class="w-3.5 h-3.5"
48+
aria-hidden="true"
49+
/>
50+
{{ copied ? buttonCopiedText : buttonCopyText }}
51+
</button>
52+
</div>
53+
</template>
54+
55+
<style module>
56+
.copyButton {
57+
clip: rect(0 0 0 0);
58+
clip-path: inset(50%);
59+
height: 1px;
60+
overflow: hidden;
61+
width: 1px;
62+
transition:
63+
opacity 0.25s 0.1s,
64+
translate 0.15s 0.1s,
65+
clip 0.01s 0.34s allow-discrete,
66+
clip-path 0.01s 0.34s allow-discrete,
67+
height 0.01s 0.34s allow-discrete,
68+
width 0.01s 0.34s allow-discrete;
69+
}
70+
71+
:global(.group):hover .copyButton,
72+
.copyButton:focus-visible {
73+
clip: auto;
74+
clip-path: none;
75+
height: auto;
76+
overflow: visible;
77+
width: auto;
78+
transition:
79+
opacity 0.15s,
80+
translate 0.15s;
81+
}
82+
83+
@media (hover: none) {
84+
.copyButton {
85+
display: none;
86+
}
87+
}
88+
</style>

app/pages/compare.vue

Lines changed: 72 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@ definePageMeta({
66
name: 'compare',
77
})
88
9+
const { locale } = useI18n()
910
const router = useRouter()
1011
const canGoBack = useCanGoBack()
12+
const { copied, copy } = useClipboard({ copiedDuring: 2000 })
1113
1214
// Sync packages with URL query param (stable ref - doesn't change on other query changes)
1315
const packagesParam = useRouteQuery<string>('packages', '', { mode: 'replace' })
@@ -79,6 +81,57 @@ const gridHeaders = computed(() =>
7981
gridColumns.value.map(col => (col.version ? `${col.name}@${col.version}` : col.name)),
8082
)
8183
84+
/*
85+
* Convert the comparison grid data to a Markdown table.
86+
*/
87+
function exportComparisonDataAsMarkdown() {
88+
const mdData: Array<Array<string>> = []
89+
const headers = [
90+
'',
91+
...gridHeaders.value,
92+
...(showNoDependency.value ? [$t('compare.no_dependency.label')] : []),
93+
]
94+
mdData.push(headers)
95+
const maxLengths = headers.map(item => item.length)
96+
97+
selectedFacets.value.forEach((facet, index) => {
98+
const label = facet.label
99+
const data = getFacetValues(facet.id)
100+
mdData.push([
101+
label,
102+
...data.map(item =>
103+
item?.type === 'date'
104+
? new Date(item.display).toLocaleDateString(locale.value, {
105+
year: 'numeric',
106+
month: 'short',
107+
day: 'numeric',
108+
})
109+
: item?.display || '',
110+
),
111+
])
112+
mdData?.[index + 1]?.forEach((item, itemIndex) => {
113+
if (item.length > (maxLengths?.[itemIndex] || 0)) {
114+
maxLengths[itemIndex] = item.length
115+
}
116+
})
117+
})
118+
119+
const markdown = mdData.reduce((result, row, index) => {
120+
// replacing pipe `|` with `ǀ` (U+01C0 Latin Letter Dental Click) to avoid breaking tables
121+
result += `| ${row
122+
.map((el, ind) => el.padEnd(maxLengths[ind] || 0, ' ').replace(/\|/g, 'ǀ'))
123+
.join(' | ')} |`
124+
if (index === 0) {
125+
result += `\n|`
126+
maxLengths.forEach(len => (result += ` ${'-'.padEnd(len, '-')} |`))
127+
}
128+
result += `\n`
129+
return result
130+
}, '')
131+
132+
copy(markdown)
133+
}
134+
82135
useSeoMeta({
83136
title: () =>
84137
packages.value.length > 0
@@ -193,7 +246,24 @@ useSeoMeta({
193246

194247
<!-- Comparison grid -->
195248
<section v-if="canCompare" class="mt-10" aria-labelledby="comparison-heading">
196-
<h2 id="comparison-heading" class="text-xs text-fg-subtle uppercase tracking-wider mb-4">
249+
<CopyToClipboardButton
250+
v-if="packagesData && packagesData.some(p => p !== null)"
251+
:copied="copied"
252+
:copy-text="$t('compare.packages.copy_as_markdown')"
253+
class="mb-4"
254+
:button-attrs="{ class: 'hidden md:inline-flex' }"
255+
@click="exportComparisonDataAsMarkdown"
256+
>
257+
<h2 id="comparison-heading" class="text-xs text-fg-subtle uppercase tracking-wider">
258+
{{ $t('compare.packages.section_comparison') }}
259+
</h2>
260+
</CopyToClipboardButton>
261+
262+
<h2
263+
v-else
264+
id="comparison-heading"
265+
class="text-xs text-fg-subtle uppercase tracking-wider mb-4"
266+
>
197267
{{ $t('compare.packages.section_comparison') }}
198268
</h2>
199269

@@ -241,7 +311,7 @@ useSeoMeta({
241311
</div>
242312

243313
<h2
244-
id="comparison-heading"
314+
id="trends-comparison-heading"
245315
class="text-xs text-fg-subtle uppercase tracking-wider mb-4 mt-10"
246316
>
247317
{{ $t('compare.facets.trends.title') }}

app/pages/package/[[org]]/[name].vue

Lines changed: 13 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -683,7 +683,12 @@ const showSkeleton = shallowRef(false)
683683
>
684684
<!-- Package name and version -->
685685
<div class="flex items-baseline gap-x-2 gap-y-1 sm:gap-x-3 flex-wrap min-w-0">
686-
<div class="group relative flex flex-col items-start min-w-0">
686+
<CopyToClipboardButton
687+
:copied="copiedPkgName"
688+
:copy-text="$t('package.copy_name')"
689+
class="flex flex-col items-start min-w-0"
690+
@click="copyPkgName()"
691+
>
687692
<h1
688693
class="font-mono text-2xl sm:text-3xl font-medium min-w-0 break-words"
689694
:title="pkg.name"
@@ -697,30 +702,14 @@ const showSkeleton = shallowRef(false)
697702
{{ orgName ? pkg.name.replace(`@${orgName}/`, '') : pkg.name }}
698703
</span>
699704
</h1>
705+
</CopyToClipboardButton>
700706

701-
<!-- Floating copy name button -->
702-
<button
703-
type="button"
704-
@click="copyPkgName()"
705-
class="absolute z-20 inset-is-0 top-full inline-flex items-center gap-1 px-2 py-1 rounded border text-xs font-mono whitespace-nowrap transition-all duration-150 opacity-0 -translate-y-1 pointer-events-none group-hover:opacity-100 group-hover:translate-y-0 group-hover:pointer-events-auto focus-visible:opacity-100 focus-visible:translate-y-0 focus-visible:pointer-events-auto"
706-
:class="[
707-
$style.copyButton,
708-
copiedPkgName ? 'text-accent bg-accent/10' : 'text-fg-muted bg-bg border-border',
709-
]"
710-
:aria-label="copiedPkgName ? $t('common.copied') : $t('package.copy_name')"
711-
>
712-
<span
713-
:class="copiedPkgName ? 'i-lucide:check' : 'i-lucide:copy'"
714-
class="w-3.5 h-3.5"
715-
aria-hidden="true"
716-
/>
717-
{{ copiedPkgName ? $t('common.copied') : $t('package.copy_name') }}
718-
</button>
719-
</div>
720-
721-
<span
707+
<CopyToClipboardButton
722708
v-if="resolvedVersion"
723-
class="inline-flex items-baseline gap-1.5 font-mono text-base sm:text-lg text-fg-muted shrink-0 relative group"
709+
:copied="copiedVersion"
710+
:copy-text="$t('package.copy_version')"
711+
class="inline-flex items-baseline gap-1.5 font-mono text-base sm:text-lg text-fg-muted shrink-0"
712+
@click="copyVersion()"
724713
>
725714
<!-- Version resolution indicator (e.g., "latest → 4.2.0") -->
726715
<template v-if="requestedVersion && resolvedVersion !== requestedVersion">
@@ -763,26 +752,7 @@ const showSkeleton = shallowRef(false)
763752
class="text-fg-subtle text-sm shrink-0"
764753
>{{ $t('package.not_latest') }}</span
765754
>
766-
767-
<!-- Floating copy version button -->
768-
<button
769-
type="button"
770-
@click="copyVersion()"
771-
class="absolute z-20 inset-is-0 top-full inline-flex items-center gap-1 px-2 py-1 rounded border text-xs font-mono whitespace-nowrap transition-all duration-150 opacity-0 -translate-y-1 pointer-events-none group-hover:opacity-100 group-hover:translate-y-0 group-hover:pointer-events-auto focus-visible:opacity-100 focus-visible:translate-y-0 focus-visible:pointer-events-auto"
772-
:class="[
773-
$style.copyButton,
774-
copiedVersion ? 'text-accent bg-accent/10' : 'text-fg-muted bg-bg border-border',
775-
]"
776-
:aria-label="copiedVersion ? $t('common.copied') : $t('package.copy_version')"
777-
>
778-
<span
779-
:class="copiedVersion ? 'i-lucide:check' : 'i-lucide:copy'"
780-
class="w-3.5 h-3.5"
781-
aria-hidden="true"
782-
/>
783-
{{ copiedVersion ? $t('common.copied') : $t('package.copy_version') }}
784-
</button>
785-
</span>
755+
</CopyToClipboardButton>
786756

787757
<!-- Docs + Code + Compare — inline on desktop, floating bottom bar on mobile -->
788758
<ButtonGroup
@@ -1563,39 +1533,6 @@ const showSkeleton = shallowRef(false)
15631533
grid-area: sidebar;
15641534
}
15651535
1566-
.copyButton {
1567-
clip: rect(0 0 0 0);
1568-
clip-path: inset(50%);
1569-
height: 1px;
1570-
overflow: hidden;
1571-
width: 1px;
1572-
transition:
1573-
opacity 0.25s 0.1s,
1574-
translate 0.15s 0.1s,
1575-
clip 0.01s 0.34s allow-discrete,
1576-
clip-path 0.01s 0.34s allow-discrete,
1577-
height 0.01s 0.34s allow-discrete,
1578-
width 0.01s 0.34s allow-discrete;
1579-
}
1580-
1581-
:global(.group):hover .copyButton,
1582-
.copyButton:focus-visible {
1583-
clip: auto;
1584-
clip-path: none;
1585-
height: auto;
1586-
overflow: visible;
1587-
width: auto;
1588-
transition:
1589-
opacity 0.15s,
1590-
translate 0.15s;
1591-
}
1592-
1593-
@media (hover: none) {
1594-
.copyButton {
1595-
display: none;
1596-
}
1597-
}
1598-
15991536
/* Mobile floating nav: safe-area positioning + kbd hiding */
16001537
@media (max-width: 639.9px) {
16011538
.packageNav {

i18n/locales/en.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -918,6 +918,7 @@
918918
"section_packages": "Packages",
919919
"section_facets": "Facets",
920920
"section_comparison": "Comparison",
921+
"copy_as_markdown": "Copy table",
921922
"loading": "Loading package data...",
922923
"error": "Failed to load package data. Please try again.",
923924
"empty_title": "Select packages to compare",

i18n/locales/pl-PL.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -908,6 +908,7 @@
908908
"section_packages": "Pakiety",
909909
"section_facets": "Aspekty",
910910
"section_comparison": "Porównanie",
911+
"copy_as_markdown": "Kopiuj tabelę",
911912
"loading": "Ładowanie danych pakietów...",
912913
"error": "Nie udało się wczytać danych pakietów. Spróbuj ponownie.",
913914
"empty_title": "Wybierz pakiety do porównania",

i18n/schema.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2758,6 +2758,9 @@
27582758
"section_comparison": {
27592759
"type": "string"
27602760
},
2761+
"copy_as_markdown": {
2762+
"type": "string"
2763+
},
27612764
"loading": {
27622765
"type": "string"
27632766
},

lunaria/files/en-GB.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -917,6 +917,7 @@
917917
"section_packages": "Packages",
918918
"section_facets": "Facets",
919919
"section_comparison": "Comparison",
920+
"copy_as_markdown": "Copy table",
920921
"loading": "Loading package data...",
921922
"error": "Failed to load package data. Please try again.",
922923
"empty_title": "Select packages to compare",

lunaria/files/en-US.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -917,6 +917,7 @@
917917
"section_packages": "Packages",
918918
"section_facets": "Facets",
919919
"section_comparison": "Comparison",
920+
"copy_as_markdown": "Copy table",
920921
"loading": "Loading package data...",
921922
"error": "Failed to load package data. Please try again.",
922923
"empty_title": "Select packages to compare",

lunaria/files/pl-PL.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -907,6 +907,7 @@
907907
"section_packages": "Pakiety",
908908
"section_facets": "Aspekty",
909909
"section_comparison": "Porównanie",
910+
"copy_as_markdown": "Kopiuj tabelę",
910911
"loading": "Ładowanie danych pakietów...",
911912
"error": "Nie udało się wczytać danych pakietów. Spróbuj ponownie.",
912913
"empty_title": "Wybierz pakiety do porównania",

server/utils/docs/render.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import type { DenoDocNode, JsDocTag } from '#shared/types/deno-doc'
1010
import { highlightCodeBlock } from '../shiki'
1111
import { formatParam, formatType, getNodeSignature } from './format'
1212
import { groupMergedByKind } from './processing'
13-
import { createSymbolId, escapeHtml, parseJsDocLinks, renderMarkdown } from './text'
13+
import { escapeHtml, createSymbolId, parseJsDocLinks, renderMarkdown } from './text'
1414
import type { MergedSymbol, SymbolLookup } from './types'
1515

1616
// =============================================================================

0 commit comments

Comments
 (0)