Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 138 additions & 0 deletions docs/DashboardOverview-PreviewCard.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
# Hover-Preview Cards on DashboardOverview (Issue #581)

## Overview

Implements a hover-triggered and keyboard-accessible **PreviewCard** overlay on all
rows and cards in the `DashboardOverview` page, as part of the GrantFox FWC26
campaign (Stellar Wave).

Users can preview rich details — status, metrics, tags, pricing, description —
**without navigating away** from the dashboard.

---

## Components changed

| File | Change |
|---|---|
| `src/components/PreviewCard.tsx` | New reusable hover+focus preview card wrapper |
| `src/pages/DashboardOverview.tsx` | Uses `PreviewCard` on all rows/cards; bugfix `api.latencyMs` → `api.avgLatencyMs` |
| `src/pages/DashboardPage.tsx` | Now accepts and passes through `DashboardOverviewProps` to `DashboardOverview` |
| `src/App.tsx` | `/dashboard` route now renders `DashboardPage` (with `DashboardOverview`) instead of the old `Dashboard` component |
| `src/components/PreviewCard.test.tsx` | Full unit-test coverage for `PreviewCard` |
| `src/pages/DashboardOverview.test.tsx` | Integration tests for all preview-card surfaces on the page |

---

## PreviewCard API

```tsx
import PreviewCard, { type PreviewCardData } from '../components/PreviewCard';

<PreviewCard data={myData} position="bottom">
<div>Hover or focus me to see a preview</div>
</PreviewCard>
```

### Props

| Prop | Type | Default | Description |
|---|---|---|---|
| `data` | `PreviewCardData` | — | Item details displayed in the overlay |
| `children` | `ReactNode` | — | The trigger element wrapped by the card |
| `position` | `'top' \| 'bottom' \| 'left' \| 'right'` | `'bottom'` | Floating overlay placement relative to the trigger |
| `className` | `string` | `''` | Optional extra class on the wrapper div |

### PreviewCardData shape

```ts
interface PreviewCardData {
id: string;
title: string;
subtitle?: string;
category?: string;
status?: StatusVariant; // renders a StatusBadge
description?: string;
metrics?: Array<{ label: string; value: string | number }>;
tags?: string[];
price?: string | number; // "$0.005 / call" auto-formatted
lastActive?: string;
details?: Record<string, string | number>;
}
```

---

## Surfaces on DashboardOverview

### 1. Balance cards (Vault Balance, Wallet Available)
Wrapped in `<PreviewCard position="bottom">`. Hovering or focusing opens a tooltip
with balance details, estimated API runway, and account metadata.

### 2. Pinned API rows
Each row in the "Pinned APIs" section is wrapped in `<PreviewCard position="top">`.
The preview shows the API name, provider, status badge, category, latency, uptime,
and price per call.

### 3. Recent Activity items
Each activity row is wrapped in `<PreviewCard position="right">`. The preview shows
the transaction type (deposit or usage charge), amount in USDC, endpoint, and
timestamp.

---

## Accessibility (WCAG 2.1 AA)

- **`role="tooltip"`** on the preview panel so screen readers announce it as
supplementary information.
- **`aria-describedby`** on the trigger points to the panel's ID while open;
cleared on close.
- **`tabIndex={0}`** and **`role="button"`** on the trigger — fully keyboard
reachable without a mouse.
- **Escape key** closes the panel and restores focus to the trigger.
- **`aria-label="Preview details for <title>"`** on every trigger for descriptive
screen reader identification.
- All colors use CSS custom properties (`--surface`, `--text-primary`, `--accent`,
etc.) that resolve correctly in both light and dark themes.
- `prefers-reduced-motion` is respected by the parent components; no CSS transitions
are added inside `PreviewCard` itself.

---

## Dark / light mode

`PreviewCard` uses only CSS custom properties that are defined in both
`[data-theme="dark"]` and `[data-theme="light"]` blocks in `src/index.css`:

```
--surface panel background
--border-color panel border
--shadow panel drop shadow
--text-primary title text
--text-secondary subtitle / description text
--accent metric values
--bg-chip metrics grid and tag chips background
--line footer separator
--success price value
```

---

## Responsive behavior

- `maxWidth: 90vw` on the floating panel prevents overflow on small screens.
- At `≤ 560 px` the panel position snaps to `bottom` by default since there is no
horizontal space for `right` or `left` placement; callers should choose
appropriate positions per breakpoint.
- `pointerEvents: 'none'` on the panel ensures it never blocks pointer interaction
with content underneath.

---

## Bug fix included

`DashboardOverview.tsx` previously referenced `api.latencyMs` (undefined property)
for the pinned-API preview metrics. This is corrected to `api.avgLatencyMs`, which
matches the `APIItem` type in `src/data/mockApis.ts`. A regression test is included
in `DashboardOverview.test.tsx` under the heading
_"shows latency and uptime metrics from api.avgLatencyMs (not api.latencyMs)"_.
4 changes: 2 additions & 2 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import { Routes, Route, NavLink, useNavigate, useLocation } from "react-router-dom";
import { ThemeToggle } from "./ThemeToggle";
import ApiUsage from "./pages/ApiUsage";
import Dashboard from "./components/Dashboard";
import DashboardPage from "./pages/DashboardPage";
import MyApis from "./pages/MyApis";
import PlanBadgePage from "./pages/PlanBadge";
import RouteProgressBar from "./components/RouteProgressBar";
Expand All @@ -12,7 +12,7 @@
import PublishApi from "./pages/PublishApi";
import { startRouteLoading, stopRouteLoading } from "./hooks/useRouteLoading";
import { formatUsdc, formatUsdShortcut } from "./utils/format";
import DepositPreview from "./components/DepositPreview";

Check failure on line 15 in src/App.tsx

View workflow job for this annotation

GitHub Actions / TypeScript check & production build

'DepositPreview' is declared but its value is never read.
import { EXPLORER_BASE_URL, MIN_DEPOSIT, NETWORK_FEE, PRESET_AMOUNTS } from "./config/constants";
import CompareDrawer from "./components/CompareDrawer";
import CompareTray from "./components/CompareTray";
Expand Down Expand Up @@ -279,7 +279,7 @@
[APP_ROUTES.billing]: "Billing – Callora",
"/api-usage": "API Usage – Callora",
[APP_ROUTES.landing]: "Callora",
[APP_ROUTES.endpointSummary]: "Endpoint Summary – Callora",

Check failure on line 282 in src/App.tsx

View workflow job for this annotation

GitHub Actions / TypeScript check & production build

Property 'endpointSummary' does not exist on type '{ readonly landing: "/"; readonly dashboard: "/dashboard"; readonly marketplace: "/marketplace"; readonly publish: "/publish"; readonly myApis: "/apis/my-apis"; readonly planBadge: "/apis/plan-badge"; ... 7 more ...; readonly rateLimitCard: "/rate-limit"; }'.
};
const routeDescriptionMap: Record<string, string> = {
[APP_ROUTES.marketplace]: "Explore APIs on the Callora marketplace, discover and integrate APIs for your applications.",
Expand All @@ -287,7 +287,7 @@
[APP_ROUTES.billing]: "Manage your USDC vault, deposit funds, and view transaction status.",
"/api-usage": "Monitor API usage, request stats, and view call history.",
[APP_ROUTES.landing]: "Callora - Programmable API Access, pay-per-call billing, and on-chain settlement.",
[APP_ROUTES.endpointSummary]: "Quick reference list of all API endpoints on Callora.",

Check failure on line 290 in src/App.tsx

View workflow job for this annotation

GitHub Actions / TypeScript check & production build

Property 'endpointSummary' does not exist on type '{ readonly landing: "/"; readonly dashboard: "/dashboard"; readonly marketplace: "/marketplace"; readonly publish: "/publish"; readonly myApis: "/apis/my-apis"; readonly planBadge: "/apis/plan-badge"; ... 7 more ...; readonly rateLimitCard: "/rate-limit"; }'.
};
const currentTitle = routeTitleMap[location.pathname] ?? "Callora";
const currentDescription = routeDescriptionMap[location.pathname];
Expand All @@ -300,7 +300,7 @@
const [amountInput, setAmountInput] = useState("50");
const [selectedPreset, setSelectedPreset] = useState<number | "custom">(50);

const [isTouchDevice, setIsTouchDevice] = useState(false);

Check failure on line 303 in src/App.tsx

View workflow job for this annotation

GitHub Actions / TypeScript check & production build

'isTouchDevice' is declared but its value is never read.
useEffect(() => {
setIsTouchDevice('ontouchstart' in window || navigator.maxTouchPoints > 0);
}, []);
Expand Down Expand Up @@ -574,7 +574,7 @@

<Route path={APP_ROUTES.publish} element={<PublishApi />} />

<Route path={APP_ROUTES.dashboard} element={<Dashboard vaultBalance={vaultBalance} walletBalance={walletBalance} costPerCall={0.08} callsPerDay={120} openDeposit={openDeposit} />} />
<Route path={APP_ROUTES.dashboard} element={<DashboardPage vaultBalance={vaultBalance} walletBalance={walletBalance} costPerCall={0.08} callsPerDay={120} openDeposit={openDeposit} />} />
<Route path={APP_ROUTES.marketplace} element={<MarketplacePage />} />

<Route path={APP_ROUTES.themePlayground} element={<ThemePlayground />} />
Expand All @@ -595,7 +595,7 @@
<p className="eyebrow">Deposit USDC to Vault</p>
<h1>Review every number before you approve.</h1>
</div>
<button className="primary-button no-print" onClick={openDeposit}>

Check failure on line 598 in src/App.tsx

View workflow job for this annotation

GitHub Actions / TypeScript check & production build

Type '(presetAmount?: number) => void' is not assignable to type 'MouseEventHandler<HTMLButtonElement>'.
Open deposit modal
</button>
</div>
Expand Down
Loading
Loading