|
1 | 1 | <script setup lang="ts"> |
| 2 | +import type { Role } from '#server/api/contributors.get' |
| 3 | +
|
2 | 4 | const router = useRouter() |
3 | 5 | const canGoBack = useCanGoBack() |
4 | 6 |
|
@@ -27,6 +29,22 @@ const pmLinks = { |
27 | 29 | } |
28 | 30 |
|
29 | 31 | const { data: contributors, status: contributorsStatus } = useLazyFetch('/api/contributors') |
| 32 | +
|
| 33 | +const governanceMembers = computed( |
| 34 | + () => contributors.value?.filter(c => c.role !== 'contributor') ?? [], |
| 35 | +) |
| 36 | +
|
| 37 | +const communityContributors = computed( |
| 38 | + () => contributors.value?.filter(c => c.role === 'contributor') ?? [], |
| 39 | +) |
| 40 | +
|
| 41 | +const roleLabels = computed( |
| 42 | + () => |
| 43 | + ({ |
| 44 | + steward: $t('about.team.role_steward'), |
| 45 | + maintainer: $t('about.team.role_maintainer'), |
| 46 | + }) as Partial<Record<Role, string>>, |
| 47 | +) |
30 | 48 | </script> |
31 | 49 |
|
32 | 50 | <template> |
@@ -139,56 +157,134 @@ const { data: contributors, status: contributorsStatus } = useLazyFetch('/api/co |
139 | 157 |
|
140 | 158 | <div> |
141 | 159 | <h2 class="text-lg text-fg-subtle uppercase tracking-wider mb-4"> |
142 | | - {{ |
143 | | - $t( |
144 | | - 'about.contributors.title', |
145 | | - { count: $n(contributors?.length ?? 0) }, |
146 | | - contributors?.length ?? 0, |
147 | | - ) |
148 | | - }} |
| 160 | + {{ $t('about.team.title') }} |
149 | 161 | </h2> |
150 | 162 | <p class="text-fg-muted leading-relaxed mb-6"> |
151 | 163 | {{ $t('about.contributors.description') }} |
152 | 164 | </p> |
153 | 165 |
|
154 | | - <!-- Contributors cloud --> |
155 | | - <div v-if="contributorsStatus === 'pending'" class="text-fg-subtle text-sm"> |
156 | | - {{ $t('about.contributors.loading') }} |
157 | | - </div> |
158 | | - <div v-else-if="contributorsStatus === 'error'" class="text-fg-subtle text-sm"> |
159 | | - {{ $t('about.contributors.error') }} |
160 | | - </div> |
161 | | - <div |
162 | | - v-else-if="contributors?.length" |
163 | | - class="grid grid-cols-[repeat(auto-fill,48px)] justify-center gap-2" |
| 166 | + <!-- Governance: stewards + maintainers --> |
| 167 | + <section |
| 168 | + v-if="governanceMembers.length" |
| 169 | + class="mb-12" |
| 170 | + aria-labelledby="governance-heading" |
164 | 171 | > |
165 | | - <a |
166 | | - v-for="contributor in contributors" |
167 | | - :key="contributor.id" |
168 | | - :href="contributor.html_url" |
169 | | - target="_blank" |
170 | | - rel="noopener noreferrer" |
171 | | - class="group relative" |
172 | | - :aria-label="$t('about.contributors.view_profile', { name: contributor.login })" |
| 172 | + <h3 |
| 173 | + id="governance-heading" |
| 174 | + class="text-sm text-fg-subtle uppercase tracking-wider mb-4" |
173 | 175 | > |
174 | | - <div class="relative flex items-center"> |
| 176 | + {{ $t('about.team.governance') }} |
| 177 | + </h3> |
| 178 | + |
| 179 | + <ul class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-3 list-none p-0"> |
| 180 | + <li |
| 181 | + v-for="person in governanceMembers" |
| 182 | + :key="person.id" |
| 183 | + 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" |
| 184 | + > |
175 | 185 | <img |
176 | | - :src="`${contributor.avatar_url}&s=64`" |
177 | | - :alt="contributor.login" |
178 | | - width="32" |
179 | | - height="32" |
180 | | - class="w-12 h-12 rounded-lg ring-2 ring-transparent group-hover:ring-accent transition-all duration-200 ease-out hover:scale-125 will-change-transform" |
| 186 | + :src="`${person.avatar_url}&s=80`" |
| 187 | + :alt="`${person.login}'s avatar`" |
| 188 | + class="w-12 h-12 rounded-md ring-1 ring-border shrink-0" |
181 | 189 | loading="lazy" |
182 | 190 | /> |
| 191 | + <div class="min-w-0 flex-1"> |
| 192 | + <div class="font-mono text-sm text-fg truncate"> |
| 193 | + <NuxtLink |
| 194 | + :to="person.html_url" |
| 195 | + target="_blank" |
| 196 | + class="decoration-none after:content-[''] after:absolute after:inset-0" |
| 197 | + :aria-label="$t('about.contributors.view_profile', { name: person.login })" |
| 198 | + > |
| 199 | + @{{ person.login }} |
| 200 | + </NuxtLink> |
| 201 | + </div> |
| 202 | + <div class="text-xs text-fg-muted tracking-tight"> |
| 203 | + {{ roleLabels[person.role] ?? person.role }} |
| 204 | + </div> |
| 205 | + <LinkBase |
| 206 | + v-if="person.sponsors_url" |
| 207 | + :to="person.sponsors_url" |
| 208 | + no-underline |
| 209 | + no-external-icon |
| 210 | + classicon="i-carbon:favorite" |
| 211 | + class="relative z-10 text-xs text-fg-muted hover:text-pink-400 mt-0.5" |
| 212 | + :aria-label="$t('about.team.sponsor_aria', { name: person.login })" |
| 213 | + > |
| 214 | + {{ $t('about.team.sponsor') }} |
| 215 | + </LinkBase> |
| 216 | + </div> |
183 | 217 | <span |
184 | | - class="pointer-events-none absolute -top-9 inset-is-1/2 -translate-x-1/2 whitespace-nowrap rounded-md bg-gray-900 text-white dark:bg-gray-100 dark:text-gray-900 text-xs px-2 py-1 shadow-lg opacity-0 scale-95 transition-all duration-150 group-hover:opacity-100 group-hover:scale-100" |
185 | | - dir="ltr" |
| 218 | + class="i-carbon:launch rtl-flip w-3.5 h-3.5 text-fg-muted opacity-50 shrink-0 self-start mt-0.5" |
| 219 | + aria-hidden="true" |
| 220 | + /> |
| 221 | + </li> |
| 222 | + </ul> |
| 223 | + </section> |
| 224 | + |
| 225 | + <!-- Contributors cloud --> |
| 226 | + <section aria-labelledby="contributors-heading"> |
| 227 | + <h3 |
| 228 | + id="contributors-heading" |
| 229 | + class="text-sm text-fg-subtle uppercase tracking-wider mb-4" |
| 230 | + > |
| 231 | + {{ |
| 232 | + $t( |
| 233 | + 'about.contributors.title', |
| 234 | + { count: $n(communityContributors.length) }, |
| 235 | + communityContributors.length, |
| 236 | + ) |
| 237 | + }} |
| 238 | + </h3> |
| 239 | + |
| 240 | + <div |
| 241 | + v-if="contributorsStatus === 'pending'" |
| 242 | + class="text-fg-subtle text-sm" |
| 243 | + role="status" |
| 244 | + > |
| 245 | + {{ $t('about.contributors.loading') }} |
| 246 | + </div> |
| 247 | + <div |
| 248 | + v-else-if="contributorsStatus === 'error'" |
| 249 | + class="text-fg-subtle text-sm" |
| 250 | + role="alert" |
| 251 | + > |
| 252 | + {{ $t('about.contributors.error') }} |
| 253 | + </div> |
| 254 | + <ul |
| 255 | + v-else-if="communityContributors.length" |
| 256 | + class="grid grid-cols-[repeat(auto-fill,48px)] justify-center gap-2 list-none p-0" |
| 257 | + > |
| 258 | + <li |
| 259 | + v-for="contributor in communityContributors" |
| 260 | + :key="contributor.id" |
| 261 | + class="group relative" |
| 262 | + > |
| 263 | + <LinkBase |
| 264 | + :to="contributor.html_url" |
| 265 | + no-underline |
| 266 | + no-external-icon |
| 267 | + :aria-label="$t('about.contributors.view_profile', { name: contributor.login })" |
186 | 268 | > |
187 | | - @{{ contributor.login }} |
188 | | - </span> |
189 | | - </div> |
190 | | - </a> |
191 | | - </div> |
| 269 | + <img |
| 270 | + :src="`${contributor.avatar_url}&s=64`" |
| 271 | + :alt="`${contributor.login}'s avatar`" |
| 272 | + width="48" |
| 273 | + height="48" |
| 274 | + class="w-12 h-12 rounded-lg ring-2 ring-transparent group-hover:ring-accent transition-all duration-200 ease-out hover:scale-125 will-change-transform" |
| 275 | + loading="lazy" |
| 276 | + /> |
| 277 | + <span |
| 278 | + class="pointer-events-none absolute -top-9 inset-is-1/2 -translate-x-1/2 whitespace-nowrap rounded-md bg-gray-900 text-white dark:bg-gray-100 dark:text-gray-900 text-xs px-2 py-1 shadow-lg opacity-0 scale-95 transition-all duration-150 group-hover:opacity-100 group-hover:scale-100" |
| 279 | + dir="ltr" |
| 280 | + role="tooltip" |
| 281 | + > |
| 282 | + @{{ contributor.login }} |
| 283 | + </span> |
| 284 | + </LinkBase> |
| 285 | + </li> |
| 286 | + </ul> |
| 287 | + </section> |
192 | 288 | </div> |
193 | 289 |
|
194 | 290 | <CallToAction /> |
|
0 commit comments