Add ServerSettingsModal group, revert form to controlled component
Revert ServerSettingsForm to a controlled component with expandedSections/onExpandedSectionsChange props. The form is a dumb component — accordion state belongs to its container. Create ServerSettingsModal group that manages: - Accordion expand/collapse state with ListToggle expand-all button - Add/remove/edit header pairs - Add/remove/edit metadata pairs - Connection mode, timeout, and OAuth changes - All changes flow through a single onSettingsChange callback Update ServerSettingsForm stories with useState render functions so accordion sections are interactive. Update UnconnectedView WithSettingsModal story to use the new ServerSettingsModal component. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
adb909daf6
commit
a6fc900271
5 changed files with 307 additions and 100 deletions
|
|
@ -1,7 +1,11 @@
|
||||||
|
import { useState } from "react";
|
||||||
import type { InspectorServerSettings } from "@inspector/core/mcp/types.js";
|
import type { InspectorServerSettings } from "@inspector/core/mcp/types.js";
|
||||||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||||
import { fn } from "storybook/test";
|
import { fn } from "storybook/test";
|
||||||
import { ServerSettingsForm } from "./ServerSettingsForm";
|
import {
|
||||||
|
ServerSettingsForm,
|
||||||
|
type ServerSettingsSection,
|
||||||
|
} from "./ServerSettingsForm";
|
||||||
|
|
||||||
const defaultSettings: InspectorServerSettings = {
|
const defaultSettings: InspectorServerSettings = {
|
||||||
connectionMode: "proxy",
|
connectionMode: "proxy",
|
||||||
|
|
@ -11,72 +15,97 @@ const defaultSettings: InspectorServerSettings = {
|
||||||
requestTimeout: 60000,
|
requestTimeout: 60000,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const defaultHandlers = {
|
||||||
|
onConnectionModeChange: fn(),
|
||||||
|
onAddHeader: fn(),
|
||||||
|
onRemoveHeader: fn(),
|
||||||
|
onHeaderChange: fn(),
|
||||||
|
onAddMetadata: fn(),
|
||||||
|
onRemoveMetadata: fn(),
|
||||||
|
onMetadataChange: fn(),
|
||||||
|
onTimeoutChange: fn(),
|
||||||
|
onOAuthChange: fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
function InteractiveForm({
|
||||||
|
settings,
|
||||||
|
initialSections = ["connectionMode"],
|
||||||
|
}: {
|
||||||
|
settings: InspectorServerSettings;
|
||||||
|
initialSections?: ServerSettingsSection[];
|
||||||
|
}) {
|
||||||
|
const [expandedSections, setExpandedSections] =
|
||||||
|
useState<ServerSettingsSection[]>(initialSections);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ServerSettingsForm
|
||||||
|
settings={settings}
|
||||||
|
expandedSections={expandedSections}
|
||||||
|
onExpandedSectionsChange={setExpandedSections}
|
||||||
|
{...defaultHandlers}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const meta: Meta<typeof ServerSettingsForm> = {
|
const meta: Meta<typeof ServerSettingsForm> = {
|
||||||
title: "Groups/ServerSettingsForm",
|
title: "Groups/ServerSettingsForm",
|
||||||
component: ServerSettingsForm,
|
component: ServerSettingsForm,
|
||||||
args: {
|
|
||||||
onConnectionModeChange: fn(),
|
|
||||||
onAddHeader: fn(),
|
|
||||||
onRemoveHeader: fn(),
|
|
||||||
onHeaderChange: fn(),
|
|
||||||
onAddMetadata: fn(),
|
|
||||||
onRemoveMetadata: fn(),
|
|
||||||
onMetadataChange: fn(),
|
|
||||||
onTimeoutChange: fn(),
|
|
||||||
onOAuthChange: fn(),
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default meta;
|
export default meta;
|
||||||
type Story = StoryObj<typeof ServerSettingsForm>;
|
type Story = StoryObj<typeof ServerSettingsForm>;
|
||||||
|
|
||||||
export const DefaultSettings: Story = {
|
export const DefaultSettings: Story = {
|
||||||
args: {
|
render: () => <InteractiveForm settings={defaultSettings} />,
|
||||||
settings: defaultSettings,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const WithHeaders: Story = {
|
export const WithHeaders: Story = {
|
||||||
args: {
|
render: () => (
|
||||||
settings: {
|
<InteractiveForm
|
||||||
...defaultSettings,
|
settings={{
|
||||||
headers: [
|
...defaultSettings,
|
||||||
{ key: "Authorization", value: "Bearer token-abc-123" },
|
headers: [
|
||||||
{ key: "X-Custom-Header", value: "custom-value" },
|
{ key: "Authorization", value: "Bearer token-abc-123" },
|
||||||
],
|
{ key: "X-Custom-Header", value: "custom-value" },
|
||||||
},
|
],
|
||||||
},
|
}}
|
||||||
|
/>
|
||||||
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const WithOAuth: Story = {
|
export const WithOAuth: Story = {
|
||||||
args: {
|
render: () => (
|
||||||
settings: {
|
<InteractiveForm
|
||||||
...defaultSettings,
|
settings={{
|
||||||
connectionMode: "direct",
|
...defaultSettings,
|
||||||
oauthClientId: "my-client-id",
|
connectionMode: "direct",
|
||||||
oauthClientSecret: "super-secret-value",
|
oauthClientId: "my-client-id",
|
||||||
oauthScopes: "read write admin",
|
oauthClientSecret: "super-secret-value",
|
||||||
},
|
oauthScopes: "read write admin",
|
||||||
},
|
}}
|
||||||
|
/>
|
||||||
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const AllConfigured: Story = {
|
export const AllConfigured: Story = {
|
||||||
args: {
|
render: () => (
|
||||||
settings: {
|
<InteractiveForm
|
||||||
connectionMode: "proxy",
|
settings={{
|
||||||
headers: [
|
connectionMode: "proxy",
|
||||||
{ key: "Authorization", value: "Bearer token-abc-123" },
|
headers: [
|
||||||
{ key: "X-Request-Id", value: "req-456" },
|
{ key: "Authorization", value: "Bearer token-abc-123" },
|
||||||
],
|
{ key: "X-Request-Id", value: "req-456" },
|
||||||
metadata: [
|
],
|
||||||
{ key: "userId", value: "user-789" },
|
metadata: [
|
||||||
{ key: "sessionId", value: "session-012" },
|
{ key: "userId", value: "user-789" },
|
||||||
],
|
{ key: "sessionId", value: "session-012" },
|
||||||
connectionTimeout: 15000,
|
],
|
||||||
requestTimeout: 45000,
|
connectionTimeout: 15000,
|
||||||
oauthClientId: "my-client-id",
|
requestTimeout: 45000,
|
||||||
oauthClientSecret: "super-secret-value",
|
oauthClientId: "my-client-id",
|
||||||
oauthScopes: "read write",
|
oauthClientSecret: "super-secret-value",
|
||||||
},
|
oauthScopes: "read write",
|
||||||
},
|
}}
|
||||||
|
/>
|
||||||
|
),
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
import { useState } from "react";
|
|
||||||
import {
|
import {
|
||||||
Accordion,
|
Accordion,
|
||||||
ActionIcon,
|
ActionIcon,
|
||||||
|
|
@ -21,7 +20,8 @@ export type ServerSettingsSection =
|
||||||
|
|
||||||
export interface ServerSettingsFormProps {
|
export interface ServerSettingsFormProps {
|
||||||
settings: InspectorServerSettings;
|
settings: InspectorServerSettings;
|
||||||
defaultExpandedSections?: ServerSettingsSection[];
|
expandedSections: ServerSettingsSection[];
|
||||||
|
onExpandedSectionsChange: (sections: ServerSettingsSection[]) => void;
|
||||||
onConnectionModeChange: (mode: "proxy" | "direct") => void;
|
onConnectionModeChange: (mode: "proxy" | "direct") => void;
|
||||||
onAddHeader: () => void;
|
onAddHeader: () => void;
|
||||||
onRemoveHeader: (index: number) => void;
|
onRemoveHeader: (index: number) => void;
|
||||||
|
|
@ -95,7 +95,8 @@ function KeyValueRows({
|
||||||
|
|
||||||
export function ServerSettingsForm({
|
export function ServerSettingsForm({
|
||||||
settings,
|
settings,
|
||||||
defaultExpandedSections = ["connectionMode"],
|
expandedSections,
|
||||||
|
onExpandedSectionsChange,
|
||||||
onConnectionModeChange,
|
onConnectionModeChange,
|
||||||
onAddHeader,
|
onAddHeader,
|
||||||
onRemoveHeader,
|
onRemoveHeader,
|
||||||
|
|
@ -106,10 +107,6 @@ export function ServerSettingsForm({
|
||||||
onTimeoutChange,
|
onTimeoutChange,
|
||||||
onOAuthChange,
|
onOAuthChange,
|
||||||
}: ServerSettingsFormProps) {
|
}: ServerSettingsFormProps) {
|
||||||
const [expandedSections, setExpandedSections] = useState<
|
|
||||||
ServerSettingsSection[]
|
|
||||||
>(defaultExpandedSections);
|
|
||||||
|
|
||||||
const handleTimeoutChange = (field: string) => (value: number | string) => {
|
const handleTimeoutChange = (field: string) => (value: number | string) => {
|
||||||
const numValue =
|
const numValue =
|
||||||
typeof value === "string" ? parseInt(value, 10) || 0 : value;
|
typeof value === "string" ? parseInt(value, 10) || 0 : value;
|
||||||
|
|
@ -121,7 +118,7 @@ export function ServerSettingsForm({
|
||||||
multiple
|
multiple
|
||||||
value={expandedSections}
|
value={expandedSections}
|
||||||
onChange={(value) =>
|
onChange={(value) =>
|
||||||
setExpandedSections(value as ServerSettingsSection[])
|
onExpandedSectionsChange(value as ServerSettingsSection[])
|
||||||
}
|
}
|
||||||
variant="separated"
|
variant="separated"
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,62 @@
|
||||||
|
import { useState } from "react";
|
||||||
|
import type { InspectorServerSettings } from "@inspector/core/mcp/types.js";
|
||||||
|
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||||
|
import { ServerSettingsModal } from "./ServerSettingsModal";
|
||||||
|
|
||||||
|
const initialSettings: InspectorServerSettings = {
|
||||||
|
connectionMode: "proxy",
|
||||||
|
headers: [
|
||||||
|
{ key: "Authorization", value: "Bearer token-abc-123" },
|
||||||
|
{ key: "X-Request-Id", value: "req-456" },
|
||||||
|
],
|
||||||
|
metadata: [{ key: "userId", value: "user-789" }],
|
||||||
|
connectionTimeout: 30000,
|
||||||
|
requestTimeout: 60000,
|
||||||
|
oauthClientId: "my-client-id",
|
||||||
|
oauthClientSecret: "super-secret-value",
|
||||||
|
oauthScopes: "read write",
|
||||||
|
};
|
||||||
|
|
||||||
|
function InteractiveModal({
|
||||||
|
startSettings,
|
||||||
|
}: {
|
||||||
|
startSettings: InspectorServerSettings;
|
||||||
|
}) {
|
||||||
|
const [settings, setSettings] =
|
||||||
|
useState<InspectorServerSettings>(startSettings);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ServerSettingsModal
|
||||||
|
opened
|
||||||
|
settings={settings}
|
||||||
|
onClose={() => {}}
|
||||||
|
onSettingsChange={setSettings}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const meta: Meta<typeof ServerSettingsModal> = {
|
||||||
|
title: "Groups/ServerSettingsModal",
|
||||||
|
component: ServerSettingsModal,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof ServerSettingsModal>;
|
||||||
|
|
||||||
|
export const FullyConfigured: Story = {
|
||||||
|
render: () => <InteractiveModal startSettings={initialSettings} />,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const EmptySettings: Story = {
|
||||||
|
render: () => (
|
||||||
|
<InteractiveModal
|
||||||
|
startSettings={{
|
||||||
|
connectionMode: "proxy",
|
||||||
|
headers: [],
|
||||||
|
metadata: [],
|
||||||
|
connectionTimeout: 30000,
|
||||||
|
requestTimeout: 60000,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,139 @@
|
||||||
|
import { useState } from "react";
|
||||||
|
import { CloseButton, Group, Modal, Title } from "@mantine/core";
|
||||||
|
import type { InspectorServerSettings } from "@inspector/core/mcp/types.js";
|
||||||
|
import { ListToggle } from "../../elements/ListToggle/ListToggle";
|
||||||
|
import {
|
||||||
|
ServerSettingsForm,
|
||||||
|
type ServerSettingsSection,
|
||||||
|
} from "../ServerSettingsForm/ServerSettingsForm";
|
||||||
|
|
||||||
|
const ALL_SECTIONS: ServerSettingsSection[] = [
|
||||||
|
"connectionMode",
|
||||||
|
"headers",
|
||||||
|
"metadata",
|
||||||
|
"timeouts",
|
||||||
|
"oauth",
|
||||||
|
];
|
||||||
|
|
||||||
|
export interface ServerSettingsModalProps {
|
||||||
|
opened: boolean;
|
||||||
|
settings: InspectorServerSettings;
|
||||||
|
onClose: () => void;
|
||||||
|
onSettingsChange: (settings: InspectorServerSettings) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ServerSettingsModal({
|
||||||
|
opened,
|
||||||
|
settings,
|
||||||
|
onClose,
|
||||||
|
onSettingsChange,
|
||||||
|
}: ServerSettingsModalProps) {
|
||||||
|
const [expandedSections, setExpandedSections] = useState<
|
||||||
|
ServerSettingsSection[]
|
||||||
|
>(["connectionMode"]);
|
||||||
|
|
||||||
|
const allExpanded = expandedSections.length === ALL_SECTIONS.length;
|
||||||
|
|
||||||
|
function handleToggleAll() {
|
||||||
|
setExpandedSections(allExpanded ? [] : ALL_SECTIONS);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleConnectionModeChange(mode: "proxy" | "direct") {
|
||||||
|
onSettingsChange({ ...settings, connectionMode: mode });
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleAddHeader() {
|
||||||
|
onSettingsChange({
|
||||||
|
...settings,
|
||||||
|
headers: [...settings.headers, { key: "", value: "" }],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleRemoveHeader(index: number) {
|
||||||
|
onSettingsChange({
|
||||||
|
...settings,
|
||||||
|
headers: settings.headers.filter((_, i) => i !== index),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleHeaderChange(index: number, key: string, value: string) {
|
||||||
|
const headers = settings.headers.map((h, i) =>
|
||||||
|
i === index ? { key, value } : h,
|
||||||
|
);
|
||||||
|
onSettingsChange({ ...settings, headers });
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleAddMetadata() {
|
||||||
|
onSettingsChange({
|
||||||
|
...settings,
|
||||||
|
metadata: [...settings.metadata, { key: "", value: "" }],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleRemoveMetadata(index: number) {
|
||||||
|
onSettingsChange({
|
||||||
|
...settings,
|
||||||
|
metadata: settings.metadata.filter((_, i) => i !== index),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleMetadataChange(index: number, key: string, value: string) {
|
||||||
|
const metadata = settings.metadata.map((m, i) =>
|
||||||
|
i === index ? { key, value } : m,
|
||||||
|
);
|
||||||
|
onSettingsChange({ ...settings, metadata });
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleTimeoutChange(field: string, value: number) {
|
||||||
|
onSettingsChange({ ...settings, [field]: value });
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleOAuthChange(field: string, value: string) {
|
||||||
|
const oauthFieldMap: Record<string, string> = {
|
||||||
|
clientId: "oauthClientId",
|
||||||
|
clientSecret: "oauthClientSecret",
|
||||||
|
scopes: "oauthScopes",
|
||||||
|
};
|
||||||
|
const settingsKey = oauthFieldMap[field] ?? field;
|
||||||
|
onSettingsChange({ ...settings, [settingsKey]: value });
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
opened={opened}
|
||||||
|
onClose={onClose}
|
||||||
|
withCloseButton={false}
|
||||||
|
title={
|
||||||
|
<Group justify="space-between" wrap="nowrap" w="100%">
|
||||||
|
<ListToggle
|
||||||
|
compact={allExpanded}
|
||||||
|
variant="subtle"
|
||||||
|
onToggle={handleToggleAll}
|
||||||
|
/>
|
||||||
|
<Title order={4} ta="center" style={{ flex: 1 }}>
|
||||||
|
Server Settings
|
||||||
|
</Title>
|
||||||
|
<CloseButton onClick={onClose} />
|
||||||
|
</Group>
|
||||||
|
}
|
||||||
|
size="lg"
|
||||||
|
centered
|
||||||
|
styles={{ title: { flex: 1 } }}
|
||||||
|
>
|
||||||
|
<ServerSettingsForm
|
||||||
|
settings={settings}
|
||||||
|
expandedSections={expandedSections}
|
||||||
|
onExpandedSectionsChange={setExpandedSections}
|
||||||
|
onConnectionModeChange={handleConnectionModeChange}
|
||||||
|
onAddHeader={handleAddHeader}
|
||||||
|
onRemoveHeader={handleRemoveHeader}
|
||||||
|
onHeaderChange={handleHeaderChange}
|
||||||
|
onAddMetadata={handleAddMetadata}
|
||||||
|
onRemoveMetadata={handleRemoveMetadata}
|
||||||
|
onMetadataChange={handleMetadataChange}
|
||||||
|
onTimeoutChange={handleTimeoutChange}
|
||||||
|
onOAuthChange={handleOAuthChange}
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
|
import { useState } from "react";
|
||||||
|
import type { InspectorServerSettings } from "@inspector/core/mcp/types.js";
|
||||||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||||
import { CloseButton, Group, Modal, Title } from "@mantine/core";
|
|
||||||
import { fn } from "storybook/test";
|
import { fn } from "storybook/test";
|
||||||
import { UnconnectedView } from "./UnconnectedView.js";
|
import { UnconnectedView } from "./UnconnectedView.js";
|
||||||
import { ServerListScreen } from "../../screens/ServerListScreen/ServerListScreen";
|
import { ServerListScreen } from "../../screens/ServerListScreen/ServerListScreen";
|
||||||
import { ServerSettingsForm } from "../../groups/ServerSettingsForm/ServerSettingsForm";
|
import { ServerSettingsModal } from "../../groups/ServerSettingsModal/ServerSettingsModal";
|
||||||
import type { ServerCardProps } from "../../groups/ServerCard/ServerCard";
|
import type { ServerCardProps } from "../../groups/ServerCard/ServerCard";
|
||||||
|
|
||||||
const meta: Meta<typeof UnconnectedView> = {
|
const meta: Meta<typeof UnconnectedView> = {
|
||||||
|
|
@ -112,6 +113,20 @@ export const WithServers: Story = {
|
||||||
};
|
};
|
||||||
|
|
||||||
function SettingsModalStory() {
|
function SettingsModalStory() {
|
||||||
|
const [settings, setSettings] = useState<InspectorServerSettings>({
|
||||||
|
connectionMode: "proxy",
|
||||||
|
headers: [
|
||||||
|
{ key: "Authorization", value: "Bearer token-abc-123" },
|
||||||
|
{ key: "X-Request-Id", value: "req-456" },
|
||||||
|
],
|
||||||
|
metadata: [{ key: "userId", value: "user-789" }],
|
||||||
|
connectionTimeout: 30000,
|
||||||
|
requestTimeout: 60000,
|
||||||
|
oauthClientId: "my-client-id",
|
||||||
|
oauthClientSecret: "super-secret-value",
|
||||||
|
oauthScopes: "read write",
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<UnconnectedView onToggleTheme={fn()}>
|
<UnconnectedView onToggleTheme={fn()}>
|
||||||
<ServerListScreen
|
<ServerListScreen
|
||||||
|
|
@ -126,47 +141,12 @@ function SettingsModalStory() {
|
||||||
onImportConfig={fn()}
|
onImportConfig={fn()}
|
||||||
onImportServerJson={fn()}
|
onImportServerJson={fn()}
|
||||||
/>
|
/>
|
||||||
<Modal
|
<ServerSettingsModal
|
||||||
opened
|
opened
|
||||||
|
settings={settings}
|
||||||
onClose={fn()}
|
onClose={fn()}
|
||||||
withCloseButton={false}
|
onSettingsChange={setSettings}
|
||||||
title={
|
/>
|
||||||
<Group justify="space-between" wrap="nowrap" w="100%">
|
|
||||||
<Title order={4} ta="center" style={{ flex: 1 }}>
|
|
||||||
Server Settings
|
|
||||||
</Title>
|
|
||||||
<CloseButton onClick={fn()} />
|
|
||||||
</Group>
|
|
||||||
}
|
|
||||||
size="lg"
|
|
||||||
centered
|
|
||||||
styles={{ title: { flex: 1 } }}
|
|
||||||
>
|
|
||||||
<ServerSettingsForm
|
|
||||||
settings={{
|
|
||||||
connectionMode: "proxy",
|
|
||||||
headers: [
|
|
||||||
{ key: "Authorization", value: "Bearer token-abc-123" },
|
|
||||||
{ key: "X-Request-Id", value: "req-456" },
|
|
||||||
],
|
|
||||||
metadata: [{ key: "userId", value: "user-789" }],
|
|
||||||
connectionTimeout: 30000,
|
|
||||||
requestTimeout: 60000,
|
|
||||||
oauthClientId: "my-client-id",
|
|
||||||
oauthClientSecret: "super-secret-value",
|
|
||||||
oauthScopes: "read write",
|
|
||||||
}}
|
|
||||||
onConnectionModeChange={fn()}
|
|
||||||
onAddHeader={fn()}
|
|
||||||
onRemoveHeader={fn()}
|
|
||||||
onHeaderChange={fn()}
|
|
||||||
onAddMetadata={fn()}
|
|
||||||
onRemoveMetadata={fn()}
|
|
||||||
onMetadataChange={fn()}
|
|
||||||
onTimeoutChange={fn()}
|
|
||||||
onOAuthChange={fn()}
|
|
||||||
/>
|
|
||||||
</Modal>
|
|
||||||
</UnconnectedView>
|
</UnconnectedView>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue