Initial commit before Ralph loop
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
"""
|
||||
UI components for the Patient Pathway Analysis Reflex application.
|
||||
|
||||
This module exports reusable layout and navigation components.
|
||||
"""
|
||||
|
||||
from .layout import sidebar, navbar, content_area, main_layout
|
||||
from .navigation import nav_item, nav_section
|
||||
|
||||
__all__ = [
|
||||
"sidebar",
|
||||
"navbar",
|
||||
"content_area",
|
||||
"main_layout",
|
||||
"nav_item",
|
||||
"nav_section",
|
||||
]
|
||||
@@ -0,0 +1,262 @@
|
||||
"""
|
||||
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,
|
||||
)
|
||||
@@ -0,0 +1,86 @@
|
||||
"""
|
||||
Navigation components for the Patient Pathway Analysis tool.
|
||||
|
||||
Provides sidebar navigation items with icons, matching the CustomTkinter design.
|
||||
Includes accessibility features: ARIA labels, keyboard navigation, focus indicators.
|
||||
"""
|
||||
|
||||
import reflex as rx
|
||||
from typing import Callable
|
||||
|
||||
|
||||
def nav_item(
|
||||
text: str,
|
||||
href: str,
|
||||
icon: str,
|
||||
is_active: bool = False,
|
||||
) -> rx.Component:
|
||||
"""
|
||||
Create a navigation item with icon.
|
||||
|
||||
Args:
|
||||
text: The display text for the nav item
|
||||
href: The route to navigate to
|
||||
icon: The Lucide icon name (e.g., "home", "pill", "building", "folder")
|
||||
is_active: Whether this item is currently active
|
||||
|
||||
Returns:
|
||||
A styled navigation button component with accessibility support
|
||||
"""
|
||||
# NHS colors - use blue for active state
|
||||
active_bg = "rgb(0, 94, 184)" # NHS Blue
|
||||
hover_bg = "rgb(0, 48, 135)" # NHS Dark Blue
|
||||
|
||||
return rx.link(
|
||||
rx.hstack(
|
||||
rx.icon(icon, size=20, aria_hidden="true"), # Hide decorative icon from screen readers
|
||||
rx.text(text, size="3", weight="medium"),
|
||||
width="100%",
|
||||
padding="12px 16px",
|
||||
spacing="3",
|
||||
align="center",
|
||||
border_radius="8px",
|
||||
bg=rx.cond(is_active, active_bg, "transparent"),
|
||||
color=rx.cond(is_active, "white", "inherit"),
|
||||
_hover={
|
||||
"background": rx.cond(is_active, active_bg, "rgba(0, 94, 184, 0.1)"),
|
||||
},
|
||||
_focus_visible={
|
||||
"outline": "2px solid rgb(0, 94, 184)",
|
||||
"outline_offset": "2px",
|
||||
},
|
||||
transition="background 0.2s ease",
|
||||
),
|
||||
href=href,
|
||||
text_decoration="none",
|
||||
width="100%",
|
||||
aria_current=rx.cond(is_active, "page", ""),
|
||||
)
|
||||
|
||||
|
||||
def nav_section(title: str, children: list[rx.Component]) -> rx.Component:
|
||||
"""
|
||||
Create a labeled section of navigation items.
|
||||
|
||||
Args:
|
||||
title: Section header text
|
||||
children: List of nav_item components
|
||||
|
||||
Returns:
|
||||
A styled section with header and items
|
||||
"""
|
||||
return rx.vstack(
|
||||
rx.text(
|
||||
title,
|
||||
size="1",
|
||||
weight="bold",
|
||||
color="gray",
|
||||
padding_x="16px",
|
||||
padding_top="16px",
|
||||
padding_bottom="8px",
|
||||
),
|
||||
*children,
|
||||
width="100%",
|
||||
spacing="1",
|
||||
align="start",
|
||||
)
|
||||
Reference in New Issue
Block a user