Overhaul PodMan UI with shadcn

This commit is contained in:
Yahya Alhinai
2026-06-28 01:04:25 +00:00
parent 80cc89072f
commit 5185845090
89 changed files with 13537 additions and 628 deletions
+80 -55
View File
@@ -1,14 +1,29 @@
import { useState } from 'react';
import { PlusIcon } from 'lucide-react';
import type { PodInput } from '@podman/shared';
import { Button } from '@/components/ui/button';
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from '@/components/ui/card';
import { Field, FieldGroup, FieldLabel } from '@/components/ui/field';
import { Input } from '@/components/ui/input';
import { cn } from '@/lib/utils';
export function CreatePodForm({
busy,
onCreate,
compact = false,
}: {
busy: boolean;
onCreate: (input: PodInput) => Promise<void>;
compact?: boolean;
}) {
const [open, setOpen] = useState(false);
const [open, setOpen] = useState(compact);
const [name, setName] = useState('');
const [repo, setRepo] = useState('karti-ai/podman');
const [description, setDescription] = useState('');
@@ -23,79 +38,89 @@ export function CreatePodForm({
description: description.trim(),
members: firstMember.trim() ? [firstMember.trim()] : [],
});
// only clear + close on success; on error keep the user's input
setName('');
setDescription('');
setFirstMember('');
setOpen(false);
if (!compact) setOpen(false);
} catch {
/* error surfaced by parent; keep inputs so nothing is lost */
/* parent owns the visible error */
}
}
if (!open) {
return (
<button
className="group flex min-h-[280px] flex-col items-center justify-center gap-3 rounded-lg border border-dashed border-slate-300 bg-white p-6 text-center text-slate-600 shadow-sm transition hover:border-blue-300 hover:bg-blue-50/40"
className="group flex min-h-64 flex-col items-center justify-center gap-3 rounded-xl border border-dashed bg-card p-6 text-center transition hover:bg-muted/30"
onClick={() => setOpen(true)}
>
<span className="grid h-11 w-11 place-items-center rounded-lg border border-slate-200 bg-slate-50 text-2xl font-light text-slate-900 transition group-hover:border-blue-200 group-hover:bg-blue-50">
+
<span className="grid size-9 place-items-center rounded-lg bg-muted transition group-hover:bg-background">
<PlusIcon className="size-4 text-muted-foreground" />
</span>
<span className="text-base font-semibold text-slate-950">New pod</span>
<span className="max-w-[18rem] text-sm leading-5 text-slate-500">
Add a workspace for a feature team, spike, or demo flow.
<span className="text-sm font-medium">New pod</span>
<span className="max-w-56 text-sm text-muted-foreground">
Add a focused room for one repo or feature stream.
</span>
</button>
);
}
return (
<div className="flex min-h-[280px] flex-col gap-3 rounded-lg border border-slate-200 bg-white p-4 shadow-sm">
<div>
<h3 className="font-semibold text-slate-950">New pod</h3>
<p className="text-xs text-slate-500">Name, repository, and first member.</p>
</div>
<input
className="rounded-md border border-slate-200 bg-white px-3 py-2 text-sm text-slate-950 outline-none transition placeholder:text-slate-400 focus:border-blue-400"
placeholder="Pod name (e.g. Mobile Pod)"
value={name}
autoFocus
onChange={(e) => setName(e.target.value)}
/>
<input
className="rounded-md border border-slate-200 bg-white px-3 py-2 text-sm text-slate-950 outline-none transition placeholder:text-slate-400 focus:border-blue-400"
placeholder="owner/repo"
value={repo}
onChange={(e) => setRepo(e.target.value)}
/>
<input
className="rounded-md border border-slate-200 bg-white px-3 py-2 text-sm text-slate-950 outline-none transition placeholder:text-slate-400 focus:border-blue-400"
placeholder="Description (optional)"
value={description}
onChange={(e) => setDescription(e.target.value)}
/>
<input
className="rounded-md border border-slate-200 bg-white px-3 py-2 text-sm text-slate-950 outline-none transition placeholder:text-slate-400 focus:border-blue-400"
placeholder="Your name (optional first member)"
value={firstMember}
onChange={(e) => setFirstMember(e.target.value)}
/>
<div className="mt-auto flex gap-2">
<button
className="rounded-md bg-blue-600 px-3 py-2 text-sm font-semibold text-white hover:bg-blue-500 disabled:opacity-50"
onClick={submit}
disabled={busy || !name.trim()}
>
<Card className={cn(compact && 'w-full border-0 shadow-none ring-0')}>
<CardHeader>
<CardTitle>New pod</CardTitle>
<CardDescription>Keep the room name short and specific.</CardDescription>
</CardHeader>
<CardContent>
<FieldGroup>
<Field>
<FieldLabel htmlFor="pod-name">Name</FieldLabel>
<Input
id="pod-name"
placeholder="Payments"
value={name}
autoFocus
onChange={(e) => setName(e.target.value)}
/>
</Field>
<Field>
<FieldLabel htmlFor="pod-repo">Repository</FieldLabel>
<Input
id="pod-repo"
placeholder="owner/repo"
value={repo}
onChange={(e) => setRepo(e.target.value)}
/>
</Field>
<Field>
<FieldLabel htmlFor="pod-description">Summary</FieldLabel>
<Input
id="pod-description"
placeholder="Optional"
value={description}
onChange={(e) => setDescription(e.target.value)}
/>
</Field>
<Field>
<FieldLabel htmlFor="pod-member">First member</FieldLabel>
<Input
id="pod-member"
placeholder="Optional"
value={firstMember}
onChange={(e) => setFirstMember(e.target.value)}
/>
</Field>
</FieldGroup>
</CardContent>
<CardFooter className="justify-end gap-2">
{!compact && (
<Button variant="outline" onClick={() => setOpen(false)}>
Cancel
</Button>
)}
<Button onClick={submit} disabled={busy || !name.trim()}>
Create
</button>
<button
className="rounded-md border border-slate-200 px-3 py-2 text-sm text-slate-700 hover:bg-slate-50"
onClick={() => setOpen(false)}
>
Cancel
</button>
</div>
</div>
</Button>
</CardFooter>
</Card>
);
}