Skip to main content

freya_components/
sidebar.rs

1use freya_core::prelude::*;
2use torin::{
3    gaps::Gaps,
4    size::Size,
5};
6
7use crate::{
8    activable_context::use_is_active,
9    define_theme,
10    get_theme,
11};
12
13define_theme! {
14    %[component]
15    pub SideBarItem {
16        %[fields]
17        color: Color,
18        background: Color,
19        hover_background: Color,
20        active_background: Color,
21        focus_border_fill: Color,
22        corner_radius: CornerRadius,
23        margin: Gaps,
24        padding: Gaps,
25    }
26}
27
28#[derive(Debug, Default, PartialEq, Clone, Copy)]
29pub enum SideBarItemStatus {
30    /// Default state.
31    #[default]
32    Idle,
33    /// User is hovering the sidebar item.
34    Hovering,
35}
36
37/// Button designed for sidebars.
38///
39/// # Example
40///
41/// ```rust
42/// # use freya::prelude::*;
43/// fn app() -> impl IntoElement {
44///     rect()
45///         .horizontal()
46///         .child(
47///             rect()
48///                 .theme_background()
49///                 .padding(8.)
50///                 .width(Size::px(150.))
51///                 .height(Size::fill())
52///                 .child(SideBarItem::new().child("Home"))
53///                 .child(SideBarItem::new().child("Settings")),
54///         )
55///         .child(rect().expanded().center().child("Main content"))
56/// }
57/// ```
58///
59/// See the [interactive components demo](https://freyaui.dev/demo).
60
61#[derive(Clone, PartialEq)]
62pub struct SideBarItem {
63    /// Theme override.
64    pub(crate) theme: Option<SideBarItemThemePartial>,
65    /// Inner child for the [SideBarItem].
66    children: Vec<Element>,
67    /// Optionally handle the `on_press` event in the [SideBarItem].
68    on_press: Option<EventHandler<Event<PressEventData>>>,
69    /// Optionally specify a custom `overflow` attribute for this component. Defaults to [OverflowMode::Clip].
70    overflow: Overflow,
71    key: DiffKey,
72}
73
74impl KeyExt for SideBarItem {
75    fn key_mut(&mut self) -> &mut DiffKey {
76        &mut self.key
77    }
78}
79
80impl Default for SideBarItem {
81    fn default() -> Self {
82        Self::new()
83    }
84}
85
86impl ChildrenExt for SideBarItem {
87    fn children_mut(&mut self) -> &mut Vec<Element> {
88        &mut self.children
89    }
90}
91
92impl SideBarItem {
93    pub fn new() -> Self {
94        Self {
95            theme: None,
96            children: Vec::new(),
97            on_press: None,
98            overflow: Overflow::Clip,
99            key: DiffKey::None,
100        }
101    }
102
103    pub fn on_press(mut self, on_press: impl Into<EventHandler<Event<PressEventData>>>) -> Self {
104        self.on_press = Some(on_press.into());
105        self
106    }
107
108    pub fn overflow(mut self, overflow: impl Into<Overflow>) -> Self {
109        self.overflow = overflow.into();
110        self
111    }
112
113    /// Get the theme override for this component.
114    pub fn get_theme(&self) -> Option<&SideBarItemThemePartial> {
115        self.theme.as_ref()
116    }
117
118    /// Set a theme override for this component.
119    pub fn theme(mut self, theme: SideBarItemThemePartial) -> Self {
120        self.theme = Some(theme);
121        self
122    }
123}
124
125impl Component for SideBarItem {
126    fn render(&self) -> impl IntoElement {
127        let SideBarItemTheme {
128            margin,
129            hover_background,
130            active_background,
131            background,
132            focus_border_fill,
133            corner_radius,
134            padding,
135            color,
136        } = get_theme!(&self.theme, SideBarItemThemePreference, "sidebar_item");
137        let mut status = use_state(SideBarItemStatus::default);
138        let is_active = use_is_active();
139        let a11y_id = use_a11y();
140        let focus = use_focus(a11y_id);
141
142        let on_pointer_enter = move |_| {
143            status.set(SideBarItemStatus::Hovering);
144        };
145
146        let on_pointer_leave = move |_| {
147            status.set(SideBarItemStatus::default());
148        };
149
150        let background = match *status.read() {
151            _ if is_active => active_background,
152            SideBarItemStatus::Hovering => hover_background,
153            SideBarItemStatus::Idle => background,
154        };
155
156        let border = (focus() == Focus::Keyboard).then(|| {
157            Border::new()
158                .fill(focus_border_fill)
159                .width(2.)
160                .alignment(BorderAlignment::Inner)
161        });
162
163        let on_press = self.on_press.clone();
164        rect()
165            .a11y_id(a11y_id)
166            .a11y_focusable(true)
167            .a11y_role(AccessibilityRole::Link)
168            .on_press(move |e: Event<PressEventData>| {
169                if let Some(handler) = &on_press {
170                    handler.call(e);
171                }
172            })
173            .on_pointer_enter(on_pointer_enter)
174            .on_pointer_leave(on_pointer_leave)
175            .overflow(self.overflow)
176            .width(Size::fill())
177            .margin(margin)
178            .padding(padding)
179            .color(color)
180            .background(background)
181            .corner_radius(corner_radius)
182            .map(border, |rect, border| rect.border(border))
183            .children(self.children.clone())
184    }
185
186    fn render_key(&self) -> DiffKey {
187        self.key.clone().or(self.default_key())
188    }
189}