Commit 0132bca9 by Torkel Ödegaard Committed by GitHub

Notifications: UX tweak to redesign of form and sections to make it left aligned…

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
parent a7ac3f14
import React, { FC, ReactNode, useState } from 'react'; import React, { FC, ReactNode, useState } from 'react';
import { css } from 'emotion'; import { css, cx } from 'emotion';
import { GrafanaTheme } from '@grafana/data'; import { GrafanaTheme } from '@grafana/data';
import { useStyles } from '../../themes'; import { useStyles } from '../../themes';
import { Icon } from '..'; import { Icon } from '..';
...@@ -13,14 +13,20 @@ export interface Props { ...@@ -13,14 +13,20 @@ export interface Props {
export const CollapsableSection: FC<Props> = ({ label, isOpen, children }) => { export const CollapsableSection: FC<Props> = ({ label, isOpen, children }) => {
const [open, toggleOpen] = useState<boolean>(isOpen); const [open, toggleOpen] = useState<boolean>(isOpen);
const styles = useStyles(collapsableSectionStyles); const styles = useStyles(collapsableSectionStyles);
const headerClass = cx({
[styles.header]: true,
[styles.headerCollapsed]: !open,
});
const tooltip = `Click to ${open ? 'collapse' : 'expand'}`;
return ( return (
<div> <div>
<div onClick={() => toggleOpen(!open)} className={styles.header}> <div onClick={() => toggleOpen(!open)} className={headerClass} title={tooltip}>
<Icon name={open ? 'angle-down' : 'angle-right'} size="xl" />
{label} {label}
<Icon name={open ? 'angle-down' : 'angle-right'} size="xl" className={styles.icon} />
</div> </div>
<div className={styles.content}>{open && children}</div> {open && <div className={styles.content}>{children}</div>}
</div> </div>
); );
}; };
...@@ -28,11 +34,19 @@ export const CollapsableSection: FC<Props> = ({ label, isOpen, children }) => { ...@@ -28,11 +34,19 @@ export const CollapsableSection: FC<Props> = ({ label, isOpen, children }) => {
const collapsableSectionStyles = (theme: GrafanaTheme) => { const collapsableSectionStyles = (theme: GrafanaTheme) => {
return { return {
header: css` header: css`
display: flex;
justify-content: space-between;
font-size: ${theme.typography.size.lg}; font-size: ${theme.typography.size.lg};
cursor: pointer; cursor: pointer;
`, `,
headerCollapsed: css`
border-bottom: 1px solid ${theme.colors.border2};
`,
icon: css`
color: ${theme.colors.textWeak};
`,
content: css` 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> { ...@@ -86,7 +86,7 @@ export class EditNotificationChannelPage extends PureComponent<Props> {
<h2 className="page-sub-heading">Edit notification channel</h2> <h2 className="page-sub-heading">Edit notification channel</h2>
{notificationChannel && notificationChannel.id > 0 ? ( {notificationChannel && notificationChannel.id > 0 ? (
<Form <Form
width={600} maxWidth={600}
onSubmit={this.onSubmit} onSubmit={this.onSubmit}
defaultValues={{ defaultValues={{
...notificationChannel, ...notificationChannel,
......
...@@ -52,7 +52,7 @@ class NewNotificationChannelPage extends PureComponent<Props> { ...@@ -52,7 +52,7 @@ class NewNotificationChannelPage extends PureComponent<Props> {
<Page navModel={navModel}> <Page navModel={navModel}>
<Page.Contents> <Page.Contents>
<h2 className="page-sub-heading">New notification channel</h2> <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 }) => { {({ register, errors, control, getValues, watch }) => {
const selectedChannel = notificationChannelTypes.find(c => c.value === getValues().type.value); const selectedChannel = notificationChannelTypes.find(c => c.value === getValues().type.value);
......
import React, { FC } from 'react'; import React, { FC } from 'react';
import { SelectableValue } from '@grafana/data'; 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 { NotificationChannelOptions } from './NotificationChannelOptions';
import { NotificationSettingsProps } from './NotificationChannelForm'; import { NotificationSettingsProps } from './NotificationChannelForm';
import { NotificationChannelSecureFields, NotificationChannelType } from '../../../types'; import { NotificationChannelSecureFields, NotificationChannelType } from '../../../types';
...@@ -23,7 +23,7 @@ export const BasicSettings: FC<Props> = ({ ...@@ -23,7 +23,7 @@ export const BasicSettings: FC<Props> = ({
resetSecureField, resetSecureField,
}) => { }) => {
return ( return (
<CollapsableSection label="Channel" isOpen> <>
<Field label="Name" invalid={!!errors.name} error={errors.name && errors.name.message}> <Field label="Name" invalid={!!errors.name} error={errors.name && errors.name.message}>
<Input name="name" ref={register({ required: 'Name is required' })} /> <Input name="name" ref={register({ required: 'Name is required' })} />
</Field> </Field>
...@@ -39,6 +39,6 @@ export const BasicSettings: FC<Props> = ({ ...@@ -39,6 +39,6 @@ export const BasicSettings: FC<Props> = ({
errors={errors} errors={errors}
control={control} control={control}
/> />
</CollapsableSection> </>
); );
}; };
...@@ -41,9 +41,14 @@ export const NotificationChannelForm: FC<Props> = ({ ...@@ -41,9 +41,14 @@ export const NotificationChannelForm: FC<Props> = ({
}, []); }, []);
const currentFormValues = getValues(); const currentFormValues = getValues();
return selectedChannel ? (
<> if (!selectedChannel) {
<div className={styles.basicSettings}> return <Spinner />;
}
return (
<div className={styles.formContainer}>
<div className={styles.formItem}>
<BasicSettings <BasicSettings
selectedChannel={selectedChannel} selectedChannel={selectedChannel}
channels={selectableChannels} channels={selectableChannels}
...@@ -54,8 +59,10 @@ export const NotificationChannelForm: FC<Props> = ({ ...@@ -54,8 +59,10 @@ export const NotificationChannelForm: FC<Props> = ({
errors={errors} errors={errors}
control={control} control={control}
/> />
{/* If there are no non-required fields, don't render this section*/} </div>
{selectedChannel.options.filter(o => !o.required).length > 0 && ( {/* 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 <ChannelSettings
selectedChannel={selectedChannel} selectedChannel={selectedChannel}
secureFields={secureFields} secureFields={secureFields}
...@@ -65,7 +72,9 @@ export const NotificationChannelForm: FC<Props> = ({ ...@@ -65,7 +72,9 @@ export const NotificationChannelForm: FC<Props> = ({
errors={errors} errors={errors}
control={control} control={control}
/> />
)} </div>
)}
<div className={styles.formItem}>
<NotificationSettings <NotificationSettings
imageRendererAvailable={imageRendererAvailable} imageRendererAvailable={imageRendererAvailable}
currentFormValues={currentFormValues} currentFormValues={currentFormValues}
...@@ -74,27 +83,32 @@ export const NotificationChannelForm: FC<Props> = ({ ...@@ -74,27 +83,32 @@ export const NotificationChannelForm: FC<Props> = ({
control={control} control={control}
/> />
</div> </div>
<HorizontalGroup> <div className={styles.formButtons}>
<Button type="submit">Save</Button> <HorizontalGroup>
<Button type="button" variant="secondary" onClick={() => onTestChannel(getValues({ nest: true }))}> <Button type="submit">Save</Button>
Test <Button type="button" variant="secondary" onClick={() => onTestChannel(getValues({ nest: true }))}>
</Button> Test
<a href="/alerting/notifications">
<Button type="button" variant="secondary">
Back
</Button> </Button>
</a> <a href="/alerting/notifications">
</HorizontalGroup> <Button type="button" variant="secondary">
</> Back
) : ( </Button>
<Spinner /> </a>
</HorizontalGroup>
</div>
</div>
); );
}; };
const getStyles = stylesFactory((theme: GrafanaTheme) => { const getStyles = stylesFactory((theme: GrafanaTheme) => {
return { return {
basicSettings: css` formContainer: css``,
margin-bottom: ${theme.spacing.xl}; 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> = ({ ...@@ -59,9 +59,14 @@ export const NotificationChannelOptions: FC<Props> = ({
<Input <Input
readOnly={true} readOnly={true}
value="Configured" value="Configured"
addonAfter={ suffix={
<Button onClick={() => onResetSecureField(option.propertyName)} variant="secondary" type="button"> <Button
Reset onClick={() => onResetSecureField(option.propertyName)}
variant="link"
type="button"
size="sm"
>
Clear
</Button> </Button>
} }
/> />
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment