fix(web): flush card content, so a full-bleed table stops doubling its inset

`<CardContent className="p-0">` never removed the padding it looked like it
removed: tailwind-merge resolves conflicts within a variant, so `p-0` cancels
`p-4` but leaves `sm:p-6` standing. Every call site that used it nests a
CardHeader inside, which brings its own `sm:p-6` — so at >=640px the title sat
24px further in than its neighbours'. Visible on the Plugins page as 'Installed
plugins' not lining up with 'Plugin runner'.

Give CardContent a `flush` prop that omits the padding outright, and use it at
the three call sites (Store, Pairing, Stats) rather than fixing the symptom
three times and leaving the footgun in place.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-21 00:25:31 +02:00
parent 04c975e9cb
commit 9f1a74ffc9
4 changed files with 20 additions and 6 deletions
+17 -3
View File
@@ -63,11 +63,25 @@ const CardDescription = React.forwardRef<
));
CardDescription.displayName = "CardDescription";
/**
* Card body. Pass `flush` for content that should meet the card's edges — a full-bleed table, most
* commonly — instead of trying to cancel the padding from the outside.
*
* `className="p-0"` does NOT work for that: tailwind-merge only resolves conflicts *within the same
* variant*, so `p-0` cancels `p-4` but leaves `sm:p-6` standing, and the padding silently returns at
* ≥640px. Every call site that tried it ended up with a doubled inset once a `CardHeader` (which
* brings its own `sm:p-6`) was nested inside — visible as one card whose title sits 24px further in
* than its neighbours'.
*/
const CardContent = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div ref={ref} className={cn("p-4 pt-0 sm:p-6 sm:pt-0", className)} {...props} />
React.HTMLAttributes<HTMLDivElement> & { flush?: boolean }
>(({ className, flush = false, ...props }, ref) => (
<div
ref={ref}
className={cn(!flush && "p-4 pt-0 sm:p-6 sm:pt-0", className)}
{...props}
/>
));
CardContent.displayName = "CardContent";
+1 -1
View File
@@ -78,7 +78,7 @@ export const PendingDevices: FC<{
return (
<Card>
<CardContent className="p-0">
<CardContent flush>
<CardHeader>
<CardTitle>
<h2 className="flex items-center gap-2 text-lg font-medium">
+1 -1
View File
@@ -114,7 +114,7 @@ export const RecordingsCard: FC<{
{m.stats_recordings_empty()}
</CardContent>
) : (
<CardContent className="p-0">
<CardContent flush>
<Table>
<TableHeader>
<TableRow>
+1 -1
View File
@@ -49,7 +49,7 @@ export const InstalledList: FC<{
const rows = installed.data ?? [];
return (
<Card>
<CardContent className="p-0">
<CardContent flush>
<CardHeader>
<CardTitle>{m.store_installed_title()}</CardTitle>
</CardHeader>