diff --git a/packages/grafana-ui/src/components/Dropdown/Dropdown.mdx b/packages/grafana-ui/src/components/Dropdown/Dropdown.mdx
new file mode 100644
index 00000000000..150cf5cf653
--- /dev/null
+++ b/packages/grafana-ui/src/components/Dropdown/Dropdown.mdx
@@ -0,0 +1,35 @@
+import { Meta, Props } from '@storybook/addon-docs/blocks';
+
+import { Dropdown } from './Dropdown';
+
+
+
+# Dropdown
+
+Hook up a menu or other overlay to any trigger.
+
+### When to use
+
+When you want a button, link or icon button to open a Menu or Popover. By default the trigger is click but it can be changed to show on hover using the
+trigger property.
+
+### Usage
+
+```tsx
+import { Dropdown, Menu, Button } from '@grafana/ui';
+
+const menu = (
+
+
+
+
+);
+
+return (
+
+
+
+);
+```
+
+
diff --git a/packages/grafana-ui/src/components/Dropdown/Dropdown.story.tsx b/packages/grafana-ui/src/components/Dropdown/Dropdown.story.tsx
new file mode 100644
index 00000000000..a8e3c5835b7
--- /dev/null
+++ b/packages/grafana-ui/src/components/Dropdown/Dropdown.story.tsx
@@ -0,0 +1,60 @@
+import { ComponentMeta } from '@storybook/react';
+import React from 'react';
+
+import { StoryExample } from '../../utils/storybook/StoryExample';
+import { withCenteredStory } from '../../utils/storybook/withCenteredStory';
+import { Button } from '../Button';
+import { VerticalGroup } from '../Layout/Layout';
+import { Menu } from '../Menu/Menu';
+
+import { Dropdown } from './Dropdown';
+import mdx from './Dropdown.mdx';
+
+const meta: ComponentMeta = {
+ title: 'Overlays/Dropdown',
+ component: Dropdown,
+ decorators: [withCenteredStory],
+ parameters: {
+ docs: {
+ page: mdx,
+ },
+ controls: {
+ exclude: ['className'],
+ },
+ },
+};
+
+export function Examples() {
+ const menu = (
+
+
+
+
+
+
+ );
+
+ return (
+
+
+
+ Button
+
+
+
+
+
+
+
+
+ );
+}
+
+Examples.parameters = {
+ controls: {
+ hideNoControlsWarning: true,
+ include: [],
+ },
+};
+
+export default meta;
diff --git a/packages/grafana-ui/src/components/Dropdown/Dropdown.tsx b/packages/grafana-ui/src/components/Dropdown/Dropdown.tsx
new file mode 100644
index 00000000000..c8614af5231
--- /dev/null
+++ b/packages/grafana-ui/src/components/Dropdown/Dropdown.tsx
@@ -0,0 +1,81 @@
+import { css } from '@emotion/css';
+import { FocusScope } from '@react-aria/focus';
+import React, { useState } from 'react';
+import { usePopperTooltip } from 'react-popper-tooltip';
+import { CSSTransition } from 'react-transition-group';
+
+import { ReactUtils } from '../../utils';
+import { Portal } from '../Portal/Portal';
+import { TooltipPlacement } from '../Tooltip/types';
+
+export interface Props {
+ overlay: React.ReactElement | (() => React.ReactElement);
+ placement?: TooltipPlacement;
+ children: React.ReactElement;
+}
+
+export const Dropdown = React.memo(({ children, overlay, placement }: Props) => {
+ const [show, setShow] = useState(false);
+
+ const { getArrowProps, getTooltipProps, setTooltipRef, setTriggerRef, visible } = usePopperTooltip({
+ visible: show,
+ placement: placement,
+ onVisibleChange: setShow,
+ interactive: true,
+ delayHide: 0,
+ delayShow: 0,
+ offset: [0, 8],
+ trigger: ['click'],
+ });
+
+ const animationDuration = 150;
+ const animationStyles = getStyles(animationDuration);
+
+ const onOverlayClicked = () => {
+ setShow(false);
+ };
+
+ return (
+ <>
+ {React.cloneElement(children, {
+ ref: setTriggerRef,
+ })}
+ {visible && (
+
+
+
+
+
+ {ReactUtils.renderOrCallToRender(overlay)}
+
+
+
+
+ )}
+ >
+ );
+});
+
+Dropdown.displayName = 'Dropdown';
+
+const getStyles = (duration: number) => {
+ return {
+ appear: css`
+ opacity: 0;
+ position: relative;
+ transform: scaleY(0.5);
+ transform-origin: top;
+ `,
+ appearActive: css`
+ opacity: 1;
+ transform: scaleY(1);
+ transition: transform ${duration}ms cubic-bezier(0.2, 0, 0.2, 1),
+ opacity ${duration}ms cubic-bezier(0.2, 0, 0.2, 1);
+ `,
+ };
+};
diff --git a/packages/grafana-ui/src/components/index.ts b/packages/grafana-ui/src/components/index.ts
index a7e16704a18..266ef273a4d 100644
--- a/packages/grafana-ui/src/components/index.ts
+++ b/packages/grafana-ui/src/components/index.ts
@@ -228,6 +228,7 @@ export { Card, Props as CardProps, getCardStyles } from './Card/Card';
export { CardContainer, CardContainerProps } from './Card/CardContainer';
export { FormattedValueDisplay } from './FormattedValueDisplay/FormattedValueDisplay';
export { ButtonSelect } from './Dropdown/ButtonSelect';
+export { Dropdown } from './Dropdown/Dropdown';
export { PluginSignatureBadge, PluginSignatureBadgeProps } from './PluginSignatureBadge/PluginSignatureBadge';
// Export this until we've figured out a good approach to inline form styles.
diff --git a/public/app/core/components/AppChrome/TopSearchBar.tsx b/public/app/core/components/AppChrome/TopSearchBar.tsx
index bc6f0863c14..113da41d8e9 100644
--- a/public/app/core/components/AppChrome/TopSearchBar.tsx
+++ b/public/app/core/components/AppChrome/TopSearchBar.tsx
@@ -2,7 +2,7 @@ import { css } from '@emotion/css';
import React from 'react';
import { GrafanaTheme2 } from '@grafana/data';
-import { FilterInput, Icon, Tooltip, useStyles2 } from '@grafana/ui';
+import { Dropdown, FilterInput, Icon, Menu, MenuItem, Tooltip, useStyles2 } from '@grafana/ui';
import { contextSrv } from 'app/core/core';
import { TOP_BAR_LEVEL_HEIGHT } from './types';
@@ -36,15 +36,30 @@ export function TopSearchBar() {
-
-
-
+
+
+
+
+
);
}
+/**
+ * This is just temporary, needs syncing with the backend option like DisableSignoutMenu
+ */
+export function ProfileMenu() {
+ return (
+
+
+
+
+
+ );
+}
+
const getStyles = (theme: GrafanaTheme2) => {
return {
searchBar: css({