Notifications: UX tweak to redesign of form and sections to make it left aligned and cleaner (#27479)
* UX: Redesign of form and sections to make it left aligned and cleaner
* reduced padding
* design tweaks
(cherry picked from commit 0132bca93a)
This commit is contained in:
committed by
Hugo Häggmark
parent
a2b97958bd
commit
89dbb0f074
@@ -1,5 +1,5 @@
|
||||
import React, { FC, ReactNode, useState } from 'react';
|
||||
import { css } from 'emotion';
|
||||
import { css, cx } from 'emotion';
|
||||
import { GrafanaTheme } from '@grafana/data';
|
||||
import { useStyles } from '../../themes';
|
||||
import { Icon } from '..';
|
||||
@@ -13,14 +13,20 @@ export interface Props {
|
||||
export const CollapsableSection: FC<Props> = ({ label, isOpen, children }) => {
|
||||
const [open, toggleOpen] = useState<boolean>(isOpen);
|
||||
const styles = useStyles(collapsableSectionStyles);
|
||||
const headerClass = cx({
|
||||
[styles.header]: true,
|
||||
[styles.headerCollapsed]: !open,
|
||||
});
|
||||
|
||||
const tooltip = `Click to ${open ? 'collapse' : 'expand'}`;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div onClick={() => toggleOpen(!open)} className={styles.header}>
|
||||
<Icon name={open ? 'angle-down' : 'angle-right'} size="xl" />
|
||||
<div onClick={() => toggleOpen(!open)} className={headerClass} title={tooltip}>
|
||||
{label}
|
||||
<Icon name={open ? 'angle-down' : 'angle-right'} size="xl" className={styles.icon} />
|
||||
</div>
|
||||
<div className={styles.content}>{open && children}</div>
|
||||
{open && <div className={styles.content}>{children}</div>}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -28,11 +34,19 @@ export const CollapsableSection: FC<Props> = ({ label, isOpen, children }) => {
|
||||
const collapsableSectionStyles = (theme: GrafanaTheme) => {
|
||||
return {
|
||||
header: css`
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: ${theme.typography.size.lg};
|
||||
cursor: pointer;
|
||||
`,
|
||||
headerCollapsed: css`
|
||||
border-bottom: 1px solid ${theme.colors.border2};
|
||||
`,
|
||||
icon: css`
|
||||
color: ${theme.colors.textWeak};
|
||||
`,
|
||||
content: css`
|
||||
padding: ${theme.spacing.md} 0 ${theme.spacing.md} ${theme.spacing.md};
|
||||
padding: ${theme.spacing.md} 0;
|
||||
`,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -86,7 +86,7 @@ export class EditNotificationChannelPage extends PureComponent<Props> {
|
||||
<h2 className="page-sub-heading">Edit notification channel</h2>
|
||||
{notificationChannel && notificationChannel.id > 0 ? (
|
||||
<Form
|
||||
width={600}
|
||||
maxWidth={600}
|
||||
onSubmit={this.onSubmit}
|
||||
defaultValues={{
|
||||
...notificationChannel,
|
||||
|
||||
@@ -52,7 +52,7 @@ class NewNotificationChannelPage extends PureComponent<Props> {
|
||||
<Page navModel={navModel}>
|
||||
<Page.Contents>
|
||||
<h2 className="page-sub-heading">New notification channel</h2>
|
||||
<Form onSubmit={this.onSubmit} validateOn="onChange" defaultValues={defaultValues}>
|
||||
<Form onSubmit={this.onSubmit} validateOn="onChange" defaultValues={defaultValues} maxWidth={600}>
|
||||
{({ register, errors, control, getValues, watch }) => {
|
||||
const selectedChannel = notificationChannelTypes.find(c => c.value === getValues().type.value);
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { FC } from 'react';
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { CollapsableSection, Field, Input, InputControl, Select } from '@grafana/ui';
|
||||
import { Field, Input, InputControl, Select } from '@grafana/ui';
|
||||
import { NotificationChannelOptions } from './NotificationChannelOptions';
|
||||
import { NotificationSettingsProps } from './NotificationChannelForm';
|
||||
import { NotificationChannelSecureFields, NotificationChannelType } from '../../../types';
|
||||
@@ -23,7 +23,7 @@ export const BasicSettings: FC<Props> = ({
|
||||
resetSecureField,
|
||||
}) => {
|
||||
return (
|
||||
<CollapsableSection label="Channel" isOpen>
|
||||
<>
|
||||
<Field label="Name" invalid={!!errors.name} error={errors.name && errors.name.message}>
|
||||
<Input name="name" ref={register({ required: 'Name is required' })} />
|
||||
</Field>
|
||||
@@ -39,6 +39,6 @@ export const BasicSettings: FC<Props> = ({
|
||||
errors={errors}
|
||||
control={control}
|
||||
/>
|
||||
</CollapsableSection>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -41,9 +41,14 @@ export const NotificationChannelForm: FC<Props> = ({
|
||||
}, []);
|
||||
|
||||
const currentFormValues = getValues();
|
||||
return selectedChannel ? (
|
||||
<>
|
||||
<div className={styles.basicSettings}>
|
||||
|
||||
if (!selectedChannel) {
|
||||
return <Spinner />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.formContainer}>
|
||||
<div className={styles.formItem}>
|
||||
<BasicSettings
|
||||
selectedChannel={selectedChannel}
|
||||
channels={selectableChannels}
|
||||
@@ -54,8 +59,10 @@ export const NotificationChannelForm: FC<Props> = ({
|
||||
errors={errors}
|
||||
control={control}
|
||||
/>
|
||||
{/* If there are no non-required fields, don't render this section*/}
|
||||
{selectedChannel.options.filter(o => !o.required).length > 0 && (
|
||||
</div>
|
||||
{/* If there are no non-required fields, don't render this section*/}
|
||||
{selectedChannel.options.filter(o => !o.required).length > 0 && (
|
||||
<div className={styles.formItem}>
|
||||
<ChannelSettings
|
||||
selectedChannel={selectedChannel}
|
||||
secureFields={secureFields}
|
||||
@@ -65,7 +72,9 @@ export const NotificationChannelForm: FC<Props> = ({
|
||||
errors={errors}
|
||||
control={control}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<div className={styles.formItem}>
|
||||
<NotificationSettings
|
||||
imageRendererAvailable={imageRendererAvailable}
|
||||
currentFormValues={currentFormValues}
|
||||
@@ -74,27 +83,32 @@ export const NotificationChannelForm: FC<Props> = ({
|
||||
control={control}
|
||||
/>
|
||||
</div>
|
||||
<HorizontalGroup>
|
||||
<Button type="submit">Save</Button>
|
||||
<Button type="button" variant="secondary" onClick={() => onTestChannel(getValues({ nest: true }))}>
|
||||
Test
|
||||
</Button>
|
||||
<a href="/alerting/notifications">
|
||||
<Button type="button" variant="secondary">
|
||||
Back
|
||||
<div className={styles.formButtons}>
|
||||
<HorizontalGroup>
|
||||
<Button type="submit">Save</Button>
|
||||
<Button type="button" variant="secondary" onClick={() => onTestChannel(getValues({ nest: true }))}>
|
||||
Test
|
||||
</Button>
|
||||
</a>
|
||||
</HorizontalGroup>
|
||||
</>
|
||||
) : (
|
||||
<Spinner />
|
||||
<a href="/alerting/notifications">
|
||||
<Button type="button" variant="secondary">
|
||||
Back
|
||||
</Button>
|
||||
</a>
|
||||
</HorizontalGroup>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const getStyles = stylesFactory((theme: GrafanaTheme) => {
|
||||
return {
|
||||
basicSettings: css`
|
||||
margin-bottom: ${theme.spacing.xl};
|
||||
formContainer: css``,
|
||||
formItem: css`
|
||||
flex-grow: 1;
|
||||
padding-top: ${theme.spacing.md};
|
||||
`,
|
||||
formButtons: css`
|
||||
padding-top: ${theme.spacing.xl};
|
||||
`,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -59,9 +59,14 @@ export const NotificationChannelOptions: FC<Props> = ({
|
||||
<Input
|
||||
readOnly={true}
|
||||
value="Configured"
|
||||
addonAfter={
|
||||
<Button onClick={() => onResetSecureField(option.propertyName)} variant="secondary" type="button">
|
||||
Reset
|
||||
suffix={
|
||||
<Button
|
||||
onClick={() => onResetSecureField(option.propertyName)}
|
||||
variant="link"
|
||||
type="button"
|
||||
size="sm"
|
||||
>
|
||||
Clear
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user