Skip to content

Commit

Permalink
feat: Align with the new design in form for inline error display (#1683)
Browse files Browse the repository at this point in the history
* validation ui refine

* revert expression err message show logic

* use format-message for err message

* Add twoSettingField

* refactor editable field to use mergeStyleSets

* clean up ExpressionWidget

* add missing format message

* correct usage of format message

* update focus border style for rich editor

* validate LG and LU on mount in form

needed to get error UI

* fix form editor overflow issues
  • Loading branch information
alanlong9278 authored and a-b-r-o-w-n committed Dec 4, 2019
1 parent a470770 commit 997df62
Show file tree
Hide file tree
Showing 16 changed files with 264 additions and 108 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

.ObjectItem .ObjectItemField {
flex: 1;
overflow: hidden;
padding: 0px 18px;
margin: 10px 0;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,18 @@
// Licensed under the MIT License.

import React, { useState, useEffect } from 'react';
import { TextField, ITextFieldStyles, ITextFieldProps } from 'office-ui-fabric-react/lib/TextField';
import { TextField, ITextFieldProps } from 'office-ui-fabric-react/lib/TextField';
import { NeutralColors } from '@uifabric/fluent-theme';
import { mergeStyles } from '@uifabric/styling';
import { mergeStyleSets } from '@uifabric/styling';

interface EditableFieldProps extends ITextFieldProps {
onChange: (e: any, newTitle?: string) => void;
styleOverrides?: Partial<ITextFieldStyles>;
placeholder?: string;
fontSize?: string;
}

export const EditableField: React.FC<EditableFieldProps> = props => {
const { styleOverrides = {}, placeholder, fontSize, onChange, onBlur, value, ...rest } = props;
const { styles = {}, placeholder, fontSize, onChange, onBlur, value, ...rest } = props;
const [editing, setEditing] = useState<boolean>(false);
const [hasFocus, setHasFocus] = useState<boolean>(false);
const [localValue, setLocalValue] = useState<string | undefined>(value);
Expand Down Expand Up @@ -49,21 +48,18 @@ export const EditableField: React.FC<EditableFieldProps> = props => {
<TextField
placeholder={placeholder || value}
value={localValue}
styles={{
root: mergeStyles({ margin: '5px 0 7px -9px' }, styleOverrides.root),
field: mergeStyles(
{
styles={mergeStyleSets(
{
root: { margin: '5px 0 7px -9px' },
field: {
fontSize: fontSize,
selectors: {
'::placeholder': {
fontSize: fontSize,
},
},
},
styleOverrides.field
),
fieldGroup: mergeStyles(
{
fieldGroup: {
borderColor,
transition: 'border-color 0.1s linear',
selectors: {
Expand All @@ -72,9 +68,9 @@ export const EditableField: React.FC<EditableFieldProps> = props => {
},
},
},
styleOverrides.fieldGroup
),
}}
},
styles
)}
onBlur={handleCommit}
onFocus={() => setHasFocus(true)}
onChange={handleChange}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ const ChoiceItem: React.FC<ChoiceItemProps> = props => {
<EditableField
onChange={handleEdit('value')}
value={choice.value}
styleOverrides={{
styles={{
root: { margin: '5px 0 7px 0' },
}}
onBlur={handleBlur}
Expand All @@ -95,7 +95,7 @@ const ChoiceItem: React.FC<ChoiceItemProps> = props => {
onChange={handleEdit('synonyms')}
value={choice.synonyms && choice.synonyms.join(', ')}
placeholder={formatMessage('Add multiple comma-separated synonyms')}
styleOverrides={{
styles={{
root: { margin: '5px 0 7px 0' },
}}
onBlur={handleBlur}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ import { MicrosoftInputDialog } from '@bfc/shared';

import { TextWidget, CheckboxWidget } from '../../widgets';

import { field, settingsFields, settingsFieldHalf, settingsFieldFull, settingsFieldInline } from './styles';
import { PromptFieldChangeHandler, GetSchema } from './types';
import { TwoSettingFields } from './twoSettingFields';
import { field, settingsFields, settingsFieldFull, settingsFieldInline } from './styles';
import { PromptFieldChangeHandler, GetSchema, InputDialogKeys } from './types';

interface PromptSettingsrops extends FieldProps<MicrosoftInputDialog> {
onChange: PromptFieldChangeHandler;
Expand All @@ -19,52 +20,43 @@ interface PromptSettingsrops extends FieldProps<MicrosoftInputDialog> {

export const PromptSettings: React.FC<PromptSettingsrops> = props => {
const { formData, idSchema, getSchema, onChange, errorSchema } = props;
const fields: { [key: string]: InputDialogKeys | string }[] = [
{
name: 'maxTurnCount',
title: formatMessage('Max turn count'),
},
{
name: 'defaultValue',
title: formatMessage('Default value'),
},
];

return (
<div css={settingsFields}>
<div css={[field, settingsFieldHalf]}>
<TextWidget
onChange={onChange('maxTurnCount')}
schema={getSchema('maxTurnCount')}
id={idSchema.maxTurnCount.__id}
value={formData.maxTurnCount}
label={formatMessage('Max turn count')}
formContext={props.formContext}
rawErrors={errorSchema.maxTurnCount && errorSchema.maxTurnCount.__errors}
/>
</div>
<div css={[field, settingsFieldHalf]}>
<TextWidget
onChange={onChange('defaultValue')}
schema={getSchema('defaultValue')}
id={idSchema.defaultValue.__id}
value={formData.defaultValue}
label={formatMessage('Default value')}
formContext={props.formContext}
rawErrors={errorSchema.defaultValue && errorSchema.defaultValue.__errors}
/>
</div>
<div css={[field, settingsFieldFull]}>
<TextWidget
onChange={onChange('allowInterruptions')}
schema={getSchema('allowInterruptions')}
id={idSchema.allowInterruptions.__id}
value={formData.allowInterruptions}
label={formatMessage('Allow interruptions')}
formContext={props.formContext}
rawErrors={errorSchema.allowInterruptions && errorSchema.allowInterruptions.__errors}
/>
</div>
<div css={[field, settingsFieldFull, settingsFieldInline]}>
<CheckboxWidget
onChange={onChange('alwaysPrompt')}
schema={getSchema('alwaysPrompt')}
id={idSchema.alwaysPrompt.__id}
value={formData.alwaysPrompt}
label={formatMessage('Always prompt')}
formContext={props.formContext}
rawErrors={errorSchema.alwaysPrompt && errorSchema.alwaysPrompt.__errors}
/>
<div>
<TwoSettingFields fields={fields} {...props} />
<div css={settingsFields}>
<div css={[field, settingsFieldFull]}>
<TextWidget
onChange={onChange('allowInterruptions')}
schema={getSchema('allowInterruptions')}
id={idSchema.allowInterruptions.__id}
value={formData.allowInterruptions}
label={formatMessage('Allow interruptions')}
formContext={props.formContext}
rawErrors={errorSchema.allowInterruptions && errorSchema.allowInterruptions.__errors}
/>
</div>
<div css={[field, settingsFieldFull, settingsFieldInline]}>
<CheckboxWidget
onChange={onChange('alwaysPrompt')}
schema={getSchema('alwaysPrompt')}
id={idSchema.alwaysPrompt.__id}
value={formData.alwaysPrompt}
label={formatMessage('Always prompt')}
formContext={props.formContext}
rawErrors={errorSchema.alwaysPrompt && errorSchema.alwaysPrompt.__errors}
/>
</div>
</div>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,14 +49,17 @@ export const settingsContainer = css`
export const settingsFields = css`
display: flex;
flex-wrap: wrap;
position: relative;
`;

export const settingsFieldFull = css`
flex-basis: 100%;
overflow: hidden;
`;

export const settingsFieldHalf = css`
flex: 1;
overflow: hidden;
& + & {
margin-left: 36px;
Expand All @@ -67,6 +70,10 @@ export const settingsFieldInline = css`
margin: 0;
`;

export const settingsFieldValidation = css`
margin-top: -10px;
`;

export const choiceItemContainer = (align = 'center') => css`
display: flex;
align-items: ${align};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/* eslint-disable format-message/literal-pattern */
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

/** @jsx jsx */
import { jsx } from '@emotion/core';
import React, { useState } from 'react';
import formatMessage from 'format-message';
import { FieldProps } from '@bfcomposer/react-jsonschema-form';
import { MicrosoftInputDialog } from '@bfc/shared';

import { TextWidget } from '../../widgets';

import { field, settingsFieldHalf, settingsFields, settingsFieldFull, settingsFieldValidation } from './styles';
import { PromptFieldChangeHandler, GetSchema, InputDialogKeys } from './types';

interface TwoSettingFieldsProps extends FieldProps<MicrosoftInputDialog> {
onChange: PromptFieldChangeHandler;
getSchema: GetSchema;
fields: { [key: string]: InputDialogKeys | string }[];
}
export const TwoSettingFields: React.FC<TwoSettingFieldsProps> = props => {
const { fields, formData, idSchema, getSchema, onChange, errorSchema } = props;
const [errorMessage, setErrorMessage] = useState<JSX.Element | string | undefined>();

const onValidate = (err?: JSX.Element | string) => {
setErrorMessage(err);
};

return (
<div css={settingsFields}>
{fields.map((settingField, index) => (
<div key={index} css={[field, settingsFieldHalf]}>
<TextWidget
onChange={onChange(settingField.name as InputDialogKeys)}
schema={getSchema(settingField.name as InputDialogKeys)}
id={idSchema[settingField.name].__id}
value={formData[settingField.name]}
label={formatMessage(settingField.title as string)}
formContext={props.formContext}
rawErrors={errorSchema[settingField.name] && errorSchema[settingField.name].__errors}
hiddenErrMessage={true}
onValidate={onValidate}
/>
</div>
))}
<div css={[field, settingsFieldFull, settingsFieldValidation]}>{errorMessage}</div>
</div>
);
};
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

import React, { useState } from 'react';
import React, { useState, useEffect } from 'react';
import { LuEditor } from '@bfc/code-editor';
import { LuFile } from '@bfc/shared';

Expand All @@ -15,16 +15,19 @@ const InlineLuEditor: React.FC<InlineLuEditorProps> = props => {
const { file, onSave, errorMsg } = props;
const [content, setContent] = useState(file.content || '');

// save on mount to trigger validation
useEffect(() => {
if (content) {
onSave(content);
}
}, []);

const commitChanges = value => {
setContent(value);
onSave(value);
};

return (
<div style={{ margin: '10px 0', padding: '0 18px' }}>
<LuEditor value={content} onChange={commitChanges} errorMsg={errorMsg} height={450} />
</div>
);
return <LuEditor value={content} onChange={commitChanges} errorMsg={errorMsg} height={450} />;
};

export default InlineLuEditor;
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

import React from 'react';
/** @jsx jsx */
import { jsx } from '@emotion/core';
import { toIdSchema } from '@bfcomposer/react-jsonschema-form/lib/utils';
import { FieldProps } from '@bfcomposer/react-jsonschema-form';
import { JSONSchema6 } from 'json-schema';
import { MicrosoftIRecognizer } from '@bfc/shared';

import { regexEditorContainer } from './styles';

export default function RegexEditor(props: FieldProps<MicrosoftIRecognizer>) {
if (!props.schema.oneOf) {
return null;
Expand All @@ -25,5 +28,9 @@ export default function RegexEditor(props: FieldProps<MicrosoftIRecognizer>) {
) as JSONSchema6;
const idSchema = toIdSchema(schema, props.idSchema.__id, definitions, formData, idPrefix);

return <ObjectField {...props} schema={schema} idSchema={idSchema} />;
return (
<div css={regexEditorContainer}>
<ObjectField {...props} schema={schema} idSchema={idSchema} />
</div>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export default function ToggleEditor(props: ToggleEditorProps) {
? formatMessage('Hide {title}', { title: props.title })
: formatMessage('View {title}', { title: props.title })}
</Link>
<div className="ToggleEditorContent">{showEditor && props.children()}</div>
<div>{showEditor && props.children()}</div>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
.ToggleEditor .BaseField {
margin-top: 5px;
}
.ToggleEditor .ToggleEditorContent {
margin: 0 -18px -26px -18px; /* offset ObjectFieldTemplate padding */
}
.ToggleEditor .ToggleEditorContent .ObjectItemContainer:last-child {
margin-bottom: 0;
border-bottom: none;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

import { css } from '@emotion/core';

// offset ObjectField's margin
export const regexEditorContainer = css`
margin: 0 -18px -26px -18px;
`;
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ export const RootField: React.FC<RootFieldProps> = props => {
<EditableField
value={getTitle()}
onChange={handleTitleChange}
styleOverrides={{ field: { fontWeight: FontWeights.semibold } }}
styles={{ field: { fontWeight: FontWeights.semibold } }}
fontSize={FontSizes.size20}
/>
<p className="RootFieldSubtitle">{getSubTitle()}</p>
Expand Down
Loading

0 comments on commit 997df62

Please sign in to comment.