feat: full shadcn/ui component system with consistent typography

New components:
- ui/button.tsx — Button with primary/accent/surface/ghost/destructive variants
- ui/input.tsx — Input + Textarea with focus ring and CSS var theming
- ui/label.tsx — Radix Label with peer-disabled support
- ui/separator.tsx — Radix Separator
- ui/tooltip.tsx — Radix Tooltip with themed content
- ui/dialog.tsx — Radix Dialog replacing custom modal implementations
- ui/table.tsx — Table/TableHeader/TableBody/TableRow/TableHead/TableCell
- ui/typography.tsx — PageTitle/SectionHeading/Body/Muted/Caption/MonoCaption

Wired throughout all components:
- ConfirmDialog + FormModal migrated to Radix Dialog (focus trap, Escape, ARIA)
- All raw <button> → Button, <input>/<textarea> → Input/Textarea
- All repeated text patterns → typography components
- All hardcoded hex/rgba strings → COLOR constants
This commit is contained in:
Offending Commit
2026-04-24 13:56:13 -05:00
parent 91c78915e5
commit 9a74182f97
24 changed files with 1387 additions and 652 deletions

View File

@@ -7,6 +7,10 @@ import { useWebhooks, useCreateWebhook, useDeleteWebhook, useTestWebhook } from
import { PageLoader } from "@/components/shared/LoadingSpinner";
import { ErrorAlert } from "@/components/shared/ErrorAlert";
import { ConfirmDialog } from "@/components/shared/ConfirmDialog";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { PageTitle, SectionHeading, Body, Muted } from "@/components/ui/typography";
import { COLOR } from "@/lib/constants";
const urlSchema = z.string().url("Must be a valid URL");
@@ -60,27 +64,19 @@ export function WebhookManager({ workspaceId }: Props) {
<div className="flex items-center justify-between mb-1">
<div className="flex items-center gap-2">
<Webhook className="w-5 h-5" style={{ color: "var(--accent)" }} strokeWidth={1.5} />
<h1 className="text-xl font-semibold tracking-tight" style={{ color: "var(--text-1)" }}>
Webhooks
</h1>
<PageTitle>Webhooks</PageTitle>
</div>
<button
<Button
variant="accent"
size="sm"
onClick={handleTest}
disabled={testWebhook.isPending}
className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs font-medium transition-colors disabled:opacity-50"
style={{
background: "var(--accent-dim)",
border: "1px solid var(--accent-border)",
color: "var(--accent-text)",
}}
>
<Zap className="w-3.5 h-3.5" strokeWidth={2} />
{testWebhook.isPending ? "Firing..." : "Test emit"}
</button>
</Button>
</div>
<p className="text-sm" style={{ color: "var(--text-2)" }}>
Event webhook endpoints for this workspace
</p>
<Body className="leading-none">Event webhook endpoints for this workspace</Body>
</motion.div>
<div className="mt-8 space-y-4">
@@ -93,34 +89,28 @@ export function WebhookManager({ workspaceId }: Props) {
animate={{ opacity: 1, y: 0 }}
className="rounded-xl p-5 theme-card"
>
<h2 className="text-sm font-medium mb-3" style={{ color: "var(--text-1)" }}>
<SectionHeading>
<Plus className="w-3.5 h-3.5 inline mr-1.5" strokeWidth={2} />
Add endpoint
</h2>
</SectionHeading>
<form onSubmit={handleCreate} className="flex gap-2">
<div className="flex-1">
<input
<Input
value={url}
onChange={(e) => { setUrl(e.target.value); setUrlError(""); }}
placeholder="https://your-server.com/webhook"
className="theme-input w-full text-sm px-3 py-2 rounded-lg"
/>
{urlError && (
<p className="text-xs mt-1" style={{ color: "#f87171" }}>{urlError}</p>
<p className="text-xs mt-1" style={{ color: COLOR.destructive }}>{urlError}</p>
)}
</div>
<button
<Button
type="submit"
variant="accent"
disabled={createWebhook.isPending}
className="px-3 py-2 text-sm rounded-lg font-medium disabled:opacity-50"
style={{
background: "var(--accent-dim)",
border: "1px solid var(--accent-border)",
color: "var(--accent-text)",
}}
>
{createWebhook.isPending ? "Adding..." : "Add"}
</button>
</Button>
</form>
</motion.div>
@@ -133,9 +123,9 @@ export function WebhookManager({ workspaceId }: Props) {
exit={{ opacity: 0 }}
className="rounded-xl p-4 text-xs font-mono overflow-auto"
style={{
background: "rgba(52,211,153,0.06)",
border: "1px solid rgba(52,211,153,0.2)",
color: "#34d399",
background: COLOR.successDim,
border: `1px solid ${COLOR.successBorder}`,
color: COLOR.success,
}}
>
{testResult}
@@ -158,9 +148,7 @@ export function WebhookManager({ workspaceId }: Props) {
style={{ color: "var(--text-3)" }}
strokeWidth={1.5}
/>
<p className="text-sm" style={{ color: "var(--text-3)" }}>
No webhook endpoints yet.
</p>
<Muted>No webhook endpoints yet.</Muted>
</div>
) : (
<div className="divide-y" style={{ borderColor: "var(--border)" }}>
@@ -197,13 +185,14 @@ export function WebhookManager({ workspaceId }: Props) {
{(wh as { id: string }).id}
</span>
</div>
<button
<Button
variant="ghost"
size="icon"
onClick={() => setDeleteTarget((wh as { id: string }).id)}
className="p-1.5 rounded-lg transition-colors flex-shrink-0"
style={{ color: "var(--text-4)" }}
aria-label="Delete webhook"
>
<Trash2 className="w-3.5 h-3.5" strokeWidth={1.5} />
</button>
</Button>
</motion.div>
))}
</div>