Make service/job/program cards fully clickable

Use the stretched-link pattern: the card is relative and the title link
gets an after:absolute after:inset-0 overlay so clicking anywhere on the
card navigates to the detail page. Interactive controls (action buttons,
subdomain/Docs links, ProgramActions) are raised with relative z-10 so they
stay independently clickable. Cards also get a hover border for affordance.
This commit is contained in:
2026-06-30 23:07:57 -07:00
parent ab10ca74fe
commit e1a4692a43
3 changed files with 20 additions and 18 deletions

View File

@@ -19,11 +19,11 @@ export function JobCard({ job, health }: JobCardProps) {
}
return (
<div className="bg-[var(--card)] border border-[var(--border)] rounded-lg p-5">
<div className="relative bg-[var(--card)] border border-[var(--border)] rounded-lg p-5 hover:border-[var(--primary)] transition-colors">
<div className="flex items-start justify-between mb-2">
<Link
to={`/jobs/${job.id}`}
className="text-base font-semibold hover:text-[var(--primary)] transition-colors"
className="text-base font-semibold hover:text-[var(--primary)] transition-colors after:absolute after:inset-0"
>
{job.id}
</Link>
@@ -59,7 +59,7 @@ export function JobCard({ job, health }: JobCardProps) {
</div>
{job.managed && (
<div className="flex items-center gap-1">
<div className="relative z-10 flex items-center gap-1">
{isDown && (
<button
onClick={() => doAction("start")}

View File

@@ -10,11 +10,11 @@ interface ProgramCardProps {
export function ProgramCard({ program }: ProgramCardProps) {
return (
<div className="bg-[var(--card)] border border-[var(--border)] rounded-lg p-5">
<div className="relative bg-[var(--card)] border border-[var(--border)] rounded-lg p-5 hover:border-[var(--primary)] transition-colors">
<div className="flex items-start justify-between mb-2">
<Link
to={`/programs/${program.id}`}
className="text-base font-semibold hover:text-[var(--primary)] transition-colors"
className="text-base font-semibold hover:text-[var(--primary)] transition-colors after:absolute after:inset-0"
>
{program.id}
</Link>
@@ -29,6 +29,7 @@ export function ProgramCard({ program }: ProgramCardProps) {
<p className="text-sm text-[var(--muted)] mb-3">{program.description}</p>
)}
<div className="relative z-10">
<ProgramActions
name={program.id}
actions={program.actions}
@@ -38,5 +39,6 @@ export function ProgramCard({ program }: ProgramCardProps) {
compact
/>
</div>
</div>
)
}

View File

@@ -22,11 +22,11 @@ export function ServiceCard({ service, health }: ServiceCardProps) {
const isDown = health?.status === "down"
return (
<div className="bg-[var(--card)] border border-[var(--border)] rounded-lg p-5">
<div className="relative bg-[var(--card)] border border-[var(--border)] rounded-lg p-5 hover:border-[var(--primary)] transition-colors">
<div className="flex items-start justify-between mb-2">
<Link
to={`/services/${service.id}`}
className="text-base font-semibold hover:text-[var(--primary)] transition-colors"
className="text-base font-semibold hover:text-[var(--primary)] transition-colors after:absolute after:inset-0"
>
{service.id}
</Link>
@@ -61,7 +61,7 @@ export function ServiceCard({ service, health }: ServiceCardProps) {
{service.subdomain && (
<a
href={subdomainUrl(service.subdomain) ?? undefined}
className="flex items-center gap-1 text-[var(--primary)] hover:underline"
className="relative z-10 flex items-center gap-1 text-[var(--primary)] hover:underline"
>
<ExternalLink size={12} />
{service.subdomain}
@@ -70,7 +70,7 @@ export function ServiceCard({ service, health }: ServiceCardProps) {
{service.port && (
<a
href={`http://localhost:${service.port}/docs`}
className="text-[var(--primary)] hover:underline"
className="relative z-10 text-[var(--primary)] hover:underline"
>
Docs
</a>
@@ -78,7 +78,7 @@ export function ServiceCard({ service, health }: ServiceCardProps) {
</div>
{service.managed && (
<div className="flex items-center gap-1">
<div className="relative z-10 flex items-center gap-1">
{isDown && (
<button
onClick={() => doAction("start")}