Skip to main content

ratatui_kit/components/
alert_modal.rs

1// AlertModal 组件:带输入互斥的提示弹窗。
2
3use crossterm::event::{Event, KeyCode, KeyEventKind};
4use ratatui::{
5    layout::{Alignment, Constraint},
6    style::{Color, Style},
7    text::Line,
8    widgets::Padding,
9};
10use ratatui_kit_macros::{Props, component, element};
11
12use crate::{
13    AnyElement, Handler, Hooks, UseEventHandler, UseInputLayer,
14    components::{Border, Modal, Text, TextParagraph},
15    input::{EventPriority, EventResult, EventScope},
16};
17
18#[derive(Props)]
19pub struct AlertModalProps {
20    pub open: bool,
21    pub title: Line<'static>,
22    pub message: TextParagraph<'static>,
23    pub close_hint: Option<Line<'static>>,
24    pub close_keys: Vec<KeyCode>,
25    pub on_close: Handler<'static, ()>,
26    pub width: Constraint,
27    pub height: Constraint,
28    pub style: Style,
29    pub border_style: Style,
30    pub title_style: Style,
31    pub message_style: Style,
32    pub padding: Padding,
33}
34
35impl Default for AlertModalProps {
36    fn default() -> Self {
37        Self {
38            open: false,
39            title: Line::from("Alert"),
40            message: TextParagraph::from(""),
41            close_hint: Some(Line::from("Esc / Enter").centered()),
42            close_keys: vec![KeyCode::Esc, KeyCode::Enter],
43            on_close: Handler::default(),
44            width: Constraint::Percentage(50),
45            height: Constraint::Length(6),
46            style: Style::default().dim(),
47            border_style: Style::default().fg(Color::Yellow),
48            title_style: Style::default().fg(Color::Yellow),
49            message_style: Style::default(),
50            padding: Padding::uniform(1),
51        }
52    }
53}
54
55#[component]
56pub fn AlertModal(props: &mut AlertModalProps, mut hooks: Hooks) -> impl Into<AnyElement<'static>> {
57    let layer = hooks.use_input_layer(props.open, true);
58    let close_keys = props.close_keys.clone();
59    let mut on_close = props.on_close.take();
60
61    hooks.use_event_handler(
62        EventScope::Layer(layer),
63        EventPriority::High,
64        move |event| {
65            if let Event::Key(key) = event
66                && key.kind == KeyEventKind::Press
67                && close_keys.contains(&key.code)
68            {
69                on_close(());
70            }
71            EventResult::Consumed
72        },
73    );
74
75    element!(Modal(
76        open: props.open,
77        layer: Some(layer),
78        width: props.width,
79        height: props.height,
80        style: props.style,
81    ) {
82        Border(
83            border_style: props.border_style,
84            top_title: props.title.clone().style(props.title_style).centered(),
85            bottom_title: props.close_hint.clone(),
86            padding: props.padding,
87        ) {
88            Text(
89                text: props.message.clone(),
90                style: props.message_style,
91                alignment: Alignment::Center,
92                wrap: true,
93            )
94        }
95    })
96}