ratatui_widgets/list/
item.rs

1use ratatui_core::style::Style;
2use ratatui_core::text::Text;
3
4/// A single item in a [`List`]
5///
6/// The item's height is defined by the number of lines it contains. This can be queried using
7/// [`ListItem::height`]. Similarly, [`ListItem::width`] will return the maximum width of all
8/// lines.
9///
10/// You can set the style of an item with [`ListItem::style`] or using the [`Stylize`] trait.
11/// This [`Style`] will be combined with the [`Style`] of the inner [`Text`]. The [`Style`]
12/// of the [`Text`] will be added to the [`Style`] of the [`ListItem`].
13///
14/// You can also align a `ListItem` by aligning its underlying [`Text`] and [`Line`]s. For that,
15/// see [`Text::alignment`] and [`Line::alignment`]. On a multiline `Text`, one `Line` can override
16/// the alignment by setting it explicitly.
17///
18/// # Examples
19///
20/// You can create [`ListItem`]s from simple `&str`
21///
22/// ```rust
23/// use ratatui::widgets::ListItem;
24/// let item = ListItem::new("Item 1");
25/// ```
26///
27/// Anything that can be converted to [`Text`] can be a [`ListItem`].
28///
29/// ```rust
30/// use ratatui::text::Line;
31/// use ratatui::widgets::ListItem;
32///
33/// let item1: ListItem = "Item 1".into();
34/// let item2: ListItem = Line::raw("Item 2").into();
35/// ```
36///
37/// A [`ListItem`] styled with [`Stylize`]
38///
39/// ```rust
40/// use ratatui::style::Stylize;
41/// use ratatui::widgets::ListItem;
42///
43/// let item = ListItem::new("Item 1").red().on_white();
44/// ```
45///
46/// If you need more control over the item's style, you can explicitly style the underlying
47/// [`Text`]
48///
49/// ```rust
50/// use ratatui::style::Stylize;
51/// use ratatui::text::{Span, Text};
52/// use ratatui::widgets::ListItem;
53///
54/// let mut text = Text::default();
55/// text.extend(["Item".blue(), Span::raw(" "), "1".bold().red()]);
56/// let item = ListItem::new(text);
57/// ```
58///
59/// A right-aligned `ListItem`
60///
61/// ```rust
62/// use ratatui::text::Text;
63/// use ratatui::widgets::ListItem;
64///
65/// ListItem::new(Text::from("foo").right_aligned());
66/// ```
67///
68/// [`List`]: crate::list::List
69/// [`Stylize`]: ratatui_core::style::Stylize
70/// [`Line`]: ratatui_core::text::Line
71/// [`Line::alignment`]: ratatui_core::text::Line::alignment
72#[derive(Debug, Clone, Eq, PartialEq, Hash)]
73pub struct ListItem<'a> {
74    pub(crate) content: Text<'a>,
75    pub(crate) style: Style,
76}
77
78impl<'a> ListItem<'a> {
79    /// Creates a new [`ListItem`]
80    ///
81    /// The `content` parameter accepts any value that can be converted into [`Text`].
82    ///
83    /// # Examples
84    ///
85    /// You can create [`ListItem`]s from simple `&str`
86    ///
87    /// ```rust
88    /// use ratatui::widgets::ListItem;
89    ///
90    /// let item = ListItem::new("Item 1");
91    /// ```
92    ///
93    /// Anything that can be converted to [`Text`] can be a [`ListItem`].
94    ///
95    /// ```rust
96    /// use ratatui::text::Line;
97    /// use ratatui::widgets::ListItem;
98    ///
99    /// let item1: ListItem = "Item 1".into();
100    /// let item2: ListItem = Line::raw("Item 2").into();
101    /// ```
102    ///
103    /// You can also create multiline items
104    ///
105    /// ```rust
106    /// use ratatui::widgets::ListItem;
107    ///
108    /// let item = ListItem::new("Multi-line\nitem");
109    /// ```
110    ///
111    /// # See also
112    ///
113    /// - [`List::new`](super::List::new) to create a list of items that can be converted to
114    ///   [`ListItem`]
115    pub fn new<T>(content: T) -> Self
116    where
117        T: Into<Text<'a>>,
118    {
119        Self {
120            content: content.into(),
121            style: Style::default(),
122        }
123    }
124
125    /// Sets the item style
126    ///
127    /// `style` accepts any type that is convertible to [`Style`] (e.g. [`Style`], [`Color`], or
128    /// your own type that implements [`Into<Style>`]).
129    ///
130    /// This [`Style`] can be overridden by the [`Style`] of the [`Text`] content.
131    ///
132    /// This is a fluent setter method which must be chained or used as it consumes self
133    ///
134    /// # Example
135    ///
136    /// ```rust
137    /// use ratatui::style::{Style, Stylize};
138    /// use ratatui::widgets::ListItem;
139    ///
140    /// let item = ListItem::new("Item 1").style(Style::new().red().italic());
141    /// ```
142    ///
143    /// `ListItem` also implements the [`Styled`] trait, which means you can use style shorthands
144    /// from the [`Stylize`](ratatui_core::style::Stylize) trait to set the style of the widget more
145    /// concisely.
146    ///
147    /// ```rust
148    /// use ratatui::style::Stylize;
149    /// use ratatui::widgets::ListItem;
150    ///
151    /// let item = ListItem::new("Item 1").red().italic();
152    /// ```
153    ///
154    /// [`Styled`]: ratatui_core::style::Styled
155    /// [`ListState`]: crate::list::ListState
156    /// [`Color`]: ratatui_core::style::Color
157    #[must_use = "method moves the value of self and returns the modified value"]
158    pub fn style<S: Into<Style>>(mut self, style: S) -> Self {
159        self.style = style.into();
160        self
161    }
162
163    /// Returns the item height
164    ///
165    /// # Examples
166    ///
167    /// One line item
168    ///
169    /// ```rust
170    /// use ratatui::widgets::ListItem;
171    ///
172    /// let item = ListItem::new("Item 1");
173    /// assert_eq!(item.height(), 1);
174    /// ```
175    ///
176    /// Two lines item (note the `\n`)
177    ///
178    /// ```rust
179    /// use ratatui::widgets::ListItem;
180    ///
181    /// let item = ListItem::new("Multi-line\nitem");
182    /// assert_eq!(item.height(), 2);
183    /// ```
184    pub fn height(&self) -> usize {
185        self.content.height()
186    }
187
188    /// Returns the max width of all the lines
189    ///
190    /// # Examples
191    ///
192    /// ```rust
193    /// use ratatui::widgets::ListItem;
194    ///
195    /// let item = ListItem::new("12345");
196    /// assert_eq!(item.width(), 5);
197    /// ```
198    ///
199    /// ```rust
200    /// use ratatui::widgets::ListItem;
201    ///
202    /// let item = ListItem::new("12345\n1234567");
203    /// assert_eq!(item.width(), 7);
204    /// ```
205    pub fn width(&self) -> usize {
206        self.content.width()
207    }
208}
209
210impl<'a, T> From<T> for ListItem<'a>
211where
212    T: Into<Text<'a>>,
213{
214    fn from(value: T) -> Self {
215        Self::new(value)
216    }
217}
218
219#[cfg(test)]
220mod tests {
221    use alloc::borrow::Cow;
222    use alloc::string::{String, ToString};
223    use alloc::vec;
224
225    use pretty_assertions::assert_eq;
226    use ratatui_core::style::{Color, Modifier, Stylize};
227    use ratatui_core::text::{Line, Span};
228
229    use super::*;
230
231    #[test]
232    fn new_from_str() {
233        let item = ListItem::new("Test item");
234        assert_eq!(item.content, Text::from("Test item"));
235        assert_eq!(item.style, Style::default());
236    }
237
238    #[test]
239    fn new_from_string() {
240        let item = ListItem::new("Test item".to_string());
241        assert_eq!(item.content, Text::from("Test item"));
242        assert_eq!(item.style, Style::default());
243    }
244
245    #[test]
246    fn new_from_cow_str() {
247        let item = ListItem::new(Cow::Borrowed("Test item"));
248        assert_eq!(item.content, Text::from("Test item"));
249        assert_eq!(item.style, Style::default());
250    }
251
252    #[test]
253    fn new_from_span() {
254        let span = Span::styled("Test item", Style::default().fg(Color::Blue));
255        let item = ListItem::new(span.clone());
256        assert_eq!(item.content, Text::from(span));
257        assert_eq!(item.style, Style::default());
258    }
259
260    #[test]
261    fn new_from_spans() {
262        let spans = Line::from(vec![
263            Span::styled("Test ", Style::default().fg(Color::Blue)),
264            Span::styled("item", Style::default().fg(Color::Red)),
265        ]);
266        let item = ListItem::new(spans.clone());
267        assert_eq!(item.content, Text::from(spans));
268        assert_eq!(item.style, Style::default());
269    }
270
271    #[test]
272    fn new_from_vec_spans() {
273        let lines = vec![
274            Line::from(vec![
275                Span::styled("Test ", Style::default().fg(Color::Blue)),
276                Span::styled("item", Style::default().fg(Color::Red)),
277            ]),
278            Line::from(vec![
279                Span::styled("Second ", Style::default().fg(Color::Green)),
280                Span::styled("line", Style::default().fg(Color::Yellow)),
281            ]),
282        ];
283        let item = ListItem::new(lines.clone());
284        assert_eq!(item.content, Text::from(lines));
285        assert_eq!(item.style, Style::default());
286    }
287
288    #[test]
289    fn str_into_list_item() {
290        let s = "Test item";
291        let item: ListItem = s.into();
292        assert_eq!(item.content, Text::from(s));
293        assert_eq!(item.style, Style::default());
294    }
295
296    #[test]
297    fn string_into_list_item() {
298        let s = String::from("Test item");
299        let item: ListItem = s.clone().into();
300        assert_eq!(item.content, Text::from(s));
301        assert_eq!(item.style, Style::default());
302    }
303
304    #[test]
305    fn span_into_list_item() {
306        let s = Span::from("Test item");
307        let item: ListItem = s.clone().into();
308        assert_eq!(item.content, Text::from(s));
309        assert_eq!(item.style, Style::default());
310    }
311
312    #[test]
313    fn vec_lines_into_list_item() {
314        let lines = vec![Line::raw("l1"), Line::raw("l2")];
315        let item: ListItem = lines.clone().into();
316        assert_eq!(item.content, Text::from(lines));
317        assert_eq!(item.style, Style::default());
318    }
319
320    #[test]
321    fn style() {
322        let item = ListItem::new("Test item").style(Style::default().bg(Color::Red));
323        assert_eq!(item.content, Text::from("Test item"));
324        assert_eq!(item.style, Style::default().bg(Color::Red));
325    }
326
327    #[test]
328    fn height() {
329        let item = ListItem::new("Test item");
330        assert_eq!(item.height(), 1);
331
332        let item = ListItem::new("Test item\nSecond line");
333        assert_eq!(item.height(), 2);
334    }
335
336    #[test]
337    fn width() {
338        let item = ListItem::new("Test item");
339        assert_eq!(item.width(), 9);
340    }
341
342    #[test]
343    fn can_be_stylized() {
344        assert_eq!(
345            ListItem::new("").black().on_white().bold().not_dim().style,
346            Style::default()
347                .fg(Color::Black)
348                .bg(Color::White)
349                .add_modifier(Modifier::BOLD)
350                .remove_modifier(Modifier::DIM)
351        );
352    }
353}