diff --git a/packages/cp-frontend/src/components/instances/list-item.js b/packages/cp-frontend/src/components/instances/list-item.js index 4e402035..049265fa 100644 --- a/packages/cp-frontend/src/components/instances/list-item.js +++ b/packages/cp-frontend/src/components/instances/list-item.js @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { Component } from 'react'; import PropTypes from 'prop-types'; import remcalc from 'remcalc'; import styled from 'styled-components'; @@ -7,10 +7,16 @@ import titleCase from 'title-case'; import { Card, + CardInfo, CardView, CardMeta, CardTitle, CardDescription, + HealthyIcon, + Tooltip, + TooltipLabel, + P, + Label, typography } from 'joyent-ui-toolkit'; @@ -29,31 +35,18 @@ const STATUSES = [ 'UNKNOWN' ]; -const Span = styled.span` - ${typography.fontFamily}; - ${typography.normal}; -`; - -const Dot = styled.div` +const Dot = styled.span` margin-right: ${remcalc(6)}; width: ${remcalc(6)}; height: ${remcalc(6)}; border-radius: ${remcalc(3)}; display: inline-block; - ${isOr('provisioning', 'ready', 'active')` - background-color: ${props => props.theme.primary}; - `}; - - ${is('running')` + ${isOr('provisioning', 'ready', 'active', 'running')` background-color: ${props => props.theme.green}; `}; - ${is('stopping')` - background-color: orange; - `}; - - ${is('stopped')` + ${isOr('stopping', 'stopped')` background-color: ${props => props.theme.grey}; `}; @@ -66,23 +59,6 @@ const Dot = styled.div` `}; `; -const StatusBadge = ({ status }) => { - const props = STATUSES.reduce( - (acc, name) => - Object.assign(acc, { - [name.toLowerCase()]: name === status - }), - {} - ); - - return ( - - - {titleCase(status)} - - ); -}; - const StyledCard = Card.extend` flex-direction: row; @@ -92,37 +68,91 @@ const StyledCard = Card.extend` border-bottom-width: 0; } - &:nth-child(odd) { + background-color: ${props => props.theme.white}; + + ${isOr('stopping', 'stopped', 'offline', 'destroyed', 'failed', 'deleted', 'incomplete', 'unknown')` background-color: ${props => props.theme.background}; & [name="card-options"] > button { background-color: ${props => props.theme.background}; - } + }` } `; const InstanceCard = ({ instance = {}, onOptionsClick = () => null, - toggleCollapsed = () => null -}) => - - - + toggleCollapsed = () => null, + onHealthMouseOver, + onStatusMouseOver, + onMouseOut +}) => { + + const statusProps = STATUSES.reduce( + (acc, name) => + Object.assign(acc, { + [name.toLowerCase()]: name === instance.status + }), + {} + ); + + const label = instance.healthy.toLowerCase(); + const icon = ; + + const handleHealthMouseOver = (evt) => { + onHealthMouseOver(evt, instance); + } + + const handleStatusMouseOver = (evt) => { + onStatusMouseOver(evt, instance); + } + + const handleMouseOut = (evt) => { + onMouseOut(evt); + } + + return ( + + {instance.name} - +
+ +
-
-
-
; + +
+ +
+
+ + + ) +}; InstanceCard.propTypes = { instance: PropTypes.object, onOptionsClick: PropTypes.func, - toggleCollapsed: PropTypes.func + toggleCollapsed: PropTypes.func, + onHealthMouseOver: PropTypes.func, + onStatusMouseOver: PropTypes.func, + onMouseOut: PropTypes.func }; export default InstanceCard; diff --git a/packages/cp-frontend/src/components/services/list-item.js b/packages/cp-frontend/src/components/services/list-item.js index c230036b..1e7673cf 100644 --- a/packages/cp-frontend/src/components/services/list-item.js +++ b/packages/cp-frontend/src/components/services/list-item.js @@ -3,6 +3,7 @@ import PropTypes from 'prop-types'; import styled from 'styled-components'; import forceArray from 'force-array'; import sortBy from 'lodash.sortby'; +import { isNot } from 'styled-is'; import { InstancesIcon, HealthyIcon, UnhealthyIcon } from 'joyent-ui-toolkit'; import Status from './status'; @@ -27,10 +28,16 @@ const StyledCardHeader = styled(CardHeader)` const TitleInnerContainer = styled.div` display: flex; flex-direction: row; - justify-content: center; + justify-content: left; align-items: center; `; +const StyledAnchor = styled(Anchor)` + ${isNot('active')` + color: ${props => props.theme.text} + `}; +`; + const ServiceListItem = ({ onQuickActionsClick = () => {}, deploymentGroup = '', @@ -42,7 +49,7 @@ const ServiceListItem = ({ }; const children = sortBy(forceArray(service.children), ['slug']); - const isServiceInactive = service.status && service.status !== 'ACTIVE'; + // const isServiceInactive = service.status && service.status !== 'ACTIVE'; const to = `/deployment-groups/${deploymentGroup}/services/${service.slug}`; const instancesCount = children.length @@ -64,9 +71,9 @@ const ServiceListItem = ({ : - + {service.name} - + ; @@ -87,28 +94,29 @@ const ServiceListItem = ({ label={`${instancesCount} ${instancesCount > 1 ? 'instances' : 'instance'}`} - color={isServiceInactive ? 'disabled' : 'light'} + color={!service.instancesActive ? 'disabled' : 'light'} /> : null; - const healthyInfo = isServiceInactive - ? null - : service.instancesHealthy - ? } - iconPosition="left" - label="Healthy" - color="dark" - /> - : } - iconPosition="left" - label="Unhealthy" - color="dark" - />; + let healthyInfo = null; + if(service.instancesActive) { + const { total, healthy } = service.instancesHealthy; + const iconHealthy = total === healthy ? 'HEALTHY' : 'NOT HEALTHY'; + const icon = ; + const label = `${healthy} of ${total} healthy`; + + healthyInfo = ( + + ) + } const view = childrenItems.length ? @@ -126,7 +134,7 @@ const ServiceListItem = ({ return ( { + /* const p = Object.keys(position).reduce((p, key) => { if (typeof position[key] === 'number') { p[key] = `${position[key]}px`; } else { p[key] = position[key]; } return p; - }, {}); + }, {}); */ const handleRestartClick = evt => { onRestartClick(evt, service); @@ -58,31 +58,43 @@ const ServicesQuickActions = ({ const startService = status === 'RUNNING' ? null - : - Start - ; + :
  • + + Start + +
  • ; const stopService = status === 'STOPPED' ? null - : - Stop - ; + :
  • + + Stop + +
  • ; return ( - - - Scale - - - Restart - - {startService} - {stopService} - - - Delete - + + +
  • + + Scale + +
  • +
  • + + Restart + +
  • + {startService} + {stopService} + +
  • + + Delete + +
  • +
    ); }; diff --git a/packages/cp-frontend/src/components/services/status.js b/packages/cp-frontend/src/components/services/status.js index 81eaccd5..7cff2c9c 100644 --- a/packages/cp-frontend/src/components/services/status.js +++ b/packages/cp-frontend/src/components/services/status.js @@ -6,13 +6,13 @@ import { StatusLoader, P } from 'joyent-ui-toolkit'; const StyledStatusContainer = styled.div` display: inline-block; - margin: 0; + margin: 0 0 ${remcalc(15)} 0; height: ${remcalc(54)}; width: ${remcalc(200)}; `; const StyledStatus = P.extend` - margin: 0; + margin: 0 0 ${remcalc(6)} 0; font-size: ${remcalc(13)}; line-height: ${remcalc(13)}; `; diff --git a/packages/cp-frontend/src/containers/instances/index.js b/packages/cp-frontend/src/containers/instances/index.js index df3e4f6f..31bb602e 100644 --- a/packages/cp-frontend/src/containers/instances/index.js +++ b/packages/cp-frontend/src/containers/instances/index.js @@ -1 +1,2 @@ export { default as InstanceList } from './list'; +export { default as InstancesTooltip } from './tooltip'; diff --git a/packages/cp-frontend/src/containers/instances/list.js b/packages/cp-frontend/src/containers/instances/list.js index a6d63c27..9459ef65 100644 --- a/packages/cp-frontend/src/containers/instances/list.js +++ b/packages/cp-frontend/src/containers/instances/list.js @@ -1,5 +1,7 @@ -import React from 'react'; +import React, { Component } from 'react'; +import styled from 'styled-components'; import { compose, graphql } from 'react-apollo'; +import { connect } from 'react-redux'; import InstancesQuery from '@graphql/Instances.gql'; import forceArray from 'force-array'; import sortBy from 'lodash.sortby'; @@ -8,10 +10,18 @@ import { LayoutContainer } from '@components/layout'; import { Title } from '@components/navigation'; import { Loader, ErrorMessage } from '@components/messaging'; import { InstanceListItem, EmptyInstances } from '@components/instances'; - +import { toggleInstancesTooltip } from '@root/state/actions'; import { withNotFound, GqlPaths } from '@containers/navigation'; -const InstanceList = ({ deploymentGroup, instances = [], loading, error }) => { +const InstanceList = ({ + deploymentGroup, + instances = [], + loading, + error, + instancesTooltip, + toggleInstancesTooltip +}) => { + const _title = Instances; if (loading && !forceArray(instances).length) { @@ -44,11 +54,48 @@ const InstanceList = ({ deploymentGroup, instances = [], loading, error }) => { ); } + const handleHealthMouseOver = (evt, instance) => { + handleMouseOver(evt, instance, 'healthy'); + }; + + const handleStatusMouseOver = (evt, instance) => { + handleMouseOver(evt, instance, 'status'); + }; + + const handleMouseOver = (evt, instance, type) => { + + const label = evt.currentTarget; + const labelRect = label.getBoundingClientRect(); + const offset = type === 'healthy' + ? 48 : type === 'status' ? 36 : 0; + + const position = { + left: + `${window.scrollX + labelRect.left + offset}px`, + top: `${window.scrollY + labelRect.bottom}px` + }; + + const tooltipData = { + instance, + position, + type + } + + toggleInstancesTooltip(tooltipData); + }; + + const handleMouseOut = (evt) => { + toggleInstancesTooltip({ show: false }); + }; + const instanceList = instances.map((instance, index) => null} + onHealthMouseOver={handleHealthMouseOver} + onStatusMouseOver={handleStatusMouseOver} + onMouseOut={handleMouseOut} /> ); @@ -60,7 +107,17 @@ const InstanceList = ({ deploymentGroup, instances = [], loading, error }) => { {_instances} ); -}; +} + +const mapStateToProps = (state, ownProps) => ({ + instancesTooltip: state.ui.instances.tooltip +}); + +const mapDispatchToProps = dispatch => ({ + toggleInstancesTooltip: data => dispatch(toggleInstancesTooltip(data)) +}); + +const UiConnect = connect(mapStateToProps, mapDispatchToProps); const InstanceListGql = graphql(InstancesQuery, { options(props) { @@ -94,6 +151,7 @@ const InstanceListGql = graphql(InstancesQuery, { }); export default compose( + UiConnect, InstanceListGql, withNotFound([ GqlPaths.DEPLOYMENT_GROUP, diff --git a/packages/cp-frontend/src/containers/instances/tooltip.js b/packages/cp-frontend/src/containers/instances/tooltip.js new file mode 100644 index 00000000..4959f544 --- /dev/null +++ b/packages/cp-frontend/src/containers/instances/tooltip.js @@ -0,0 +1,68 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import styled from 'styled-components'; +import { Tooltip, TooltipLabel } from 'joyent-ui-toolkit'; +import { ServicesQuickActions } from '@components/services'; + +const StyledContainer = styled.div` + position: absolute; + top: 0; + left: 0; +`; + +const healthMessages = { + healthy: 'Your instance is operating as expected', + unhealthy: 'Your instance is not operating as expected', + maintenance: 'You\'ve set your instance to this manually, use the Container Pilot CLI to change', + unknown: 'We\'ve connected to your instance but we have no health information', + unavailable: 'We cannot connect to your instance', +}; + +const statusMessages = { + running: 'Your instance is operating', + provisioning: 'Your instance is downloading dependencies and compiling', + ready: 'Your instance finished provisioning and is ready to be run, it\'ll be running soon', + stopping: 'Your instance is going to be stopped soon', + stopped: 'Your instance isn\'t doing anything, you can start it', + offline: 'We have no idea what this means, do you??????', + failed: 'Your instance has crashed', + unknown: 'We cannot work out what status your instance is in', +}; + +const InstancesTooltip = ({ + instancesTooltip +}) => { + + if(instancesTooltip.show) { + const { + type, + instance + } = instancesTooltip; + + const message = type === 'healthy' + ? healthMessages[instance.healthy.toLowerCase()] + : type === 'status' + ? statusMessages[instance.status.toLowerCase()] + : ''; + + return ( + + + {message} + + + ) + } + + return null; +}; + +const mapStateToProps = (state, ownProps) => ({ + instancesTooltip: state.ui.instances.tooltip +}); + +const mapDispatchToProps = dispatch => ({}); + +const UiConnect = connect(mapStateToProps, mapDispatchToProps); + +export default UiConnect(InstancesTooltip); diff --git a/packages/cp-frontend/src/containers/services/index.js b/packages/cp-frontend/src/containers/services/index.js index ca469cb3..d18914cf 100644 --- a/packages/cp-frontend/src/containers/services/index.js +++ b/packages/cp-frontend/src/containers/services/index.js @@ -1,3 +1,4 @@ export { default as ServiceList } from './list'; export { default as ServicesTopology } from './topology'; export { default as ServicesMenu } from './menu'; +export { default as ServicesQuickActions } from './quick-actions'; diff --git a/packages/cp-frontend/src/containers/services/list.js b/packages/cp-frontend/src/containers/services/list.js index cc88610b..1bda23bb 100644 --- a/packages/cp-frontend/src/containers/services/list.js +++ b/packages/cp-frontend/src/containers/services/list.js @@ -6,21 +6,14 @@ import forceArray from 'force-array'; import sortBy from 'lodash.sortby'; import ServicesQuery from '@graphql/Services.gql'; -import ServicesRestartMutation from '@graphql/ServicesRestartMutation.gql'; -import ServicesStopMutation from '@graphql/ServicesStopMutation.gql'; -import ServicesStartMutation from '@graphql/ServicesStartMutation.gql'; import { processServices } from '@root/state/selectors'; import { toggleServicesQuickActions } from '@root/state/actions'; - +import { withNotFound, GqlPaths } from '@containers/navigation'; import { LayoutContainer } from '@components/layout'; import { Loader, ErrorMessage } from '@components/messaging'; import { ServiceListItem } from '@components/services'; -import { ServicesQuickActions } from '@components/services'; - -import { withNotFound, GqlPaths } from '@containers/navigation'; - const StyledContainer = styled.div` position: relative; `; @@ -34,28 +27,13 @@ class ServiceList extends Component { }; } - ref(name) { - this._refs = this._refs || {}; - - return el => { - this._refs[name] = el; - }; - } - render() { const { deploymentGroup, services, loading, error, - servicesQuickActions, toggleServicesQuickActions, - url, - push, - restartServices, - stopServices, - startServices, - location } = this.props; if (loading && !forceArray(services).length) { @@ -90,17 +68,13 @@ class ServiceList extends Component { } const handleQuickActionsClick = (evt, service) => { - const list = this._refs.container; - const listRect = list.getBoundingClientRect(); const button = evt.currentTarget; const buttonRect = button.getBoundingClientRect(); const position = { left: - buttonRect.left - - listRect.left + - (buttonRect.right - buttonRect.left) / 2, - top: buttonRect.bottom - listRect.top + `${buttonRect.left + window.scrollX + (buttonRect.right - buttonRect.left) / 2}px`, + top: `${buttonRect.bottom + window.scrollY}px` }; toggleServicesQuickActions({ @@ -109,41 +83,6 @@ class ServiceList extends Component { }); }; - const handleRestartClick = (evt, service) => { - this.setState({ errors: {} }); - restartServices(service.id).catch(err => { - this.setState({ errors: { restart: err } }); - }); - }; - - const handleStopClick = (evt, service) => { - this.setState({ errors: {} }); - stopServices(service.id).catch(err => { - this.setState({ errors: { stop: err } }); - }); - }; - - const handleStartClick = (evt, service) => { - this.setState({ errors: {} }); - startServices(service.id).catch(err => { - this.setState({ errors: { start: err } }); - }); - }; - - const handleScaleClick = (evt, service) => { - toggleServicesQuickActions({ show: false }); - push(`${url}/${service.slug}/scale`); - }; - - const handleDeleteClick = (evt, service) => { - toggleServicesQuickActions({ show: false }); - push(`${url}/${service.slug}/delete`); - }; - - const handleQuickActionsBlur = o => { - toggleServicesQuickActions({ show: false }); - }; - let renderedError = null; if ( @@ -181,31 +120,14 @@ class ServiceList extends Component { {renderedError} -
    - {serviceList} - -
    + {serviceList}
    ); } } -const mapStateToProps = (state, ownProps) => ({ - servicesQuickActions: state.ui.services.quickActions, - url: ownProps.match.url.replace(/\/$/, ''), - push: ownProps.history.push -}); +const mapStateToProps = (state, ownProps) => ({}); const mapDispatchToProps = dispatch => ({ toggleServicesQuickActions: data => dispatch(toggleServicesQuickActions(data)) @@ -232,29 +154,7 @@ const ServicesGql = graphql(ServicesQuery, { }) }); -const ServicesRestartGql = graphql(ServicesRestartMutation, { - props: ({ mutate }) => ({ - restartServices: serviceId => mutate({ variables: { ids: [serviceId] } }) - }) -}); - -const ServicesStopGql = graphql(ServicesStopMutation, { - props: ({ mutate }) => ({ - stopServices: serviceId => mutate({ variables: { ids: [serviceId] } }) - }) -}); - -const ServicesStartGql = graphql(ServicesStartMutation, { - props: ({ mutate }) => ({ - startServices: serviceId => mutate({ variables: { ids: [serviceId] } }) - }) -}); - const ServiceListWithData = compose( - ServicesGql, - ServicesRestartGql, - ServicesStopGql, - ServicesStartGql, ServicesGql, UiConnect, withNotFound([ GqlPaths.DEPLOYMENT_GROUP ]) diff --git a/packages/cp-frontend/src/containers/services/quick-actions.js b/packages/cp-frontend/src/containers/services/quick-actions.js new file mode 100644 index 00000000..890c4053 --- /dev/null +++ b/packages/cp-frontend/src/containers/services/quick-actions.js @@ -0,0 +1,172 @@ +import React, { Component } from 'react'; +import { connect } from 'react-redux'; +import { compose, graphql } from 'react-apollo'; +import styled from 'styled-components'; +import ServicesRestartMutation from '@graphql/ServicesRestartMutation.gql'; +import ServicesStopMutation from '@graphql/ServicesStopMutation.gql'; +import ServicesStartMutation from '@graphql/ServicesStartMutation.gql'; +import { Tooltip, TooltipLabel } from 'joyent-ui-toolkit'; +import { toggleServicesQuickActions } from '@root/state/actions'; +import { ServicesQuickActions as QuickActions } from '@components/services'; +import { ErrorMessage } from '@components/messaging'; +import { LayoutContainer } from '@components/layout'; + +const StyledContainer = styled.div` + position: absolute; + top: 0; + left: 0; +`; + +class ServicesQuickActions extends Component { + + constructor(props) { + super(props); + + this.state = { + errors: {} + } + } + + render() { + const { + servicesQuickActions, + toggleServicesQuickActions, + restartServices, + stopServices, + startServices, + url, + push + } = this.props; + + let errorMessage = null; + let quickActions = null; + + if ( + this.state.errors.stop || + this.state.errors.start || + this.state.errors.restart + ) { + const message = this.state.errors.stop + ? 'An error occurred while attempting to stop your service.' + : this.state.errors.start + ? 'An error occurred while attempting to start your service.' + : this.state.errors.restart + ? 'An error occurred while attempting to restart your service.' + : ''; + + errorMessage = ( + + + + ); + } + + if(servicesQuickActions.show) { + const handleTooltipBlur = evt => { + toggleServicesQuickActions({ show: false }); + }; + + const handleRestartClick = (evt, service) => { + this.setState({errors: {}}); + toggleServicesQuickActions({ show: false }); + restartServices(service.id).catch(err => { + this.setState({ errors: { restart: err } }); + }); + }; + + const handleStopClick = (evt, service) => { + this.setState({errors: {}}); + toggleServicesQuickActions({ show: false }); + stopServices(service.id).catch(err => { + this.setState({ errors: { stop: err } }); + }); + }; + + const handleStartClick = (evt, service) => { + this.setState({errors: {}}); + toggleServicesQuickActions({ show: false }); + startServices(service.id).catch(err => { + this.setState({ errors: { start: err } }); + }); + }; + + const handleScaleClick = (evt, service) => { + this.setState({errors: {}}); + toggleServicesQuickActions({ show: false }); + push(`${url}/${service.slug}/scale`); + }; + + const handleDeleteClick = (evt, service) => { + this.setState({errors: {}}); + toggleServicesQuickActions({ show: false }); + push(`${url}/${service.slug}/delete`); + }; + + quickActions = ( + + + + ) + } + + if(quickActions || errorMessage) { + return ( +
    + {errorMessage} + {quickActions} +
    + ) + } + + return null; + } +} + +const mapStateToProps = (state, ownProps) => ({ + servicesQuickActions: state.ui.services.quickActions, + url: ownProps.match.url.replace(/\/$/, ''), + push: ownProps.history.push +}); + +const mapDispatchToProps = dispatch => ({ + toggleServicesQuickActions: data => dispatch(toggleServicesQuickActions(data)) +}); + +const UiConnect = connect(mapStateToProps, mapDispatchToProps); + +const ServicesRestartGql = graphql(ServicesRestartMutation, { + props: ({ mutate }) => ({ + restartServices: serviceId => mutate({ variables: { ids: [serviceId] } }) + }) +}); + +const ServicesStopGql = graphql(ServicesStopMutation, { + props: ({ mutate }) => ({ + stopServices: serviceId => mutate({ variables: { ids: [serviceId] } }) + }) +}); + +const ServicesStartGql = graphql(ServicesStartMutation, { + props: ({ mutate }) => ({ + startServices: serviceId => mutate({ variables: { ids: [serviceId] } }) + }) +}); + +const ConnectedServicesQuickActions = compose( + ServicesRestartGql, + ServicesStopGql, + ServicesStartGql, + UiConnect +)(ServicesQuickActions); + +export default ConnectedServicesQuickActions; diff --git a/packages/cp-frontend/src/containers/services/topology.js b/packages/cp-frontend/src/containers/services/topology.js index 59c213b1..98236c15 100644 --- a/packages/cp-frontend/src/containers/services/topology.js +++ b/packages/cp-frontend/src/containers/services/topology.js @@ -3,23 +3,16 @@ import { compose, graphql } from 'react-apollo'; import { connect } from 'react-redux'; import styled from 'styled-components'; import forceArray from 'force-array'; -import ServicesQuery from '@graphql/Services.gql'; -import ServicesRestartMutation from '@graphql/ServicesRestartMutation.gql'; -import ServicesStopMutation from '@graphql/ServicesStopMutation.gql'; -import ServicesStartMutation from '@graphql/ServicesStartMutation.gql'; import unitcalc from 'unitcalc'; +import ServicesQuery from '@graphql/Services.gql'; import { processServicesForTopology } from '@root/state/selectors'; import { toggleServicesQuickActions } from '@root/state/actions'; - +import { withNotFound, GqlPaths } from '@containers/navigation'; import { LayoutContainer } from '@components/layout'; import { Loader, ErrorMessage } from '@components/messaging'; -import { ServicesQuickActions } from '@components/services'; - import { Topology } from 'joyent-ui-toolkit'; -import { withNotFound, GqlPaths } from '@containers/navigation'; - const StyledBackground = styled.div` padding: ${unitcalc(4)}; background-color: ${props => props.theme.whiteActive}; @@ -38,6 +31,14 @@ class ServicesTopology extends Component { }; } + ref(name) { + this._refs = this._refs || {}; + + return el => { + this._refs[name] = el; + }; + } + render() { const { url, @@ -46,12 +47,7 @@ class ServicesTopology extends Component { services, loading, error, - servicesQuickActions, toggleServicesQuickActions, - restartServices, - stopServices, - startServices, - location } = this.props; if (loading && !forceArray(services).length) { @@ -86,42 +82,17 @@ class ServicesTopology extends Component { } const handleQuickActionsClick = (evt, tooltipData) => { - toggleServicesQuickActions(tooltipData); - }; - - const handleTooltipBlur = evt => { - toggleServicesQuickActions({ show: false }); - }; - - const handleRestartClick = (evt, service) => { - this.setState({ errors: {} }); - restartServices(service.id).catch(err => { - this.setState({ errors: { restart: err } }); - }); - }; - - const handleStopClick = (evt, service) => { - this.setState({ errors: {} }); - stopServices(service.id).catch(err => { - this.setState({ errors: { stop: err } }); - }); - }; - - const handleStartClick = (evt, service) => { - this.setState({ errors: {} }); - startServices(service.id).catch(err => { - this.setState({ errors: { start: err } }); - }); - }; - - const handleScaleClick = (evt, service) => { - toggleServicesQuickActions({ show: false }); - push(`${url}/${service.slug}/scale`); - }; - - const handleDeleteClick = (evt, service) => { - toggleServicesQuickActions({ show: false }); - push(`${url}/${service.slug}/delete`); + const container = this._refs.container; + const containerRect = container.getBoundingClientRect(); + const position = { + top: `${containerRect.top + window.scrollY + tooltipData.position.top}px`, + left: `${containerRect.left + window.scrollX + tooltipData.position.left}px` + } + const data = { + ...tooltipData, + position + } + toggleServicesQuickActions(data); }; const handleNodeTitleClick = (evt, { service }) => { @@ -155,22 +126,13 @@ class ServicesTopology extends Component { {renderedError} - - +
    + +
    @@ -179,7 +141,6 @@ class ServicesTopology extends Component { } const mapStateToProps = (state, ownProps) => ({ - servicesQuickActions: state.ui.services.quickActions, url: ownProps.match.url.replace(/\/$/, ''), push: ownProps.history.push }); @@ -209,28 +170,7 @@ const ServicesGql = graphql(ServicesQuery, { }) }); -const ServicesRestartGql = graphql(ServicesRestartMutation, { - props: ({ mutate }) => ({ - restartServices: serviceId => mutate({ variables: { ids: [serviceId] } }) - }) -}); - -const ServicesStopGql = graphql(ServicesStopMutation, { - props: ({ mutate }) => ({ - stopServices: serviceId => mutate({ variables: { ids: [serviceId] } }) - }) -}); - -const ServicesStartGql = graphql(ServicesStartMutation, { - props: ({ mutate }) => ({ - startServices: serviceId => mutate({ variables: { ids: [serviceId] } }) - }) -}); - const ServicesTopologyWithData = compose( - ServicesRestartGql, - ServicesStopGql, - ServicesStartGql, ServicesGql, UiConnect, withNotFound([ GqlPaths.DEPLOYMENT_GROUP ]) diff --git a/packages/cp-frontend/src/router.js b/packages/cp-frontend/src/router.js index f500146d..3956913d 100644 --- a/packages/cp-frontend/src/router.js +++ b/packages/cp-frontend/src/router.js @@ -4,7 +4,6 @@ import styled from 'styled-components'; import { Header, Breadcrumb, Menu } from '@containers/navigation'; import { ServiceScale, ServiceDelete } from '@containers/service'; -import { InstanceList } from '@containers/instances'; import Manifest from '@containers/manifest'; import Environment from '@containers/environment'; @@ -17,9 +16,19 @@ import { import { ServiceList, ServicesTopology, - ServicesMenu + ServicesMenu, + ServicesQuickActions } from '@containers/services'; +import { + InstanceList, + InstancesTooltip +} from '@containers/instances'; + +import { + Tooltip +} from '@containers/tooltip'; + import { DeploymentGroupDelete } from '@containers/deployment-group'; import { NotFound } from '@components/navigation'; @@ -88,6 +97,28 @@ const App = p => component={ServicesMenu} /> + + + + + + + + { + const { position, service, show } = action.payload; + + const s = + show === undefined + ? !state.services.quickActions.service || + service.id !== state.services.quickActions.service.id + : show; + + const quickActions = s + ? { + show: s, + position, + service + } + : { + show: false + }; + + return { + ...state, + services: { + ...state.services, + quickActions + } + }; +}; + +const _toggleInstancesTooltip = (state, action) => { + const { position, instance, show, type } = action.payload; + + const s = + show === undefined + ? !state.instances.tooltip.instance || + instance.id !== state.instances.tooltip.instance.id + : show; + + const tooltip = s + ? { + show: true, + position, + instance, + type + } + : { + show: false + }; + + return { + ...state, + instances: { + ...state.instances, + tooltip + } + }; +}; export default handleActions( { - [toggleServicesQuickActions.toString()]: (state, action) => { - const { position, service, show } = action.payload; - - const s = - show === undefined - ? !state.services.quickActions.service || - service.id !== state.services.quickActions.service.id - : show; - - const quickActions = s - ? { - show: s, - position, - service - } - : { - show: false - }; - - return { - ...state, - services: { - ...state.services, - quickActions - } - }; - } + [toggleServicesQuickActions.toString()]: _toggleServicesQuickActions, + [toggleInstancesTooltip.toString()]: _toggleInstancesTooltip }, {} ); diff --git a/packages/cp-frontend/src/state/selectors.js b/packages/cp-frontend/src/state/selectors.js index 8d635c46..7d1015e9 100644 --- a/packages/cp-frontend/src/state/selectors.js +++ b/packages/cp-frontend/src/state/selectors.js @@ -61,7 +61,6 @@ const activeInstanceStatuses = [ 'READY', 'ACTIVE', 'RUNNING', - 'STOPPING', 'INCOMPLETE' ]; @@ -103,8 +102,12 @@ const getInstancesActive = instanceStatuses => { const getInstancesHealthy = instances => { return instances.reduce( - (healthy, instance) => (instance.healthy === 'HEALTHY' ? healthy : false), - true + (healthy, instance) => ({ + total: healthy.total + 1, + healthy: instance.healthy === 'HEALTHY' ? + healthy.healthy + 1 : healthy.healthy + }), + {total: 0, healthy: 0} ); }; diff --git a/packages/cp-frontend/src/state/state.js b/packages/cp-frontend/src/state/state.js index b0192f4c..0526e8ab 100644 --- a/packages/cp-frontend/src/state/state.js +++ b/packages/cp-frontend/src/state/state.js @@ -30,6 +30,11 @@ const state = { quickActions: { show: false } + }, + instances: { + tooltip: { + show: false + } } } }; diff --git a/packages/cp-gql-mock-server/src/resolvers.js b/packages/cp-gql-mock-server/src/resolvers.js index 5a82753c..765b5a83 100644 --- a/packages/cp-gql-mock-server/src/resolvers.js +++ b/packages/cp-gql-mock-server/src/resolvers.js @@ -76,6 +76,7 @@ const getUnfilteredServices = query => { const getServices = query => { // get all services + const services = getUnfilteredServices(query) // get all instances .then(services => @@ -95,9 +96,10 @@ const getServices = query => { ); // get all the serviceIds of the available instances // and then get the servcies with those ids - return uniq( + const ret = uniq( availableInstances.map(({ serviceId }) => serviceId) ).map(serviceId => lfind(services, ['id', serviceId])); + return ret; }); return Promise.resolve(services) @@ -331,12 +333,12 @@ const updateServiceAndInstancesStatus = ( instancesStatus ) => { return Promise.all([ - getServices({ id: serviceId }), - getServices({ parentId: serviceId }) + getServices({ id: serviceId })/*, + getServices({ parentId: serviceId })*/ ]) - .then(services => - services.reduce((services, service) => services.concat(service), []) - ) + .then(services => { + return services.reduce((services, service) => services.concat(service), []) + }) .then(services => { updateServiceStatus(services, serviceStatus); return Promise.all( @@ -356,8 +358,8 @@ const updateServiceAndInstancesStatus = ( }) .then(() => Promise.all([ - getUnfilteredServices({ id: serviceId }), - getUnfilteredServices({ parentId: serviceId }) + getUnfilteredServices({ id: serviceId })/*, + getUnfilteredServices({ parentId: serviceId })*/ ]) ) .then(services => diff --git a/packages/cp-gql-mock-server/src/wp-data.json b/packages/cp-gql-mock-server/src/wp-data.json index 59efbbf3..9d3e5ae9 100644 --- a/packages/cp-gql-mock-server/src/wp-data.json +++ b/packages/cp-gql-mock-server/src/wp-data.json @@ -114,8 +114,8 @@ "serviceId": "6d31aff4-de1e-4042-a983-fbd23d5c530c", "deploymentGroupId": "e0ea0c02-55cc-45fe-8064-3e5176a59401", "machineId": "8d8a2238-d981-4849-b523-a37456fbe20b", - "status": "RUNNING", - "healthy": "HEALTHY" + "status": "STOPPING", + "healthy": "MAINTENANCE" }, { "id": "68d3046e-8e34-4f5d-a0e5-db3795a250fd", @@ -132,8 +132,8 @@ "serviceId": "6d31aff4-de1e-4042-a983-fbd23d5c530c", "deploymentGroupId": "e0ea0c02-55cc-45fe-8064-3e5176a59401", "machineId": "d6871ac4-6433-40c3-89e8-8853ce7f8571", - "status": "RUNNING", - "healthy": "HEALTHY" + "status": "OFFLINE", + "healthy": "UNAVAILABLE" }, { "id": "25f6bc62-63b8-4959-908e-1f6d7ff6341d", @@ -141,8 +141,8 @@ "serviceId": "6d31aff4-de1e-4042-a983-fbd23d5c530c", "deploymentGroupId": "e0ea0c02-55cc-45fe-8064-3e5176a59401", "machineId": "d89612c8-0578-474a-b45d-98a1dcf6dd18", - "status": "RUNNING", - "healthy": "HEALTHY" + "status": "FAILED", + "healthy": "UNHEALTHY" }, { "id": "8be01042-0281-4a77-a357-25979e87bf3d", @@ -151,7 +151,7 @@ "deploymentGroupId": "e0ea0c02-55cc-45fe-8064-3e5176a59401", "machineId": "3a9fbaf8-722b-463a-86bd-8d3afe0dd759", "status": "RUNNING", - "healthy": "HEALTHY" + "healthy": "UNKNOWN" }, { "id": "3d652e9d-73e8-4a6f-8171-84fa83740662", diff --git a/packages/ui-toolkit/src/boxes/index.js b/packages/ui-toolkit/src/boxes/index.js index 86bed8c9..ec9a52cb 100644 --- a/packages/ui-toolkit/src/boxes/index.js +++ b/packages/ui-toolkit/src/boxes/index.js @@ -14,5 +14,6 @@ export const border = { checked: css`${remcalc(1)} solid ${props => props.theme.primary}`, unchecked: css`${remcalc(1)} solid ${props => props.theme.grey}`, confirmed: css`${remcalc(1)} solid ${props => props.theme.grey}`, - error: css`${remcalc(1)} solid ${props => props.theme.red}` + error: css`${remcalc(1)} solid ${props => props.theme.red}`, + secondary: css`${remcalc(1)} solid ${props => props.theme.secondaryActive}`, }; diff --git a/packages/ui-toolkit/src/card/card.js b/packages/ui-toolkit/src/card/card.js index 42c2b0de..a978a501 100644 --- a/packages/ui-toolkit/src/card/card.js +++ b/packages/ui-toolkit/src/card/card.js @@ -3,7 +3,7 @@ import Baseline from '../baseline'; import paperEffect from '../paper-effect'; import { bottomShaddow, bottomShaddowDarker } from '../boxes'; import remcalc from 'remcalc'; -import is from 'styled-is'; +import is, { isNot } from 'styled-is'; import { Row } from 'react-styled-flexboxgrid'; import PropTypes from 'prop-types'; import React from 'react'; @@ -38,6 +38,10 @@ const StyledCard = Row.extend` ${is('stacked')` ${paperEffect} `}; + + ${isNot('active')` + background-color: ${props => props.theme.disabled}; + `}; `; /** @@ -47,7 +51,7 @@ const Card = ({ children, collapsed = false, headed = false, - disabled = false, + active = true, ...rest }) => { const render = value => { @@ -55,14 +59,14 @@ const Card = ({ fromHeader: (value || {}).fromHeader, headed, collapsed, - disabled + active }; return ( { diff --git a/packages/ui-toolkit/src/card/header.js b/packages/ui-toolkit/src/card/header.js index 1f859bd0..8f2b6e3a 100644 --- a/packages/ui-toolkit/src/card/header.js +++ b/packages/ui-toolkit/src/card/header.js @@ -2,7 +2,7 @@ import React from 'react'; import { Broadcast, Subscriber } from 'react-broadcast'; import remcalc from 'remcalc'; import PropTypes from 'prop-types'; -import is from 'styled-is'; +import is, { isNot } from 'styled-is'; import Baseline from '../baseline'; import Card from './card'; @@ -17,16 +17,15 @@ const StyledCard = Card.extend` width: calc(100% + ${remcalc(2)}); margin: ${remcalc(-1)} ${remcalc(-1)} 0 ${remcalc(-1)}; - ${is('disabled')` + ${isNot('active')` background-color: ${props => props.theme.disabled}; border-color: ${props => props.theme.grey}; - color: ${props => props.theme.grey}; `}; `; const Header = ({ children, ...rest }) => { const render = value => { - const { disabled } = value; + const { active } = value; const newValue = { ...value, @@ -37,7 +36,7 @@ const Header = ({ children, ...rest }) => { (props.color === 'light' ? `color: ${props.theme.white};` : '')}; - ${props => (props.color === 'disabled' ? `color: ${props.theme.grey};` : '')}; + ${props => (props.color === 'disabled' ? `color: ${props.theme.text};` : '')}; margin-left: ${props => (props.iconPosition === 'left' ? remcalc(24) : 0)}; + &::first-letter { + text-transform: capitalize; + } `; const StyledIconContainer = styled.div` @@ -15,7 +19,7 @@ const StyledIconContainer = styled.div` > svg { ${props => (props.color === 'light' ? `fill: ${props.theme.white};` : '')}; - ${props => (props.color === 'disabled' ? `fill: ${props.theme.grey};` : '')}; + ${props => (props.color === 'disabled' ? `fill: ${props.theme.text};` : '')}; } `; diff --git a/packages/ui-toolkit/src/card/options.js b/packages/ui-toolkit/src/card/options.js index 2b869447..86a9863f 100644 --- a/packages/ui-toolkit/src/card/options.js +++ b/packages/ui-toolkit/src/card/options.js @@ -3,7 +3,7 @@ import styled from 'styled-components'; import { Nav } from 'normalized-styled-components'; import Baseline from '../baseline'; import remcalc from 'remcalc'; -import is from 'styled-is'; +import is, { isNot } from 'styled-is'; import PropTypes from 'prop-types'; import Button from '../button'; import React from 'react'; @@ -17,7 +17,7 @@ const StyledNav = Nav.extend` border-left-color: ${props => props.theme.primaryDesaturatedActive}; `}; - ${is('disabled')` + ${isNot('active')` border-left-color: ${props => props.theme.grey}; `}; `; @@ -54,6 +54,19 @@ const StyledButton = Button.extend` &:active:focus { border-width: 0; } + + ${isNot('active')` + background-color: ${props => props.theme.disabled}; + border-color: ${props => props.theme.grey}; + + &:focus, + &:hover, + &:active, + &:active:hover, + &:active:focus { + background-color: ${props => props.theme.grey}; + } + `} `; const StyledContainer = styled.div` @@ -73,8 +86,8 @@ const StyledCircle = styled.div` background-color: ${props => props.theme.secondary}; `}; - ${is('disabled')` - background-color: ${props => props.theme.grey}; + ${isNot('active')` + background-color: ${props => props.theme.text}; `}; `; @@ -82,20 +95,20 @@ const Options = ({ children, ...rest }) => { const render = ({ fromHeader = false, collapsed = false, - disabled = false + active = true }) => - + - - - + + + ; diff --git a/packages/ui-toolkit/src/card/outlet.js b/packages/ui-toolkit/src/card/outlet.js index 9dba7100..26af3bcc 100644 --- a/packages/ui-toolkit/src/card/outlet.js +++ b/packages/ui-toolkit/src/card/outlet.js @@ -2,7 +2,7 @@ import { Subscriber } from 'react-broadcast'; import typography from '../typography'; import Baseline from '../baseline'; import { Col } from 'react-styled-flexboxgrid'; -import is from 'styled-is'; +import is, { isNot } from 'styled-is'; import remcalc from 'remcalc'; import PropTypes from 'prop-types'; import React from 'react'; @@ -20,16 +20,16 @@ const StyledCol = Col.extend` display: none; `}; - ${is('disabled')` + ${isNot('active')` color: ${props => props.theme.grey}; `}; `; const Outlet = ({ children, ...rest }) => { - const render = ({ disabled = false, collapsed = false }) => + const render = ({ active = true, collapsed = false }) => props.theme.white}; `}; - - ${is('disabled')` - color: ${props => props.theme.grey}; - `}; `; const StyledTitle = Title.extend` @@ -48,7 +44,7 @@ const StyledTitle = Title.extend` const Subtitle = ({ children, ...props }) => { const render = ({ - disabled = false, + active = true, fromHeader = false, collapsed = false }) => @@ -56,7 +52,7 @@ const Subtitle = ({ children, ...props }) => { name="card-subtitle" fromHeader={fromHeader} collapsed={collapsed} - disabled={disabled} + active={active} {...props} > diff --git a/packages/ui-toolkit/src/card/title.js b/packages/ui-toolkit/src/card/title.js index 957a7220..49e35a37 100644 --- a/packages/ui-toolkit/src/card/title.js +++ b/packages/ui-toolkit/src/card/title.js @@ -3,7 +3,7 @@ import isString from 'lodash.isstring'; import typography from '../typography'; import Baseline from '../baseline'; import remcalc from 'remcalc'; -import is from 'styled-is'; +import is, { isNot } from 'styled-is'; import styled from 'styled-components'; import PropTypes from 'prop-types'; import React from 'react'; @@ -21,6 +21,7 @@ const Container = styled.div` justify-content: flex-start; flex-grow: 2; + flex-basis: ${remcalc(90)}; width: 100%; padding: ${remcalc(12)} ${remcalc(18)} 0 ${remcalc(18)}; @@ -29,12 +30,8 @@ const Container = styled.div` color: ${props => props.theme.white}; `}; - ${is('disabled')` - color: ${props => props.theme.grey}; - `}; - ${is('collapsed')` - flex-grow: 0; + flex-grow: 6; flex-direction: column; width: auto; justify-content: center; @@ -61,16 +58,17 @@ const Title = ({ children, ...rest }) => { const render = ({ collapsed = false, - disabled = false, + active = true, fromHeader = false }) => {_children} ; diff --git a/packages/ui-toolkit/src/dropdown/index.js b/packages/ui-toolkit/src/dropdown/index.js index 2329b875..dcbddbf0 100644 --- a/packages/ui-toolkit/src/dropdown/index.js +++ b/packages/ui-toolkit/src/dropdown/index.js @@ -18,6 +18,9 @@ const StyledSelectList = styled(Tooltip)` position: relative; display: block; left: auto; + margin: 0; + padding: 0; + list-style-type: none; } ul:after, ul:before { left: 97%; @@ -107,15 +110,19 @@ class Dropdown extends Component { {this.state.isDroppedDown && - {data.map((val, index) => - - {val} - - )} +
      + {data.map((val, index) => +
    • + + {val} + +
    • + )} +
    } ); diff --git a/packages/ui-toolkit/src/icons/healthy.js b/packages/ui-toolkit/src/icons/healthy.js index c8124e89..4ba6a62a 100644 --- a/packages/ui-toolkit/src/icons/healthy.js +++ b/packages/ui-toolkit/src/icons/healthy.js @@ -1,7 +1,13 @@ import Baseline from '../baseline'; // eslint-disable-next-line no-unused-vars import React from 'react'; +import styled from 'styled-components'; import HealthyIcon from './svg/icon_healthy.svg'; -export default Baseline(HealthyIcon); +const StyledHealthyIcon = styled(HealthyIcon)` + fill: ${props => !props.healthy || props.healthy === 'HEALTHY' + ? props.theme.green : props.theme.orange}; +`; + +export default Baseline(StyledHealthyIcon); diff --git a/packages/ui-toolkit/src/icons/svg/icon_healthy.svg b/packages/ui-toolkit/src/icons/svg/icon_healthy.svg index 0c11ba6f..564a8dfa 100755 --- a/packages/ui-toolkit/src/icons/svg/icon_healthy.svg +++ b/packages/ui-toolkit/src/icons/svg/icon_healthy.svg @@ -4,16 +4,16 @@ icon: state Created with Sketch. - + - + - \ No newline at end of file + diff --git a/packages/ui-toolkit/src/index.js b/packages/ui-toolkit/src/index.js index c36b36c6..67ebb298 100644 --- a/packages/ui-toolkit/src/index.js +++ b/packages/ui-toolkit/src/index.js @@ -17,7 +17,7 @@ export { default as Chevron } from './chevron'; export { default as CloseButton } from './close-button'; export { default as Divider } from './divider'; export { default as IconButton } from './icon-button'; -export { Tooltip, TooltipButton, TooltipDivider } from './tooltip'; +export { Tooltip, TooltipButton, TooltipDivider, TooltipList, TooltipLabel } from './tooltip'; export { Dropdown } from './dropdown'; export { default as StatusLoader } from './status-loader'; export { default as Message } from './message'; diff --git a/packages/ui-toolkit/src/tooltip/button.js b/packages/ui-toolkit/src/tooltip/button.js index a01aa0f3..881d787d 100644 --- a/packages/ui-toolkit/src/tooltip/button.js +++ b/packages/ui-toolkit/src/tooltip/button.js @@ -45,8 +45,6 @@ const StyledButton = styled(Button)` `; const TooltipButton = props => -
  • - -
  • ; + ; export default TooltipButton; diff --git a/packages/ui-toolkit/src/tooltip/index.js b/packages/ui-toolkit/src/tooltip/index.js index 75659891..398fc34b 100644 --- a/packages/ui-toolkit/src/tooltip/index.js +++ b/packages/ui-toolkit/src/tooltip/index.js @@ -1,3 +1,5 @@ export { default as Tooltip } from './tooltip'; export { default as TooltipButton } from './button'; export { default as TooltipDivider } from './divider'; +export { default as TooltipList } from './list'; +export { default as TooltipLabel } from './label'; diff --git a/packages/ui-toolkit/src/tooltip/label.js b/packages/ui-toolkit/src/tooltip/label.js new file mode 100644 index 00000000..dc739412 --- /dev/null +++ b/packages/ui-toolkit/src/tooltip/label.js @@ -0,0 +1,9 @@ +import styled from 'styled-components'; +import remcalc from 'remcalc'; +import P from '../text/p'; + +export default styled(P)` + margin: 0 ${remcalc(18)}; + color: ${props => props.theme.white}; + white-space: nowrap; +`; diff --git a/packages/ui-toolkit/src/tooltip/list.js b/packages/ui-toolkit/src/tooltip/list.js new file mode 100644 index 00000000..8966240d --- /dev/null +++ b/packages/ui-toolkit/src/tooltip/list.js @@ -0,0 +1,7 @@ +import styled from 'styled-components'; + +export default styled.ul` + margin: 0; + padding: 0; + list-style-type: none; +`; diff --git a/packages/ui-toolkit/src/tooltip/tooltip.js b/packages/ui-toolkit/src/tooltip/tooltip.js index e783e6a6..79b75f8c 100644 --- a/packages/ui-toolkit/src/tooltip/tooltip.js +++ b/packages/ui-toolkit/src/tooltip/tooltip.js @@ -18,19 +18,18 @@ const StyledContainer = styled.div` } `; -const StyledList = styled.ul` +const StyledInnerContainer = styled.div` position: relative; display: inline-block; top: ${remcalc(5)}; left: -50%; margin: 0; padding: ${unitcalc(2)} 0; - list-style-type: none; - background-color: ${theme.white}; - border: ${border.unchecked}; + background-color: ${props => props.secondary ? props.theme.secondary : props.theme.white}; + border: ${props => props.secondary ? border.secondary : border.unchecked}; box-shadow: ${tooltipShadow}; border-radius: ${borderRadius}; - z-index: 1; + z-index: 1000; &:after, &:before { @@ -44,13 +43,13 @@ const StyledList = styled.ul` } &:after { - border-bottom-color: ${theme.white}; + border-bottom-color: ${props => props.secondary ? props.theme.secondary : theme.white}; border-width: ${remcalc(3)}; margin-left: ${remcalc(-3)}; } &:before { - border-bottom-color: ${theme.grey}; + border-bottom-color: ${props => props.secondary ? props.theme.secondaryActive : theme.grey}; border-width: ${remcalc(5)}; margin-left: ${remcalc(-5)}; } @@ -84,27 +83,39 @@ class Tooltip extends Component { } render() { - const { + let { children, top = 'auto', left = 'auto', bottom = 'auto', right = 'auto', - className, + secondary, ...rest } = this.props; + + if(typeof top === 'number') { + top = `${top}px` + } + if(typeof left === 'number') { + left = `${left}px` + } + if(typeof bottom === 'number') { + bottom = `${bottom}px` + } + if(typeof right === 'number') { + right = `${right}px` + } + return ( - + {children} - + ); } @@ -116,7 +127,8 @@ Tooltip.propTypes = { left: PropTypes.string, bottom: PropTypes.string, right: PropTypes.string, - onBlur: PropTypes.func + onBlur: PropTypes.func, + secondary: PropTypes.boolean }; export default Tooltip;