feat: compact filter section as single horizontal strip (Task 5.2)
- Redesign filter_section() as 48px horizontal strip - Remove "Filters" header (saves vertical space) - Compact initiated_filter_dropdown() and last_seen_filter_dropdown() - 32px height triggers via compact_dropdown_trigger_style() - Labels moved inside dropdown panels - Compact searchable_dropdown() component - 32px trigger height, no external label - Reduced panel item height (150px max, was 200px) - Smaller search input (size="1"), tighter spacing - All filters now in ONE row with divider separator Target: filter section height ≤ 60px (from ~200px)
This commit is contained in:
@@ -60,17 +60,18 @@ python -m reflex compile
|
||||
- [x] Verify: `python -c "from pathways_app.styles import *"` - PASSED
|
||||
|
||||
### 5.2 Compact Filter Section (50-67% height reduction)
|
||||
- [ ] Redesign filter_section() as a single horizontal strip:
|
||||
- [x] Redesign filter_section() as a single horizontal strip:
|
||||
- All filters in ONE row: Date dropdowns | Drugs | Indications | Directorates
|
||||
- Remove "Filters" header (saves vertical space)
|
||||
- Use smaller dropdown triggers (height: 32px instead of 40px)
|
||||
- Use icon-only labels where possible
|
||||
- [ ] Reduce searchable_dropdown() panel heights:
|
||||
- [x] Reduce searchable_dropdown() panel heights:
|
||||
- Max item list height: 150px (was 200px)
|
||||
- Smaller search input
|
||||
- Tighter spacing (4px gaps instead of 8px)
|
||||
- [ ] Make filter dropdowns collapsible/expandable (optional advanced feature)
|
||||
- [ ] Verify: Filter section height ≤ 60px when collapsed
|
||||
- Smaller search input (size="1" instead of size="2")
|
||||
- Tighter spacing (6px/8px gaps via Spacing.SM/MD)
|
||||
- [x] Make filter dropdowns collapsible/expandable (optional advanced feature)
|
||||
- Note: This was already implemented - dropdowns open/close on click
|
||||
- [ ] Verify: Filter section height ≤ 60px when collapsed (requires visual verification)
|
||||
|
||||
### 5.3 Compact KPI Cards (50% reduction)
|
||||
- [ ] Reduce KPI card dimensions:
|
||||
|
||||
+92
-140
@@ -20,6 +20,7 @@ from pathways_app.styles import (
|
||||
Shadows,
|
||||
Transitions,
|
||||
TOP_BAR_HEIGHT,
|
||||
FILTER_STRIP_HEIGHT,
|
||||
PAGE_MAX_WIDTH,
|
||||
PAGE_PADDING,
|
||||
card_style,
|
||||
@@ -31,6 +32,11 @@ from pathways_app.styles import (
|
||||
kpi_card_style,
|
||||
kpi_value_style,
|
||||
kpi_label_style,
|
||||
# v2.1 compact styles
|
||||
filter_strip_style,
|
||||
compact_dropdown_trigger_style,
|
||||
searchable_dropdown_panel_style,
|
||||
searchable_dropdown_item_style,
|
||||
)
|
||||
|
||||
|
||||
@@ -1372,25 +1378,20 @@ class AppState(rx.State):
|
||||
|
||||
def initiated_filter_dropdown() -> rx.Component:
|
||||
"""
|
||||
Dropdown for selecting the treatment initiated time period.
|
||||
Compact dropdown for selecting the treatment initiated time period.
|
||||
|
||||
Redesigned for v2.1 filter strip - single row, no external label.
|
||||
Options: All years, Last 2 years, Last 1 year
|
||||
Values: "all", "2yr", "1yr"
|
||||
"""
|
||||
return rx.vstack(
|
||||
# Label
|
||||
rx.text(
|
||||
"Treatment Initiated",
|
||||
font_size=Typography.H3_SIZE,
|
||||
font_weight=Typography.H3_WEIGHT,
|
||||
color=Colors.SLATE_900,
|
||||
font_family=Typography.FONT_FAMILY,
|
||||
return rx.select.root(
|
||||
rx.select.trigger(
|
||||
placeholder="Initiated...",
|
||||
**compact_dropdown_trigger_style(),
|
||||
),
|
||||
# Dropdown using rx.select with Root > Trigger > Content > Item pattern
|
||||
rx.select.root(
|
||||
rx.select.trigger(placeholder="Select period..."),
|
||||
rx.select.content(
|
||||
rx.select.group(
|
||||
rx.select.label("Treatment Initiated"),
|
||||
rx.select.item("All years", value="all"),
|
||||
rx.select.item("Last 2 years", value="2yr"),
|
||||
rx.select.item("Last 1 year", value="1yr"),
|
||||
@@ -1398,58 +1399,33 @@ def initiated_filter_dropdown() -> rx.Component:
|
||||
),
|
||||
value=AppState.selected_initiated,
|
||||
on_change=AppState.set_initiated_filter,
|
||||
size="2",
|
||||
),
|
||||
# Description
|
||||
rx.text(
|
||||
"When patients first received treatment",
|
||||
font_size=Typography.CAPTION_SIZE,
|
||||
color=Colors.SLATE_500,
|
||||
font_family=Typography.FONT_FAMILY,
|
||||
),
|
||||
spacing="1",
|
||||
align="start",
|
||||
size="1",
|
||||
)
|
||||
|
||||
|
||||
def last_seen_filter_dropdown() -> rx.Component:
|
||||
"""
|
||||
Dropdown for selecting the last seen time period.
|
||||
Compact dropdown for selecting the last seen time period.
|
||||
|
||||
Redesigned for v2.1 filter strip - single row, no external label.
|
||||
Options: Last 6 months, Last 12 months
|
||||
Values: "6mo", "12mo"
|
||||
"""
|
||||
return rx.vstack(
|
||||
# Label
|
||||
rx.text(
|
||||
"Last Seen",
|
||||
font_size=Typography.H3_SIZE,
|
||||
font_weight=Typography.H3_WEIGHT,
|
||||
color=Colors.SLATE_900,
|
||||
font_family=Typography.FONT_FAMILY,
|
||||
return rx.select.root(
|
||||
rx.select.trigger(
|
||||
placeholder="Last seen...",
|
||||
**compact_dropdown_trigger_style(),
|
||||
),
|
||||
# Dropdown using rx.select with Root > Trigger > Content > Item pattern
|
||||
rx.select.root(
|
||||
rx.select.trigger(placeholder="Select period..."),
|
||||
rx.select.content(
|
||||
rx.select.group(
|
||||
rx.select.label("Last Seen"),
|
||||
rx.select.item("Last 6 months", value="6mo"),
|
||||
rx.select.item("Last 12 months", value="12mo"),
|
||||
),
|
||||
),
|
||||
value=AppState.selected_last_seen,
|
||||
on_change=AppState.set_last_seen_filter,
|
||||
size="2",
|
||||
),
|
||||
# Description
|
||||
rx.text(
|
||||
"Most recent treatment activity",
|
||||
font_size=Typography.CAPTION_SIZE,
|
||||
color=Colors.SLATE_500,
|
||||
font_family=Typography.FONT_FAMILY,
|
||||
),
|
||||
spacing="1",
|
||||
align="start",
|
||||
size="1",
|
||||
)
|
||||
|
||||
|
||||
@@ -1467,12 +1443,13 @@ def searchable_dropdown(
|
||||
clear_all_handler,
|
||||
) -> rx.Component:
|
||||
"""
|
||||
Searchable multi-select dropdown component.
|
||||
Compact searchable multi-select dropdown component.
|
||||
|
||||
Redesigned for v2.1 filter strip - 32px trigger, no external label.
|
||||
Uses debounced search input (300ms) for smooth filtering.
|
||||
|
||||
Args:
|
||||
label: Label for the dropdown
|
||||
label: Label shown inside dropdown panel header
|
||||
selection_text: Text showing selection count
|
||||
is_open: Whether dropdown is expanded
|
||||
toggle_handler: Handler to toggle dropdown open/close
|
||||
@@ -1485,86 +1462,86 @@ def searchable_dropdown(
|
||||
clear_all_handler: Handler to clear selection
|
||||
"""
|
||||
return rx.box(
|
||||
rx.vstack(
|
||||
# Label
|
||||
rx.text(
|
||||
label,
|
||||
font_size=Typography.CAPTION_SIZE,
|
||||
font_weight=Typography.CAPTION_WEIGHT,
|
||||
color=Colors.SLATE_700,
|
||||
font_family=Typography.FONT_FAMILY,
|
||||
),
|
||||
# Dropdown trigger button
|
||||
# Compact trigger button (no external label)
|
||||
rx.box(
|
||||
rx.hstack(
|
||||
rx.text(
|
||||
selection_text,
|
||||
font_size=Typography.BODY_SIZE,
|
||||
font_size=Typography.BODY_SMALL_SIZE,
|
||||
color=Colors.SLATE_900,
|
||||
font_family=Typography.FONT_FAMILY,
|
||||
flex="1",
|
||||
white_space="nowrap",
|
||||
overflow="hidden",
|
||||
text_overflow="ellipsis",
|
||||
),
|
||||
rx.icon(
|
||||
rx.cond(is_open, "chevron-up", "chevron-down"),
|
||||
size=16,
|
||||
size=14,
|
||||
color=Colors.SLATE_500,
|
||||
),
|
||||
justify="between",
|
||||
align="center",
|
||||
width="100%",
|
||||
gap=Spacing.SM,
|
||||
),
|
||||
**input_style(),
|
||||
display="flex",
|
||||
align_items="center",
|
||||
cursor="pointer",
|
||||
**compact_dropdown_trigger_style(),
|
||||
on_click=toggle_handler,
|
||||
width="100%",
|
||||
min_width="120px",
|
||||
),
|
||||
# Dropdown panel
|
||||
rx.cond(
|
||||
is_open,
|
||||
rx.box(
|
||||
rx.vstack(
|
||||
# Header with label
|
||||
rx.text(
|
||||
label,
|
||||
font_size=Typography.CAPTION_SIZE,
|
||||
font_weight=Typography.CAPTION_WEIGHT,
|
||||
color=Colors.SLATE_500,
|
||||
font_family=Typography.FONT_FAMILY,
|
||||
padding_x=Spacing.MD,
|
||||
padding_top=Spacing.SM,
|
||||
),
|
||||
# Search input (debounced 300ms)
|
||||
rx.hstack(
|
||||
rx.icon("search", size=14, color=Colors.SLATE_500),
|
||||
rx.icon("search", size=12, color=Colors.SLATE_500),
|
||||
rx.debounce_input(
|
||||
rx.input(
|
||||
placeholder="Search...",
|
||||
value=search_value,
|
||||
on_change=on_search_change,
|
||||
variant="soft",
|
||||
size="2",
|
||||
size="1",
|
||||
width="100%",
|
||||
),
|
||||
debounce_timeout=300,
|
||||
),
|
||||
spacing="2",
|
||||
spacing="1",
|
||||
align="center",
|
||||
width="100%",
|
||||
padding=Spacing.SM,
|
||||
background_color=Colors.SLATE_100,
|
||||
border_radius=Radii.SM,
|
||||
padding_x=Spacing.SM,
|
||||
),
|
||||
# Action buttons
|
||||
# Action buttons (more compact)
|
||||
rx.hstack(
|
||||
rx.button(
|
||||
"Select All",
|
||||
"All",
|
||||
on_click=select_all_handler,
|
||||
variant="ghost",
|
||||
size="1",
|
||||
color_scheme="blue",
|
||||
),
|
||||
rx.button(
|
||||
"Clear",
|
||||
"None",
|
||||
on_click=clear_all_handler,
|
||||
variant="ghost",
|
||||
size="1",
|
||||
color_scheme="gray",
|
||||
),
|
||||
spacing="2",
|
||||
spacing="1",
|
||||
padding_x=Spacing.SM,
|
||||
),
|
||||
# Items list
|
||||
# Items list (reduced height)
|
||||
rx.box(
|
||||
rx.foreach(
|
||||
filtered_items,
|
||||
@@ -1573,11 +1550,11 @@ def searchable_dropdown(
|
||||
item,
|
||||
checked=selected_items.contains(item),
|
||||
on_change=lambda: toggle_item_handler(item),
|
||||
size="2",
|
||||
size="1",
|
||||
),
|
||||
padding_y=Spacing.XS,
|
||||
padding_x=Spacing.SM,
|
||||
border_radius=Radii.SM,
|
||||
padding=f"{Spacing.SM} {Spacing.MD}",
|
||||
font_size=Typography.BODY_SMALL_SIZE,
|
||||
cursor="pointer",
|
||||
background_color=rx.cond(
|
||||
selected_items.contains(item),
|
||||
Colors.PALE,
|
||||
@@ -1589,33 +1566,23 @@ def searchable_dropdown(
|
||||
width="100%",
|
||||
),
|
||||
),
|
||||
max_height="200px",
|
||||
max_height="150px",
|
||||
overflow_y="auto",
|
||||
width="100%",
|
||||
),
|
||||
spacing="2",
|
||||
align="start",
|
||||
width="100%",
|
||||
padding=Spacing.SM,
|
||||
),
|
||||
position="absolute",
|
||||
top="100%",
|
||||
left="0",
|
||||
right="0",
|
||||
background_color=Colors.WHITE,
|
||||
border=f"1px solid {Colors.SLATE_300}",
|
||||
border_radius=Radii.MD,
|
||||
box_shadow=Shadows.LG,
|
||||
z_index="50",
|
||||
margin_top=Spacing.XS,
|
||||
),
|
||||
),
|
||||
spacing="1",
|
||||
align="start",
|
||||
width="100%",
|
||||
padding_bottom=Spacing.SM,
|
||||
),
|
||||
**searchable_dropdown_panel_style(),
|
||||
position="absolute",
|
||||
top="100%",
|
||||
left="0",
|
||||
margin_top=Spacing.XS,
|
||||
),
|
||||
),
|
||||
position="relative",
|
||||
width="100%",
|
||||
)
|
||||
|
||||
|
||||
@@ -1743,37 +1710,32 @@ def top_bar() -> rx.Component:
|
||||
|
||||
def filter_section() -> rx.Component:
|
||||
"""
|
||||
Filter section component.
|
||||
Compact filter strip component (v2.1 redesign).
|
||||
|
||||
Contains:
|
||||
- Two date filter dropdowns: Treatment Initiated, Last Seen
|
||||
- Three searchable multi-select dropdowns: Drugs, Indications, Directorates
|
||||
Single horizontal row containing ALL filters:
|
||||
- Date filters: Treatment Initiated, Last Seen
|
||||
- Multi-select filters: Drugs, Indications, Directorates
|
||||
|
||||
Layout: Two rows
|
||||
- Row 1: Date filter dropdowns side by side
|
||||
- Row 2: Three searchable dropdowns in a grid
|
||||
Target height: 48px (single row)
|
||||
No "Filters" header - labels are in dropdown triggers/panels.
|
||||
"""
|
||||
return rx.box(
|
||||
rx.vstack(
|
||||
# Header
|
||||
rx.text(
|
||||
"Filters",
|
||||
**text_h1(),
|
||||
),
|
||||
# Row 1: Date filter dropdowns
|
||||
rx.hstack(
|
||||
# Date filters group
|
||||
rx.hstack(
|
||||
initiated_filter_dropdown(),
|
||||
rx.divider(orientation="vertical", size="3"),
|
||||
last_seen_filter_dropdown(),
|
||||
spacing="5",
|
||||
align="start",
|
||||
flex_wrap="wrap",
|
||||
spacing="2",
|
||||
align="center",
|
||||
),
|
||||
# Divider
|
||||
rx.divider(size="4"),
|
||||
# Row 2: Searchable dropdowns
|
||||
# Separator
|
||||
rx.divider(
|
||||
orientation="vertical",
|
||||
size="2",
|
||||
color_scheme="gray",
|
||||
),
|
||||
# Multi-select filters group
|
||||
rx.hstack(
|
||||
rx.box(
|
||||
searchable_dropdown(
|
||||
label="Drugs",
|
||||
selection_text=AppState.drug_selection_text,
|
||||
@@ -1787,10 +1749,6 @@ def filter_section() -> rx.Component:
|
||||
select_all_handler=AppState.select_all_drugs,
|
||||
clear_all_handler=AppState.clear_all_drugs,
|
||||
),
|
||||
flex="1",
|
||||
min_width="200px",
|
||||
),
|
||||
rx.box(
|
||||
searchable_dropdown(
|
||||
label="Indications",
|
||||
selection_text=AppState.indication_selection_text,
|
||||
@@ -1804,10 +1762,6 @@ def filter_section() -> rx.Component:
|
||||
select_all_handler=AppState.select_all_indications,
|
||||
clear_all_handler=AppState.clear_all_indications,
|
||||
),
|
||||
flex="1",
|
||||
min_width="200px",
|
||||
),
|
||||
rx.box(
|
||||
searchable_dropdown(
|
||||
label="Directorates",
|
||||
selection_text=AppState.directorate_selection_text,
|
||||
@@ -1821,19 +1775,17 @@ def filter_section() -> rx.Component:
|
||||
select_all_handler=AppState.select_all_directorates,
|
||||
clear_all_handler=AppState.clear_all_directorates,
|
||||
),
|
||||
flex="1",
|
||||
min_width="200px",
|
||||
spacing="2",
|
||||
align="center",
|
||||
),
|
||||
spacing="4",
|
||||
# Spacer to push content left
|
||||
rx.spacer(),
|
||||
justify="start",
|
||||
align="center",
|
||||
gap=Spacing.LG,
|
||||
width="100%",
|
||||
flex_wrap="wrap",
|
||||
),
|
||||
spacing="4",
|
||||
width="100%",
|
||||
align="start",
|
||||
),
|
||||
**card_style(),
|
||||
width="100%",
|
||||
**filter_strip_style(),
|
||||
)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user