Skip to content

Commit 98c2a16

Browse files
committed
fix(LayoutContent): briefly show scrollbar when scroll event occures
1 parent 47102ec commit 98c2a16

File tree

4 files changed

+57
-12
lines changed

4 files changed

+57
-12
lines changed

src/components/content/Layout/Layout.stories.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -445,7 +445,7 @@ export const CompleteApplicationShell: Story = {
445445
title="Navigation"
446446
onClose={() => setSidebarOpen(false)}
447447
/>
448-
<Layout.Content padding="1x" scrollbar="tiny" gap=".5x">
448+
<Layout.Content padding=".5x" scrollbar="tiny" gap="1bw">
449449
{['Dashboard', 'Analytics', 'Reports', 'Users', 'Settings'].map(
450450
(item) => (
451451
<ItemButton key={item} type="neutral" width="100%">
@@ -454,6 +454,14 @@ export const CompleteApplicationShell: Story = {
454454
),
455455
)}
456456
</Layout.Content>
457+
<Layout.Footer>
458+
<Text preset="t4" color="#dark-03">
459+
v2.4.1
460+
</Text>
461+
<Button type="link" size="small">
462+
Help
463+
</Button>
464+
</Layout.Footer>
457465
</Layout.Panel>
458466

459467
<Layout.Header

src/components/content/Layout/LayoutContent.tsx

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ const ContentElement = tasty({
5959
fill: '#dark.35',
6060
opacity: {
6161
'': 0,
62-
'(hovered | focused) & scrollbar=tiny': 1,
62+
'(hovered | focused | scrolling) & scrollbar=tiny': 1,
6363
},
6464
transition: 'opacity 0.15s',
6565
pointerEvents: 'none',
@@ -75,7 +75,7 @@ const ContentElement = tasty({
7575
fill: '#dark.35',
7676
opacity: {
7777
'': 0,
78-
'(hovered | focused) & scrollbar=tiny': 1,
78+
'(hovered | focused | scrolling) & scrollbar=tiny': 1,
7979
},
8080
transition: 'opacity 0.15s',
8181
pointerEvents: 'none',
@@ -111,8 +111,13 @@ function LayoutContent(
111111
const isTinyScrollbar = scrollbar === 'tiny';
112112
const { hoverProps, isHovered } = useHover({});
113113

114-
const { handleVStyle, handleHStyle, hasOverflowY, hasOverflowX } =
115-
useTinyScrollbar(innerRef, isTinyScrollbar);
114+
const {
115+
handleVStyle,
116+
handleHStyle,
117+
hasOverflowY,
118+
hasOverflowX,
119+
isScrolling,
120+
} = useTinyScrollbar(innerRef, isTinyScrollbar);
116121

117122
const scrollbarStyle = useMemo(() => {
118123
if (!isTinyScrollbar) return {};
@@ -128,8 +133,9 @@ function LayoutContent(
128133
...externalMods,
129134
scrollbar,
130135
hovered: isHovered,
136+
scrolling: isScrolling,
131137
}),
132-
[externalMods, scrollbar, isHovered],
138+
[externalMods, scrollbar, isHovered, isScrolling],
133139
);
134140

135141
// Merge styles: outer styles to root, inner styles to Inner element

src/components/content/Layout/LayoutPane.tsx

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ const PaneElement = tasty({
116116
fill: '#dark.35',
117117
opacity: {
118118
'': 0,
119-
'(hovered | focused) & scrollbar=tiny': 1,
119+
'(hovered | focused | scrolling) & scrollbar=tiny': 1,
120120
},
121121
transition: 'opacity 0.15s',
122122
pointerEvents: 'none',
@@ -135,7 +135,7 @@ const PaneElement = tasty({
135135
fill: '#dark.35',
136136
opacity: {
137137
'': 0,
138-
'(hovered | focused) & scrollbar=tiny': 1,
138+
'(hovered | focused | scrolling) & scrollbar=tiny': 1,
139139
},
140140
transition: 'opacity 0.15s',
141141
pointerEvents: 'none',
@@ -354,14 +354,18 @@ function LayoutPane(
354354
const inner = extractStyles(otherProps, INNER_STYLES);
355355

356356
return { outerStyles: outer, innerStyles: inner };
357-
358357
}, [styles]); // Only recalculate when styles prop changes
359358

360359
const isTinyScrollbar = scrollbar === 'tiny';
361360
const { hoverProps, isHovered } = useHover({});
362361

363-
const { handleVStyle, handleHStyle, hasOverflowY, hasOverflowX } =
364-
useTinyScrollbar(innerRef, isTinyScrollbar);
362+
const {
363+
handleVStyle,
364+
handleHStyle,
365+
hasOverflowY,
366+
hasOverflowX,
367+
isScrolling,
368+
} = useTinyScrollbar(innerRef, isTinyScrollbar);
365369

366370
// Clamp size to min/max constraints
367371
const clampSize = useCallback(
@@ -435,6 +439,7 @@ function LayoutPane(
435439
() => ({
436440
scrollbar,
437441
hovered: isHovered,
442+
scrolling: isScrolling,
438443
handlerHovered: debouncedHandlerHovered,
439444
focused: isHandlerFocused,
440445
disabled: !isResizable,
@@ -445,6 +450,7 @@ function LayoutPane(
445450
[
446451
scrollbar,
447452
isHovered,
453+
isScrolling,
448454
debouncedHandlerHovered,
449455
isHandlerFocused,
450456
isResizable,

src/components/content/Layout/hooks/useTinyScrollbar.ts

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
RefObject,
44
useCallback,
55
useEffect,
6+
useRef,
67
useState,
78
} from 'react';
89

@@ -22,9 +23,11 @@ export interface TinyScrollbarResult {
2223
handleHStyle: CSSProperties;
2324
hasOverflowY: boolean;
2425
hasOverflowX: boolean;
26+
isScrolling: boolean;
2527
}
2628

2729
const MIN_HANDLE_SIZE = 20;
30+
const SCROLL_VISIBILITY_DURATION = 1000;
2831

2932
export function useTinyScrollbar(
3033
ref: RefObject<HTMLElement | null>,
@@ -40,6 +43,8 @@ export function useTinyScrollbar(
4043
clientHeight: 0,
4144
clientWidth: 0,
4245
});
46+
const [isScrolling, setIsScrolling] = useState(false);
47+
const scrollTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
4348

4449
const updateScrollState = useCallback(() => {
4550
const element = ref.current;
@@ -78,7 +83,22 @@ export function useTinyScrollbar(
7883
updateScrollState();
7984

8085
// Listen for scroll events
81-
const handleScroll = () => updateScrollState();
86+
const handleScroll = () => {
87+
updateScrollState();
88+
89+
// Show scrollbar on scroll
90+
setIsScrolling(true);
91+
92+
// Clear existing timeout
93+
if (scrollTimeoutRef.current) {
94+
clearTimeout(scrollTimeoutRef.current);
95+
}
96+
97+
// Hide scrollbar after delay
98+
scrollTimeoutRef.current = setTimeout(() => {
99+
setIsScrolling(false);
100+
}, SCROLL_VISIBILITY_DURATION);
101+
};
82102

83103
element.addEventListener('scroll', handleScroll, { passive: true });
84104

@@ -97,6 +117,10 @@ export function useTinyScrollbar(
97117
return () => {
98118
element.removeEventListener('scroll', handleScroll);
99119
resizeObserver.disconnect();
120+
121+
if (scrollTimeoutRef.current) {
122+
clearTimeout(scrollTimeoutRef.current);
123+
}
100124
};
101125
}, [enabled, ref, updateScrollState]);
102126

@@ -159,5 +183,6 @@ export function useTinyScrollbar(
159183
} as CSSProperties,
160184
hasOverflowY: scrollState.hasOverflowY,
161185
hasOverflowX: scrollState.hasOverflowX,
186+
isScrolling,
162187
};
163188
}

0 commit comments

Comments
 (0)