feat: compact KPI badges integrated into filter strip (Task 5.3)

- Add kpi_badge() and kpi_badges() functions for inline pill-style KPIs
- Integrate KPI badges into filter_section() on the right side
- Remove separate kpi_row() from main_content() layout
- Zero extra vertical height - KPIs now share the filter strip row

Design: Follows Option A from DESIGN_SYSTEM.md (preferred approach)
This commit is contained in:
Andrew Charlwood
2026-02-05 01:59:00 +00:00
parent 645fe0ab6c
commit 826dd1c022
2 changed files with 108 additions and 18 deletions
+9 -4
View File
@@ -74,16 +74,21 @@ python -m reflex compile
- [ ] Verify: Filter section height ≤ 60px when collapsed (requires visual verification)
### 5.3 Compact KPI Cards (50% reduction)
- [ ] Reduce KPI card dimensions:
- [x] Reduce KPI card dimensions:
- Padding: 12px (was 24px)
- Value font size: 24px (was 32px)
- Label font size: 11px (was 12px)
- [ ] Make KPIs a single compact row:
- [x] Make KPIs a single compact row:
- All 4 KPIs in horizontal strip
- Minimal vertical footprint
- Consider inline layout: "12,345 patients | £45.2M cost | 89 drugs | 7 trusts"
- [ ] Alternative: KPI badges/pills instead of cards
- [ ] Verify: KPI row height ≤ 48px
- [x] Alternative: KPI badges/pills instead of cards
- Implemented kpi_badge() and kpi_badges() functions
- KPIs are now inline badges integrated into the filter strip
- Zero additional vertical height (Option A from design system)
- [x] Verify: KPI row height ≤ 48px
- KPIs now embedded in filter strip - no separate row needed
- reflex compile succeeds in 15s
### 5.4 Full-Width Chart Layout
- [ ] Remove PAGE_MAX_WIDTH constraint for chart container
+99 -14
View File
@@ -37,6 +37,10 @@ from pathways_app.styles import (
compact_dropdown_trigger_style,
searchable_dropdown_panel_style,
searchable_dropdown_item_style,
# v2.1 KPI badge styles
kpi_badge_style,
kpi_badge_value_style,
kpi_badge_label_style,
)
@@ -1778,8 +1782,10 @@ def filter_section() -> rx.Component:
spacing="2",
align="center",
),
# Spacer to push content left
# Spacer pushes KPIs to right
rx.spacer(),
# KPI badges on the right
kpi_badges(),
justify="start",
align="center",
gap=Spacing.LG,
@@ -1860,16 +1866,10 @@ def kpi_card(
def kpi_row() -> rx.Component:
"""
KPI metrics row component with responsive grid layout.
LEGACY: KPI metrics row component with card layout.
Contains:
- Unique Patients: COUNT(DISTINCT patient_id)
- Total Drugs: Count of selected/filtered drugs
- Total Cost: Sum of costs in filtered data
- Match Rate: Indication match percentage
Layout: Responsive flex row that wraps on smaller screens.
KPIs update reactively when filters change (Phase 3).
Replaced by kpi_badges() for v2.1 compact layout.
Kept for reference and potential fallback.
"""
return rx.hstack(
# Unique Patients KPI - highlighted as primary metric
@@ -1907,6 +1907,90 @@ def kpi_row() -> rx.Component:
)
# =============================================================================
# Compact KPI Components (v2.1 SaaS Redesign)
# =============================================================================
def kpi_badge(
value: rx.Var[str],
label: str,
highlight: bool = False,
) -> rx.Component:
"""
Compact KPI badge (pill) for inline display.
Args:
value: The display value (formatted string from computed var)
label: Short label for the metric
highlight: If True, uses primary blue styling
Design specs from DESIGN_SYSTEM.md (Option A):
- Padding: 4px 12px
- Border radius: full (pill)
- Background: Slate 100 (or Primary for highlight)
- Value: 14px mono weight 600
- Label: 11px Slate 500
"""
# Build value text style - override color based on highlight
value_style = kpi_badge_value_style().copy()
value_style["color"] = Colors.WHITE if highlight else Colors.SLATE_900
# Build label text style - override color based on highlight
label_style = kpi_badge_label_style().copy()
label_style["color"] = Colors.WHITE if highlight else Colors.SLATE_500
if highlight:
label_style["opacity"] = "0.8"
# Build badge container style - override background
badge_style = kpi_badge_style().copy()
badge_style["background_color"] = Colors.PRIMARY if highlight else Colors.SLATE_100
return rx.box(
rx.hstack(
rx.text(value, **value_style),
rx.text(label, **label_style),
spacing="1",
align="center",
),
**badge_style,
)
def kpi_badges() -> rx.Component:
"""
Compact KPI badges row for v2.1 redesign.
Zero extra vertical height - designed to sit alongside filters.
Format: "12,345 patients • £45.2M cost • 89 drugs"
Returns horizontal flex of pill badges.
"""
return rx.hstack(
# Unique Patients - highlighted as primary
kpi_badge(
value=AppState.unique_patients_display,
label="patients",
highlight=True,
),
# Total Cost
kpi_badge(
value=AppState.total_cost_display,
label="cost",
highlight=False,
),
# Drug Types
kpi_badge(
value=AppState.total_drugs_display,
label="drugs",
highlight=False,
),
spacing="2",
align="center",
flex_shrink="0", # Don't shrink badges
)
def chart_loading_skeleton() -> rx.Component:
"""
Loading skeleton for the chart area.
@@ -2197,15 +2281,16 @@ def main_content() -> rx.Component:
"""
Main content area below the top bar.
Layout: Filter Section → KPI Row → Chart Section
Layout (v2.1): Filter Section (with KPI badges) → Chart Section
KPIs are now inline badges in the filter strip (zero extra height).
Max width constrained to PAGE_MAX_WIDTH, centered.
"""
return rx.box(
rx.vstack(
filter_section(),
kpi_row(),
# KPIs now integrated into filter_section as badges
chart_section(),
spacing="5",
spacing="4", # Tighter spacing
width="100%",
align="stretch",
),
@@ -2213,7 +2298,7 @@ def main_content() -> rx.Component:
max_width=PAGE_MAX_WIDTH,
margin_x="auto",
padding=PAGE_PADDING,
padding_top=Spacing.XL,
padding_top=Spacing.MD, # Tighter top padding
)