feat(ui,web): add shared components and terminal panel (MS15-UI-003, UI-004, UI-005)
UI-003: MetricsStrip (6-cell KPI grid), ProgressBar (4 variants with ARIA), FilterTabs (segmented control). UI-004: SectionHeader (title/subtitle/actions), DataTable (generic typed table with hover), LogLine (colored log entry grid). UI-005: TerminalPanel bottom-drawer component with tabs, line coloring, blinking cursor, slide animation. All components use CSS custom properties for theming. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
102
packages/ui/src/components/DataTable.tsx
Normal file
102
packages/ui/src/components/DataTable.tsx
Normal file
@@ -0,0 +1,102 @@
|
||||
import { useState, type ReactElement, type ReactNode } from "react";
|
||||
|
||||
export interface DataTableColumn<T> {
|
||||
key: string;
|
||||
header: string;
|
||||
render?: (row: T) => ReactNode;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export interface DataTableProps<T extends Record<string, unknown>> {
|
||||
columns: DataTableColumn<T>[];
|
||||
data: T[];
|
||||
className?: string;
|
||||
onRowClick?: (row: T) => void;
|
||||
}
|
||||
|
||||
export function DataTable<T extends Record<string, unknown>>({
|
||||
columns,
|
||||
data,
|
||||
className = "",
|
||||
onRowClick,
|
||||
}: DataTableProps<T>): ReactElement {
|
||||
const [hoveredRow, setHoveredRow] = useState<number | null>(null);
|
||||
|
||||
return (
|
||||
<table
|
||||
className={className}
|
||||
style={{
|
||||
width: "100%",
|
||||
borderCollapse: "collapse",
|
||||
fontSize: "0.83rem",
|
||||
}}
|
||||
>
|
||||
<thead>
|
||||
<tr>
|
||||
{columns.map((col) => (
|
||||
<th
|
||||
key={col.key}
|
||||
className={col.className}
|
||||
style={{
|
||||
textAlign: "left",
|
||||
padding: "8px 12px",
|
||||
fontSize: "0.7rem",
|
||||
fontWeight: 600,
|
||||
textTransform: "uppercase",
|
||||
letterSpacing: "0.07em",
|
||||
color: "var(--muted)",
|
||||
borderBottom: "1px solid var(--border)",
|
||||
background: "var(--surface)",
|
||||
}}
|
||||
>
|
||||
{col.header}
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{data.map((row, rowIndex) => {
|
||||
const isLast = rowIndex === data.length - 1;
|
||||
const isHovered = hoveredRow === rowIndex;
|
||||
|
||||
return (
|
||||
<tr
|
||||
key={rowIndex}
|
||||
style={{
|
||||
background: isHovered ? "var(--surface)" : undefined,
|
||||
cursor: onRowClick !== undefined ? "pointer" : undefined,
|
||||
}}
|
||||
onClick={
|
||||
onRowClick !== undefined
|
||||
? (): void => {
|
||||
onRowClick(row);
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
onMouseEnter={(): void => {
|
||||
setHoveredRow(rowIndex);
|
||||
}}
|
||||
onMouseLeave={(): void => {
|
||||
setHoveredRow(null);
|
||||
}}
|
||||
>
|
||||
{columns.map((col) => (
|
||||
<td
|
||||
key={col.key}
|
||||
className={col.className}
|
||||
style={{
|
||||
padding: "10px 12px",
|
||||
borderBottom: isLast ? undefined : "1px solid var(--border)",
|
||||
color: isHovered ? "var(--text)" : "var(--text-2)",
|
||||
}}
|
||||
>
|
||||
{col.render !== undefined ? col.render(row) : (row[col.key] as ReactNode)}
|
||||
</td>
|
||||
))}
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user