/ libs / primitives / src / scroll_area.rs
scroll_area.rs
  1  //! Defines the [`ScrollArea`] component for creating scrollable areas with customizable scrollbars.
  2  
  3  use dioxus::prelude::*;
  4  
  5  /// The props for the [`ScrollArea`] component.
  6  #[derive(Props, Clone, PartialEq)]
  7  pub struct ScrollAreaProps {
  8      /// The scroll direction.
  9      #[props(default)]
 10      pub direction: ReadSignal<ScrollDirection>,
 11  
 12      /// Whether the scrollbars should be always visible.
 13      #[props(default)]
 14      pub always_show_scrollbars: ReadSignal<bool>,
 15  
 16      /// The scroll type.
 17      #[props(default)]
 18      pub scroll_type: ReadSignal<ScrollType>,
 19  
 20      /// Additional attributes to apply to the scroll area element.
 21      #[props(extends = GlobalAttributes)]
 22      pub attributes: Vec<Attribute>,
 23      /// The children of the scroll area component.
 24      pub children: Element,
 25  }
 26  
 27  /// The direction in which scrolling is allowed.
 28  #[derive(Clone, Copy, PartialEq, Default)]
 29  pub enum ScrollDirection {
 30      /// Allow vertical scrolling only.
 31      Vertical,
 32      /// Allow horizontal scrolling only.
 33      Horizontal,
 34      /// Allow scrolling in both directions.
 35      #[default]
 36      Both,
 37  }
 38  
 39  /// The type of scrolling behavior.
 40  #[derive(Clone, Copy, PartialEq, Default)]
 41  pub enum ScrollType {
 42      /// Browser default scrolling.
 43      #[default]
 44      Auto,
 45      /// Always show scrollbars.
 46      Always,
 47      /// Hide scrollbars but enable scrolling.
 48      Hidden,
 49  }
 50  
 51  /// # ScrollArea
 52  ///
 53  /// The `ScrollArea` component creates a scrollable area. If you don't
 54  /// have any focusable content within the scroll area, you should make the
 55  /// scroll area focusable by adding a `tabindex` attribute.
 56  ///
 57  /// ## Example
 58  ///
 59  /// ```rust
 60  /// use dioxus::prelude::*;
 61  /// use dioxus_primitives::scroll_area::{ScrollArea, ScrollDirection};
 62  /// #[component]
 63  /// fn Demo() -> Element {
 64  ///     rsx! {
 65  ///         ScrollArea {
 66  ///             width: "10em",
 67  ///             height: "10em",
 68  ///             direction: ScrollDirection::Vertical,
 69  ///             tabindex: "0",
 70  ///             div { class: "scroll-content",
 71  ///                 for i in 1..=20 {
 72  ///                     p {
 73  ///                         "Scrollable content item {i}"
 74  ///                     }
 75  ///                 }
 76  ///             }
 77  ///         }
 78  ///     }
 79  /// }
 80  /// ```
 81  ///
 82  /// ## Styling
 83  ///
 84  /// The [`ScrollArea`] component defines the following data attributes you can use to control styling:
 85  /// - `data-scroll-direction`: Indicates the scroll direction. Values are `vertical`, `horizontal`, or `both`.
 86  #[component]
 87  pub fn ScrollArea(props: ScrollAreaProps) -> Element {
 88      let direction = props.direction;
 89      let scroll_type = props.scroll_type;
 90      let always_show = props.always_show_scrollbars;
 91  
 92      let (overflow_x, overflow_y, scrollbar_width) = match scroll_type() {
 93          ScrollType::Auto => match direction() {
 94              ScrollDirection::Vertical => (Some("hidden"), Some("auto"), None),
 95              ScrollDirection::Horizontal => (Some("auto"), Some("hidden"), None),
 96              ScrollDirection::Both => (Some("auto"), Some("auto"), None),
 97          },
 98          ScrollType::Always => match direction() {
 99              ScrollDirection::Vertical => (Some("hidden"), Some("scroll"), None),
100              ScrollDirection::Horizontal => (Some("scroll"), Some("hidden"), None),
101              ScrollDirection::Both => (Some("scroll"), Some("scroll"), None),
102          },
103          ScrollType::Hidden => match direction() {
104              ScrollDirection::Vertical => (Some("hidden"), Some("scroll"), Some("none")),
105              ScrollDirection::Horizontal => (Some("scroll"), Some("hidden"), Some("none")),
106              ScrollDirection::Both => (Some("scroll"), Some("scroll"), Some("none")),
107          },
108      };
109  
110      let visibility_class = use_memo(move || {
111          if always_show() {
112              "scroll-area-always-show"
113          } else {
114              "scroll-area-auto-hide"
115          }
116      });
117  
118      rsx! {
119          div {
120              class: "{visibility_class}",
121              overflow_x,
122              overflow_y,
123              "scrollbar-width": scrollbar_width,
124              "data-scroll-direction": match direction() {
125                  ScrollDirection::Vertical => "vertical",
126                  ScrollDirection::Horizontal => "horizontal",
127                  ScrollDirection::Both => "both",
128              },
129              ..props.attributes,
130  
131              {props.children}
132          }
133      }
134  }