feat(SPA): screens for Sites and Authors
This commit is contained in:
1 parent
b34a173d83
commit
3d81d2c717
82 files changed
+10676
-37
No files matched your search
@@ -0,0 +1,115 @@
|
||||
<script setup lang="ts">
|
||||
import { t } from '@/i18n'
|
||||
|
||||
import { computed } from 'vue'
|
||||
import AuthorAvatar from '@/components/AuthorAvatar.vue'
|
||||
import type { LinkableUser } from '@/lib/directoryTypes'
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
users: LinkableUser[]
|
||||
status: 'loading' | 'ready' | 'error'
|
||||
idlePlaceholder?: string
|
||||
clearable?: boolean
|
||||
disabled?: boolean
|
||||
invalid?: boolean
|
||||
}>(),
|
||||
{ idlePlaceholder: '', clearable: false, disabled: false, invalid: false },
|
||||
)
|
||||
|
||||
const emit = defineEmits<{ retry: []; open: [] }>()
|
||||
|
||||
const menuOpen = defineModel<boolean>('open', { default: false })
|
||||
|
||||
const userId = defineModel<number | null>({ default: null })
|
||||
|
||||
const items = computed(() =>
|
||||
props.users.length === 0
|
||||
? []
|
||||
: [
|
||||
{ type: 'label' as const, label: t('Users without an author') },
|
||||
...props.users.map((user) => ({
|
||||
label: user.label,
|
||||
description: user.email,
|
||||
value: user.id,
|
||||
avatarName: user.label,
|
||||
})),
|
||||
],
|
||||
)
|
||||
|
||||
const placeholder = computed(() =>
|
||||
menuOpen.value || !props.idlePlaceholder ? t('Choose a user') : props.idlePlaceholder,
|
||||
)
|
||||
const chosen = computed(() => props.users.find((user) => user.id === userId.value))
|
||||
|
||||
function onOpen(value: boolean) {
|
||||
if (value) {
|
||||
emit('open')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<USelectMenu
|
||||
v-model="userId"
|
||||
v-model:open="menuOpen"
|
||||
:portal="false"
|
||||
:content="{ position: 'inline' }"
|
||||
:items="items"
|
||||
value-key="value"
|
||||
:search-input="false"
|
||||
:placeholder="placeholder"
|
||||
:loading="status === 'loading'"
|
||||
:disabled="disabled"
|
||||
:color="invalid ? 'error' : undefined"
|
||||
:highlight="invalid"
|
||||
:clear="clearable && userId !== null"
|
||||
class="w-full"
|
||||
:ui="{
|
||||
base: 'h-11 rounded-none text-lg ring-(--ui-border-accented) md:h-10 md:text-sm',
|
||||
trailingIcon: 'size-3.5 text-muted',
|
||||
content: 'rounded-none shadow-(--shadow-popover)',
|
||||
label: 'px-2 py-1.5 text-xs text-muted',
|
||||
item: 'min-h-12 gap-2 px-2 py-1 data-[state=checked]:bg-elevated md:min-h-9',
|
||||
itemDescription: 'truncate text-xs text-muted',
|
||||
placeholder: 'text-muted',
|
||||
}"
|
||||
@update:open="onOpen"
|
||||
>
|
||||
<template #default>
|
||||
<span v-if="chosen" class="truncate text-highlighted">{{ chosen.label }}</span>
|
||||
<span v-else class="truncate text-muted">{{ placeholder }}</span>
|
||||
</template>
|
||||
<template #item-leading="{ item }">
|
||||
<AuthorAvatar v-if="'avatarName' in item" :name="String(item.avatarName)" :size="24" />
|
||||
</template>
|
||||
<template #empty>
|
||||
<div v-if="status === 'error'" class="flex flex-col items-start gap-2 p-3">
|
||||
<span class="text-error">{{ t("Couldn't load users") }}</span>
|
||||
<UButton
|
||||
color="neutral"
|
||||
variant="outline"
|
||||
icon="i-lucide-rotate-cw"
|
||||
class="h-11 gap-1.5 border border-accented bg-default px-2.5 text-xs text-highlighted ring-0 md:h-7"
|
||||
:ui="{ leadingIcon: 'size-3.5' }"
|
||||
@click="emit('retry')"
|
||||
>
|
||||
{{ t('Try again') }}
|
||||
</UButton>
|
||||
</div>
|
||||
<div
|
||||
v-else-if="status === 'loading'"
|
||||
class="flex flex-col gap-2.5 p-3"
|
||||
aria-busy="true"
|
||||
>
|
||||
<USkeleton
|
||||
v-for="n in 3"
|
||||
:key="n"
|
||||
class="h-3 rounded-none bg-accented"
|
||||
:class="n === 3 ? 'w-3/5' : 'w-4/5'"
|
||||
/>
|
||||
</div>
|
||||
<div v-else class="p-3 text-muted">{{ t('Every user already has an author.') }}</div>
|
||||
</template>
|
||||
</USelectMenu>
|
||||
</template>
|
||||
Reference in new issue
Block a user