Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
61 changes: 61 additions & 0 deletions app/components/About/GovernanceList.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<script setup lang="ts">
import type { Role, GitHubContributor } from '~~/server/api/contributors.get'

const props = defineProps<{
members: GitHubContributor[]
}>()

const roleLabels = computed(
() =>
({
steward: $t('about.team.role_steward'),
core: $t('about.team.role_core'),
maintainer: $t('about.team.role_maintainer'),
}) as Partial<Record<Role, string>>,
)
Comment thread
coderabbitai[bot] marked this conversation as resolved.
</script>

<template>
<ul class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-3 list-none p-0">
<li
v-for="person in props.members"
:key="person.id"
class="relative p-3 bg-bg-muted border border-border rounded-xl hover:border-border-hover hover:bg-bg-subtle transition-[border-color,background-color] duration-200 cursor-pointer focus-within:ring-2 focus-within:ring-offset-bg focus-within:ring-offset-2 focus-within:ring-fg/50"
>
<div class="flex gap-3">
<img
:src="`${person.avatar_url}&s=80`"
:alt="`${person.login}'s avatar`"
class="block w-15 h-15 rounded-md ring-1 ring-bg shrink-0"
loading="lazy"
/>
<div class="min-w-0 flex-1">
<div class="font-mono text-sm text-fg truncate">
<NuxtLink
:to="person.html_url"
target="_blank"
class="decoration-none after:content-[''] after:absolute after:inset-0"
:aria-label="$t('about.contributors.view_profile', { name: person.login })"
>
@{{ person.login }}
</NuxtLink>
</div>
<div class="text-sm text-fg-muted tracking-tight">
{{ roleLabels[person.role] ?? person.role }}
</div>
<LinkBase
v-if="person.sponsors_url"
:to="person.sponsors_url"
no-underline
no-external-icon
classicon="i-lucide:heart"
class="flex! relative z-10 text-xs text-fg-muted hover:text-pink-400 mt-1"
:aria-label="$t('about.team.sponsor_aria', { name: person.login })"
>
{{ $t('about.team.sponsor') }}
</LinkBase>
</div>
</div>
</li>
</ul>
</template>
4 changes: 2 additions & 2 deletions app/components/About/LogoList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ const props = defineProps<{
:href="item.url"
target="_blank"
rel="noopener noreferrer"
class="relative flex items-center justify-center h-16 rounded-md bg-bg-muted hover:bg-bg-subtle border border-border transition-colors py-1 px-3"
class="relative flex items-center justify-center h-16 rounded-md bg-bg-muted hover:bg-bg-subtle border border-border hover:border-border-hover transition-colors py-1 px-3"
:style="{ paddingBlock: item.normalisingIndent }"
:aria-label="item.name"
>
Expand Down Expand Up @@ -65,7 +65,7 @@ const props = defineProps<{
:href="groupItem.url"
target="_blank"
rel="noopener noreferrer"
class="relative flex items-center justify-center h-full aspect-square rounded-md hover:bg-bg-subtle border border-transparent hover:border-border transition-colors p-1.5"
class="relative flex items-center justify-center h-full aspect-square rounded-md hover:bg-bg-subtle border border-transparent hover:border-border-hover transition-colors p-1.5"
:style="{ paddingBlock: groupItem.normalisingIndent }"
:aria-label="groupItem.name"
>
Expand Down
81 changes: 23 additions & 58 deletions app/pages/about.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
<script setup lang="ts">
import type { Role } from '#server/api/contributors.get'
import { SPONSORS } from '~/assets/logos/sponsors'
import { OSS_PARTNERS } from '~/assets/logos/oss-partners'

Expand Down Expand Up @@ -33,19 +32,15 @@ const pmLinks = {
const { data: contributors, status: contributorsStatus } = useLazyFetch('/api/contributors')

const governanceMembers = computed(
() => contributors.value?.filter(c => c.role !== 'contributor') ?? [],
() => contributors.value?.filter(c => c.role === 'steward' || c.role === 'core') ?? [],
)

const communityContributors = computed(
() => contributors.value?.filter(c => c.role === 'contributor') ?? [],
const maintainersMembers = computed(
() => contributors.value?.filter(c => c.role === 'maintainer') ?? [],
)

const roleLabels = computed(
() =>
({
steward: $t('about.team.role_steward'),
maintainer: $t('about.team.role_maintainer'),
}) as Partial<Record<Role, string>>,
const communityContributors = computed(
() => contributors.value?.filter(c => c.role === 'contributor') ?? [],
)
</script>

Expand Down Expand Up @@ -179,7 +174,7 @@ const roleLabels = computed(
{{ $t('about.contributors.description') }}
</p>

<!-- Governance: stewards + maintainers -->
<!-- Governance: stewards + core -->
<section
v-if="governanceMembers.length"
class="mb-12"
Expand All @@ -189,50 +184,20 @@ const roleLabels = computed(
{{ $t('about.team.governance') }}
</h3>

<ul class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-3 list-none p-0">
<li
v-for="person in governanceMembers"
:key="person.id"
class="relative flex items-center gap-3 p-3 border border-border rounded-lg hover:border-border-hover hover:bg-bg-muted transition-[border-color,background-color] duration-200 cursor-pointer focus-within:ring-2 focus-within:ring-offset-bg focus-within:ring-offset-2 focus-within:ring-fg/50"
>
<img
:src="`${person.avatar_url}&s=80`"
:alt="`${person.login}'s avatar`"
class="w-12 h-12 rounded-md ring-1 ring-border shrink-0"
loading="lazy"
/>
<div class="min-w-0 flex-1">
<div class="font-mono text-sm text-fg truncate">
<NuxtLink
:to="person.html_url"
target="_blank"
class="decoration-none after:content-[''] after:absolute after:inset-0"
:aria-label="$t('about.contributors.view_profile', { name: person.login })"
>
@{{ person.login }}
</NuxtLink>
</div>
<div class="text-xs text-fg-muted tracking-tight">
{{ roleLabels[person.role] ?? person.role }}
</div>
<LinkBase
v-if="person.sponsors_url"
:to="person.sponsors_url"
no-underline
no-external-icon
classicon="i-lucide:heart"
class="relative z-10 text-xs text-fg-muted hover:text-pink-400 mt-0.5"
:aria-label="$t('about.team.sponsor_aria', { name: person.login })"
>
{{ $t('about.team.sponsor') }}
</LinkBase>
</div>
<span
class="i-lucide:external-link rtl-flip w-3.5 h-3.5 text-fg-muted opacity-50 shrink-0 self-start mt-0.5 pointer-events-none"
aria-hidden="true"
/>
</li>
</ul>
<AboutGovernanceList :members="governanceMembers" />
</section>

<!-- maintainers -->
<section
v-if="maintainersMembers.length"
class="mb-12"
aria-labelledby="maintainers-heading"
>
<h3 id="maintainers-heading" class="text-sm text-fg uppercase tracking-wider mb-4">
{{ $t('about.team.maintainers') }}
</h3>

<AboutGovernanceList :members="maintainersMembers" />
</section>

<!-- Contributors cloud -->
Expand Down Expand Up @@ -263,12 +228,12 @@ const roleLabels = computed(
</div>
<ul
v-else-if="communityContributors.length"
class="grid grid-cols-[repeat(auto-fill,48px)] justify-center gap-2 list-none p-0"
class="grid grid-cols-[repeat(auto-fill,48px)] justify-center gap-1 list-none p-0"
>
<li
v-for="contributor in communityContributors"
:key="contributor.id"
class="block group relative"
class="block group relative hover:z-1"
>
<TooltipApp :text="`@${contributor.login}`" class="block" position="top">
<a
Expand All @@ -283,7 +248,7 @@ const roleLabels = computed(
:alt="`${contributor.login}'s avatar`"
width="48"
height="48"
class="w-12 h-12 rounded-lg ring-2 ring-transparent group-hover:ring-accent transition-all duration-200 ease-out group-hover:scale-125 will-change-transform"
class="w-12 h-12 rounded-md ring-1 ring-transparent group-hover:ring-accent transition-all duration-200 ease-out"
loading="lazy"
/>
</a>
Expand Down
4 changes: 2 additions & 2 deletions app/pages/pds.vue
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ const totalAccounts = computed(() => pdsUsers.value.length)
{{ $t('pds.community.empty') }}
</div>
<div v-else>
<ul class="grid grid-cols-[repeat(auto-fill,48px)] justify-center gap-2 list-none p-0">
<ul class="grid grid-cols-[repeat(auto-fill,48px)] justify-center gap-1 list-none p-0">
<li
v-for="user in usersWithAvatars"
:key="user.handle"
Expand All @@ -162,7 +162,7 @@ const totalAccounts = computed(() => pdsUsers.value.length)
@error="handleImageError(user.handle)"
width="48"
height="48"
class="w-12 h-12 rounded-lg ring-2 ring-transparent group-hover:ring-accent transition-all duration-200 ease-out group-hover:scale-125 will-change-transform"
class="w-12 h-12 rounded-md ring-1 ring-transparent group-hover:ring-accent transition-all duration-200 ease-out"
loading="lazy"
/>
</a>
Expand Down
2 changes: 2 additions & 0 deletions i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1185,6 +1185,8 @@
"team": {
"title": "Team",
"governance": "Governance",
"maintainers": "Maintainers",
"role_core": "core",
"role_steward": "steward",
"role_maintainer": "maintainer",
"sponsor": "sponsor",
Expand Down
6 changes: 6 additions & 0 deletions i18n/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -3559,6 +3559,12 @@
"governance": {
"type": "string"
},
"maintainers": {
"type": "string"
},
"role_core": {
"type": "string"
},
"role_steward": {
"type": "string"
},
Expand Down
17 changes: 11 additions & 6 deletions server/api/contributors.get.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export type Role = 'steward' | 'maintainer' | 'contributor'
export type Role = 'steward' | 'maintainer' | 'contributor' | 'core'

export interface GitHubContributor {
login: string
Expand All @@ -18,17 +18,19 @@ const FALLBACK_STEWARDS = new Set(['danielroe', 'patak-cat'])

interface TeamMembers {
steward: Set<string>
core: Set<string>
maintainer: Set<string>
}

async function fetchTeamMembers(token: string): Promise<TeamMembers | null> {
const teams: Record<keyof TeamMembers, string> = {
steward: 'stewards',
core: 'core',
maintainer: 'maintainers',
}

try {
const result: TeamMembers = { steward: new Set(), maintainer: new Set() }
const result: TeamMembers = { steward: new Set(), maintainer: new Set(), core: new Set() }

for (const [role, slug] of Object.entries(teams) as [keyof TeamMembers, string][]) {
const response = await fetch(
Expand Down Expand Up @@ -110,8 +112,9 @@ async function fetchSponsorable(token: string, logins: string[]): Promise<Set<st

function getRoleInfo(login: string, teams: TeamMembers): { role: Role; order: number } {
if (teams.steward.has(login)) return { role: 'steward', order: 0 }
if (teams.maintainer.has(login)) return { role: 'maintainer', order: 1 }
return { role: 'contributor', order: 2 }
if (teams.core.has(login)) return { role: 'core', order: 1 }
if (teams.maintainer.has(login)) return { role: 'maintainer', order: 2 }
return { role: 'contributor', order: 3 }
}

export default defineCachedEventHandler(
Expand All @@ -124,7 +127,7 @@ export default defineCachedEventHandler(
const fetched = await fetchTeamMembers(githubToken)
if (fetched) return fetched
}
return { steward: FALLBACK_STEWARDS, maintainer: new Set<string>() }
return { steward: FALLBACK_STEWARDS, maintainer: new Set<string>(), core: new Set<string>() }
})()

const allContributors: GitHubAPIContributor[] = []
Expand Down Expand Up @@ -169,7 +172,9 @@ export default defineCachedEventHandler(

// Identify maintainers (stewards + maintainers) and check their sponsors status
const maintainerLogins = filtered
.filter(c => teams.steward.has(c.login) || teams.maintainer.has(c.login))
.filter(
c => teams.steward.has(c.login) || teams.core.has(c.login) || teams.maintainer.has(c.login),
)
.map(c => c.login)

const sponsorable = githubToken
Expand Down
32 changes: 32 additions & 0 deletions test/nuxt/a11y.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ import {
AppMark,
AboutLogoImg,
AboutLogoList,
AboutGovernanceList,
AuthorAvatar,
AuthorList,
BackButton,
Expand Down Expand Up @@ -517,6 +518,37 @@ describe('component accessibility audits', () => {
})
})

describe('AboutGovernanceList', () => {
it('should have no accessibility violations', async () => {
const component = await mountSuspended(AboutGovernanceList, {
props: {
members: [
{
id: 1,
login: 'steward1',
avatar_url: 'https://github.com/steward1.png',
html_url: 'https://github.com/steward1',
contributions: 100,
role: 'steward',
sponsors_url: 'https://github.com/sponsors/steward1',
},
{
id: 3,
login: 'contributor1',
avatar_url: 'https://github.com/contributor1.png',
html_url: 'https://github.com/contributor1',
contributions: 100,
role: 'contributor',
sponsors_url: 'https://github.com/sponsors/contributor1',
},
],
},
})
const results = await runAxe(component)
expect(results.violations).toEqual([])
})
})

describe('BaseCard', () => {
it('should have no accessibility violations', async () => {
const component = await mountSuspended(BaseCard, {
Expand Down
Loading