reratui_hooks/area/mod.rs
1//! Component Area Hook - Access to component's render area
2//!
3//! This module provides a professional context-based API for accessing the current
4//! component's render area (Rect). The renderer provides the area via context,
5//! and components can consume it using `use_area()`.
6//!
7//! # Architecture
8//!
9//! - Renderer provides area via `use_context_provider`
10//! - Components consume area via `use_area()` (which calls `use_context`)
11//! - Clean separation of concerns following SOLID principles
12//!
13//! # Use Cases
14//!
15//! - Layout calculations
16//! - Responsive design decisions
17//! - Scroll calculations
18//! - Mouse event handling within component bounds
19
20use crate::context::use_context;
21use ratatui::layout::Rect;
22use std::ops::Deref;
23
24/// Context type for component render area
25///
26/// This is provided by the renderer and consumed by components via `use_area()`.
27///
28/// Implements `Deref<Target = Rect>` so you can access Rect methods directly:
29///
30/// ```rust,ignore
31/// use reratui::prelude::*;
32/// use ratatui::layout::Margin;
33///
34/// let area = use_area();
35/// let width = area.width; // Direct access to Rect fields
36/// let height = area.height;
37/// let inner = area.inner(&Margin::new(1, 1)); // Call Rect methods
38/// ```
39#[derive(Clone, Copy, Debug, PartialEq, Eq)]
40pub struct ComponentArea(pub Rect);
41
42impl Deref for ComponentArea {
43 type Target = Rect;
44
45 fn deref(&self) -> &Self::Target {
46 &self.0
47 }
48}
49
50/// Hook to access the current component's render area
51///
52/// This hook retrieves the component's render area from the context provided
53/// by the renderer. The area represents the Rect that the component is being
54/// rendered into.
55///
56/// # Panics
57///
58/// Panics if called outside of a component render context where the area
59/// has been provided by the renderer.
60///
61/// # Use Cases
62///
63/// - **Layout calculations**: Determine available space for child components
64/// - **Responsive design**: Adjust rendering based on available space
65/// - **Positioning**: Calculate absolute positions within the component
66/// - **Scroll handling**: Track viewport dimensions for scrollable content
67/// - **Mouse events**: Determine if mouse events are within component bounds
68///
69/// # Examples
70///
71/// ## Basic Usage - Direct Field Access
72///
73/// ```rust,ignore
74/// use reratui::prelude::*;
75/// #[component]
76/// fn MyComponent() -> Element {
77/// let area = use_area();
78///
79/// // Direct access to Rect fields via Deref
80/// println!("Width: {}, Height: {}", area.width, area.height);
81/// println!("Position: ({}, {})", area.x, area.y);
82///
83/// rsx! {
84/// <Block title="My Component">
85/// <Paragraph>
86/// {format!("Size: {}x{}", area.width, area.height)}
87/// </Paragraph>
88/// </Block>
89/// }
90/// }
91/// ```
92///
93/// ## Responsive Layout
94///
95/// ```rust,ignore
96/// use reratui::prelude::*;
97/// #[component]
98/// fn AdaptiveLayout() -> Element {
99/// let area = use_area();
100///
101/// // Direct field access - no .0 needed!
102/// let layout_mode = if area.width < 80 {
103/// "compact"
104/// } else {
105/// "wide"
106/// };
107///
108/// rsx! {
109/// <Block title={format!("Layout: {}", layout_mode)}>
110/// <Paragraph>
111/// {format!("Adaptive content")}
112/// </Paragraph>
113/// </Block>
114/// }
115/// }
116/// ```
117///
118/// ## Scroll Viewport
119///
120/// ```rust,ignore
121/// use reratui::prelude::*;
122/// #[component]
123/// fn ScrollableList() -> Element {
124/// let area = use_area();
125/// let (scroll_offset, set_scroll) = use_state(|| 0u16);
126///
127/// // Calculate visible items based on area height
128/// let visible_count = area.height.saturating_sub(2);
129///
130/// rsx! {
131/// <Block title="Scrollable List">
132/// <Paragraph>
133/// {format!("Showing {} items", visible_count)}
134/// </Paragraph>
135/// </Block>
136/// }
137/// }
138/// ```
139///
140/// ## Using Rect Methods
141///
142/// ```rust,ignore
143/// use reratui::prelude::*;
144/// use ratatui::layout::Margin;
145/// #[component]
146/// fn LayoutComponent() -> Element {
147/// let area = use_area();
148///
149/// // Call Rect methods directly
150/// let inner = area.inner(&Margin::new(1, 1));
151/// let is_empty = area.is_empty();
152/// let area_size = area.area();
153///
154/// rsx! {
155/// <Block title="Layout">
156/// <Paragraph>
157/// {format!("Inner: {}x{}", inner.width, inner.height)}
158/// </Paragraph>
159/// </Block>
160/// }
161/// }
162/// ```
163pub fn use_area() -> ComponentArea {
164 use_context::<ComponentArea>()
165}
166
167#[cfg(test)]
168mod tests {
169 use super::*;
170 use crate::context::use_context_provider;
171 use crate::test_utils::with_component_id;
172 use ratatui::layout::Rect;
173
174 #[test]
175 fn test_use_area_with_context() {
176 // Simulate renderer providing area context
177 with_component_id("RendererComponent", |_ctx| {
178 let test_rect = Rect::new(10, 20, 100, 50);
179 let _area = use_context_provider(|| ComponentArea(test_rect));
180
181 // Simulate child component consuming area
182 with_component_id("ChildComponent", |_ctx| {
183 let area = use_area();
184
185 // Direct field access via Deref
186 assert_eq!(*area, test_rect);
187 assert_eq!(area.width, 100);
188 assert_eq!(area.height, 50);
189 assert_eq!(area.x, 10);
190 assert_eq!(area.y, 20);
191 });
192 });
193 }
194
195 #[test]
196 fn test_use_area_responsive_layout() {
197 // Test responsive behavior with different sizes
198 with_component_id("ResponsiveComponent", |_ctx| {
199 // Small screen
200 let small_rect = Rect::new(0, 0, 60, 20);
201 let _area = use_context_provider(|| ComponentArea(small_rect));
202
203 with_component_id("ChildComponent", |_ctx| {
204 let area = use_area();
205 // Direct field access - no .0 needed!
206 let is_compact = area.width < 80;
207 assert!(is_compact);
208 });
209 });
210
211 with_component_id("ResponsiveComponent", |_ctx| {
212 // Large screen
213 let large_rect = Rect::new(0, 0, 120, 40);
214 let _area = use_context_provider(|| ComponentArea(large_rect));
215
216 with_component_id("ChildComponent", |_ctx| {
217 let area = use_area();
218 // Direct field access
219 let is_compact = area.width < 80;
220 assert!(!is_compact);
221 });
222 });
223 }
224
225 #[test]
226 fn test_use_area_deref() {
227 // Test that Deref works correctly
228 with_component_id("DerefTestComponent", |_ctx| {
229 let test_rect = Rect::new(5, 10, 80, 24);
230 let _area = use_context_provider(|| ComponentArea(test_rect));
231
232 with_component_id("ChildComponent", |_ctx| {
233 let area = use_area();
234
235 // Test direct field access
236 assert_eq!(area.width, 80);
237 assert_eq!(area.height, 24);
238
239 // Test calling Rect methods
240 let area_size = area.area();
241 assert_eq!(area_size, 80 * 24);
242
243 let is_empty = area.is_empty();
244 assert!(!is_empty);
245 });
246 });
247 }
248
249 #[test]
250 #[should_panic(expected = "Context value for type")]
251 fn test_use_area_without_context_panics() {
252 // Calling use_area without context should panic
253 with_component_id("ComponentWithoutContext", |_ctx| {
254 let _area = use_area();
255 });
256 }
257}