Skip to content

Commit 0ce5c90

Browse files
authored
Merge pull request #726 from code0-tech/feat/#725
Auto scroll functionality
2 parents d3b3a50 + 14c128d commit 0ce5c90

9 files changed

Lines changed: 126 additions & 11 deletions

File tree

src/components/context-menu/ContextMenu.style.scss

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@
99
position: relative;
1010
box-sizing: border-box;
1111
z-index: 999;
12+
// clamp to the space the popper has left on screen; the inner
13+
// auto scroll area shrinks with it and takes over scrolling
14+
max-height: var(--radix-popper-available-height, 100vh);
15+
display: flex;
16+
flex-direction: column;
1217

1318
& {
1419
@include helpers.borderRadius();

src/components/context-menu/ContextMenu.tsx

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,7 @@ import React from "react";
44
import {ComponentProps, Color, mergeComponentProps} from "../../utils";
55
import * as Radix from "@radix-ui/react-context-menu";
66
import "./ContextMenu.style.scss"
7-
import {Card} from "../card/Card";
8-
import {Flex} from "../flex/Flex";
9-
import {Badge} from "../badge/Badge";
10-
import {IconArrowDown, IconArrowUp, IconCornerDownLeft} from "@tabler/icons-react";
11-
import {Spacing} from "../spacing/Spacing";
7+
import {AutoScrollArea} from "../scroll-area/ScrollArea";
128

139
export type ContextMenuProps = ComponentProps & Radix.ContextMenuProps
1410
export type ContextMenuTriggerProps = ComponentProps & Radix.ContextMenuTriggerProps
@@ -40,8 +36,13 @@ export const ContextMenuPortal: React.FC<ContextMenuPortalProps> = (props) => {
4036
}
4137

4238
export const ContextMenuContent: React.FC<ContextMenuContentProps> = (props) => {
39+
const {children, ...rest} = props
4340
return <Radix.ContextMenuContent
44-
align={props.align} {...mergeComponentProps(`context-menu__content context-menu__content--${props.color ?? "primary"}`, props) as ContextMenuContentProps}/>
41+
align={props.align} {...mergeComponentProps(`context-menu__content context-menu__content--${props.color ?? "primary"}`, rest) as ContextMenuContentProps}>
42+
<AutoScrollArea>
43+
{children}
44+
</AutoScrollArea>
45+
</Radix.ContextMenuContent>
4546
}
4647

4748
export const ContextMenuLabel: React.FC<ContextMenuLabelProps> = (props) => {
@@ -65,7 +66,12 @@ export const ContextMenuSubTrigger: React.FC<ContextMenuSubTriggerProps> = (prop
6566
}
6667

6768
export const ContextMenuSubContent: React.FC<ContextMenuSubContentProps> = (props) => {
68-
return <Radix.ContextMenuSubContent align={props.align} {...mergeComponentProps(`context-menu__sub-content context-menu__sub-content--${props.color ?? "primary"}`, props) as ContextMenuSubContentProps}/>
69+
const {children, ...rest} = props
70+
return <Radix.ContextMenuSubContent align={props.align} {...mergeComponentProps(`context-menu__sub-content context-menu__sub-content--${props.color ?? "primary"}`, rest) as ContextMenuSubContentProps}>
71+
<AutoScrollArea>
72+
{children}
73+
</AutoScrollArea>
74+
</Radix.ContextMenuSubContent>
6975
}
7076

7177
export const ContextMenuSeparator: React.FC<ContextMenuSeparatorProps> = (props) => {

src/components/menu/Menu.stories.tsx

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,4 +58,35 @@ export const ExampleMenu = () => {
5858
</MenuPortal>
5959
</Menu>
6060

61+
}
62+
63+
export const ScrollableMenu = () => {
64+
65+
return <Menu>
66+
<MenuTrigger asChild>
67+
<Button>Open Menu</Button>
68+
</MenuTrigger>
69+
<MenuPortal>
70+
<MenuContent side={"bottom"} align={"start"} sideOffset={5.6} collisionPadding={16}>
71+
{Array.from({length: 50}, (_, index) => {
72+
return <MenuItem key={index}>Item {index + 1}</MenuItem>
73+
})}
74+
<MenuSub>
75+
<MenuSubTrigger>
76+
<Flex justify={"space-between"} align={"center"} w={"100%"}>
77+
More items <IconChevronRight size={16}/>
78+
</Flex>
79+
</MenuSubTrigger>
80+
<MenuPortal>
81+
<MenuSubContent collisionPadding={16}>
82+
{Array.from({length: 50}, (_, index) => {
83+
return <MenuItem key={index}>Sub item {index + 1}</MenuItem>
84+
})}
85+
</MenuSubContent>
86+
</MenuPortal>
87+
</MenuSub>
88+
</MenuContent>
89+
</MenuPortal>
90+
</Menu>
91+
6192
}

src/components/menu/Menu.style.scss

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@
99
position: relative;
1010
box-sizing: border-box;
1111
z-index: 999;
12+
// clamp to the space the popper has left on screen; the inner
13+
// auto scroll area shrinks with it and takes over scrolling
14+
max-height: var(--radix-popper-available-height, 100vh);
15+
display: flex;
16+
flex-direction: column;
1217

1318
& {
1419
@include helpers.borderRadius();
@@ -66,6 +71,6 @@
6671
@each $name, $color in variables.$colors {
6772
.menu__content--#{$name}, .menu__sub-content--#{$name} {
6873
@include box.box($color);
69-
border: helpers.backgroundColor(variables.$secondary) 2px solid;
74+
border: helpers.backgroundColor(variables.$secondary) 1px solid;
7075
}
7176
}

src/components/menu/Menu.tsx

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import {
3232
DropdownMenuTrigger,
3333
DropdownMenuTriggerProps
3434
} from "@radix-ui/react-dropdown-menu";
35+
import {AutoScrollArea} from "../scroll-area/ScrollArea";
3536

3637
export type MenuProps = ComponentProps & DropdownMenuProps
3738
export type MenuTriggerProps = ComponentProps & DropdownMenuTriggerProps
@@ -65,8 +66,13 @@ export const MenuPortal: React.FC<MenuPortalProps> = (props) => {
6566
}
6667

6768
export const MenuContent: React.FC<MenuContentProps> = (props) => {
69+
const {children, ...rest} = props
6870
return <DropdownMenuContent
69-
align={props.align} {...mergeComponentProps(`menu__content menu__content--${props.color ?? "primary"}`, props) as MenuContentProps}/>
71+
align={props.align} {...mergeComponentProps(`menu__content menu__content--${props.color ?? "primary"}`, rest) as MenuContentProps}>
72+
<AutoScrollArea>
73+
{children}
74+
</AutoScrollArea>
75+
</DropdownMenuContent>
7076
}
7177

7278
export const MenuLabel: React.FC<MenuLabelProps> = (props) => {
@@ -90,7 +96,12 @@ export const MenuSubTrigger: React.FC<MenuSubTriggerProps> = (props) => {
9096
}
9197

9298
export const MenuSubContent: React.FC<MenuSubContentProps> = (props) => {
93-
return <DropdownMenuSubContent {...mergeComponentProps(`menu__sub-content menu__sub-content--${props.color ?? "primary"}`, props) as MenuSubContentProps}/>
99+
const {children, ...rest} = props
100+
return <DropdownMenuSubContent {...mergeComponentProps(`menu__sub-content menu__sub-content--${props.color ?? "primary"}`, rest) as MenuSubContentProps}>
101+
<AutoScrollArea>
102+
{children}
103+
</AutoScrollArea>
104+
</DropdownMenuSubContent>
94105
}
95106

96107
export const MenuSeparator: React.FC<MenuSeparatorProps> = (props) => {

src/components/scroll-area/ScrollArea.style.scss

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@
55
.scroll-area {
66
overflow: hidden;
77

8+
// allows the auto scroll area to shrink inside flexed popper
9+
// content (menus, sub menus, tooltips) so it never overflows it
10+
&--auto {
11+
min-height: 0;
12+
}
13+
814
&__viewport {
915
width: 100%;
1016
height: 100%;

src/components/scroll-area/ScrollArea.tsx

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
"use client"
2+
13
import React from "react";
24
import {ComponentProps, mergeComponentProps} from "../../utils";
35
import * as RadixScrollArea from "@radix-ui/react-scroll-area";
@@ -23,4 +25,41 @@ export const ScrollAreaThumb: React.FC<ScrollAreaThumbProps> = (props) => {
2325
}
2426
export const ScrollAreaCorner: React.FC<ScrollAreaCornerProps> = (props) => {
2527
return <RadixScrollArea.ScrollAreaCorner {...mergeComponentProps("scroll-area__corner", props)} />
28+
}
29+
30+
export type AutoScrollAreaProps = ScrollAreaProps
31+
32+
/**
33+
* ScrollArea that sizes itself to its content and scrolls once the content
34+
* exceeds the available height of the surrounding Radix popper (menus,
35+
* sub menus, tooltips). Used internally by MenuContent, MenuSubContent,
36+
* ContextMenuContent, ContextMenuSubContent and TooltipContent.
37+
*/
38+
export const AutoScrollArea: React.FC<AutoScrollAreaProps> = (props) => {
39+
const {children, mah = "var(--radix-popper-available-height, 100vh)", ...rest} = props
40+
const contentRef = React.useRef<HTMLDivElement>(null)
41+
const [height, setHeight] = React.useState<number>()
42+
43+
React.useLayoutEffect(() => {
44+
const el = contentRef.current
45+
if (!el) return
46+
const observer = new ResizeObserver((entries) => {
47+
const entry = entries[0]
48+
if (entry) setHeight(entry.contentRect.height)
49+
})
50+
observer.observe(el)
51+
return () => observer.disconnect()
52+
}, [])
53+
54+
return <ScrollArea className={"scroll-area--auto"} mah={mah}
55+
h={height !== undefined ? `${height}px` : undefined} {...rest}>
56+
<ScrollAreaViewport>
57+
<div ref={contentRef}>
58+
{children}
59+
</div>
60+
</ScrollAreaViewport>
61+
<ScrollAreaScrollbar orientation={"vertical"}>
62+
<ScrollAreaThumb/>
63+
</ScrollAreaScrollbar>
64+
</ScrollArea>
2665
}

src/components/tooltip/Tooltip.style.scss

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,12 @@
88

99
z-index: 999;
1010
padding: variables.$xxs variables.$xs;
11+
box-sizing: border-box;
12+
// clamp to the space the popper has left on screen; the inner
13+
// auto scroll area shrinks with it and takes over scrolling
14+
max-height: var(--radix-popper-available-height, 100vh);
15+
display: flex;
16+
flex-direction: column;
1117

1218
& {
1319
@include box.box(variables.$tertiary);

src/components/tooltip/Tooltip.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import React from "react";
22
import {Color, ComponentProps, mergeComponentProps} from "../../utils";
33
import * as RadixTooltip from "@radix-ui/react-tooltip";
44
import "./Tooltip.style.scss"
5+
import {AutoScrollArea} from "../scroll-area/ScrollArea";
56

67
export type TooltipProps = ComponentProps & RadixTooltip.TooltipProps
78
export type TooltipTriggerProps = ComponentProps & RadixTooltip.TooltipTriggerProps
@@ -24,8 +25,13 @@ export const TooltipPortal: React.FC<TooltipPortalProps> = (props) => {
2425
}
2526

2627
export const TooltipContent: React.FC<TooltipContentProps> = (props) => {
28+
const {children, ...rest} = props
2729
return <RadixTooltip.TooltipContent
28-
align={props.align} {...mergeComponentProps(`tooltip__content tooltip__content--${props.color ?? "tertiary"}`, props) as TooltipContentProps}/>
30+
align={props.align} {...mergeComponentProps(`tooltip__content tooltip__content--${props.color ?? "tertiary"}`, rest) as TooltipContentProps}>
31+
<AutoScrollArea>
32+
{children}
33+
</AutoScrollArea>
34+
</RadixTooltip.TooltipContent>
2935
}
3036

3137
export const TooltipArrow: React.FC<TooltipArrowProps> = (props) => {

0 commit comments

Comments
 (0)