* feat(web): add Mantine clear buttons to text inputs app-wide (#1333) Add a clear (×) affordance to every user-editable text input across the web client so a populated field can be reset in one click. - Autocomplete: enable `clearable` by default via the theme (`theme/Autocomplete.ts`) — covers the prompt-argument and resource-template variable autocomplete branches. - TextInput / Textarea: Mantine v8 does not expose a public `clearable` prop on these primitives (only the combobox-based components do), so add the clear button manually via `rightSection` + `CloseButton` (aria-label="Clear") shown only when the field has a value, wired to reset the field's controlled value to "". Applied to sidebar search inputs (prompts, tools, apps, tasks, history, resources, network, logs), prompt-argument and resource-template variable inputs, the sampling response/model fields, server config & settings forms, roots table, schema form, experimental features panel, and the server.json import panel. Read-only/display Textareas (OutputValidationModal, UrlElicitationErrorModal) are intentionally left untouched. Tests cover the clear behavior on representative inputs from each category (sidebar search, prompt argument, resource-template variable, sampling response, plus the config/settings forms). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(web): disable ServerConfigModal clear buttons while submitting Address review #2 on #1448: the clear CloseButton sat in `rightSection` with `rightSectionPointerEvents="auto"`, so it stayed clickable even while the field was `disabled={submitting}`, letting a user wipe a field mid-submit. Forward `disabled={submitting}` to each clear button so it locks together with its input. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(web): remove redundant Auto-respond from SamplingRequestPanel The sampling modal pre-loads its draft with the default stub result, and "Send Response" sends that draft — so on an untouched form "Auto-respond" did exactly the same thing as "Send Response", just with a fresh copy of the same defaults. Drop the redundant button and its `onAutoRespond` prop; "Send Response" now covers both the unedited (stub) and edited cases. The InlineSamplingRequest variant keeps its Auto-respond button — there it is the quick path versus "Edit & Send" (which opens this editable modal), not a duplicate of an adjacent send action. Updated PendingClientRequestModal wiring, stories, and tests accordingly. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
62d442b073
commit
0890c7509a
33 changed files with 805 additions and 28 deletions
|
|
@ -659,7 +659,7 @@ describe("App pending server-initiated request modal", () => {
|
|||
|
||||
// Resolving via the modal calls the queued request's respond() — this is
|
||||
// what unblocks the originating call (the "spinner clears" criterion).
|
||||
await user.click(screen.getByRole("button", { name: "Auto-respond" }));
|
||||
await user.click(screen.getByRole("button", { name: "Send Response" }));
|
||||
expect(respond).toHaveBeenCalledTimes(1);
|
||||
|
||||
// The client clearing its queue (empty event) closes the modal.
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import {
|
||||
CloseButton,
|
||||
Group,
|
||||
ScrollArea,
|
||||
Stack,
|
||||
|
|
@ -61,6 +62,15 @@ export function AppControls({
|
|||
placeholder="Search apps..."
|
||||
value={searchText}
|
||||
onChange={(e) => onSearchChange(e.currentTarget.value)}
|
||||
rightSectionPointerEvents="auto"
|
||||
rightSection={
|
||||
searchText ? (
|
||||
<CloseButton
|
||||
aria-label="Clear"
|
||||
onClick={() => onSearchChange("")}
|
||||
/>
|
||||
) : null
|
||||
}
|
||||
/>
|
||||
<ScrollArea.Autosize viewportRef={viewportRef} mah={LIST_MAX_HEIGHT}>
|
||||
<Stack gap="xs">
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import {
|
|||
Button,
|
||||
Card,
|
||||
Checkbox,
|
||||
CloseButton,
|
||||
Divider,
|
||||
Group,
|
||||
Stack,
|
||||
|
|
@ -244,6 +245,15 @@ export function ExperimentalFeaturesPanel({
|
|||
onChange={(e) =>
|
||||
onHeaderChange(index, e.currentTarget.value, header.value)
|
||||
}
|
||||
rightSectionPointerEvents="auto"
|
||||
rightSection={
|
||||
header.key ? (
|
||||
<CloseButton
|
||||
aria-label="Clear"
|
||||
onClick={() => onHeaderChange(index, "", header.value)}
|
||||
/>
|
||||
) : null
|
||||
}
|
||||
/>
|
||||
<TextInput
|
||||
placeholder="Header value"
|
||||
|
|
@ -251,6 +261,15 @@ export function ExperimentalFeaturesPanel({
|
|||
onChange={(e) =>
|
||||
onHeaderChange(index, header.key, e.currentTarget.value)
|
||||
}
|
||||
rightSectionPointerEvents="auto"
|
||||
rightSection={
|
||||
header.value ? (
|
||||
<CloseButton
|
||||
aria-label="Clear"
|
||||
onClick={() => onHeaderChange(index, header.key, "")}
|
||||
/>
|
||||
) : null
|
||||
}
|
||||
/>
|
||||
<RemoveIcon onClick={() => onRemoveHeader(index)}>
|
||||
<Text size="xs">✕</Text>
|
||||
|
|
@ -269,6 +288,15 @@ export function ExperimentalFeaturesPanel({
|
|||
onChange={(e) => onRequestChange(e.currentTarget.value)}
|
||||
autosize
|
||||
minRows={6}
|
||||
rightSectionPointerEvents="auto"
|
||||
rightSection={
|
||||
requestDraft ? (
|
||||
<CloseButton
|
||||
aria-label="Clear"
|
||||
onClick={() => onRequestChange("")}
|
||||
/>
|
||||
) : null
|
||||
}
|
||||
/>
|
||||
|
||||
<Button onClick={onSendRequest}>Send Request</Button>
|
||||
|
|
|
|||
|
|
@ -31,6 +31,20 @@ describe("HistoryControls", () => {
|
|||
expect(onSearchChange).toHaveBeenCalledWith("a");
|
||||
});
|
||||
|
||||
it("clears the search input when the Clear button is clicked", async () => {
|
||||
const user = userEvent.setup();
|
||||
const onSearchChange = vi.fn();
|
||||
renderWithMantine(
|
||||
<HistoryControls
|
||||
{...baseProps}
|
||||
searchText="abc"
|
||||
onSearchChange={onSearchChange}
|
||||
/>,
|
||||
);
|
||||
await user.click(screen.getByRole("button", { name: "Clear" }));
|
||||
expect(onSearchChange).toHaveBeenCalledWith("");
|
||||
});
|
||||
|
||||
it("renders the method filter placeholder", () => {
|
||||
renderWithMantine(<HistoryControls {...baseProps} />);
|
||||
expect(screen.getByPlaceholderText("All methods")).toBeInTheDocument();
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Select, Stack, TextInput, Title } from "@mantine/core";
|
||||
import { CloseButton, Select, Stack, TextInput, Title } from "@mantine/core";
|
||||
import type {
|
||||
MessageMethod,
|
||||
MessageOrigin,
|
||||
|
|
@ -33,6 +33,15 @@ export function HistoryControls({
|
|||
placeholder="Search..."
|
||||
value={searchText}
|
||||
onChange={(event) => onSearchChange(event.currentTarget.value)}
|
||||
rightSectionPointerEvents="auto"
|
||||
rightSection={
|
||||
searchText ? (
|
||||
<CloseButton
|
||||
aria-label="Clear"
|
||||
onClick={() => onSearchChange("")}
|
||||
/>
|
||||
) : null
|
||||
}
|
||||
/>
|
||||
|
||||
<Title order={6}>Filter by Method</Title>
|
||||
|
|
|
|||
|
|
@ -196,6 +196,40 @@ describe("ImportServerJsonPanel", () => {
|
|||
expect(onServerNameChange).toHaveBeenCalledWith("X");
|
||||
});
|
||||
|
||||
it("clears the paste textarea, env-var, and name-override fields via Clear buttons", async () => {
|
||||
const user = userEvent.setup();
|
||||
const onJsonChange = vi.fn();
|
||||
const onEnvVarChange = vi.fn();
|
||||
const onServerNameChange = vi.fn();
|
||||
const envVars: EnvVarInfo[] = [
|
||||
{ name: "DEBUG", required: false, value: "true" },
|
||||
];
|
||||
renderWithMantine(
|
||||
<ImportServerJsonPanel
|
||||
{...baseHandlers}
|
||||
onJsonChange={onJsonChange}
|
||||
onEnvVarChange={onEnvVarChange}
|
||||
onServerNameChange={onServerNameChange}
|
||||
draft={{
|
||||
rawText: "{}",
|
||||
envOverrides: {},
|
||||
nameOverride: "Custom Name",
|
||||
}}
|
||||
validation={[]}
|
||||
envVars={envVars}
|
||||
/>,
|
||||
);
|
||||
// DOM order: paste textarea, env-var input, name-override input.
|
||||
const clearButtons = screen.getAllByRole("button", { name: "Clear" });
|
||||
expect(clearButtons).toHaveLength(3);
|
||||
await user.click(clearButtons[0]);
|
||||
expect(onJsonChange).toHaveBeenCalledWith("");
|
||||
await user.click(clearButtons[1]);
|
||||
expect(onEnvVarChange).toHaveBeenCalledWith("DEBUG", "");
|
||||
await user.click(clearButtons[2]);
|
||||
expect(onServerNameChange).toHaveBeenCalledWith("");
|
||||
});
|
||||
|
||||
it("renders the existing nameOverride value", () => {
|
||||
renderWithMantine(
|
||||
<ImportServerJsonPanel
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import {
|
||||
Button,
|
||||
CloseButton,
|
||||
Divider,
|
||||
Group,
|
||||
Radio,
|
||||
|
|
@ -88,6 +89,12 @@ export function ImportServerJsonPanel({
|
|||
autosize
|
||||
minRows={8}
|
||||
maxRows={15}
|
||||
rightSectionPointerEvents="auto"
|
||||
rightSection={
|
||||
draft.rawText ? (
|
||||
<CloseButton aria-label="Clear" onClick={() => onJsonChange("")} />
|
||||
) : null
|
||||
}
|
||||
/>
|
||||
|
||||
<Divider />
|
||||
|
|
@ -139,6 +146,15 @@ export function ImportServerJsonPanel({
|
|||
onChange={(e) =>
|
||||
onEnvVarChange(envVar.name, e.currentTarget.value)
|
||||
}
|
||||
rightSectionPointerEvents="auto"
|
||||
rightSection={
|
||||
envVar.value ? (
|
||||
<CloseButton
|
||||
aria-label="Clear"
|
||||
onClick={() => onEnvVarChange(envVar.name, "")}
|
||||
/>
|
||||
) : null
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
|
|
@ -150,6 +166,15 @@ export function ImportServerJsonPanel({
|
|||
label="Server Name (optional override)"
|
||||
value={draft.nameOverride ?? ""}
|
||||
onChange={(e) => onServerNameChange(e.currentTarget.value)}
|
||||
rightSectionPointerEvents="auto"
|
||||
rightSection={
|
||||
draft.nameOverride ? (
|
||||
<CloseButton
|
||||
aria-label="Clear"
|
||||
onClick={() => onServerNameChange("")}
|
||||
/>
|
||||
) : null
|
||||
}
|
||||
/>
|
||||
|
||||
<Group justify="flex-end">
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import {
|
||||
Button,
|
||||
CloseButton,
|
||||
Group,
|
||||
Select,
|
||||
Stack,
|
||||
|
|
@ -64,6 +65,15 @@ export function LogControls({
|
|||
placeholder="Search..."
|
||||
value={filterText}
|
||||
onChange={(e) => onFilterChange(e.currentTarget.value)}
|
||||
rightSectionPointerEvents="auto"
|
||||
rightSection={
|
||||
filterText ? (
|
||||
<CloseButton
|
||||
aria-label="Clear"
|
||||
onClick={() => onFilterChange("")}
|
||||
/>
|
||||
) : null
|
||||
}
|
||||
/>
|
||||
|
||||
<Title order={5}>Set Active Level</Title>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import {
|
||||
Button,
|
||||
CloseButton,
|
||||
Group,
|
||||
Stack,
|
||||
Text,
|
||||
|
|
@ -45,6 +46,15 @@ export function NetworkControls({
|
|||
placeholder="Search..."
|
||||
value={filterText}
|
||||
onChange={(e) => onFilterChange(e.currentTarget.value)}
|
||||
rightSectionPointerEvents="auto"
|
||||
rightSection={
|
||||
filterText ? (
|
||||
<CloseButton
|
||||
aria-label="Clear"
|
||||
onClick={() => onFilterChange("")}
|
||||
/>
|
||||
) : null
|
||||
}
|
||||
/>
|
||||
|
||||
<Group justify="space-between">
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ export const Sampling: Story = {
|
|||
expect(
|
||||
body.getByText("The server is requesting an LLM completion."),
|
||||
).toBeInTheDocument();
|
||||
await userEvent.click(body.getByRole("button", { name: "Auto-respond" }));
|
||||
await userEvent.click(body.getByRole("button", { name: "Send Response" }));
|
||||
expect(args.onSamplingRespond).toHaveBeenCalled();
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -79,12 +79,12 @@ describe("PendingClientRequestModal", () => {
|
|||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("auto-responds with a stub sampling result", async () => {
|
||||
it("sends the default stub sampling result when the draft is untouched", async () => {
|
||||
const user = userEvent.setup();
|
||||
renderWithMantine(
|
||||
<PendingClientRequestModal {...baseProps} request={samplingContent} />,
|
||||
);
|
||||
await user.click(screen.getByRole("button", { name: "Auto-respond" }));
|
||||
await user.click(screen.getByRole("button", { name: "Send Response" }));
|
||||
expect(baseProps.onSamplingRespond).toHaveBeenCalledWith({
|
||||
model: "stub-model",
|
||||
stopReason: "endTurn",
|
||||
|
|
@ -124,10 +124,10 @@ describe("PendingClientRequestModal", () => {
|
|||
renderWithMantine(
|
||||
<PendingClientRequestModal {...baseProps} request={samplingContent} />,
|
||||
);
|
||||
const autoRespond = screen.getByRole("button", { name: "Auto-respond" });
|
||||
await user.click(autoRespond);
|
||||
const sendResponse = screen.getByRole("button", { name: "Send Response" });
|
||||
await user.click(sendResponse);
|
||||
// After the first dispatch the actions lock (busy); a second click no-ops.
|
||||
await user.click(autoRespond);
|
||||
await user.click(sendResponse);
|
||||
expect(baseProps.onSamplingRespond).toHaveBeenCalledTimes(1);
|
||||
expect(
|
||||
screen.getByRole("button", { name: "Send Response" }),
|
||||
|
|
|
|||
|
|
@ -55,8 +55,8 @@ const TitleText = Text.withProps({ fw: 600 });
|
|||
const QueueLabel = Text.withProps({ size: "xs", c: "dimmed" });
|
||||
|
||||
/**
|
||||
* The stub result pre-filled into a sampling draft. "Auto-respond" sends this
|
||||
* as-is; "Send Response" sends whatever the user edited it into.
|
||||
* The stub result pre-filled into a sampling draft. "Send Response" sends this
|
||||
* as-is when untouched, or whatever the user edited it into.
|
||||
*/
|
||||
function createDefaultSamplingResult(): CreateMessageResult {
|
||||
return {
|
||||
|
|
@ -119,7 +119,6 @@ function SamplingModalBody({
|
|||
request={request}
|
||||
draftResult={draftResult}
|
||||
onResultChange={setDraftResult}
|
||||
onAutoRespond={once(() => onRespond(createDefaultSamplingResult()))}
|
||||
onSend={once(() => onRespond(draftResult))}
|
||||
onReject={once(onReject)}
|
||||
busy={responded}
|
||||
|
|
|
|||
|
|
@ -144,6 +144,23 @@ describe("PromptArgumentsForm", () => {
|
|||
expect(onArgumentChange).toHaveBeenCalledWith("text", "h");
|
||||
});
|
||||
|
||||
it("clears an argument via its Clear button (onArgumentChange with empty value)", async () => {
|
||||
const user = userEvent.setup();
|
||||
const onArgumentChange = vi.fn();
|
||||
renderWithMantine(
|
||||
<PromptArgumentsForm
|
||||
prompt={promptWithArgs}
|
||||
argumentValues={{ text: "Hello" }}
|
||||
onArgumentChange={onArgumentChange}
|
||||
onGetPrompt={vi.fn()}
|
||||
/>,
|
||||
);
|
||||
// Non-autocomplete branch (completions unsupported) renders a TextInput
|
||||
// with a Clear button whenever the value is non-empty.
|
||||
await user.click(screen.getByRole("button", { name: "Clear" }));
|
||||
expect(onArgumentChange).toHaveBeenCalledWith("text", "");
|
||||
});
|
||||
|
||||
it("invokes onGetPrompt when Get Prompt is clicked", async () => {
|
||||
const user = userEvent.setup();
|
||||
const onGetPrompt = vi.fn();
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { useCallback, useEffect, useRef, useState } from "react";
|
|||
import {
|
||||
Autocomplete,
|
||||
Button,
|
||||
CloseButton,
|
||||
Group,
|
||||
Stack,
|
||||
Text,
|
||||
|
|
@ -221,6 +222,15 @@ export function PromptArgumentsForm({
|
|||
onChange={(event) =>
|
||||
handleChange(arg.name, event.currentTarget.value)
|
||||
}
|
||||
rightSectionPointerEvents="auto"
|
||||
rightSection={
|
||||
argumentValues[arg.name] ? (
|
||||
<CloseButton
|
||||
aria-label="Clear"
|
||||
onClick={() => handleChange(arg.name, "")}
|
||||
/>
|
||||
) : null
|
||||
}
|
||||
/>
|
||||
),
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,11 @@
|
|||
import { Group, ScrollArea, Stack, TextInput, Title } from "@mantine/core";
|
||||
import {
|
||||
CloseButton,
|
||||
Group,
|
||||
ScrollArea,
|
||||
Stack,
|
||||
TextInput,
|
||||
Title,
|
||||
} from "@mantine/core";
|
||||
import type { Prompt } from "@modelcontextprotocol/sdk/types.js";
|
||||
import { ListChangedIndicator } from "../../elements/ListChangedIndicator/ListChangedIndicator";
|
||||
import { PromptListItem } from "../PromptListItem/PromptListItem";
|
||||
|
|
@ -47,6 +54,15 @@ export function PromptControls({
|
|||
placeholder="Search prompts..."
|
||||
value={searchText}
|
||||
onChange={(e) => onSearchChange(e.currentTarget.value)}
|
||||
rightSectionPointerEvents="auto"
|
||||
rightSection={
|
||||
searchText ? (
|
||||
<CloseButton
|
||||
aria-label="Clear"
|
||||
onClick={() => onSearchChange("")}
|
||||
/>
|
||||
) : null
|
||||
}
|
||||
/>
|
||||
<ScrollArea.Autosize viewportRef={viewportRef} mah={LIST_MAX_HEIGHT}>
|
||||
<Stack gap="xs">
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import {
|
||||
Accordion,
|
||||
CloseButton,
|
||||
Group,
|
||||
ScrollArea,
|
||||
Stack,
|
||||
|
|
@ -127,6 +128,15 @@ export function ResourceControls({
|
|||
placeholder="Search..."
|
||||
value={searchText}
|
||||
onChange={(e) => onSearchChange(e.currentTarget.value)}
|
||||
rightSectionPointerEvents="auto"
|
||||
rightSection={
|
||||
searchText ? (
|
||||
<CloseButton
|
||||
aria-label="Clear"
|
||||
onClick={() => onSearchChange("")}
|
||||
/>
|
||||
) : null
|
||||
}
|
||||
/>
|
||||
<ListToggle compact={!allExpanded} onToggle={handleToggleList} />
|
||||
</Group>
|
||||
|
|
|
|||
|
|
@ -105,6 +105,22 @@ describe("ResourceTemplatePanel", () => {
|
|||
expect(screen.getByText("file:///users/bob/profile")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("clears a variable via its Clear button (non-autocomplete branch)", async () => {
|
||||
const user = userEvent.setup();
|
||||
renderWithMantine(
|
||||
<ResourceTemplatePanel
|
||||
template={singleVarTemplate}
|
||||
onReadResource={vi.fn()}
|
||||
/>,
|
||||
);
|
||||
const input = screen.getByLabelText("userId");
|
||||
await user.type(input, "alice");
|
||||
expect(input).toHaveValue("alice");
|
||||
// The Clear button only renders while the value is non-empty.
|
||||
await user.click(screen.getByRole("button", { name: "Clear" }));
|
||||
expect(input).toHaveValue("");
|
||||
});
|
||||
|
||||
it("renders annotation badges when present", () => {
|
||||
renderWithMantine(
|
||||
<ResourceTemplatePanel
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|||
import {
|
||||
Autocomplete,
|
||||
Button,
|
||||
CloseButton,
|
||||
Group,
|
||||
Stack,
|
||||
Text,
|
||||
|
|
@ -261,6 +262,15 @@ export function ResourceTemplatePanel({
|
|||
onChange={(e) =>
|
||||
handleVariableChange(varName, e.currentTarget.value)
|
||||
}
|
||||
rightSectionPointerEvents="auto"
|
||||
rightSection={
|
||||
variables[varName] ? (
|
||||
<CloseButton
|
||||
aria-label="Clear"
|
||||
onClick={() => handleVariableChange(varName, "")}
|
||||
/>
|
||||
) : null
|
||||
}
|
||||
/>
|
||||
),
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -97,6 +97,31 @@ describe("RootsTable", () => {
|
|||
expect(onNewRootDraftChange).toHaveBeenCalledWith({ name: "", uri: "y" });
|
||||
});
|
||||
|
||||
it("clears the Name and URI inputs via their Clear buttons", async () => {
|
||||
const user = userEvent.setup();
|
||||
const onNewRootDraftChange = vi.fn();
|
||||
renderWithMantine(
|
||||
<RootsTable
|
||||
{...baseProps}
|
||||
newRootDraft={{ name: "n", uri: "u" }}
|
||||
onNewRootDraftChange={onNewRootDraftChange}
|
||||
/>,
|
||||
);
|
||||
const clearButtons = screen.getAllByRole("button", { name: "Clear" });
|
||||
expect(clearButtons).toHaveLength(2);
|
||||
await user.click(clearButtons[0]);
|
||||
expect(onNewRootDraftChange).toHaveBeenCalledWith({ name: "", uri: "u" });
|
||||
await user.click(clearButtons[1]);
|
||||
expect(onNewRootDraftChange).toHaveBeenCalledWith({ name: "n", uri: "" });
|
||||
});
|
||||
|
||||
it("renders no Clear buttons when the draft fields are empty", () => {
|
||||
renderWithMantine(<RootsTable {...baseProps} />);
|
||||
expect(
|
||||
screen.queryByRole("button", { name: "Clear" }),
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders the current draft values in the inputs", () => {
|
||||
renderWithMantine(
|
||||
<RootsTable
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import {
|
|||
ActionIcon,
|
||||
Alert,
|
||||
Button,
|
||||
CloseButton,
|
||||
Divider,
|
||||
Group,
|
||||
Stack,
|
||||
|
|
@ -90,6 +91,17 @@ export function RootsTable({
|
|||
name: e.currentTarget.value,
|
||||
})
|
||||
}
|
||||
rightSectionPointerEvents="auto"
|
||||
rightSection={
|
||||
newRootDraft.name ? (
|
||||
<CloseButton
|
||||
aria-label="Clear"
|
||||
onClick={() =>
|
||||
onNewRootDraftChange({ ...newRootDraft, name: "" })
|
||||
}
|
||||
/>
|
||||
) : null
|
||||
}
|
||||
/>
|
||||
<TextInput
|
||||
label="URI"
|
||||
|
|
@ -100,6 +112,15 @@ export function RootsTable({
|
|||
uri: e.currentTarget.value,
|
||||
})
|
||||
}
|
||||
rightSectionPointerEvents="auto"
|
||||
rightSection={
|
||||
newRootDraft.uri ? (
|
||||
<CloseButton
|
||||
aria-label="Clear"
|
||||
onClick={() => onNewRootDraftChange({ ...newRootDraft, uri: "" })}
|
||||
/>
|
||||
) : null
|
||||
}
|
||||
/>
|
||||
<Group justify="flex-end">
|
||||
<Button variant="light" onClick={onBrowse}>
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue