fix(stage-layouts): replace Tooltip with Popover for microphone controls (#1169)
* refactor(ChatArea): replace Tooltip with Popover for microphone controls The Tooltip component was replaced with Popover to provide better user experience and more consistent behavior with other UI elements. The functionality remains the same but uses the more appropriate Popover component from the UI library. * refactor(ChatArea): rename hearingTooltipOpen to hearingPopoverOpen for clarity The variable name was changed to better reflect its actual usage with PopoverRoot component. This improves code readability and maintainability by using more accurate terminology. --------- Co-authored-by: Rin <shinohara-rin@users.noreply.github.com>
This commit is contained in:
@@ -13,14 +13,14 @@ import { useSettings, useSettingsAudioDevice } from '@proj-airi/stage-ui/stores/
|
||||
import { BasicTextarea, FieldSelect } from '@proj-airi/ui'
|
||||
import { until } from '@vueuse/core'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { TooltipContent, TooltipProvider, TooltipRoot, TooltipTrigger } from 'reka-ui'
|
||||
import { PopoverContent, PopoverRoot, PopoverTrigger } from 'reka-ui'
|
||||
import { computed, nextTick, onUnmounted, ref, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import IndicatorMicVolume from './IndicatorMicVolume.vue'
|
||||
|
||||
const messageInput = ref('')
|
||||
const hearingTooltipOpen = ref(false)
|
||||
const hearingPopoverOpen = ref(false)
|
||||
const isComposing = ref(false)
|
||||
const isListening = ref(false) // Transcription listening state (separate from microphone enabled)
|
||||
|
||||
@@ -128,7 +128,7 @@ async function handleSend() {
|
||||
}
|
||||
}
|
||||
|
||||
watch(hearingTooltipOpen, async (value) => {
|
||||
watch(hearingPopoverOpen, async (value) => {
|
||||
if (value) {
|
||||
await askPermission()
|
||||
}
|
||||
@@ -158,7 +158,7 @@ function teardownAnalyzer() {
|
||||
|
||||
async function setupAnalyzer() {
|
||||
teardownAnalyzer()
|
||||
if (!hearingTooltipOpen.value || !enabled.value || !stream.value)
|
||||
if (!hearingPopoverOpen.value || !enabled.value || !stream.value)
|
||||
return
|
||||
if (audioContext.state === 'suspended')
|
||||
await audioContext.resume()
|
||||
@@ -169,7 +169,7 @@ async function setupAnalyzer() {
|
||||
analyzerSource.connect(analyser)
|
||||
}
|
||||
|
||||
watch([hearingTooltipOpen, enabled, stream], () => {
|
||||
watch([hearingPopoverOpen, enabled, stream], () => {
|
||||
setupAnalyzer()
|
||||
}, { immediate: true })
|
||||
|
||||
@@ -427,74 +427,70 @@ watch(autoSendEnabled, (enabled) => {
|
||||
absolute bottom-2 left-2 z-10 flex items-center gap-2
|
||||
>
|
||||
<!-- Microphone icon button -->
|
||||
<TooltipProvider :delay-duration="0" :skip-delay-duration="0">
|
||||
<TooltipRoot v-model:open="hearingTooltipOpen">
|
||||
<TooltipTrigger as-child>
|
||||
<button
|
||||
class="h-8 w-8 flex items-center justify-center rounded-md outline-none transition-all duration-200 active:scale-95"
|
||||
text="lg neutral-500 dark:neutral-400"
|
||||
:title="t('settings.hearing.title')"
|
||||
>
|
||||
<Transition name="fade" mode="out-in">
|
||||
<IndicatorMicVolume v-if="enabled" class="h-5 w-5" />
|
||||
<div v-else class="i-ph:microphone-slash h-5 w-5" />
|
||||
</Transition>
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<Transition name="fade">
|
||||
<TooltipContent
|
||||
side="top"
|
||||
:side-offset="8"
|
||||
:class="[
|
||||
'w-72 max-w-[18rem] rounded-xl border border-neutral-200/60 bg-neutral-50/90 p-4',
|
||||
'shadow-lg backdrop-blur-md dark:border-neutral-800/30 dark:bg-neutral-900/80',
|
||||
'flex flex-col gap-3',
|
||||
]"
|
||||
>
|
||||
<div class="flex flex-col items-center justify-center">
|
||||
<div class="relative h-28 w-28 select-none">
|
||||
<div
|
||||
class="absolute left-1/2 top-1/2 h-20 w-20 rounded-full transition-all duration-150 -translate-x-1/2 -translate-y-1/2"
|
||||
:style="{ transform: `translate(-50%, -50%) scale(${1 + normalizedVolume * 0.35})`, opacity: String(0.25 + normalizedVolume * 0.25) }"
|
||||
:class="enabled ? 'bg-primary-500/15 dark:bg-primary-600/20' : 'bg-neutral-300/20 dark:bg-neutral-700/20'"
|
||||
/>
|
||||
<div
|
||||
class="absolute left-1/2 top-1/2 h-24 w-24 rounded-full transition-all duration-200 -translate-x-1/2 -translate-y-1/2"
|
||||
:style="{ transform: `translate(-50%, -50%) scale(${1.2 + normalizedVolume * 0.55})`, opacity: String(0.15 + normalizedVolume * 0.2) }"
|
||||
:class="enabled ? 'bg-primary-500/10 dark:bg-primary-600/15' : 'bg-neutral-300/10 dark:bg-neutral-700/10'"
|
||||
/>
|
||||
<div
|
||||
class="absolute left-1/2 top-1/2 h-28 w-28 rounded-full transition-all duration-300 -translate-x-1/2 -translate-y-1/2"
|
||||
:style="{ transform: `translate(-50%, -50%) scale(${1.5 + normalizedVolume * 0.8})`, opacity: String(0.08 + normalizedVolume * 0.15) }"
|
||||
:class="enabled ? 'bg-primary-500/5 dark:bg-primary-600/10' : 'bg-neutral-300/5 dark:bg-neutral-700/5'"
|
||||
/>
|
||||
<button
|
||||
class="absolute left-1/2 top-1/2 grid h-16 w-16 place-items-center rounded-full shadow-md outline-none transition-all duration-200 -translate-x-1/2 -translate-y-1/2"
|
||||
:class="enabled
|
||||
? 'bg-primary-500 text-white hover:bg-primary-600 active:scale-95'
|
||||
: 'bg-neutral-200 text-neutral-600 hover:bg-neutral-300 active:scale-95 dark:bg-neutral-700 dark:text-neutral-200'"
|
||||
@click="enabled = !enabled"
|
||||
>
|
||||
<div :class="enabled ? 'i-ph:microphone' : 'i-ph:microphone-slash'" class="h-6 w-6" />
|
||||
</button>
|
||||
</div>
|
||||
<p class="mt-3 text-xs text-neutral-500 dark:text-neutral-400">
|
||||
{{ enabled ? 'Microphone enabled' : 'Microphone disabled' }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<FieldSelect
|
||||
v-model="selectedAudioInput"
|
||||
label="Input device"
|
||||
description="Select the microphone you want to use."
|
||||
:options="audioInputs.map(device => ({ label: device.label || 'Unknown Device', value: device.deviceId }))"
|
||||
layout="vertical"
|
||||
placeholder="Select microphone"
|
||||
<PopoverRoot v-model:open="hearingPopoverOpen">
|
||||
<PopoverTrigger as-child>
|
||||
<button
|
||||
class="h-8 w-8 flex items-center justify-center rounded-md outline-none transition-all duration-200 active:scale-95"
|
||||
text="lg neutral-500 dark:neutral-400"
|
||||
:title="t('settings.hearing.title')"
|
||||
>
|
||||
<Transition name="fade" mode="out-in">
|
||||
<IndicatorMicVolume v-if="enabled" class="h-5 w-5" />
|
||||
<div v-else class="i-ph:microphone-slash h-5 w-5" />
|
||||
</Transition>
|
||||
</button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
side="top"
|
||||
:side-offset="8"
|
||||
:class="[
|
||||
'w-72 max-w-[18rem] rounded-xl border border-neutral-200/60 bg-neutral-50/90 p-4',
|
||||
'shadow-lg backdrop-blur-md dark:border-neutral-800/30 dark:bg-neutral-900/80',
|
||||
'flex flex-col gap-3',
|
||||
]"
|
||||
>
|
||||
<div class="flex flex-col items-center justify-center">
|
||||
<div class="relative h-28 w-28 select-none">
|
||||
<div
|
||||
class="absolute left-1/2 top-1/2 h-20 w-20 rounded-full transition-all duration-150 -translate-x-1/2 -translate-y-1/2"
|
||||
:style="{ transform: `translate(-50%, -50%) scale(${1 + normalizedVolume * 0.35})`, opacity: String(0.25 + normalizedVolume * 0.25) }"
|
||||
:class="enabled ? 'bg-primary-500/15 dark:bg-primary-600/20' : 'bg-neutral-300/20 dark:bg-neutral-700/20'"
|
||||
/>
|
||||
</TooltipContent>
|
||||
</Transition>
|
||||
</TooltipRoot>
|
||||
</TooltipProvider>
|
||||
<div
|
||||
class="absolute left-1/2 top-1/2 h-24 w-24 rounded-full transition-all duration-200 -translate-x-1/2 -translate-y-1/2"
|
||||
:style="{ transform: `translate(-50%, -50%) scale(${1.2 + normalizedVolume * 0.55})`, opacity: String(0.15 + normalizedVolume * 0.2) }"
|
||||
:class="enabled ? 'bg-primary-500/10 dark:bg-primary-600/15' : 'bg-neutral-300/10 dark:bg-neutral-700/10'"
|
||||
/>
|
||||
<div
|
||||
class="absolute left-1/2 top-1/2 h-28 w-28 rounded-full transition-all duration-300 -translate-x-1/2 -translate-y-1/2"
|
||||
:style="{ transform: `translate(-50%, -50%) scale(${1.5 + normalizedVolume * 0.8})`, opacity: String(0.08 + normalizedVolume * 0.15) }"
|
||||
:class="enabled ? 'bg-primary-500/5 dark:bg-primary-600/10' : 'bg-neutral-300/5 dark:bg-neutral-700/5'"
|
||||
/>
|
||||
<button
|
||||
class="absolute left-1/2 top-1/2 grid h-16 w-16 place-items-center rounded-full shadow-md outline-none transition-all duration-200 -translate-x-1/2 -translate-y-1/2"
|
||||
:class="enabled
|
||||
? 'bg-primary-500 text-white hover:bg-primary-600 active:scale-95'
|
||||
: 'bg-neutral-200 text-neutral-600 hover:bg-neutral-300 active:scale-95 dark:bg-neutral-700 dark:text-neutral-200'"
|
||||
@click="enabled = !enabled"
|
||||
>
|
||||
<div :class="enabled ? 'i-ph:microphone' : 'i-ph:microphone-slash'" class="h-6 w-6" />
|
||||
</button>
|
||||
</div>
|
||||
<p class="mt-3 text-xs text-neutral-500 dark:text-neutral-400">
|
||||
{{ enabled ? 'Microphone enabled' : 'Microphone disabled' }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<FieldSelect
|
||||
v-model="selectedAudioInput"
|
||||
label="Input device"
|
||||
description="Select the microphone you want to use."
|
||||
:options="audioInputs.map(device => ({ label: device.label || 'Unknown Device', value: device.deviceId }))"
|
||||
layout="vertical"
|
||||
placeholder="Select microphone"
|
||||
/>
|
||||
</PopoverContent>
|
||||
</PopoverRoot>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user