Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed
do eiusmod tempor incididunt ut labore et dolore magna
@@ -46,65 +86,26 @@ export const Default = () => {
)
}
-export const NoArrow = () => {
- const ref = useRef(null)
-
- return (
-
- )
+export const Default = Template.bind({})
+Default.parameters = {
+ docs: {
+ // Enable this story for the docs page
+ disable: false,
+ // Show source, including 'ref' hooks
+ source: { type: 'code' },
+ },
}
-export const Customization = () => {
- const ref = useRef(null)
+export const NoArrow = Template.bind({})
+NoArrow.args = { arrow: false }
- return (
-
- )
+export const Customization = Template.bind({})
+Customization.args = {
+ arrow: true,
+ className: 'custom-classname',
+ dataTest: 'custom-data-test-id',
+ elevation: elevations.e200,
+ maxWidth: 400,
+ placement: 'bottom-start',
+ onClickOutside: () => console.log('backdrop was clicked...'),
}
diff --git a/packages/core/src/Popper/Popper.js b/packages/core/src/Popper/Popper.js
index a61aea4aea..c4d8face59 100644
--- a/packages/core/src/Popper/Popper.js
+++ b/packages/core/src/Popper/Popper.js
@@ -1,5 +1,5 @@
-import propTypes from '@dhis2/prop-types'
import { sharedPropTypes } from '@dhis2/ui-constants'
+import PropTypes from 'prop-types'
import React, { useState, useMemo } from 'react'
import { usePopper } from 'react-popper'
import { getReferenceElement } from './getReferenceElement.js'
@@ -91,21 +91,29 @@ Popper.defaultProps = {
*/
// Prop names follow the names here: https://popper.js.org/docs/v2/constructors/
Popper.propTypes = {
- children: propTypes.node.isRequired,
- className: propTypes.string,
- dataTest: propTypes.string,
- modifiers: propTypes.arrayOf(
- propTypes.shape({
- name: propTypes.string,
- options: propTypes.object,
+ /** Content inside the Popper */
+ children: PropTypes.node.isRequired,
+ className: PropTypes.string,
+ dataTest: PropTypes.string,
+ /** A property of the `createPopper` options. See [popper docs](https://popper.js.org/docs/v2/constructors/) */
+ modifiers: PropTypes.arrayOf(
+ PropTypes.shape({
+ name: PropTypes.string,
+ options: PropTypes.object,
})
),
- observePopperResize: propTypes.bool,
- observeReferenceResize: propTypes.bool,
+ /** Makes the Popper update position when the **Popper content** changes size */
+ observePopperResize: PropTypes.bool,
+ /** Makes the Popper update position when the **reference element** changes size */
+ observeReferenceResize: PropTypes.bool,
+ /** A property of the `createPopper` options. See [popper docs](https://popper.js.org/docs/v2/constructors/) */
placement: sharedPropTypes.popperPlacementPropType,
+ /** A React ref, DOM node, or [virtual element](https://popper.js.org/docs/v2/virtual-elements/) for the popper to position itself against */
reference: sharedPropTypes.popperReferencePropType,
- strategy: propTypes.oneOf(['absolute', 'fixed']), // defaults to 'absolute'
- onFirstUpdate: propTypes.func,
+ /** A property of the `createPopper` options. See [popper docs](https://popper.js.org/docs/v2/constructors/) */
+ strategy: PropTypes.oneOf(['absolute', 'fixed']), // defaults to 'absolute'
+ /** A property of the `createPopper` options. See [popper docs](https://popper.js.org/docs/v2/constructors/) */
+ onFirstUpdate: PropTypes.func,
}
export { Popper }
diff --git a/packages/core/src/Popper/Popper.stories.js b/packages/core/src/Popper/Popper.stories.js
index cebff0968c..d447bdea36 100644
--- a/packages/core/src/Popper/Popper.stories.js
+++ b/packages/core/src/Popper/Popper.stories.js
@@ -1,13 +1,27 @@
-import propTypes from '@dhis2/prop-types'
-import React, { Component, createRef } from 'react'
+import { sharedPropTypes } from '@dhis2/ui-constants'
+import React, { useRef } from 'react'
import { Popper } from './Popper.js'
+const description = `
+A tool for adding additional information or content outside of the document flow, used for example in the Tooltip or Popover components.
+
+Since it's built using [Popper.js](https://popper.js.org/docs/v2/) and [react-popper](https://popper.js.org/react-popper/), some of that functionality can be accessed through the props of this component, like modifiers.
+
+\`\`\`js
+import { Popper } from '@dhis2/ui'
+\`\`\`
+
+_**Note**: Some of the stories may not look right on this page. View those examples in the 'Canvas' tab instead._
+`
+
export default {
- title: 'Popper',
+ title: 'Helpers/Popper',
component: Popper,
- decorators: [
- storyFN =>
,
- ],
+ parameters: { docs: { description: { component: description } } },
+ argTypes: {
+ placement: { ...sharedPropTypes.popperPlacementArgType },
+ reference: { ...sharedPropTypes.popperReferenceArgType },
+ },
}
const boxStyle = {
@@ -16,7 +30,6 @@ const boxStyle = {
justifyContent: 'center',
width: 400,
height: 400,
- marginBottom: 1000,
backgroundColor: 'aliceblue',
}
@@ -36,106 +49,77 @@ const popperStyle = {
padding: 6,
}
-class BoxWithCenteredReferenceElement extends Component {
- ref = createRef()
-
- render() {
- const { renderChildren } = this.props
- return (
-
-
- Reference element
-
- {renderChildren({ referenceElement: this.ref })}
+const Template = args => {
+ const ref = useRef(null)
+
+ return (
+
+
+ Reference Element
- )
- }
-}
-BoxWithCenteredReferenceElement.propTypes = {
- renderChildren: propTypes.func,
+
+ {args.placement}
+
+
+ )
}
-/* eslint-disable react/prop-types */
-export const Top = ({ referenceElement }) => (
-
- Top
-
-)
-export const TopStart = ({ referenceElement }) => (
-
- Top start
-
-)
-export const TopEnd = ({ referenceElement }) => (
-
- Top end
-
-)
-export const Bottom = ({ referenceElement }) => (
-
- Bottom
-
-)
-export const BottomStart = ({ referenceElement }) => (
-
- Bottom start
-
-)
-export const BottomEnd = ({ referenceElement }) => (
-
- Bottom end
-
-)
-export const Right = ({ referenceElement }) => (
-
- Right
-
-)
-export const RightStart = ({ referenceElement }) => (
-
- Right start
-
-)
-export const RightEnd = ({ referenceElement }) => (
-
- Right end
-
-)
-export const Left = ({ referenceElement }) => (
-
- Left
-
-)
-export const LeftStart = ({ referenceElement }) => (
-
- Left start
-
-)
-export const LeftEnd = ({ referenceElement }) => (
-
- Left end
-
-)
-export const ElementRef = () => {
+export const Top = Template.bind({})
+Top.args = { placement: 'top' }
+
+export const TopStart = Template.bind({})
+TopStart.args = { placement: 'top-start' }
+
+export const TopEnd = Template.bind({})
+TopEnd.args = { placement: 'top-end' }
+
+export const Bottom = Template.bind({})
+Bottom.args = { placement: 'bottom' }
+
+export const BottomStart = Template.bind({})
+BottomStart.args = { placement: 'bottom-start' }
+
+export const BottomEnd = Template.bind({})
+BottomEnd.args = { placement: 'bottom-end' }
+
+export const Right = Template.bind({})
+Right.args = { placement: 'right' }
+
+export const RightStart = Template.bind({})
+RightStart.args = { placement: 'right-start' }
+
+export const RightEnd = Template.bind({})
+RightEnd.args = { placement: 'right-end' }
+
+export const Left = Template.bind({})
+Left.args = { placement: 'left' }
+
+export const LeftStart = Template.bind({})
+LeftStart.args = { placement: 'left-start' }
+
+export const LeftEnd = Template.bind({})
+LeftEnd.args = { placement: 'left-end' }
+
+export const ElementRef = args => {
const anchor = document.createElement('div')
document.body.appendChild(anchor)
return (
-
- Left end
-
-
+
)
}
-export const VirtualElementRef = () => {
+ElementRef.args = { placement: 'left-end' }
+ElementRef.parameters = { docs: { source: { type: 'code' } } }
+
+export const VirtualElementRef = args => {
const virtualElement = {
getBoundingClientRect: () => ({
width: 0,
@@ -150,13 +134,12 @@ export const VirtualElementRef = () => {
}
return (
-
- Left end
-
-
+
)
}
+VirtualElementRef.args = { placement: 'left-end' }
+VirtualElementRef.parameters = { docs: { source: { type: 'code' } } }
diff --git a/packages/core/src/Radio/Radio.js b/packages/core/src/Radio/Radio.js
index 32096a2b5f..43a9807e05 100644
--- a/packages/core/src/Radio/Radio.js
+++ b/packages/core/src/Radio/Radio.js
@@ -1,6 +1,6 @@
-import propTypes from '@dhis2/prop-types'
import { colors, theme, sharedPropTypes } from '@dhis2/ui-constants'
import cx from 'classnames'
+import PropTypes from 'prop-types'
import React, { Component, createRef } from 'react'
import { RadioRegular, RadioDense } from '../Icons/index.js'
;('') // TODO: https://github.com/jsdoc/jsdoc/issues/1718
@@ -189,22 +189,30 @@ Radio.defaultProps = {
* @prop {string} [dataTest]
*/
Radio.propTypes = {
- checked: propTypes.bool,
- className: propTypes.string,
- dataTest: propTypes.string,
- dense: propTypes.bool,
- disabled: propTypes.bool,
+ checked: PropTypes.bool,
+ className: PropTypes.string,
+ dataTest: PropTypes.string,
+ dense: PropTypes.bool,
+ disabled: PropTypes.bool,
+ /** Adds 'error' styling for feedback. Mutually exclusive with `valid` and `warning` props */
error: sharedPropTypes.statusPropType,
- initialFocus: propTypes.bool,
- label: propTypes.node,
- name: propTypes.string,
- tabIndex: propTypes.string,
+ initialFocus: PropTypes.bool,
+ label: PropTypes.node,
+ /** Name associated with this element. Passed in object to event handler functions */
+ name: PropTypes.string,
+ tabIndex: PropTypes.string,
+ /** Adds 'valid' styling for feedback. Mutually exclusive with `error` and `warning` props */
valid: sharedPropTypes.statusPropType,
- value: propTypes.string,
+ /** Value associated with this element. Passed in object to event handler functions */
+ value: PropTypes.string,
+ /** Adds 'warning' styling for feedback. Mutually exclusive with `valid` and `error` props */
warning: sharedPropTypes.statusPropType,
- onBlur: propTypes.func,
- onChange: propTypes.func,
- onFocus: propTypes.func,
+ /** Called with the signature `({ name: string, value: string, checked: bool }, event)` */
+ onBlur: PropTypes.func,
+ /** Called with the signature `({ name: string, value: string, checked: bool }, event)` */
+ onChange: PropTypes.func,
+ /** Called with the signature `({ name: string, value: string, checked: bool }, event)` */
+ onFocus: PropTypes.func,
}
export { Radio }
diff --git a/packages/core/src/Radio/Radio.stories.js b/packages/core/src/Radio/Radio.stories.js
index 8ff5fd167b..bcf1abd811 100644
--- a/packages/core/src/Radio/Radio.stories.js
+++ b/packages/core/src/Radio/Radio.stories.js
@@ -1,7 +1,29 @@
-import { storiesOf } from '@storybook/react'
+import { sharedPropTypes } from '@dhis2/ui-constants'
import React from 'react'
import { Radio } from './Radio.js'
+const subtitle = `A control that allows a user to select a single option from a choice of several`
+
+const description = `
+Radio buttons are used where a user has the choice of several options but must select only one. Radio buttons should be used where the user has to make a choice, there is no 'off' or 'none' state unless explicitly defined. Radio buttons should be used when there are 5 or less options available. With more than five, a dropdown/Select menu should be used instead.
+
+Do not use a radio button if only a single option is available; use a Checkbox here instead.
+
+If there are many options that need to select from, consider using a Select instead.
+
+#### Size
+
+Radio buttons are available in Regular and Dense sizes. Regular size is usually used in forms and whenever radio buttons are used standalone. Dense size radio buttons are used inside other complex components, not as main elements of a UI.
+
+#### See more
+
+Learn more about Radio buttons at [Design System: Radio](https://github.com/dhis2/design-system/blob/master/atoms/radio.md).
+
+\`\`\`js
+import { Radio } from '@dhis2/ui'
+\`\`\`
+`
+
window.onChange = (payload, event) => {
console.log('onChange payload', payload)
console.log('onChange event', event)
@@ -21,362 +43,111 @@ const onChange = (...args) => window.onChange(...args)
const onFocus = (...args) => window.onFocus(...args)
const onBlur = (...args) => window.onBlur(...args)
-storiesOf('Radio', module)
- // Regular
- .add('Default', () => (
-
- ))
-
- .add('Focused unchecked', () => (
- <>
-
-
- >
- ))
-
- .add('Focused checked', () => (
- <>
-
-
- >
- ))
-
- .add('Checked', () => (
-
- ))
-
- .add('Disabled', () => (
- <>
-
-
- >
- ))
-
- .add('Valid', () => (
- <>
-
-
- >
- ))
-
- .add('Warning', () => (
- <>
-
-
- >
- ))
-
- .add('Error', () => (
- <>
-
-
- >
- ))
-
- .add('Image label', () => (
-
}
- value="with-help"
- onChange={onChange}
- onFocus={onFocus}
- onBlur={onBlur}
- />
- ))
-
- // Dense
- .add('Default - Dense', () => (
-
- ))
-
- .add('Focused unchecked - Dense', () => (
-
- ))
-
- .add('Focused checked - Dense', () => (
-
- ))
-
- .add('Checked - Dense', () => (
-
- ))
-
- .add('Disabled - Dense', () => (
- <>
-
-
- >
- ))
-
- .add('Valid - Dense', () => (
- <>
-
-
- >
- ))
-
- .add('Warning - Dense', () => (
- <>
-
-
- >
- ))
-
- .add('Error - Dense', () => (
- <>
-
-
- >
- ))
-
- .add('Image label - Dense', () => (
-
}
- value="with-help"
- onChange={onChange}
- onFocus={onFocus}
- onBlur={onBlur}
- />
- ))
- .add('No Label', () => (
-
- ))
+export default {
+ title: 'Forms/Radio/Radio',
+ component: Radio,
+ parameters: {
+ componentSubtitle: subtitle,
+ docs: { description: { component: description } },
+ },
+ // Default args for all stories
+ args: {
+ name: 'Ex',
+ label: 'Radio',
+ value: 'default',
+ onChange: onChange,
+ onFocus: onFocus,
+ onBlur: onBlur,
+ },
+ argTypes: {
+ valid: { ...sharedPropTypes.statusArgType },
+ error: { ...sharedPropTypes.statusArgType },
+ warning: { ...sharedPropTypes.statusArgType },
+ },
+}
+
+const Template = args =>
+
+const CheckedUncheckedTemplate = args => (
+ <>
+
+
+ >
+)
+
+export const Default = Template.bind({})
+
+export const FocusedUnchecked = args => (
+ <>
+
+
+ >
+)
+// Stories with initial focus are distracting on docs page
+FocusedUnchecked.parameters = { docs: { disable: true } }
+
+export const FocusedChecked = FocusedUnchecked.bind({})
+FocusedChecked.args = { checked: true }
+FocusedChecked.parameters = { docs: { disable: true } }
+
+export const Checked = Template.bind({})
+Checked.args = { checked: true, value: 'checked' }
+
+export const Disabled = CheckedUncheckedTemplate.bind({})
+Disabled.args = { disabled: true, value: 'disabled' }
+
+export const Valid = CheckedUncheckedTemplate.bind({})
+Valid.args = { valid: true, value: 'valid' }
+
+export const Warning = CheckedUncheckedTemplate.bind({})
+Warning.args = { warning: true, value: 'warning' }
+
+export const Error = CheckedUncheckedTemplate.bind({})
+Error.args = { error: true, value: 'error' }
+
+export const ImageLabel = Template.bind({})
+ImageLabel.args = {
+ label:
,
+ value: 'with-help',
+}
+
+export const DefaultDense = Template.bind({})
+DefaultDense.args = { dense: true }
+DefaultDense.storyName = 'Default - Dense'
+
+export const FocusedUncheckedDense = FocusedUnchecked.bind({})
+FocusedUncheckedDense.args = { ...DefaultDense.args }
+FocusedUncheckedDense.storyName = 'Focused unchecked - Dense'
+FocusedUncheckedDense.parameters = { docs: { disable: true } }
+
+export const FocusedCheckedDense = FocusedUnchecked.bind({})
+FocusedCheckedDense.args = { ...DefaultDense.args, checked: true }
+FocusedCheckedDense.storyName = 'Focused checked - Dense'
+FocusedCheckedDense.parameters = { docs: { disable: true } }
+
+export const CheckedDense = Template.bind({})
+CheckedDense.args = { ...Checked.args, ...DefaultDense.args }
+CheckedDense.storyName = 'Checked - Dense'
+
+export const DisabledDense = CheckedUncheckedTemplate.bind({})
+DisabledDense.args = { ...Disabled.args, ...DefaultDense.args }
+DisabledDense.storyName = 'Disabled - Dense'
+
+export const ValidDense = CheckedUncheckedTemplate.bind({})
+ValidDense.args = { ...Valid.args, ...DefaultDense.args }
+ValidDense.storyName = 'Valid - Dense'
+
+export const WarningDense = CheckedUncheckedTemplate.bind({})
+WarningDense.args = { ...Warning.args, ...DefaultDense.args }
+WarningDense.storyName = 'Warning - Dense'
+
+export const ErrorDense = CheckedUncheckedTemplate.bind({})
+ErrorDense.args = { ...Error.args, ...DefaultDense.args }
+ErrorDense.storyName = 'Error - Dense'
+
+export const ImageLabelDense = Template.bind({})
+ImageLabelDense.args = { ...ImageLabel.args, ...DefaultDense.args }
+ImageLabelDense.storyName = 'Image label - Dense'
+
+export const NoLabel = Template.bind({})
+NoLabel.args = { label: null, className: 'some-name' }
diff --git a/packages/core/src/SingleSelect/SingleSelect.js b/packages/core/src/SingleSelect/SingleSelect.js
index e5f97337c7..20c0e08b64 100644
--- a/packages/core/src/SingleSelect/SingleSelect.js
+++ b/packages/core/src/SingleSelect/SingleSelect.js
@@ -1,5 +1,6 @@
import propTypes from '@dhis2/prop-types'
import { spacers, sharedPropTypes } from '@dhis2/ui-constants'
+import PropTypes from 'prop-types'
import React from 'react'
import { StatusIcon } from '../Icons/index.js'
import { Loading } from '../Select/Loading.js'
@@ -151,35 +152,43 @@ SingleSelect.defaultProps = {
* @prop {string} [dataTest]
*/
SingleSelect.propTypes = {
- children: propTypes.node,
- className: propTypes.string,
- clearText: propTypes.requiredIf(props => props.clearable, propTypes.string),
- clearable: propTypes.bool,
- dataTest: propTypes.string,
- dense: propTypes.bool,
- disabled: propTypes.bool,
- empty: propTypes.node,
+ children: PropTypes.node,
+ className: PropTypes.string,
+ /** Text on button that clears selection. Required if `clearable` prop is true */
+ clearText: propTypes.requiredIf(props => props.clearable, PropTypes.string),
+ /** Adds a button to clear selection */
+ clearable: PropTypes.bool,
+ dataTest: PropTypes.string,
+ dense: PropTypes.bool,
+ disabled: PropTypes.bool,
+ /** Text or component to display when there are no options */
+ empty: PropTypes.node,
+ /** Applies 'error' appearance for validation feedback. Mutually exclusive with `warning` and `valid` props */
error: sharedPropTypes.statusPropType,
- filterPlaceholder: propTypes.string,
- filterable: propTypes.bool,
- initialFocus: propTypes.bool,
- inputMaxHeight: propTypes.string,
- loading: propTypes.bool,
- loadingText: propTypes.string,
- maxHeight: propTypes.string,
+ filterPlaceholder: PropTypes.string,
+ /** Adds a filter field to add text to filter options */
+ filterable: PropTypes.bool,
+ initialFocus: PropTypes.bool,
+ inputMaxHeight: PropTypes.string,
+ loading: PropTypes.bool,
+ loadingText: PropTypes.string,
+ maxHeight: PropTypes.string,
+ /** Text to show when filter returns no results. Required if `filterable` prop is true */
noMatchText: propTypes.requiredIf(
props => props.filterable,
- propTypes.string
+ PropTypes.string
),
- placeholder: propTypes.string,
- prefix: propTypes.string,
- selected: propTypes.string,
- tabIndex: propTypes.string,
+ placeholder: PropTypes.string,
+ prefix: PropTypes.string,
+ selected: PropTypes.string,
+ tabIndex: PropTypes.string,
+ /** Applies 'valid' appearance for validation feedback. Mutually exclusive with `warning` and `error` props */
valid: sharedPropTypes.statusPropType,
+ /** Applies 'warning' appearance for validation feedback. Mutually exclusive with `valid` and `error` props */
warning: sharedPropTypes.statusPropType,
- onBlur: propTypes.func,
- onChange: propTypes.func,
- onFocus: propTypes.func,
+ onBlur: PropTypes.func,
+ onChange: PropTypes.func,
+ onFocus: PropTypes.func,
}
export { SingleSelect }
diff --git a/packages/core/src/SingleSelect/SingleSelect.stories.js b/packages/core/src/SingleSelect/SingleSelect.stories.js
index eda2561f89..20367fb861 100644
--- a/packages/core/src/SingleSelect/SingleSelect.stories.js
+++ b/packages/core/src/SingleSelect/SingleSelect.stories.js
@@ -1,11 +1,28 @@
import propTypes from '@dhis2/prop-types'
-import { storiesOf } from '@storybook/react'
+import { sharedPropTypes } from '@dhis2/ui-constants'
import React from 'react'
import { SingleSelect, SingleSelectOption } from '../index.js'
-window.onChange = window.Cypress && window.Cypress.cy.stub()
-window.onFocus = window.Cypress && window.Cypress.cy.stub()
-window.onBlur = window.Cypress && window.Cypress.cy.stub()
+const description = `
+Use a select component wherever the user needs to make a selection of one or more options from a list of 6 or more options. If there are less than 6 options to choose from and space permits, use checkboxes for multiple selection and radio buttons for single selection. If the user needs to make a complex selection with a specific ordering, use a transfer instead.
+
+Learn more about using Selects at [Design System: Select](https://github.com/dhis2/design-system/blob/master/molecules/select.md).
+
+\`\`\`js
+import { SingleSelect, SingleSelectOption } from '@dhis2/ui'
+\`\`\`
+
+_**Note:** Due to demo limitations on this page, only one representative example is rendered here. For more (interactive) examples, see individual stories in the 'Canvas' tab._
+`
+
+const eventHandler = handlerName => (payload, event) => {
+ console.log(`${handlerName} payload`, payload)
+ console.log(`${handlerName} event`, event)
+}
+
+const onChange = eventHandler('onChange')
+const onFocus = eventHandler('onFocus')
+const onBlur = eventHandler('onBlur')
const CustomSingleSelectOption = ({ label, onClick }) => (
onClick({}, e)}>{label}
@@ -16,94 +33,218 @@ CustomSingleSelectOption.propTypes = {
onClick: propTypes.func,
}
-storiesOf('SingleSelect', module)
- .add('With options', () => (
-
-
-
-
-
- ))
- .add('With options and onChange', () => (
-
-
-
-
-
- ))
- .add('With onFocus', () => (
-
-
-
-
-
- ))
- .add('With onBlur', () => (
-
-
-
-
-
- ))
- .add('With custom options and onChange', () => (
-
-
-
-
-
- ))
- .add('With invalid options', () => (
-
- invalid one
-
- invalid two
-
- invalid three
-
- {null}
- {undefined}
- {false}
-
- ))
- .add('With invalid filterable options', () => (
-
- invalid one
-
- invalid two
-
- invalid three
-
-
- ))
- .add('With initialFocus', () => (
-
- ))
- .add('Empty', () =>
)
- .add('Empty with empty text', () => (
-
- ))
- .add('Empty with empty component', () => (
-
Custom empty component