fix(applaunchpad): constrain route service menu height (#7037)

* fix(applaunchpad): constrain route service menu height

* fix(applaunchpad): let route service menu escape modal clipping

* fix(applaunchpad): render route service menu in portal
This commit is contained in:
Wang Zhihao
2026-06-29 17:11:34 +08:00
committed by GitHub
parent 5b7daf46c2
commit 66559f8730
2 changed files with 75 additions and 57 deletions
@@ -10,9 +10,10 @@ import {
useDisclosure,
useOutsideClick,
MenuButton,
Flex
Flex,
Portal
} from '@chakra-ui/react';
import type { BoxProps, ButtonProps } from '@chakra-ui/react';
import type { BoxProps, ButtonProps, MenuListProps, MenuProps } from '@chakra-ui/react';
import { ChevronDownIcon } from '@chakra-ui/icons';
interface Props extends ButtonProps {
@@ -27,6 +28,9 @@ interface Props extends ButtonProps {
onchange?: (val: string) => void;
isInvalid?: boolean;
boxStyle?: BoxProps;
menuProps?: Omit<MenuProps, 'children'>;
menuListProps?: MenuListProps;
menuListPortal?: boolean;
}
const MySelect = (
@@ -39,6 +43,9 @@ const MySelect = (
onchange,
isInvalid,
boxStyle,
menuProps,
menuListProps,
menuListPortal,
...props
}: Props,
selectRef: any
@@ -50,6 +57,7 @@ const MySelect = (
useOutsideClick({
ref: SelectRef,
handler: () => {
if (menuListPortal) return;
onClose();
}
});
@@ -65,8 +73,56 @@ const MySelect = (
return foundItem;
}, [list, value]);
const menuList = (
<MenuList
minW={(() => {
const w = ref.current?.clientWidth;
if (w) {
return `${w}px !important`;
}
return Array.isArray(width)
? width.map((item) => `${item} !important`)
: `${width} !important`;
})()}
p={'6px'}
borderRadius={'base'}
border={'1px solid #E8EBF0'}
boxShadow={
'0px 4px 10px 0px rgba(19, 51, 107, 0.10), 0px 0px 1px 0px rgba(19, 51, 107, 0.10)'
}
zIndex={99}
overflow={'overlay'}
maxH={'300px'}
{...menuListProps}
>
{list.map((item) => (
<MenuItem
key={item.value}
{...(value === item.value
? {
color: 'brightBlue.600'
}
: {})}
borderRadius={'4px'}
_hover={{
bg: 'rgba(17, 24, 36, 0.05)',
color: 'brightBlue.600'
}}
p={'6px'}
onClick={() => {
if (onchange && value !== item.value) {
onchange(item.value);
}
}}
>
<Box>{item.label}</Box>
</MenuItem>
))}
</MenuList>
);
return (
<Menu autoSelect={false} isOpen={isOpen} onOpen={onOpen} onClose={onClose}>
<Menu autoSelect={false} isOpen={isOpen} onOpen={onOpen} onClose={onClose} {...menuProps}>
<Box
ref={SelectRef}
position={'relative'}
@@ -111,50 +167,7 @@ const MySelect = (
<Flex justifyContent={'flex-start'}>{activeMenu ? activeMenu.label : placeholder}</Flex>
</MenuButton>
<MenuList
minW={(() => {
const w = ref.current?.clientWidth;
if (w) {
return `${w}px !important`;
}
return Array.isArray(width)
? width.map((item) => `${item} !important`)
: `${width} !important`;
})()}
p={'6px'}
borderRadius={'base'}
border={'1px solid #E8EBF0'}
boxShadow={
'0px 4px 10px 0px rgba(19, 51, 107, 0.10), 0px 0px 1px 0px rgba(19, 51, 107, 0.10)'
}
zIndex={99}
overflow={'overlay'}
maxH={'300px'}
>
{list.map((item) => (
<MenuItem
key={item.value}
{...(value === item.value
? {
color: 'brightBlue.600'
}
: {})}
borderRadius={'4px'}
_hover={{
bg: 'rgba(17, 24, 36, 0.05)',
color: 'brightBlue.600'
}}
p={'6px'}
onClick={() => {
if (onchange && value !== item.value) {
onchange(item.value);
}
}}
>
<Box>{item.label}</Box>
</MenuItem>
))}
</MenuList>
{menuListPortal ? <Portal>{menuList}</Portal> : menuList}
</Box>
</Menu>
);
@@ -25,7 +25,7 @@ const routeRuleFieldGapPx = 19;
const routeRuleFieldWidth = `${routeRuleFieldWidthPx}px`;
const routeRuleFieldGap = `${routeRuleFieldGapPx}px`;
const routeRuleContentWidth = `${routeRuleFieldWidthPx * 2 + routeRuleFieldGapPx}px`;
const routeRuleSelectDropdownGap = '8px';
const routeRuleBackendSelectMenuMaxH = '220px';
const routeRulesScrollbarWidth = '6px';
type RouteRulesForm = {
@@ -346,15 +346,20 @@ export default function RouteRulesModal({
routes?.[index]?.servicePort || defaultServicePort
)}
list={backendServiceOptions}
boxStyle={{
sx: {
'& [aria-haspopup="menu"][aria-expanded="true"] + .chakra-menu__menu-list':
{
position: 'static',
transform: 'none !important',
mt: routeRuleSelectDropdownGap
}
}
menuProps={{
strategy: 'fixed',
gutter: 8,
flip: true,
preventOverflow: true,
boundary: 'clippingParents'
}}
menuListPortal
menuListProps={{
zIndex: 1500,
maxH: routeRuleBackendSelectMenuMaxH,
overflowY: 'auto',
overflowX: 'hidden',
overscrollBehavior: 'contain'
}}
onchange={(val: string) => {
const backendService = parseBackendServiceValue(val);