Skip to content

Commit

Permalink
feat: add noticebox component
Browse files Browse the repository at this point in the history
  • Loading branch information
ismay committed Apr 8, 2020
1 parent 2f52e3f commit 357ef6d
Show file tree
Hide file tree
Showing 14 changed files with 378 additions and 0 deletions.
5 changes: 5 additions & 0 deletions cypress/integration/NoticeBox/accepts_children.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Feature: The NoticeBox can render an optional message

Scenario: A NoticeBox is provided a message
Given a NoticeBox receives a message as children
Then the message is visible
12 changes: 12 additions & 0 deletions cypress/integration/NoticeBox/accepts_children/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Given, Then } from 'cypress-cucumber-preprocessor/steps'

Given('a NoticeBox receives a message as children', () => {
cy.visitStory('NoticeBox', 'With children')
cy.get('[data-test="dhis2-uicore-noticebox"]').should('be.visible')
})

Then('the message is visible', () => {
cy.get('[data-test="dhis2-uicore-noticebox-message"]')
.contains('The noticebox content')
.should('be.visible')
})
5 changes: 5 additions & 0 deletions cypress/integration/NoticeBox/accepts_title.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Feature: The NoticeBox can render an optional title

Scenario: A NoticeBox is provided a title
Given a NoticeBox receives a title prop
Then the title is visible
12 changes: 12 additions & 0 deletions cypress/integration/NoticeBox/accepts_title/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Given, Then } from 'cypress-cucumber-preprocessor/steps'

Given('a NoticeBox receives a title prop', () => {
cy.visitStory('NoticeBox', 'With title')
cy.get('[data-test="dhis2-uicore-noticebox"]').should('be.visible')
})

Then('the title is visible', () => {
cy.get('[data-test="dhis2-uicore-noticebox-title"]')
.contains('The noticebox title')
.should('be.visible')
})
87 changes: 87 additions & 0 deletions packages/widgets/src/NoticeBox/NoticeBox.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import React from 'react'
import cx from 'classnames'
import { spacers, colors } from '@dhis2/ui-constants'
import propTypes from '@dhis2/prop-types'
import { NoticeBoxTitle } from './NoticeBoxTitle.js'
import { NoticeBoxIcon } from './NoticeBoxIcon.js'
import { NoticeBoxMessage } from './NoticeBoxMessage.js'

/**
* @module
*
* @param {NoticeBox.PropTypes} props
* @returns {React.Component}
*
* @example import { NoticeBox } from '@dhis2/ui-core'
*
* @see Live demo: {@link /demo/?path=/story/component-widget-noticebox--default|Storybook}
*/
export const NoticeBox = ({
className,
children,
dataTest,
title,
warning,
error,
}) => {
const classnames = cx(className, 'root', { warning, error })

return (
<div className={classnames} data-test={dataTest}>
<NoticeBoxIcon
error={error}
warning={warning}
dataTest={`${dataTest}-icon`}
/>
<div>
<NoticeBoxTitle title={title} dataTest={`${dataTest}-title`} />
<NoticeBoxMessage dataTest={`${dataTest}-message`}>
{children}
</NoticeBoxMessage>
</div>

<style jsx>{`
.root {
background: ${colors.blue050};
border: 1px solid ${colors.blue200};
border-radius: 3px;
display: flex;
padding: ${spacers.dp12} ${spacers.dp16};
}
.root.warning {
background: ${colors.yellow050};
border: 1px solid ${colors.yellow200};
}
.root.error {
background: ${colors.red050};
border: 2px solid ${colors.red500};
}
`}</style>
</div>
)
}

NoticeBox.defaultProps = {
dataTest: 'dhis2-uicore-noticebox',
}

/**
* @typedef {Object} PropTypes
* @static
* @prop {Node} [children]
* @prop {className} [string]
* @prop {title} [string]
* @prop {string} [dataTest]
* @prop {boolean} [warning] - `warning` and `error` are mutually exclusive boolean props
* @prop {boolean} [error]
*/
NoticeBox.propTypes = {
children: propTypes.node,
className: propTypes.string,
dataTest: propTypes.string,
error: propTypes.mutuallyExclusive(['error', 'warning'], propTypes.bool),
title: propTypes.string,
warning: propTypes.mutuallyExclusive(['error', 'warning'], propTypes.bool),
}
7 changes: 7 additions & 0 deletions packages/widgets/src/NoticeBox/NoticeBox.stories.e2e.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import React from 'react'
import { storiesOf } from '@storybook/react'
import { NoticeBox } from './NoticeBox.js'

storiesOf('NoticeBox', module)
.add('With children', () => <NoticeBox>The noticebox content</NoticeBox>)
.add('With title', () => <NoticeBox title="The noticebox title" />)
40 changes: 40 additions & 0 deletions packages/widgets/src/NoticeBox/NoticeBox.stories.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import React from 'react'
import { NoticeBox } from './NoticeBox.js'

export default {
title: 'Component/Widget/NoticeBox',
component: NoticeBox,
}

export const Default = () => (
<NoticeBox title="Your database was updated in the last 24 hours">
Data shown in this dashboard may take a few hours to update. Scheduled
dashboard updates can be managed in the scheduler app.
</NoticeBox>
)

export const Warning = () => (
<NoticeBox title="This program has no assigned Organisation Units" warning>
No one will be able to access this program. Add some Organisation Units
to the access list.
</NoticeBox>
)

export const Error = () => (
<NoticeBox title="Access rules for this instance are set to 'Public'" error>
Data could be accessed from outside this instance. Update access rules
immediately.
</NoticeBox>
)

const text =
'Lorem ipsum dolor sit amet, consectetur adipiscing elit.' +
'Ut semper interdum scelerisque. Suspendisse ut velit sed' +
'lacus pretium convallis vitae sit amet purus. Nam ut' +
'libero rhoncus, consectetur sem a, sollicitudin lectus.'

export const WithALongTitle = () => (
<NoticeBox title={text} error>
The title text will wrap
</NoticeBox>
)
46 changes: 46 additions & 0 deletions packages/widgets/src/NoticeBox/NoticeBoxIcon.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import React from 'react'
import propTypes from '@dhis2/prop-types'
import { colors, spacers } from '@dhis2/ui-constants'
import { Info, Warning, Error as ErrorIcon } from '@dhis2/ui-icons'
import css from 'styled-jsx/css'

const getIconStyles = color =>
css.resolve`
svg {
fill: ${color};
width: 24px;
height: 24px;
margin-right: ${spacers.dp12};
}
`

export const NoticeBoxIcon = ({ warning, error, dataTest }) => {
// Info is the default icon
let color = colors.blue900
let Icon = Info

if (warning) {
color = colors.yellow700
Icon = Warning
}

if (error) {
color = colors.red700
Icon = ErrorIcon
}

const { className, styles } = getIconStyles(color)

return (
<div data-test={dataTest}>
<Icon className={className} />
{styles}
</div>
)
}

NoticeBoxIcon.propTypes = {
dataTest: propTypes.string.isRequired,
error: propTypes.mutuallyExclusive(['error', 'warning'], propTypes.bool),
warning: propTypes.mutuallyExclusive(['error', 'warning'], propTypes.bool),
}
28 changes: 28 additions & 0 deletions packages/widgets/src/NoticeBox/NoticeBoxMessage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import React from 'react'
import { colors } from '@dhis2/ui-constants'
import propTypes from '@dhis2/prop-types'

export const NoticeBoxMessage = ({ children, dataTest }) => {
if (!children) {
return null
}

return (
<div data-test={dataTest}>
{children}

<style jsx>{`
div {
color: ${colors.grey900};
font-size: 14px;
line-height: 20px;
}
`}</style>
</div>
)
}

NoticeBoxMessage.propTypes = {
dataTest: propTypes.string.isRequired,
children: propTypes.node,
}
29 changes: 29 additions & 0 deletions packages/widgets/src/NoticeBox/NoticeBoxTitle.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import React from 'react'
import { colors, spacers } from '@dhis2/ui-constants'
import propTypes from '@dhis2/prop-types'

export const NoticeBoxTitle = ({ title, dataTest }) => {
if (!title) {
return null
}

return (
<h6 data-test={dataTest}>
{title}
<style jsx>{`
h6 {
color: ${colors.grey900};
font-size: 14px;
font-weight: 500;
line-height: 20px;
margin: 0 0 ${spacers.dp12} 0;
}
`}</style>
</h6>
)
}

NoticeBoxTitle.propTypes = {
dataTest: propTypes.string.isRequired,
title: propTypes.string,
}
54 changes: 54 additions & 0 deletions packages/widgets/src/NoticeBox/__test__/NoticeBoxIcon.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import React from 'react'
import { shallow } from 'enzyme'
import { NoticeBoxIcon } from '../NoticeBoxIcon.js'

describe('NoticeBoxIcon', () => {
it('should render info icon by default', () => {
const wrapper = shallow(<NoticeBoxIcon dataTest="test" />)

expect(wrapper.find('Warning')).toHaveLength(0)
expect(wrapper.find('Error')).toHaveLength(0)
expect(wrapper.find('Info')).toHaveLength(1)
})

it('should log errors when both warning and error flag are set', () => {
const spy = jest
.spyOn(global.console, 'error')
.mockImplementation(() => {})
shallow(<NoticeBoxIcon warning error dataTest="test" />)

expect(spy.mock.calls[0][0]).toMatchSnapshot()
expect(spy.mock.calls[1][0]).toMatchSnapshot()

spy.mockRestore()
})

it('should render error icon when both warning and error flag are set', () => {
const spy = jest
.spyOn(global.console, 'error')
.mockImplementation(() => {})
const wrapper = shallow(<NoticeBoxIcon warning error dataTest="test" />)

expect(wrapper.find('Warning')).toHaveLength(0)
expect(wrapper.find('Info')).toHaveLength(0)
expect(wrapper.find('Error')).toHaveLength(1)

spy.mockRestore()
})

it('should render error icon when only error flag is set', () => {
const wrapper = shallow(<NoticeBoxIcon error dataTest="test" />)

expect(wrapper.find('Warning')).toHaveLength(0)
expect(wrapper.find('Info')).toHaveLength(0)
expect(wrapper.find('Error')).toHaveLength(1)
})

it('should render warning icon when only warning flag is set', () => {
const wrapper = shallow(<NoticeBoxIcon warning dataTest="test" />)

expect(wrapper.find('Info')).toHaveLength(0)
expect(wrapper.find('Error')).toHaveLength(0)
expect(wrapper.find('Warning')).toHaveLength(1)
})
})
21 changes: 21 additions & 0 deletions packages/widgets/src/NoticeBox/__test__/NoticeBoxMessage.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import React from 'react'
import { shallow } from 'enzyme'
import { NoticeBoxMessage } from '../NoticeBoxMessage.js'

describe('NoticeBoxMessage', () => {
it('should return null when there are no children', () => {
const props = {
dataTest: 'test',
}

expect(NoticeBoxMessage(props)).toBe(null)
})

it('should render children', () => {
const wrapper = shallow(
<NoticeBoxMessage dataTest="test">children</NoticeBoxMessage>
)

expect(wrapper.text()).toEqual(expect.stringContaining('children'))
})
})
21 changes: 21 additions & 0 deletions packages/widgets/src/NoticeBox/__test__/NoticeBoxTitle.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import React from 'react'
import { shallow } from 'enzyme'
import { NoticeBoxTitle } from '../NoticeBoxTitle.js'

describe('NoticeBoxTitle', () => {
it('should return null when there is no title', () => {
const props = {
dataTest: 'test',
}

expect(NoticeBoxTitle(props)).toBe(null)
})

it('should render title', () => {
const wrapper = shallow(
<NoticeBoxTitle title="title" dataTest="test" />
)

expect(wrapper.text()).toEqual(expect.stringContaining('title'))
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`NoticeBoxIcon should log errors when both warning and error flag are set 1`] = `
"Warning: Failed prop type: Invalid prop \`error\` supplied to \`NoticeBoxIcon\`, Property 'error' is mutually exclusive with 'warning', but both have a thruthy value.
in NoticeBoxIcon"
`;

exports[`NoticeBoxIcon should log errors when both warning and error flag are set 2`] = `
"Warning: Failed prop type: Invalid prop \`warning\` supplied to \`NoticeBoxIcon\`, Property 'warning' is mutually exclusive with 'error', but both have a thruthy value.
in NoticeBoxIcon"
`;

0 comments on commit 357ef6d

Please sign in to comment.