@@ -12,7 +12,7 @@ import { useSearchParams } from "~/hooks/useSearchParam";
1212import { objectToSearchParams } from "~/utils/searchParams" ;
1313import { type TaskRunListSearchFilters } from "./RunFilters" ;
1414import { cn } from "~/utils/cn" ;
15- import { motion } from "framer-motion" ;
15+ import { motion , AnimatePresence } from "framer-motion" ;
1616import { Popover , PopoverContent , PopoverTrigger } from "~/components/primitives/Popover" ;
1717
1818type AIFilterResult =
@@ -37,20 +37,6 @@ export function AIFilterInput() {
3737 const inputRef = useRef < HTMLInputElement > ( null ) ;
3838 const fetcher = useFetcher < AIFilterResult > ( ) ;
3939
40- // Calculate position for error message
41- const [ errorPosition , setErrorPosition ] = useState ( { top : 0 , left : 0 , width : 0 } ) ;
42-
43- useEffect ( ( ) => {
44- if ( fetcher . data ?. success === false && inputRef . current ) {
45- const rect = inputRef . current . getBoundingClientRect ( ) ;
46- setErrorPosition ( {
47- top : rect . bottom + window . scrollY ,
48- left : rect . left + window . scrollX ,
49- width : rect . width ,
50- } ) ;
51- }
52- } , [ fetcher . data ?. success ] ) ;
53-
5440 useEffect ( ( ) => {
5541 if ( fetcher . data ?. success && fetcher . state === "loading" ) {
5642 // Clear the input after successful application
@@ -100,8 +86,19 @@ export function AIFilterInput() {
10086 stiffness : 300 ,
10187 damping : 30 ,
10288 } }
103- className = "animated-gradient-glow relative"
89+ className = "relative"
10490 >
91+ < AnimatePresence >
92+ { isFocused && (
93+ < motion . div
94+ initial = { { opacity : 0 } }
95+ animate = { { opacity : 1 } }
96+ exit = { { opacity : 0 } }
97+ transition = { { duration : 0.2 , ease : "linear" } }
98+ className = "animated-gradient-glow pointer-events-none absolute inset-0"
99+ />
100+ ) }
101+ </ AnimatePresence >
105102 < Input
106103 type = "text"
107104 name = "text"
@@ -113,9 +110,10 @@ export function AIFilterInput() {
113110 fullWidth
114111 ref = { inputRef }
115112 className = { cn (
116- "placeholder :text-text-bright " ,
113+ "disabled :text-text-dimmed/50 " ,
117114 isFocused && "placeholder:text-text-dimmed"
118115 ) }
116+ containerClassName = "has-[:disabled]:opacity-100"
119117 onKeyDown = { ( e ) => {
120118 if ( e . key === "Enter" && text . trim ( ) && ! isLoading ) {
121119 e . preventDefault ( ) ;
@@ -135,7 +133,13 @@ export function AIFilterInput() {
135133 icon = { < AISparkleIcon className = "size-4" /> }
136134 accessory = {
137135 isLoading ? (
138- < Spinner color = "muted" className = "size-4" />
136+ < Spinner
137+ color = { {
138+ background : "rgba(99, 102, 241, 1)" ,
139+ foreground : "rgba(217, 70, 239, 1)" ,
140+ } }
141+ className = "size-4"
142+ />
139143 ) : text . length > 0 ? (
140144 < ShortcutKey
141145 shortcut = { { key : "enter" } }
@@ -145,20 +149,6 @@ export function AIFilterInput() {
145149 ) : undefined
146150 }
147151 />
148- { fetcher . data ?. success === false && (
149- < Portal >
150- < div
151- className = "fixed z-[9999] rounded-md bg-rose-500 px-3 py-2 text-sm text-white shadow-lg"
152- style = { {
153- top : `${ errorPosition . top + 8 } px` ,
154- left : `${ errorPosition . left } px` ,
155- width : `${ errorPosition . width } px` ,
156- } }
157- >
158- { fetcher . data . error }
159- </ div >
160- </ Portal >
161- ) }
162152 </ motion . div >
163153 </ ErrorPopover >
164154 </ fetcher . Form >
@@ -168,7 +158,7 @@ export function AIFilterInput() {
168158function ErrorPopover ( {
169159 children,
170160 error,
171- durationMs = 2_000 ,
161+ durationMs = 10_000 ,
172162} : {
173163 children : React . ReactNode ;
174164 error ?: string ;
@@ -178,11 +168,14 @@ function ErrorPopover({
178168 const timeout = useRef < NodeJS . Timeout | undefined > ( ) ;
179169
180170 useEffect ( ( ) => {
171+ if ( error ) {
172+ setIsOpen ( true ) ;
173+ }
181174 if ( timeout . current ) {
182175 clearTimeout ( timeout . current ) ;
183176 }
184177 timeout . current = setTimeout ( ( ) => {
185- setIsOpen ( ( s ) => true ) ;
178+ setIsOpen ( false ) ;
186179 } , durationMs ) ;
187180
188181 return ( ) => {
@@ -193,9 +186,15 @@ function ErrorPopover({
193186 } , [ error , durationMs ] ) ;
194187
195188 return (
196- < Popover open = { isOpen } onOpenChange = { setIsOpen } >
189+ < Popover open = { isOpen } >
197190 < PopoverTrigger asChild > { children } </ PopoverTrigger >
198- < PopoverContent > { error } </ PopoverContent >
191+ < PopoverContent
192+ align = "start"
193+ side = "bottom"
194+ className = "w-[var(--radix-popover-trigger-width)] min-w-[var(--radix-popover-trigger-width)] max-w-[var(--radix-popover-trigger-width)] border border-error/20 bg-[#2F1D24] px-3 py-2 text-xs text-text-dimmed"
195+ >
196+ { error }
197+ </ PopoverContent >
199198 </ Popover >
200199 ) ;
201200}
0 commit comments