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