diff --git a/packages/grafana-ui/src/components/Modal/Modal.story.tsx b/packages/grafana-ui/src/components/Modal/Modal.story.tsx index b729670d0bb..41f449e2f73 100644 --- a/packages/grafana-ui/src/components/Modal/Modal.story.tsx +++ b/packages/grafana-ui/src/components/Modal/Modal.story.tsx @@ -1,9 +1,11 @@ import React, { useState } from 'react'; import { oneLineTrim } from 'common-tags'; -import { text, boolean } from '@storybook/addon-knobs'; +import { boolean, text } from '@storybook/addon-knobs'; +import { Icon, Modal, ModalTabsHeader, TabContent } from '@grafana/ui'; +import { css, cx } from 'emotion'; + import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; import { UseState } from '../../utils/storybook/UseState'; -import { Modal, Icon, TabContent, ModalTabsHeader } from '@grafana/ui'; import mdx from './Modal.mdx'; const getKnobs = () => { @@ -89,3 +91,21 @@ export const WithTabs = () => { ); }; + +export const UsingContentClassName = () => { + const { body, visible } = getKnobs(); + const override = { + modalContent: css` + background-color: darkorange; + `, + }; + return ( + + {body} + + ); +}; diff --git a/packages/grafana-ui/src/components/Modal/Modal.test.tsx b/packages/grafana-ui/src/components/Modal/Modal.test.tsx index 49b3b7d63ba..4001fbfeee5 100644 --- a/packages/grafana-ui/src/components/Modal/Modal.test.tsx +++ b/packages/grafana-ui/src/components/Modal/Modal.test.tsx @@ -9,10 +9,10 @@ describe('Modal', () => { it('renders nothing by default or when isOpen is false', () => { const wrapper = mount(); - expect(wrapper.html()).toBe(''); + expect(wrapper.html()).toBe(null); wrapper.setProps({ ...wrapper.props(), isOpen: false }); - expect(wrapper.html()).toBe(''); + expect(wrapper.html()).toBe(null); }); it('renders correct contents', () => { diff --git a/packages/grafana-ui/src/components/Modal/Modal.tsx b/packages/grafana-ui/src/components/Modal/Modal.tsx index 0e4a19c2eb9..777a764b105 100644 --- a/packages/grafana-ui/src/components/Modal/Modal.tsx +++ b/packages/grafana-ui/src/components/Modal/Modal.tsx @@ -1,18 +1,19 @@ -import React from 'react'; +import React, { FC, PropsWithChildren, useCallback } from 'react'; import { Portal } from '../Portal/Portal'; import { cx } from 'emotion'; -import { withTheme } from '../../themes'; -import { IconName, Themeable } from '../../types'; +import { useTheme } from '../../themes'; +import { IconName } from '../../types'; import { getModalStyles } from './getModalStyles'; import { ModalHeader } from './ModalHeader'; import { IconButton } from '../IconButton/IconButton'; -export interface Props extends Themeable { +export interface Props { icon?: IconName; iconTooltip?: string; /** Title for the modal or custom header element */ title: string | JSX.Element; className?: string; + contentClassName?: string; isOpen?: boolean; onDismiss?: () => void; @@ -21,46 +22,51 @@ export interface Props extends Themeable { onClickBackdrop?: () => void; } -export class UnthemedModal extends React.PureComponent { - onDismiss = () => { - if (this.props.onDismiss) { - this.props.onDismiss(); +export function Modal(props: PropsWithChildren): ReturnType> { + const { + title, + children, + isOpen = false, + className, + contentClassName, + onDismiss: propsOnDismiss, + onClickBackdrop, + } = props; + const theme = useTheme(); + const styles = getModalStyles(theme); + const onDismiss = useCallback(() => { + if (propsOnDismiss) { + propsOnDismiss(); } - }; + }, [propsOnDismiss]); - onClickBackdrop = () => { - this.onDismiss(); - }; - - renderDefaultHeader(title: string) { - const { icon, iconTooltip } = this.props; - - return ; + if (!isOpen) { + return null; } - render() { - const { title, isOpen = false, theme, className } = this.props; - const styles = getModalStyles(theme); - - if (!isOpen) { - return null; - } - - return ( - -
-
- {typeof title === 'string' ? this.renderDefaultHeader(title) : title} -
- -
+ return ( + +
+
+ {typeof title === 'string' && } + {typeof title !== 'string' && title} +
+
-
{this.props.children}
-
- - ); - } +
{children}
+
+
+ + ); } -export const Modal = withTheme(UnthemedModal); +interface DefaultModalHeaderProps { + title: string; + icon?: IconName; + iconTooltip?: string; +} + +function DefaultModalHeader({ icon, iconTooltip, title }: DefaultModalHeaderProps): JSX.Element { + return ; +} diff --git a/public/app/features/admin/UserOrgs.tsx b/public/app/features/admin/UserOrgs.tsx index 7eb5f0ac67f..73688d93d43 100644 --- a/public/app/features/admin/UserOrgs.tsx +++ b/public/app/features/admin/UserOrgs.tsx @@ -1,18 +1,18 @@ import React, { PureComponent } from 'react'; import { css, cx } from 'emotion'; import { - Modal, - Themeable, - stylesFactory, - withTheme, - ConfirmButton, Button, - HorizontalGroup, + ConfirmButton, Container, Field, + HorizontalGroup, + Modal, + stylesFactory, + Themeable, + withTheme, } from '@grafana/ui'; import { GrafanaTheme } from '@grafana/data'; -import { UserOrg, Organization, OrgRole } from 'app/types'; +import { Organization, OrgRole, UserOrg } from 'app/types'; import { OrgPicker, OrgSelectItem } from 'app/core/components/Select/OrgPicker'; import { OrgRolePicker } from './OrgRolePicker'; @@ -180,6 +180,9 @@ const getAddToOrgModalStyles = stylesFactory(() => ({ buttonRow: css` text-align: center; `, + modalContent: css` + overflow: visible; + `, })); interface AddToOrgModalProps { @@ -224,9 +227,14 @@ export class AddToOrgModal extends PureComponent +