mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Dropdown: New dropdown component (#52684)
* Progress * Example usage in application * Add FocusScope * REmove unused type * simplify it by removing trigger property * Use new Menu.Item way to access component
This commit is contained in:
parent
21dab47293
commit
6af5f71dda
35
packages/grafana-ui/src/components/Dropdown/Dropdown.mdx
Normal file
35
packages/grafana-ui/src/components/Dropdown/Dropdown.mdx
Normal file
@ -0,0 +1,35 @@
|
||||
import { Meta, Props } from '@storybook/addon-docs/blocks';
|
||||
|
||||
import { Dropdown } from './Dropdown';
|
||||
|
||||
<Meta title="MDX|Dropdown" component={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 = (
|
||||
<Menu>
|
||||
<Menu.Item label="Google" />
|
||||
<Menu.Item label="Filter" />
|
||||
</Menu>
|
||||
);
|
||||
|
||||
return (
|
||||
<Dropdown overlay={menu}>
|
||||
<Button icon="bars" />
|
||||
</Dropdown>
|
||||
);
|
||||
```
|
||||
|
||||
<Props of={Dropdown} />
|
@ -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<typeof Dropdown> = {
|
||||
title: 'Overlays/Dropdown',
|
||||
component: Dropdown,
|
||||
decorators: [withCenteredStory],
|
||||
parameters: {
|
||||
docs: {
|
||||
page: mdx,
|
||||
},
|
||||
controls: {
|
||||
exclude: ['className'],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export function Examples() {
|
||||
const menu = (
|
||||
<Menu>
|
||||
<Menu.Item label="View settings" tabIndex={0} />
|
||||
<Menu.Item label="Edit actions" tabIndex={1} />
|
||||
<Menu.Item label="Share" tabIndex={2} />
|
||||
<Menu.Item label="Delete" tabIndex={3} />
|
||||
</Menu>
|
||||
);
|
||||
|
||||
return (
|
||||
<VerticalGroup>
|
||||
<StoryExample name="Button + defaults">
|
||||
<Dropdown overlay={menu}>
|
||||
<Button variant="secondary">Button</Button>
|
||||
</Dropdown>
|
||||
</StoryExample>
|
||||
<StoryExample name="Icon button, placement=bottom-start">
|
||||
<Dropdown overlay={menu} placement="bottom-start">
|
||||
<Button variant="secondary" icon="bars" />
|
||||
</Dropdown>
|
||||
</StoryExample>
|
||||
</VerticalGroup>
|
||||
);
|
||||
}
|
||||
|
||||
Examples.parameters = {
|
||||
controls: {
|
||||
hideNoControlsWarning: true,
|
||||
include: [],
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
81
packages/grafana-ui/src/components/Dropdown/Dropdown.tsx
Normal file
81
packages/grafana-ui/src/components/Dropdown/Dropdown.tsx
Normal file
@ -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 && (
|
||||
<Portal>
|
||||
<FocusScope autoFocus>
|
||||
<div ref={setTooltipRef} {...getTooltipProps()} onClick={onOverlayClicked}>
|
||||
<div {...getArrowProps({ className: 'tooltip-arrow' })} />
|
||||
<CSSTransition
|
||||
appear={true}
|
||||
in={true}
|
||||
timeout={{ appear: animationDuration, exit: 0, enter: 0 }}
|
||||
classNames={animationStyles}
|
||||
>
|
||||
{ReactUtils.renderOrCallToRender(overlay)}
|
||||
</CSSTransition>
|
||||
</div>
|
||||
</FocusScope>
|
||||
</Portal>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
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);
|
||||
`,
|
||||
};
|
||||
};
|
@ -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.
|
||||
|
@ -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() {
|
||||
</button>
|
||||
</Tooltip>
|
||||
<Tooltip placement="bottom" content="User profile (todo)">
|
||||
<button className={styles.actionItem}>
|
||||
<img src={contextSrv.user.gravatarUrl} />
|
||||
</button>
|
||||
<Dropdown overlay={ProfileMenu}>
|
||||
<button className={styles.actionItem}>
|
||||
<img src={contextSrv.user.gravatarUrl} />
|
||||
</button>
|
||||
</Dropdown>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* This is just temporary, needs syncing with the backend option like DisableSignoutMenu
|
||||
*/
|
||||
export function ProfileMenu() {
|
||||
return (
|
||||
<Menu>
|
||||
<MenuItem url="profile" label="Your profile" />
|
||||
<MenuItem url="profile/notifications" label="Your notifications" />
|
||||
<MenuItem url="logout" label="Sign out" />
|
||||
</Menu>
|
||||
);
|
||||
}
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2) => {
|
||||
return {
|
||||
searchBar: css({
|
||||
|
Loading…
Reference in New Issue
Block a user