From fa94b7c9a38c198d93db45cf7b4615e06b2cca1a Mon Sep 17 00:00:00 2001 From: Woody Date: Thu, 23 Apr 2026 11:23:08 +0800 Subject: [PATCH] feat(frontend): add QueryInput and KeywordsDisplay components with tests QueryInput: textarea with submit button, loading state, Enter-to-submit, clears on submit. KeywordsDisplay: keyword chips with loading skeletons, animated entrance. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- frontend/src/components/KeywordsDisplay.tsx | 46 +++++++ frontend/src/components/QueryInput.tsx | 49 ++++++++ .../test/components/KeywordsDisplay.test.tsx | 79 ++++++++++++ .../src/test/components/QueryInput.test.tsx | 114 ++++++++++++++++++ 4 files changed, 288 insertions(+) create mode 100644 frontend/src/components/KeywordsDisplay.tsx create mode 100644 frontend/src/components/QueryInput.tsx create mode 100644 frontend/src/test/components/KeywordsDisplay.test.tsx create mode 100644 frontend/src/test/components/QueryInput.test.tsx diff --git a/frontend/src/components/KeywordsDisplay.tsx b/frontend/src/components/KeywordsDisplay.tsx new file mode 100644 index 0000000..ba17383 --- /dev/null +++ b/frontend/src/components/KeywordsDisplay.tsx @@ -0,0 +1,46 @@ +import React from 'react' + +export interface KeywordsDisplayProps { + keywords?: string[] + isLoading: boolean +} + +export const KeywordsDisplay: React.FC = ({ keywords, isLoading }) => { + if (!isLoading && (!keywords || keywords.length === 0)) { + return null + } + + if (isLoading) { + return ( +
+ Extracted Keywords: +
+ {[1, 2, 3].map((i) => ( +
+ ))} +
+
+ ) + } + + return ( +
+ Extracted Keywords: +
+ {keywords?.map((keyword, index) => ( + + {keyword} + + ))} +
+
+ ) +} diff --git a/frontend/src/components/QueryInput.tsx b/frontend/src/components/QueryInput.tsx new file mode 100644 index 0000000..f2401bc --- /dev/null +++ b/frontend/src/components/QueryInput.tsx @@ -0,0 +1,49 @@ +import React, { useState, type FormEvent, type KeyboardEvent } from 'react' + +export interface QueryInputProps { + onSubmit: (question: string) => void + isLoading: boolean +} + +export const QueryInput: React.FC = ({ onSubmit, isLoading }) => { + const [question, setQuestion] = useState('') + + const handleSubmit = (e: FormEvent): void => { + e.preventDefault() + const trimmed = question.trim() + if (trimmed && !isLoading) { + onSubmit(trimmed) + setQuestion('') + } + } + + const handleKeyDown = (e: KeyboardEvent): void => { + if (e.key === 'Enter' && !e.shiftKey) { + e.preventDefault() + handleSubmit(e) + } + } + + const isDisabled = isLoading || question.trim() === '' + + return ( +
+