Files
HighCostDrugsDemo/pathways_app/components/layout.py
T
2026-02-04 13:04:29 +00:00

263 lines
7.1 KiB
Python

"""
Layout components for the Patient Pathway Analysis tool.
Provides the main application layout with sidebar navigation and content area.
Includes accessibility features: skip links, ARIA landmarks, keyboard navigation.
"""
import reflex as rx
from .navigation import nav_item
# NHS Color scheme
NHS_BLUE = "rgb(0, 94, 184)"
NHS_DARK_BLUE = "rgb(0, 48, 135)"
NHS_LIGHT_BLUE = "rgb(65, 182, 230)"
NHS_WHITE = "white"
NHS_GREY = "rgb(231, 231, 231)"
def skip_link() -> rx.Component:
"""
Skip link for keyboard users to bypass navigation.
Visually hidden until focused, allowing keyboard users to skip
directly to main content.
"""
return rx.link(
"Skip to main content",
href="#main-content",
position="absolute",
top="-40px",
left="0",
background=NHS_BLUE,
color="white",
padding="8px 16px",
z_index="1000",
text_decoration="none",
font_weight="bold",
_focus={
"top": "0",
},
)
def logo_section() -> rx.Component:
"""NHS branding logo section at top of sidebar."""
return rx.hstack(
rx.image(
src="/logo.png",
height="32px",
alt="NHS Norfolk and Waveney Logo",
),
rx.text(
"HCD Analysis",
size="5",
weight="bold",
color=NHS_BLUE,
),
padding="16px",
spacing="3",
align="center",
width="100%",
border_bottom=f"1px solid {NHS_GREY}",
)
def sidebar(current_page: str = "home") -> rx.Component:
"""
Create the sidebar navigation panel.
Args:
current_page: The current active page name for highlighting
Returns:
A sidebar component with navigation items and ARIA landmark
"""
return rx.el.nav(
rx.vstack(
# Logo section
logo_section(),
# Navigation items
rx.vstack(
nav_item(
"Home",
"/",
"home",
is_active=(current_page == "home"),
),
nav_item(
"Drug Selection",
"/drugs",
"pill",
is_active=(current_page == "drugs"),
),
nav_item(
"Trust Selection",
"/trusts",
"building",
is_active=(current_page == "trusts"),
),
nav_item(
"Directory Selection",
"/directories",
"folder",
is_active=(current_page == "directories"),
),
padding="8px",
spacing="1",
width="100%",
align="start",
),
# Spacer to push theme toggle to bottom
rx.spacer(),
# Theme toggle at bottom
rx.box(
rx.hstack(
rx.el.label(
"Theme:",
html_for="theme-toggle",
font_size="14px",
color="gray",
),
rx.color_mode.switch(id="theme-toggle"),
spacing="2",
align="center",
),
padding="16px",
border_top=f"1px solid {NHS_GREY}",
width="100%",
),
height="100vh",
width="100%",
spacing="0",
align="start",
),
aria_label="Main navigation",
width="240px",
min_width="240px",
background="white",
border_right=f"1px solid {NHS_GREY}",
position="fixed",
left="0",
top="0",
height="100vh",
overflow_y="auto",
z_index="100",
)
def navbar() -> rx.Component:
"""
Create a top navigation bar for mobile/smaller screens.
Returns:
A horizontal navbar component (collapsed sidebar for mobile) with ARIA support
"""
return rx.el.header(
rx.hstack(
rx.image(src="/logo.png", height="28px", alt="NHS Norfolk and Waveney Logo"),
rx.text("HCD Analysis", size="4", weight="bold"),
rx.spacer(),
rx.el.label(
rx.color_mode.switch(id="theme-toggle-mobile"),
html_for="theme-toggle-mobile",
aria_label="Toggle dark mode",
),
width="100%",
padding="12px 16px",
align="center",
justify="between",
),
background="white",
border_bottom=f"1px solid {NHS_GREY}",
display=["flex", "flex", "none"], # Show on mobile, hide on desktop
width="100%",
position="fixed",
top="0",
left="0",
z_index="100",
role="banner",
)
def content_area(*children, page_title: str = "") -> rx.Component:
"""
Create the main content area.
Args:
*children: Child components to render in the content area
page_title: Optional title to display at top of content
Returns:
A styled content area component with ARIA main landmark
"""
content_children = list(children)
if page_title:
content_children.insert(
0,
rx.heading(
page_title,
size="6",
weight="bold",
color=NHS_DARK_BLUE,
margin_bottom="16px",
),
)
return rx.el.main(
rx.vstack(
*content_children,
width="100%",
max_width="1200px",
padding="24px",
spacing="4",
align="start",
),
id="main-content",
tabindex="-1", # Allow focus for skip link
# Offset for sidebar on desktop
margin_left=["0", "0", "240px"],
# Offset for navbar on mobile
margin_top=["60px", "60px", "0"],
min_height="100vh",
background=rx.color_mode_cond(
light="rgb(249, 250, 251)", # Light gray background
dark="rgb(17, 24, 39)", # Dark background
),
width="100%",
_focus={
"outline": "none", # Hide focus ring on main (only accessible via skip link)
},
)
def main_layout(
content: rx.Component,
current_page: str = "home",
) -> rx.Component:
"""
Create the complete page layout with sidebar and content.
Args:
content: The main content to display
current_page: The current page name for navigation highlighting
Returns:
A complete page layout component with accessibility features
"""
return rx.fragment(
# Skip link for keyboard users
skip_link(),
# Sidebar (visible on desktop)
rx.box(
sidebar(current_page=current_page),
display=["none", "none", "block"], # Hide on mobile
),
# Navbar (visible on mobile)
navbar(),
# Main content
content,
)