feat(web/ui): refactor component inline styles into utility classes

Pass 2 of the web UI spacing/typography/visual-rhythm rework.

- Add layout, spacing, typography, visual, card, and component utilities
- Add form-section, form-row, form-help, text-error, alert-success
- Unify .form-group and .form-field; add .status-badge family
- Alias legacy button classes to .btn primitives
- Refactor ToolTypeListSidebar and ConfigProfileListSidebar to use .sidebar
  and var(--sidebar-width) instead of hardcoded 280px
- Refactor ToolTypeEditorPanel, ConfigProfileEditorPanel,
  git-mount-editor, and manifest-editor to use utility classes

Quality gates: npm run typecheck, npm run lint, npm run build pass.
Inline style blocks in target components reduced from 198 to 11.
This commit is contained in:
Developer
2026-06-16 14:47:37 +00:00
parent 38d116dcf7
commit 5d5b39bec1
8 changed files with 957 additions and 571 deletions
@@ -284,12 +284,12 @@ export const ManifestEditor = ({
};
return (
<div className="stack" style={{ gap: "1.5rem" }}>
<div className="stack stack-lg">
{/* Base Image */}
<div className="card stack" style={{ gap: "1rem", padding: "1rem" }}>
<h4 style={{ margin: 0 }}>Base Image</h4>
<div className="row" style={{ gap: "1rem" }}>
<div className="form-group" style={{ flex: 1 }}>
<div className="card-md stack stack-md">
<h4 className="m-0">Base Image</h4>
<div className="form-row row-md">
<div className="form-group">
<label>Base Definition</label>
<select
value={baseDefinitionId}
@@ -307,7 +307,7 @@ export const ManifestEditor = ({
))}
</select>
</div>
<div className="form-group" style={{ flex: 1 }}>
<div className="form-group">
<label>Custom Base Image</label>
<input
type="text"
@@ -325,8 +325,8 @@ export const ManifestEditor = ({
</div>
{/* Packages */}
<div className="card stack" style={{ gap: "1rem", padding: "1rem" }}>
<h4 style={{ margin: 0 }}>Packages</h4>
<div className="card-md stack stack-md">
<h4 className="m-0">Packages</h4>
<div className="form-group">
<label>Node.js Version</label>
@@ -342,9 +342,9 @@ export const ManifestEditor = ({
<div>
<label>APT Packages</label>
<div className="stack" style={{ gap: "0.5rem", marginTop: "0.5rem" }}>
<div className="stack stack-sm mt-2">
{aptPackages.map((pkg, idx) => (
<div key={idx} className="row" style={{ gap: "0.5rem" }}>
<div key={idx} className="form-row row-sm">
<input
type="text"
value={pkg.name}
@@ -365,8 +365,7 @@ export const ManifestEditor = ({
<button
type="button"
onClick={addAptPackage}
className="button-secondary"
style={{ width: "fit-content" }}
className="button-secondary w-fit"
>
<Icon name="add" size="sm" /> Add APT Package
</button>
@@ -375,9 +374,9 @@ export const ManifestEditor = ({
<div>
<label>NPM Global Packages</label>
<div className="stack" style={{ gap: "0.5rem", marginTop: "0.5rem" }}>
<div className="stack stack-sm mt-2">
{npmPackages.map((pkg, idx) => (
<div key={idx} className="row" style={{ gap: "0.5rem" }}>
<div key={idx} className="form-row row-sm">
<input
type="text"
value={pkg.name}
@@ -398,8 +397,7 @@ export const ManifestEditor = ({
<button
type="button"
onClick={addNpmPackage}
className="button-secondary"
style={{ width: "fit-content" }}
className="button-secondary w-fit"
>
<Icon name="add" size="sm" /> Add NPM Package
</button>
@@ -408,9 +406,9 @@ export const ManifestEditor = ({
<div>
<label>Pip Packages</label>
<div className="stack" style={{ gap: "0.5rem", marginTop: "0.5rem" }}>
<div className="stack stack-sm mt-2">
{pipPackages.map((pkg, idx) => (
<div key={idx} className="row" style={{ gap: "0.5rem" }}>
<div key={idx} className="form-row row-sm">
<input
type="text"
value={pkg.name}
@@ -431,8 +429,7 @@ export const ManifestEditor = ({
<button
type="button"
onClick={addPipPackage}
className="button-secondary"
style={{ width: "fit-content" }}
className="button-secondary w-fit"
>
<Icon name="add" size="sm" /> Add Pip Package
</button>
@@ -441,10 +438,10 @@ export const ManifestEditor = ({
</div>
{/* Runtime User */}
<div className="card stack" style={{ gap: "1rem", padding: "1rem" }}>
<h4 style={{ margin: 0 }}>Runtime User</h4>
<div className="row" style={{ gap: "1rem" }}>
<div className="form-group" style={{ flex: 1 }}>
<div className="card-md stack stack-md">
<h4 className="m-0">Runtime User</h4>
<div className="form-row row-md">
<div className="form-group">
<label>User Name</label>
<input
type="text"
@@ -453,7 +450,7 @@ export const ManifestEditor = ({
className="form-input"
/>
</div>
<div className="form-group" style={{ flex: 1 }}>
<div className="form-group">
<label>UID</label>
<input
type="number"
@@ -462,7 +459,7 @@ export const ManifestEditor = ({
className="form-input"
/>
</div>
<div className="form-group" style={{ flex: 1 }}>
<div className="form-group">
<label>GID</label>
<input
type="number"
@@ -475,11 +472,11 @@ export const ManifestEditor = ({
</div>
{/* Environment */}
<div className="card stack" style={{ gap: "1rem", padding: "1rem" }}>
<h4 style={{ margin: 0 }}>Environment Variables</h4>
<div className="stack" style={{ gap: "0.5rem" }}>
<div className="card-md stack stack-md">
<h4 className="m-0">Environment Variables</h4>
<div className="stack stack-sm">
{envVars.map((ev, idx) => (
<div key={idx} className="row" style={{ gap: "0.5rem" }}>
<div key={idx} className="form-row row-sm">
<input
type="text"
value={ev.key}
@@ -507,8 +504,7 @@ export const ManifestEditor = ({
<button
type="button"
onClick={addEnvVar}
className="button-secondary"
style={{ width: "fit-content" }}
className="button-secondary w-fit"
>
<Icon name="add" size="sm" /> Add Env Var
</button>
@@ -516,26 +512,20 @@ export const ManifestEditor = ({
</div>
{/* Build Scripts */}
<div className="card stack" style={{ gap: "1rem", padding: "1rem" }}>
<h4 style={{ margin: 0 }}>Build Scripts (run during docker build)</h4>
<div className="stack" style={{ gap: "0.5rem" }}>
<div className="card-md stack stack-md">
<h4 className="m-0">Build Scripts (run during docker build)</h4>
<div className="stack stack-sm">
{buildScripts.map((script, idx) => (
<div
key={idx}
className="row"
style={{ gap: "0.5rem", alignItems: "flex-start" }}
className="form-row row-sm items-start"
>
<textarea
value={script}
onChange={(e) => updateBuildScript(idx, e.target.value)}
placeholder="git config --global user.email 'dev@example.com'"
className="form-input"
className="form-input font-mono text-sm flex-1"
rows={2}
style={{
fontFamily: "monospace",
fontSize: "0.8125rem",
flex: 1,
}}
/>
<button
type="button"
@@ -550,8 +540,7 @@ export const ManifestEditor = ({
<button
type="button"
onClick={addBuildScript}
className="button-secondary"
style={{ width: "fit-content" }}
className="button-secondary w-fit"
>
<Icon name="add" size="sm" /> Add Build Script
</button>
@@ -559,28 +548,22 @@ export const ManifestEditor = ({
</div>
{/* Startup Scripts */}
<div className="card stack" style={{ gap: "1rem", padding: "1rem" }}>
<h4 style={{ margin: 0 }}>
<div className="card-md stack stack-md">
<h4 className="m-0">
Startup Scripts (run when container starts)
</h4>
<div className="stack" style={{ gap: "0.5rem" }}>
<div className="stack stack-sm">
{startupScripts.map((script, idx) => (
<div
key={idx}
className="row"
style={{ gap: "0.5rem", alignItems: "flex-start" }}
className="form-row row-sm items-start"
>
<textarea
value={script}
onChange={(e) => updateStartupScript(idx, e.target.value)}
placeholder="chown -R user:user /workspace"
className="form-input"
className="form-input font-mono text-sm flex-1"
rows={2}
style={{
fontFamily: "monospace",
fontSize: "0.8125rem",
flex: 1,
}}
/>
<button
type="button"
@@ -595,8 +578,7 @@ export const ManifestEditor = ({
<button
type="button"
onClick={addStartupScript}
className="button-secondary"
style={{ width: "fit-content" }}
className="button-secondary w-fit"
>
<Icon name="add" size="sm" /> Add Startup Script
</button>
@@ -604,21 +586,15 @@ export const ManifestEditor = ({
</div>
{/* Mounts */}
<div className="card stack" style={{ gap: "1rem", padding: "1rem" }}>
<h4 style={{ margin: 0 }}>Mount Schema</h4>
<div className="stack" style={{ gap: "1rem" }}>
<div className="card-md stack stack-md">
<h4 className="m-0">Mount Schema</h4>
<div className="stack stack-md">
{mounts.map((mount, idx) => (
<div
key={idx}
className="stack"
style={{
gap: "0.5rem",
padding: "0.75rem",
border: "1px solid var(--border)",
borderRadius: "0.375rem",
}}
className="stack stack-sm p-3 border rounded-md"
>
<div className="row" style={{ gap: "0.5rem" }}>
<div className="form-row row-sm">
<input
type="text"
value={mount.name}
@@ -655,15 +631,8 @@ export const ManifestEditor = ({
<Icon name="delete" size="sm" />
</button>
</div>
<div className="row" style={{ gap: "0.5rem" }}>
<label
style={{
display: "flex",
alignItems: "center",
gap: "0.25rem",
fontSize: "0.875rem",
}}
>
<div className="form-row row-sm flex-wrap">
<label className="checkbox-label text-sm min-h-0">
<input
type="checkbox"
checked={mount.writable}
@@ -673,14 +642,7 @@ export const ManifestEditor = ({
/>
Writable
</label>
<label
style={{
display: "flex",
alignItems: "center",
gap: "0.25rem",
fontSize: "0.875rem",
}}
>
<label className="checkbox-label text-sm min-h-0">
<input
type="checkbox"
checked={mount.readonly}
@@ -734,8 +696,7 @@ export const ManifestEditor = ({
<button
type="button"
onClick={addMount}
className="button-secondary"
style={{ width: "fit-content" }}
className="button-secondary w-fit"
>
<Icon name="add" size="sm" /> Add Mount
</button>
@@ -743,10 +704,10 @@ export const ManifestEditor = ({
</div>
{/* Runtime */}
<div className="card stack" style={{ gap: "1rem", padding: "1rem" }}>
<h4 style={{ margin: 0 }}>Runtime</h4>
<div className="row" style={{ gap: "1rem" }}>
<div className="form-group" style={{ flex: 1 }}>
<div className="card-md stack stack-md">
<h4 className="m-0">Runtime</h4>
<div className="form-row row-md">
<div className="form-group">
<label>Command</label>
<input
type="text"
@@ -756,7 +717,7 @@ export const ManifestEditor = ({
className="form-input"
/>
</div>
<div className="form-group" style={{ flex: 1 }}>
<div className="form-group">
<label>Working Directory</label>
<input
type="text"
@@ -767,10 +728,8 @@ export const ManifestEditor = ({
/>
</div>
</div>
<div className="row" style={{ gap: "1rem" }}>
<label
style={{ display: "flex", alignItems: "center", gap: "0.5rem" }}
>
<div className="form-row row-md">
<label className="checkbox-label min-h-0">
<input
type="checkbox"
checked={stdinOpen}
@@ -778,9 +737,7 @@ export const ManifestEditor = ({
/>
stdin_open
</label>
<label
style={{ display: "flex", alignItems: "center", gap: "0.5rem" }}
>
<label className="checkbox-label min-h-0">
<input
type="checkbox"
checked={tty}
@@ -792,15 +749,9 @@ export const ManifestEditor = ({
</div>
{/* Preview */}
<div className="card stack" style={{ gap: "1rem", padding: "1rem" }}>
<div
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
}}
>
<h4 style={{ margin: 0 }}>Live Preview</h4>
<div className="card-md stack stack-md">
<div className="row justify-between items-center">
<h4 className="m-0">Live Preview</h4>
<button
type="button"
onClick={handlePreview}
@@ -813,40 +764,16 @@ export const ManifestEditor = ({
</div>
{previewError && <p className="text-error">{previewError}</p>}
{preview && (
<div className="stack" style={{ gap: "1rem" }}>
<div className="stack stack-md">
<div>
<label style={{ fontWeight: 600, fontSize: "0.875rem" }}>
Dockerfile
</label>
<pre
style={{
background: "var(--code-bg, #1e1e1e)",
color: "var(--code-fg, #d4d4d4)",
padding: "1rem",
borderRadius: "0.375rem",
overflow: "auto",
fontSize: "0.8125rem",
maxHeight: "300px",
}}
>
<label className="font-semibold text-sm">Dockerfile</label>
<pre className="code-block" style={{ maxHeight: "300px" }}>
{preview.dockerfile}
</pre>
</div>
<div>
<label style={{ fontWeight: 600, fontSize: "0.875rem" }}>
Compose
</label>
<pre
style={{
background: "var(--code-bg, #1e1e1e)",
color: "var(--code-fg, #d4d4d4)",
padding: "1rem",
borderRadius: "0.375rem",
overflow: "auto",
fontSize: "0.8125rem",
maxHeight: "200px",
}}
>
<label className="font-semibold text-sm">Compose</label>
<pre className="code-block" style={{ maxHeight: "200px" }}>
{preview.compose}
</pre>
</div>