Skip to main content

cli_ui/
styles.rs

1//! ANSI style constants and theme support.
2//!
3//! All styles are zero-allocation [`anstyle::Style`] constants.
4//! [`paint`] applies a style to a `&str` and returns an owned `String`.
5
6use anstyle::{AnsiColor, Color, Style};
7
8// ── base styles ───────────────────────────────────────────────────────────────
9
10/// Bright magenta bold — default accent color.
11pub const ACCENT: Style = Style::new()
12    .fg_color(Some(Color::Ansi(AnsiColor::BrightMagenta)))
13    .bold();
14
15/// Bright black — dimmed / secondary text.
16pub const DIM: Style = Style::new().fg_color(Some(Color::Ansi(AnsiColor::BrightBlack)));
17
18/// Bold white.
19pub const BOLD: Style = Style::new().bold();
20
21/// Green — success indicators.
22pub const OK: Style = Style::new().fg_color(Some(Color::Ansi(AnsiColor::Green)));
23
24/// Bright red bold — error indicators.
25pub const ERR: Style = Style::new()
26    .fg_color(Some(Color::Ansi(AnsiColor::BrightRed)))
27    .bold();
28
29/// Cyan — paths and highlights.
30pub const CYAN: Style = Style::new().fg_color(Some(Color::Ansi(AnsiColor::Cyan)));
31
32/// Yellow — sizes and warnings.
33pub const YELLOW: Style = Style::new().fg_color(Some(Color::Ansi(AnsiColor::Yellow)));
34
35/// Bright white — section headers.
36pub const WHITE: Style = Style::new().fg_color(Some(Color::Ansi(AnsiColor::BrightWhite)));
37
38/// Bright white + bold — prompt question text.
39pub const WHITE_BOLD: Style = Style::new()
40    .fg_color(Some(Color::Ansi(AnsiColor::BrightWhite)))
41    .bold();
42
43// ── badge styles (bg + white fg + bold) ──────────────────────────────────────
44
45/// Cyan background badge — default theme.
46pub const BADGE_CYAN: Style = Style::new()
47    .bg_color(Some(Color::Ansi(AnsiColor::Cyan)))
48    .fg_color(Some(Color::Ansi(AnsiColor::BrightWhite)))
49    .bold();
50
51/// Cyan background, black text, bold — the clack-style intro pill.
52pub const BADGE_INTRO: Style = Style::new()
53    .bg_color(Some(Color::Ansi(AnsiColor::Cyan)))
54    .fg_color(Some(Color::Ansi(AnsiColor::Black)))
55    .bold();
56
57/// Green background badge.
58pub const BADGE_GREEN: Style = Style::new()
59    .bg_color(Some(Color::Ansi(AnsiColor::Green)))
60    .fg_color(Some(Color::Ansi(AnsiColor::BrightWhite)))
61    .bold();
62
63/// Yellow background badge (dark text for contrast).
64pub const BADGE_YELLOW: Style = Style::new()
65    .bg_color(Some(Color::Ansi(AnsiColor::Yellow)))
66    .fg_color(Some(Color::Ansi(AnsiColor::Black)))
67    .bold();
68
69// ── symbols ───────────────────────────────────────────────────────────────────
70
71/// Section diamond `◆`
72pub const DIAMOND: &str = "◆";
73/// Error cross `✘`
74pub const CROSS: &str = "✘";
75/// Vertical bar `│`
76pub const BAR: &str = "│";
77/// Hint arrow `➜`
78pub const ARROW: &str = "➜";
79/// Success check `✓`
80pub const CHECK: &str = "✓";
81/// Step bullet `▸`
82pub const BULLET: &str = "▸";
83/// Phase triangle `▶`
84pub const PHASE: &str = "▶";
85/// Sub-step branch `└─`
86pub const BRANCH: &str = "└─";
87
88// ── theme ─────────────────────────────────────────────────────────────────────
89
90/// Returns `(badge_style, accent_style)` for the given theme name.
91///
92/// Available themes: `"cyan"` (default), `"magenta"`, `"green"`,
93/// `"blue"`, `"yellow"`, `"red"`.
94///
95/// Used by the `#[cli(theme = "...")]` attribute — called from generated code.
96///
97/// # Example
98/// ```rust
99/// let (badge, accent) = cli_ui::styles::theme_styles("green");
100/// let painted = cli_ui::styles::paint(badge, " mytool ");
101/// ```
102pub fn theme_styles(theme: &str) -> (Style, Style) {
103    match theme {
104        "magenta" | "purple" => (
105            Style::new()
106                .bg_color(Some(Color::Ansi(AnsiColor::Magenta)))
107                .fg_color(Some(Color::Ansi(AnsiColor::BrightWhite)))
108                .bold(),
109            Style::new()
110                .fg_color(Some(Color::Ansi(AnsiColor::BrightMagenta)))
111                .bold(),
112        ),
113        "green" => (
114            Style::new()
115                .bg_color(Some(Color::Ansi(AnsiColor::Green)))
116                .fg_color(Some(Color::Ansi(AnsiColor::BrightWhite)))
117                .bold(),
118            Style::new()
119                .fg_color(Some(Color::Ansi(AnsiColor::Green)))
120                .bold(),
121        ),
122        "blue" => (
123            Style::new()
124                .bg_color(Some(Color::Ansi(AnsiColor::Blue)))
125                .fg_color(Some(Color::Ansi(AnsiColor::BrightWhite)))
126                .bold(),
127            Style::new()
128                .fg_color(Some(Color::Ansi(AnsiColor::BrightBlue)))
129                .bold(),
130        ),
131        "yellow" => (
132            Style::new()
133                .bg_color(Some(Color::Ansi(AnsiColor::Yellow)))
134                .fg_color(Some(Color::Ansi(AnsiColor::Black)))
135                .bold(),
136            Style::new()
137                .fg_color(Some(Color::Ansi(AnsiColor::Yellow)))
138                .bold(),
139        ),
140        "red" => (
141            Style::new()
142                .bg_color(Some(Color::Ansi(AnsiColor::Red)))
143                .fg_color(Some(Color::Ansi(AnsiColor::BrightWhite)))
144                .bold(),
145            Style::new()
146                .fg_color(Some(Color::Ansi(AnsiColor::BrightRed)))
147                .bold(),
148        ),
149        _ =>
150        /* cyan default */
151        {
152            (
153                Style::new()
154                    .bg_color(Some(Color::Ansi(AnsiColor::Cyan)))
155                    .fg_color(Some(Color::Ansi(AnsiColor::BrightWhite)))
156                    .bold(),
157                Style::new()
158                    .fg_color(Some(Color::Ansi(AnsiColor::Cyan)))
159                    .bold(),
160            )
161        }
162    }
163}
164
165// ── paint ─────────────────────────────────────────────────────────────────────
166
167/// Apply an [`anstyle::Style`] to `s` and return an owned `String`.
168///
169/// Respects `NO_COLOR` and pipe detection via `anstream`.
170///
171/// # Example
172/// ```rust
173/// use cli_ui::styles::{paint, OK, CHECK};
174/// let line = format!("{}  done", paint(OK, CHECK));
175/// ```
176pub fn paint(style: Style, s: &str) -> String {
177    format!("{style}{s}{style:#}")
178}