Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/components/context-menu/ContextMenu.style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@
position: relative;
box-sizing: border-box;
z-index: 999;
// clamp to the space the popper has left on screen; the inner
// auto scroll area shrinks with it and takes over scrolling
max-height: var(--radix-popper-available-height, 100vh);
display: flex;
flex-direction: column;

& {
@include helpers.borderRadius();
Expand Down
20 changes: 13 additions & 7 deletions src/components/context-menu/ContextMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,7 @@ import React from "react";
import {ComponentProps, Color, mergeComponentProps} from "../../utils";
import * as Radix from "@radix-ui/react-context-menu";
import "./ContextMenu.style.scss"
import {Card} from "../card/Card";
import {Flex} from "../flex/Flex";
import {Badge} from "../badge/Badge";
import {IconArrowDown, IconArrowUp, IconCornerDownLeft} from "@tabler/icons-react";
import {Spacing} from "../spacing/Spacing";
import {AutoScrollArea} from "../scroll-area/ScrollArea";

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

export const ContextMenuContent: React.FC<ContextMenuContentProps> = (props) => {
const {children, ...rest} = props
return <Radix.ContextMenuContent
align={props.align} {...mergeComponentProps(`context-menu__content context-menu__content--${props.color ?? "primary"}`, props) as ContextMenuContentProps}/>
align={props.align} {...mergeComponentProps(`context-menu__content context-menu__content--${props.color ?? "primary"}`, rest) as ContextMenuContentProps}>
<AutoScrollArea>
{children}
</AutoScrollArea>
</Radix.ContextMenuContent>
}

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

export const ContextMenuSubContent: React.FC<ContextMenuSubContentProps> = (props) => {
return <Radix.ContextMenuSubContent align={props.align} {...mergeComponentProps(`context-menu__sub-content context-menu__sub-content--${props.color ?? "primary"}`, props) as ContextMenuSubContentProps}/>
const {children, ...rest} = props
return <Radix.ContextMenuSubContent align={props.align} {...mergeComponentProps(`context-menu__sub-content context-menu__sub-content--${props.color ?? "primary"}`, rest) as ContextMenuSubContentProps}>
<AutoScrollArea>
{children}
</AutoScrollArea>
</Radix.ContextMenuSubContent>
}

export const ContextMenuSeparator: React.FC<ContextMenuSeparatorProps> = (props) => {
Expand Down
31 changes: 31 additions & 0 deletions src/components/menu/Menu.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,35 @@ export const ExampleMenu = () => {
</MenuPortal>
</Menu>

}

export const ScrollableMenu = () => {

return <Menu>
<MenuTrigger asChild>
<Button>Open Menu</Button>
</MenuTrigger>
<MenuPortal>
<MenuContent side={"bottom"} align={"start"} sideOffset={5.6} collisionPadding={16}>
{Array.from({length: 50}, (_, index) => {
return <MenuItem key={index}>Item {index + 1}</MenuItem>
})}
<MenuSub>
<MenuSubTrigger>
<Flex justify={"space-between"} align={"center"} w={"100%"}>
More items <IconChevronRight size={16}/>
</Flex>
</MenuSubTrigger>
<MenuPortal>
<MenuSubContent collisionPadding={16}>
{Array.from({length: 50}, (_, index) => {
return <MenuItem key={index}>Sub item {index + 1}</MenuItem>
})}
</MenuSubContent>
</MenuPortal>
</MenuSub>
</MenuContent>
</MenuPortal>
</Menu>

}
7 changes: 6 additions & 1 deletion src/components/menu/Menu.style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@
position: relative;
box-sizing: border-box;
z-index: 999;
// clamp to the space the popper has left on screen; the inner
// auto scroll area shrinks with it and takes over scrolling
max-height: var(--radix-popper-available-height, 100vh);
display: flex;
flex-direction: column;

& {
@include helpers.borderRadius();
Expand Down Expand Up @@ -66,6 +71,6 @@
@each $name, $color in variables.$colors {
.menu__content--#{$name}, .menu__sub-content--#{$name} {
@include box.box($color);
border: helpers.backgroundColor(variables.$secondary) 2px solid;
border: helpers.backgroundColor(variables.$secondary) 1px solid;
}
}
15 changes: 13 additions & 2 deletions src/components/menu/Menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import {
DropdownMenuTrigger,
DropdownMenuTriggerProps
} from "@radix-ui/react-dropdown-menu";
import {AutoScrollArea} from "../scroll-area/ScrollArea";

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

export const MenuContent: React.FC<MenuContentProps> = (props) => {
const {children, ...rest} = props
return <DropdownMenuContent
align={props.align} {...mergeComponentProps(`menu__content menu__content--${props.color ?? "primary"}`, props) as MenuContentProps}/>
align={props.align} {...mergeComponentProps(`menu__content menu__content--${props.color ?? "primary"}`, rest) as MenuContentProps}>
<AutoScrollArea>
{children}
</AutoScrollArea>
</DropdownMenuContent>
}

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

export const MenuSubContent: React.FC<MenuSubContentProps> = (props) => {
return <DropdownMenuSubContent {...mergeComponentProps(`menu__sub-content menu__sub-content--${props.color ?? "primary"}`, props) as MenuSubContentProps}/>
const {children, ...rest} = props
return <DropdownMenuSubContent {...mergeComponentProps(`menu__sub-content menu__sub-content--${props.color ?? "primary"}`, rest) as MenuSubContentProps}>
<AutoScrollArea>
{children}
</AutoScrollArea>
</DropdownMenuSubContent>
}

export const MenuSeparator: React.FC<MenuSeparatorProps> = (props) => {
Expand Down
6 changes: 6 additions & 0 deletions src/components/scroll-area/ScrollArea.style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@
.scroll-area {
overflow: hidden;

// allows the auto scroll area to shrink inside flexed popper
// content (menus, sub menus, tooltips) so it never overflows it
&--auto {
min-height: 0;
}

&__viewport {
width: 100%;
height: 100%;
Expand Down
39 changes: 39 additions & 0 deletions src/components/scroll-area/ScrollArea.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"use client"

import React from "react";
import {ComponentProps, mergeComponentProps} from "../../utils";
import * as RadixScrollArea from "@radix-ui/react-scroll-area";
Expand All @@ -23,4 +25,41 @@ export const ScrollAreaThumb: React.FC<ScrollAreaThumbProps> = (props) => {
}
export const ScrollAreaCorner: React.FC<ScrollAreaCornerProps> = (props) => {
return <RadixScrollArea.ScrollAreaCorner {...mergeComponentProps("scroll-area__corner", props)} />
}

export type AutoScrollAreaProps = ScrollAreaProps

/**
* ScrollArea that sizes itself to its content and scrolls once the content
* exceeds the available height of the surrounding Radix popper (menus,
* sub menus, tooltips). Used internally by MenuContent, MenuSubContent,
* ContextMenuContent, ContextMenuSubContent and TooltipContent.
*/
export const AutoScrollArea: React.FC<AutoScrollAreaProps> = (props) => {
const {children, mah = "var(--radix-popper-available-height, 100vh)", ...rest} = props
const contentRef = React.useRef<HTMLDivElement>(null)
const [height, setHeight] = React.useState<number>()

React.useLayoutEffect(() => {
const el = contentRef.current
if (!el) return
const observer = new ResizeObserver((entries) => {
const entry = entries[0]
if (entry) setHeight(entry.contentRect.height)
})
observer.observe(el)
return () => observer.disconnect()
}, [])

return <ScrollArea className={"scroll-area--auto"} mah={mah}
h={height !== undefined ? `${height}px` : undefined} {...rest}>
<ScrollAreaViewport>
<div ref={contentRef}>
{children}
</div>
</ScrollAreaViewport>
<ScrollAreaScrollbar orientation={"vertical"}>
<ScrollAreaThumb/>
</ScrollAreaScrollbar>
</ScrollArea>
}
6 changes: 6 additions & 0 deletions src/components/tooltip/Tooltip.style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@

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

& {
@include box.box(variables.$tertiary);
Expand Down
8 changes: 7 additions & 1 deletion src/components/tooltip/Tooltip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React from "react";
import {Color, ComponentProps, mergeComponentProps} from "../../utils";
import * as RadixTooltip from "@radix-ui/react-tooltip";
import "./Tooltip.style.scss"
import {AutoScrollArea} from "../scroll-area/ScrollArea";

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

export const TooltipContent: React.FC<TooltipContentProps> = (props) => {
const {children, ...rest} = props
return <RadixTooltip.TooltipContent
align={props.align} {...mergeComponentProps(`tooltip__content tooltip__content--${props.color ?? "tertiary"}`, props) as TooltipContentProps}/>
align={props.align} {...mergeComponentProps(`tooltip__content tooltip__content--${props.color ?? "tertiary"}`, rest) as TooltipContentProps}>
<AutoScrollArea>
{children}
</AutoScrollArea>
</RadixTooltip.TooltipContent>
}

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