tazuna 0.1.0

TUI tool for managing multiple Claude Code sessions in parallel
Documentation
//! Theme constants and style helpers for consistent TUI styling.
//!
//! Provides centralized style definitions to eliminate duplicated focus/border logic.

use ratatui::{
    style::{Color, Modifier, Style},
    widgets::{Block, BorderType, Borders},
};

/// Border color when element is focused.
pub const BORDER_FOCUSED: Color = Color::Cyan;

/// Border color when element is not focused.
pub const BORDER_UNFOCUSED: Color = Color::Gray;

/// Border color for error popups.
pub const BORDER_ERROR: Color = Color::Red;

/// Border color for warning/confirmation popups.
pub const BORDER_WARNING: Color = Color::Yellow;

/// Block style variants for tazuna UI.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BlockVariant {
    /// Focused element (Cyan border)
    Focused,
    /// Unfocused element (Gray border)
    Unfocused,
    /// Error popup (Red border)
    Error,
    /// Warning/confirmation popup (Yellow border)
    Warning,
}

/// Background color for highlighted/selected items.
pub const HIGHLIGHT_BG: Color = Color::DarkGray;

/// Symbol shown before selected item when focused.
pub const HIGHLIGHT_SYMBOL: &str = "> ";

/// Symbol shown before selected item when not focused (maintains alignment).
pub const NO_HIGHLIGHT_SYMBOL: &str = "  ";

/// Create a styled `Block` with rounded borders.
///
/// # Examples
/// ```ignore
/// use crate::tui::theme::{block, BlockVariant};
///
/// let b = block("Sessions", BlockVariant::Focused);
/// let b = block("Error", BlockVariant::Error);
/// ```
#[must_use]
pub fn block(title: &str, variant: BlockVariant) -> Block<'_> {
    Block::default()
        .title(title)
        .borders(Borders::ALL)
        .border_type(BorderType::Rounded)
        .border_style(Style::default().fg(match variant {
            BlockVariant::Focused => BORDER_FOCUSED,
            BlockVariant::Unfocused => BORDER_UNFOCUSED,
            BlockVariant::Error => BORDER_ERROR,
            BlockVariant::Warning => BORDER_WARNING,
        }))
}

/// Get highlight style for selected items based on focus state.
///
/// Returns bold + dark background when focused, default style otherwise.
#[must_use]
pub fn highlight_style(focused: bool) -> Style {
    if focused {
        Style::default()
            .bg(HIGHLIGHT_BG)
            .add_modifier(Modifier::BOLD)
    } else {
        Style::default()
    }
}

/// Get highlight symbol based on focus state.
///
/// Returns "> " when focused, "  " (two spaces) otherwise.
#[must_use]
pub fn highlight_symbol(focused: bool) -> &'static str {
    if focused {
        HIGHLIGHT_SYMBOL
    } else {
        NO_HIGHLIGHT_SYMBOL
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn block_focused_has_cyan_border() {
        let b = block("Test", BlockVariant::Focused);
        assert_eq!(b.inner(ratatui::layout::Rect::new(0, 0, 10, 5)).width, 8);
    }

    #[test]
    fn block_unfocused_has_gray_border() {
        let b = block("Test", BlockVariant::Unfocused);
        assert_eq!(b.inner(ratatui::layout::Rect::new(0, 0, 10, 5)).width, 8);
    }

    #[test]
    fn block_error_has_red_border() {
        let b = block("Error", BlockVariant::Error);
        assert_eq!(b.inner(ratatui::layout::Rect::new(0, 0, 10, 5)).width, 8);
    }

    #[test]
    fn block_warning_has_yellow_border() {
        let b = block("Warning", BlockVariant::Warning);
        assert_eq!(b.inner(ratatui::layout::Rect::new(0, 0, 10, 5)).width, 8);
    }

    #[test]
    fn highlight_style_returns_bold_bg_when_focused() {
        let style = highlight_style(true);
        assert!(style.add_modifier.contains(Modifier::BOLD));
        assert_eq!(style.bg, Some(HIGHLIGHT_BG));
    }

    #[test]
    fn highlight_style_returns_default_when_unfocused() {
        let style = highlight_style(false);
        assert!(!style.add_modifier.contains(Modifier::BOLD));
        assert_eq!(style.bg, None);
    }

    #[test]
    fn highlight_symbol_returns_arrow_when_focused() {
        assert_eq!(highlight_symbol(true), "> ");
    }

    #[test]
    fn highlight_symbol_returns_spaces_when_unfocused() {
        assert_eq!(highlight_symbol(false), "  ");
    }
}