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:
@@ -172,25 +172,25 @@ export function SimulationList({ engagementId }: SimulationListProps): JSX.Eleme
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="card-product overflow-hidden p-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">
|
<thead className="bg-cloud border-b border-hairline">
|
||||||
<tr className="text-[12px] uppercase tracking-[0.5px] text-graphite">
|
<tr>
|
||||||
<th className="px-xl py-md">Name</th>
|
<th>Name</th>
|
||||||
<th className="px-xl py-md">MITRE</th>
|
<th>MITRE</th>
|
||||||
<th className="px-xl py-md">Status</th>
|
<th>Status</th>
|
||||||
<th className="px-xl py-md">Executed at</th>
|
<th>Executed at</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{data.map((sim) => (
|
{data.map((sim) => (
|
||||||
<tr
|
<tr
|
||||||
key={sim.id}
|
key={sim.id}
|
||||||
className="border-b border-hairline last:border-0 hover:bg-cloud cursor-pointer"
|
className="hover:bg-cloud cursor-pointer"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
navigate(`/engagements/${engagementId}/simulations/${sim.id}/edit`)
|
navigate(`/engagements/${engagementId}/simulations/${sim.id}/edit`)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<td className="px-xl py-md">
|
<td>
|
||||||
<Link
|
<Link
|
||||||
to={`/engagements/${engagementId}/simulations/${sim.id}/edit`}
|
to={`/engagements/${engagementId}/simulations/${sim.id}/edit`}
|
||||||
className="text-ink font-medium hover:underline"
|
className="text-ink font-medium hover:underline"
|
||||||
@@ -199,7 +199,7 @@ export function SimulationList({ engagementId }: SimulationListProps): JSX.Eleme
|
|||||||
{sim.name}
|
{sim.name}
|
||||||
</Link>
|
</Link>
|
||||||
</td>
|
</td>
|
||||||
<td className="px-xl py-md text-charcoal text-[14px] font-mono">
|
<td className="text-charcoal font-mono">
|
||||||
{(() => {
|
{(() => {
|
||||||
const items = [
|
const items = [
|
||||||
...(sim.tactics ?? []).map((t) => t.id),
|
...(sim.tactics ?? []).map((t) => t.id),
|
||||||
@@ -210,10 +210,10 @@ export function SimulationList({ engagementId }: SimulationListProps): JSX.Eleme
|
|||||||
return `${items[0]} +${items.length - 1}`;
|
return `${items[0]} +${items.length - 1}`;
|
||||||
})()}
|
})()}
|
||||||
</td>
|
</td>
|
||||||
<td className="px-xl py-md">
|
<td>
|
||||||
<SimulationStatusBadge status={sim.status} />
|
<SimulationStatusBadge status={sim.status} />
|
||||||
</td>
|
</td>
|
||||||
<td className="px-xl py-md text-charcoal text-[14px] font-mono">
|
<td className="text-charcoal font-mono">
|
||||||
{formatDate(sim.executed_at)}
|
{formatDate(sim.executed_at)}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -69,32 +69,32 @@ export function EngagementsListPage(): JSX.Element {
|
|||||||
|
|
||||||
{!isLoading && !isError && data && data.length > 0 ? (
|
{!isLoading && !isError && data && data.length > 0 ? (
|
||||||
<div className="card-product overflow-hidden p-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">
|
<thead className="bg-cloud border-b border-hairline">
|
||||||
<tr className="text-[12px] uppercase tracking-[0.5px] text-graphite">
|
<tr>
|
||||||
<th className="px-xl py-md">Name</th>
|
<th>Name</th>
|
||||||
<th className="px-xl py-md">Status</th>
|
<th>Status</th>
|
||||||
<th className="px-xl py-md">Start</th>
|
<th>Start</th>
|
||||||
<th className="px-xl py-md">End</th>
|
<th>End</th>
|
||||||
<th className="px-xl py-md">Created by</th>
|
<th>Created by</th>
|
||||||
<th className="px-xl py-md text-right">Actions</th>
|
<th className="text-right">Actions</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{data.map((eng) => (
|
{data.map((eng) => (
|
||||||
<tr key={eng.id} className="border-b border-hairline last:border-0">
|
<tr key={eng.id}>
|
||||||
<td className="px-xl py-md">
|
<td>
|
||||||
<Link to={`/engagements/${eng.id}`} className="text-ink font-medium hover:underline">
|
<Link to={`/engagements/${eng.id}`} className="text-ink font-medium hover:underline">
|
||||||
{eng.name}
|
{eng.name}
|
||||||
</Link>
|
</Link>
|
||||||
</td>
|
</td>
|
||||||
<td className="px-xl py-md">
|
<td>
|
||||||
<StatusBadge status={eng.status} />
|
<StatusBadge status={eng.status} />
|
||||||
</td>
|
</td>
|
||||||
<td className="px-xl py-md text-charcoal font-mono">{formatDate(eng.start_date)}</td>
|
<td className="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="text-charcoal font-mono">{formatDate(eng.end_date)}</td>
|
||||||
<td className="px-xl py-md text-charcoal">{eng.created_by.username}</td>
|
<td className="text-charcoal">{eng.created_by.username}</td>
|
||||||
<td className="px-xl py-md text-right">
|
<td className="text-right">
|
||||||
<div className="inline-flex gap-sm">
|
<div className="inline-flex gap-sm">
|
||||||
<Link to={`/engagements/${eng.id}`} className="btn-text-link">
|
<Link to={`/engagements/${eng.id}`} className="btn-text-link">
|
||||||
View
|
View
|
||||||
|
|||||||
@@ -69,20 +69,20 @@ export function TemplatesListPage(): JSX.Element {
|
|||||||
|
|
||||||
{!isLoading && !isError && data && data.length > 0 ? (
|
{!isLoading && !isError && data && data.length > 0 ? (
|
||||||
<div className="card-product overflow-hidden p-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">
|
<thead className="bg-cloud border-b border-hairline">
|
||||||
<tr className="text-[12px] uppercase tracking-[0.5px] text-graphite">
|
<tr>
|
||||||
<th className="px-xl py-md">Name</th>
|
<th>Name</th>
|
||||||
<th className="px-xl py-md">MITRE</th>
|
<th>MITRE</th>
|
||||||
<th className="px-xl py-md">Created by</th>
|
<th>Created by</th>
|
||||||
<th className="px-xl py-md">Updated</th>
|
<th>Updated</th>
|
||||||
<th className="px-xl py-md text-right">Actions</th>
|
<th className="text-right">Actions</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{data.map((t) => (
|
{data.map((t) => (
|
||||||
<tr key={t.id} className="border-b border-hairline last:border-0">
|
<tr key={t.id}>
|
||||||
<td className="px-xl py-md">
|
<td>
|
||||||
<Link
|
<Link
|
||||||
to={`/admin/templates/${t.id}/edit`}
|
to={`/admin/templates/${t.id}/edit`}
|
||||||
className="text-ink font-medium hover:underline"
|
className="text-ink font-medium hover:underline"
|
||||||
@@ -90,12 +90,12 @@ export function TemplatesListPage(): JSX.Element {
|
|||||||
{t.name}
|
{t.name}
|
||||||
</Link>
|
</Link>
|
||||||
</td>
|
</td>
|
||||||
<td className="px-xl py-md text-charcoal text-[14px]">
|
<td className="text-charcoal">
|
||||||
{mitreCount(t) === 0 ? '—' : mitreCount(t)}
|
{mitreCount(t) === 0 ? '—' : mitreCount(t)}
|
||||||
</td>
|
</td>
|
||||||
<td className="px-xl py-md text-charcoal">{t.created_by.username}</td>
|
<td className="text-charcoal">{t.created_by.username}</td>
|
||||||
<td className="px-xl py-md text-charcoal font-mono">{formatDate(t.updated_at)}</td>
|
<td className="text-charcoal font-mono">{formatDate(t.updated_at)}</td>
|
||||||
<td className="px-xl py-md text-right">
|
<td className="text-right">
|
||||||
<div className="inline-flex gap-sm">
|
<div className="inline-flex gap-sm">
|
||||||
<Link to={`/admin/templates/${t.id}/edit`} className="btn-text-link">
|
<Link to={`/admin/templates/${t.id}/edit`} className="btn-text-link">
|
||||||
Edit
|
Edit
|
||||||
|
|||||||
@@ -188,13 +188,13 @@ export function UsersAdminPage(): JSX.Element {
|
|||||||
|
|
||||||
{!list.isLoading && !list.isError && list.data && list.data.length > 0 ? (
|
{!list.isLoading && !list.isError && list.data && list.data.length > 0 ? (
|
||||||
<div className="card-product overflow-hidden p-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">
|
<thead className="bg-cloud border-b border-hairline">
|
||||||
<tr className="text-[12px] uppercase tracking-[0.5px] text-graphite">
|
<tr>
|
||||||
<th className="px-xl py-md">Username</th>
|
<th>Username</th>
|
||||||
<th className="px-xl py-md">Role</th>
|
<th>Role</th>
|
||||||
<th className="px-xl py-md">Created</th>
|
<th>Created</th>
|
||||||
<th className="px-xl py-md text-right">Actions</th>
|
<th className="text-right">Actions</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -204,8 +204,8 @@ export function UsersAdminPage(): JSX.Element {
|
|||||||
// Fragment must carry the key — `<>` cannot, which broke
|
// Fragment must carry the key — `<>` cannot, which broke
|
||||||
// per-row reconciliation (reset-password state leaked across rows).
|
// per-row reconciliation (reset-password state leaked across rows).
|
||||||
<Fragment key={u.id}>
|
<Fragment key={u.id}>
|
||||||
<tr className="border-b border-hairline last:border-0">
|
<tr>
|
||||||
<td className="px-xl py-md text-ink">
|
<td className="text-ink">
|
||||||
<span className="font-mono font-medium">{u.username}</span>
|
<span className="font-mono font-medium">{u.username}</span>
|
||||||
{isSelf ? (
|
{isSelf ? (
|
||||||
<span className="ml-sm font-sans text-[12px] text-graphite">
|
<span className="ml-sm font-sans text-[12px] text-graphite">
|
||||||
@@ -213,7 +213,7 @@ export function UsersAdminPage(): JSX.Element {
|
|||||||
</span>
|
</span>
|
||||||
) : null}
|
) : null}
|
||||||
</td>
|
</td>
|
||||||
<td className="px-xl py-md">
|
<td>
|
||||||
<Select
|
<Select
|
||||||
value={u.role}
|
value={u.role}
|
||||||
onChange={(e) => onRoleChange(u, e.target.value as Role)}
|
onChange={(e) => onRoleChange(u, e.target.value as Role)}
|
||||||
@@ -222,8 +222,8 @@ export function UsersAdminPage(): JSX.Element {
|
|||||||
disabled={patchMutation.isPending}
|
disabled={patchMutation.isPending}
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
<td className="px-xl py-md text-charcoal font-mono">{u.created_at}</td>
|
<td className="text-charcoal font-mono">{u.created_at}</td>
|
||||||
<td className="px-xl py-md text-right">
|
<td className="text-right">
|
||||||
<div className="inline-flex gap-sm">
|
<div className="inline-flex gap-sm">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
|||||||
Reference in New Issue
Block a user