diff --git a/src/Explorer/Controls/ThroughputInput/ThroughputInput.less b/src/Explorer/Controls/ThroughputInput/ThroughputInput.less index 059b210f6..28e9e3a71 100644 --- a/src/Explorer/Controls/ThroughputInput/ThroughputInput.less +++ b/src/Explorer/Controls/ThroughputInput/ThroughputInput.less @@ -20,7 +20,7 @@ outline-offset: 2px; } -.outlineNone{ +.outlineNone { outline: none !important; } @@ -28,3 +28,8 @@ .deleteQuery:focus::after { outline: none !important; } + +.highlightedButtonStyles { + outline: 1px dashed back; + background-color: #ebebeb; +} diff --git a/src/Explorer/QueryCopilot/QueryCopilotPromptbar.tsx b/src/Explorer/QueryCopilot/QueryCopilotPromptbar.tsx index 2bd61a916..f45c808fa 100644 --- a/src/Explorer/QueryCopilot/QueryCopilotPromptbar.tsx +++ b/src/Explorer/QueryCopilot/QueryCopilotPromptbar.tsx @@ -34,12 +34,13 @@ import { SamplePrompts, SamplePromptsProps } from "Explorer/QueryCopilot/Shared/ import { Action } from "Shared/Telemetry/TelemetryConstants"; import { userContext } from "UserContext"; import { useQueryCopilot } from "hooks/useQueryCopilot"; -import React, { useRef, useState } from "react"; +import React, { useCallback, useRef, useState } from "react"; import HintIcon from "../../../images/Hint.svg"; import RecentIcon from "../../../images/Recent.svg"; import errorIcon from "../../../images/close-black.svg"; import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor"; import { useTabs } from "../../hooks/useTabs"; +import "../Controls/ThroughputInput/ThroughputInput.less"; import { useCopilotStore } from "../QueryCopilot/QueryCopilotContext"; import { useSelectedNode } from "../useSelectedNode"; @@ -108,7 +109,7 @@ export const QueryCopilotPromptbar: React.FC = ({ setErrorMessage, errorMessage, } = useCopilotStore(); - + const [focusedIndex, setFocusedIndex] = useState(-1); const sampleProps: SamplePromptsProps = { isSamplePromptsOpen: isSamplePromptsOpen, setIsSamplePromptsOpen: setIsSamplePromptsOpen, @@ -301,6 +302,41 @@ export const QueryCopilotPromptbar: React.FC = ({ return "Content is updated"; } }; + const openSamplePrompts = () => { + inputEdited.current = true; + setShowSamplePrompts(true); + }; + const suggestionHistory = filteredHistories.concat(filteredSuggestedPrompts.map((item) => item.text)); + const handleKeyDown: React.KeyboardEventHandler = (e) => { + const { key } = e; + if (key === "ArrowDown") { + setFocusedIndex((focusedIndex + 1) % suggestionHistory.length); + } + if (key === "ArrowUp") { + setFocusedIndex((focusedIndex + suggestionHistory.length - 1) % suggestionHistory.length); + } + if (key === "Enter") { + e.preventDefault(); + handleSelection(focusedIndex); + } + }; + const handleSelection = (selectedIndex: number) => { + const selectedItem = suggestionHistory[selectedIndex]; + if (!selectedItem) { + return resetSearchComplete(); + } else { + handlePromptSet(selectedItem); + } + }; + const resetSearchComplete = useCallback(() => { + setFocusedIndex(-1); + setShowSamplePrompts(false); + }, []); + + const handlePromptSet = (promptText: string) => { + inputEdited.current = true; + setUserPrompt(promptText); + }; React.useEffect(() => { useTabs.getState().setIsQueryErrorThrown(false); @@ -331,23 +367,13 @@ export const QueryCopilotPromptbar: React.FC = ({ id="naturalLanguageInput" value={userPrompt} onChange={handleUserPromptChange} - onClick={() => { - inputEdited.current = true; - setShowSamplePrompts(true); - }} - onKeyDown={(e) => { - if (e.key === "Enter" && userPrompt) { - inputEdited.current = true; - startGenerateQueryProcess(); - } - }} + onClick={openSamplePrompts} + onFocus={() => setShowSamplePrompts(true)} + onKeyDown={handleKeyDown} style={{ lineHeight: 30 }} styles={{ root: { width: "100%" }, - suffix: { - background: "none", - padding: 0, - }, + suffix: { background: "none", padding: 0 }, fieldGroup: { borderRadius: 4, borderColor: "#D1D1D1", @@ -360,7 +386,8 @@ export const QueryCopilotPromptbar: React.FC = ({ }, }} disabled={isGeneratingQuery} - autoComplete="off" + autoComplete="list" + aria-expanded={showSamplePrompts} placeholder="Ask a question in natural language and we’ll generate the query for you." aria-labelledby="copilot-textfield-label" onRenderSuffix={() => { @@ -369,7 +396,10 @@ export const QueryCopilotPromptbar: React.FC = ({ iconProps={{ iconName: "Send" }} disabled={isGeneratingQuery || !userPrompt.trim()} style={{ background: "none" }} - onClick={() => startGenerateQueryProcess()} + onClick={() => { + startGenerateQueryProcess(); + setShowSamplePrompts(false); + }} aria-label="Send" /> ); @@ -434,6 +464,7 @@ export const QueryCopilotPromptbar: React.FC = ({ }} onRenderIcon={() => } styles={promptStyles} + className={focusedIndex === i ? "highlightedButtonStyles" : "buttonstyles"} > {history} @@ -454,7 +485,7 @@ export const QueryCopilotPromptbar: React.FC = ({ > Suggested Prompts - {filteredSuggestedPrompts.map((prompt) => ( + {filteredSuggestedPrompts.map((prompt, index) => ( { @@ -464,6 +495,7 @@ export const QueryCopilotPromptbar: React.FC = ({ }} onRenderIcon={() => } styles={promptStyles} + className={focusedIndex === filteredHistories.length + index ? "highlightedButtonStyles" : ""} > {prompt.text} @@ -720,4 +752,4 @@ export const QueryCopilotPromptbar: React.FC = ({ ); -}; \ No newline at end of file +};