feat(site): make TaskPrompt PromptTextarea read-only when submitting (#20363)

- Makes the text area of TaskPrompt read-only when submitting
- Adds a "scanning" animation to the textarea on submit.
This commit is contained in:
Cian Johnston
2025-10-17 18:11:31 +01:00
committed by GitHub
parent cfbbcfc65a
commit f6526b789a
3 changed files with 67 additions and 11 deletions
@@ -103,6 +103,35 @@ export const SubmitDisabledWhenPromptEmpty: Story = {
},
};
export const Submitting: Story = {
decorators: [withGlobalSnackbar],
beforeEach: () => {
spyOn(API.experimental, "createTask").mockImplementation(
() =>
// Never resolve to keep the component in the submitting state for visual testing.
new Promise(() => {}),
);
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const prompt = await canvas.findByLabelText(/prompt/i);
await userEvent.type(
prompt,
"Lorem ipsum dolor sit amet, consectetur adipiscing elit.{enter}{enter}Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
);
const submitButton = canvas.getByRole("button", { name: /run task/i });
await waitFor(() => expect(submitButton).toBeEnabled());
await userEvent.click(submitButton);
},
parameters: {
chromatic: {
disableSnapshot: true,
},
},
};
export const OnSuccess: Story = {
decorators: [withGlobalSnackbar],
parameters: {
@@ -259,6 +259,7 @@ const CreateTaskForm: FC<CreateTaskFormProps> = ({ templates, onSuccess }) => {
value={prompt}
onChange={(e) => setPrompt(e.target.value)}
readOnly={isPromptReadOnly}
isSubmitting={createTaskMutation.isPending}
/>
<div className="flex items-center justify-between pt-2">
<div className="flex items-center gap-1">
@@ -457,17 +458,35 @@ async function createTaskWithLatestTemplateVersion(
});
}
const PromptTextarea: FC<TextareaAutosizeProps> = (props) => {
type PromptTextareaProps = TextareaAutosizeProps & {
isSubmitting?: boolean;
};
const PromptTextarea: FC<PromptTextareaProps> = ({
isSubmitting,
...props
}) => {
return (
<TextareaAutosize
{...props}
required
id="prompt"
name="prompt"
placeholder="Prompt your AI agent to start a task..."
className={`border-0 px-3 py-2 resize-none w-full h-full bg-transparent rounded-lg
outline-none flex min-h-24 text-sm shadow-sm text-content-primary
placeholder:text-content-secondary md:text-sm ${props.readOnly ? "opacity-60 cursor-not-allowed" : ""}`}
/>
<div className="relative">
<TextareaAutosize
{...props}
required
id="prompt"
name="prompt"
placeholder="Prompt your AI agent to start a task..."
className={`border-0 px-3 py-2 resize-none w-full h-full bg-transparent rounded-lg
outline-none flex min-h-24 text-sm shadow-sm text-content-primary
placeholder:text-content-secondary md:text-sm ${props.readOnly || isSubmitting ? "opacity-60 cursor-not-allowed" : ""}`}
/>
{isSubmitting && (
<div className="absolute inset-0 pointer-events-none overflow-hidden">
<div
className={`absolute top-0 w-0.5 h-full
bg-green-400/90 animate-caret-scan rounded-sm
shadow-[-15px_0_15px_rgba(0,255,0,0.9),-30px_0_30px_rgba(0,255,0,0.7),-45px_0_45px_rgba(0,255,0,0.5),-60px_0_60px_rgba(0,255,0,0.3)]`}
/>
</div>
)}
</div>
);
};
+8
View File
@@ -83,6 +83,14 @@ module.exports = {
"75%": { opacity: 0.3 },
"100%": { opacity: 0.2 },
},
"caret-scan": {
"0%": { left: "0%" },
"100%": { left: "100%" },
},
},
animation: {
loading: "loading 2s ease-in-out infinite alternate",
"caret-scan": "caret-scan 3s ease-in-out infinite",
},
},
},