Skip to content

Commit b02f876

Browse files
committed
Merge branch 'main' into vt/nullvoxpopuli
2 parents f0a386d + e4c6642 commit b02f876

File tree

77 files changed

+4405
-461
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

77 files changed

+4405
-461
lines changed

app/components/AppFooter.vue

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -23,22 +23,9 @@ const showModal = () => modalRef.value?.showModal?.()
2323
<LinkBase :to="{ name: 'privacy' }">
2424
{{ $t('privacy_policy.title') }}
2525
</LinkBase>
26-
<LinkBase to="https://docs.npmx.dev">
27-
{{ $t('footer.docs') }}
28-
</LinkBase>
29-
<LinkBase to="https://repo.npmx.dev">
30-
{{ $t('footer.source') }}
31-
</LinkBase>
32-
<LinkBase to="https://social.npmx.dev">
33-
{{ $t('footer.social') }}
34-
</LinkBase>
35-
<LinkBase to="https://chat.npmx.dev">
36-
{{ $t('footer.chat') }}
37-
</LinkBase>
38-
3926
<button
4027
type="button"
41-
class="group inline-flex gap-x-1 items-center justify-center underline-offset-[0.2rem] underline decoration-1 decoration-fg/30 font-mono text-fg hover:(decoration-accent text-accent) focus-visible:(decoration-accent text-accent) transition-colors duration-200"
28+
class="cursor-pointer group inline-flex gap-x-1 items-center justify-center underline-offset-[0.2rem] underline decoration-1 decoration-fg/30 font-mono text-fg hover:(decoration-accent text-accent) focus-visible:(decoration-accent text-accent) transition-colors duration-200"
4229
@click.prevent="showModal"
4330
aria-haspopup="dialog"
4431
>
@@ -102,6 +89,18 @@ const showModal = () => modalRef.value?.showModal?.()
10289
</li>
10390
</ul>
10491
</Modal>
92+
<LinkBase to="https://docs.npmx.dev">
93+
{{ $t('footer.docs') }}
94+
</LinkBase>
95+
<LinkBase to="https://repo.npmx.dev">
96+
{{ $t('footer.source') }}
97+
</LinkBase>
98+
<LinkBase to="https://social.npmx.dev">
99+
{{ $t('footer.social') }}
100+
</LinkBase>
101+
<LinkBase to="https://chat.npmx.dev">
102+
{{ $t('footer.chat') }}
103+
</LinkBase>
105104
</div>
106105
</div>
107106
<BuildEnvironment v-if="!isHome" footer />

app/components/BuildEnvironment.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ const { footer = false, buildInfo: buildInfoProp } = defineProps<{
66
buildInfo?: BuildInfo
77
}>()
88
9-
const { locale } = useI18n()
109
const appConfig = useAppConfig()
1110
const buildInfo = computed(() => buildInfoProp || appConfig.buildInfo)
11+
const buildTime = computed(() => new Date(buildInfo.value.time))
1212
</script>
1313

1414
<template>
@@ -18,7 +18,7 @@ const buildInfo = computed(() => buildInfoProp || appConfig.buildInfo)
1818
style="animation-delay: 0.05s"
1919
>
2020
<i18n-t keypath="built_at" scope="global">
21-
<NuxtTime :datetime="buildInfo.time" :locale="locale" relative />
21+
<DateTime :datetime="buildTime" year="numeric" month="short" day="numeric" />
2222
</i18n-t>
2323
<span>&middot;</span>
2424
<LinkBase

app/components/Compare/PackageSelector.vue

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -92,10 +92,17 @@ function handleKeydown(e: KeyboardEvent) {
9292
}
9393
}
9494
95+
const { start, stop } = useTimeoutFn(() => {
96+
isInputFocused.value = false
97+
}, 200)
98+
9599
function handleBlur() {
96-
useTimeoutFn(() => {
97-
isInputFocused.value = false
98-
}, 200)
100+
start()
101+
}
102+
103+
function handleFocus() {
104+
stop()
105+
isInputFocused.value = true
99106
}
100107
</script>
101108

@@ -151,7 +158,7 @@ function handleBlur() {
151158
size="medium"
152159
class="w-full min-w-25 ps-7"
153160
aria-autocomplete="list"
154-
@focus="isInputFocused = true"
161+
@focus="handleFocus"
155162
@blur="handleBlur"
156163
@keydown="handleKeydown"
157164
/>

app/components/Header/AccountMenu.client.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ function openAuthModal() {
5656
</script>
5757

5858
<template>
59-
<div ref="accountMenuRef" class="relative flex min-w-24 justify-end">
59+
<div ref="accountMenuRef" class="relative flex min-w-28 justify-end">
6060
<ButtonBase
6161
type="button"
6262
:aria-expanded="isOpen"

app/components/Header/AccountMenu.server.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<template>
2-
<div class="relative flex min-w-24 justify-end">
2+
<div class="relative flex min-w-28 justify-end">
33
<div
44
class="inline-flex gap-x-1 items-center justify-center font-mono border border-border rounded-md text-sm px-4 py-2 bg-transparent text-fg border-none"
55
>

app/components/Header/AuthModal.client.vue

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<script setup lang="ts">
22
import { useAtproto } from '~/composables/atproto/useAtproto'
33
import { authRedirect } from '~/utils/atproto/helpers'
4-
import { ensureValidAtIdentifier } from '@atproto/syntax'
4+
import { isAtIdentifierString } from '@atproto/lex'
55
66
const handleInput = shallowRef('')
77
const errorMessage = shallowRef('')
@@ -28,20 +28,15 @@ async function handleCreateAccount() {
2828
2929
async function handleLogin() {
3030
if (handleInput.value) {
31-
// URLS to PDSs are valid for oauth redirects
32-
if (!handleInput.value.startsWith('https://')) {
33-
try {
34-
ensureValidAtIdentifier(handleInput.value)
35-
} catch (error) {
36-
errorMessage.value =
37-
error instanceof Error ? error.message : $t('auth.modal.default_input_error')
38-
return
39-
}
31+
// URLS to PDSs are valid for initiating oauth flows
32+
if (handleInput.value.startsWith('https://') || isAtIdentifierString(handleInput.value)) {
33+
await authRedirect(handleInput.value, {
34+
redirectTo: route.fullPath,
35+
locale: locale.value,
36+
})
37+
} else {
38+
errorMessage.value = $t('auth.modal.default_input_error')
4039
}
41-
await authRedirect(handleInput.value, {
42-
redirectTo: route.fullPath,
43-
locale: locale.value,
44-
})
4540
}
4641
}
4742

app/components/Input/Base.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,11 @@ defineExpose({
3535
v-bind="props.noCorrect ? noCorrect : undefined"
3636
@focus="emit('focus', $event)"
3737
@blur="emit('blur', $event)"
38-
class="bg-bg-subtle border border-border font-mono text-fg placeholder:text-fg-subtle transition-[border-color,outline-color] duration-300 hover:border-fg-subtle outline-2 outline-transparent outline-offset-2 focus:border-accent focus-visible:outline-accent/70 disabled:(opacity-50 cursor-not-allowed)"
38+
class="appearance-none bg-bg-subtle border border-border font-mono text-fg placeholder:text-fg-subtle transition-[border-color,outline-color] duration-300 hover:border-fg-subtle outline-2 outline-transparent outline-offset-2 focus:border-accent focus-visible:outline-accent/70 disabled:(opacity-50 cursor-not-allowed)"
3939
:class="{
4040
'text-xs leading-[1.2] px-2 py-2 rounded-md': size === 'small',
4141
'text-sm leading-none px-3 py-2.5 rounded-lg': size === 'medium',
42-
'text-base leading-none px-6 py-3.5 h-14 rounded-xl': size === 'large',
42+
'text-base leading-[1.4] px-6 py-4 rounded-xl': size === 'large',
4343
}"
4444
:disabled="
4545
/** Catching Vue render-bug of invalid `disabled=false` attribute in the final HTML */

app/components/Org/MembersPanel.vue

Lines changed: 48 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ const isLoadingTeams = shallowRef(false)
3535
// Search/filter
3636
const searchQuery = shallowRef('')
3737
const filterRole = shallowRef<MemberRoleFilter>('all')
38-
const filterTeam = shallowRef<string | null>(null)
38+
const filterTeam = shallowRef<string>('')
3939
const sortBy = shallowRef<'name' | 'role'>('name')
4040
const sortOrder = shallowRef<'asc' | 'desc'>('asc')
4141
@@ -362,18 +362,19 @@ watch(lastExecutionTime, () => {
362362
</div>
363363
<!-- Team filter -->
364364
<div v-if="teamNames.length > 0">
365-
<label for="team-filter" class="sr-only">{{ $t('org.members.filter_by_team') }}</label>
366-
<select
365+
<SelectField
366+
:label="$t('org.members.filter_by_team')"
367+
hidden-label
367368
id="team-filter"
368369
v-model="filterTeam"
369370
name="team-filter"
370-
class="px-2 py-1 font-mono text-xs bg-bg-subtle border border-border rounded text-fg transition-colors duration-200 focus:border-border-hover"
371-
>
372-
<option :value="null">{{ $t('org.members.all_teams') }}</option>
373-
<option v-for="team in teamNames" :key="team" :value="team">
374-
{{ team }}
375-
</option>
376-
</select>
371+
block
372+
size="sm"
373+
:items="[
374+
{ label: $t('org.members.all_teams'), value: '' },
375+
...teamNames.map(team => ({ label: team, value: team })),
376+
]"
377+
/>
377378
</div>
378379
<div
379380
class="flex items-center gap-1 text-xs"
@@ -462,22 +463,22 @@ watch(lastExecutionTime, () => {
462463
<label :for="`role-${member.name}`" class="sr-only">{{
463464
$t('org.members.change_role_for', { name: member.name })
464465
}}</label>
465-
<select
466+
<SelectField
467+
:label="$t('org.members.change_role_for', { name: member.name })"
468+
hidden-label
466469
:id="`role-${member.name}`"
467-
:value="member.role"
470+
:model-value="member.role"
468471
:name="`role-${member.name}`"
469-
class="px-1.5 py-0.5 font-mono text-xs bg-bg-subtle border border-border rounded text-fg transition-colors duration-200 focus:border-border-hover"
470-
@change="
471-
handleChangeRole(
472-
member.name,
473-
($event.target as HTMLSelectElement).value as 'developer' | 'admin' | 'owner',
474-
)
475-
"
476-
>
477-
<option value="developer">{{ getRoleLabel('developer') }}</option>
478-
<option value="admin">{{ getRoleLabel('admin') }}</option>
479-
<option value="owner">{{ getRoleLabel('owner') }}</option>
480-
</select>
472+
block
473+
size="sm"
474+
:items="[
475+
{ label: getRoleLabel('developer'), value: 'developer' },
476+
{ label: getRoleLabel('admin'), value: 'admin' },
477+
{ label: getRoleLabel('owner'), value: 'owner' },
478+
]"
479+
:value="member.role"
480+
@update:modelValue="value => handleChangeRole(member.name, value as MemberRole)"
481+
/>
481482
<!-- Remove button -->
482483
<button
483484
type="button"
@@ -528,30 +529,36 @@ watch(lastExecutionTime, () => {
528529
size="small"
529530
/>
530531
<div class="flex items-center gap-2">
531-
<label for="new-member-role" class="sr-only">{{ $t('org.members.role_label') }}</label>
532-
<select
532+
<SelectField
533+
:label="$t('org.members.role_label')"
534+
hidden-label
533535
id="new-member-role"
534536
v-model="newRole"
535537
name="new-member-role"
536-
class="flex-1 px-2 py-1.5 font-mono text-sm bg-bg border border-border rounded text-fg transition-colors duration-200 focus:border-border-hover"
537-
>
538-
<option value="developer">{{ $t('org.members.role.developer') }}</option>
539-
<option value="admin">{{ $t('org.members.role.admin') }}</option>
540-
<option value="owner">{{ $t('org.members.role.owner') }}</option>
541-
</select>
538+
block
539+
class="flex-1"
540+
size="sm"
541+
:items="[
542+
{ label: $t('org.members.role.developer'), value: 'developer' },
543+
{ label: $t('org.members.role.admin'), value: 'admin' },
544+
{ label: $t('org.members.role.owner'), value: 'owner' },
545+
]"
546+
/>
542547
<!-- Team selection -->
543-
<label for="new-member-team" class="sr-only">{{ $t('org.members.team_label') }}</label>
544-
<select
548+
<SelectField
549+
:label="$t('org.members.team_label')"
550+
hidden-label
545551
id="new-member-team"
546552
v-model="newTeam"
547553
name="new-member-team"
548-
class="flex-1 px-2 py-1.5 font-mono text-sm bg-bg border border-border rounded text-fg transition-colors duration-200 focus:border-border-hover"
549-
>
550-
<option value="">{{ $t('org.members.no_team') }}</option>
551-
<option v-for="team in teamNames" :key="team" :value="team">
552-
{{ team }}
553-
</option>
554-
</select>
554+
block
555+
class="flex-1"
556+
size="sm"
557+
:items="[
558+
{ label: $t('org.members.no_team'), value: '' },
559+
...teamNames.map(team => ({ label: team, value: team })),
560+
]"
561+
/>
555562
<button
556563
type="submit"
557564
:disabled="!newUsername.trim() || isAddingMember"

app/components/Package/AccessControls.vue

Lines changed: 25 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -243,41 +243,39 @@ watch(
243243
<div v-if="showGrantAccess">
244244
<form class="space-y-2" @submit.prevent="handleGrantAccess">
245245
<div class="flex items-center gap-2">
246-
<label for="grant-team-select" class="sr-only">{{
247-
$t('package.access.select_team_label')
248-
}}</label>
249-
<select
246+
<SelectField
247+
:label="$t('package.access.select_team_label')"
248+
hidden-label
250249
id="grant-team-select"
251250
v-model="selectedTeam"
252251
name="grant-team"
253-
class="flex-1 px-2 py-1.5 font-mono text-sm bg-bg-subtle border border-border rounded text-fg transition-colors duration-200 focus:border-border-hover"
252+
block
253+
size="sm"
254254
:disabled="isLoadingTeams"
255-
>
256-
<option value="" disabled>
257-
{{
258-
isLoadingTeams
255+
:items="[
256+
{
257+
label: isLoadingTeams
259258
? $t('package.access.loading_teams')
260-
: $t('package.access.select_team')
261-
}}
262-
</option>
263-
<option v-for="team in teams" :key="team" :value="team">
264-
{{ orgName }}:{{ team }}
265-
</option>
266-
</select>
267-
</div>
268-
<div class="flex items-center gap-2">
269-
<label for="grant-permission-select" class="sr-only">{{
270-
$t('package.access.permission_label')
271-
}}</label>
272-
<select
259+
: $t('package.access.select_team'),
260+
value: '',
261+
disabled: true,
262+
},
263+
...teams.map(team => ({ label: `${orgName}:${team}`, value: team })),
264+
]"
265+
/>
266+
<SelectField
267+
:label="$t('package.access.permission_label')"
268+
hidden-label
273269
id="grant-permission-select"
274270
v-model="permission"
275271
name="grant-permission"
276-
class="flex-1 px-2 py-1.5 font-mono text-sm bg-bg-subtle border border-border rounded text-fg transition-colors duration-200 focus:border-border-hover"
277-
>
278-
<option value="read-only">{{ $t('package.access.permission.read_only') }}</option>
279-
<option value="read-write">{{ $t('package.access.permission.read_write') }}</option>
280-
</select>
272+
block
273+
size="sm"
274+
:items="[
275+
{ label: $t('package.access.permission.read_only'), value: 'read-only' },
276+
{ label: $t('package.access.permission.read_write'), value: 'read-write' },
277+
]"
278+
/>
281279
<button
282280
type="submit"
283281
:disabled="!selectedTeam || isGranting"

app/components/Package/Dependencies.vue

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ const numberFormatter = useNumberFormatter()
8585
)
8686
"
8787
>
88-
<ul class="space-y-1 list-none m-0" :aria-label="$t('package.dependencies.list_label')">
88+
<ul class="px-1 space-y-1 list-none m-0" :aria-label="$t('package.dependencies.list_label')">
8989
<li
9090
v-for="[dep, version] in sortedDependencies.slice(0, depsExpanded ? undefined : 10)"
9191
:key="dep"
@@ -168,7 +168,10 @@ const numberFormatter = useNumberFormatter()
168168
})
169169
"
170170
>
171-
<ul class="space-y-1 list-none m-0" :aria-label="$t('package.peer_dependencies.list_label')">
171+
<ul
172+
class="px-1 space-y-1 list-none m-0"
173+
:aria-label="$t('package.peer_dependencies.list_label')"
174+
>
172175
<li
173176
v-for="peer in sortedPeerDependencies.slice(0, peerDepsExpanded ? undefined : 10)"
174177
:key="peer.name"
@@ -225,7 +228,7 @@ const numberFormatter = useNumberFormatter()
225228
"
226229
>
227230
<ul
228-
class="space-y-1 list-none m-0"
231+
class="px-1 space-y-1 list-none m-0"
229232
:aria-label="$t('package.optional_dependencies.list_label')"
230233
>
231234
<li

0 commit comments

Comments
 (0)