Skip to content

Commit 540ed93

Browse files
committed
fix: address PR review feedback for brand page
- Fix Safari clipboard by using ClipboardItem with promise blob - Add loading spinner to PNG download button in customize section - Fix logo height mismatch between dark/light variants - Force canonical sky accent color on light logo previews
1 parent 7c04e99 commit 540ed93

File tree

3 files changed

+35
-5
lines changed

3 files changed

+35
-5
lines changed

app/components/Brand/Customize.vue

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,11 +210,15 @@ async function downloadCustomPng() {
210210
</ButtonBase>
211211
<ButtonBase
212212
size="sm"
213-
classicon="i-lucide:download"
214213
:aria-label="$t('brand.customize.download_png_aria')"
215214
:disabled="pngLoading"
216215
@click="downloadCustomPng"
217216
>
217+
<span
218+
class="size-[1em]"
219+
aria-hidden="true"
220+
:class="pngLoading ? 'i-lucide:loader-circle animate-spin' : 'i-lucide:download'"
221+
/>
218222
{{ $t('brand.logos.download_png') }}
219223
</ButtonBase>
220224
</div>

app/components/LogoContextMenu.vue

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,34 @@ function close() {
3131
show.value = false
3232
}
3333
34-
const { copy, copied } = useClipboard({ copiedDuring: 2000 })
34+
const copied = shallowRef(false)
3535
3636
async function copySvg() {
37-
const res = await fetch('/logo.svg')
38-
const svg = await res.text()
39-
await copy(svg)
37+
try {
38+
// Build the ClipboardItem synchronously so Safari keeps the user-gesture context.
39+
// The blob *value* can be a promise — Safari resolves it while the gesture is still valid.
40+
const textPromise = fetch('/logo.svg').then(r => r.text())
41+
const item = new ClipboardItem({
42+
'text/plain': textPromise.then(t => new Blob([t], { type: 'text/plain' })),
43+
})
44+
await navigator.clipboard.write([item])
45+
copied.value = true
46+
setTimeout(() => { copied.value = false }, 2000)
47+
} catch {
48+
// Fallback for older browsers that don't support ClipboardItem with promises
49+
const res = await fetch('/logo.svg')
50+
const svg = await res.text()
51+
const textarea = document.createElement('textarea')
52+
textarea.value = svg
53+
textarea.style.position = 'fixed'
54+
textarea.style.opacity = '0'
55+
document.body.appendChild(textarea)
56+
textarea.select()
57+
document.execCommand('copy')
58+
document.body.removeChild(textarea)
59+
copied.value = true
60+
setTimeout(() => { copied.value = false }, 2000)
61+
}
4062
setTimeout(close, 1000)
4163
}
4264

app/pages/brand.vue

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
<script setup lang="ts">
2+
import { ACCENT_COLORS } from '#shared/utils/constants'
3+
24
useSeoMeta({
35
title: () => `${$t('brand.title')} - npmx`,
46
ogTitle: () => `${$t('brand.title')} - npmx`,
@@ -146,13 +148,15 @@ async function handlePngDownload(logo: (typeof logos)[number]) {
146148
<AppLogo
147149
v-if="logo.src === '/logo.svg'"
148150
class="max-h-20 w-auto max-w-full text-[#0a0a0a]"
151+
:style="{ '--accent': ACCENT_COLORS.light.sky }"
149152
:aria-label="logo.altLight()"
150153
/>
151154
<img
152155
v-else
153156
:src="logo.src"
154157
:alt="logo.altLight()"
155158
class="max-h-16 w-auto max-w-full"
159+
:class="{ 'max-h-20': logo.span }"
156160
:style="logo.src === '/logo-mark.svg' ? 'filter: invert(1)' : ''"
157161
/>
158162
</div>

0 commit comments

Comments
 (0)