fix(web,ui): visual polish aligned to design reference (#457)
All checks were successful
ci/woodpecker/push/orchestrator Pipeline was successful
ci/woodpecker/push/web Pipeline was successful
ci/woodpecker/push/api Pipeline was successful

- Add responsive CSS classes for dashboard grid and metrics strip
  (3-col tablet, 2-col mobile, single-col stacking)
- Fix QuickActions layout from vertical to horizontal per design ref
- Replace non-existent var(--r-md) token with var(--r) in
  OrchestratorSessions and QuickActions
- Fix NavItem borderRadius to var(--r-sm) matching reference
- Fix ActivityFeed spacing (gap 12px, padding 10px per reference)
- Refactor MetricsStrip to CSS class-based responsive grid

Task: MS-P1-003

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-22 18:06:39 -06:00
parent 8fa0b3059c
commit d97a98b686
7 changed files with 84 additions and 43 deletions

View File

@@ -11,13 +11,7 @@ export default function DashboardPage(): ReactElement {
return (
<div style={{ display: "flex", flexDirection: "column", gap: 16 }}>
<DashboardMetrics />
<div
style={{
display: "grid",
gridTemplateColumns: "1fr 320px",
gap: 16,
}}
>
<div className="dash-grid">
<div style={{ display: "flex", flexDirection: "column", gap: 16, minWidth: 0 }}>
<OrchestratorSessions />
<QuickActions />

View File

@@ -765,6 +765,62 @@ body::before {
animation: scaleIn 0.1s ease-out;
}
/* -----------------------------------------------------------------------------
Dashboard Layout — Responsive Grids
----------------------------------------------------------------------------- */
.metrics-strip {
display: grid;
grid-template-columns: repeat(var(--ms-cols, 6), 1fr);
gap: 0;
border-radius: var(--r-lg);
overflow: hidden;
border: 1px solid var(--border);
}
.metric-cell {
border-left: 1px solid var(--border);
}
.metric-cell:first-child {
border-left: none;
}
@media (max-width: 900px) {
.metrics-strip {
grid-template-columns: repeat(3, 1fr);
}
.metric-cell:nth-child(3n + 1) {
border-left: none;
}
}
@media (max-width: 640px) {
.metrics-strip {
grid-template-columns: repeat(2, 1fr);
}
.metric-cell:nth-child(3n + 1) {
border-left: 1px solid var(--border);
}
.metric-cell:nth-child(2n + 1) {
border-left: none;
}
}
.dash-grid {
display: grid;
grid-template-columns: 1fr 320px;
gap: 16px;
}
@media (max-width: 900px) {
.dash-grid {
grid-template-columns: 1fr;
}
}
/* -----------------------------------------------------------------------------
Responsive Typography Adjustments
----------------------------------------------------------------------------- */

View File

@@ -102,8 +102,8 @@ function ActivityItemRow({ item }: ActivityItemRowProps): ReactElement {
style={{
display: "flex",
alignItems: "flex-start",
gap: 10,
padding: "8px 0",
gap: 12,
padding: "10px 0",
borderBottom: "1px solid var(--border)",
}}
>

View File

@@ -169,7 +169,7 @@ function OrchCard({ session }: OrchCardProps): ReactElement {
style={{
background: "var(--bg-mid)",
border: "1px solid var(--border)",
borderRadius: "var(--r-md)",
borderRadius: "var(--r)",
padding: "12px 14px",
marginBottom: 10,
}}

View File

@@ -12,10 +12,10 @@ interface QuickAction {
}
const actions: QuickAction[] = [
{ id: "new-project", label: "New Project", icon: "🚀", iconBg: "rgba(47,128,255,0.15)" },
{ id: "spawn-agent", label: "Spawn Agent", icon: "🤖", iconBg: "rgba(139,92,246,0.15)" },
{ id: "view-telemetry", label: "View Telemetry", icon: "📊", iconBg: "rgba(20,184,166,0.15)" },
{ id: "review-tasks", label: "Review Tasks", icon: "📋", iconBg: "rgba(245,158,11,0.15)" },
{ id: "new-project", label: "New Project", icon: "🚀", iconBg: "rgba(47,128,255,0.12)" },
{ id: "spawn-agent", label: "Spawn Agent", icon: "🤖", iconBg: "rgba(139,92,246,0.12)" },
{ id: "view-telemetry", label: "View Telemetry", icon: "📊", iconBg: "rgba(20,184,166,0.12)" },
{ id: "review-tasks", label: "Review Tasks", icon: "📋", iconBg: "rgba(245,158,11,0.12)" },
];
interface ActionButtonProps {
@@ -36,24 +36,25 @@ function ActionButton({ action }: ActionButtonProps): ReactElement {
}}
style={{
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
gap: 8,
padding: "16px 12px",
borderRadius: "var(--r-md)",
padding: "10px 12px",
borderRadius: "var(--r)",
border: `1px solid ${hovered ? "var(--ms-border-700)" : "var(--border)"}`,
background: hovered ? "var(--surface)" : "var(--bg-mid)",
cursor: "pointer",
transition: "border-color 0.15s, background 0.15s",
transition: "border-color 0.15s, background 0.15s, color 0.15s",
width: "100%",
fontSize: "0.8rem",
fontWeight: 600,
color: hovered ? "var(--text)" : "var(--text-2)",
}}
>
<div
style={{
width: 24,
height: 24,
borderRadius: 6,
borderRadius: 5,
background: action.iconBg,
display: "flex",
alignItems: "center",
@@ -63,15 +64,7 @@ function ActionButton({ action }: ActionButtonProps): ReactElement {
>
{action.icon}
</div>
<span
style={{
fontSize: "0.8rem",
fontWeight: 600,
color: "var(--text)",
}}
>
{action.label}
</span>
<span>{action.label}</span>
</button>
);
}
@@ -84,7 +77,7 @@ export function QuickActions(): ReactElement {
style={{
display: "grid",
gridTemplateColumns: "1fr 1fr",
gap: 10,
gap: 8,
}}
>
{actions.map((action) => (

View File

@@ -356,7 +356,7 @@ function NavItem({ item, isActive, collapsed }: NavItemProps): React.JSX.Element
alignItems: "center",
gap: "11px",
padding: "9px 10px",
borderRadius: "6px",
borderRadius: "var(--r-sm)",
fontSize: "0.875rem",
fontWeight: 500,
color: isActive ? "var(--text)" : "var(--muted)",

View File

@@ -16,7 +16,7 @@ export interface MetricsStripProps {
className?: string;
}
function MetricCellItem({ cell, isFirst }: { cell: MetricCell; isFirst: boolean }): ReactElement {
function MetricCellItem({ cell }: { cell: MetricCell }): ReactElement {
const [hovered, setHovered] = useState(false);
const trendColor =
@@ -28,6 +28,7 @@ function MetricCellItem({ cell, isFirst }: { cell: MetricCell; isFirst: boolean
return (
<div
className="metric-cell"
onMouseEnter={(): void => {
setHovered(true);
}}
@@ -37,7 +38,6 @@ function MetricCellItem({ cell, isFirst }: { cell: MetricCell; isFirst: boolean
style={{
padding: "14px 16px",
background: hovered ? "var(--surface-2)" : "var(--surface)",
borderLeft: isFirst ? "none" : "1px solid var(--border)",
borderTop: `2px solid ${cell.color}`,
transition: "background 0.15s ease",
}}
@@ -82,17 +82,15 @@ function MetricCellItem({ cell, isFirst }: { cell: MetricCell; isFirst: boolean
export function MetricsStrip({ cells, className = "" }: MetricsStripProps): ReactElement {
return (
<div
className={className}
style={{
display: "grid",
gridTemplateColumns: `repeat(${String(cells.length)}, 1fr)`,
borderRadius: "var(--r-lg)",
overflow: "hidden",
border: "1px solid var(--border)",
}}
className={`metrics-strip ${className}`.trim()}
style={
{
"--ms-cols": String(cells.length),
} as React.CSSProperties
}
>
{cells.map((cell, index) => (
<MetricCellItem key={cell.label} cell={cell} isFirst={index === 0} />
{cells.map((cell) => (
<MetricCellItem key={cell.label} cell={cell} />
))}
</div>
);