Selaa lähdekoodia

Fix Options fields changing in a confusing way

This issue is visible when changing the Openstack source 'Replica
Transfer Mechanism' field, which causes other fields to appear /
disappear, which is confusing, especially since the layout of the fields
also changes from vertical to horizontal.

As a UX hot fix, the layout of the options will always be horizontal and
a small animation will highlight the appearance of new fields.
Sergiu Miclea 5 vuotta sitten
vanhempi
sitoutus
9470700b09

+ 1 - 0
package.json

@@ -79,6 +79,7 @@
     "react-notification-system": "^0.4.0",
     "react-router-dom": "^5.1.2",
     "react-tooltip": "^4.2.7",
+    "react-transition-group": "1.x",
     "request": "^2.88.0",
     "require-without-cache": "^0.0.6",
     "rimraf": "^2.6.2",

+ 1 - 0
src/@types/Field.ts

@@ -31,6 +31,7 @@ export type Field = {
   enum?: EnumItem[],
   default?: any,
   password?: boolean,
+  disabled?: boolean,
   nullableBoolean?: boolean,
   items?: Field[],
   fields?: Field[],

+ 1 - 0
src/@types/declarations.d.ts

@@ -8,6 +8,7 @@ declare module '*.woff'
 declare module 'ansi-to-html'
 
 declare module 'require-without-cache'
+declare module 'react-transition-group'
 
 interface Window {
   /**

+ 6 - 1
src/components/molecules/FieldInput/FieldInput.tsx

@@ -59,6 +59,9 @@ const Label = styled.div<any>`
     align-items: center;
   `)}
   ${props => (props.disabledLoading ? StyleProps.animations.disabledLoading : '')}
+  ${props => (props.disabled ? css`
+    opacity: 0.5;
+  ` : '')}
 `
 const LabelText = styled.span``
 const Asterisk = styled.div<any>`
@@ -186,13 +189,14 @@ class FieldInput extends React.Component<Props> {
         properties={this.props.properties}
         valueCallback={field => this.props.valueCallback && this.props.valueCallback(field)}
         onChange={(field, value) => {
-          if (this.props.onChange) {
+          if (!this.props.disabled && this.props.onChange) {
             this.props.onChange(value, field)
           }
         }}
         labelRenderer={this.props.labelRenderer}
         hideRequiredSymbol={this.props.layout === 'page'}
         disabledLoading={this.props.disabledLoading}
+        disabled={this.props.disabled}
       />
     )
   }
@@ -374,6 +378,7 @@ class FieldInput extends React.Component<Props> {
         layout={this.props.layout}
         disabledLoading={this.props.disabledLoading}
         width={this.props.width}
+        disabled={this.props.disabled}
       >
         <LabelText style={{ marginRight }}>
           {this.props.label}

+ 9 - 1
src/components/molecules/PropertiesTable/PropertiesTable.tsx

@@ -32,6 +32,9 @@ const Wrapper = styled.div<any>`
   flex-direction: column;
   border: 1px solid ${Palette.grayscale[2]};
   border-radius: ${StyleProps.borderRadius};
+  ${props => (props.disabled ? css`
+    opacity: 0.5;
+  ` : '')}
   ${props => (props.disabledLoading ? StyleProps.animations.disabledLoading : '')}
 `
 const Column = styled.div<any>`
@@ -72,6 +75,7 @@ type Props = {
   onChange: (property: Field, value: any) => void,
   valueCallback: (property: Field) => any,
   hideRequiredSymbol?: boolean,
+  disabled?: boolean,
   disabledLoading?: boolean,
   labelRenderer?: ((propName: string) => string) | null,
   width?: number,
@@ -195,7 +199,11 @@ class PropertiesTable extends React.Component<Props> {
 
   render() {
     return (
-      <Wrapper disabledLoading={this.props.disabledLoading} width={this.props.width}>
+      <Wrapper
+        disabled={this.props.disabled}
+        disabledLoading={this.props.disabledLoading}
+        width={this.props.width}
+      >
         {this.props.properties.map(prop => (
           <Row key={prop.name} data-test-id={`${baseId}-row-${prop.name}`}>
             <Column header data-test-id={`${baseId}-header`}><span title={this.getName(prop.name)}>{this.getName(prop.name)}</span></Column>

+ 43 - 42
src/components/organisms/WizardOptions/WizardOptions.tsx

@@ -17,6 +17,7 @@ import styled from 'styled-components'
 import { observer } from 'mobx-react'
 import { toJS } from 'mobx'
 import autobind from 'autobind-decorator'
+import { CSSTransitionGroup } from 'react-transition-group'
 
 import configLoader from '../../../utils/Config'
 import StyleProps from '../../styleUtils/StyleProps'
@@ -57,6 +58,14 @@ const Group = styled.div<any>`
   display: flex;
   flex-direction: column;
   flex-shrink: 0;
+
+  &.field-group-transition-appear {
+    opacity: 0.01;
+  }
+  &.field-group-transition-appear-active {
+    opacity: 1;
+    transition: opacity 250ms ease-out;
+  }
 `
 const GroupName = styled.div<any>`
   display: flex;
@@ -76,7 +85,6 @@ const GroupFields = styled.div<any>`
   display: flex;
   justify-content: space-between;
 `
-const OneColumn = styled.div<any>``
 const Column = styled.div<any>`
   margin-top: -16px;
 `
@@ -218,13 +226,13 @@ class WizardOptions extends React.Component<Props> {
     if (this.props.wizardType === 'replica') {
       fieldsSchema.push({ name: 'execute_now', type: 'boolean', default: true })
       const executeNowValue = this.getFieldValue('execute_now', true)
-      if (executeNowValue) {
-        fieldsSchema.push({
-          name: 'execute_now_options',
-          type: 'object',
-          properties: executionOptions,
-        })
-      }
+      fieldsSchema.push({
+        name: 'execute_now_options',
+        type: 'object',
+        properties: executionOptions,
+        disabled: !executeNowValue,
+        description: !executeNowValue ? 'Enable \'Execute Now\' to set \'Execute Now Options\'' : `Set the options for ${this.props.wizardType} execution`,
+      })
     } else if (this.props.wizardType === 'migration' || this.props.wizardType === 'migration-destination-options-edit') {
       fieldsSchema = [...fieldsSchema, ...migrationFields]
     }
@@ -336,6 +344,7 @@ class WizardOptions extends React.Component<Props> {
         data-test-id={`wOptions-field-${field.name}`}
         width={this.props.fieldWidth || StyleProps.inputSizes.wizard.width}
         nullableBoolean={field.nullableBoolean}
+        disabled={field.disabled}
         disabledLoading={this.props.optionsLoading
           && !optionsLoadingReqFields.find(fn => fn === field.name)}
         // eslint-disable-next-line react/jsx-props-no-spreading
@@ -409,44 +418,36 @@ class WizardOptions extends React.Component<Props> {
       }
     })
 
-    const availableHeight = this.props.availableHeight || (window.innerHeight - 450)
-
-    if (fields.length * 96 < availableHeight) {
-      return (
-        <Fields padding={this.props.layout === 'page' ? null : 32}>
-          <Group>
-            <GroupFields style={{ justifyContent: 'center' }}>
-              <OneColumn style={this.props.oneColumnStyle}>
-                {fields.map(f => f.component)}
-              </OneColumn>
-            </GroupFields>
-          </Group>
-        </Fields>
-      )
-    }
-
     const groups = this.generateGroups(fields)
 
     return (
       <Fields ref={this.props.onScrollableRef} padding={this.props.layout === 'page' ? null : 32}>
-        {groups.map(g => (
-          <Group key={g.name || 0}>
-            {g.name ? (
-              <GroupName>
-                <GroupNameBar />
-                <GroupNameText>{LabelDictionary.get(g.name)}</GroupNameText>
-                <GroupNameBar />
-              </GroupName>
-            ) : null}
-            <GroupFields>
-              <Column left>
-                {g.fields.map(f => f.column === 0 && f.component)}
-              </Column>
-              <Column right>
-                {g.fields.map(f => f.column === 1 && f.component)}
-              </Column>
-            </GroupFields>
-          </Group>
+        {groups.map((g, i) => (
+          <CSSTransitionGroup
+            key={g.name || 0}
+            transitionName={i > 0 ? 'field-group-transition' : ''}
+            transitionAppear
+            transitionAppearTimeout={250}
+            in={false}
+          >
+            <Group>
+              {g.name ? (
+                <GroupName>
+                  <GroupNameBar />
+                  <GroupNameText>{LabelDictionary.get(g.name)}</GroupNameText>
+                  <GroupNameBar />
+                </GroupName>
+              ) : null}
+              <GroupFields>
+                <Column left>
+                  {g.fields.map(f => f.column === 0 && f.component)}
+                </Column>
+                <Column right>
+                  {g.fields.map(f => f.column === 1 && f.component)}
+                </Column>
+              </GroupFields>
+            </Group>
+          </CSSTransitionGroup>
         ))}
       </Fields>
     )

+ 30 - 0
yarn.lock

@@ -3551,6 +3551,11 @@ center-align@^0.1.1:
     align-text "^0.1.3"
     lazy-cache "^1.0.3"
 
+chain-function@^1.0.0:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/chain-function/-/chain-function-1.0.1.tgz#c63045e5b4b663fb86f1c6e186adaf1de402a1cc"
+  integrity sha512-SxltgMwL9uCko5/ZCLiyG2B7R9fY4pDZUw7hJ4MhirdjBLosoDqkWABi3XMucddHdLiFJMb7PD2MZifZriuMTg==
+
 chalk@2.4.2, chalk@^2.4.1, chalk@^2.4.2:
   version "2.4.2"
   resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
@@ -4830,6 +4835,13 @@ dom-converter@~0.1:
   dependencies:
     utila "~0.3"
 
+dom-helpers@^3.2.0:
+  version "3.4.0"
+  resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-3.4.0.tgz#e9b369700f959f62ecde5a6babde4bccd9169af8"
+  integrity sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA==
+  dependencies:
+    "@babel/runtime" "^7.1.2"
+
 dom-serializer@0:
   version "0.1.0"
   resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.0.tgz#073c697546ce0780ce23be4a28e293e40bc30c82"
@@ -10712,6 +10724,17 @@ react-tooltip@*, react-tooltip@^4.2.7:
     prop-types "^15.7.2"
     uuid "^7.0.3"
 
+react-transition-group@1.x:
+  version "1.2.1"
+  resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-1.2.1.tgz#e11f72b257f921b213229a774df46612346c7ca6"
+  integrity sha512-CWaL3laCmgAFdxdKbhhps+c0HRGF4c+hdM4H23+FI1QBNUyx/AMeIJGWorehPNSaKnQNOAxL7PQmqMu78CDj3Q==
+  dependencies:
+    chain-function "^1.0.0"
+    dom-helpers "^3.2.0"
+    loose-envify "^1.3.1"
+    prop-types "^15.5.6"
+    warning "^3.0.0"
+
 react@^16.13.1, react@^16.8.3:
   version "16.13.1"
   resolved "https://registry.yarnpkg.com/react/-/react-16.13.1.tgz#2e818822f1a9743122c063d6410d85c1e3afe48e"
@@ -12944,6 +12967,13 @@ vm-browserify@^1.0.1:
   resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0"
   integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==
 
+warning@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/warning/-/warning-3.0.0.tgz#32e5377cb572de4ab04753bdf8821c01ed605b7c"
+  integrity sha1-MuU3fLVy3kqwR1O9+IIcAe1gW3w=
+  dependencies:
+    loose-envify "^1.0.0"
+
 warning@^4.0.2, warning@^4.0.3:
   version "4.0.3"
   resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3"