Skip to content

Commit 9d64566

Browse files
ofershapcursoragent
andcommitted
feat: add fullscreen expand to all dashboard cards
ExpandableCard component wraps cards with a hover-reveal expand button that opens content in a fullscreen overlay portal. Handles escape/click-outside to close, locks body scroll, strips max-height constraints and stretches Recharts containers in fullscreen mode. - Wrap all data cards across dashboard, insights, and user detail pages - Top Spenders chart shows 15 users (vs 8) when expanded via render fn - Plan Exhaustion auto-shows user list when expanded - Global :focus-visible fix removes click-triggered focus outlines Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 71ac9bf commit 9d64566

7 files changed

Lines changed: 854 additions & 664 deletions

File tree

.cursor/rules/coding-conventions.mdc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ import type { Anomaly } from "@/lib/types";
3939
- Use Tailwind CSS with zinc color palette (dark theme)
4040
- No shadcn/ui installed — components are hand-rolled
4141
- Model names displayed via `shortModel()` from `src/lib/format-utils.ts` with full name in tooltip
42+
- Self-contained components: components like `upgrade-banner.tsx` are designed with minimal footprint — single file, own modal/overlay logic baked in, easy to add/remove with 1-2 line diffs in consuming files. Do NOT extract shared modals or abstractions from them; keep them isolated.
43+
- `ExpandableCard` (`src/components/expandable-card.tsx`): wraps any card to add a fullscreen expand button. Wrap the outer card div, don't restructure the card internals.
4244

4345
## Anomaly Detection
4446

src/app/dashboard-client.tsx

Lines changed: 35 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { SpendTrendChart } from "@/components/charts/spend-trend-chart";
99
import { MembersTable } from "@/components/dashboard/members-table";
1010
import Link from "next/link";
1111
import { shortModel } from "@/lib/format-utils";
12+
import { ExpandableCard } from "@/components/expandable-card";
1213

1314
interface BillingGroupWithMembers {
1415
id: string;
@@ -492,31 +493,47 @@ export function DashboardClient({ initialData }: DashboardClientProps) {
492493

493494
{/* ── Row: Team Spend Trend + Model Cost Comparison ── */}
494495
<div className="grid grid-cols-1 lg:grid-cols-2 gap-3">
495-
<SpendTrendChart
496-
data={filteredTeamDailySpend}
497-
selectedDays={days}
498-
avgPerDay={filteredSpendCents / 100 / (effectiveDays || 1)}
499-
/>
500-
<ModelCostComparison data={data.modelCosts} />
496+
<ExpandableCard>
497+
<SpendTrendChart
498+
data={filteredTeamDailySpend}
499+
selectedDays={days}
500+
avgPerDay={filteredSpendCents / 100 / (effectiveDays || 1)}
501+
/>
502+
</ExpandableCard>
503+
<ExpandableCard>
504+
<ModelCostComparison data={data.modelCosts} />
505+
</ExpandableCard>
501506
</div>
502507

503508
{/* ── Charts ── */}
504509
<div className="grid grid-cols-1 lg:grid-cols-2 gap-3">
505-
<SpendBarChart data={filteredUsers.slice(0, 20)} highlightEmail={searchedUser?.email} />
506-
<DailySpendChart data={dailySpendData} onNameClick={(name) => setSearch(name)} />
510+
<ExpandableCard>
511+
{(exp) => (
512+
<SpendBarChart
513+
data={filteredUsers.slice(0, 20)}
514+
highlightEmail={searchedUser?.email}
515+
expanded={exp}
516+
/>
517+
)}
518+
</ExpandableCard>
519+
<ExpandableCard>
520+
<DailySpendChart data={dailySpendData} onNameClick={(name) => setSearch(name)} />
521+
</ExpandableCard>
507522
</div>
508523

509524
{/* ── Table ── */}
510-
<MembersTable
511-
data={filteredUsers}
512-
sortCol={sortCol}
513-
sortAsc={sortAsc}
514-
onSort={handleSort}
515-
highlightEmail={searchedUser?.email}
516-
timeLabel={timeLabel}
517-
badgeFilter={badgeFilter}
518-
onBadgeFilter={handleBadgeFilter}
519-
/>
525+
<ExpandableCard>
526+
<MembersTable
527+
data={filteredUsers}
528+
sortCol={sortCol}
529+
sortAsc={sortAsc}
530+
onSort={handleSort}
531+
highlightEmail={searchedUser?.email}
532+
timeLabel={timeLabel}
533+
badgeFilter={badgeFilter}
534+
onBadgeFilter={handleBadgeFilter}
535+
/>
536+
</ExpandableCard>
520537
</div>
521538
);
522539
}

src/app/globals.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ label[for] {
2727
cursor: pointer;
2828
}
2929

30+
:focus:not(:focus-visible) {
31+
outline: none;
32+
}
33+
3034
body {
3135
background: var(--background);
3236
color: var(--foreground);

0 commit comments

Comments
 (0)