<script setup lang="ts">import { AnimatePresence, motion } from 'motion-v'import { useDocusI18n } from '../../../../app/composables/useDocusI18n' const route = useRoute()const appConfig = useAppConfig()const { open, isOpen, isStudioExpanded } = useAssistant()const { t } = useDocusI18n()const input = ref('')const isVisible = ref(true)const inputRef = ref<{ inputRef: HTMLInputElement } | null>(null) const isDocsRoute = computed(() => route.meta.layout === 'docs')const isFloatingInputEnabled = computed(() => appConfig.assistant?.floatingInput !== false)const focusInputShortcut = computed(() => appConfig.assistant?.shortcuts?.focusInput || 'meta_i')const placeholder = computed(() => t('assistant.placeholder')) const shortcutDisplayKeys = computed(() => { const shortcut = focusInputShortcut.value const parts = shortcut.split('_') return parts.map((part: string) => part === 'meta' ? 'meta' : part.toUpperCase())}) function handleSubmit() { if (!input.value.trim()) return const message = input.value isVisible.value = false setTimeout(() => { open(message, true) input.value = '' isVisible.value = true }, 200)} const shortcuts = computed(() => ({ [focusInputShortcut.value]: { usingInput: true, handler: () => { if (!isDocsRoute.value || !isFloatingInputEnabled.value) return inputRef.value?.inputRef?.focus() }, },})) defineShortcuts(shortcuts) function onEscape() { inputRef.value?.inputRef?.blur()} function onKeydown(event: KeyboardEvent) { if (event.key === 'Enter' && !event.shiftKey && !event.altKey && !event.ctrlKey && !event.metaKey) { event.preventDefault() handleSubmit() return } if (event.key === 'Escape') { onEscape() }}</script> <template> <AnimatePresence> <motion.div v-if="isFloatingInputEnabled && isDocsRoute && isVisible && !isOpen && !isStudioExpanded" key="floating-input" :initial="{ y: 20, opacity: 0 }" :animate="{ y: 0, opacity: 1 }" :exit="{ y: 100, opacity: 0 }" :transition="{ duration: 0.2, ease: 'easeOut' }" class="pointer-events-none fixed inset-x-0 z-10 bottom-[max(1.5rem,env(safe-area-inset-bottom))] px-4 sm:px-24" style="will-change: transform" > <form class="pointer-events-none flex w-full min-w-3xs justify-center" @submit.prevent="handleSubmit" > <div class="pointer-events-auto w-full max-w-96"> <UInput ref="inputRef" v-model="input" :placeholder="placeholder" size="lg" maxlength="1000" :ui="{ root: 'group w-full! min-w-0 sm:max-w-96 transition-all duration-300 ease-out [@media(hover:hover)]:hover:scale-105 [@media(hover:hover)]:focus-within:scale-105', base: 'bg-default shadow-lg rounded-xl text-base', trailing: 'pe-2', }" @keydown="onKeydown" > <template #trailing> <div class="flex items-center gap-2"> <div class="hidden sm:flex group-focus-within:hidden items-center gap-1"> <UKbd v-for="key in shortcutDisplayKeys" :key="key" :value="key" /> </div> <UButton type="submit" icon="i-lucide-arrow-up" color="primary" size="xs" :disabled="!input.trim()" /> </div> </template> </UInput> </div> </form> </motion.div> </AnimatePresence></template>
<script setup lang="ts">
import { AnimatePresence, motion } from 'motion-v'
import { useDocusI18n } from '../../../../app/composables/useDocusI18n'
const route = useRoute()
const appConfig = useAppConfig()
const { open, isOpen, isStudioExpanded } = useAssistant()
const { t } = useDocusI18n()
const input = ref('')
const isVisible = ref(true)
const inputRef = ref<{ inputRef: HTMLInputElement } | null>(null)
const isDocsRoute = computed(() => route.meta.layout === 'docs')
const isFloatingInputEnabled = computed(() => appConfig.assistant?.floatingInput !== false)
const focusInputShortcut = computed(() => appConfig.assistant?.shortcuts?.focusInput || 'meta_i')
const placeholder = computed(() => t('assistant.placeholder'))
const shortcutDisplayKeys = computed(() => {
const shortcut = focusInputShortcut.value
const parts = shortcut.split('_')
return parts.map((part: string) => part === 'meta' ? 'meta' : part.toUpperCase())
})
function handleSubmit() {
if (!input.value.trim()) return
const message = input.value
isVisible.value = false
setTimeout(() => {
open(message, true)
input.value = ''
isVisible.value = true
}, 200)
}
const shortcuts = computed(() => ({
[focusInputShortcut.value]: {
usingInput: true,
handler: () => {
if (!isDocsRoute.value || !isFloatingInputEnabled.value) return
inputRef.value?.inputRef?.focus()
},
},
}))
defineShortcuts(shortcuts)
function onEscape() {
inputRef.value?.inputRef?.blur()
}
function onKeydown(event: KeyboardEvent) {
if (event.key === 'Enter' && !event.shiftKey && !event.altKey && !event.ctrlKey && !event.metaKey) {
event.preventDefault()
handleSubmit()
return
}
if (event.key === 'Escape') {
onEscape()
}
}
</script>
<template>
<AnimatePresence>
<motion.div
v-if="isFloatingInputEnabled && isDocsRoute && isVisible && !isOpen && !isStudioExpanded"
key="floating-input"
:initial="{ y: 20, opacity: 0 }"
:animate="{ y: 0, opacity: 1 }"
:exit="{ y: 100, opacity: 0 }"
:transition="{ duration: 0.2, ease: 'easeOut' }"
class="pointer-events-none fixed inset-x-0 z-10 bottom-[max(1.5rem,env(safe-area-inset-bottom))] px-4 sm:px-24"
style="will-change: transform"
>
<form
class="pointer-events-none flex w-full min-w-3xs justify-center"
@submit.prevent="handleSubmit"
>
<div class="pointer-events-auto w-full max-w-96">
<UInput
ref="inputRef"
v-model="input"
:placeholder="placeholder"
size="lg"
maxlength="1000"
:ui="{
root: 'group w-full! min-w-0 sm:max-w-96 transition-all duration-300 ease-out [@media(hover:hover)]:hover:scale-105 [@media(hover:hover)]:focus-within:scale-105',
base: 'bg-default shadow-lg rounded-xl text-base',
trailing: 'pe-2',
}"
@keydown="onKeydown"
>
<template #trailing>
<div class="flex items-center gap-2">
<div class="hidden sm:flex group-focus-within:hidden items-center gap-1">
<UKbd
v-for="key in shortcutDisplayKeys"
:key="key"
:value="key"
/>
</div>
<UButton
type="submit"
icon="i-lucide-arrow-up"
color="primary"
size="xs"
:disabled="!input.trim()"
/>
</div>
</template>
</UInput>
</div>
</form>
</motion.div>
</AnimatePresence>
</template>