feat: create design tokens module (styles.py)

- Add Colors class with NHS-inspired blue palette and neutrals
- Add Typography class with font family, sizes, weights
- Add Spacing, Radii, Shadows, Transitions classes
- Add helper functions: card_style(), button_*_style(), input_style()
- Add KPI card and text style helpers
- Add layout constants (TOP_BAR_HEIGHT, PAGE_MAX_WIDTH)

All tokens match DESIGN_SYSTEM.md specifications.
This commit is contained in:
Andrew Charlwood
2026-02-04 13:10:35 +00:00
parent fdd33a67af
commit 2bd28f5f22
2 changed files with 411 additions and 3 deletions
+3 -3
View File
@@ -32,14 +32,14 @@ cd pathways_app && timeout 60 python -m reflex run 2>&1 | head -30
## Phase 1: Foundation
### 1.1 Design Tokens Module
- [ ] Create `pathways_app/styles.py` with design token classes:
- [x] Create `pathways_app/styles.py` with design token classes:
- `Colors` class with all palette colors as constants
- `Typography` class with font sizes, weights
- `Spacing` class with spacing scale
- `Shadows` class with shadow values
- `Radii` class with border radius values
- [ ] Create helper functions for common style patterns (e.g., `card_style()`, `button_primary_style()`)
- [ ] Verify imports work: `from pathways_app.styles import Colors, Spacing`
- [x] Create helper functions for common style patterns (e.g., `card_style()`, `button_primary_style()`)
- [x] Verify imports work: `from pathways_app.styles import Colors, Spacing`
### 1.2 App Skeleton
- [ ] Create `pathways_app/app_v2.py` with basic Reflex app structure
+408
View File
@@ -0,0 +1,408 @@
"""
Design tokens and style helpers for HCD Analysis v2.
All visual styling should use these tokens for consistency.
Import: from pathways_app.styles import Colors, Spacing, Typography, etc.
"""
class Colors:
"""Color palette from DESIGN_SYSTEM.md"""
# Primary Blues (NHS-inspired, modernized)
HERITAGE_BLUE = "#003087" # Deep headers, authoritative accents
PRIMARY = "#0066CC" # Main actions, links, focus states
VIBRANT = "#1E88E5" # Highlights, hover states, chart primary
SKY = "#4FC3F7" # Accents, progress bars, secondary elements
PALE = "#E3F2FD" # Subtle backgrounds, card tints
# Neutrals (warm-tinted for clinical warmth)
SLATE_900 = "#1E293B" # Primary text
SLATE_700 = "#334155" # Secondary text
SLATE_500 = "#64748B" # Muted text, placeholders
SLATE_300 = "#CBD5E1" # Borders, dividers
SLATE_100 = "#F1F5F9" # Card backgrounds, hover states
WHITE = "#FFFFFF" # Page background
# Semantic Colors
SUCCESS = "#059669" # Positive states, confirmations
WARNING = "#D97706" # Caution states, alerts
ERROR = "#DC2626" # Error states, destructive actions
INFO = "#0284C7" # Informational (matches primary family)
# Chart Palette
CHART_SERIES = ["#003087", "#0066CC", "#1E88E5", "#4FC3F7", "#90CAF9"]
CHART_CATEGORICAL = ["#0066CC", "#059669", "#D97706", "#8B5CF6", "#EC4899"]
class Typography:
"""Typography tokens from DESIGN_SYSTEM.md"""
# Font families
FONT_FAMILY = "Inter, system-ui, -apple-system, sans-serif"
FONT_MONO = "JetBrains Mono, monospace"
# Display: Page titles
DISPLAY_SIZE = "32px"
DISPLAY_WEIGHT = "700"
DISPLAY_TRACKING = "-0.02em"
DISPLAY_LINE_HEIGHT = "1.2"
# Heading 1: Section headers
H1_SIZE = "24px"
H1_WEIGHT = "600"
H1_TRACKING = "-0.01em"
H1_LINE_HEIGHT = "1.3"
# Heading 2: Card titles
H2_SIZE = "20px"
H2_WEIGHT = "600"
H2_TRACKING = "normal"
H2_LINE_HEIGHT = "1.4"
# Heading 3: Subsections
H3_SIZE = "16px"
H3_WEIGHT = "600"
H3_TRACKING = "normal"
H3_LINE_HEIGHT = "1.4"
# Body: Default text
BODY_SIZE = "14px"
BODY_WEIGHT = "400"
BODY_LINE_HEIGHT = "1.5"
# Body Small: Secondary info
BODY_SMALL_SIZE = "13px"
BODY_SMALL_WEIGHT = "400"
BODY_SMALL_LINE_HEIGHT = "1.5"
# Caption: Labels, metadata
CAPTION_SIZE = "12px"
CAPTION_WEIGHT = "500"
CAPTION_LINE_HEIGHT = "1.4"
# Mono: Data values, codes
MONO_SIZE = "13px"
MONO_WEIGHT = "400"
MONO_LINE_HEIGHT = "1.5"
class Spacing:
"""Spacing scale from DESIGN_SYSTEM.md"""
XS = "4px" # Tight internal padding
SM = "8px" # Between related elements
MD = "12px" # Standard gaps
LG = "16px" # Section padding
XL = "24px" # Card padding
XXL = "32px" # Major section gaps
XXXL = "48px" # Page margins
class Radii:
"""Border radius values from DESIGN_SYSTEM.md"""
SM = "4px" # Small elements, inputs
MD = "8px" # Buttons, small cards
LG = "12px" # Cards, modals
XL = "16px" # Large containers
FULL = "9999px" # Pills, avatars
class Shadows:
"""Shadow values from DESIGN_SYSTEM.md"""
SM = "0 1px 2px rgba(0,0,0,0.05)" # Subtle elevation
MD = "0 1px 3px rgba(0,0,0,0.08)" # Cards at rest
LG = "0 4px 6px rgba(0,0,0,0.1)" # Cards on hover, dropdowns
XL = "0 10px 15px rgba(0,0,0,0.1)" # Modals, popovers
class Transitions:
"""Transition values from DESIGN_SYSTEM.md"""
COLOR = "150ms ease-out"
TRANSFORM = "200ms ease-out"
SHADOW = "200ms ease-out"
OPACITY = "200ms ease-in-out"
# ==============================================================================
# Helper functions for common style patterns
# ==============================================================================
def card_style(hoverable: bool = False) -> dict:
"""
Card styling following DESIGN_SYSTEM.md specifications.
- Background: White
- Border: 1px Slate 300
- Border radius: lg (12px)
- Padding: xl (24px)
- Shadow: md at rest, lg on hover
"""
base_style = {
"background_color": Colors.WHITE,
"border": f"1px solid {Colors.SLATE_300}",
"border_radius": Radii.LG,
"padding": Spacing.XL,
"box_shadow": Shadows.MD,
}
if hoverable:
base_style.update({
"transition": f"box-shadow {Transitions.SHADOW}, transform {Transitions.TRANSFORM}",
"_hover": {
"box_shadow": Shadows.LG,
"transform": "translateY(-2px)",
}
})
return base_style
def button_primary_style() -> dict:
"""
Primary button styling following DESIGN_SYSTEM.md specifications.
- Background: Primary Blue
- Text: White
- Border radius: md (8px)
- Padding: 10px 20px
- Hover: Vibrant Blue background, slight scale (1.02)
"""
return {
"background_color": Colors.PRIMARY,
"color": Colors.WHITE,
"border_radius": Radii.MD,
"padding": "10px 20px",
"font_weight": "500",
"font_size": Typography.BODY_SIZE,
"cursor": "pointer",
"border": "none",
"transition": f"background-color {Transitions.COLOR}, transform {Transitions.TRANSFORM}",
"_hover": {
"background_color": Colors.VIBRANT,
"transform": "scale(1.02)",
}
}
def button_secondary_style() -> dict:
"""
Secondary button styling following DESIGN_SYSTEM.md specifications.
- Background: White
- Border: 1px Primary Blue
- Text: Primary Blue
- Hover: Pale Blue background
"""
return {
"background_color": Colors.WHITE,
"color": Colors.PRIMARY,
"border": f"1px solid {Colors.PRIMARY}",
"border_radius": Radii.MD,
"padding": "10px 20px",
"font_weight": "500",
"font_size": Typography.BODY_SIZE,
"cursor": "pointer",
"transition": f"background-color {Transitions.COLOR}",
"_hover": {
"background_color": Colors.PALE,
}
}
def button_ghost_style() -> dict:
"""
Ghost button styling following DESIGN_SYSTEM.md specifications.
- Background: transparent
- Text: Primary Blue
- Hover: Pale Blue background
"""
return {
"background_color": "transparent",
"color": Colors.PRIMARY,
"border": "none",
"border_radius": Radii.MD,
"padding": "10px 20px",
"font_weight": "500",
"font_size": Typography.BODY_SIZE,
"cursor": "pointer",
"transition": f"background-color {Transitions.COLOR}",
"_hover": {
"background_color": Colors.PALE,
}
}
def input_style() -> dict:
"""
Form input styling following DESIGN_SYSTEM.md specifications.
- Height: 40px
- Border: 1px Slate 300
- Border radius: md (8px)
- Focus: 2px Primary Blue ring
- Placeholder: Slate 500
"""
return {
"height": "40px",
"border": f"1px solid {Colors.SLATE_300}",
"border_radius": Radii.MD,
"padding": f"0 {Spacing.MD}",
"font_size": Typography.BODY_SIZE,
"font_family": Typography.FONT_FAMILY,
"color": Colors.SLATE_900,
"background_color": Colors.WHITE,
"transition": f"border-color {Transitions.COLOR}, box-shadow {Transitions.COLOR}",
"_placeholder": {
"color": Colors.SLATE_500,
},
"_focus": {
"outline": "none",
"border_color": Colors.PRIMARY,
"box_shadow": f"0 0 0 2px {Colors.PALE}",
}
}
def kpi_card_style() -> dict:
"""
KPI card styling following DESIGN_SYSTEM.md specifications.
- Large mono number: 32-48px, Slate 900
- Label: Caption size, Slate 500
- Background: White or Pale Blue tint
"""
return {
"background_color": Colors.WHITE,
"border": f"1px solid {Colors.SLATE_300}",
"border_radius": Radii.LG,
"padding": Spacing.XL,
"box_shadow": Shadows.SM,
"text_align": "center",
}
def kpi_value_style() -> dict:
"""Style for the large number in a KPI card."""
return {
"font_family": Typography.FONT_MONO,
"font_size": "32px",
"font_weight": "600",
"color": Colors.SLATE_900,
"line_height": "1.2",
}
def kpi_label_style() -> dict:
"""Style for the label in a KPI card."""
return {
"font_size": Typography.CAPTION_SIZE,
"font_weight": Typography.CAPTION_WEIGHT,
"color": Colors.SLATE_500,
"margin_top": Spacing.SM,
}
def text_display() -> dict:
"""Display text style for page titles."""
return {
"font_size": Typography.DISPLAY_SIZE,
"font_weight": Typography.DISPLAY_WEIGHT,
"letter_spacing": Typography.DISPLAY_TRACKING,
"line_height": Typography.DISPLAY_LINE_HEIGHT,
"color": Colors.SLATE_900,
"font_family": Typography.FONT_FAMILY,
}
def text_h1() -> dict:
"""Heading 1 style for section headers."""
return {
"font_size": Typography.H1_SIZE,
"font_weight": Typography.H1_WEIGHT,
"letter_spacing": Typography.H1_TRACKING,
"line_height": Typography.H1_LINE_HEIGHT,
"color": Colors.SLATE_900,
"font_family": Typography.FONT_FAMILY,
}
def text_h2() -> dict:
"""Heading 2 style for card titles."""
return {
"font_size": Typography.H2_SIZE,
"font_weight": Typography.H2_WEIGHT,
"letter_spacing": Typography.H2_TRACKING,
"line_height": Typography.H2_LINE_HEIGHT,
"color": Colors.SLATE_900,
"font_family": Typography.FONT_FAMILY,
}
def text_h3() -> dict:
"""Heading 3 style for subsections."""
return {
"font_size": Typography.H3_SIZE,
"font_weight": Typography.H3_WEIGHT,
"letter_spacing": Typography.H3_TRACKING,
"line_height": Typography.H3_LINE_HEIGHT,
"color": Colors.SLATE_900,
"font_family": Typography.FONT_FAMILY,
}
def text_body() -> dict:
"""Default body text style."""
return {
"font_size": Typography.BODY_SIZE,
"font_weight": Typography.BODY_WEIGHT,
"line_height": Typography.BODY_LINE_HEIGHT,
"color": Colors.SLATE_900,
"font_family": Typography.FONT_FAMILY,
}
def text_body_small() -> dict:
"""Secondary/small body text style."""
return {
"font_size": Typography.BODY_SMALL_SIZE,
"font_weight": Typography.BODY_SMALL_WEIGHT,
"line_height": Typography.BODY_SMALL_LINE_HEIGHT,
"color": Colors.SLATE_700,
"font_family": Typography.FONT_FAMILY,
}
def text_caption() -> dict:
"""Caption style for labels and metadata."""
return {
"font_size": Typography.CAPTION_SIZE,
"font_weight": Typography.CAPTION_WEIGHT,
"line_height": Typography.CAPTION_LINE_HEIGHT,
"color": Colors.SLATE_500,
"font_family": Typography.FONT_FAMILY,
}
def text_mono() -> dict:
"""Monospace text style for data values and codes."""
return {
"font_size": Typography.MONO_SIZE,
"font_weight": Typography.MONO_WEIGHT,
"line_height": Typography.MONO_LINE_HEIGHT,
"color": Colors.SLATE_900,
"font_family": Typography.FONT_MONO,
}
# ==============================================================================
# Layout constants
# ==============================================================================
TOP_BAR_HEIGHT = "64px"
PAGE_MAX_WIDTH = "1600px"
PAGE_PADDING = Spacing.XXXL