Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion app/components/Package/Maintainers.vue
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,8 @@ watch(
class="link-subtle text-sm shrink-0"
dir="ltr"
>
~{{ maintainer.name }}
<UserAvatar :username="maintainer.name" size="xs" aria-hidden="true" />
<span>{{ maintainer.name }}</span>
Comment thread
bluwy marked this conversation as resolved.
Outdated
</LinkBase>
<span v-else class="font-mono text-sm text-fg-muted" dir="ltr">{{
maintainer.email
Expand Down
52 changes: 45 additions & 7 deletions app/components/User/Avatar.vue
Original file line number Diff line number Diff line change
@@ -1,8 +1,36 @@
<script setup lang="ts">
const props = defineProps<{
username: string
size: 'xs' | 'lg'
}>()

const sizePixels = computed(() => {
switch (props.size) {
case 'xs':
return 24
case 'lg':
return 64
}
})

const sizeClass = computed(() => {
switch (props.size) {
case 'xs':
return 'size-6'
case 'lg':
return 'size-16'
}
})

const textClass = computed(() => {
switch (props.size) {
case 'xs':
return 'text-xs'
case 'lg':
return 'text-2xl'
}
})
Comment thread
bluwy marked this conversation as resolved.

const { data: gravatarUrl } = useLazyFetch(() => `/api/gravatar/${props.username}`, {
transform: res => (res.hash ? `/_avatar/${res.hash}?s=128&d=404` : null),
getCachedData(key, nuxtApp) {
Expand All @@ -14,7 +42,8 @@ const { data: gravatarUrl } = useLazyFetch(() => `/api/gravatar/${props.username
<template>
<!-- Avatar -->
<div
class="size-16 shrink-0 rounded-full bg-bg-muted border border-border flex items-center justify-center overflow-hidden"
class="shrink-0 rounded-full bg-bg-muted border border-border flex items-center justify-center overflow-hidden"
:class="sizeClass"
role="img"
:aria-label="`Avatar for ${username}`"
>
Expand All @@ -23,13 +52,22 @@ const { data: gravatarUrl } = useLazyFetch(() => `/api/gravatar/${props.username
v-if="gravatarUrl"
:src="gravatarUrl"
alt=""
width="64"
height="64"
:width="sizePixels"
:height="sizePixels"
class="w-full h-full object-cover"
/>
<!-- Else fallback to initials -->
<span v-else class="text-2xl text-fg-subtle font-mono" aria-hidden="true">
{{ username.charAt(0).toUpperCase() }}
</span>
<!-- Else fallback to initials (use svg to avoid underline styling) -->
<svg
Comment on lines +59 to +60
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I couldn't figure out how to remove the underline style if wrapped in an anchor tag, so I used an SVG here. Seems like you need block or inline-block in order to override it, but the parent div is a flex which seems to make anything under not work.

v-else
xmlns="http://www.w3.org/2000/svg"
:width="sizePixels"
:height="sizePixels"
class="text-fg-subtle"
:class="textClass"
>
<text x="50%" y="50%" dominant-baseline="central" text-anchor="middle" fill="currentColor">
{{ username.charAt(0).toUpperCase() }}
</text>
</svg>
</div>
</template>
2 changes: 1 addition & 1 deletion app/pages/~[username]/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ defineOgImageComponent('Default', {
<!-- Header -->
<header class="mb-8 pb-8 border-b border-border">
<div class="flex flex-wrap items-center gap-4">
<UserAvatar :username="username" />
<UserAvatar :username="username" size="lg" />
<div>
<h1 class="font-mono text-2xl sm:text-3xl font-medium">~{{ username }}</h1>
<p v-if="results?.total" class="text-fg-muted text-sm mt-1">
Expand Down
2 changes: 1 addition & 1 deletion app/pages/~[username]/orgs.vue
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ defineOgImageComponent('Default', {
<!-- Header -->
<header class="mb-8 pb-8 border-b border-border">
<div class="flex flex-wrap items-center gap-4 mb-4">
<UserAvatar :username="username" />
<UserAvatar :username="username" size="lg" />
<div>
<h1 class="font-mono text-2xl sm:text-3xl font-medium">~{{ username }}</h1>
<p class="text-fg-muted text-sm mt-1">{{ $t('user.orgs_page.title') }}</p>
Expand Down
17 changes: 14 additions & 3 deletions test/nuxt/a11y.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2854,27 +2854,38 @@ describe('component accessibility audits', () => {
describe('UserAvatar', () => {
it('should have no accessibility violations', async () => {
const component = await mountSuspended(UserAvatar, {
props: { username: 'testuser' },
props: { username: 'testuser', size: 'lg' },
})
const results = await runAxe(component)
expect(results.violations).toEqual([])
})

it('should have no accessibility violations with short username', async () => {
const component = await mountSuspended(UserAvatar, {
props: { username: 'a' },
props: { username: 'a', size: 'lg' },
})
const results = await runAxe(component)
expect(results.violations).toEqual([])
})

it('should have no accessibility violations with long username', async () => {
const component = await mountSuspended(UserAvatar, {
props: { username: 'verylongusernameexample' },
props: { username: 'verylongusernameexample', size: 'lg' },
})
const results = await runAxe(component)
expect(results.violations).toEqual([])
})

it('should have no accessibility violations in all sizes', async () => {
const sizes = ['xs', 'lg'] as const
for (const size of sizes) {
const component = await mountSuspended(UserAvatar, {
props: { username: 'testuser', size },
})
const results = await runAxe(component)
expect(results.violations).toEqual([])
}
})
})

// Diff components
Expand Down
Loading