@@ -24,46 +24,115 @@ import { AIFeedback } from './ai-feedback';
2424import { AIGuideCue } from './ai-guide-cue' ;
2525
2626const containerStyles = css ( {
27- display : 'flex ' ,
27+ width : '100% ' ,
2828 flexDirection : 'column' ,
29- gap : spacing [ 1 ] ,
29+ gap : spacing [ 25 ] ,
3030 marginBottom : spacing [ 300 ] ,
3131} ) ;
3232
3333const inputBarContainerStyles = css ( {
34+ display : 'flex' ,
35+ width : '100%' ,
3436 paddingTop : spacing [ 100 ] ,
3537 gap : spacing [ 2 ] ,
36- flexGrow : 1 ,
38+ } ) ;
39+
40+ const gradientWidth = spacing [ 50 ] ;
41+ const gradientOffset = spacing [ 25 ] ;
42+
43+ const gradientAnimationStyles = css `
44+ & : before ,
45+ & : after {
46+ content : '' ;
47+ position : absolute;
48+ top : -${ gradientWidth + gradientOffset } px;
49+ left : -${ gradientWidth + gradientOffset } px;
50+ width : calc (100% + ${ ( gradientWidth + gradientOffset ) * 2 } px);
51+ height : calc (100% + ${ ( gradientWidth + gradientOffset ) * 2 } px);
52+ border-radius : 12px ;
53+ background-color : ${ palette . blue . light1 } ;
54+ background-size : 400% 400% ;
55+ background-position : 800% 800% ;
56+ }
57+
58+ & : after {
59+ animation : 4s animateBg linear;
60+ }
61+
62+ & : before {
63+ filter : blur (4px ) opacity (0.6 );
64+ animation : 4s animateBg, animateShadow linear infinite;
65+ opacity : 0 ;
66+ }
67+
68+ @keyframes animateBg {
69+ 0% {
70+ background-position : 400% 400% ;
71+ background-image : linear-gradient (
72+ 20deg ,
73+ ${ palette . blue . light1 } 0% ,
74+ ${ palette . blue . light1 } 30% ,
75+ # 00ede0 45% ,
76+ # 00ebc1 75% ,
77+ # 0498ec
78+ );
79+ }
80+ 100% {
81+ background-position : 0% 0% ;
82+ background-image : linear-gradient (
83+ 20deg ,
84+ ${ palette . blue . light1 } 0% ,
85+ ${ palette . blue . light1 } 30% ,
86+ # 00ede0 45% ,
87+ # 00ebc1 75% ,
88+ # 0498ec
89+ );
90+ }
91+ }
92+
93+ @keyframes animateShadow {
94+ 0% {
95+ opacity : 1 ;
96+ }
97+ 100% {
98+ opacity : 0 ;
99+ }
100+ }
101+ ` ;
102+
103+ const contentWrapperStyles = css ( {
104+ width : '100%' ,
37105 display : 'flex' ,
106+ flexDirection : 'column' ,
107+ position : 'relative' ,
108+ zIndex : 2 ,
38109} ) ;
39110
40111const inputContainerStyles = css ( {
41- display : 'flex' ,
42- flexGrow : 1 ,
112+ width : '100%' ,
43113 position : 'relative' ,
44114} ) ;
45115
46116const textInputStyles = css ( {
47117 flexGrow : 1 ,
48- // Override LeafyGreen input's padding to space for our robot.
49118 input : {
50- paddingLeft : spacing [ 5 ] ,
51- paddingRight : spacing [ 6 ] * 2 + spacing [ 2 ] ,
119+ paddingLeft : spacing [ 800 ] ,
120+ paddingRight : spacing [ 1600 ] * 2 + spacing [ 200 ] ,
52121 } ,
53122} ) ;
54123
55124const errorSummaryContainer = css ( {
56- marginTop : spacing [ 1 ] ,
125+ marginTop : spacing [ 100 ] ,
57126} ) ;
58127
59128const floatingButtonsContainerStyles = css ( {
60129 position : 'absolute' ,
61- right : spacing [ 1 ] ,
130+ right : spacing [ 100 ] ,
62131 display : 'flex' ,
63- gap : spacing [ 2 ] ,
132+ gap : spacing [ 200 ] ,
64133 alignItems : 'center' ,
65134 // Match the whole textbox.
66- height : spacing [ 4 ] + spacing [ 1 ] ,
135+ height : spacing [ 600 ] + spacing [ 100 ] ,
67136} ) ;
68137
69138const successIndicatorDarkModeStyles = css ( {
@@ -80,7 +149,7 @@ const successIndicatorLightModeStyles = css({
80149
81150const generateButtonStyles = css ( {
82151 border : 'none' ,
83- height : spacing [ 4 ] - spacing [ 1 ] ,
152+ height : spacing [ 600 ] - spacing [ 100 ] ,
84153 display : 'flex' ,
85154 fontSize : '12px' ,
86155 borderRadius : spacing [ 1 ] ,
@@ -96,7 +165,7 @@ const buttonHighlightStyles = css({
96165 // Custom button styles.
97166 height : `${ highlightSize } px` ,
98167 lineHeight : `${ highlightSize } px` ,
99- padding : `0px ${ spacing [ 1 ] } px` ,
168+ padding : `0px ${ spacing [ 100 ] } px` ,
100169 borderRadius : '2px' ,
101170} ) ;
102171
@@ -111,9 +180,9 @@ const buttonHighlightLightModeStyles = css({
111180} ) ;
112181
113182const loaderContainerStyles = css ( {
114- padding : spacing [ 1 ] ,
183+ padding : spacing [ 100 ] ,
115184 display : 'inline-flex' ,
116- width : DEFAULT_AI_ENTRY_SIZE + spacing [ 2 ] ,
185+ width : DEFAULT_AI_ENTRY_SIZE + spacing [ 200 ] ,
117186 justifyContent : 'space-around' ,
118187} ) ;
119188
@@ -126,10 +195,10 @@ const buttonResetStyles = css({
126195} ) ;
127196
128197const closeAIButtonStyles = css ( buttonResetStyles , focusRing , {
129- height : spacing [ 4 ] + spacing [ 1 ] ,
198+ height : spacing [ 600 ] + spacing [ 100 ] ,
130199 display : 'flex' ,
131200 alignItems : 'center' ,
132- padding : `${ spacing [ 1 ] } px ${ spacing [ 2 ] } px` ,
201+ padding : `${ spacing [ 100 ] } px ${ spacing [ 200 ] } px` ,
133202 position : 'absolute' ,
134203} ) ;
135204
@@ -261,102 +330,109 @@ function GenerativeAIInput({
261330 < div className = { containerStyles } >
262331 < div className = { inputBarContainerStyles } >
263332 < div className = { inputContainerStyles } >
264- < TextInput
265- className = { textInputStyles }
266- ref = { promptTextInputRef }
267- sizeVariant = "small"
268- data-testid = "ai-user-text-input"
269- aria-label = "Enter a plain text query that the AI will translate into MongoDB query language."
270- placeholder = { placeholder }
271- value = { aiPromptText }
272- onChange = { ( evt : React . ChangeEvent < HTMLInputElement > ) =>
273- onChangeAIPromptText ( evt . currentTarget . value )
274- }
275- onKeyDown = { onTextInputKeyDown }
276- />
277- < div className = { floatingButtonsContainerStyles } >
278- { isFetching ? (
279- < div className = { loaderContainerStyles } >
280- < SpinLoader />
281- </ div >
282- ) : showSuccess ? (
283- < div className = { loaderContainerStyles } >
284- < Icon
285- className = {
286- darkMode
287- ? successIndicatorDarkModeStyles
288- : successIndicatorLightModeStyles
289- }
290- glyph = "CheckmarkWithCircle"
291- />
292- </ div >
293- ) : (
294- < >
295- { aiPromptText && (
296- < IconButton
297- aria-label = "Clear prompt"
298- onClick = { ( ) => onChangeAIPromptText ( '' ) }
299- data-testid = "ai-text-clear-prompt"
300- >
301- < Icon glyph = "X" />
302- </ IconButton >
303- ) }
304- </ >
305- ) }
306- < Button
307- size = "small"
308- className = { cx (
309- generateButtonStyles ,
310- ! darkMode && generateButtonLightModeStyles
311- ) }
312- disabled = { ! aiPromptText }
313- data-testid = "ai-generate-button"
314- onClick = { ( ) =>
315- isFetching ? onCancelRequest ( ) : handleSubmit ( aiPromptText )
316- }
333+ < div className = { cx ( isFetching ? gradientAnimationStyles : null ) } >
334+ < div
335+ className = { contentWrapperStyles }
336+ data-testid = "ai-user-text-input-wrapper"
317337 >
318- { isFetching ? (
319- < >
320- < div > Cancel</ div >
321- < span
322- className = { cx (
323- buttonHighlightStyles ,
324- darkMode
325- ? buttonHighlightDarkModeStyles
326- : buttonHighlightLightModeStyles
338+ < TextInput
339+ className = { textInputStyles }
340+ ref = { promptTextInputRef }
341+ sizeVariant = "small"
342+ data-testid = "ai-user-text-input"
343+ aria-label = "Enter a plain text query that the AI will translate into MongoDB query language."
344+ placeholder = { placeholder }
345+ value = { aiPromptText }
346+ onChange = { ( evt : React . ChangeEvent < HTMLInputElement > ) =>
347+ onChangeAIPromptText ( evt . currentTarget . value )
348+ }
349+ onKeyDown = { onTextInputKeyDown }
350+ />
351+ < div className = { floatingButtonsContainerStyles } >
352+ { isFetching ? (
353+ < div className = { loaderContainerStyles } >
354+ < SpinLoader />
355+ </ div >
356+ ) : showSuccess ? (
357+ < div className = { loaderContainerStyles } >
358+ < Icon
359+ className = {
360+ darkMode
361+ ? successIndicatorDarkModeStyles
362+ : successIndicatorLightModeStyles
363+ }
364+ glyph = "CheckmarkWithCircle"
365+ />
366+ </ div >
367+ ) : (
368+ < >
369+ { aiPromptText && (
370+ < IconButton
371+ aria-label = "Clear prompt"
372+ onClick = { ( ) => onChangeAIPromptText ( '' ) }
373+ data-testid = "ai-text-clear-prompt"
374+ >
375+ < Icon glyph = "X" />
376+ </ IconButton >
327377 ) }
328- >
329- esc
330- </ span >
331- </ >
332- ) : (
333- < >
334- < div > Generate</ div >
335- < SubmitArrowSVG darkMode = { darkMode } />
336- </ >
337- ) }
338- </ Button >
378+ </ >
379+ ) }
380+ < Button
381+ size = "small"
382+ className = { cx (
383+ generateButtonStyles ,
384+ ! darkMode && generateButtonLightModeStyles
385+ ) }
386+ disabled = { ! aiPromptText }
387+ data-testid = "ai-generate-button"
388+ onClick = { ( ) =>
389+ isFetching ? onCancelRequest ( ) : handleSubmit ( aiPromptText )
390+ }
391+ >
392+ { isFetching ? (
393+ < >
394+ < div > Cancel</ div >
395+ < span
396+ className = { cx (
397+ buttonHighlightStyles ,
398+ darkMode
399+ ? buttonHighlightDarkModeStyles
400+ : buttonHighlightLightModeStyles
401+ ) }
402+ >
403+ esc
404+ </ span >
405+ </ >
406+ ) : (
407+ < >
408+ < div > Generate</ div >
409+ < SubmitArrowSVG darkMode = { darkMode } />
410+ </ >
411+ ) }
412+ </ Button >
413+ </ div >
414+ < button
415+ className = { closeAIButtonStyles }
416+ data-testid = "close-ai-button"
417+ aria-label = { closeText }
418+ title = { closeText }
419+ onClick = { ( ) => onClose ( ) }
420+ >
421+ < AIGuideCue
422+ showGuideCue = { isAggregationGeneratedFromQuery }
423+ onCloseGuideCue = { ( ) => {
424+ onResetIsAggregationGeneratedFromQuery ?.( ) ;
425+ } }
426+ refEl = { guideCueRef }
427+ title = "Aggregation generated"
428+ description = "Your query requires stages from MongoDB's aggregation framework. Continue to work on it in our Aggregation Pipeline Builder"
429+ />
430+ < span className = { aiEntryContainerStyles } ref = { guideCueRef } >
431+ < Icon glyph = "Sparkle" />
432+ </ span >
433+ </ button >
434+ </ div >
339435 </ div >
340- < button
341- className = { closeAIButtonStyles }
342- data-testid = "close-ai-button"
343- aria-label = { closeText }
344- title = { closeText }
345- onClick = { ( ) => onClose ( ) }
346- >
347- < AIGuideCue
348- showGuideCue = { isAggregationGeneratedFromQuery }
349- onCloseGuideCue = { ( ) => {
350- onResetIsAggregationGeneratedFromQuery ?.( ) ;
351- } }
352- refEl = { guideCueRef }
353- title = "Aggregation generated"
354- description = "Your query requires stages from MongoDB's aggregation framework. Continue to work on it in our Aggregation Pipeline Builder"
355- />
356- < span className = { aiEntryContainerStyles } ref = { guideCueRef } >
357- < Icon glyph = "Sparkle" />
358- </ span >
359- </ button >
360436 </ div >
361437 { didSucceed && onSubmitFeedback && (
362438 < AIFeedback onSubmitFeedback = { onSubmitFeedback } />
0 commit comments