feat(frontend): slice 2 — migrate 11 shared components to shadcn/Tailwind

Web UI rework. Shared-components slice (drift prevention):
- Migrate SectionCard, SelectionRailCard, TabbedCard, MetricCard,
  DiskSpaceCard, HoverEditButton, DialogFooter, ConfirmDialog,
  LibraryOverview, NowPlaying, SessionActivityPanel off @mui
- HoverEditButton: MUI IconButton + EditOutlined -> Button + lucide Pencil
- Status mapping uses the success Badge variant (chart-2) for healthy
- Exported APIs preserved so consuming pages still compile (no page edits)
- 11 behavioral Vitest component tests added

Gate: build + lint + test green.
This commit is contained in:
Developer
2026-06-17 12:33:47 +00:00
parent dd778d8850
commit 109e74db41
23 changed files with 830 additions and 469 deletions
+18 -17
View File
@@ -1,38 +1,39 @@
import type { ReactElement, ReactNode } from "react";
import { Box, Card, CardContent, Tabs } from "@mui/material";
import { Card } from "@/components/ui/card";
import { Tabs, TabsList } from "@/components/ui/tabs";
interface TabbedCardProps {
value: string;
onChange: (value: string) => void;
tabs: ReactElement[];
children: ReactNode;
/** Legacy MUI `sx` passthroughs; retained for API compatibility (no-op). */
contentSx?: object;
/** Legacy MUI `sx` passthroughs; retained for API compatibility (no-op). */
tabsSx?: object;
}
/**
* Card surface with a line-style tab bar on top and a content area below.
*
* `value`/`onChange` stay string-typed (controlled) and the `tabs` prop stays
* `ReactElement[]`, so consuming pages compile unchanged. The page owns the
* rendered content from `children` keyed off `value`, exactly as before.
*/
export function TabbedCard({
value,
onChange,
tabs,
children,
contentSx,
tabsSx,
}: TabbedCardProps) {
return (
<Card variant="outlined">
<CardContent sx={{ p: 0 }}>
<Tabs
value={value}
onChange={(_, next) => onChange(String(next))}
variant="scrollable"
scrollButtons="auto"
allowScrollButtonsMobile
sx={{ px: 1, borderBottom: 1, borderColor: "divider", ...tabsSx }}
>
{tabs}
</Tabs>
<Box sx={{ p: 1.5, ...contentSx }}>{children}</Box>
</CardContent>
<Card className="gap-0 py-0">
<Tabs value={value} onValueChange={(next) => onChange(String(next))}>
<div className="border-b px-2">
<TabsList variant="line">{tabs}</TabsList>
</div>
<div className="p-4">{children}</div>
</Tabs>
</Card>
);
}