Explorar o código

Improve tooltip's layout and add it to `Endpoint`

Improve tooltip's layout by wrapping it with the text when text is on
multiple lines.
Increase tooltip's width when tooltip's text is large.
Add tooltip feature to Endpoint modal.
Rename `Field` to `EndpointField` as it is used only for endpoint.
Sergiu Miclea %!s(int64=8) %!d(string=hai) anos
pai
achega
7a2629e821

+ 3 - 2
src/components/atoms/InfoIcon/InfoIcon.jsx

@@ -24,17 +24,18 @@ const Wrapper = styled.div`
   background: url('${questionImage}') center no-repeat;
   display: inline-block;
   margin-bottom: -4px;
-  margin-left: 4px;
+  margin-left: ${props => props.marginLeft ? `${props.marginLeft}px` : '4px'};
 `
 
 class InfoIcon extends React.Component {
   static propTypes = {
     text: PropTypes.string,
+    marginLeft: PropTypes.number,
   }
 
   render() {
     return (
-      <Wrapper data-tip={this.props.text} />
+      <Wrapper data-tip={this.props.text} marginLeft={this.props.marginLeft} />
     )
   }
 }

+ 10 - 1
src/components/atoms/Switch/Switch.jsx

@@ -24,6 +24,8 @@ const Wrapper = styled.div`
   height: ${StyleProps.inputSizes.regular.height}px;
   align-items: center;
   ${props => props.disabled ? 'opacity: 0.5;' : ''}
+  ${props => props.justifyContent ? `justify-content: ${props.justifyContent};` : ''}
+  ${props => props.width ? `width: ${props.width};` : ''}
 `
 const InputWrapper = styled.div`
   position: relative;
@@ -118,6 +120,8 @@ class Switch extends React.Component {
     secondary: PropTypes.bool,
     noLabel: PropTypes.bool,
     height: PropTypes.number,
+    width: PropTypes.string,
+    justifyContent: PropTypes.string,
     big: PropTypes.bool,
     checkedLabel: PropTypes.string,
     uncheckedLabel: PropTypes.string,
@@ -215,7 +219,12 @@ class Switch extends React.Component {
 
   render() {
     return (
-      <Wrapper disabled={this.props.disabled} style={this.props.style}>
+      <Wrapper
+        disabled={this.props.disabled}
+        style={this.props.style}
+        width={this.props.width}
+        justifyContent={this.props.justifyContent}
+      >
         {this.renderLeftLabel()}
         {this.renderInput()}
         {this.renderRightLabel()}

+ 5 - 1
src/components/atoms/Tooltip/Tooltip.jsx

@@ -22,7 +22,7 @@ injectGlobal`
   .reactTooltip {
     color: ${Palette.grayscale[4]} !important;
     background: ${Palette.grayscale[1]} !important;
-    width: 128px;
+    max-width: 192px;
     padding: 8px !important;
     box-shadow: 0 0 9px 1px rgba(32, 34, 52, 0.1);
     margin-left: 12px !important;
@@ -39,6 +39,10 @@ injectGlobal`
 `
 
 class Tooltip extends React.Component {
+  static rebuild = () => {
+    ReactTooltip.rebuild()
+  }
+
   render() {
     return (
       <ReactTooltip place="right" effect="solid" className="reactTooltip" />

+ 29 - 6
src/components/molecules/Field/Field.jsx → src/components/molecules/EndpointField/EndpointField.jsx

@@ -16,8 +16,9 @@ import React from 'react'
 import styled from 'styled-components'
 import PropTypes from 'prop-types'
 
-import { Switch, TextInput, Dropdown, RadioInput } from 'components'
+import { Switch, TextInput, Dropdown, RadioInput, InfoIcon } from 'components'
 
+import LabelDictionary from '../../../utils/LabelDictionary'
 import StyleProps from '../../styleUtils/StyleProps'
 import Palette from '../../styleUtils/Palette'
 
@@ -29,11 +30,14 @@ const Label = styled.div`
   text-transform: uppercase;
   margin-bottom: 4px;
 `
+const LabelText = styled.span`
+  margin-right: 24px;
+`
 
 class Field extends React.Component {
   static propTypes = {
-    label: PropTypes.string.isRequired,
-    type: PropTypes.string.isRequired,
+    name: PropTypes.string,
+    type: PropTypes.string,
     value: PropTypes.any,
     onChange: PropTypes.func,
     className: PropTypes.string,
@@ -65,7 +69,7 @@ class Field extends React.Component {
         large={this.props.large}
         value={this.props.value}
         onChange={e => { this.props.onChange(e.target.value) }}
-        placeholder={this.props.label}
+        placeholder={LabelDictionary.get(this.props.name)}
         disabled={this.props.disabled}
       />
     )
@@ -96,7 +100,7 @@ class Field extends React.Component {
     return (
       <RadioInput
         checked={this.props.value}
-        label={this.props.label}
+        label={LabelDictionary.get(this.props.name)}
         onChange={e => this.props.onChange(e.target.checked)}
         disabled={this.props.disabled}
       />
@@ -121,10 +125,29 @@ class Field extends React.Component {
     }
   }
 
+  renderLabel() {
+    if (this.props.type === 'radio') {
+      return null
+    }
+
+    let description = LabelDictionary.getDescription(this.props.name)
+    let infoIcon = null
+    if (description) {
+      infoIcon = <InfoIcon text={description} marginLeft={-20} />
+    }
+
+    return (
+      <Label>
+        <LabelText>{LabelDictionary.get(this.props.name)}</LabelText>
+        {infoIcon}
+      </Label>
+    )
+  }
+
   render() {
     return (
       <Wrapper className={this.props.className}>
-        {this.props.type !== 'radio' ? <Label>{this.props.label}</Label> : null}
+        {this.renderLabel()}
         {this.renderInput()}
       </Wrapper>
     )

+ 14 - 14
src/components/molecules/Field/story.jsx → src/components/molecules/EndpointField/story.jsx

@@ -14,7 +14,7 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 import React from 'react'
 import { storiesOf } from '@storybook/react'
-import Field from './Field'
+import EndpointField from './EndpointField'
 
 class Wrapper extends React.Component {
   constructor() {
@@ -28,7 +28,7 @@ class Wrapper extends React.Component {
 
   render() {
     return (
-      <Field
+      <EndpointField
         {...this.props}
         value={this.state.value}
         onChange={value => { this.handleChange(value) }}
@@ -37,37 +37,37 @@ class Wrapper extends React.Component {
   }
 }
 
-storiesOf('Field', module)
+storiesOf('EndpointField', module)
   .add('text input', () => (
-    <Wrapper label="Field label" type="string" />
+    <Wrapper name="field_name" type="string" />
   ))
   .add('text input large', () => (
-    <Wrapper large label="Field label" type="string" />
+    <Wrapper large name="field_name" type="string" />
   ))
   .add('text input disabled', () => (
-    <Wrapper label="Field label" type="string" disabled />
+    <Wrapper name="field_name" type="string" disabled />
   ))
   .add('text input highlight', () => (
-    <Wrapper label="Field label" type="string" highlight />
+    <Wrapper name="field_name" type="string" highlight />
   ))
   .add('text input password', () => (
-    <Wrapper label="Field label" type="string" password />
+    <Wrapper name="field_name" type="string" password />
   ))
   .add('switch', () => (
-    <Wrapper label="Field label" type="boolean" />
+    <Wrapper name="migr_worker_use_config_drive" type="boolean" />
   ))
   .add('number dropdown', () => (
-    <Wrapper label="Field label" type="integer" minimum={1} maximum={5} />
+    <Wrapper name="field_name" type="integer" minimum={1} maximum={5} />
   ))
   .add('number dropdown large', () => (
-    <Wrapper label="Field label" type="integer" minimum={1} maximum={5} large />
+    <Wrapper name="field_name" type="integer" minimum={1} maximum={5} large />
   ))
   .add('number dropdown disabled', () => (
-    <Wrapper label="Field label" type="integer" minimum={1} maximum={5} disabled />
+    <Wrapper name="field_name" type="integer" minimum={1} maximum={5} disabled />
   ))
   .add('radio', () => (
-    <Wrapper label="Field label" type="radio" />
+    <Wrapper name="field_name" type="radio" />
   ))
   .add('radio disabled', () => (
-    <Wrapper label="Field label" type="radio" disabled />
+    <Wrapper name="field_name" type="radio" disabled />
   ))

+ 9 - 9
src/components/molecules/Field/test.jsx → src/components/molecules/EndpointField/test.jsx

@@ -14,24 +14,24 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 import React from 'react'
 import { shallow } from 'enzyme'
-import Field from './Field'
+import EndpointField from './EndpointField'
 
-const wrap = props => shallow(<Field {...props} />)
+const wrap = props => shallow(<EndpointField {...props} />)
 
 it('renders boolean field with correct value', () => {
-  let wrapper = wrap({ type: 'boolean', label: 'label', value: true })
+  let wrapper = wrap({ type: 'boolean', name: 'label', value: true })
   expect(wrapper.childAt(1).name()).toBe('Switch')
   expect(wrapper.childAt(1).prop('checked')).toBe(true)
 })
 
 it('renders boolean field disabled', () => {
-  let wrapper = wrap({ type: 'boolean', label: 'label', disabled: true })
+  let wrapper = wrap({ type: 'boolean', name: 'label', disabled: true })
   expect(wrapper.childAt(1).prop('disabled')).toBe(true)
 })
 
 it('renders text input field with correct label and value', () => {
-  let wrapper = wrap({ type: 'string', label: 'field-label', value: 'text-input' })
-  expect(wrapper.childAt(0).contains('field-label')).toBe(true)
+  let wrapper = wrap({ type: 'string', name: 'field_label', value: 'text-input' })
+  expect(wrapper.childAt(0).contains('Field Label')).toBe(true)
   expect(wrapper.childAt(1).name()).toBe('TextInput')
   expect(wrapper.childAt(1).prop('value')).toBe('text-input')
 })
@@ -39,7 +39,7 @@ it('renders text input field with correct label and value', () => {
 it('renders text input field with password, large, disabled, highlighted and required', () => {
   let wrapper = wrap({
     type: 'string',
-    label: 'field-label',
+    name: 'field-label',
     value: 'text-input',
     password: true,
     large: true,
@@ -57,7 +57,7 @@ it('renders text input field with password, large, disabled, highlighted and req
 it('renders integer dropdown field with correct items', () => {
   let wrapper = wrap({
     type: 'integer',
-    label: 'field-label',
+    name: 'field-label',
     value: 11,
     minimum: 10,
     maximum: 15,
@@ -68,7 +68,7 @@ it('renders integer dropdown field with correct items', () => {
 })
 
 it('renders radio input field with correct value', () => {
-  let wrapper = wrap({ type: 'radio', label: 'label', value: true })
+  let wrapper = wrap({ type: 'radio', name: 'label', value: true })
   expect(wrapper.childAt(0).name()).toBe('RadioInput')
   expect(wrapper.childAt(0).prop('checked')).toBe(true)
 })

+ 7 - 2
src/components/molecules/WizardOptionsField/WizardOptionsField.jsx

@@ -37,6 +37,9 @@ const Label = styled.div`
   font-weight: ${StyleProps.fontWeights.medium};
   ${props => getDirection(props) === 'column' ? 'margin-bottom: 8px;' : ''}
 `
+const LabelText = styled.span`
+  margin-right: 24px;
+`
 
 class WizardOptionsField extends React.Component {
   static propTypes = {
@@ -54,6 +57,8 @@ class WizardOptionsField extends React.Component {
   renderSwitch({ triState }) {
     return (
       <Switch
+        width="112px"
+        justifyContent="flex-end"
         triState={triState}
         checked={this.props.value}
         onChange={checked => { this.props.onChange(checked) }}
@@ -144,12 +149,12 @@ class WizardOptionsField extends React.Component {
     let description = LabelDictionary.getDescription(this.props.name)
     let infoIcon = null
     if (description) {
-      infoIcon = <InfoIcon text={description} />
+      infoIcon = <InfoIcon text={description} marginLeft={-20} />
     }
 
     return (
       <Label>
-        {LabelDictionary.get(this.props.name)}
+        <LabelText>{LabelDictionary.get(this.props.name)}</LabelText>
         {infoIcon}
       </Label>
     )

+ 5 - 4
src/components/organisms/Endpoint/Endpoint.jsx

@@ -17,8 +17,7 @@ import styled from 'styled-components'
 import PropTypes from 'prop-types'
 import connectToStores from 'alt-utils/lib/connectToStores'
 
-import { EndpointLogos, Field, Button, StatusIcon, LoadingButton, CopyButton } from 'components'
-import LabelDictionary from '../../../utils/LabelDictionary'
+import { EndpointLogos, EndpointField, Button, StatusIcon, LoadingButton, CopyButton, Tooltip } from 'components'
 import NotificationActions from '../../../actions/NotificationActions'
 import EndpointStore from '../../../stores/EndpointStore'
 import EndpointActions from '../../../actions/EndpointActions'
@@ -41,7 +40,7 @@ const Fields = styled.div`
   margin-left: -64px;
   margin-top: 32px;
 `
-const FieldStyled = styled(Field) `
+const FieldStyled = styled(EndpointField)`
   margin-left: 64px;
   min-width: 224px;
   max-width: 224px;
@@ -334,7 +333,7 @@ class Endpoint extends React.Component {
           password={field.name === 'password'}
           type={field.type}
           highlight={this.state.invalidFields.findIndex(fn => fn === field.name) > -1}
-          label={LabelDictionary.get(field.name)}
+          name={field.name}
           value={this.getFieldValue(field, parentGroup)}
           onChange={value => { this.handleFieldChange(field, value, parentGroup) }}
         />
@@ -415,6 +414,8 @@ class Endpoint extends React.Component {
         {this.renderEndpointStatus()}
         <Fields>
           {this.renderFields(this.props.providerStore.connectionInfoSchema)}
+          <Tooltip />
+          {Tooltip.rebuild()}
         </Fields>
         <Buttons>
           <Button large secondary onClick={() => { this.handleCancelClick() }}>Cancel</Button>

+ 7 - 3
src/components/organisms/WizardOptions/story.jsx

@@ -18,12 +18,16 @@ import WizardOptions from './WizardOptions'
 
 let fields = [
   {
-    name: 'separate_vm',
+    name: 'list_all_destination_networks',
     type: 'boolean',
   },
   {
-    name: 'string_field',
-    type: 'string',
+    name: 'migr_worker_use_config_drive',
+    type: 'boolean',
+  },
+  {
+    name: 'set_dhcp',
+    type: 'boolean',
   },
   {
     name: 'string_field_with_default',