Skip to content

Commit 64223a9

Browse files
[@mantine/core] ScrollArea: Add offsetScrollbars="present" option support (#7527)
* Update index.ts * add updateModal and updateContextModal * update storybook example * update formatting with prettier * open modal documentation * Update events.ts * Update events.ts * Update events.ts * Update Modals.demo.updateModal.tsx * update modal docs * wip context modal documentation * finish context modal documentation * Update Modals.demo.updateContextModal.tsx * formatting * implement offset scrollbars when present * Update ScrollArea.tsx * Add support for horizontal scrollbars * Update documentation * Update ScrollArea.module.css * Fix * Update ScrollArea.module.css * Update MdxTemplatesList.tsx --------- Co-authored-by: Vitaly Rtishchev <rtivital@gmail.com>
1 parent 24b66fa commit 64223a9

File tree

3 files changed

+64
-9
lines changed

3 files changed

+64
-9
lines changed

Diff for: apps/mantine.dev/src/pages/core/scroll-area.mdx

+5-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,11 @@ export default Layout(MDX_DATA.ScrollArea);
1414
- `auto` – similar to `overflow: auto` – scrollbars are always visible when the content is overflowing
1515
- `always` – same as `auto`, but scrollbars are always visible regardless of whether the content is overflowing
1616
- `never` – scrollbars are always hidden
17-
- `offsetScrollbars` – offset scrollbars with padding
17+
- `offsetScrollbars` – adds padding to offset scrollbars with the following options:
18+
- `x` – adds padding to offset horizontal scrollbar only
19+
- `y` – adds padding to offset vertical scrollbar only
20+
- `xy` – adds padding to offset both scrollbars
21+
- `present` – adds padding only when scrollbars are visible
1822
- `scrollbarSize` – scrollbar size, controls scrollbar and thumb width/height
1923
- `scrollHideDelay` – delay in ms to hide scrollbars, applicable only when type is `hover` or `scroll`
2024
- `overscrollBehavior` – controls [overscroll-behavior](https://developer.mozilla.org/en-US/docs/Web/CSS/overscroll-behavior) of the viewport

Diff for: packages/@mantine/core/src/components/ScrollArea/ScrollArea.module.css

+26-5
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,36 @@
1818
}
1919

2020
&:where([data-scrollbars='xy'], [data-scrollbars='y']) {
21-
&:where([data-offset-scrollbars='xy'], [data-offset-scrollbars='y']) {
22-
padding-inline-end: var(--scrollarea-scrollbar-size);
23-
padding-inline-start: unset;
21+
&:where(
22+
[data-offset-scrollbars='xy'],
23+
[data-offset-scrollbars='y'],
24+
[data-offset-scrollbars='present']
25+
) {
26+
&:where([data-vertical-hidden]) {
27+
padding-inline-end: 0;
28+
padding-inline-start: 0;
29+
}
30+
31+
&:not([data-vertical-hidden]) {
32+
padding-inline-end: var(--scrollarea-scrollbar-size);
33+
padding-inline-start: unset;
34+
}
2435
}
2536
}
2637

2738
&:where([data-scrollbars='xy'], [data-scrollbars='x']) {
28-
&:where([data-offset-scrollbars='xy'], [data-offset-scrollbars='x']) {
29-
padding-bottom: var(--scrollarea-scrollbar-size);
39+
&:where(
40+
[data-offset-scrollbars='xy'],
41+
[data-offset-scrollbars='x'],
42+
[data-offset-scrollbars='present']
43+
) {
44+
&:where([data-horizontal-hidden]) {
45+
padding-bottom: 0;
46+
}
47+
48+
&:not([data-horizontal-hidden]) {
49+
padding-bottom: var(--scrollarea-scrollbar-size);
50+
}
3051
}
3152
}
3253
}

Diff for: packages/@mantine/core/src/components/ScrollArea/ScrollArea.tsx

+33-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { useState } from 'react';
1+
import { useEffect, useRef, useState } from 'react';
2+
import { useMergeRefs } from '@floating-ui/react';
23
import {
34
Box,
45
BoxProps,
@@ -47,7 +48,7 @@ export interface ScrollAreaProps
4748
scrollbars?: 'x' | 'y' | 'xy' | false;
4849

4950
/** Determines whether scrollbars should be offset with padding on given axis, `false` by default */
50-
offsetScrollbars?: boolean | 'x' | 'y';
51+
offsetScrollbars?: boolean | 'x' | 'y' | 'present';
5152

5253
/** Assigns viewport element (scrollable container) ref */
5354
viewportRef?: React.ForwardedRef<HTMLDivElement>;
@@ -120,6 +121,8 @@ export const ScrollArea = factory<ScrollAreaFactory>((_props, ref) => {
120121
} = props;
121122

122123
const [scrollbarHovered, setScrollbarHovered] = useState(false);
124+
const [verticalThumbVisible, setVerticalThumbVisible] = useState(false);
125+
const [horizontalThumbVisible, setHorizontalThumbVisible] = useState(false);
123126

124127
const getStyles = useStyles<ScrollAreaFactory>({
125128
name: 'ScrollArea',
@@ -134,6 +137,27 @@ export const ScrollArea = factory<ScrollAreaFactory>((_props, ref) => {
134137
varsResolver,
135138
});
136139

140+
const localViewportRef = useRef<HTMLDivElement>(null);
141+
const combinedViewportRef = useMergeRefs([viewportRef, localViewportRef]);
142+
143+
useEffect(() => {
144+
if (!localViewportRef.current) {
145+
return;
146+
}
147+
148+
const element = localViewportRef.current;
149+
150+
const observer = new ResizeObserver(() => {
151+
const { scrollHeight, clientHeight, scrollWidth, clientWidth } = element;
152+
setVerticalThumbVisible(scrollHeight > clientHeight);
153+
setHorizontalThumbVisible(scrollWidth > clientWidth);
154+
});
155+
156+
observer.observe(element);
157+
158+
return () => observer.disconnect();
159+
}, [localViewportRef, offsetScrollbars]);
160+
137161
return (
138162
<ScrollAreaRoot
139163
type={type === 'never' ? 'always' : type}
@@ -146,9 +170,15 @@ export const ScrollArea = factory<ScrollAreaFactory>((_props, ref) => {
146170
<ScrollAreaViewport
147171
{...viewportProps}
148172
{...getStyles('viewport', { style: viewportProps?.style })}
149-
ref={viewportRef}
173+
ref={combinedViewportRef}
150174
data-offset-scrollbars={offsetScrollbars === true ? 'xy' : offsetScrollbars || undefined}
151175
data-scrollbars={scrollbars || undefined}
176+
data-horizontal-hidden={
177+
offsetScrollbars === 'present' && !horizontalThumbVisible ? 'true' : undefined
178+
}
179+
data-vertical-hidden={
180+
offsetScrollbars === 'present' && !verticalThumbVisible ? 'true' : undefined
181+
}
152182
onScroll={(e) => {
153183
viewportProps?.onScroll?.(e);
154184
onScrollPositionChange?.({ x: e.currentTarget.scrollLeft, y: e.currentTarget.scrollTop });

0 commit comments

Comments
 (0)