Skip to content

Commit

Permalink
feat(core/presentation): Migrate ValidationMessage to new CSS styles (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
christopherthielen committed Oct 3, 2019
1 parent 523c6bb commit 3c08b38
Show file tree
Hide file tree
Showing 8 changed files with 115 additions and 116 deletions.
Original file line number Diff line number Diff line change
@@ -1,33 +1,13 @@
import * as React from 'react';
import { Modal } from 'react-bootstrap';
import { HelpField, HelpContentsRegistry } from '@spinnaker/core';
import { HelpField, HelpContentsRegistry, ValidationMessage } from '@spinnaker/core';

export interface IPolicyTypeSelectionModalProps {
showCallback: () => void;
typeSelectedCallback: (type: string) => void;
warnOnMinMaxCapacity?: boolean;
}

// TODO: Replace with the custom icomoon version when it arrives
const StubbedInInfoIcon = () => (
<div
style={{
border: '1px solid var(--color-accent)',
borderRadius: '50%',
fontWeight: 900,
width: '25px',
height: '24px',
lineHeight: '24px',
textAlign: 'center',
margin: '7px',
paddingLeft: '1px',
fontFamily: 'Courier New',
}}
>
i
</div>
);

export function PolicyTypeSelectionModal(props: IPolicyTypeSelectionModalProps) {
const [typeSelection, setTypeSelection] = React.useState(null);

Expand Down Expand Up @@ -63,27 +43,24 @@ export function PolicyTypeSelectionModal(props: IPolicyTypeSelectionModalProps)
</div>
</div>
{hasCustomHelpMessage && (
<div className="messageContainer previewMessage">
<StubbedInInfoIcon />
<div className="message">
<HelpField id={customHelpKey} expand={true} />
</div>
</div>
<ValidationMessage type="info" message={<HelpField id={customHelpKey} expand={true} />} />
)}
{props.warnOnMinMaxCapacity && (
<div className="messageContainer warningMessage">
<i className="fa icon-alert-triangle" />
<div className="message">
<p>
This server group's <em>min</em> and <em>max</em> capacity are identical, so scaling policies will have{' '}
<b>no effect.</b>
</p>
<p>
Scaling policies work by adjusting the server group's <em>desired</em> capacity to a value between the
min and max.
</p>
</div>
</div>
<ValidationMessage
type="warning"
message={
<>
<p>
This server group's <em>min</em> and <em>max</em> capacity are identical, so scaling policies will
have <b>no effect.</b>
</p>
<p>
Scaling policies work by adjusting the server group's <em>desired</em> capacity to a value between the
min and max.
</p>
</>
}
/>
)}
</Modal.Body>
<Modal.Footer>
Expand Down
33 changes: 0 additions & 33 deletions app/scripts/modules/core/src/presentation/forms/forms.less
Original file line number Diff line number Diff line change
Expand Up @@ -175,36 +175,3 @@
background-color: var(--color-danger);
}
}

.messageContainer {
min-height: 40px;
display: flex;
margin-bottom: 2px;

&:last-child {
margin-bottom: 8px;
}

i {
flex: 0 0 40px;
height: 40px;
font-size: 24px;
padding: 7px;
}

.message {
margin: 8px 8px 8px 0;
align-self: center;
}
}
.warningMessage {
background-color: rgba(255, 220, 60, 0.4);
}
.previewMessage {
background-color: rgba(60, 200, 255, 0.15);
color: var(--color-accent);
}
.errorMessage {
background-color: rgba(255, 0, 0, 0.1);
color: var(--color-danger);
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as React from 'react';
import * as classNames from 'classnames';

import { HelpContextProvider } from 'core/help';
import { ValidationMessage } from 'core/validation';

import { IFieldLayoutProps } from '../interface';

Expand Down Expand Up @@ -31,18 +31,7 @@ export class ResponsiveFieldLayout extends React.Component<IFieldLayoutProps> {
</span>
</div>
{helpUnder && help && <div className="description">{help}</div>}
{validationMessage && (
<div
className={classNames('messageContainer', {
errorMessage: validationStatus === 'error',
warningMessage: validationStatus === 'warning',
previewMessage: validationStatus === 'message',
})}
>
<i />
<div className="message">{validationMessage}</div>
</div>
)}
{validationMessage && <ValidationMessage type={validationStatus} message={validationMessage} />}
</div>
</div>
</HelpContextProvider>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,15 @@ describe('categorizedErrors', () => {
const emptyErrors = Object.freeze({
async: {},
error: {},
info: {},
message: {},
success: {},
warning: {},
});

it('returns an object with all error categories as keys', () => {
const categories = categorizeErrors({});
expect(Object.keys(categories).sort()).toEqual(['async', 'error', 'message', 'success', 'warning']);
expect(Object.keys(categories).sort()).toEqual(['async', 'error', 'info', 'message', 'success', 'warning']);
});

it('categorizes an unlabeled error message into "error"', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export type IValidator = (value: any, label?: string) => IValidatorResult;
export const categoryLabels = {
async: 'Async',
error: 'Error',
info: 'Info',
message: 'Message',
success: 'Success',
warning: 'Warning',
Expand Down
42 changes: 42 additions & 0 deletions app/scripts/modules/core/src/validation/ValidationMessage.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
.ValidationMessage {
min-height: 40px;
display: flex;
margin-bottom: 2px;

&.errorMessage {
color: var(--color-validation-error);
background-color: var(--color-validation-error-light);
}

&.infoMessage {
color: var(--color-validation-info);
background-color: var(--color-validation-info-light);
}

&.previewMessage {
color: var(--color-validation-info);
background-color: var(--color-validation-info-light);
}

&.successMessage {
color: var(--color-validation-success);
background-color: var(--color-validation-success-light);
}

&.warningMessage {
color: var(--color-validation-black);
background-color: var(--color-validation-warning-light);
}

i {
flex: 0 0 40px;
height: 40px;
font-size: 24px;
padding: 7px;
}

.message {
margin: 8px 8px 8px 0;
align-self: center;
}
}
54 changes: 37 additions & 17 deletions app/scripts/modules/core/src/validation/ValidationMessage.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,50 @@
import * as React from 'react';
import { IValidationCategory } from '../presentation/forms/validation';
import { ICategorizedErrors, IValidationCategory } from '../presentation/forms/validation';

export interface IValidationMessageProps {
message: React.ReactNode;
type: IValidationCategory | undefined;
// default: true
showIcon?: boolean;
}
import './ValidationMessage.less';

const iconClassName = {
success: 'far fa-check-circle',
const containerClassNames: ICategorizedErrors = {
async: 'infoMessage',
error: 'errorMessage',
info: 'infoMessage',
message: 'infoMessage',
success: 'successMessage',
warning: 'warningMessage',
};

const iconClassNames: ICategorizedErrors = {
async: 'fa fa-spinner fa-spin',
error: 'fa fa-exclamation-circle',
warning: 'fa fa-exclamation-circle',
info: 'fa fa-info-circle',
message: 'icon-view-1',
async: 'fa fa-spinner fa-spin',
none: '',
success: 'far fa-check-circle',
warning: 'fa icon-alert-triangle',
};

export interface IValidationMessageProps {
message: React.ReactNode;
type: IValidationCategory | undefined;
/**
* The (optional) class name to apply to the icon.
* If none is provided, the icon is determined by the value of category.
* If false to provided, no icon will be rendered
*/
iconClassName?: string | false;
/**
* The (optional) class name to apply to the ValidationMessage.
* If none is provided, the class is determined by the value of category.
*/
containerClassName?: string;
}

export const ValidationMessage = (props: IValidationMessageProps) => {
const divClassName = `${props.type}-message`;
const showIcon = props.showIcon === undefined ? true : props.showIcon;
const spanClassName = (showIcon && iconClassName[props.type]) || '';
const { type, message, iconClassName, containerClassName } = props;
const showIcon = iconClassName !== false;

return (
<div className={divClassName}>
<span className={spanClassName} /> {props.message}
<div className={`ValidationMessage ${containerClassName || containerClassNames[type] || ''}`}>
{showIcon && <i className={iconClassName || iconClassNames[type] || ''} />}
<div className="message">{message}</div>
</div>
);
};
26 changes: 14 additions & 12 deletions app/scripts/modules/docker/src/image/DockerImageAndTagSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as React from 'react';
import Select, { Option } from 'react-select';
import { groupBy, reduce, trim, uniq } from 'lodash';

import { AccountService, HelpField, IAccount, IFindImageParams, Tooltip } from '@spinnaker/core';
import { AccountService, HelpField, IAccount, IFindImageParams, Tooltip, ValidationMessage } from '@spinnaker/core';

import { DockerImageReader, IDockerImage } from './DockerImageReader';
import { DockerImageUtils, IDockerImageParts } from './DockerImageUtils';
Expand Down Expand Up @@ -468,17 +468,19 @@ export class DockerImageAndTagSelector extends React.Component<
<div className="sp-formItem">
<div className="sp-formItem__left" />
<div className="sp-formItem__right">
<div className="messageContainer warningMessage">
<i className="fa icon-alert-triangle" />
<div className="message">
{switchedManualWarning}
{(missingFields || []).map(f => (
<div>
<HelpField expand={true} key={f} id={`pipeline.config.docker.trigger.missing.${f}`} />
</div>
))}
</div>
</div>
<ValidationMessage
type="warning"
message={
<>
{switchedManualWarning}
{(missingFields || []).map(f => (
<div key={f}>
<HelpField expand={true} id={`pipeline.config.docker.trigger.missing.${f}`} />
</div>
))}
</>
}
/>
</div>
</div>
) : null;
Expand Down

0 comments on commit 3c08b38

Please sign in to comment.