feat(SPA): profile page, display name and the new account menu
This commit is contained in:
1 parent
55525c06d6
commit
b0f7b7892b
29 files changed
+3166
-469
No files matched your search
@@ -0,0 +1,150 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import type { ColorModePreference } from '@/composables/useColorMode'
|
||||
import { colorModeOptions, useAccountMenu } from '@/composables/useAccountMenu'
|
||||
|
||||
const ITEM_SELECTOR = '[role="menuitem"], [role="menuitemcheckbox"]'
|
||||
const ITEM_CLASS = 'h-12 justify-start gap-3 px-4 text-[0.9375rem] text-highlighted'
|
||||
|
||||
const props = defineProps<{
|
||||
username: string
|
||||
displayName?: string
|
||||
email?: string
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
logout: []
|
||||
}>()
|
||||
|
||||
const open = defineModel<boolean>('open', { default: false })
|
||||
|
||||
const {
|
||||
colorMode,
|
||||
name,
|
||||
initials,
|
||||
onProfile,
|
||||
goToProfile: navigateToProfile,
|
||||
} = useAccountMenu(() => props)
|
||||
const menu = ref<HTMLElement | null>(null)
|
||||
|
||||
function goToProfile() {
|
||||
open.value = false
|
||||
navigateToProfile()
|
||||
}
|
||||
|
||||
function chooseColorMode(value: ColorModePreference) {
|
||||
colorMode.preference = value
|
||||
open.value = false
|
||||
}
|
||||
|
||||
function logOut() {
|
||||
open.value = false
|
||||
emit('logout')
|
||||
}
|
||||
|
||||
function moveFocus(event: KeyboardEvent) {
|
||||
const items = Array.from(menu.value?.querySelectorAll<HTMLElement>(ITEM_SELECTOR) ?? [])
|
||||
if (items.length === 0) {
|
||||
return
|
||||
}
|
||||
const current = items.indexOf(document.activeElement as HTMLElement)
|
||||
const lastIndex = items.length - 1
|
||||
const targets: Record<string, number> = {
|
||||
ArrowDown: current >= lastIndex ? 0 : current + 1,
|
||||
ArrowUp: current <= 0 ? lastIndex : current - 1,
|
||||
Home: 0,
|
||||
End: lastIndex,
|
||||
}
|
||||
const target = targets[event.key]
|
||||
if (target === undefined) {
|
||||
return
|
||||
}
|
||||
event.preventDefault()
|
||||
items[target]?.focus()
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<USlideover
|
||||
v-model:open="open"
|
||||
side="bottom"
|
||||
title="Account"
|
||||
description="Your account menu"
|
||||
:close="false"
|
||||
:ui="{
|
||||
content: 'max-h-none',
|
||||
body: 'p-0 sm:p-0',
|
||||
header: 'sr-only',
|
||||
}"
|
||||
>
|
||||
<template #body>
|
||||
<div ref="menu" role="menu" aria-label="Account" class="pt-2 pb-6" @keydown="moveFocus">
|
||||
<div class="mx-auto mb-2 h-1 w-9 bg-accented" aria-hidden="true" />
|
||||
<div class="mb-1 flex items-center gap-3 border-b border-default px-4 pt-2 pb-3.5">
|
||||
<UAvatar
|
||||
aria-hidden="true"
|
||||
:text="initials"
|
||||
:ui="{
|
||||
root: 'size-9 bg-secondary-300',
|
||||
fallback: 'text-[0.8125rem] font-semibold text-primary-900',
|
||||
}"
|
||||
/>
|
||||
<div class="flex min-w-0 flex-col">
|
||||
<span class="truncate text-[0.9375rem] font-semibold text-highlighted">{{
|
||||
name
|
||||
}}</span>
|
||||
<span class="truncate text-muted">{{ email }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<UButton
|
||||
role="menuitem"
|
||||
color="neutral"
|
||||
variant="ghost"
|
||||
block
|
||||
icon="i-lucide-circle-user"
|
||||
:class="[ITEM_CLASS, onProfile ? 'bg-elevated' : '']"
|
||||
:ui="{ leadingIcon: 'size-4.5' }"
|
||||
@click="goToProfile"
|
||||
>
|
||||
Profile
|
||||
</UButton>
|
||||
|
||||
<USeparator class="my-1" />
|
||||
|
||||
<UButton
|
||||
v-for="option in colorModeOptions"
|
||||
:key="option.value"
|
||||
role="menuitemcheckbox"
|
||||
color="neutral"
|
||||
variant="ghost"
|
||||
block
|
||||
:aria-checked="colorMode.preference === option.value"
|
||||
:trailing-icon="
|
||||
colorMode.preference === option.value ? 'i-lucide-check' : undefined
|
||||
"
|
||||
:class="ITEM_CLASS"
|
||||
:ui="{ trailingIcon: 'ms-auto size-4.5' }"
|
||||
@click="chooseColorMode(option.value)"
|
||||
>
|
||||
{{ option.label }}
|
||||
</UButton>
|
||||
|
||||
<USeparator class="my-1" />
|
||||
|
||||
<UButton
|
||||
role="menuitem"
|
||||
color="neutral"
|
||||
variant="ghost"
|
||||
block
|
||||
icon="i-lucide-log-out"
|
||||
:class="ITEM_CLASS"
|
||||
:ui="{ leadingIcon: 'size-4.5' }"
|
||||
@click="logOut"
|
||||
>
|
||||
Log out
|
||||
</UButton>
|
||||
</div>
|
||||
</template>
|
||||
</USlideover>
|
||||
</template>
|
||||
Reference in new issue
Block a user