@@ -134,6 +134,11 @@ fun rememberScrollbarState(lazyListState: LazyListState): ScrollbarState =
134134 * [TrackClickBehavior.Seek] seeks to the click position.
135135 * Use [TrackClickBehavior.None] on touch-only platforms.
136136 * @param style Optional style override; defaults to [MacosTheme.componentStyling.scrollbar].
137+ * @param topInset Inset from the top edge before the thumb track starts.
138+ * Use this to keep the scrollbar below a fixed title bar or glass
139+ * overlay. Pass `contentPadding.calculateTopPadding()` from
140+ * [Scaffold][io.github.kdroidfilter.nucleus.ui.apple.macos.components.Scaffold]
141+ * for page-level scrollbars. Defaults to 0.dp.
137142 */
138143@Composable
139144fun VerticalScrollbar (
@@ -143,6 +148,7 @@ fun VerticalScrollbar(
143148 showOnEdgeHover : Boolean = true,
144149 trackClickBehavior : TrackClickBehavior = TrackClickBehavior .Jump ,
145150 style : ScrollbarStyle = MacosTheme .componentStyling.scrollbar,
151+ topInset : Dp = 0.dp,
146152) {
147153 ScrollbarImpl (
148154 state = state,
@@ -152,6 +158,7 @@ fun VerticalScrollbar(
152158 showOnEdgeHover = showOnEdgeHover,
153159 trackClickBehavior = trackClickBehavior,
154160 style = style,
161+ topInset = topInset,
155162 )
156163}
157164
@@ -200,6 +207,7 @@ private fun ScrollbarImpl(
200207 showOnEdgeHover : Boolean ,
201208 trackClickBehavior : TrackClickBehavior ,
202209 style : ScrollbarStyle ,
210+ topInset : Dp = 0.dp,
203211) {
204212 val metrics = style.metrics
205213 val colors = style.colors
@@ -361,8 +369,10 @@ private fun ScrollbarImpl(
361369 )
362370 },
363371 ) { measurables, constraints ->
364- // Track length: fill all available space on the scroll axis
365- val trackLength = if (isVertical) constraints.maxHeight else constraints.maxWidth
372+ // Track length: fill all available space on the scroll axis, minus top inset
373+ val totalLength = if (isVertical) constraints.maxHeight else constraints.maxWidth
374+ val topInsetPx = if (isVertical) topInset.roundToPx() else 0
375+ val trackLength = (totalLength - topInsetPx).coerceAtLeast(0 )
366376 trackSizePx = trackLength
367377
368378 val minPx = thumbMinLength.toPx()
@@ -386,15 +396,13 @@ private fun ScrollbarImpl(
386396 null
387397 }
388398
389- val layoutW = if (isVertical) trackBreadthPx else trackLength
390- val layoutH = if (isVertical) trackLength else trackBreadthPx
399+ val layoutW = if (isVertical) trackBreadthPx else totalLength
400+ val layoutH = if (isVertical) totalLength else trackBreadthPx
391401
392402 layout(layoutW, layoutH) {
393403 if (placeable != null ) {
394- val posAlong = thumbOff.roundToInt()
395- .coerceIn(0 , (trackLength - thumbLenPx).coerceAtLeast(0 ))
396- // Trailing edge stays trailingPad from the container edge;
397- // leading edge expands toward content on hover.
404+ val posAlong = (thumbOff.roundToInt() + topInsetPx)
405+ .coerceIn(topInsetPx, (totalLength - thumbLenPx).coerceAtLeast(topInsetPx))
398406 val posAcross = (trackBreadthPx - trailPadPx - thumbBreadthPx).coerceAtLeast(0 )
399407
400408 if (isVertical) placeable.place(posAcross, posAlong)
0 commit comments