1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
use *;
use ;
use component_doc;
use inject_style;
use PopoverPanel;
use popover_styles;
use ;
use crateoverlay_surface_class;
use cratePopoverTrigger;
/// `Popover` anchors a floating panel to a trigger — detail cards, compact pickers, or
/// on-demand content. Put the anchor in [`PopoverTrigger`]; use `trigger_type=Click` when
/// the panel has inputs or must stay open. Tune `size`, `position`, and `appearance`; hook
/// `lifecycle` to load data on first open.
///
/// # When to use
///
/// - Detail cards, compact pickers, or rich content on demand
/// - Panels that should not block the whole page — unlike [`Dialog`](crate::Dialog)
/// - Brief hover hints — use [`Tooltip`](crate::Tooltip) instead
///
/// # Overlay surfaces
///
/// - **Brief non-interactive hint** — [`Tooltip`](crate::Tooltip)
/// - **Floating panel with content or inputs** — `Popover` (this component)
/// - **List of actions from a trigger** — [`Menu`](crate::Menu) or [`MenuButton`](crate::MenuButton)
/// - **Block the page or trap focus** — [`Dialog`](crate::Dialog)
///
/// # Usage
///
/// 1. Put the anchor in [`PopoverTrigger`] (often [`crate::Button`]).
/// 2. Set `trigger_type`: `Hover` (default) or `Click` when the panel has inputs or must stay open.
/// 3. Optional: `appearance`, `size`, `position`, `lifecycle` for lazy-load on first open.
/// 4. Render panel body as remaining children.
///
/// ```rust
/// use crate::Button;
/// view! {
/// <Popover trigger_type=PopoverTriggerType::Click>
/// <PopoverTrigger slot>
/// <Button>"Details"</Button>
/// </PopoverTrigger>
/// <p>"Panel content"</p>
/// </Popover>
/// }
/// ```
///
/// # Best Practices
///
/// ## Do's
///
/// * Use `Click` when the panel contains inputs or must stay open * Use `on_open` / `on_close` to lazy-load panel data * Match `size` to content density (`Small` for hints, `Large` for forms)
///
/// ## Don'ts
///
/// * Do not rely on a popover as the only place for irreversible actions — use [`Dialog`](crate::Dialog) * Do not nest popovers deeply — use a dialog for multi-step flows
///
/// # Examples
///
/// ## Click popover
/// Explicit click opens a floating panel—required when the panel contains inputs, links, or must stay open while the user interacts.
/// <!-- preview -->
/// ```rust
/// use crate::Button;
/// view! {
/// <div data-testid="popover-preview">
/// <Popover trigger_type=PopoverTriggerType::Click>
/// <PopoverTrigger slot>
/// <Button>"Open popover"</Button>
/// </PopoverTrigger>
/// <div data-testid="popover-content">"Popover body content"</div>
/// </Popover>
/// </div>
/// }
/// ```
///
/// ## Hover popover (default trigger)
/// PopoverTrigger defaults to hover for lightweight supplementary content shown on pointer enter. Use for read-only detail cards, not interactive forms.
/// <!-- preview -->
/// ```rust
/// use crate::{Button, ButtonAppearance};
/// view! {
/// <div data-testid="popover-hover">
/// <Popover>
/// <PopoverTrigger slot>
/// <Button appearance=ButtonAppearance::Subtle>"Hover"</Button>
/// </PopoverTrigger>
/// <span data-testid="popover-hover-body">"Details on hover"</span>
/// </Popover>
/// </div>
/// }
/// ```
///
/// ## Brand appearance
/// Brand-colored popover surface aligned with primary theme tokens for contextual panels in branded chrome.
/// <!-- preview -->
/// ```rust
/// use crate::Button;
/// view! {
/// <div data-testid="popover-brand">
/// <Popover appearance=PopoverAppearance::Brand trigger_type=PopoverTriggerType::Click>
/// <PopoverTrigger slot>
/// <Button>"Brand popover"</Button>
/// </PopoverTrigger>
/// <div data-testid="popover-brand-body">"Branded surface"</div>
/// </Popover>
/// </div>
/// }
/// ```
///
/// ## Inverted appearance
/// Dark inverted panel for anchors on dark surfaces or high-contrast overlays.
/// <!-- preview -->
/// ```rust
/// use crate::Button;
/// view! {
/// <div data-testid="popover-inverted">
/// <Popover appearance=PopoverAppearance::Inverted trigger_type=PopoverTriggerType::Click>
/// <PopoverTrigger slot>
/// <Button>"Inverted"</Button>
/// </PopoverTrigger>
/// <div data-testid="popover-inverted-body">"Dark surface"</div>
/// </Popover>
/// </div>
/// }
/// ```
///
/// ## Small size
/// Compact width for short hints or single-line pickers.
/// <!-- preview -->
/// ```rust
/// use crate::Button;
/// view! {
/// <div data-testid="popover-small">
/// <Popover size=PopoverSize::Small trigger_type=PopoverTriggerType::Click>
/// <PopoverTrigger slot>
/// <Button>"Small"</Button>
/// </PopoverTrigger>
/// <span>"Compact panel"</span>
/// </Popover>
/// </div>
/// }
/// ```
///
/// ## Large size
/// Roomier panel for mini-forms or short lists without upgrading to a modal dialog.
/// <!-- preview -->
/// ```rust
/// use crate::Button;
/// view! {
/// <div data-testid="popover-large">
/// <Popover size=PopoverSize::Large trigger_type=PopoverTriggerType::Click>
/// <PopoverTrigger slot>
/// <Button>"Large"</Button>
/// </PopoverTrigger>
/// <div style="padding: 8px;">"Roomier panel"</div>
/// </Popover>
/// </div>
/// }
/// ```
///
/// ## Position (left)
/// Opens beside the trigger when vertical space is constrained.
/// <!-- preview -->
/// ```rust
/// use crate::Button;
/// view! {
/// <div data-testid="popover-left">
/// <Popover position=PopoverPosition::Left trigger_type=PopoverTriggerType::Click>
/// <PopoverTrigger slot>
/// <Button>"Left"</Button>
/// </PopoverTrigger>
/// <span>"Opens to the left"</span>
/// </Popover>
/// </div>
/// }
/// ```
///
/// ## Lifecycle hooks
/// Use `on_open` and `on_close` to lazy-load panel data or record analytics when visibility changes.
/// <!-- preview -->
/// ```rust
/// use orbital_base_components::Handler;
/// use crate::{Button, PopoverLifecycle, PopoverTriggerType};
///
/// let opened = RwSignal::new(false);
/// view! {
/// <div data-testid="popover-lifecycle">
/// <Popover
/// trigger_type=PopoverTriggerType::Click
/// lifecycle=PopoverLifecycle {
/// on_open: Some(Handler::new(move || opened.set(true))),
/// on_close: Some(Handler::new(move || opened.set(false))),
/// }
/// >
/// <PopoverTrigger slot>
/// <Button>"Lifecycle"</Button>
/// </PopoverTrigger>
/// <span data-opened=move || opened.get()>"Panel body"</span>
/// </Popover>
/// </div>
/// }
/// ```
///
/// ## Inner scroll area
/// Popover trigger inside a bounded [`ScrollArea`](crate::ScrollArea). Scrolling the scrollport after the panel opens should keep the teleported surface aligned with the trigger.
/// <!-- preview -->
/// ```rust
/// use crate::{Button, PopoverTriggerType, ScrollArea};
///
/// const FRAME: &str = "display: block; width: 100%; height: 280px; box-sizing: border-box; border: 1px solid var(--orb-color-border-default); padding: 12px; background: var(--orb-color-surface-subtle);";
/// view! {
/// <div data-testid="popover-scroll-area" style="width: 100%; max-width: 560px;">
/// <ScrollArea scroll_testid="popover-scroll-area-scrollport" style=FRAME>
/// <div style="display: flex; flex-direction: column; gap: 12px; min-height: 720px;">
/// {(0..10)
/// .map(|i| view! { <p style="margin: 0;">{format!("Filler line {i}")}</p> })
/// .collect_view()}
/// <Popover trigger_type=PopoverTriggerType::Click>
/// <PopoverTrigger slot>
/// <Button>"Open in scroll area"</Button>
/// </PopoverTrigger>
/// <div data-testid="popover-scroll-area-content">"Anchored panel"</div>
/// </Popover>
/// {(0..10)
/// .map(|i| view! { <p style="margin: 0;">{format!("Trailing line {i}")}</p> })
/// .collect_view()}
/// </div>
/// </ScrollArea>
/// </div>
/// }
/// ```