bubbletea_widgets/list/
style.rs

1//! Styling system for list components.
2//!
3//! This module provides comprehensive styling options for list components, including
4//! styles for different UI elements and visual states. The styling system is built
5//! on top of lipgloss and provides sensible defaults for terminal applications.
6//!
7//! ## Style Categories
8//!
9//! - **Title and Header**: Styles for list titles and column headers
10//! - **Status and Filter**: Styles for status bar and filter prompt
11//! - **Pagination**: Styles for pagination indicators and navigation
12//! - **Visual Elements**: Styles for spinners, dots, and separators
13//!
14//! ## Constants
15//!
16//! - `BULLET`: Unicode bullet character (`•`) used in pagination
17//! - `ELLIPSIS`: Unicode ellipsis character (`…`) for truncated content
18//!
19//! ## Color Scheme
20//!
21//! The default styles use a dark theme with:
22//! - Bright colors for active/selected elements
23//! - Subdued colors for secondary information
24//! - High contrast for readability
25//!
26//! ## Adaptive Colors
27//!
28//! All default styles use `AdaptiveColor` which automatically adjusts to the
29//! terminal's light or dark theme, ensuring optimal readability and visual
30//! consistency across different environments.
31//!
32//! ## Example
33//!
34//! ```rust
35//! use bubbletea_widgets::list::style::{ListStyles, BULLET, ELLIPSIS};
36//! use lipgloss_extras::prelude::*;
37//!
38//! // Use default adaptive styles
39//! let mut styles = ListStyles::default();
40//!
41//! // Customize specific elements with adaptive colors
42//! styles.title = Style::new()
43//!     .foreground(AdaptiveColor { Light: "#1a1a1a", Dark: "#ffffff" })
44//!     .bold(true);
45//!
46//! // Use the constants for consistent symbols
47//! println!("Pagination: {}", BULLET);
48//! println!("Truncated: item1, item2{}", ELLIPSIS);
49//! ```
50
51use lipgloss_extras::prelude::*;
52
53/// Unicode bullet character (•) used in pagination indicators and visual separators.
54///
55/// This constant provides a consistent bullet symbol for pagination dots, dividers,
56/// and other list UI elements. The bullet character is automatically styled by
57/// the respective style configurations in `ListStyles`.
58///
59/// # Usage
60///
61/// The bullet is automatically used by default in:
62/// - Active and inactive pagination dots
63/// - Divider elements between UI sections
64/// - Visual separation in status displays
65///
66/// # Examples
67///
68/// ```rust
69/// use bubbletea_widgets::list::style::BULLET;
70///
71/// // Display pagination indicator
72/// println!("Page 1 {} 2 {} 3", BULLET, BULLET);
73///
74/// // Create divider text
75/// let divider = format!(" {} ", BULLET);
76/// assert_eq!(divider, " • ");
77/// ```
78pub const BULLET: &str = "•";
79
80/// Unicode ellipsis character (…) used for truncated content display.
81///
82/// This constant provides a consistent ellipsis symbol for indicating truncated
83/// text in list items, headers, and other UI elements when content exceeds the
84/// available display width.
85///
86/// # Usage
87///
88/// The ellipsis is commonly used for:
89/// - Truncating long item titles or descriptions
90/// - Indicating overflow in fixed-width displays
91/// - Showing partial content in constrained layouts
92///
93/// # Examples
94///
95/// ```rust
96/// use bubbletea_widgets::list::style::ELLIPSIS;
97///
98/// // Simulate text truncation
99/// let long_text = "This is a very long text that needs truncation";
100/// let max_width = 20;
101///
102/// let truncated = if long_text.len() > max_width {
103///     format!("{}{}", &long_text[..max_width-1], ELLIPSIS)
104/// } else {
105///     long_text.to_string()
106/// };
107///
108/// println!("{}", truncated); // "This is a very lon…"
109/// ```
110pub const ELLIPSIS: &str = "…";
111
112/// Comprehensive styling configuration for all list component UI elements.
113///
114/// This struct contains styling definitions for every visual element in a list
115/// component, from the title bar and items to pagination and help text. All
116/// styles use adaptive colors that automatically adjust to the terminal's
117/// light or dark theme.
118///
119/// # Style Categories
120///
121/// The styles are organized into logical categories:
122///
123/// ## Header and Title
124/// - `title_bar`: Container for the list title
125/// - `title`: The list title text styling
126///
127/// ## Filtering and Input
128/// - `filter_prompt`: The "Filter:" prompt text
129/// - `filter_cursor`: Cursor/caret in filter input
130/// - `default_filter_character_match`: Character-level match highlighting
131///
132/// ## Status and Information
133/// - `status_bar`: Main status bar container
134/// - `status_empty`: Status when list is empty
135/// - `status_bar_active_filter`: Active filter indicator
136/// - `status_bar_filter_count`: Filter result count
137/// - `no_items`: "No items" message
138///
139/// ## Pagination and Navigation
140/// - `pagination_style`: Pagination area container
141/// - `active_pagination_dot`: Current page indicator (•)
142/// - `inactive_pagination_dot`: Other page indicators (•)
143/// - `arabic_pagination`: Numeric pagination (1, 2, 3...)
144/// - `divider_dot`: Separator between elements ( • )
145///
146/// ## Interactive Elements
147/// - `spinner`: Loading/processing indicator
148/// - `help_style`: Help text area
149///
150/// # Adaptive Color System
151///
152/// All default styles use `AdaptiveColor` which provides different colors
153/// for light and dark terminal themes:
154/// - **Light themes**: Darker text on light backgrounds
155/// - **Dark themes**: Lighter text on dark backgrounds
156///
157/// # Examples
158///
159/// ```rust
160/// use bubbletea_widgets::list::style::ListStyles;
161/// use lipgloss_extras::prelude::*;
162///
163/// // Start with default adaptive styles
164/// let mut styles = ListStyles::default();
165///
166/// // Customize title with branded colors
167/// styles.title = Style::new()
168///     .background(Color::from("#7D56F4"))
169///     .foreground(Color::from("#FFFFFF"))
170///     .bold(true)
171///     .padding(0, 1, 0, 1);
172///
173/// // Make filter prompt more prominent
174/// styles.filter_prompt = Style::new()
175///     .foreground(AdaptiveColor { Light: "#059669", Dark: "#10B981" })
176///     .bold(true);
177///
178/// // Use subtle pagination
179/// styles.pagination_style = Style::new()
180///     .foreground(AdaptiveColor { Light: "#9CA3AF", Dark: "#6B7280" })
181///     .padding_left(2);
182/// ```
183///
184/// # Integration
185///
186/// This struct is typically used with `Model` to configure the entire
187/// list appearance:
188///
189/// ```rust
190/// use bubbletea_widgets::list::{Model, DefaultItem, DefaultDelegate, style::ListStyles};
191///
192/// let items = vec![DefaultItem::new("Item 1", "Description 1")];
193/// let delegate = DefaultDelegate::new();
194/// let list: Model<DefaultItem> = Model::new(items, delegate, 80, 24);
195///
196/// // Custom styles can be created and configured
197/// let mut custom_styles = ListStyles::default();
198/// custom_styles.title = custom_styles.title.bold(true);
199/// // Note: Styles would be applied through constructor or builder pattern
200/// ```
201#[derive(Debug, Clone)]
202pub struct ListStyles {
203    /// Style for the title bar container.
204    pub title_bar: Style,
205    /// Style for the list title text.
206    pub title: Style,
207    /// Style for spinner glyphs.
208    pub spinner: Style,
209    /// Style for the filter prompt label.
210    pub filter_prompt: Style,
211    /// Style for the filter cursor/caret.
212    pub filter_cursor: Style,
213    /// Style for default filter character highlight.
214    pub default_filter_character_match: Style,
215    /// Style for the status bar container.
216    pub status_bar: Style,
217    /// Style for the status bar when the list is empty.
218    pub status_empty: Style,
219    /// Style for active filter text in the status bar.
220    pub status_bar_active_filter: Style,
221    /// Style for filter match count in the status bar.
222    pub status_bar_filter_count: Style,
223    /// Style for the "No items" message.
224    pub no_items: Style,
225    /// Style for pagination area.
226    pub pagination_style: Style,
227    /// Style for help text area.
228    pub help_style: Style,
229    /// Style for the active pagination dot.
230    pub active_pagination_dot: Style,
231    /// Style for the inactive pagination dot.
232    pub inactive_pagination_dot: Style,
233    /// Style for arabic numerals in pagination.
234    pub arabic_pagination: Style,
235    /// Style for the divider dot between elements.
236    pub divider_dot: Style,
237}
238
239impl Default for ListStyles {
240    /// Creates default list styles matching the Go bubbles library appearance.
241    ///
242    /// The default styles provide a professional, accessible appearance with:
243    /// - **Adaptive colors** that automatically adjust to terminal themes
244    /// - **Consistent typography** with proper spacing and alignment
245    /// - **High contrast** for accessibility and readability
246    /// - **Visual hierarchy** with appropriate emphasis and subdued elements
247    ///
248    /// # Color Palette
249    ///
250    /// The default styles use a carefully chosen adaptive color palette:
251    ///
252    /// ## Primary Colors
253    /// - **Title**: Fixed colors (background: #3E (purple), text: #E6 (light))
254    /// - **Active elements**: Bright colors for focus and selection
255    ///
256    /// ## Adaptive Colors
257    /// - **Normal text**: Light: dark text, Dark: light text
258    /// - **Subdued elements**: Light: medium gray, Dark: dark gray
259    /// - **Interactive elements**: Green/yellow filter prompts, purple accents
260    ///
261    /// ## Accessibility
262    /// - All color combinations meet WCAG contrast requirements
263    /// - Colors are distinguishable for common color vision differences
264    /// - Text remains readable in both light and dark terminal themes
265    ///
266    /// # Visual Design
267    ///
268    /// The default layout follows these principles:
269    /// - **Padding**: Consistent 2-unit left padding for alignment
270    /// - **Spacing**: 1-line spacing for readability
271    /// - **Borders**: Minimal, used only for emphasis
272    /// - **Typography**: No decorative fonts, clear hierarchy
273    ///
274    /// # Examples
275    ///
276    /// ```rust
277    /// use bubbletea_widgets::list::style::ListStyles;
278    ///
279    /// // Get default styles
280    /// let styles = ListStyles::default();
281    ///
282    /// // The styles will automatically adapt to your terminal theme
283    /// // In light terminals: dark text on light backgrounds
284    /// // In dark terminals: light text on dark backgrounds
285    /// ```
286    ///
287    /// # Compatibility
288    ///
289    /// These defaults are designed to match the visual appearance of the
290    /// original Go charmbracelet/bubbles library, ensuring consistency
291    /// across different implementations.
292    fn default() -> Self {
293        // Adaptive colors matching Go version
294        let very_subdued_color = AdaptiveColor {
295            Light: "#DDDADA",
296            Dark: "#3C3C3C",
297        };
298        let subdued_color = AdaptiveColor {
299            Light: "#9B9B9B",
300            Dark: "#5C5C5C",
301        };
302
303        Self {
304            title_bar: Style::new().padding(0, 0, 1, 1), // Reduce left padding to align title properly
305            title: Style::new()
306                .background(Color::from("62"))
307                .foreground(Color::from("230"))
308                .padding(0, 1, 1, 2), // Keep minimal padding for clean purple background
309            spinner: Style::new().foreground(AdaptiveColor {
310                Light: "#8E8E8E",
311                Dark: "#747373",
312            }),
313            filter_prompt: Style::new().foreground(AdaptiveColor {
314                Light: "#04B575", // Match Go version exactly
315                Dark: "#ECFD65",  // Match Go version exactly
316            }),
317            filter_cursor: Style::new().foreground(AdaptiveColor {
318                Light: "#EE6FF8",
319                Dark: "#EE6FF8",
320            }),
321            default_filter_character_match: Style::new().underline(true),
322            status_bar: Style::new()
323                .foreground(AdaptiveColor {
324                    Light: "#A49FA5",
325                    Dark: "#777777",
326                })
327                .padding(0, 0, 1, 2), // Match Go version exactly
328            status_empty: Style::new().foreground(subdued_color.clone()),
329            status_bar_active_filter: Style::new().foreground(AdaptiveColor {
330                Light: "#1a1a1a",
331                Dark: "#dddddd",
332            }),
333            status_bar_filter_count: Style::new().foreground(very_subdued_color.clone()),
334            no_items: Style::new().foreground(AdaptiveColor {
335                Light: "#909090",
336                Dark: "#626262",
337            }),
338            arabic_pagination: Style::new().foreground(subdued_color.clone()),
339            pagination_style: Style::new().padding(1, 0, 0, 2), // Add top padding to separate items from pagination
340            help_style: Style::new().padding(1, 0, 0, 2), // Match Go version exactly (2 lines high)
341            active_pagination_dot: Style::new()
342                .foreground(AdaptiveColor {
343                    Light: "#847A85",
344                    Dark: "#979797",
345                })
346                .set_string(BULLET),
347            inactive_pagination_dot: Style::new()
348                .foreground(very_subdued_color.clone())
349                .set_string(BULLET),
350            divider_dot: Style::new()
351                .foreground(very_subdued_color)
352                .set_string(&format!(" {} ", BULLET)),
353        }
354    }
355}