feat(frontend): compact table density (32px row) global

Add table-compact class to all 4 list surfaces: EngagementsListPage,
TemplatesListPage, UsersAdminPage, SimulationList. Remove inline
px-xl py-md from th/td — recipe handles padding. Row border-b moved
to recipe. WCAG SC 2.5.5 tradeoff accepted (BAS operator tool).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Knacky
2026-06-21 22:11:24 +02:00
parent 9a8c5f52ab
commit 1819089925
4 changed files with 50 additions and 50 deletions

View File

@@ -172,25 +172,25 @@ export function SimulationList({ engagementId }: SimulationListProps): JSX.Eleme
</div>
<div className="card-product overflow-hidden p-0">
<table className="w-full text-left">
<table className="table-compact w-full text-left">
<thead className="bg-cloud border-b border-hairline">
<tr className="text-[12px] uppercase tracking-[0.5px] text-graphite">
<th className="px-xl py-md">Name</th>
<th className="px-xl py-md">MITRE</th>
<th className="px-xl py-md">Status</th>
<th className="px-xl py-md">Executed at</th>
<tr>
<th>Name</th>
<th>MITRE</th>
<th>Status</th>
<th>Executed at</th>
</tr>
</thead>
<tbody>
{data.map((sim) => (
<tr
key={sim.id}
className="border-b border-hairline last:border-0 hover:bg-cloud cursor-pointer"
className="hover:bg-cloud cursor-pointer"
onClick={() =>
navigate(`/engagements/${engagementId}/simulations/${sim.id}/edit`)
}
>
<td className="px-xl py-md">
<td>
<Link
to={`/engagements/${engagementId}/simulations/${sim.id}/edit`}
className="text-ink font-medium hover:underline"
@@ -199,7 +199,7 @@ export function SimulationList({ engagementId }: SimulationListProps): JSX.Eleme
{sim.name}
</Link>
</td>
<td className="px-xl py-md text-charcoal text-[14px] font-mono">
<td className="text-charcoal font-mono">
{(() => {
const items = [
...(sim.tactics ?? []).map((t) => t.id),
@@ -210,10 +210,10 @@ export function SimulationList({ engagementId }: SimulationListProps): JSX.Eleme
return `${items[0]} +${items.length - 1}`;
})()}
</td>
<td className="px-xl py-md">
<td>
<SimulationStatusBadge status={sim.status} />
</td>
<td className="px-xl py-md text-charcoal text-[14px] font-mono">
<td className="text-charcoal font-mono">
{formatDate(sim.executed_at)}
</td>
</tr>

View File

@@ -69,32 +69,32 @@ export function EngagementsListPage(): JSX.Element {
{!isLoading && !isError && data && data.length > 0 ? (
<div className="card-product overflow-hidden p-0">
<table className="w-full text-left">
<table className="table-compact w-full text-left">
<thead className="bg-cloud border-b border-hairline">
<tr className="text-[12px] uppercase tracking-[0.5px] text-graphite">
<th className="px-xl py-md">Name</th>
<th className="px-xl py-md">Status</th>
<th className="px-xl py-md">Start</th>
<th className="px-xl py-md">End</th>
<th className="px-xl py-md">Created by</th>
<th className="px-xl py-md text-right">Actions</th>
<tr>
<th>Name</th>
<th>Status</th>
<th>Start</th>
<th>End</th>
<th>Created by</th>
<th className="text-right">Actions</th>
</tr>
</thead>
<tbody>
{data.map((eng) => (
<tr key={eng.id} className="border-b border-hairline last:border-0">
<td className="px-xl py-md">
<tr key={eng.id}>
<td>
<Link to={`/engagements/${eng.id}`} className="text-ink font-medium hover:underline">
{eng.name}
</Link>
</td>
<td className="px-xl py-md">
<td>
<StatusBadge status={eng.status} />
</td>
<td className="px-xl py-md text-charcoal font-mono">{formatDate(eng.start_date)}</td>
<td className="px-xl py-md text-charcoal font-mono">{formatDate(eng.end_date)}</td>
<td className="px-xl py-md text-charcoal">{eng.created_by.username}</td>
<td className="px-xl py-md text-right">
<td className="text-charcoal font-mono">{formatDate(eng.start_date)}</td>
<td className="text-charcoal font-mono">{formatDate(eng.end_date)}</td>
<td className="text-charcoal">{eng.created_by.username}</td>
<td className="text-right">
<div className="inline-flex gap-sm">
<Link to={`/engagements/${eng.id}`} className="btn-text-link">
View

View File

@@ -69,20 +69,20 @@ export function TemplatesListPage(): JSX.Element {
{!isLoading && !isError && data && data.length > 0 ? (
<div className="card-product overflow-hidden p-0">
<table className="w-full text-left">
<table className="table-compact w-full text-left">
<thead className="bg-cloud border-b border-hairline">
<tr className="text-[12px] uppercase tracking-[0.5px] text-graphite">
<th className="px-xl py-md">Name</th>
<th className="px-xl py-md">MITRE</th>
<th className="px-xl py-md">Created by</th>
<th className="px-xl py-md">Updated</th>
<th className="px-xl py-md text-right">Actions</th>
<tr>
<th>Name</th>
<th>MITRE</th>
<th>Created by</th>
<th>Updated</th>
<th className="text-right">Actions</th>
</tr>
</thead>
<tbody>
{data.map((t) => (
<tr key={t.id} className="border-b border-hairline last:border-0">
<td className="px-xl py-md">
<tr key={t.id}>
<td>
<Link
to={`/admin/templates/${t.id}/edit`}
className="text-ink font-medium hover:underline"
@@ -90,12 +90,12 @@ export function TemplatesListPage(): JSX.Element {
{t.name}
</Link>
</td>
<td className="px-xl py-md text-charcoal text-[14px]">
<td className="text-charcoal">
{mitreCount(t) === 0 ? '—' : mitreCount(t)}
</td>
<td className="px-xl py-md text-charcoal">{t.created_by.username}</td>
<td className="px-xl py-md text-charcoal font-mono">{formatDate(t.updated_at)}</td>
<td className="px-xl py-md text-right">
<td className="text-charcoal">{t.created_by.username}</td>
<td className="text-charcoal font-mono">{formatDate(t.updated_at)}</td>
<td className="text-right">
<div className="inline-flex gap-sm">
<Link to={`/admin/templates/${t.id}/edit`} className="btn-text-link">
Edit

View File

@@ -188,13 +188,13 @@ export function UsersAdminPage(): JSX.Element {
{!list.isLoading && !list.isError && list.data && list.data.length > 0 ? (
<div className="card-product overflow-hidden p-0">
<table className="w-full text-left">
<table className="table-compact w-full text-left">
<thead className="bg-cloud border-b border-hairline">
<tr className="text-[12px] uppercase tracking-[0.5px] text-graphite">
<th className="px-xl py-md">Username</th>
<th className="px-xl py-md">Role</th>
<th className="px-xl py-md">Created</th>
<th className="px-xl py-md text-right">Actions</th>
<tr>
<th>Username</th>
<th>Role</th>
<th>Created</th>
<th className="text-right">Actions</th>
</tr>
</thead>
<tbody>
@@ -204,8 +204,8 @@ export function UsersAdminPage(): JSX.Element {
// Fragment must carry the key — `<>` cannot, which broke
// per-row reconciliation (reset-password state leaked across rows).
<Fragment key={u.id}>
<tr className="border-b border-hairline last:border-0">
<td className="px-xl py-md text-ink">
<tr>
<td className="text-ink">
<span className="font-mono font-medium">{u.username}</span>
{isSelf ? (
<span className="ml-sm font-sans text-[12px] text-graphite">
@@ -213,7 +213,7 @@ export function UsersAdminPage(): JSX.Element {
</span>
) : null}
</td>
<td className="px-xl py-md">
<td>
<Select
value={u.role}
onChange={(e) => onRoleChange(u, e.target.value as Role)}
@@ -222,8 +222,8 @@ export function UsersAdminPage(): JSX.Element {
disabled={patchMutation.isPending}
/>
</td>
<td className="px-xl py-md text-charcoal font-mono">{u.created_at}</td>
<td className="px-xl py-md text-right">
<td className="text-charcoal font-mono">{u.created_at}</td>
<td className="text-right">
<div className="inline-flex gap-sm">
<button
type="button"