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}