ratatui_kit/components/
alert_modal.rs1use crossterm::event::{Event, KeyCode, KeyEventKind};
4use ratatui::{
5 layout::{Alignment, Constraint},
6 style::Style,
7 text::Line,
8 widgets::Padding,
9};
10use ratatui_kit_macros::{Props, component, element};
11
12use crate::{
13 AnyElement, ComponentTheme, Handler, Hooks, Palette, UseEventHandler, UseInputLayer, UseTheme,
14 components::theme::resolve_style,
15 components::{Border, Modal, Text, TextParagraph},
16 input::{EventPriority, EventResult, EventScope},
17};
18
19#[non_exhaustive]
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22pub struct AlertModalTheme {
23 pub border_style: Style,
25 pub title_style: Style,
27 pub message_style: Style,
29}
30
31impl ComponentTheme for AlertModalTheme {
32 fn from_palette(palette: &Palette) -> Self {
33 Self {
34 border_style: Style::new().fg(palette.warning),
35 title_style: Style::new().fg(palette.warning),
36 message_style: Style::new().fg(palette.fg),
37 }
38 }
39}
40
41impl Default for AlertModalTheme {
42 fn default() -> Self {
43 Self::from_palette(&Palette::default())
44 }
45}
46
47#[derive(Props)]
48pub struct AlertModalProps {
49 pub open: bool,
50 pub title: Line<'static>,
51 pub message: TextParagraph<'static>,
52 pub close_hint: Option<Line<'static>>,
53 pub close_keys: Vec<KeyCode>,
54 pub on_close: Handler<'static, ()>,
55 pub width: Constraint,
56 pub height: Constraint,
57 pub style: Option<Style>,
59 pub border_style: Option<Style>,
61 pub title_style: Option<Style>,
62 pub message_style: Option<Style>,
63 pub padding: Padding,
64}
65
66impl Default for AlertModalProps {
67 fn default() -> Self {
68 Self {
69 open: false,
70 title: Line::from("Alert"),
71 message: TextParagraph::from(""),
72 close_hint: Some(Line::from("Esc / Enter").centered()),
73 close_keys: vec![KeyCode::Esc, KeyCode::Enter],
74 on_close: Handler::default(),
75 width: Constraint::Percentage(50),
76 height: Constraint::Length(6),
77 style: None,
78 border_style: None,
79 title_style: None,
80 message_style: None,
81 padding: Padding::uniform(1),
82 }
83 }
84}
85
86#[component]
87pub fn AlertModal(props: &mut AlertModalProps, mut hooks: Hooks) -> impl Into<AnyElement<'static>> {
88 let layer = hooks.use_input_layer(props.open, true);
89 let close_keys = props.close_keys.clone();
90 let mut on_close = props.on_close.take();
91
92 hooks.use_event_handler(
93 EventScope::Layer(layer),
94 EventPriority::High,
95 move |event| {
96 if let Event::Key(key) = event
97 && key.kind == KeyEventKind::Press
98 && close_keys.contains(&key.code)
99 {
100 on_close(());
101 }
102 EventResult::Consumed
103 },
104 );
105
106 let theme = hooks.use_component_theme::<AlertModalTheme>();
108 let border_style = resolve_style(theme.border_style, props.border_style);
109 let title_style = resolve_style(theme.title_style, props.title_style);
110 let message_style = resolve_style(theme.message_style, props.message_style);
111
112 element!(Modal(
113 open: props.open,
114 layer: Some(layer),
115 width: props.width,
116 height: props.height,
117 style: props.style,
118 ) {
119 Border(
120 border_style: border_style,
121 top_title: props.title.clone().style(title_style).centered(),
122 bottom_title: props.close_hint.clone(),
123 padding: props.padding,
124 ) {
125 Text(
126 text: props.message.clone(),
127 style: message_style,
128 alignment: Alignment::Center,
129 wrap: true,
130 )
131 }
132 })
133}