ratatui/widgets/list/
item.rs

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