diff --git a/src/components/context-menu/ContextMenu.style.scss b/src/components/context-menu/ContextMenu.style.scss index 6729147b0..99719bd65 100644 --- a/src/components/context-menu/ContextMenu.style.scss +++ b/src/components/context-menu/ContextMenu.style.scss @@ -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(); diff --git a/src/components/context-menu/ContextMenu.tsx b/src/components/context-menu/ContextMenu.tsx index 97d98a11e..0e8f55134 100644 --- a/src/components/context-menu/ContextMenu.tsx +++ b/src/components/context-menu/ContextMenu.tsx @@ -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 @@ -40,8 +36,13 @@ export const ContextMenuPortal: React.FC = (props) => { } export const ContextMenuContent: React.FC = (props) => { + const {children, ...rest} = props return + align={props.align} {...mergeComponentProps(`context-menu__content context-menu__content--${props.color ?? "primary"}`, rest) as ContextMenuContentProps}> + + {children} + + } export const ContextMenuLabel: React.FC = (props) => { @@ -65,7 +66,12 @@ export const ContextMenuSubTrigger: React.FC = (prop } export const ContextMenuSubContent: React.FC = (props) => { - return + const {children, ...rest} = props + return + + {children} + + } export const ContextMenuSeparator: React.FC = (props) => { diff --git a/src/components/menu/Menu.stories.tsx b/src/components/menu/Menu.stories.tsx index 54eb01cad..ab458c818 100644 --- a/src/components/menu/Menu.stories.tsx +++ b/src/components/menu/Menu.stories.tsx @@ -58,4 +58,35 @@ export const ExampleMenu = () => { +} + +export const ScrollableMenu = () => { + + return + + + + + + {Array.from({length: 50}, (_, index) => { + return Item {index + 1} + })} + + + + More items + + + + + {Array.from({length: 50}, (_, index) => { + return Sub item {index + 1} + })} + + + + + + + } \ No newline at end of file diff --git a/src/components/menu/Menu.style.scss b/src/components/menu/Menu.style.scss index 7cda98df8..8a736a4e4 100644 --- a/src/components/menu/Menu.style.scss +++ b/src/components/menu/Menu.style.scss @@ -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(); @@ -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; } } \ No newline at end of file diff --git a/src/components/menu/Menu.tsx b/src/components/menu/Menu.tsx index 50db9e6f8..89859776e 100644 --- a/src/components/menu/Menu.tsx +++ b/src/components/menu/Menu.tsx @@ -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 @@ -65,8 +66,13 @@ export const MenuPortal: React.FC = (props) => { } export const MenuContent: React.FC = (props) => { + const {children, ...rest} = props return + align={props.align} {...mergeComponentProps(`menu__content menu__content--${props.color ?? "primary"}`, rest) as MenuContentProps}> + + {children} + + } export const MenuLabel: React.FC = (props) => { @@ -90,7 +96,12 @@ export const MenuSubTrigger: React.FC = (props) => { } export const MenuSubContent: React.FC = (props) => { - return + const {children, ...rest} = props + return + + {children} + + } export const MenuSeparator: React.FC = (props) => { diff --git a/src/components/scroll-area/ScrollArea.style.scss b/src/components/scroll-area/ScrollArea.style.scss index e25ccb399..1b47ec723 100644 --- a/src/components/scroll-area/ScrollArea.style.scss +++ b/src/components/scroll-area/ScrollArea.style.scss @@ -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%; diff --git a/src/components/scroll-area/ScrollArea.tsx b/src/components/scroll-area/ScrollArea.tsx index 1c817e429..21bf367e9 100644 --- a/src/components/scroll-area/ScrollArea.tsx +++ b/src/components/scroll-area/ScrollArea.tsx @@ -1,3 +1,5 @@ +"use client" + import React from "react"; import {ComponentProps, mergeComponentProps} from "../../utils"; import * as RadixScrollArea from "@radix-ui/react-scroll-area"; @@ -23,4 +25,41 @@ export const ScrollAreaThumb: React.FC = (props) => { } export const ScrollAreaCorner: React.FC = (props) => { return +} + +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 = (props) => { + const {children, mah = "var(--radix-popper-available-height, 100vh)", ...rest} = props + const contentRef = React.useRef(null) + const [height, setHeight] = React.useState() + + 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 + +
+ {children} +
+
+ + + +
} \ No newline at end of file diff --git a/src/components/tooltip/Tooltip.style.scss b/src/components/tooltip/Tooltip.style.scss index baa94711d..d9478dc70 100644 --- a/src/components/tooltip/Tooltip.style.scss +++ b/src/components/tooltip/Tooltip.style.scss @@ -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); diff --git a/src/components/tooltip/Tooltip.tsx b/src/components/tooltip/Tooltip.tsx index c79a53cb6..7659cf3cb 100644 --- a/src/components/tooltip/Tooltip.tsx +++ b/src/components/tooltip/Tooltip.tsx @@ -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 @@ -24,8 +25,13 @@ export const TooltipPortal: React.FC = (props) => { } export const TooltipContent: React.FC = (props) => { + const {children, ...rest} = props return + align={props.align} {...mergeComponentProps(`tooltip__content tooltip__content--${props.color ?? "tertiary"}`, rest) as TooltipContentProps}> + + {children} + + } export const TooltipArrow: React.FC = (props) => {