diff --git a/crates/bds-ui/src/components/inputs.rs b/crates/bds-ui/src/components/inputs.rs index 6a8257f..e1f319b 100644 --- a/crates/bds-ui/src/components/inputs.rs +++ b/crates/bds-ui/src/components/inputs.rs @@ -1,3 +1,4 @@ +use iced::widget::scrollable; use iced::widget::text::Shaping; use iced::widget::{ Container, button, checkbox, column, container, pick_list, row, text, text_editor, text_input, @@ -55,6 +56,68 @@ pub fn tooltip_style(_theme: &Theme) -> container::Style { } } +/// Thin scrollbar geometry shared by every vertical application surface. +pub fn compact_scrollbar() -> scrollable::Scrollbar { + scrollable::Scrollbar::new() + .width(6) + .margin(1) + .scroller_width(3) +} + +/// Muted overlay-like scrollbar chrome that gains contrast only while interacting. +pub fn scrollable_style(_theme: &Theme, status: scrollable::Status) -> scrollable::Style { + let rail = |opacity| scrollable::Rail { + background: None, + border: Border::default(), + scroller: scrollable::Scroller { + color: Color::from_rgba(0.72, 0.74, 0.80, opacity), + border: Border { + radius: 999.0.into(), + ..Border::default() + }, + }, + }; + let (horizontal_opacity, vertical_opacity) = match status { + scrollable::Status::Active => (0.24, 0.24), + scrollable::Status::Hovered { + is_horizontal_scrollbar_hovered, + is_vertical_scrollbar_hovered, + } => ( + if is_horizontal_scrollbar_hovered { + 0.55 + } else { + 0.24 + }, + if is_vertical_scrollbar_hovered { + 0.55 + } else { + 0.24 + }, + ), + scrollable::Status::Dragged { + is_horizontal_scrollbar_dragged, + is_vertical_scrollbar_dragged, + } => ( + if is_horizontal_scrollbar_dragged { + 0.75 + } else { + 0.24 + }, + if is_vertical_scrollbar_dragged { + 0.75 + } else { + 0.24 + }, + ), + }; + scrollable::Style { + container: container::Style::default(), + vertical_rail: rail(vertical_opacity), + horizontal_rail: rail(horizontal_opacity), + gap: None, + } +} + pub fn field_style(_theme: &Theme, status: text_input::Status) -> text_input::Style { let border_color = match status { text_input::Status::Focused => FOCUS_COLOR, @@ -402,4 +465,27 @@ mod tests { assert_eq!(style.border.width, 1.0); assert_eq!(style.border.radius.top_left, 6.0); } + + #[test] + fn application_scrollbars_are_compact_muted_and_only_brighten_on_hover() { + assert_eq!( + compact_scrollbar(), + scrollable::Scrollbar::new() + .width(6) + .margin(1) + .scroller_width(3) + ); + let theme = app_theme(); + let active = scrollable_style(&theme, scrollable::Status::Active); + let hovered = scrollable_style( + &theme, + scrollable::Status::Hovered { + is_horizontal_scrollbar_hovered: false, + is_vertical_scrollbar_hovered: true, + }, + ); + assert!(active.vertical_rail.background.is_none()); + assert!(active.vertical_rail.scroller.color.a < 0.5); + assert!(hovered.vertical_rail.scroller.color.a > active.vertical_rail.scroller.color.a); + } } diff --git a/crates/bds-ui/src/views/chat_surfaces.rs b/crates/bds-ui/src/views/chat_surfaces.rs index 7439f1f..879481a 100644 --- a/crates/bds-ui/src/views/chat_surfaces.rs +++ b/crates/bds-ui/src/views/chat_surfaces.rs @@ -335,8 +335,9 @@ fn table<'a>(surface: &'a InlineSurface, locale: UiLocale) -> Element<'a, Messag } scrollable(iced::widget::Column::with_children(body).spacing(6)) .direction(scrollable::Direction::Horizontal( - scrollable::Scrollbar::default(), + inputs::compact_scrollbar(), )) + .style(inputs::scrollable_style) .into() } diff --git a/crates/bds-ui/src/views/chat_view.rs b/crates/bds-ui/src/views/chat_view.rs index 0fbd25a..7287788 100644 --- a/crates/bds-ui/src/views/chat_view.rs +++ b/crates/bds-ui/src/views/chat_view.rs @@ -339,6 +339,8 @@ pub fn view<'a>( .spacing(10) .width(Length::Fill), ) + .direction(scrollable::Direction::Vertical(inputs::compact_scrollbar())) + .style(inputs::scrollable_style) .anchor_bottom() .height(Length::Fill); diff --git a/crates/bds-ui/src/views/dashboard.rs b/crates/bds-ui/src/views/dashboard.rs index 52682bd..3abfa89 100644 --- a/crates/bds-ui/src/views/dashboard.rs +++ b/crates/bds-ui/src/views/dashboard.rs @@ -174,6 +174,8 @@ pub fn view<'a>(state: &'a DashboardState, locale: UiLocale) -> Element<'a, Mess } scrollable(container(content.padding(24).width(Length::Fill))) + .direction(scrollable::Direction::Vertical(inputs::compact_scrollbar())) + .style(inputs::scrollable_style) .width(Length::Fill) .height(Length::Fill) .into() diff --git a/crates/bds-ui/src/views/documentation.rs b/crates/bds-ui/src/views/documentation.rs index 1af88e3..623c3f9 100644 --- a/crates/bds-ui/src/views/documentation.rs +++ b/crates/bds-ui/src/views/documentation.rs @@ -200,6 +200,8 @@ pub fn view(state: &DocumentationState, locale: UiLocale) -> Element<'_, Message }); } scrollable(container(content).padding(2)) + .direction(scrollable::Direction::Vertical(inputs::compact_scrollbar())) + .style(inputs::scrollable_style) .id(scroll_id(state.kind)) .height(Length::Fill) .into() diff --git a/crates/bds-ui/src/views/duplicates.rs b/crates/bds-ui/src/views/duplicates.rs index 1c33390..5a13fd1 100644 --- a/crates/bds-ui/src/views/duplicates.rs +++ b/crates/bds-ui/src/views/duplicates.rs @@ -170,6 +170,8 @@ pub fn view(state: &DuplicatesState, locale: UiLocale) -> Element<'_, Message> { ); } scrollable(container(pairs).padding(2)) + .direction(scrollable::Direction::Vertical(inputs::compact_scrollbar())) + .style(inputs::scrollable_style) .height(Length::Fill) .into() }; diff --git a/crates/bds-ui/src/views/git.rs b/crates/bds-ui/src/views/git.rs index ff3170c..3bb74a0 100644 --- a/crates/bds-ui/src/views/git.rs +++ b/crates/bds-ui/src/views/git.rs @@ -458,11 +458,15 @@ pub fn diff_view( } else { sections.push(muted_text(t(locale, "git.noDiff"))); } - container(scrollable( - iced::widget::Column::with_children(sections) - .spacing(12) - .padding(16), - )) + container( + scrollable( + iced::widget::Column::with_children(sections) + .spacing(12) + .padding(16), + ) + .direction(scrollable::Direction::Vertical(inputs::compact_scrollbar())) + .style(inputs::scrollable_style), + ) .width(Length::Fill) .height(Length::Fill) .into() @@ -480,6 +484,8 @@ fn code_card(label: String, contents: String, wrapping: Wrapping) -> Element<'st .font(Font::MONOSPACE) .wrapping(wrapping) ) + .direction(scrollable::Direction::Vertical(inputs::compact_scrollbar())) + .style(inputs::scrollable_style) .height(Length::Fill), ] .spacing(8) diff --git a/crates/bds-ui/src/views/import_editor.rs b/crates/bds-ui/src/views/import_editor.rs index 21fbe4b..856a837 100644 --- a/crates/bds-ui/src/views/import_editor.rs +++ b/crates/bds-ui/src/views/import_editor.rs @@ -259,6 +259,8 @@ pub fn view<'a>(state: &'a ImportEditorState, locale: UiLocale) -> Element<'a, M .padding(16) .width(Length::Fill), ) + .direction(scrollable::Direction::Vertical(inputs::compact_scrollbar())) + .style(inputs::scrollable_style) .height(Length::Fill) .into() } diff --git a/crates/bds-ui/src/views/media_editor.rs b/crates/bds-ui/src/views/media_editor.rs index c7fda5e..906457b 100644 --- a/crates/bds-ui/src/views/media_editor.rs +++ b/crates/bds-ui/src/views/media_editor.rs @@ -540,7 +540,9 @@ pub fn view<'a>( .spacing(12) .padding(16) .width(Length::Fill), - ); + ) + .direction(scrollable::Direction::Vertical(inputs::compact_scrollbar())) + .style(inputs::scrollable_style); container(body) .width(Length::Fill) diff --git a/crates/bds-ui/src/views/menu_editor.rs b/crates/bds-ui/src/views/menu_editor.rs index 1b4f3a5..6a075f1 100644 --- a/crates/bds-ui/src/views/menu_editor.rs +++ b/crates/bds-ui/src/views/menu_editor.rs @@ -777,6 +777,8 @@ pub fn view(state: &MenuEditorState, locale: UiLocale) -> Element<'_, Message> { centered_status(locale, "menuEditor.empty") } else { scrollable(tree_view(state, locale)) + .direction(scrollable::Direction::Vertical(inputs::compact_scrollbar())) + .style(inputs::scrollable_style) .height(Length::Fill) .into() } diff --git a/crates/bds-ui/src/views/metadata_diff.rs b/crates/bds-ui/src/views/metadata_diff.rs index 1885ade..a11b508 100644 --- a/crates/bds-ui/src/views/metadata_diff.rs +++ b/crates/bds-ui/src/views/metadata_diff.rs @@ -136,11 +136,15 @@ pub fn view<'a>(state: &'a MetadataDiffState, locale: UiLocale) -> Element<'a, M content = content.push(message_card(t(locale, "metadataDiff.idle"))); } - container(scrollable(content)) - .width(Length::Fill) - .height(Length::Fill) - .padding(24) - .into() + container( + scrollable(content) + .direction(scrollable::Direction::Vertical(inputs::compact_scrollbar())) + .style(inputs::scrollable_style), + ) + .width(Length::Fill) + .height(Length::Fill) + .padding(24) + .into() } fn message_card(value: String) -> Element<'static, Message> { diff --git a/crates/bds-ui/src/views/modal.rs b/crates/bds-ui/src/views/modal.rs index 426d892..ff3aebb 100644 --- a/crates/bds-ui/src/views/modal.rs +++ b/crates/bds-ui/src/views/modal.rs @@ -1196,7 +1196,10 @@ pub fn view( let content = column![ title, Space::with_height(12.0), - scrollable(column(rows).spacing(8)).height(Length::Fixed(320.0)), + scrollable(column(rows).spacing(8)) + .direction(scrollable::Direction::Vertical(inputs::compact_scrollbar())) + .style(inputs::scrollable_style) + .height(Length::Fixed(320.0)), Space::with_height(16.0), buttons, ] diff --git a/crates/bds-ui/src/views/panel.rs b/crates/bds-ui/src/views/panel.rs index 9c13eb8..93e8ad0 100644 --- a/crates/bds-ui/src/views/panel.rs +++ b/crates/bds-ui/src/views/panel.rs @@ -256,6 +256,8 @@ pub fn view( .spacing(4) .padding(8), ) + .direction(scrollable::Direction::Vertical(inputs::compact_scrollbar())) + .style(inputs::scrollable_style) .into() } } @@ -285,6 +287,8 @@ pub fn view( .spacing(2) .padding(8), ) + .direction(scrollable::Direction::Vertical(inputs::compact_scrollbar())) + .style(inputs::scrollable_style) .into() } } @@ -347,6 +351,8 @@ pub fn view( .spacing(4) .padding(8), ) + .direction(scrollable::Direction::Vertical(inputs::compact_scrollbar())) + .style(inputs::scrollable_style) .into() } } @@ -388,6 +394,8 @@ pub fn view( .spacing(2) .padding(8), ) + .direction(scrollable::Direction::Vertical(inputs::compact_scrollbar())) + .style(inputs::scrollable_style) .into() } } diff --git a/crates/bds-ui/src/views/post_editor.rs b/crates/bds-ui/src/views/post_editor.rs index cb82cfb..1888beb 100644 --- a/crates/bds-ui/src/views/post_editor.rs +++ b/crates/bds-ui/src/views/post_editor.rs @@ -969,6 +969,8 @@ pub fn view<'a>( .spacing(8) .width(Length::Fill), ) + .direction(scrollable::Direction::Vertical(inputs::compact_scrollbar())) + .style(inputs::scrollable_style) .height(Length::Shrink); // ── Full layout: top pane (shrink), editor (fill), footer (shrink) ── diff --git a/crates/bds-ui/src/views/script_editor.rs b/crates/bds-ui/src/views/script_editor.rs index 26ed4de..a051702 100644 --- a/crates/bds-ui/src/views/script_editor.rs +++ b/crates/bds-ui/src/views/script_editor.rs @@ -260,6 +260,8 @@ pub fn view<'a>(state: &'a ScriptEditorState, locale: UiLocale) -> Element<'a, M .width(Length::Fill), ); let top_pane = scrollable(column![header, metadata].spacing(12).width(Length::Fill)) + .direction(scrollable::Direction::Vertical(inputs::compact_scrollbar())) + .style(inputs::scrollable_style) .height(Length::Shrink); // Full layout: top pane (shrink), content (fill), validation + footer (shrink) diff --git a/crates/bds-ui/src/views/settings_view.rs b/crates/bds-ui/src/views/settings_view.rs index 9bc5705..8b54c1a 100644 --- a/crates/bds-ui/src/views/settings_view.rs +++ b/crates/bds-ui/src/views/settings_view.rs @@ -421,6 +421,8 @@ pub fn view<'a>(state: &'a SettingsViewState, locale: UiLocale) -> Element<'a, M .padding(16) .width(Length::Fill), ) + .direction(scrollable::Direction::Vertical(inputs::compact_scrollbar())) + .style(inputs::scrollable_style) .width(Length::Fill) .height(Length::Fill); diff --git a/crates/bds-ui/src/views/sidebar.rs b/crates/bds-ui/src/views/sidebar.rs index 50fcf6f..ff47d1b 100644 --- a/crates/bds-ui/src/views/sidebar.rs +++ b/crates/bds-ui/src/views/sidebar.rs @@ -1254,11 +1254,15 @@ pub fn view( .padding(12); // layout.allium: sidebar width is resizable, passed as parameter - container(scrollable(content)) - .width(Length::Fixed(width)) - .height(Length::Fill) - .style(sidebar_style) - .into() + container( + scrollable(content) + .direction(scrollable::Direction::Vertical(inputs::compact_scrollbar())) + .style(inputs::scrollable_style), + ) + .width(Length::Fixed(width)) + .height(Length::Fill) + .style(sidebar_style) + .into() } #[cfg(test)] diff --git a/crates/bds-ui/src/views/site_validation.rs b/crates/bds-ui/src/views/site_validation.rs index 5493b51..799be12 100644 --- a/crates/bds-ui/src/views/site_validation.rs +++ b/crates/bds-ui/src/views/site_validation.rs @@ -131,15 +131,19 @@ pub fn view<'a>(state: &'a SiteValidationState, locale: UiLocale) -> Element<'a, } } - container(scrollable(content)) - .width(Length::Fill) - .height(Length::Fill) - .padding(24) - .style(|_theme: &Theme| container::Style { - background: Some(Background::Color(Color::from_rgb(0.11, 0.11, 0.14))), - ..container::Style::default() - }) - .into() + container( + scrollable(content) + .direction(scrollable::Direction::Vertical(inputs::compact_scrollbar())) + .style(inputs::scrollable_style), + ) + .width(Length::Fill) + .height(Length::Fill) + .padding(24) + .style(|_theme: &Theme| container::Style { + background: Some(Background::Color(Color::from_rgb(0.11, 0.11, 0.14))), + ..container::Style::default() + }) + .into() } fn help_text<'a>(value: String) -> Element<'a, Message> { diff --git a/crates/bds-ui/src/views/tags_view.rs b/crates/bds-ui/src/views/tags_view.rs index d898b23..c77f662 100644 --- a/crates/bds-ui/src/views/tags_view.rs +++ b/crates/bds-ui/src/views/tags_view.rs @@ -287,6 +287,8 @@ fn view_cloud<'a>(state: &'a TagsViewState, locale: UiLocale) -> Element<'a, Mes )] .padding(16), ) + .direction(scrollable::Direction::Vertical(inputs::compact_scrollbar())) + .style(inputs::scrollable_style) .width(Length::Fill) .height(Length::Fill) .into() @@ -457,6 +459,8 @@ fn view_manage<'a>(state: &'a TagsViewState, locale: UiLocale) -> Element<'a, Me .padding(16) .width(Length::Fill), ) + .direction(scrollable::Direction::Vertical(inputs::compact_scrollbar())) + .style(inputs::scrollable_style) .width(Length::Fill) .height(Length::Fill) .into() diff --git a/crates/bds-ui/src/views/template_editor.rs b/crates/bds-ui/src/views/template_editor.rs index ce57fef..3b8d985 100644 --- a/crates/bds-ui/src/views/template_editor.rs +++ b/crates/bds-ui/src/views/template_editor.rs @@ -224,6 +224,8 @@ pub fn view<'a>(state: &'a TemplateEditorState, locale: UiLocale) -> Element<'a, .width(Length::Fill), ); let top_pane = scrollable(column![header, metadata].spacing(12).width(Length::Fill)) + .direction(scrollable::Direction::Vertical(inputs::compact_scrollbar())) + .style(inputs::scrollable_style) .height(Length::Shrink); // Full layout: top pane (shrink), content (fill), validation + footer (shrink) diff --git a/crates/bds-ui/src/views/translation_validation.rs b/crates/bds-ui/src/views/translation_validation.rs index 19f59cf..5336a7c 100644 --- a/crates/bds-ui/src/views/translation_validation.rs +++ b/crates/bds-ui/src/views/translation_validation.rs @@ -59,11 +59,15 @@ pub fn view<'a>(state: &'a TranslationValidationState, locale: UiLocale) -> Elem content = content.push(message_card(t(locale, "translationValidation.idle"))); } - container(scrollable(content)) - .width(Length::Fill) - .height(Length::Fill) - .padding(24) - .into() + container( + scrollable(content) + .direction(scrollable::Direction::Vertical(inputs::compact_scrollbar())) + .style(inputs::scrollable_style), + ) + .width(Length::Fill) + .height(Length::Fill) + .padding(24) + .into() } fn issue_section<'a>(