Radish alpha
r
Radicle terminal user interface
Radicle
Git (anonymous pull)
Log in to clone via SSH
lib: Use themed style builders
Erik Kundt committed 2 years ago
commit b5d8453d013f4b81934da88d649ea3685f113dc2
parent 618ed3e679a628614a9099cdffc6dd730afcc9c4
14 files changed +262 -336
modified bin/commands/issue/suite.rs
@@ -18,7 +18,7 @@ use radicle_tui as tui;
use tui::cob;
use tui::context::Context;
use tui::ui::subscription;
-
use tui::ui::theme::{self, Theme};
+
use tui::ui::theme::Theme;
use tui::{Exit, PageStack, Tui};

use page::{IssuePage, ListPage};
@@ -110,7 +110,7 @@ impl App {
        Self {
            context,
            pages: PageStack::default(),
-
            theme: theme::default_dark(),
+
            theme: Theme::default(),
            quit: false,
        }
    }
@@ -160,7 +160,7 @@ impl App {
        app: &mut Application<Cid, Message, NoUserEvent>,
        message: Message,
    ) -> Result<Option<Message>> {
-
        let theme = theme::default_dark();
+
        let theme = Theme::default();
        match message {
            Message::Batch(messages) => {
                let mut results = vec![];
modified bin/commands/issue/suite/ui.rs
@@ -6,6 +6,7 @@ use radicle::cob::thread::CommentId;
use radicle::cob::issue::Issue;
use radicle::cob::issue::IssueId;

+
use tui::ui::theme::style;
use tuirealm::command::{Cmd, CmdResult};
use tuirealm::tui::layout::{Constraint, Direction, Layout, Rect};
use tuirealm::{AttrValue, Attribute, Frame, MockComponent, Props, State};
@@ -72,7 +73,7 @@ impl IssueBrowser {
        };

        let table = Widget::new(Table::new(&items, selected, header, widths, theme.clone()))
-
            .highlight(theme.colors.item_list_highlighted_bg);
+
            .highlight(style::highlight().fg.unwrap());

        Self { items, table }
    }
@@ -123,7 +124,7 @@ impl LargeList {
            selected.map(|(id, issue)| IssueItem::from((context.profile(), repo, id, issue)));

        let list = Widget::new(List::new(&items, selected, theme.clone()))
-
            .highlight(theme.colors.item_list_highlighted_bg);
+
            .highlight(style::highlight().fg.unwrap());

        let container = tui::ui::container(theme, list.to_boxed());

@@ -173,35 +174,35 @@ impl IssueHeader {
        let item = IssueItem::from((context.profile(), repo, id, issue.clone()));

        let title = Property::new(
-
            tui::ui::label("Title").foreground(theme.colors.property_name_fg),
-
            tui::ui::label(item.title()).foreground(theme.colors.browser_list_title),
+
            tui::ui::label("Title").foreground(style::cyan().fg.unwrap()),
+
            tui::ui::label(item.title()).foreground(style::reset().fg.unwrap()),
        );

-
        let author = match alias {
-
            Some(_) => tui::ui::label(&cob::format_author(issue.author().id(), &alias, by_you))
-
                .foreground(theme.colors.browser_list_author),
-
            None => tui::ui::label(&cob::format_author(issue.author().id(), &alias, by_you))
-
                .foreground(theme.colors.browser_list_author)
-
                .dim(),
+
        let author_style = match alias {
+
            Some(_) => style::magenta(),
+
            None => style::magenta_dim(),
        };
+

+
        let author = tui::ui::label(&cob::format_author(issue.author().id(), &alias, by_you))
+
            .foreground(author_style.fg.unwrap());
        let author = Property::new(
-
            tui::ui::label("Author").foreground(theme.colors.property_name_fg),
+
            tui::ui::label("Author").foreground(style::cyan().fg.unwrap()),
            author,
        );

        let issue_id = Property::new(
-
            tui::ui::label("Issue").foreground(theme.colors.property_name_fg),
-
            tui::ui::label(&id.to_string()).foreground(theme.colors.browser_list_description),
+
            tui::ui::label("Issue").foreground(style::cyan().fg.unwrap()),
+
            tui::ui::label(&id.to_string()).foreground(style::gray().fg.unwrap()),
        );

        let labels = Property::new(
-
            tui::ui::label("Labels").foreground(theme.colors.property_name_fg),
+
            tui::ui::label("Labels").foreground(style::cyan().fg.unwrap()),
            tui::ui::label(&cob::format_labels(item.labels()))
-
                .foreground(theme.colors.browser_list_labels),
+
                .foreground(style::lightblue().fg.unwrap()),
        );

        let assignees = Property::new(
-
            tui::ui::label("Assignees").foreground(theme.colors.property_name_fg),
+
            tui::ui::label("Assignees").foreground(style::cyan().fg.unwrap()),
            tui::ui::label(&cob::format_assignees(
                &item
                    .assignees()
@@ -209,12 +210,12 @@ impl IssueHeader {
                    .map(|item| (item.did(), item.alias(), item.is_you()))
                    .collect::<Vec<_>>(),
            ))
-
            .foreground(theme.colors.browser_list_author),
+
            .foreground(author_style.fg.unwrap()),
        );

        let state = Property::new(
-
            tui::ui::label("Status").foreground(theme.colors.property_name_fg),
-
            tui::ui::label(&item.state().to_string()).foreground(theme.colors.browser_list_title),
+
            tui::ui::label("Status").foreground(style::cyan().fg.unwrap()),
+
            tui::ui::label(&item.state().to_string()).foreground(style::reset().fg.unwrap()),
        );

        let table = tui::ui::property_table(
@@ -303,9 +304,9 @@ impl CommentBody {
            Some((_, comment)) => comment.body().to_string(),
            None => String::new(),
        };
-
        let textarea = Widget::new(Textarea::new(theme.clone()))
+
        let textarea = Widget::new(Textarea::default())
            .content(AttrValue::String(content))
-
            .foreground(theme.colors.default_fg);
+
            .foreground(style::reset().fg.unwrap());

        let textarea = tui::ui::container(theme, textarea.to_boxed());

@@ -335,7 +336,7 @@ impl WidgetComponent for CommentBody {
pub fn list_navigation(theme: &Theme) -> Widget<Tabs> {
    tui::ui::tabs(
        theme,
-
        vec![tui::ui::reversable_label("Issues").foreground(theme.colors.tabs_highlighted_fg)],
+
        vec![tui::ui::reversable_label("Issues").foreground(style::magenta().fg.unwrap())],
    )
}

modified bin/commands/patch/list/ui.rs
@@ -8,7 +8,7 @@ use radicle_tui as tui;

use tui::context::Context;
use tui::ui::cob::PatchItem;
-
use tui::ui::theme::Theme;
+
use tui::ui::theme::{style, Theme};
use tui::ui::widget::{Widget, WidgetComponent};

use tui::ui::widget::container::Tabs;
@@ -23,14 +23,14 @@ pub struct PatchBrowser {
impl PatchBrowser {
    pub fn new(context: &Context, theme: &Theme, selected: Option<(PatchId, Patch)>) -> Self {
        let header = [
-
            tui::ui::label(" ● "),
-
            tui::ui::label("ID"),
-
            tui::ui::label("Title"),
-
            tui::ui::label("Author"),
-
            tui::ui::label("Head"),
-
            tui::ui::label("+"),
-
            tui::ui::label("-"),
-
            tui::ui::label("Updated"),
+
            tui::ui::label(" ● ").foreground(style::reset_dim().fg.unwrap()),
+
            tui::ui::label("ID").foreground(style::reset_dim().fg.unwrap()),
+
            tui::ui::label("Title").foreground(style::reset_dim().fg.unwrap()),
+
            tui::ui::label("Author").foreground(style::reset_dim().fg.unwrap()),
+
            tui::ui::label("Head").foreground(style::reset_dim().fg.unwrap()),
+
            tui::ui::label("+").foreground(style::reset_dim().fg.unwrap()),
+
            tui::ui::label("-").foreground(style::reset_dim().fg.unwrap()),
+
            tui::ui::label("Updated").foreground(style::reset_dim().fg.unwrap()),
        ];

        let widths = [
@@ -65,7 +65,7 @@ impl PatchBrowser {
        };

        let table = Widget::new(Table::new(&items, selected, header, widths, theme.clone()))
-
            .highlight(theme.colors.item_list_highlighted_bg);
+
            .highlight(style::highlight().fg.unwrap());

        Self { items, table }
    }
@@ -97,7 +97,7 @@ impl WidgetComponent for PatchBrowser {
pub fn list_navigation(theme: &Theme) -> Widget<Tabs> {
    tui::ui::tabs(
        theme,
-
        vec![tui::ui::reversable_label("Patches").foreground(theme.colors.tabs_highlighted_fg)],
+
        vec![tui::ui::reversable_label("Patches").foreground(style::cyan().fg.unwrap())],
    )
}

@@ -109,7 +109,7 @@ pub fn patches(
    Widget::new(PatchBrowser::new(context, theme, selected))
}

-
pub fn browse_context(context: &Context, theme: &Theme, progress: Progress) -> Widget<ContextBar> {
+
pub fn browse_context(context: &Context, _theme: &Theme, progress: Progress) -> Widget<ContextBar> {
    use radicle::cob::patch::State;
    use tui::ui::{label, label_group};

@@ -132,49 +132,48 @@ pub fn browse_context(context: &Context, theme: &Theme, progress: Progress) -> W
    }

    let context = label(" Patches ")
-
        .foreground(theme.colors.context_bg)
-
        .background(theme.colors.context_badge_bg);
+
        .foreground(style::magenta_reversed().fg.unwrap())
+
        .background(style::magenta_reversed().bg.unwrap());

    let divider = label(" | ")
-
        .foreground(theme.colors.context_light)
-
        .background(theme.colors.context_bg);
+
        .foreground(style::default_reversed().fg.unwrap())
+
        .background(style::default_reversed().bg.unwrap());

    let draft_n = label(&format!("{draft}"))
-
        .foreground(tuirealm::props::Color::Gray)
-
        .background(theme.colors.context_bg);
+
        .foreground(style::default_reversed().fg.unwrap())
+
        .background(style::default_reversed().bg.unwrap());
    let draft = label(" Draft")
-
        .foreground(theme.colors.context_light)
-
        .background(theme.colors.context_bg);
+
        .foreground(style::default_reversed().fg.unwrap())
+
        .background(style::default_reversed().bg.unwrap());

    let open_n = label(&format!("{open}"))
-
        .foreground(tuirealm::props::Color::Green)
-
        .background(theme.colors.context_bg);
+
        .foreground(style::green().fg.unwrap())
+
        .background(style::default_reversed().bg.unwrap());
    let open = label(" Open")
-
        .foreground(theme.colors.context_light)
-
        .background(theme.colors.context_bg);
+
        .foreground(style::default_reversed().fg.unwrap())
+
        .background(style::default_reversed().bg.unwrap());

    let archived_n = label(&format!("{archived}"))
-
        .foreground(tuirealm::props::Color::Yellow)
-
        .background(theme.colors.context_bg);
+
        .foreground(style::yellow().fg.unwrap())
+
        .background(style::default_reversed().bg.unwrap());
    let archived = label(" Archived")
-
        .foreground(theme.colors.context_light)
-
        .background(theme.colors.context_bg);
+
        .foreground(style::default_reversed().fg.unwrap())
+
        .background(style::default_reversed().bg.unwrap());

    let merged_n = label(&format!("{merged}"))
-
        .foreground(tuirealm::props::Color::Cyan)
-
        .background(theme.colors.context_bg);
+
        .foreground(style::cyan().fg.unwrap())
+
        .background(style::default_reversed().bg.unwrap());
    let merged = label(" Merged ")
-
        .foreground(theme.colors.context_light)
-
        .background(theme.colors.context_bg);
+
        .foreground(style::default_reversed().fg.unwrap())
+
        .background(style::default_reversed().bg.unwrap());

    let progress = label(&format!(" {} ", progress.to_string()))
-
        .foreground(theme.colors.context_bg)
-
        .background(theme.colors.context_badge_bg);
+
        .foreground(style::magenta_reversed().fg.unwrap())
+
        .background(style::magenta_reversed().bg.unwrap());

-
    let spacer = label("").background(theme.colors.context_bg);
+
    let spacer = label("").background(style::default_reversed().bg.unwrap());

    let context_bar = ContextBar::new(
-
        theme.clone(),
        label_group(&[context]),
        label_group(&[spacer.clone()]),
        label_group(&[spacer]),
modified bin/commands/patch/select.rs
@@ -19,7 +19,7 @@ use radicle_tui as tui;

use tui::context::Context;
use tui::ui::subscription;
-
use tui::ui::theme::{self, Theme};
+
use tui::ui::theme::Theme;
use tui::{Exit, PageStack, Tui};

use page::ListView;
@@ -74,7 +74,7 @@ impl App {
        Self {
            context,
            pages: PageStack::default(),
-
            theme: theme::default_dark(),
+
            theme: Theme::default(),
            quit: false,
            patch_id: None,
        }
@@ -96,7 +96,7 @@ impl App {
        app: &mut Application<Cid, Message, NoUserEvent>,
        message: Message,
    ) -> Result<Option<Message>> {
-
        let theme = theme::default_dark();
+
        let theme = Theme::default();
        match message {
            Message::Batch(messages) => {
                let mut results = vec![];
modified bin/commands/patch/suite.rs
@@ -20,7 +20,7 @@ use radicle_tui as tui;
use tui::cob;
use tui::context::Context;
use tui::ui::subscription;
-
use tui::ui::theme::{self, Theme};
+
use tui::ui::theme::Theme;
use tui::{Exit, PageStack, Tui};

use page::{ListView, PatchView};
@@ -94,7 +94,7 @@ impl App {
        Self {
            context,
            pages: PageStack::default(),
-
            theme: theme::default_dark(),
+
            theme: Theme::default(),
            quit: false,
        }
    }
@@ -135,7 +135,7 @@ impl App {
        app: &mut Application<Cid, Message, NoUserEvent>,
        message: Message,
    ) -> Result<Option<Message>> {
-
        let theme = theme::default_dark();
+
        let theme = Theme::default();
        match message {
            Message::Batch(messages) => {
                let mut results = vec![];
modified bin/commands/patch/suite/ui.rs
@@ -11,7 +11,7 @@ use tui::context::Context;
use tui::ui::cob;
use tui::ui::cob::PatchItem;
use tui::ui::layout;
-
use tui::ui::theme::Theme;
+
use tui::ui::theme::{style, Theme};
use tui::ui::widget::{Widget, WidgetComponent};

use tui::ui::widget::container::Tabs;
@@ -69,7 +69,7 @@ impl PatchBrowser {
        };

        let table = Widget::new(Table::new(&items, selected, header, widths, theme.clone()))
-
            .highlight(theme.colors.item_list_highlighted_bg);
+
            .highlight(style::highlight().fg.unwrap());

        Self { items, table }
    }
@@ -163,7 +163,7 @@ impl WidgetComponent for Files {
pub fn list_navigation(theme: &Theme) -> Widget<Tabs> {
    tui::ui::tabs(
        theme,
-
        vec![tui::ui::reversable_label("Patches").foreground(theme.colors.tabs_highlighted_fg)],
+
        vec![tui::ui::reversable_label("Patches").foreground(style::magenta().fg.unwrap())],
    )
}

@@ -171,8 +171,8 @@ pub fn navigation(theme: &Theme) -> Widget<Tabs> {
    tui::ui::tabs(
        theme,
        vec![
-
            tui::ui::reversable_label("Activity").foreground(theme.colors.tabs_highlighted_fg),
-
            tui::ui::reversable_label("Files").foreground(theme.colors.tabs_highlighted_fg),
+
            tui::ui::reversable_label("Activity").foreground(style::magenta().fg.unwrap()),
+
            tui::ui::reversable_label("Files").foreground(style::magenta().fg.unwrap()),
        ],
    )
}
@@ -185,15 +185,15 @@ pub fn patches(
    Widget::new(PatchBrowser::new(context, theme, selected))
}

-
pub fn activity(theme: &Theme) -> Widget<Activity> {
-
    let not_implemented = tui::ui::label("not implemented").foreground(theme.colors.default_fg);
+
pub fn activity(_theme: &Theme) -> Widget<Activity> {
+
    let not_implemented = tui::ui::label("not implemented").foreground(style::reset().fg.unwrap());
    let activity = Activity::new(not_implemented);

    Widget::new(activity)
}

-
pub fn files(theme: &Theme) -> Widget<Files> {
-
    let not_implemented = tui::ui::label("not implemented").foreground(theme.colors.default_fg);
+
pub fn files(_theme: &Theme) -> Widget<Files> {
+
    let not_implemented = tui::ui::label("not implemented").foreground(style::reset().fg.unwrap());
    let files = Files::new(not_implemented);

    Widget::new(files)
modified src/ui.rs
@@ -19,10 +19,10 @@ use widget::list::{ColumnWidth, Property, PropertyList, PropertyTable};

use widget::Widget;

+
use self::theme::{style, Theme};
use self::widget::label::LabelGroup;

use super::context::Context;
-
use crate::ui::theme::Theme;

pub fn global_listener() -> Widget<GlobalListener> {
    Widget::new(GlobalListener::default())
@@ -75,7 +75,7 @@ pub fn labeled_container(
) -> Widget<LabeledContainer> {
    let header = container_header(
        theme,
-
        label(&format!(" {title} ")).foreground(theme.colors.default_fg),
+
        label(&format!(" {title} ")).foreground(style::reset().fg.unwrap()),
    );
    let container = LabeledContainer::new(header, component, theme.clone());

@@ -83,9 +83,9 @@ pub fn labeled_container(
}

pub fn shortcut(theme: &Theme, short: &str, long: &str) -> Widget<Shortcut> {
-
    let short = label(short).foreground(theme.colors.shortcut_short_fg);
+
    let short = label(short).foreground(style::gray().fg.unwrap());
+
    let long = label(long).foreground(style::gray_dim().fg.unwrap());
    let divider = label(&theme.icons.whitespace.to_string());
-
    let long = label(long).foreground(theme.colors.shortcut_long_fg);

    // TODO: Remove when size constraints are implemented
    let short_w = short.query(Attribute::Width).unwrap().unwrap_size();
@@ -100,16 +100,16 @@ pub fn shortcut(theme: &Theme, short: &str, long: &str) -> Widget<Shortcut> {

pub fn shortcuts(theme: &Theme, shortcuts: Vec<Widget<Shortcut>>) -> Widget<Shortcuts> {
    let divider = label(&format!(" {} ", theme.icons.shortcutbar_divider))
-
        .foreground(theme.colors.shortcutbar_divider_fg);
+
        .foreground(style::gray_dim().fg.unwrap());
    let shortcut_bar = Shortcuts::new(shortcuts, divider);

    Widget::new(shortcut_bar).height(1)
}

pub fn property(theme: &Theme, name: &str, value: &str) -> Widget<Property> {
-
    let name = label(name).foreground(theme.colors.property_name_fg);
+
    let name = label(name).foreground(style::cyan().fg.unwrap());
    let divider = label(&format!(" {} ", theme.icons.property_divider));
-
    let value = label(value).foreground(theme.colors.default_fg);
+
    let value = label(value).foreground(style::reset().fg.unwrap());

    // TODO: Remove when size constraints are implemented
    let name_w = name.query(Attribute::Width).unwrap().unwrap_size();
@@ -140,9 +140,9 @@ pub fn tabs(_theme: &Theme, tabs: Vec<Widget<Label>>) -> Widget<Tabs> {
    Widget::new(tabs).height(2)
}

-
pub fn app_info(context: &Context, theme: &Theme) -> Widget<AppInfo> {
-
    let project = label(context.project().name()).foreground(theme.colors.app_header_project_fg);
-
    let rid = label(&format!(" ({})", context.id())).foreground(theme.colors.app_header_rid_fg);
+
pub fn app_info(context: &Context) -> Widget<AppInfo> {
+
    let project = label(context.project().name()).foreground(style::cyan().fg.unwrap());
+
    let rid = label(&format!(" ({})", context.id())).foreground(style::yellow().fg.unwrap());

    let project_w = project
        .query(Attribute::Width)
@@ -163,17 +163,16 @@ pub fn app_header(
    nav: Option<Widget<Tabs>>,
) -> Widget<AppHeader> {
    let line =
-
        label(&theme.icons.tab_overline.to_string()).foreground(theme.colors.tabs_highlighted_fg);
+
        label(&theme.icons.tab_overline.to_string()).foreground(style::magenta().fg.unwrap());
    let line = Widget::new(VerticalLine::new(line));
-
    let info = app_info(context, theme);
+
    let info = app_info(context);
    let header = AppHeader::new(nav, info, line);

    Widget::new(header)
}

pub fn info(theme: &Theme, message: &str) -> Widget<Popup> {
-
    let textarea =
-
        Widget::new(Textarea::new(theme.clone())).content(AttrValue::String(message.to_owned()));
+
    let textarea = Widget::new(Textarea::default()).content(AttrValue::String(message.to_owned()));
    let container = labeled_container(theme, "Info", textarea.to_boxed());

    Widget::new(Popup::new(theme.clone(), container))
@@ -182,8 +181,7 @@ pub fn info(theme: &Theme, message: &str) -> Widget<Popup> {
}

pub fn warning(theme: &Theme, message: &str) -> Widget<Popup> {
-
    let textarea =
-
        Widget::new(Textarea::new(theme.clone())).content(AttrValue::String(message.to_owned()));
+
    let textarea = Widget::new(Textarea::default()).content(AttrValue::String(message.to_owned()));
    let container = labeled_container(theme, "Warning", textarea.to_boxed());

    Widget::new(Popup::new(theme.clone(), container))
@@ -192,8 +190,7 @@ pub fn warning(theme: &Theme, message: &str) -> Widget<Popup> {
}

pub fn error(theme: &Theme, message: &str) -> Widget<Popup> {
-
    let textarea =
-
        Widget::new(Textarea::new(theme.clone())).content(AttrValue::String(message.to_owned()));
+
    let textarea = Widget::new(Textarea::default()).content(AttrValue::String(message.to_owned()));
    let container = labeled_container(theme, "Error", textarea.to_boxed());

    Widget::new(Popup::new(theme.clone(), container))
modified src/ui/cob.rs
@@ -2,7 +2,7 @@ pub mod format;

use radicle_surf;

-
use tuirealm::props::{Color, Style, TextModifiers};
+
use tuirealm::props::{Color, Style};
use tuirealm::tui::text::{Span, Spans};
use tuirealm::tui::widgets::Cell;

@@ -20,6 +20,8 @@ use radicle::cob::{Label, Timestamp};
use crate::ui::theme::Theme;
use crate::ui::widget::list::{ListItem, TableItem};

+
use super::theme::style;
+

/// An author item that can be used in tables, list or trees.
///
/// Breaks up dependencies to [`Profile`] and [`Repository`] that
@@ -142,21 +144,16 @@ impl TryFrom<(&Profile, &Repository, PatchId, Patch)> for PatchItem {
}

impl TableItem<8> for PatchItem {
-
    fn row(&self, theme: &Theme) -> [Cell; 8] {
+
    fn row(&self, _theme: &Theme) -> [Cell; 8] {
        let (icon, color) = format_patch_state(&self.state);
-
        let state = Cell::from(icon).style(Style::default().fg(color));
-

-
        let id = Cell::from(format::cob(&self.id))
-
            .style(Style::default().fg(theme.colors.browser_list_id));

-
        let title = Cell::from(self.title.clone())
-
            .style(Style::default().fg(theme.colors.browser_list_title));
+
        let state = Cell::from(icon).style(Style::default().fg(color));
+
        let id = Cell::from(format::cob(&self.id)).style(style::cyan());
+
        let title = Cell::from(self.title.clone()).style(style::reset());

        let author_style = match &self.author.alias {
-
            Some(_) => Style::default().fg(theme.colors.browser_list_author),
-
            None => Style::default()
-
                .fg(theme.colors.browser_list_author)
-
                .add_modifier(TextModifiers::DIM),
+
            Some(_) => style::magenta(),
+
            None => style::magenta_dim(),
        };
        let author = Cell::from(format_author(
            &self.author.did,
@@ -165,17 +162,10 @@ impl TableItem<8> for PatchItem {
        ))
        .style(author_style);

-
        let head = Cell::from(format::oid(self.head))
-
            .style(Style::default().fg(theme.colors.browser_patch_list_head));
-

-
        let added = Cell::from(format!("+{}", self.added))
-
            .style(Style::default().fg(theme.colors.browser_patch_list_added));
-

-
        let removed = Cell::from(format!("-{}", self.removed))
-
            .style(Style::default().fg(theme.colors.browser_patch_list_removed));
-

-
        let updated = Cell::from(format::timestamp(&self.timestamp))
-
            .style(Style::default().fg(theme.colors.browser_list_timestamp));
+
        let head = Cell::from(format::oid(self.head)).style(style::lightblue());
+
        let added = Cell::from(format!("+{}", self.added)).style(style::green());
+
        let removed = Cell::from(format!("-{}", self.removed)).style(style::red());
+
        let updated = Cell::from(format::timestamp(&self.timestamp)).style(style::gray());

        [state, id, title, author, head, added, removed, updated]
    }
@@ -262,21 +252,16 @@ impl From<(&Profile, &Repository, IssueId, Issue)> for IssueItem {
}

impl TableItem<7> for IssueItem {
-
    fn row(&self, theme: &Theme) -> [Cell; 7] {
+
    fn row(&self, _theme: &Theme) -> [Cell; 7] {
        let (icon, color) = format_issue_state(&self.state);
-
        let state = Cell::from(icon).style(Style::default().fg(color));
-

-
        let id = Cell::from(format::cob(&self.id))
-
            .style(Style::default().fg(theme.colors.browser_list_id));

-
        let title = Cell::from(self.title.clone())
-
            .style(Style::default().fg(theme.colors.browser_list_title));
+
        let state = Cell::from(icon).style(Style::default().fg(color));
+
        let id = Cell::from(format::cob(&self.id)).style(style::cyan());
+
        let title = Cell::from(self.title.clone()).style(style::reset());

        let author_style = match &self.author.alias {
-
            Some(_) => Style::default().fg(theme.colors.browser_list_author),
-
            None => Style::default()
-
                .fg(theme.colors.browser_list_author)
-
                .add_modifier(TextModifiers::DIM),
+
            Some(_) => style::magenta(),
+
            None => style::magenta_dim(),
        };
        let author = Cell::from(format_author(
            &self.author.did,
@@ -285,19 +270,15 @@ impl TableItem<7> for IssueItem {
        ))
        .style(author_style);

-
        let labels = Cell::from(format_labels(&self.labels))
-
            .style(Style::default().fg(theme.colors.browser_list_labels));
-

+
        let labels = Cell::from(format_labels(&self.labels)).style(style::lightblue());
        let assignees = self
            .assignees
            .iter()
            .map(|author| (author.did, author.alias.clone(), author.is_you))
            .collect::<Vec<_>>();
-
        let assignees = Cell::from(format_assignees(&assignees))
-
            .style(Style::default().fg(theme.colors.browser_list_author));

-
        let opened = Cell::from(format::timestamp(&self.timestamp))
-
            .style(Style::default().fg(theme.colors.browser_list_timestamp));
+
        let assignees = Cell::from(format_assignees(&assignees)).style(author_style);
+
        let opened = Cell::from(format::timestamp(&self.timestamp)).style(style::gray());

        [state, id, title, author, labels, assignees, opened]
    }
@@ -307,18 +288,14 @@ impl ListItem for IssueItem {
    fn row(&self, theme: &Theme) -> tuirealm::tui::widgets::ListItem {
        let (state, state_color) = format_issue_state(&self.state);
        let author_style = match &self.author.alias {
-
            Some(_) => Style::default().fg(theme.colors.browser_list_author),
-
            None => Style::default()
-
                .fg(theme.colors.browser_list_author)
-
                .add_modifier(TextModifiers::DIM),
+
            Some(_) => style::magenta(),
+
            None => style::magenta_dim(),
        };
+

        let lines = vec![
            Spans::from(vec![
                Span::styled(state, Style::default().fg(state_color)),
-
                Span::styled(
-
                    self.title.clone(),
-
                    Style::default().fg(theme.colors.browser_list_title),
-
                ),
+
                Span::styled(self.title.clone(), style::reset()),
            ]),
            Spans::from(vec![
                Span::raw(String::from("   ")),
@@ -326,14 +303,8 @@ impl ListItem for IssueItem {
                    format_author(&self.author.did, &self.author.alias, self.author.is_you),
                    author_style,
                ),
-
                Span::styled(
-
                    format!(" {} ", theme.icons.property_divider),
-
                    Style::default().fg(theme.colors.property_divider_fg),
-
                ),
-
                Span::styled(
-
                    format::timestamp(&self.timestamp),
-
                    Style::default().fg(theme.colors.browser_list_timestamp),
-
                ),
+
                Span::styled(format!(" {} ", theme.icons.property_divider), style::gray()),
+
                Span::styled(format::timestamp(&self.timestamp), style::gray()),
            ]),
        ];
        tuirealm::tui::widgets::ListItem::new(lines)
modified src/ui/theme.rs
@@ -1,45 +1,4 @@
-
use tuirealm::props::Color;
-

-
const COLOR_DEFAULT_FG: Color = Color::Rgb(200, 200, 200);
-
const COLOR_DEFAULT_DARK_FG: Color = Color::Rgb(150, 150, 150);
-
const COLOR_DEFAULT_DARK: Color = Color::Rgb(100, 100, 100);
-
const COLOR_DEFAULT_DARKER: Color = Color::Rgb(70, 70, 70);
-
const COLOR_DEFAULT_DARKEST: Color = Color::Rgb(40, 40, 40);
-
const COLOR_DEFAULT_FAINT: Color = Color::Rgb(20, 20, 20);
-

-
#[derive(Debug, Clone)]
-
pub struct Colors {
-
    pub default_fg: Color,
-
    pub tabs_highlighted_fg: Color,
-
    pub app_header_project_fg: Color,
-
    pub app_header_rid_fg: Color,
-
    pub labeled_container_bg: Color,
-
    pub item_list_highlighted_bg: Color,
-
    pub property_name_fg: Color,
-
    pub property_divider_fg: Color,
-
    pub shortcut_short_fg: Color,
-
    pub shortcut_long_fg: Color,
-
    pub shortcutbar_divider_fg: Color,
-
    pub browser_list_id: Color,
-
    pub browser_list_title: Color,
-
    pub browser_list_description: Color,
-
    pub browser_list_author: Color,
-
    pub browser_list_labels: Color,
-
    pub browser_list_comments: Color,
-
    pub browser_list_timestamp: Color,
-
    pub browser_patch_list_head: Color,
-
    pub browser_patch_list_added: Color,
-
    pub browser_patch_list_removed: Color,
-
    pub context_bg: Color,
-
    pub context_light: Color,
-
    pub context_dark: Color,
-
    pub context_badge_bg: Color,
-
    pub context_badge_edit_bg: Color,
-
    pub context_color_fg: Color,
-
    pub container_border_fg: Color,
-
    pub container_border_focus_fg: Color,
-
    pub input_placeholder_fg: Color,
-
}
+
use tuirealm::props::BorderType;

#[derive(Debug, Clone)]
pub struct Icons {
@@ -55,70 +14,118 @@ pub struct Tables {
    pub spacing: u16,
}

-
/// The Radicle TUI theme. Will be defined in a JSON config file in the
-
/// future. e.g.:
-
/// {
-
///     "name": "Default",
-
///     "colors": {
-
///         "foreground": "#ffffff",
-
///         "propertyForeground": "#ffffff",
-
///         "highlightedBackground": "#000000",
-
///     },
-
///     "icons": {
-
///         "workspaces.divider": "|",
-
///         "shortcuts.divider: "∙",
-
///     }
-
/// }
+
/// The Radicle TUI theme. In the future, it might be defined in a JSON
+
/// config file.
#[derive(Debug, Clone)]
pub struct Theme {
    pub name: String,
-
    pub colors: Colors,
    pub icons: Icons,
    pub tables: Tables,
+
    pub border_type: BorderType,
}

-
pub fn default_dark() -> Theme {
-
    Theme {
-
        name: String::from("Default"),
-
        colors: Colors {
-
            default_fg: COLOR_DEFAULT_FG,
-
            tabs_highlighted_fg: Color::Magenta,
-
            app_header_project_fg: Color::Cyan,
-
            app_header_rid_fg: Color::Yellow,
-
            labeled_container_bg: COLOR_DEFAULT_FAINT,
-
            item_list_highlighted_bg: COLOR_DEFAULT_DARKER,
-
            property_name_fg: Color::Cyan,
-
            property_divider_fg: COLOR_DEFAULT_DARK,
-
            shortcut_short_fg: COLOR_DEFAULT_DARK,
-
            shortcut_long_fg: COLOR_DEFAULT_DARKER,
-
            shortcutbar_divider_fg: COLOR_DEFAULT_DARKER,
-
            browser_list_id: Color::Cyan,
-
            browser_list_title: COLOR_DEFAULT_FG,
-
            browser_list_description: COLOR_DEFAULT_DARK,
-
            browser_list_author: Color::Magenta,
-
            browser_list_labels: Color::LightBlue,
-
            browser_list_comments: COLOR_DEFAULT_DARK_FG,
-
            browser_list_timestamp: COLOR_DEFAULT_DARK,
-
            browser_patch_list_head: Color::LightBlue,
-
            browser_patch_list_added: Color::Green,
-
            browser_patch_list_removed: Color::Red,
-
            context_bg: COLOR_DEFAULT_DARKEST,
-
            context_light: Color::Gray,
-
            context_dark: COLOR_DEFAULT_DARK,
-
            context_badge_bg: Color::Magenta,
-
            context_badge_edit_bg: Color::Red,
-
            context_color_fg: Color::Cyan,
-
            container_border_fg: COLOR_DEFAULT_DARKEST,
-
            container_border_focus_fg: COLOR_DEFAULT_DARK,
-
            input_placeholder_fg: COLOR_DEFAULT_DARK,
-
        },
-
        icons: Icons {
-
            property_divider: '∙',
-
            shortcutbar_divider: '∙',
-
            tab_divider: '|',
-
            tab_overline: '▔',
-
            whitespace: ' ',
-
        },
-
        tables: Tables { spacing: 2 },
+
impl Default for Theme {
+
    fn default() -> Theme {
+
        Theme {
+
            name: String::from("Default"),
+
            icons: Icons {
+
                property_divider: '∙',
+
                shortcutbar_divider: '∙',
+
                tab_divider: '|',
+
                tab_overline: '▔',
+
                whitespace: ' ',
+
            },
+
            tables: Tables { spacing: 2 },
+
            border_type: BorderType::Rounded,
+
        }
+
    }
+
}
+

+
pub mod style {
+
    use tuirealm::props::{Color, Style, TextModifiers};
+

+
    pub fn reset() -> Style {
+
        Style::default().fg(Color::Reset)
+
    }
+

+
    pub fn reset_dim() -> Style {
+
        Style::default()
+
            .fg(Color::Reset)
+
            .add_modifier(TextModifiers::DIM)
+
    }
+

+
    pub fn red() -> Style {
+
        Style::default().fg(Color::Red)
+
    }
+

+
    pub fn green() -> Style {
+
        Style::default().fg(Color::Green)
+
    }
+

+
    pub fn yellow() -> Style {
+
        Style::default().fg(Color::Yellow)
+
    }
+

+
    pub fn blue() -> Style {
+
        Style::default().fg(Color::Blue)
+
    }
+

+
    pub fn magenta() -> Style {
+
        Style::default().fg(Color::Magenta)
+
    }
+

+
    pub fn magenta_dim() -> Style {
+
        Style::default()
+
            .fg(Color::Magenta)
+
            .add_modifier(TextModifiers::DIM)
+
    }
+

+
    pub fn cyan() -> Style {
+
        Style::default().fg(Color::Cyan)
+
    }
+

+
    pub fn lightblue() -> Style {
+
        Style::default().fg(Color::LightBlue)
+
    }
+

+
    pub fn gray() -> Style {
+
        Style::default().fg(Color::Gray)
+
    }
+

+
    pub fn gray_dim() -> Style {
+
        Style::default()
+
            .fg(Color::Gray)
+
            .add_modifier(TextModifiers::DIM)
+
    }
+

+
    pub fn darkgray() -> Style {
+
        Style::default().fg(Color::DarkGray)
+
    }
+

+
    pub fn default_reversed() -> Style {
+
        Style::default()
+
            .fg(Color::Reset)
+
            .bg(Color::DarkGray)
+
            .add_modifier(TextModifiers::DIM)
+
    }
+

+
    pub fn magenta_reversed() -> Style {
+
        Style::default().fg(Color::DarkGray).bg(Color::Magenta)
+
    }
+

+
    pub fn yellow_reversed() -> Style {
+
        Style::default().fg(Color::DarkGray).bg(Color::Yellow)
+
    }
+

+
    pub fn border(focus: bool) -> Style {
+
        if focus {
+
            gray_dim()
+
        } else {
+
            darkgray()
+
        }
+
    }
+

+
    pub fn highlight() -> Style {
+
        darkgray()
    }
}
modified src/ui/widget/container.rs
@@ -1,5 +1,5 @@
use tuirealm::command::{Cmd, CmdResult};
-
use tuirealm::props::{AttrValue, Attribute, BorderSides, BorderType, Props, Style, TextModifiers};
+
use tuirealm::props::{AttrValue, Attribute, BorderSides, Props, TextModifiers};
use tuirealm::tui::layout::{Constraint, Direction, Layout, Margin, Rect};
use tuirealm::tui::widgets::{Block, Cell, Clear, Row};
use tuirealm::{Frame, MockComponent, State, StateValue};
@@ -7,7 +7,7 @@ use tuirealm::{Frame, MockComponent, State, StateValue};
use crate::ui::ext::HeaderBlock;
use crate::ui::layout;
use crate::ui::state::TabState;
-
use crate::ui::theme::Theme;
+
use crate::ui::theme::{style, Theme};
use crate::ui::widget::{utils, Widget, WidgetComponent};

use super::label::Label;
@@ -72,8 +72,6 @@ impl WidgetComponent for VerticalLine {
    }
}

-
////////////////////////////////////////////////
-

/// A tab header that displays all labels horizontally aligned and separated
/// by a divider. Highlights the label defined by the current tab index.
#[derive(Clone)]
@@ -278,17 +276,11 @@ impl<const W: usize> WidgetComponent for Header<W> {
            .get_or(Attribute::Focus, AttrValue::Flag(false))
            .unwrap_flag();

-
        let color = if focus {
-
            self.theme.colors.container_border_focus_fg
-
        } else {
-
            self.theme.colors.container_border_fg
-
        };
-

        if display {
            let block = HeaderBlock::default()
                .borders(BorderSides::all())
-
                .border_style(Style::default().fg(color))
-
                .border_type(BorderType::Rounded);
+
                .border_style(style::border(focus))
+
                .border_type(self.theme.border_type);
            frame.render_widget(block, area);

            let layout = Layout::default()
@@ -304,7 +296,7 @@ impl<const W: usize> WidgetComponent for Header<W> {
                .iter()
                .map(|label| {
                    let cell: Cell = label.into();
-
                    cell.style(Style::default().fg(self.theme.colors.default_fg))
+
                    cell.style(style::reset())
                })
                .collect::<Vec<_>>()
                .try_into()
@@ -348,12 +340,6 @@ impl WidgetComponent for Container {
            .get_or(Attribute::Focus, AttrValue::Flag(false))
            .unwrap_flag();

-
        let color = if focus {
-
            self.theme.colors.container_border_focus_fg
-
        } else {
-
            self.theme.colors.container_border_fg
-
        };
-

        if display {
            // Make some space on the left
            let layout = Layout::default()
@@ -369,8 +355,8 @@ impl WidgetComponent for Container {

            let block = Block::default()
                .borders(BorderSides::ALL)
-
                .border_style(Style::default().fg(color))
-
                .border_type(BorderType::Rounded);
+
                .border_style(style::border(focus))
+
                .border_type(self.theme.border_type);
            frame.render_widget(block, area);
        }
    }
@@ -409,12 +395,6 @@ impl WidgetComponent for LabeledContainer {
            .get_or(Attribute::Focus, AttrValue::Flag(false))
            .unwrap_flag();

-
        let color = if focus {
-
            self.theme.colors.container_border_focus_fg
-
        } else {
-
            self.theme.colors.container_border_fg
-
        };
-

        let header_height = self
            .header
            .query(Attribute::Height)
@@ -432,8 +412,8 @@ impl WidgetComponent for LabeledContainer {

            let block = Block::default()
                .borders(BorderSides::BOTTOM | BorderSides::LEFT | BorderSides::RIGHT)
-
                .border_style(Style::default().fg(color))
-
                .border_type(BorderType::Rounded);
+
                .border_style(style::border(focus))
+
                .border_type(self.theme.border_type);
            frame.render_widget(block.clone(), layout[1]);

            self.component
modified src/ui/widget/context.rs
@@ -6,7 +6,7 @@ use tuirealm::{Frame, MockComponent, State};
use super::label::{Label, LabelGroup};

use crate::ui::layout;
-
use crate::ui::theme::Theme;
+
use crate::ui::theme::{style, Theme};
use crate::ui::widget::{Widget, WidgetComponent};

pub enum Progress {
@@ -128,14 +128,12 @@ pub struct ContextBar {
    col_2: Widget<LabelGroup>,
    col_3: Widget<LabelGroup>,
    col_4: Widget<LabelGroup>,
-
    theme: Theme,
}

impl ContextBar {
    pub const PROP_EDIT_MODE: &'static str = "edit-mode";

    pub fn new(
-
        theme: Theme,
        col_0: Widget<LabelGroup>,
        col_1: Widget<LabelGroup>,
        col_2: Widget<LabelGroup>,
@@ -143,7 +141,6 @@ impl ContextBar {
        col_4: Widget<LabelGroup>,
    ) -> Self {
        Self {
-
            theme,
            col_0,
            col_1,
            col_2,
@@ -173,7 +170,7 @@ impl WidgetComponent for ContextBar {
        if edit_mode {
            self.col_0.attr(
                Attribute::Background,
-
                AttrValue::Color(self.theme.colors.context_badge_edit_bg),
+
                AttrValue::Color(style::yellow_reversed().bg.unwrap()),
            )
        }

@@ -211,7 +208,7 @@ impl WidgetComponent for ContextBar {
}

pub fn bar(
-
    theme: &Theme,
+
    _theme: &Theme,
    label_0: &str,
    label_1: &str,
    label_2: &str,
@@ -221,20 +218,20 @@ pub fn bar(
    use crate::ui::{label, label_group};

    let label_0 = label(&format!(" {label_0} "))
-
        .foreground(theme.colors.context_bg)
-
        .background(theme.colors.context_badge_bg);
+
        .foreground(style::magenta_reversed().fg.unwrap())
+
        .background(style::magenta_reversed().bg.unwrap());
    let label_1 = label(&format!(" {label_1} "))
-
        .foreground(theme.colors.context_color_fg)
-
        .background(theme.colors.context_bg);
+
        .foreground(style::default_reversed().fg.unwrap())
+
        .background(style::default_reversed().bg.unwrap());
    let label_2 = label(&format!(" {label_2} "))
-
        .foreground(theme.colors.default_fg)
-
        .background(theme.colors.context_bg);
+
        .foreground(style::default_reversed().fg.unwrap())
+
        .background(style::default_reversed().bg.unwrap());
    let label_3 = label(&format!(" {label_3} "))
-
        .foreground(theme.colors.context_light)
-
        .background(theme.colors.context_bg);
+
        .foreground(style::default_reversed().fg.unwrap())
+
        .background(style::default_reversed().bg.unwrap());
    let label_4 = label(&format!(" {label_4} "))
-
        .foreground(theme.colors.context_light)
-
        .background(theme.colors.context_bg);
+
        .foreground(style::default_reversed().fg.unwrap())
+
        .background(style::default_reversed().bg.unwrap());

    let label_0 = label_group(&[label_0]);
    let label_1 = label_group(&[label_1]);
@@ -242,7 +239,7 @@ pub fn bar(
    let label_3 = label_group(&[label_3]);
    let label_4 = label_group(&[label_4]);

-
    let context_bar = ContextBar::new(theme.clone(), label_0, label_1, label_2, label_3, label_4);
+
    let context_bar = ContextBar::new(label_0, label_1, label_2, label_3, label_4);

    Widget::new(context_bar).height(1)
}
modified src/ui/widget/form.rs
@@ -6,7 +6,7 @@ use tuirealm::tui::layout::{Constraint, Direction, Margin, Rect};
use tuirealm::{AttrValue, Attribute, Frame, MockComponent, Props, State, StateValue};

use crate::ui::state::FormState;
-
use crate::ui::theme::Theme;
+
use crate::ui::theme::{style, Theme};
use crate::ui::widget::{Widget, WidgetComponent};

use super::container::Container;
@@ -24,12 +24,12 @@ impl TextField {
            .wrap(false)
            .single_line(true)
            .cursor_line_style(Style::reset())
-
            .style(Style::default().fg(theme.colors.default_fg));
+
            .style(style::reset());
        let container = crate::ui::container(&theme, Box::new(input));

        Self {
            input: container,
-
            placeholder: crate::ui::label(title).foreground(theme.colors.input_placeholder_fg),
+
            placeholder: crate::ui::label(title).foreground(style::gray_dim().fg.unwrap()),
            show_placeholder: true,
        }
    }
@@ -98,12 +98,12 @@ impl TextArea {
            .wrap(true)
            .single_line(false)
            .cursor_line_style(Style::reset())
-
            .style(Style::default().fg(theme.colors.default_fg));
+
            .style(style::reset());
        let container = crate::ui::container(&theme, Box::new(input));

        Self {
            input: container,
-
            placeholder: crate::ui::label(title).foreground(theme.colors.input_placeholder_fg),
+
            placeholder: crate::ui::label(title).foreground(style::gray_dim().fg.unwrap()),
            show_placeholder: true,
        }
    }
modified src/ui/widget/label.rs
@@ -5,7 +5,7 @@ use tuirealm::tui::text::{Span, Spans, Text};
use tuirealm::{Frame, MockComponent, State, StateValue};

use crate::ui::layout;
-
use crate::ui::theme::Theme;
+
use crate::ui::theme::style;
use crate::ui::widget::{Widget, WidgetComponent};

/// A label that can be styled using a foreground color and text modifiers.
@@ -123,9 +123,8 @@ impl WidgetComponent for LabelGroup {
    }
}

+
#[derive(Default)]
pub struct Textarea {
-
    /// The current theme.
-
    theme: Theme,
    /// The scroll offset.
    offset: usize,
    /// The current line count.
@@ -139,16 +138,6 @@ pub struct Textarea {
impl Textarea {
    pub const PROP_DISPLAY_PROGRESS: &'static str = "display-progress";

-
    pub fn new(theme: Theme) -> Self {
-
        Self {
-
            theme,
-
            offset: 0,
-
            len: 0,
-
            height: 0,
-
            scroll_percent: 0,
-
        }
-
    }
-

    fn scroll_percent(offset: usize, len: usize, height: usize) -> usize {
        if height >= len {
            100
@@ -170,9 +159,6 @@ impl WidgetComponent for Textarea {
        let focus = properties
            .get_or(Attribute::Focus, AttrValue::Flag(false))
            .unwrap_flag();
-
        let fg = properties
-
            .get_or(Attribute::Foreground, AttrValue::Color(Color::Reset))
-
            .unwrap_color();
        let display_progress = properties
            .get_or(
                Attribute::Custom(Self::PROP_DISPLAY_PROGRESS),
@@ -189,12 +175,6 @@ impl WidgetComponent for Textarea {
            .constraints([Constraint::Min(1), Constraint::Length(1)])
            .split(area);

-
        let highlight_color = if focus {
-
            self.theme.colors.container_border_focus_fg
-
        } else {
-
            self.theme.colors.container_border_fg
-
        };
-

        // TODO: replace with `ratatui`'s reflow module when that becomes
        // public: https://github.com/tui-rs-revival/ratatui/pull/9.
        //
@@ -212,7 +192,7 @@ impl WidgetComponent for Textarea {

        let paragraph = Paragraph::new(body)
            .scroll((self.offset as u16, 0))
-
            .style(Style::default().fg(fg));
+
            .style(style::reset());
        frame.render_widget(paragraph, layout[0]);

        self.scroll_percent = Self::scroll_percent(self.offset, self.len, self.height);
@@ -220,7 +200,7 @@ impl WidgetComponent for Textarea {
        if display_progress {
            let progress = Spans::from(vec![Span::styled(
                format!("{} %", self.scroll_percent),
-
                Style::default().fg(highlight_color),
+
                style::border(focus),
            )]);

            let progress = Paragraph::new(progress).alignment(Alignment::Right);
modified src/ui/widget/list.rs
@@ -1,12 +1,12 @@
use tuirealm::command::{Cmd, CmdResult};
-
use tuirealm::props::{AttrValue, Attribute, BorderSides, BorderType, Color, Props, Style};
+
use tuirealm::props::{AttrValue, Attribute, BorderSides, Color, Props, Style};
use tuirealm::tui::layout::{Constraint, Direction, Layout, Rect};
use tuirealm::tui::widgets::{Block, Cell, ListState, Row, TableState};
use tuirealm::{Frame, MockComponent, State, StateValue};

use crate::ui::layout;
use crate::ui::state::ItemState;
-
use crate::ui::theme::Theme;
+
use crate::ui::theme::{style, Theme};
use crate::ui::widget::{utils, Widget, WidgetComponent};

use super::container::Header;
@@ -234,12 +234,6 @@ where
            .get_or(Attribute::Focus, AttrValue::Flag(false))
            .unwrap_flag();

-
        let color = if focus {
-
            self.theme.colors.container_border_focus_fg
-
        } else {
-
            self.theme.colors.container_border_fg
-
        };
-

        let layout = Layout::default()
            .direction(Direction::Vertical)
            .constraints(vec![Constraint::Length(3), Constraint::Min(1)])
@@ -256,8 +250,8 @@ where
            .block(
                Block::default()
                    .borders(BorderSides::BOTTOM | BorderSides::LEFT | BorderSides::RIGHT)
-
                    .border_style(Style::default().fg(color))
-
                    .border_type(BorderType::Rounded),
+
                    .border_style(style::border(focus))
+
                    .border_type(self.theme.border_type),
            )
            .highlight_style(Style::default().bg(highlight))
            .column_spacing(self.theme.tables.spacing)