diff --git a/public/app/core/components/RolePicker/RolePicker.tsx b/public/app/core/components/RolePicker/RolePicker.tsx index a916ed9a1d2..b7c1e8de11c 100644 --- a/public/app/core/components/RolePicker/RolePicker.tsx +++ b/public/app/core/components/RolePicker/RolePicker.tsx @@ -1,10 +1,11 @@ -import React, { FormEvent, useCallback, useEffect, useState } from 'react'; +import React, { FormEvent, useCallback, useEffect, useState, useRef } from 'react'; import { ClickOutsideWrapper, HorizontalGroup, Spinner } from '@grafana/ui'; import { Role, OrgRole } from 'app/types'; import { RolePickerInput } from './RolePickerInput'; import { RolePickerMenu } from './RolePickerMenu'; +import { MENU_MAX_HEIGHT } from './constants'; export interface Props { builtInRole?: OrgRole; @@ -23,7 +24,6 @@ export const RolePicker = ({ builtInRole, appliedRoles, roleOptions, - builtInRoles, disabled, isLoading, builtinRolesDisabled, @@ -35,11 +35,28 @@ export const RolePicker = ({ const [selectedRoles, setSelectedRoles] = useState(appliedRoles); const [selectedBuiltInRole, setSelectedBuiltInRole] = useState(builtInRole); const [query, setQuery] = useState(''); + const [offset, setOffset] = useState(0); + const ref = useRef(null); useEffect(() => { setSelectedRoles(appliedRoles); }, [appliedRoles]); + useEffect(() => { + const dimensions = ref?.current?.getBoundingClientRect(); + if (!dimensions || !isOpen) { + return; + } + const { bottom, top } = dimensions; + const distance = window.innerHeight - bottom; + const offset = bottom - top + 10; // Add extra 10px to offset to account for border and outline + if (distance < MENU_MAX_HEIGHT) { + setOffset(offset); + } else { + setOffset(-offset); + } + }, [isOpen]); + const onOpen = useCallback( (event: FormEvent) => { if (!disabled) { @@ -103,7 +120,7 @@ export const RolePicker = ({ } return ( -
+
)} diff --git a/public/app/core/components/RolePicker/RolePickerMenu.tsx b/public/app/core/components/RolePicker/RolePickerMenu.tsx index 6ecc86fe487..1f3989d0fde 100644 --- a/public/app/core/components/RolePicker/RolePickerMenu.tsx +++ b/public/app/core/components/RolePicker/RolePickerMenu.tsx @@ -17,7 +17,7 @@ import { import { getSelectStyles } from '@grafana/ui/src/components/Select/getSelectStyles'; import { OrgRole, Role } from 'app/types'; -type BuiltInRoles = Record; +import { MENU_MAX_HEIGHT } from './constants'; const BuiltinRoles = Object.values(OrgRole); const BuiltinRoleOption: Array> = BuiltinRoles.map((r) => ({ @@ -32,7 +32,6 @@ const fixedRoleGroupNames: Record = { interface RolePickerMenuProps { builtInRole?: OrgRole; - builtInRoles?: BuiltInRoles; options: Role[]; appliedRoles: Role[]; showGroups?: boolean; @@ -42,11 +41,11 @@ interface RolePickerMenuProps { onBuiltInRoleSelect?: (role: OrgRole) => void; onUpdate: (newRoles: string[], newBuiltInRole?: OrgRole) => void; onClear?: () => void; + offset: number; } export const RolePickerMenu = ({ builtInRole, - builtInRoles, options, appliedRoles, showGroups, @@ -56,6 +55,7 @@ export const RolePickerMenu = ({ onBuiltInRoleSelect, onUpdate, onClear, + offset, }: RolePickerMenuProps): JSX.Element => { const [selectedOptions, setSelectedOptions] = useState(appliedRoles); const [selectedBuiltInRole, setSelectedBuiltInRole] = useState(builtInRole); @@ -63,7 +63,6 @@ export const RolePickerMenu = ({ const [openedMenuGroup, setOpenedMenuGroup] = useState(''); const [subMenuOptions, setSubMenuOptions] = useState([]); const subMenuNode = useRef(null); - const theme = useTheme2(); const styles = getSelectStyles(theme); const customStyles = useStyles2(getStyles); @@ -175,9 +174,18 @@ export const RolePickerMenu = ({ }; return ( -
+
0 ? `${offset}px` : 'unset'}; + top: ${offset < 0 ? `${Math.abs(offset)}px` : 'unset'}; + ` + )} + >
- + {showBuiltInRole && (
Built-in roles
@@ -327,7 +335,7 @@ export const RolePickerSubMenu = ({ return (
- +
{options.map((option, i) => (