Enhances floating generate box UX

- Adds tooltips on hover for buttons of the generate box
- Replaces the message square icon with a sliders icon for the instruction mode toggle.
- Adds a tooltip to the instruction mode toggle button.
- Updates the placeholder text for the input field.
This commit is contained in:
Sergej Lopatkin
2026-02-02 22:19:54 +01:00
parent f5b6edc2e7
commit 6f4503b521
@@ -1,6 +1,6 @@
import { useMatchRoute } from '@tanstack/react-router'; import { useMatchRoute } from '@tanstack/react-router';
import { AnimatePresence, motion } from 'framer-motion'; import { AnimatePresence, motion } from 'framer-motion';
import { Loader2, MessageSquare, Sparkles } from 'lucide-react'; import { Loader2, SlidersHorizontal, Sparkles } from 'lucide-react';
import { useEffect, useRef, useState } from 'react'; import { useEffect, useRef, useState } from 'react';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Form, FormControl, FormField, FormItem, FormMessage } from '@/components/ui/form'; import { Form, FormControl, FormField, FormItem, FormMessage } from '@/components/ui/form';
@@ -187,7 +187,7 @@ export function FloatingGenerateBox({
}} }}
> >
<motion.div <motion.div
className="bg-background/30 backdrop-blur-2xl border border-accent/20 rounded-[2rem] shadow-2xl hover:bg-background/40 hover:border-accent/20 transition-all duration-300 overflow-hidden p-3" className="bg-background/30 backdrop-blur-2xl border border-accent/20 rounded-[2rem] shadow-2xl hover:bg-background/40 hover:border-accent/20 transition-all duration-300 p-3"
transition={{ duration: 0.6, ease: 'easeInOut' }} transition={{ duration: 0.6, ease: 'easeInOut' }}
> >
<Form {...form}> <Form {...form}>
@@ -274,7 +274,7 @@ export function FloatingGenerateBox({
field.ref(node); field.ref(node);
} }
}} }}
placeholder="Add delivery instructions..." placeholder="e.g. very happy and excited"
className="resize-none bg-transparent border-none focus-visible:ring-0 focus-visible:ring-offset-0 focus:outline-none focus:ring-0 outline-none ring-0 rounded-2xl text-sm placeholder:text-muted-foreground/60 w-full" className="resize-none bg-transparent border-none focus-visible:ring-0 focus-visible:ring-offset-0 focus:outline-none focus:ring-0 outline-none ring-0 rounded-2xl text-sm placeholder:text-muted-foreground/60 w-full"
style={{ style={{
minHeight: isExpanded ? '100px' : '32px', minHeight: isExpanded ? '100px' : '32px',
@@ -294,18 +294,27 @@ export function FloatingGenerateBox({
</motion.div> </motion.div>
<div className="relative shrink-0"> <div className="relative shrink-0">
<Button <div className="group relative">
type="submit" <Button
disabled={isPending || !selectedProfileId} type="submit"
className="h-10 w-10 rounded-full bg-accent hover:bg-accent/90 hover:scale-105 text-accent-foreground shadow-lg hover:shadow-accent/50 transition-all duration-200" disabled={isPending || !selectedProfileId}
size="icon" className="h-10 w-10 rounded-full bg-accent hover:bg-accent/90 hover:scale-105 text-accent-foreground shadow-lg hover:shadow-accent/50 transition-all duration-200"
> size="icon"
{isPending ? ( >
<Loader2 className="h-4 w-4 animate-spin" /> {isPending ? (
) : ( <Loader2 className="h-4 w-4 animate-spin" />
<Sparkles className="h-4 w-4" /> ) : (
)} <Sparkles className="h-4 w-4" />
</Button> )}
</Button>
<span className="pointer-events-none absolute bottom-full left-1/2 -translate-x-1/2 mb-2 whitespace-nowrap rounded-md bg-popover px-3 py-1.5 text-xs text-popover-foreground border border-border opacity-0 transition-opacity group-hover:opacity-100 z-[9999]">
{isPending
? 'Generating...'
: !selectedProfileId
? 'Select a voice profile first'
: 'Generate speech'}
</span>
</div>
<AnimatePresence> <AnimatePresence>
{isExpanded && ( {isExpanded && (
<motion.div <motion.div
@@ -315,20 +324,25 @@ export function FloatingGenerateBox({
transition={{ duration: 0.2 }} transition={{ duration: 0.2 }}
className="absolute top-0 right-[calc(100%+0.5rem)]" className="absolute top-0 right-[calc(100%+0.5rem)]"
> >
<Button <div className="group relative">
type="button" <Button
variant="ghost" type="button"
size="icon" variant="ghost"
onClick={() => setIsInstructMode(!isInstructMode)} size="icon"
className={cn( onClick={() => setIsInstructMode(!isInstructMode)}
'h-10 w-10 rounded-full transition-all duration-200', className={cn(
isInstructMode 'h-10 w-10 rounded-full transition-all duration-200',
? 'bg-accent text-accent-foreground border border-accent hover:bg-accent/90' isInstructMode
: 'bg-card border border-border hover:bg-background/50', ? 'bg-accent text-accent-foreground border border-accent hover:bg-accent/90'
)} : 'bg-card border border-border hover:bg-background/50',
> )}
<MessageSquare className="h-4 w-4" /> >
</Button> <SlidersHorizontal className="h-4 w-4" />
</Button>
<span className="pointer-events-none absolute bottom-full left-1/2 -translate-x-1/2 mb-2 whitespace-nowrap rounded-md bg-popover px-3 py-1.5 text-xs text-popover-foreground border border-border opacity-0 transition-opacity group-hover:opacity-100 z-[9999]">
Fine tune instructions
</span>
</div>
</motion.div> </motion.div>
)} )}
</AnimatePresence> </AnimatePresence>