kanban-tui 0.9.0

Terminal user interface for the kanban project management tool
Documentation
use crate::app::App;
use crate::components::*;
use crate::theme::*;
use ratatui::{
    layout::{Constraint, Direction, Layout},
    style::{Color, Style},
    widgets::{Block, Borders, Clear, List, ListItem, Paragraph},
    Frame,
};

pub(crate) fn render_create_column_popup(app: &App, frame: &mut Frame) {
    let statuses = kanban_view::selection_dialog::DEFAULT_STATUS_POPUP_ORDER;
    let dialog_height = (1 + 3 + 1 + statuses.len() + 2 + 6) as u16;
    let area = centered_rect_abs(60, dialog_height, frame.area());
    frame.render_widget(Clear, area);

    let block = Block::default()
        .title("Create New Column")
        .borders(Borders::ALL)
        .style(Style::default().bg(Color::Black));
    let inner = block.inner(area);
    frame.render_widget(block, area);

    let chunks = Layout::default()
        .direction(Direction::Vertical)
        .margin(2)
        .constraints([
            Constraint::Length(1),
            Constraint::Length(3),
            Constraint::Length(1),
            Constraint::Min(0),
        ])
        .split(inner);

    let name_focused = app.dialog_input.create_column_focus_is_name();
    let unfocused_border = Style::default().fg(Color::DarkGray);

    frame.render_widget(
        Paragraph::new("Column Name:").style(Style::default().fg(Color::Yellow)),
        chunks[0],
    );

    let input = Paragraph::new(app.input.as_str())
        .style(crate::theme::normal_text())
        .block(
            Block::default()
                .borders(Borders::ALL)
                .border_style(if name_focused {
                    crate::theme::focused_border()
                } else {
                    unfocused_border
                }),
        );
    frame.render_widget(input, chunks[1]);
    if name_focused {
        let cursor_x = chunks[1].x + app.input.cursor_byte_offset() as u16 + 1;
        let cursor_y = chunks[1].y + 1;
        frame.set_cursor_position((cursor_x, cursor_y));
    }

    frame.render_widget(
        Paragraph::new("Default Status (Tab to switch, j/k to select):")
            .style(Style::default().fg(Color::Yellow)),
        chunks[2],
    );

    let selected = app.dialog_input.default_status_selection.get();
    let items: Vec<ListItem> = statuses
        .iter()
        .enumerate()
        .map(|(idx, (_, label))| {
            let style = if Some(idx) == selected {
                bold_highlight()
            } else {
                normal_text()
            };
            ListItem::new(*label).style(style)
        })
        .collect();
    let list = List::new(items).block(Block::default().borders(Borders::ALL).border_style(
        if name_focused {
            unfocused_border
        } else {
            crate::theme::focused_border()
        },
    ));
    frame.render_widget(list, chunks[3]);
}

pub(crate) fn render_rename_column_popup(app: &App, frame: &mut Frame) {
    render_input_popup(
        frame,
        "Rename Column",
        "New Column Name:",
        app.input.as_str(),
        app.input.cursor_byte_offset(),
    );
}

pub(crate) fn render_set_column_default_status_popup(app: &App, frame: &mut Frame) {
    use crate::components::{ColumnDefaultStatusDialog, SelectionDialog};
    let dialog = ColumnDefaultStatusDialog;
    dialog.render(app, frame);
}

pub(crate) fn render_delete_column_confirm_popup(_app: &App, frame: &mut Frame) {
    super::render_confirm_popup(
        frame,
        "Delete Column",
        "Are you sure you want to delete this column?\nAll cards will be moved to the first column."
            .to_string(),
    );
}

pub(crate) fn render_select_task_list_view_popup(app: &App, frame: &mut Frame) {
    use kanban_domain::TaskListView;

    let views = [
        TaskListView::Flat,
        TaskListView::GroupedByColumn,
        TaskListView::ColumnView,
    ];

    let selected = app.dialog_input.task_list_view_selection.get();

    let current_view = app.active_board().map(|board| board.task_list_view);

    let items: Vec<ListItem> = views
        .iter()
        .enumerate()
        .map(|(idx, view)| {
            let style = if Some(idx) == selected {
                bold_highlight()
            } else {
                normal_text()
            };
            let is_current = current_view == Some(*view);
            let view_name = match view {
                TaskListView::Flat => {
                    if is_current {
                        "Flat (current)"
                    } else {
                        "Flat"
                    }
                }
                TaskListView::GroupedByColumn => {
                    if is_current {
                        "Grouped by Column (current)"
                    } else {
                        "Grouped by Column"
                    }
                }
                TaskListView::ColumnView => {
                    if is_current {
                        "Column View (kanban board) (current)"
                    } else {
                        "Column View (kanban board)"
                    }
                }
            };
            ListItem::new(view_name).style(style)
        })
        .collect();

    render_selection_popup_with_list_items(frame, "Select Task List View", items, 50, 40);
}