bubbletea_widgets/
lib.rs

1#![warn(missing_docs)]
2#![doc(html_root_url = "https://docs.rs/bubbletea-widgets/")]
3
4//! # bubbletea-widgets
5//!
6//! A Rust port of the Go library [charmbracelet/bubbles](https://github.com/charmbracelet/bubbles),
7//! providing reusable TUI components for building terminal applications with [bubbletea-rs](https://github.com/joshka/bubbletea-rs).
8//!
9//! [![Crates.io](https://img.shields.io/crates/v/bubbletea-widgets.svg)](https://crates.io/crates/bubbletea-widgets)
10//! [![Documentation](https://docs.rs/bubbletea-widgets/badge.svg)](https://docs.rs/bubbletea-widgets)
11//! [![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://opensource.org/licenses/MIT)
12//!
13//! ## Overview
14//!
15//! bubbletea-widgets offers a collection of common terminal UI components that can be easily integrated
16//! into bubbletea-rs applications. Each component follows the Elm Architecture pattern with
17//! `init()`, `update()`, and `view()` methods, providing a consistent and predictable API
18//! for building complex terminal user interfaces.
19//!
20//! ## Features
21//!
22//! - **Type-safe key bindings** with comprehensive key combination support
23//! - **Focus management** system for keyboard navigation between components
24//! - **Responsive design** with automatic width/height handling
25//! - **Theming support** through customizable styles
26//! - **Go compatibility** for easy migration from charmbracelet/bubbles
27//! - **Performance optimized** with efficient rendering and state management
28//!
29//! ## Components
30//!
31//! - **Input Components**: `TextInput`, `TextArea`, `FilePicker`
32//! - **Display Components**: `List`, `Table`, `Progress`, `Spinner`, `Help`
33//! - **Utility Components**: `Cursor`, `Viewport`, `Paginator`, `Timer`, `Stopwatch`
34//!
35//! ## Focus Management
36//!
37//! All components implement the `Component` trait which provides standardized focus management:
38//!
39//! ```rust
40//! use bubbletea_widgets::prelude::*;
41//! use bubbletea_rs::Cmd;
42//!
43//! fn handle_focus<T: Component>(component: &mut T) {
44//!     let _cmd: Option<Cmd> = component.focus();
45//!     assert!(component.focused());
46//!     component.blur();
47//!     assert!(!component.focused());
48//! }
49//!
50//! // Example with a text area (implements Component trait)
51//! let mut textarea = textarea_new();
52//! handle_focus(&mut textarea);
53//! ```
54//!
55//! ## Key Bindings
56//!
57//! Components use the type-safe key binding system from the `key` module:
58//!
59//! ```rust
60//! use bubbletea_widgets::key::{Binding, KeyMap};
61//! use crossterm::event::{KeyCode, KeyModifiers};
62//!
63//! // Create key bindings
64//! let confirm = Binding::new(vec![KeyCode::Enter])
65//!     .with_help("enter", "Confirm selection");
66//!
67//! let save = Binding::new(vec![(KeyCode::Char('s'), KeyModifiers::CONTROL)])
68//!     .with_help("ctrl+s", "Save file");
69//!
70//! // Implement KeyMap for your component
71//! struct MyKeyMap {
72//!     confirm: Binding,
73//!     save: Binding,
74//! }
75//!
76//! impl KeyMap for MyKeyMap {
77//!     fn short_help(&self) -> Vec<&Binding> {
78//!         vec![&self.confirm, &self.save]
79//!     }
80//!
81//!     fn full_help(&self) -> Vec<Vec<&Binding>> {
82//!         vec![
83//!             vec![&self.confirm],
84//!             vec![&self.save],
85//!         ]
86//!     }
87//! }
88//! ```
89//!
90//! ## Integration with bubbletea-rs
91//!
92//! Components are designed to work seamlessly with bubbletea-rs models:
93//!
94//! ```rust
95//! use bubbletea_widgets::prelude::*;
96//! use bubbletea_rs::{Model, Cmd, Msg};
97//!
98//! struct App {
99//!     input: TextInput,
100//! }
101//!
102//! impl Model for App {
103//!     fn init() -> (Self, Option<Cmd>) {
104//!         let mut input = textinput_new();
105//!         let focus_cmd = input.focus();
106//!         (Self { input }, Some(focus_cmd))
107//!     }
108//!
109//!     fn update(&mut self, msg: Msg) -> Option<Cmd> {
110//!         // Handle component updates
111//!         if let Some(cmd) = self.input.update(msg) {
112//!             return Some(cmd);
113//!         }
114//!
115//!         // Handle other messages
116//!         match msg {
117//!             // Your app-specific message handling
118//!             _ => None,
119//!         }
120//!     }
121//!
122//!     fn view(&self) -> String {
123//!         format!("Enter text: {}\n{}", self.input.view(), "Press Ctrl+C to quit")
124//!     }
125//! }
126//! ```
127//!
128//! ## Quick Start
129//!
130//! Add bubbletea-widgets to your `Cargo.toml`:
131//!
132//! ```toml
133//! [dependencies]
134//! bubbletea-widgets = "0.1.0"
135//! bubbletea-rs = "0.0.7"
136//! crossterm = "0.27"
137//! ```
138//!
139//! For convenience, you can import the prelude:
140//!
141//! ```rust
142//! use bubbletea_widgets::prelude::*;
143//! ```
144//!
145//! ## Component Overview
146//!
147//! | Component | Description | Use Case |
148//! |-----------|-------------|----------|
149//! | `TextInput` | Single-line text input | Forms, search boxes |
150//! | `TextArea` | Multi-line text editor | Code editing, long text |
151//! | `List` | Scrollable item list | Menus, file browsers |
152//! | `Table` | Tabular data display | Data tables, spreadsheets |
153//! | `Progress` | Progress bar with animation | Loading indicators |
154//! | `Spinner` | Animated loading spinner | Background operations |
155//! | `Help` | Key binding help display | User guidance |
156//! | `FilePicker` | File system navigator | File selection |
157//! | `Timer` | Countdown timer | Time-based operations |
158//! | `Stopwatch` | Elapsed time tracker | Performance monitoring |
159
160pub mod cursor;
161pub mod filepicker;
162pub mod help;
163pub mod key;
164pub mod list;
165pub mod paginator;
166pub mod progress;
167pub mod spinner;
168pub mod stopwatch;
169pub mod table;
170pub mod textarea;
171pub mod textinput;
172pub mod timer;
173pub mod viewport;
174
175use bubbletea_rs::Cmd;
176
177/// Core trait for components that support focus management.
178///
179/// This trait provides a standardized interface for managing keyboard focus
180/// across all bubbletea-widgets components. Components that implement this trait can
181/// participate in focus management systems and provide consistent behavior
182/// for keyboard navigation.
183///
184/// ## Focus States
185///
186/// - **Focused**: The component can receive keyboard input and should visually
187///   indicate its active state
188/// - **Blurred**: The component cannot receive keyboard input and should
189///   display in an inactive state
190///
191/// ## Implementation Guidelines
192///
193/// When implementing this trait:
194/// - `focus()` should set the component's focused state and may return a command
195///   for initialization (e.g., starting a cursor blink timer)
196/// - `blur()` should unset the focused state and clean up any focus-related state
197/// - `focused()` should return the current focus state consistently
198///
199/// ## Examples
200///
201/// ### Basic Usage
202///
203/// ```rust
204/// use bubbletea_widgets::prelude::*;
205///
206/// let mut input = textinput_new();
207/// assert!(!input.focused());
208///
209/// input.focus();
210/// assert!(input.focused());
211///
212/// input.blur();
213/// assert!(!input.focused());
214/// ```
215///
216/// ### Focus Management in Applications
217///
218/// ```rust
219/// use bubbletea_widgets::prelude::*;
220/// use bubbletea_rs::Cmd;
221///
222/// struct App {
223///     input: TextInput,
224///     textarea: TextArea,
225///     focused_component: usize,
226/// }
227///
228/// impl App {
229///     fn focus_next(&mut self) -> Option<Cmd> {
230///         // Blur current component
231///         match self.focused_component {
232///             0 => self.input.blur(),
233///             1 => self.textarea.blur(),
234///             _ => {}
235///         }
236///
237///         // Focus next component
238///         self.focused_component = (self.focused_component + 1) % 2;
239///         match self.focused_component {
240///             0 => Some(self.input.focus()),
241///             1 => self.textarea.focus(),
242///             _ => None,
243///         }
244///     }
245/// }
246/// ```
247pub trait Component {
248    /// Sets the component to focused state.
249    ///
250    /// This method should update the component's internal state to indicate
251    /// that it can receive keyboard input. It may return a command for
252    /// initialization tasks like starting timers or triggering redraws.
253    ///
254    /// # Returns
255    ///
256    /// An optional command to be executed by the bubbletea runtime. Common
257    /// use cases include starting cursor blink timers or triggering immediate
258    /// redraws to show the focus state change.
259    ///
260    /// # Examples
261    ///
262    /// ```rust
263    /// use bubbletea_widgets::prelude::*;
264    ///
265    /// let mut input = textinput_new();
266    /// let cmd = input.focus();
267    /// assert!(input.focused());
268    /// // cmd may contain a cursor blink timer start command
269    /// ```
270    fn focus(&mut self) -> Option<Cmd>;
271
272    /// Sets the component to blurred (unfocused) state.
273    ///
274    /// This method should update the component's internal state to indicate
275    /// that it cannot receive keyboard input. It should clean up any
276    /// focus-related resources like stopping timers.
277    ///
278    /// # Examples
279    ///
280    /// ```rust
281    /// use bubbletea_widgets::prelude::*;
282    ///
283    /// let mut input = textinput_new();
284    /// input.focus();
285    /// assert!(input.focused());
286    ///
287    /// input.blur();
288    /// assert!(!input.focused());
289    /// ```
290    fn blur(&mut self);
291
292    /// Returns the current focus state of the component.
293    ///
294    /// # Returns
295    ///
296    /// `true` if the component is currently focused and can receive keyboard
297    /// input, `false` otherwise.
298    ///
299    /// # Examples
300    ///
301    /// ```rust
302    /// use bubbletea_widgets::prelude::*;
303    ///
304    /// let mut input = textinput_new();
305    /// assert!(!input.focused()); // Initially unfocused
306    ///
307    /// input.focus();
308    /// assert!(input.focused()); // Now focused
309    /// ```
310    fn focused(&self) -> bool;
311}
312
313pub use cursor::Model as Cursor;
314pub use filepicker::Model as FilePicker;
315pub use help::Model as HelpModel;
316pub use key::{
317    matches, matches_binding, new_binding, with_disabled, with_help, with_keys, Binding,
318    Help as KeyHelp, KeyMap, KeyPress,
319};
320pub use list::Model as List;
321pub use list::{
322    DefaultDelegate as ListDefaultDelegate, DefaultItem as ListDefaultItem,
323    DefaultItemStyles as ListDefaultItemStyles, FilterState, FilterStateInfo, ListKeyMap,
324    ListStyles,
325};
326pub use paginator::Model as Paginator;
327pub use progress::Model as Progress;
328pub use spinner::{
329    new as spinner_new, with_spinner, with_style, Model as Spinner, SpinnerOption,
330    TickMsg as SpinnerTickMsg, DOT, ELLIPSIS, GLOBE, HAMBURGER, JUMP, LINE, METER, MINI_DOT,
331    MONKEY, MOON, POINTS, PULSE,
332};
333pub use stopwatch::Model as Stopwatch;
334pub use table::Model as Table;
335pub use textarea::{
336    default_styles as textarea_default_styles, new as textarea_new, LineInfo, Model as TextArea,
337    PasteErrMsg as TextAreaPasteErrMsg, PasteMsg as TextAreaPasteMsg,
338};
339pub use textinput::{
340    blink, default_key_map as textinput_default_key_map, new as textinput_new, paste, EchoMode,
341    KeyMap as TextInputKeyMap, Model as TextInput, PasteErrMsg, PasteMsg, ValidateFunc,
342};
343pub use timer::{
344    new as timer_new, new_with_interval as timer_new_with_interval, Model as Timer,
345    StartStopMsg as TimerStartStopMsg, TickMsg as TimerTickMsg, TimeoutMsg as TimerTimeoutMsg,
346};
347pub use viewport::Model as Viewport;
348
349/// Prelude module for convenient imports.
350///
351/// This module re-exports the most commonly used types and functions from
352/// bubbletea-widgets, allowing users to import everything they need with a single
353/// `use` statement.
354///
355/// # Usage
356///
357/// Instead of importing each component individually:
358///
359/// ```rust
360/// use bubbletea_widgets::{TextInput, List, Spinner, Component};
361/// use bubbletea_widgets::key::{Binding, KeyMap};
362/// ```
363///
364/// You can use the prelude:
365///
366/// ```rust
367/// use bubbletea_widgets::prelude::*;
368/// ```
369///
370/// # What's Included
371///
372/// The prelude includes:
373/// - All component types (`TextInput`, `List`, `Table`, etc.)
374/// - The `Component` trait for focus management
375/// - Key binding types and functions (`Binding`, `KeyMap`, etc.)
376/// - Utility types and commonly used enums
377/// - Constructor functions for components
378///
379/// # Examples
380///
381/// ```rust
382/// use bubbletea_widgets::prelude::*;
383/// use bubbletea_rs::{Model, Cmd, Msg};
384///
385/// struct App {
386///     input: TextInput,
387///     list: List<ListDefaultItem>,
388/// }
389///
390/// impl Model for App {
391///     fn init() -> (Self, Option<Cmd>) {
392///         let mut input = textinput_new();
393///         let focus_cmd = input.focus();
394///         
395///         let delegate = ListDefaultDelegate::new();
396///         let items = vec![
397///             ListDefaultItem::new("Item 1", "First item"),
398///             ListDefaultItem::new("Item 2", "Second item"),
399///         ];
400///         let list = List::new(items, delegate, 80, 10);
401///         
402///         (Self { input, list }, Some(focus_cmd))
403///     }
404///
405///     fn update(&mut self, msg: Msg) -> Option<Cmd> {
406///         // Handle updates...
407///         None
408///     }
409///
410///     fn view(&self) -> String {
411///         format!("{}\\n{}", self.input.view(), self.list.view())
412///     }
413/// }
414/// ```
415pub mod prelude {
416    pub use crate::cursor::Model as Cursor;
417    pub use crate::help::Model as HelpModel;
418    pub use crate::key::{
419        matches, matches_binding, new_binding, with_disabled, with_help, with_keys, Binding,
420        Help as KeyHelp, KeyMap, KeyPress,
421    };
422    pub use crate::list::Model as List;
423    pub use crate::list::{
424        DefaultDelegate as ListDefaultDelegate, DefaultItem as ListDefaultItem,
425        DefaultItemStyles as ListDefaultItemStyles, FilterState, FilterStateInfo, ListKeyMap,
426        ListStyles,
427    };
428    pub use crate::paginator::Model as Paginator;
429    pub use crate::progress::Model as Progress;
430    pub use crate::spinner::{
431        new as spinner_new, with_spinner, with_style, Model as Spinner, SpinnerOption,
432        TickMsg as SpinnerTickMsg, DOT, ELLIPSIS, GLOBE, HAMBURGER, JUMP, LINE, METER, MINI_DOT,
433        MONKEY, MOON, POINTS, PULSE,
434    };
435    pub use crate::table::Model as Table;
436    pub use crate::textarea::{
437        default_styles as textarea_default_styles, new as textarea_new, LineInfo,
438        Model as TextArea, PasteErrMsg as TextAreaPasteErrMsg, PasteMsg as TextAreaPasteMsg,
439    };
440    pub use crate::textinput::{
441        blink, default_key_map as textinput_default_key_map, new as textinput_new, paste, EchoMode,
442        KeyMap as TextInputKeyMap, Model as TextInput, PasteErrMsg, PasteMsg, ValidateFunc,
443    };
444    pub use crate::timer::{
445        new as timer_new, new_with_interval as timer_new_with_interval, Model as Timer,
446        StartStopMsg as TimerStartStopMsg, TickMsg as TimerTickMsg, TimeoutMsg as TimerTimeoutMsg,
447    };
448    pub use crate::viewport::Model as Viewport;
449    pub use crate::Component;
450}