fix: refine create page layout spacing

This commit is contained in:
a.tolmachev
2026-03-26 22:56:49 +03:00
parent ff3a44eef6
commit 3a17f9151f
3 changed files with 169 additions and 142 deletions
@@ -345,158 +345,165 @@ export function OperationForm() {
creationMutation.mutate(values);
})}
>
<section className="builder-card">
<header className="builder-card-header">
<div>
<h2>1 Protocol</h2>
<p>One tool maps to exactly one upstream method.</p>
</div>
</header>
<div className="builder-card-body">
<div className="proto-grid">
{protocolCards.map((card) => {
const isSelected = activeProtocol === card.protocol;
const selectedClassName = isSelected
? `proto-card selected-${card.protocol === "graphql" ? "gql" : card.protocol}`
: "proto-card";
<div className="builder-content">
<section className="builder-card">
<header className="builder-card-header">
<div>
<h2>1 Protocol</h2>
<p>One tool maps to exactly one upstream method.</p>
</div>
</header>
<div className="builder-card-body">
<div className="proto-grid">
{protocolCards.map((card) => {
const isSelected = activeProtocol === card.protocol;
const selectedClassName = isSelected
? `proto-card selected-${card.protocol === "graphql" ? "gql" : card.protocol}`
: "proto-card";
return (
<button
className={selectedClassName}
key={card.protocol}
onClick={() => {
form.setValue("protocol", card.protocol, {
shouldDirty: true,
shouldValidate: true,
});
}}
type="button"
>
<div className="proto-card-top">
<div className={`proto-icon pi-${card.protocol === "graphql" ? "gql" : card.protocol}`}>
{card.protocol === "graphql" ? "GQL" : card.protocol.toUpperCase()}
return (
<button
className={selectedClassName}
key={card.protocol}
onClick={() => {
form.setValue("protocol", card.protocol, {
shouldDirty: true,
shouldValidate: true,
});
}}
type="button"
>
<div className="proto-card-top">
<div className={`proto-icon pi-${card.protocol === "graphql" ? "gql" : card.protocol}`}>
{card.protocol === "graphql" ? "GQL" : card.protocol.toUpperCase()}
</div>
<div className="proto-radio" />
</div>
<div className="proto-radio" />
</div>
<div className="proto-card-copy">
<h3>{card.title}</h3>
<p>{card.description}</p>
</div>
</button>
);
})}
<div className="proto-card-copy">
<h3>{card.title}</h3>
<p>{card.description}</p>
</div>
</button>
);
})}
</div>
</div>
</div>
</section>
</section>
<section className="builder-card">
<header className="builder-card-header">
<div>
<h2>2 Tool identity</h2>
<p>Name and description visible to the LLM at runtime.</p>
<section className="builder-card">
<header className="builder-card-header">
<div>
<h2>2 Tool identity</h2>
<p>Name and description visible to the LLM at runtime.</p>
</div>
<span className="status-pill status-draft">draft</span>
</header>
<div className="builder-card-body">
<div className="form-grid">{commonFields.map(renderField)}</div>
</div>
<span className="status-pill status-draft">draft</span>
</header>
<div className="builder-card-body">
<div className="form-grid">{commonFields.map(renderField)}</div>
</div>
</section>
</section>
<section className="builder-card">
<header className="builder-card-header">
<div>
<h2>3 Upstream target</h2>
<p>Protocol-specific target configuration exposed through one MCP tool.</p>
</div>
<span className={`protocol-pill protocol-${activeProtocol}`}>{activeProtocol}</span>
</header>
<div className="builder-card-body">
<div className="form-grid">
{protocolFields.map(renderField)}
{activeProtocol === "grpc" ? (
<label className="field-block field-block-wide">
<span>Descriptor set file</span>
<small className="field-hint">
Upload a compiled descriptor-set file to autofill the base64 payload.
</small>
<input
className="input"
type="file"
accept=".bin,.pb,.desc"
onChange={(event) => {
void handleDescriptorFileChange(event.target.files?.[0]);
}}
/>
</label>
) : null}
{activeProtocol === "rest" ? renderField({
name: "restStaticHeadersText",
label: "Static headers",
description:
"Always sent for this REST target before dynamic request headers are merged.",
rows: 8,
wide: true,
code: true,
}) : null}
</div>
<section className="builder-card">
<header className="builder-card-header">
<div>
<h2>3 Upstream target</h2>
<p>Protocol-specific target configuration exposed through one MCP tool.</p>
</div>
<span className={`protocol-pill protocol-${activeProtocol}`}>{activeProtocol}</span>
</header>
<div className="builder-card-body">
<div className="form-grid">
{protocolFields.map(renderField)}
{activeProtocol === "grpc" ? (
<label className="field-block field-block-wide">
<span>Descriptor set file</span>
<small className="field-hint">
Upload a compiled descriptor-set file to autofill the base64 payload.
</small>
<input
className="input"
type="file"
accept=".bin,.pb,.desc"
onChange={(event) => {
void handleDescriptorFileChange(event.target.files?.[0]);
}}
/>
</label>
) : null}
{activeProtocol === "rest"
? renderField({
name: "restStaticHeadersText",
label: "Static headers",
description:
"Always sent for this REST target before dynamic request headers are merged.",
rows: 8,
wide: true,
code: true,
})
: null}
</div>
<div className="info-banner">
<div className="info-dot" />
<p>
Headers and auth stay separate from path and payload mapping. Configure shared
transport headers in the execution section below.
</p>
<div className="info-banner">
<div className="info-dot" />
<p>
Headers and auth stay separate from path and payload mapping. Configure shared
transport headers in the execution section below.
</p>
</div>
</div>
</div>
</section>
</section>
<section className="builder-card">
<header className="builder-card-header">
<div>
<h2>4 Contract schemas</h2>
<p>Protocol-agnostic schemas for MCP input and output payloads.</p>
<section className="builder-card">
<header className="builder-card-header">
<div>
<h2>4 Contract schemas</h2>
<p>Protocol-agnostic schemas for MCP input and output payloads.</p>
</div>
</header>
<div className="builder-card-body">
<div className="form-grid">{schemaFields.map(renderField)}</div>
</div>
</header>
<div className="builder-card-body">
<div className="form-grid">{schemaFields.map(renderField)}</div>
</div>
</section>
</section>
<section className="builder-card">
<header className="builder-card-header">
<div>
<h2>5 Mapping and execution</h2>
<p>Translate MCP input to request fields, then map upstream output back to tool output.</p>
</div>
</header>
<div className="builder-card-body">
<div className="section-divider">
<span className="section-divider-label">Input Request</span>
<div className="section-divider-line" />
</div>
<div className="form-grid">{mappingFields.slice(0, 1).map(renderField)}</div>
<section className="builder-card">
<header className="builder-card-header">
<div>
<h2>5 Mapping and execution</h2>
<p>
Translate MCP input to request fields, then map upstream output back to tool
output.
</p>
</div>
</header>
<div className="builder-card-body">
<div className="section-divider">
<span className="section-divider-label">Input Request</span>
<div className="section-divider-line" />
</div>
<div className="form-grid">{mappingFields.slice(0, 1).map(renderField)}</div>
<div className="section-divider">
<span className="section-divider-label">Response Output</span>
<div className="section-divider-line" />
</div>
<div className="form-grid">{mappingFields.slice(1).map(renderField)}</div>
<div className="section-divider">
<span className="section-divider-label">Response Output</span>
<div className="section-divider-line" />
</div>
<div className="form-grid">{mappingFields.slice(1).map(renderField)}</div>
<div className="section-divider">
<span className="section-divider-label">Headers And Runtime</span>
<div className="section-divider-line" />
<div className="section-divider">
<span className="section-divider-label">Headers And Runtime</span>
<div className="section-divider-line" />
</div>
<div className="form-grid">{headerFields.map(renderField)}</div>
</div>
<div className="form-grid">{headerFields.map(renderField)}</div>
</div>
</section>
</section>
{creationMutation.error ? (
<div className="feedback-card feedback-error">
{creationMutation.error instanceof ApiError
? creationMutation.error.message
: "Failed to create operation"}
</div>
) : null}
{creationMutation.error ? (
<div className="feedback-card feedback-error">
{creationMutation.error instanceof ApiError
? creationMutation.error.message
: "Failed to create operation"}
</div>
) : null}
</div>
<div className="sticky-action-bar">
<div className="sticky-action-copy">
+1
View File
@@ -60,6 +60,7 @@ export function AppShell({ children }: AppShellProps) {
<p className="sidebar-label">Workspace</p>
<nav className="sidebar-nav">
<NavLink
end
to="/operations"
className={({ isActive }) =>
isActive ? "nav-link nav-link-active" : "nav-link"
+24 -5
View File
@@ -324,7 +324,8 @@ button {
}
.page-header {
margin-bottom: 24px;
max-width: 1240px;
margin: 0 auto 24px;
}
.page-header h1 {
@@ -405,7 +406,14 @@ button {
.builder-stack {
width: 100%;
max-width: 1320px;
}
.builder-content {
width: 100%;
max-width: 1240px;
margin: 0 auto;
display: grid;
gap: 16px;
}
.page-section,
@@ -470,6 +478,11 @@ button {
padding: 0 24px 24px;
}
.builder-content .form-grid {
grid-template-columns: repeat(2, minmax(280px, 420px));
justify-content: space-between;
}
.proto-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
@@ -859,8 +872,9 @@ button {
align-items: center;
justify-content: space-between;
gap: 16px;
padding: 12px 20px;
border-radius: var(--radius-xl);
width: 100%;
padding: 12px 28px;
border-radius: 0;
background: rgba(255, 255, 255, 0.9);
backdrop-filter: blur(16px);
border: 1px solid var(--border);
@@ -1114,7 +1128,7 @@ pre {
padding: 20px;
}
.builder-stack {
.builder-content {
max-width: none;
}
}
@@ -1144,4 +1158,9 @@ pre {
padding-left: 16px;
padding-right: 16px;
}
.sticky-action-bar {
padding-left: 16px;
padding-right: 16px;
}
}