Просмотр исходного кода

Improve Wizard additional options loading UI

Don't cover the screen with the loading animation while loading
destination options.

An individual 'disabled loading' animation is displayed for each field input.

Fields need to be disabled while the destination options are loading,
because the destination options result directly affects what values can
be entered into an input.
Sergiu Miclea 6 лет назад
Родитель
Сommit
f2f134b18a

+ 9 - 4
src/components/atoms/AutocompleteInput/AutocompleteInput.jsx

@@ -53,10 +53,12 @@ const Wrapper = styled.div`
   border-radius: ${StyleProps.borderRadius};
   cursor: ${props => props.disabled ? 'default' : 'pointer'};
   transition: all ${StyleProps.animations.swift};
-  background: ${props => props.disabled ? Palette.grayscale[0] : 'white'};
+  background: ${props => props.disabled && !props.embedded ? Palette.grayscale[0] : 'white'};
 
   #dropdown-arrow-image {stroke: ${props => props.disabled ? Palette.grayscale[3] : Palette.black};}
   ${props => props.focus ? css`border-color: ${Palette.primary};` : ''}
+  ${props => props.disabledLoading ? StyleProps.animations.disabledLoading : ''}
+
 `
 const Arrow = styled.div`
   position: absolute;
@@ -75,6 +77,7 @@ type Props = {
   onChange: (value: string) => void,
   onClick?: () => void,
   disabled?: boolean,
+  disabledLoading?: boolean,
   width?: number,
   large?: boolean,
   onFocus?: () => void,
@@ -90,13 +93,15 @@ class AutocompleteInput extends React.Component<Props, State> {
   }
 
   render() {
+    let disabled = this.props.disabled || this.props.disabledLoading
     return (
       <Wrapper
         large={this.props.large}
         width={this.props.width}
         focus={this.state.textInputFocus}
         highlight={this.props.highlight}
-        disabled={this.props.disabled}
+        disabled={disabled}
+        disabledLoading={this.props.disabledLoading}
         embedded={this.props.embedded}
         innerRef={e => {
           if (this.props.customRef) {
@@ -108,7 +113,7 @@ class AutocompleteInput extends React.Component<Props, State> {
       >
         <TextInput
           data-test-id="acInput-text"
-          disabled={this.props.disabled}
+          disabled={disabled}
           value={this.props.value}
           onChange={e => { this.props.onChange(e.target.value) }}
           embedded
@@ -125,7 +130,7 @@ class AutocompleteInput extends React.Component<Props, State> {
         />
         <Arrow
           data-test-id="acInput-arrow"
-          disabled={this.props.disabled}
+          disabled={disabled}
           dangerouslySetInnerHTML={{ __html: arrowImage }}
           onClick={this.props.onClick}
         />

+ 5 - 1
src/components/atoms/AutocompleteInput/story.jsx

@@ -21,7 +21,7 @@ import AutocompleteInput from '.'
 type State = {
   value: string,
 }
-class Wrapper extends React.Component<{}, State> {
+class Wrapper extends React.Component<any, State> {
   state = {
     value: '',
   }
@@ -30,6 +30,7 @@ class Wrapper extends React.Component<{}, State> {
     return (
       <AutocompleteInput
         large
+        disabledLoading={this.props.disabledLoading}
         value={this.state.value}
         onChange={value => { this.setState({ value }) }}
       />
@@ -41,3 +42,6 @@ storiesOf('AutocompleteInput', module)
   .add('default', () => (
     <Wrapper />
   ))
+  .add('disabled loading', () => (
+    <Wrapper disabledLoading />
+  ))

+ 14 - 3
src/components/atoms/DropdownButton/DropdownButton.jsx

@@ -45,6 +45,10 @@ const Label = styled.div`
 `
 
 const getBackgroundColor = props => {
+  if (props.embedded) {
+    return 'white'
+  }
+
   if (props.disabled) {
     return Palette.grayscale[0]
   }
@@ -130,6 +134,7 @@ const Wrapper = styled.div`
   &:hover ${Label} {
     color: ${props => props.disabled ? '' : props.embedded ? '' : 'white'};
   }
+  ${props => props.disabledLoading ? StyleProps.animations.disabledLoading : ''}
 `
 const Arrow = styled.div`
   position: absolute;
@@ -149,6 +154,7 @@ type Props = {
   arrowRef?: (ref: HTMLElement) => void,
   className?: string,
   disabled?: boolean,
+  disabledLoading?: boolean,
   'data-test-id'?: string,
   embedded?: boolean,
   highlight?: boolean,
@@ -157,10 +163,13 @@ type Props = {
 }
 class DropdownButton extends React.Component<Props> {
   render() {
+    let disabled = this.props.disabled || this.props.disabledLoading
     return (
       <Wrapper
         data-test-id={this.props['data-test-id'] || 'dropdownButton'}
         {...this.props}
+        disabled={disabled}
+        disabledLoading={this.props.disabledLoading}
         innerRef={e => {
           if (this.props.customRef) {
             this.props.customRef(e)
@@ -168,14 +177,16 @@ class DropdownButton extends React.Component<Props> {
             this.props.innerRef(e)
           }
         }}
-        onClick={e => { if (!this.props.disabled && this.props.onClick) this.props.onClick(e) }}
+        onClick={e => {
+          if (!disabled && this.props.onClick) this.props.onClick(e)
+        }}
       >
         <Label
           {...this.props}
           onClick={() => { }}
           innerRef={() => { }}
           data-test-id="dropdownButton-value"
-          disabled={this.props.disabled}
+          disabled={disabled}
         >
           {this.props.value}
         </Label>
@@ -184,7 +195,7 @@ class DropdownButton extends React.Component<Props> {
           innerRef={ref => { if (this.props.arrowRef) this.props.arrowRef(ref) }}
           onClick={() => { }}
           data-test-id=""
-          disabled={this.props.disabled}
+          disabled={disabled}
           dangerouslySetInnerHTML={{ __html: arrowImage }}
         />
       </Wrapper>

+ 3 - 0
src/components/atoms/DropdownButton/story.jsx

@@ -26,6 +26,9 @@ storiesOf('DropdownButton', module)
   .add('disabled', () => (
     <DropdownButton disabled value="Dropdown Button" onClick={action('clicked')} />
   ))
+  .add('disabled loading', () => (
+    <DropdownButton disabledLoading value="Dropdown Button" onClick={action('clicked')} />
+  ))
   .add('secondary centered', () => (
     <DropdownButton secondary centered value="Dropdown Button" onClick={action('clicked')} />
   ))

+ 6 - 2
src/components/atoms/RadioInput/RadioInput.jsx

@@ -25,6 +25,7 @@ import checkedImage from './images/checked.svg'
 
 const Wrapper = styled.div`
   ${props => props.disabled ? 'opacity: 0.5;' : ''}
+  ${props => props.disabledLoading ? StyleProps.animations.disabledLoading : ''}
 `
 const LabelStyled = styled.label`
   display: flex;
@@ -53,14 +54,17 @@ const InputStyled = styled.input`
 
 type Props = {
   label: string,
+  disabledLoading?: boolean,
+  disabled?: boolean,
 }
 @observer
 class RadioInput extends React.Component<Props> {
   render() {
+    let disabled = this.props.disabled || this.props.disabledLoading
     return (
-      <Wrapper {...this.props}>
+      <Wrapper {...this.props} disabled={disabled} disabledLoading={this.props.disabledLoading}>
         <LabelStyled>
-          <InputStyled type="radio" {...this.props} data-test-id="radioInput-input" />
+          <InputStyled type="radio" {...this.props} disabled={disabled} data-test-id="radioInput-input" />
           <Text data-test-id="radioInput-label">{this.props.label}</Text>
         </LabelStyled>
       </Wrapper>

+ 4 - 1
src/components/atoms/RadioInput/story.jsx

@@ -18,5 +18,8 @@ import RadioInput from '.'
 
 storiesOf('RadioInput', module)
   .add('default', () => (
-    <RadioInput label="Radio input" onChange={() => {}} />
+    <RadioInput label="Radio input" onChange={() => { }} />
+  ))
+  .add('disabled loading', () => (
+    <RadioInput label="Radio input" onChange={() => { }} disabledLoading />
   ))

+ 5 - 2
src/components/atoms/Switch/Switch.jsx

@@ -28,6 +28,7 @@ const Wrapper = styled.div`
   ${props => props.disabled ? 'opacity: 0.5;' : ''}
   ${props => props.justifyContent ? `justify-content: ${props.justifyContent};` : ''}
   ${props => props.width ? `width: ${props.width};` : ''}
+  ${props => props.disabledLoading ? StyleProps.animations.disabledLoading : ''}
 `
 const InputWrapper = styled.div`
   position: relative;
@@ -116,6 +117,7 @@ type Props = {
   onChange: (checked: ?boolean) => void,
   checked: boolean,
   disabled: boolean,
+  disabledLoading: boolean,
   triState: boolean,
   leftLabel: boolean,
   secondary: boolean,
@@ -154,7 +156,7 @@ class Switch extends React.Component<Props, State> {
   }
 
   handleInputChange() {
-    if (this.props.disabled) {
+    if (this.props.disabled || this.props.disabledLoading) {
       return
     }
 
@@ -186,7 +188,7 @@ class Switch extends React.Component<Props, State> {
         big={this.props.big}
         height={this.props.height}
         secondary={this.props.secondary}
-        disabled={this.props.disabled}
+        disabled={this.props.disabled || this.props.disabledLoading}
         onClick={() => { this.handleInputChange() }}
         tabIndex={0}
         onKeyDown={evt => { this.handleKeyDown(evt) }}
@@ -235,6 +237,7 @@ class Switch extends React.Component<Props, State> {
     return (
       <Wrapper
         disabled={this.props.disabled}
+        disabledLoading={this.props.disabledLoading}
         style={this.props.style}
         width={this.props.width}
         justifyContent={this.props.justifyContent}

+ 1 - 0
src/components/atoms/Switch/story.jsx

@@ -42,6 +42,7 @@ class Wrapper extends React.Component {
 storiesOf('Switch', module)
   .add('default', () => <Wrapper />)
   .add('disabled', () => <Wrapper disabled />)
+  .add('disabled loading', () => <Wrapper disabledLoading />)
   .add('secondary', () => <Wrapper secondary />)
   .add('tri-state', () => <Wrapper triState />)
   .add('colored', () => <Wrapper big />)

+ 3 - 0
src/components/atoms/TextArea/TextArea.jsx

@@ -24,6 +24,7 @@ import requiredImage from './images/required.svg'
 
 const Wrapper = styled.div`
   position: relative;
+  ${props => props.disabledLoading ? StyleProps.animations.disabledLoading : ''}
 `
 const Required = styled.div`
   position: absolute;
@@ -81,6 +82,8 @@ class TextArea extends React.Component<any> {
       <Wrapper>
         <Input
           {...this.props}
+          disabled={this.props.disabled || this.props.disabledLoading}
+          disabledLoading={this.props.disabledLoading}
           innerRef={r => {
             if (this.props.innerRef) {
               this.props.innerRef(r)

+ 1 - 0
src/components/atoms/TextArea/story.jsx

@@ -20,3 +20,4 @@ import TextArea from '.'
 
 storiesOf('TextArea', module)
   .add('default', () => <TextArea />)
+  .add('disabled loading', () => <TextArea disabledLoading />)

+ 9 - 5
src/components/atoms/TextInput/TextInput.jsx

@@ -24,6 +24,7 @@ import requiredImage from './images/required.svg'
 
 const Wrapper = styled.div`
   position: relative;
+  ${props => props.disabledLoading ? StyleProps.animations.disabledLoading : ''}
 `
 const Required = styled.div`
   position: absolute;
@@ -69,9 +70,9 @@ const Input = styled.input`
     outline: none;
   }
   &:disabled {
-    color: ${Palette.grayscale[3]};
-    border-color: ${Palette.grayscale[0]};
-    background-color: ${Palette.grayscale[0]};
+    color: ${props => !props.embedded ? Palette.grayscale[3] : 'inherit'};
+    border-color: ${props => !props.embedded ? Palette.grayscale[0] : 'inherit'};
+    background-color: ${props => !props.embedded ? Palette.grayscale[0] : 'inherit'};
   }
   &::placeholder {
     color: ${Palette.grayscale[3]};
@@ -103,12 +104,14 @@ type Props = {
   height?: string,
   'data-test-id'?: string,
   required?: boolean,
+  disabledLoading?: boolean,
 }
 const TextInput = (props: Props) => {
-  const { _ref, value, onChange, showClose, onCloseClick } = props
+  const { _ref, value, onChange, showClose, onCloseClick, disabled, disabledLoading } = props
+  let actualDisabled = disabled || disabledLoading
   let input
   return (
-    <Wrapper data-test-id={props['data-test-id'] || 'textInput'}>
+    <Wrapper data-test-id={props['data-test-id'] || 'textInput'} disabledLoading={disabledLoading}>
       <Input
         innerRef={ref => { input = ref; if (_ref) _ref(ref) }}
         type="text"
@@ -116,6 +119,7 @@ const TextInput = (props: Props) => {
         onChange={onChange}
         data-test-id="textInput-input"
         {...props}
+        disabled={actualDisabled}
       />
       {props.required ? <Required /> : null}
       <Close

+ 6 - 0
src/components/atoms/TextInput/story.jsx

@@ -38,6 +38,12 @@ storiesOf('TextInput', module)
   .add('default', () => (
     <Wrapper><TextInput /></Wrapper>
   ))
+  .add('disabled', () => (
+    <Wrapper><TextInput disabled /></Wrapper>
+  ))
+  .add('disabled loading', () => (
+    <Wrapper><TextInput disabledLoading /></Wrapper>
+  ))
   .add('required', () => (
     <Wrapper><TextInput required /></Wrapper>
   ))

+ 0 - 32
src/components/atoms/Tooltip/story.jsx

@@ -1,32 +0,0 @@
-/*
-Copyright (C) 2017  Cloudbase Solutions SRL
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU Affero General Public License as
-published by the Free Software Foundation, either version 3 of the
-License, or (at your option) any later version.
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU Affero General Public License for more details.
-You should have received a copy of the GNU Affero General Public License
-along with this program.  If not, see <http://www.gnu.org/licenses/>.
-*/
-
-import React from 'react'
-import { storiesOf } from '@storybook/react'
-import WizardOptionsField from '../../molecules/WizardOptionsField'
-
-import Tooltip from '.'
-
-storiesOf('Tooltip', module)
-  .add('default', () => (
-    <div>
-      <WizardOptionsField
-        name="separate_vm"
-        type="boolean"
-        value
-        onChange={() => { }}
-      />
-      <Tooltip />
-    </div>
-  ))

+ 2 - 0
src/components/molecules/AutocompleteDropdown/AutocompleteDropdown.jsx

@@ -120,6 +120,7 @@ type Props = {
   onInputChange?: (value: string, filteredItems: any[]) => void,
   noItemsMessage?: string,
   disabled?: boolean,
+  disabledLoading?: boolean,
   width?: number,
   dimNullValue?: boolean,
   highlight?: boolean,
@@ -451,6 +452,7 @@ class AutocompleteDropdown extends React.Component<Props, State> {
           onFocus={() => { this.handleSearchInputChange(this.state.searchValue, true) }}
           highlight={this.props.highlight}
           disabled={this.props.disabled}
+          disabledLoading={this.props.disabledLoading}
           embedded={this.props.embedded}
         />
         {this.props.required ? <Required /> : null}

+ 1 - 0
src/components/molecules/Dropdown/Dropdown.jsx

@@ -194,6 +194,7 @@ type Props = {
   noItemsMessage: string,
   noSelectionMessage: string,
   disabled: boolean,
+  disabledLoading: boolean,
   width: number,
   'data-test-id'?: string,
   embedded?: boolean,

+ 13 - 1
src/components/molecules/FieldInput/FieldInput.jsx

@@ -56,6 +56,7 @@ const Label = styled.div`
     display: flex;
     align-items: center;
   `}
+  ${props => props.disabledLoading ? StyleProps.animations.disabledLoading : ''}
 `
 const LabelText = styled.span``
 const Asterisk = styled.div`
@@ -84,6 +85,7 @@ type Props = {
   password?: boolean,
   highlight?: boolean,
   disabled?: boolean,
+  disabledLoading?: boolean,
   items?: any[],
   useTextArea?: boolean,
   noSelectionMessage?: string,
@@ -104,6 +106,7 @@ class FieldInput extends React.Component<Props> {
         height={this.props.layout === 'page' ? 16 : 24}
         justifyContent={this.props.layout === 'page' ? 'flex-end' : ''}
         disabled={this.props.disabled}
+        disabledLoading={this.props.disabledLoading}
         triState={propss.triState}
         checked={this.props.value}
         onChange={checked => { if (this.props.onChange) this.props.onChange(checked) }}
@@ -124,6 +127,7 @@ class FieldInput extends React.Component<Props> {
         placeholder={LabelDictionary.get(this.props.name)}
         disabled={this.props.disabled}
         required={this.props.layout === 'page' ? false : this.props.required}
+        disabledLoading={this.props.disabledLoading}
       />
     )
   }
@@ -142,6 +146,7 @@ class FieldInput extends React.Component<Props> {
         }}
         placeholder={LabelDictionary.get(this.props.name)}
         disabled={this.props.disabled}
+        disabledLoading={this.props.disabledLoading}
       />
     )
   }
@@ -161,6 +166,7 @@ class FieldInput extends React.Component<Props> {
           }
         }}
         hideRequiredSymbol={this.props.layout === 'page'}
+        disabledLoading={this.props.disabledLoading}
       />
     )
   }
@@ -174,6 +180,7 @@ class FieldInput extends React.Component<Props> {
         onChange={e => { console.log('changing', e); if (this.props.onChange) this.props.onChange(e.target.value) }}
         placeholder={LabelDictionary.get(this.props.name)}
         disabled={this.props.disabled}
+        disabledLoading={this.props.disabledLoading}
         required={this.props.required}
       />
     )
@@ -203,6 +210,7 @@ class FieldInput extends React.Component<Props> {
       width: this.props.width,
       selectedItem,
       items,
+      disabledLoading: this.props.disabledLoading,
       onChange: item => this.props.onChange && this.props.onChange(item.value),
     }
 
@@ -240,6 +248,7 @@ class FieldInput extends React.Component<Props> {
         multipleSelection
         width={this.props.width}
         disabled={this.props.disabled}
+        disabledLoading={this.props.disabledLoading}
         noSelectionMessage="Choose values"
         noItemsMessage={this.props.noItemsMessage}
         items={items}
@@ -272,6 +281,7 @@ class FieldInput extends React.Component<Props> {
         items={items}
         onChange={item => { if (this.props.onChange) this.props.onChange(item.value) }}
         disabled={this.props.disabled}
+        disabledLoading={this.props.disabledLoading}
         highlight={this.props.highlight}
         required={this.props.required}
       />
@@ -285,6 +295,7 @@ class FieldInput extends React.Component<Props> {
         label={LabelDictionary.get(this.props.name)}
         onChange={e => { if (this.props.onChange) this.props.onChange(e.target.checked) }}
         disabled={this.props.disabled}
+        disabledLoading={this.props.disabledLoading}
       />
     )
   }
@@ -312,6 +323,7 @@ class FieldInput extends React.Component<Props> {
         placeholder={LabelDictionary.get(fieldName)}
         highlight={this.props.highlight}
         disabled={this.props.disabled}
+        disabledLoading={this.props.disabledLoading}
         required={this.props.required}
       />
     )
@@ -356,7 +368,7 @@ class FieldInput extends React.Component<Props> {
     let marginRight = this.props.layout === 'modal' || description || this.props.required ? '24px' : 0
 
     return (
-      <Label layout={this.props.layout}>
+      <Label layout={this.props.layout} disabledLoading={this.props.disabledLoading}>
         <LabelText style={{ marginRight }}>
           {this.props.label || LabelDictionary.get(this.props.name)}
         </LabelText>

+ 6 - 1
src/components/molecules/PropertiesTable/PropertiesTable.jsx

@@ -33,6 +33,7 @@ const Wrapper = styled.div`
   flex-direction: column;
   border: 1px solid ${Palette.grayscale[2]};
   border-radius: ${StyleProps.borderRadius};
+  ${props => props.disabledLoading ? StyleProps.animations.disabledLoading : ''}
 `
 const Column = styled.div`
   ${StyleProps.exactWidth('calc(50% - 24px)')}
@@ -65,6 +66,7 @@ type Props = {
   onChange: (property: Field, value: any) => void,
   valueCallback: (property: Field) => any,
   hideRequiredSymbol?: boolean,
+  disabledLoading?: boolean,
 }
 @observer
 class PropertiesTable extends React.Component<Props> {
@@ -80,6 +82,7 @@ class PropertiesTable extends React.Component<Props> {
       <Switch
         data-test-id={`${baseId}-switch-${prop.name}`}
         secondary
+        disabled={this.props.disabledLoading}
         triState={opts.triState}
         height={16}
         checked={this.props.valueCallback(prop)}
@@ -99,6 +102,7 @@ class PropertiesTable extends React.Component<Props> {
         onChange={e => { this.props.onChange(prop, e.target.value) }}
         placeholder={this.getName(prop.name)}
         required={typeof prop.required === 'boolean' && !this.props.hideRequiredSymbol ? prop.required : false}
+        disabled={this.props.disabledLoading}
       />
     )
   }
@@ -134,6 +138,7 @@ class PropertiesTable extends React.Component<Props> {
       width: 320,
       selectedItem,
       items,
+      disabled: this.props.disabledLoading,
       onChange: item => this.props.onChange(prop, item.value),
       required: typeof prop.required === 'boolean' && !this.props.hideRequiredSymbol ? prop.required : false,
     }
@@ -176,7 +181,7 @@ class PropertiesTable extends React.Component<Props> {
 
   render() {
     return (
-      <Wrapper>
+      <Wrapper disabledLoading={this.props.disabledLoading}>
         {this.props.properties.map(prop => {
           return (
             <Row key={prop.name} data-test-id={`${baseId}-row-${prop.name}`}>

+ 7 - 1
src/components/molecules/PropertiesTable/story.jsx

@@ -12,6 +12,8 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+// @flow
+
 import React from 'react'
 import { storiesOf } from '@storybook/react'
 import PropertiesTable from '.'
@@ -24,7 +26,7 @@ let properties = [
   { type: 'string', enum: ['a', 'b', 'c'], name: 'prop-4', label: 'String enum' },
 ]
 
-class Wrapper extends React.Component {
+class Wrapper extends React.Component<any, any> {
   constructor() {
     super()
     this.state = {}
@@ -47,6 +49,7 @@ class Wrapper extends React.Component {
           properties={properties}
           valueCallback={prop => this.valueCallback(prop)}
           onChange={(prop, value) => { this.handleChange(prop, value) }}
+          disabledLoading={this.props.disabledLoading}
         />
       </div>
     )
@@ -57,3 +60,6 @@ storiesOf('PropertiesTable', module)
   .add('default', () => (
     <Wrapper />
   ))
+  .add('disabled loading', () => (
+    <Wrapper disabledLoading />
+  ))

+ 0 - 32
src/components/molecules/WizardBreadcrumbs/story.jsx

@@ -1,32 +0,0 @@
-/*
-Copyright (C) 2017  Cloudbase Solutions SRL
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU Affero General Public License as
-published by the Free Software Foundation, either version 3 of the
-License, or (at your option) any later version.
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU Affero General Public License for more details.
-You should have received a copy of the GNU Affero General Public License
-along with this program.  If not, see <http://www.gnu.org/licenses/>.
-*/
-
-import React from 'react'
-import { storiesOf } from '@storybook/react'
-import WizardBreadcrumbs from '.'
-import { wizardConfig } from '../../../config'
-
-storiesOf('WizardBreadcrumbs', module)
-  .add('replica', () => (
-    <WizardBreadcrumbs
-      selected={{ ...wizardConfig.pages[0] }}
-      wizardType="replica"
-    />
-  ))
-  .add('migration', () => (
-    <WizardBreadcrumbs
-      selected={{ ...wizardConfig.pages[0] }}
-      wizardType="migration"
-    />
-  ))

+ 3 - 2
src/components/organisms/EditReplica/EditReplica.jsx

@@ -395,11 +395,11 @@ class EditReplica extends React.Component<Props, State> {
   }
 
   renderOptions(type: 'source' | 'destination') {
-    let loading = type === 'source' ? (providerStore.sourceSchemaLoading || providerStore.sourceOptionsLoading) :
-      providerStore.destinationSchemaLoading || providerStore.destinationOptionsLoading
+    let loading = type === 'source' ? providerStore.sourceSchemaLoading : providerStore.destinationSchemaLoading
     if (loading) {
       return this.renderLoading(`Loading ${type === 'source' ? 'source' : 'target'} options ...`)
     }
+    let optionsLoading = type === 'source' ? providerStore.sourceOptionsLoading : providerStore.destinationOptionsLoading
     let schema = type === 'source' ? providerStore.sourceSchema : providerStore.destinationSchema
     let fields = this.props.type === 'replica' ? schema.filter(f => !f.readOnly) : schema
 
@@ -419,6 +419,7 @@ class EditReplica extends React.Component<Props, State> {
         availableHeight={384}
         useAdvancedOptions
         layout="modal"
+        optionsLoading={optionsLoading}
       />
     )
   }

+ 2 - 0
src/components/organisms/WizardOptions/WizardOptions.jsx

@@ -91,6 +91,7 @@ type Props = {
   onScrollableRef?: (ref: HTMLElement) => void,
   availableHeight?: number,
   layout?: 'page' | 'modal',
+  optionsLoading?: boolean,
 }
 @observer
 class WizardOptions extends React.Component<Props> {
@@ -196,6 +197,7 @@ class WizardOptions extends React.Component<Props> {
         width={this.props.fieldWidth || StyleProps.inputSizes.wizard.width}
         label={field.label}
         nullableBoolean={field.nullableBoolean}
+        disabledLoading={this.props.optionsLoading}
         {...additionalProps}
       />
     )

+ 4 - 2
src/components/organisms/WizardPageContent/WizardPageContent.jsx

@@ -334,7 +334,8 @@ class WizardPageContent extends React.Component<Props, State> {
       case 'source-options':
         body = (
           <WizardOptions
-            loading={this.props.providerStore.sourceSchemaLoading || this.props.providerStore.sourceOptionsLoading}
+            loading={this.props.providerStore.sourceSchemaLoading}
+            optionsLoading={this.props.providerStore.sourceOptionsLoading}
             fields={this.props.providerStore.sourceSchema}
             onChange={this.props.onSourceOptionsChange}
             data={this.props.wizardData.sourceOptions}
@@ -347,7 +348,8 @@ class WizardPageContent extends React.Component<Props, State> {
       case 'dest-options':
         body = (
           <WizardOptions
-            loading={this.props.providerStore.destinationSchemaLoading || this.props.providerStore.destinationOptionsLoading}
+            loading={this.props.providerStore.destinationSchemaLoading}
+            optionsLoading={this.props.providerStore.destinationOptionsLoading}
             selectedInstances={this.props.wizardData.selectedInstances}
             fields={this.props.providerStore.destinationSchema}
             onChange={this.props.onDestOptionsChange}

+ 7 - 0
src/components/styleUtils/StyleProps.js

@@ -42,6 +42,13 @@ const StyleProps = {
         to {transform: rotate(360deg);}
       }
     `,
+    disabledLoading: css`
+      animation: opacityToggle 750ms linear infinite alternate-reverse;
+      @keyframes opacityToggle {
+        0% {opacity: 1;}
+        100% {opacity: 0.3;}
+      }
+    `,
   },
 
   media: {