fix: language buttons real design / conformance
This commit is contained in:
1 parent
2c8f39273c
commit
0490a4c494
23 files changed
+1408
-412
No files matched your search
@@ -1,13 +1,17 @@
|
||||
<script setup lang="ts">
|
||||
import { t } from '@/i18n'
|
||||
import { locale, t } from '@/i18n'
|
||||
|
||||
import { computed, ref } from 'vue'
|
||||
import { useEventListener, useMediaQuery } from '@vueuse/core'
|
||||
import { useMediaQuery } from '@vueuse/core'
|
||||
import { colorModeOptions, useAccountMenu } from '@/composables/useAccountMenu'
|
||||
import LanguagePicker from '@/components/LanguagePicker.vue'
|
||||
import { chooseLanguage, languageOptions } from '@/composables/useLanguage'
|
||||
import { useAccountMenuRadios } from '@/composables/useRadioMenuRoles'
|
||||
|
||||
const DESKTOP_QUERY = '(min-width: 64rem)'
|
||||
const ITEM_CLASS = 'h-8 items-center py-0'
|
||||
const ITEM_CLASS = 'h-8 items-center gap-2.5 px-2 py-0 text-sm font-normal text-highlighted'
|
||||
const CHOICE_CLASS = 'h-8 items-center gap-2.5 px-2 py-0 text-base font-normal text-highlighted'
|
||||
const SECTION_LABEL_CLASS =
|
||||
'px-2 pt-1.5 pb-0.5 text-[0.6875rem] font-medium uppercase tracking-[0.04em] text-muted'
|
||||
|
||||
const props = defineProps<{
|
||||
username: string
|
||||
@@ -25,31 +29,8 @@ const emit = defineEmits<{
|
||||
const { colorMode, name, initials, onProfile, goToProfile } = useAccountMenu(() => props)
|
||||
const isDesktop = useMediaQuery(DESKTOP_QUERY)
|
||||
const open = ref(false)
|
||||
useAccountMenuRadios(open)
|
||||
const chip = ref<{ $el: HTMLElement } | null>(null)
|
||||
const languagePicker = ref<{ $el: HTMLElement } | null>(null)
|
||||
|
||||
function focusLanguage(event: KeyboardEvent) {
|
||||
const target = event.target
|
||||
if (!(target instanceof HTMLElement)) return
|
||||
if (target.closest('[role="menu"]') !== languagePicker.value?.$el.closest('[role="menu"]'))
|
||||
return
|
||||
if (target.closest('[role="combobox"]')) return
|
||||
const entries = Array.from(
|
||||
target
|
||||
.closest('[role="menu"]')
|
||||
?.querySelectorAll<HTMLElement>('[role="menuitem"], [role="menuitemcheckbox"]') ?? [],
|
||||
)
|
||||
const movesToLanguage =
|
||||
event.key === 'End' ||
|
||||
(event.key === 'ArrowDown' && target === entries[entries.length - 1]) ||
|
||||
(event.key === 'ArrowUp' && target === entries[0])
|
||||
if (movesToLanguage) {
|
||||
event.preventDefault()
|
||||
languagePicker.value?.$el.querySelector<HTMLElement>('[role="combobox"]')?.focus()
|
||||
}
|
||||
}
|
||||
|
||||
useEventListener(document, 'keydown', focusLanguage, { capture: true })
|
||||
|
||||
const chipActive = computed(() => open.value || onProfile.value)
|
||||
|
||||
@@ -94,18 +75,44 @@ const items = computed(() => [
|
||||
onSelect: goToProfile,
|
||||
},
|
||||
],
|
||||
colorModeOptions.map((option) => ({
|
||||
type: 'checkbox' as const,
|
||||
label: option.label,
|
||||
checked: colorMode.preference === option.value,
|
||||
class: ITEM_CLASS,
|
||||
onSelect: (event: Event) => event.preventDefault(),
|
||||
onUpdateChecked: (checked: boolean) => {
|
||||
if (checked) {
|
||||
colorMode.preference = option.value
|
||||
}
|
||||
[
|
||||
{
|
||||
type: 'label' as const,
|
||||
get label() {
|
||||
return t('Theme')
|
||||
},
|
||||
class: SECTION_LABEL_CLASS,
|
||||
},
|
||||
})),
|
||||
...colorModeOptions.map((option) => ({
|
||||
type: 'checkbox' as const,
|
||||
label: option.label,
|
||||
checked: colorMode.preference === option.value,
|
||||
class: CHOICE_CLASS,
|
||||
onSelect: (event: Event) => event.preventDefault(),
|
||||
onUpdateChecked: (checked: boolean) => {
|
||||
if (checked) {
|
||||
colorMode.preference = option.value
|
||||
}
|
||||
},
|
||||
})),
|
||||
],
|
||||
[
|
||||
{
|
||||
type: 'label' as const,
|
||||
get label() {
|
||||
return t('Language')
|
||||
},
|
||||
class: SECTION_LABEL_CLASS,
|
||||
},
|
||||
...languageOptions.map((option) => ({
|
||||
type: 'checkbox' as const,
|
||||
label: option.label,
|
||||
checked: locale.value === option.value,
|
||||
class: CHOICE_CLASS,
|
||||
onSelect: (event: Event) => event.preventDefault(),
|
||||
onUpdateChecked: () => chooseLanguage(option.value),
|
||||
})),
|
||||
],
|
||||
[
|
||||
{
|
||||
get label() {
|
||||
@@ -123,8 +130,12 @@ const items = computed(() => [
|
||||
<UDropdownMenu
|
||||
:open="open"
|
||||
:items="items"
|
||||
:content="{ side: 'top', align: 'start' }"
|
||||
:ui="{ content: 'w-56 p-1' }"
|
||||
:content="{ side: 'top', align: 'start', sideOffset: 4, loop: true }"
|
||||
:ui="{
|
||||
content: 'w-56 p-0 ring-0 border border-default',
|
||||
itemLeadingIcon: 'size-4 text-highlighted',
|
||||
itemTrailingIcon: 'size-4',
|
||||
}"
|
||||
@update:open="onUpdateOpen"
|
||||
>
|
||||
<UButton
|
||||
@@ -156,11 +167,8 @@ const items = computed(() => [
|
||||
<template #account-header>
|
||||
<span class="flex min-w-0 flex-col gap-0.5">
|
||||
<span class="truncate text-sm font-semibold text-highlighted">{{ name }}</span>
|
||||
<span class="truncate text-xs font-normal text-muted">{{ email }}</span>
|
||||
<span class="truncate text-xs/5 font-normal text-muted">{{ email }}</span>
|
||||
</span>
|
||||
</template>
|
||||
<template #content-bottom>
|
||||
<LanguagePicker ref="languagePicker" />
|
||||
</template>
|
||||
</UDropdownMenu>
|
||||
</template>
|
||||
Reference in new issue
Block a user