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
use *;
use BaseAnchor;
use component_doc;
use inject_style;
use anchor_styles;
use AnchorConfig;
/// In-page section rail — a scroll-spy table of contents beside long content.
///
/// Add [`AnchorLink`](crate::AnchorLink) children with `href="#section-id"` matching element ids on the page. The active link updates as the user scrolls; clicking a link scrolls the target into view. Set `config.offset_target` when scrolling a bounded container instead of the document. For cross-route navigation use [`Link`](crate::Link) or the router — not `Anchor`.
///
/// # When to use
///
/// - Long documentation or settings pages with in-page section navigation - Table-of-contents rails beside scrollable content - Nested section hierarchies via nested [`AnchorLink`](crate::AnchorLink) children
///
/// # Usage
///
/// 1. Render [`AnchorLink`](crate::AnchorLink) children with `href="#section-id"` matching element ids. 2. Place target sections in the page with matching `id` attributes. 3. Set `config.offset_target` when scrolling a bounded container instead of the document.
///
/// # Best Practices
///
/// ## Do's
///
/// * Keep link titles short — they truncate with ellipsis * Use nested links for subsection hierarchies
///
/// ## Don'ts
///
/// * Do not use Anchor for cross-route navigation — prefer [`Link`](crate::Link) or the router
///
/// # Examples
///
/// ## Basic rail
/// Side rail with two section links beside scrollable content.
/// <!-- preview -->
/// ```rust
/// use crate::{Anchor, AnchorLink};
/// view! {
/// <div data-testid="anchor-preview" style="display: flex; gap: 24px; width: 100%; max-width: 560px;">
/// <Anchor>
/// <AnchorLink title="Intro".to_string() href="#intro" />
/// <AnchorLink title="Details".to_string() href="#details" />
/// </Anchor>
/// <div style="height: 240px; overflow: auto; flex: 1;">
/// <h3 id="intro" data-testid="anchor-link-1">"Intro"</h3>
/// <p style="height: 120px">"Intro section content."</p>
/// <h3 id="details" data-testid="anchor-link-2">"Details"</h3>
/// <p data-testid="anchor-link-3">"Details section content."</p>
/// </div>
/// </div>
/// }
/// ```
///
/// ## Nested links
/// Subsection links nest under a parent [`AnchorLink`](crate::AnchorLink).
/// <!-- preview -->
/// ```rust
/// use crate::{Anchor, AnchorLink};
/// view! {
/// <div data-testid="anchor-nested">
/// <Anchor>
/// <AnchorLink title="Overview".to_string() href="#overview" />
/// <AnchorLink title="API".to_string() href="#api">
/// <AnchorLink title="REST".to_string() href="#rest" />
/// </AnchorLink>
/// </Anchor>
/// </div>
/// }
/// ```
///
/// ## Custom scroll container
/// `offset_target` scopes scroll-spy calculations to a bounded scrollport.
/// <!-- preview -->
/// ```rust
/// use crate::{Anchor, AnchorConfig, AnchorLink};
/// view! {
/// <div data-testid="anchor-offset" style="display: flex; gap: 16px; width: 100%; max-width: 560px;">
/// <Anchor config=AnchorConfig { offset_target: Some("#anchor-scroll".into()), ..Default::default() }>
/// <AnchorLink title="Top".to_string() href="#top" />
/// <AnchorLink title="Bottom".to_string() href="#bottom" />
/// </Anchor>
/// <div id="anchor-scroll" style="height: 200px; overflow: auto; flex: 1;">
/// <h4 id="top">"Top"</h4>
/// <p style="height: 180px">"…"</p>
/// <h4 id="bottom">"Bottom"</h4>
/// </div>
/// </div>
/// }
/// ```
///
/// ## Active on scroll
/// Scrolling the content region updates the active link class on the rail.
/// <!-- preview -->
/// ```rust
/// use crate::{Anchor, AnchorConfig, AnchorLink};
/// view! {
/// <div data-testid="anchor-active" style="display: flex; gap: 16px; width: 100%; max-width: 560px;">
/// <Anchor config=AnchorConfig { offset_target: Some("#anchor-active-scroll".into()), ..Default::default() }>
/// <AnchorLink title="First".to_string() href="#first" />
/// <AnchorLink title="Second".to_string() href="#second" />
/// </Anchor>
/// <div id="anchor-active-scroll" data-testid="anchor-active-scroll" style="height: 180px; overflow: auto; flex: 1;">
/// <h4 id="first">"First"</h4>
/// <p style="height: 160px">"…"</p>
/// <h4 id="second">"Second"</h4>
/// <p style="height: 80px">"…"</p>
/// </div>
/// </div>
/// }
/// ```
///
/// ## Layout composition
/// Rail sits beside a multi-section article layout.
/// <!-- preview -->
/// ```rust
/// use crate::{Anchor, AnchorLink};
/// view! {
/// <div data-testid="anchor-layout" style="display: flex; gap: 24px; width: 100%; max-width: 640px;">
/// <Anchor>
/// <AnchorLink title="Setup".to_string() href="#setup" />
/// <AnchorLink title="Deploy".to_string() href="#deploy" />
/// </Anchor>
/// <article style="flex: 1;">
/// <section id="setup"><h3>"Setup"</h3><p>"Install and configure."</p></section>
/// <section id="deploy"><h3>"Deploy"</h3><p>"Ship to production."</p></section>
/// </article>
/// </div>
/// }
/// ```
///
/// ## Theme tokens
/// Active rail bar uses brand background tokens from the Orbital theme.
/// <!-- preview -->
/// ```rust
/// use crate::{Anchor, AnchorLink};
/// view! {
/// <div data-testid="anchor-theme" style="display: flex; gap: 16px; width: 100%; max-width: 560px;">
/// <Anchor>
/// <AnchorLink title="Theme".to_string() href="#theme-section" />
/// </Anchor>
/// <div>
/// <h4 id="theme-section" data-testid="anchor-theme-target">"Theme section"</h4>
/// </div>
/// </div>
/// }
/// ```
]
config: AnchorConfig,
/// Extra CSS class names merged onto the anchor rail root.
class: ,
/// [`AnchorLink`] children — one per in-page section target.
children: Children,
)