mirror of
https://github.com/grafana/grafana.git
synced 2025-02-12 00:25:46 -06:00
Admin: Fixes so whole org drop down is visible when adding users to org (#30481)
* Modal: Admin: Fixes so whole org drop down is visible * Tests: fixes failing tests * Chore: cleans up the return type * Chore: changes after PR comments * Chore: changes after PR comments
This commit is contained in:
parent
1588d7235c
commit
ffd39933d4
@ -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 = () => {
|
||||
</UseState>
|
||||
);
|
||||
};
|
||||
|
||||
export const UsingContentClassName = () => {
|
||||
const { body, visible } = getKnobs();
|
||||
const override = {
|
||||
modalContent: css`
|
||||
background-color: darkorange;
|
||||
`,
|
||||
};
|
||||
return (
|
||||
<Modal
|
||||
title="Using contentClassName to override background"
|
||||
isOpen={visible}
|
||||
contentClassName={cx(override.modalContent)}
|
||||
>
|
||||
{body}
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
@ -9,10 +9,10 @@ describe('Modal', () => {
|
||||
|
||||
it('renders nothing by default or when isOpen is false', () => {
|
||||
const wrapper = mount(<Modal title={'Some Title'} />);
|
||||
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', () => {
|
||||
|
@ -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<Props> {
|
||||
onDismiss = () => {
|
||||
if (this.props.onDismiss) {
|
||||
this.props.onDismiss();
|
||||
export function Modal(props: PropsWithChildren<Props>): ReturnType<FC<Props>> {
|
||||
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 <ModalHeader icon={icon} iconTooltip={iconTooltip} title={title} />;
|
||||
if (!isOpen) {
|
||||
return null;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { title, isOpen = false, theme, className } = this.props;
|
||||
const styles = getModalStyles(theme);
|
||||
|
||||
if (!isOpen) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Portal>
|
||||
<div className={cx(styles.modal, className)}>
|
||||
<div className={styles.modalHeader}>
|
||||
{typeof title === 'string' ? this.renderDefaultHeader(title) : title}
|
||||
<div className={styles.modalHeaderClose}>
|
||||
<IconButton surface="header" name="times" size="lg" onClick={this.onDismiss} />
|
||||
</div>
|
||||
return (
|
||||
<Portal>
|
||||
<div className={cx(styles.modal, className)}>
|
||||
<div className={styles.modalHeader}>
|
||||
{typeof title === 'string' && <DefaultModalHeader {...props} title={title} />}
|
||||
{typeof title !== 'string' && title}
|
||||
<div className={styles.modalHeaderClose}>
|
||||
<IconButton surface="header" name="times" size="lg" onClick={onDismiss} />
|
||||
</div>
|
||||
<div className={styles.modalContent}>{this.props.children}</div>
|
||||
</div>
|
||||
<div className={styles.modalBackdrop} onClick={this.props.onClickBackdrop || this.onClickBackdrop} />
|
||||
</Portal>
|
||||
);
|
||||
}
|
||||
<div className={cx(styles.modalContent, contentClassName)}>{children}</div>
|
||||
</div>
|
||||
<div className={styles.modalBackdrop} onClick={onClickBackdrop || onDismiss} />
|
||||
</Portal>
|
||||
);
|
||||
}
|
||||
|
||||
export const Modal = withTheme(UnthemedModal);
|
||||
interface DefaultModalHeaderProps {
|
||||
title: string;
|
||||
icon?: IconName;
|
||||
iconTooltip?: string;
|
||||
}
|
||||
|
||||
function DefaultModalHeader({ icon, iconTooltip, title }: DefaultModalHeaderProps): JSX.Element {
|
||||
return <ModalHeader icon={icon} iconTooltip={iconTooltip} title={title} />;
|
||||
}
|
||||
|
@ -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<AddToOrgModalProps, AddToOrgMod
|
||||
const { isOpen } = this.props;
|
||||
const { role } = this.state;
|
||||
const styles = getAddToOrgModalStyles();
|
||||
|
||||
return (
|
||||
<Modal className={styles.modal} title="Add to an organization" isOpen={isOpen} onDismiss={this.onCancel}>
|
||||
<Modal
|
||||
className={styles.modal}
|
||||
contentClassName={styles.modalContent}
|
||||
title="Add to an organization"
|
||||
isOpen={isOpen}
|
||||
onDismiss={this.onCancel}
|
||||
>
|
||||
<Field label="Organisation">
|
||||
<OrgPicker onSelected={this.onOrgSelect} />
|
||||
</Field>
|
||||
|
Loading…
Reference in New Issue
Block a user