@dhis2-ui/button 10.16.2 → 10.16.3-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +8 -7
- package/src/button/__tests__/Button.test.js +126 -0
- package/src/button/button.e2e.stories.js +24 -0
- package/src/button/button.js +162 -0
- package/src/button/button.prod.stories.js +305 -0
- package/src/button/button.styles.js +298 -0
- package/src/button/features/can_be_blurred/index.js +19 -0
- package/src/button/features/can_be_blurred.feature +6 -0
- package/src/button/features/can_be_clicked/index.js +18 -0
- package/src/button/features/can_be_clicked.feature +6 -0
- package/src/button/features/can_be_focused/index.js +18 -0
- package/src/button/features/can_be_focused.feature +6 -0
- package/src/button/index.js +1 -0
- package/src/button-strip/button-strip.e2e.stories.js +13 -0
- package/src/button-strip/button-strip.js +58 -0
- package/src/button-strip/button-strip.prod.stories.js +88 -0
- package/src/button-strip/features/accepts_children/index.js +10 -0
- package/src/button-strip/features/accepts_children.feature +5 -0
- package/src/button-strip/index.js +1 -0
- package/src/dropdown-button/__tests__/dropdown-button.test.js +135 -0
- package/src/dropdown-button/dropdown-button.e2e.stories.js +67 -0
- package/src/dropdown-button/dropdown-button.js +239 -0
- package/src/dropdown-button/dropdown-button.prod.stories.js +129 -0
- package/src/dropdown-button/features/accepts_children/index.js +10 -0
- package/src/dropdown-button/features/accepts_children.feature +5 -0
- package/src/dropdown-button/features/accepts_component/index.js +18 -0
- package/src/dropdown-button/features/accepts_component.feature +5 -0
- package/src/dropdown-button/features/accepts_icon/index.js +10 -0
- package/src/dropdown-button/features/accepts_icon.feature +5 -0
- package/src/dropdown-button/features/accepts_initial_focus/index.js +11 -0
- package/src/dropdown-button/features/accepts_initial_focus.feature +5 -0
- package/src/dropdown-button/features/button_is_clickable/index.js +15 -0
- package/src/dropdown-button/features/button_is_clickable.feature +6 -0
- package/src/dropdown-button/features/can_be_disabled/index.js +11 -0
- package/src/dropdown-button/features/can_be_disabled.feature +6 -0
- package/src/dropdown-button/features/common/index.js +5 -0
- package/src/dropdown-button/features/opens_a_dropdown/index.js +26 -0
- package/src/dropdown-button/features/opens_a_dropdown.feature +11 -0
- package/src/dropdown-button/index.js +1 -0
- package/src/index.js +4 -0
- package/src/locales/en/translations.json +3 -0
- package/src/locales/index.js +16 -0
- package/src/split-button/features/accepts_children/index.js +12 -0
- package/src/split-button/features/accepts_children.feature +5 -0
- package/src/split-button/features/accepts_icon/index.js +16 -0
- package/src/split-button/features/accepts_icon.feature +5 -0
- package/src/split-button/features/accepts_initial_focus/index.js +13 -0
- package/src/split-button/features/accepts_initial_focus.feature +5 -0
- package/src/split-button/features/arrow_opens_menu/index.js +34 -0
- package/src/split-button/features/arrow_opens_menu.feature +15 -0
- package/src/split-button/features/button_is_clickable/index.js +13 -0
- package/src/split-button/features/button_is_clickable.feature +6 -0
- package/src/split-button/features/can_be_disabled/index.js +25 -0
- package/src/split-button/features/can_be_disabled.feature +11 -0
- package/src/split-button/features/common/index.js +9 -0
- package/src/split-button/index.js +1 -0
- package/src/split-button/split-button.e2e.stories.js +56 -0
- package/src/split-button/split-button.js +273 -0
- package/src/split-button/split-button.prod.stories.js +162 -0
- package/src/split-button/split-button.test.js +84 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Given, Then } from '@badeball/cypress-cucumber-preprocessor'
|
|
2
|
+
|
|
3
|
+
Given('a SplitButton with initialFocus is rendered', () => {
|
|
4
|
+
cy.visitStory('SplitButton', 'With initial focus')
|
|
5
|
+
})
|
|
6
|
+
|
|
7
|
+
Then('the SplitButton button is focused', () => {
|
|
8
|
+
cy.focused().should(
|
|
9
|
+
'have.attr',
|
|
10
|
+
'data-test',
|
|
11
|
+
'dhis2-uicore-splitbutton-button'
|
|
12
|
+
)
|
|
13
|
+
})
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Given, When, Then } from '@badeball/cypress-cucumber-preprocessor'
|
|
2
|
+
|
|
3
|
+
Given('a SplitButton is rendered', () => {
|
|
4
|
+
cy.visitStory('SplitButton', 'Default')
|
|
5
|
+
})
|
|
6
|
+
|
|
7
|
+
Given('the SplitButton menu is open', () => {
|
|
8
|
+
cy.get('[data-test="dhis2-uicore-splitbutton-toggle"]').click()
|
|
9
|
+
cy.get('[data-test="dhis2-uicore-splitbutton-menu"]').should('be.visible')
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
Given('the SplitButton menu is closed', () => {
|
|
13
|
+
cy.get('[data-test="dhis2-uicore-splitbutton-menu"]').should('not.exist')
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
When('the user clicks the backdrop Layer', () => {
|
|
17
|
+
cy.get('[data-test="dhis2-uicore-layer"]').click()
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
Then('the menu is not visible', () => {
|
|
21
|
+
cy.get('[data-test="dhis2-uicore-splitbutton-menu"]').should('not.exist')
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
Then('the component is not visible', () => {
|
|
25
|
+
cy.contains('Component').should('not.exist')
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
Then('the menu is visible', () => {
|
|
29
|
+
cy.get('[data-test="dhis2-uicore-splitbutton-menu"]').should('be.visible')
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
Then('the component is visible', () => {
|
|
33
|
+
cy.contains('Component').should('be.visible')
|
|
34
|
+
})
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
Feature: The SplitButton renders a Popper
|
|
2
|
+
|
|
3
|
+
Scenario: The user opens the Popper
|
|
4
|
+
Given a SplitButton is rendered
|
|
5
|
+
And the SplitButton menu is closed
|
|
6
|
+
When the SplitButton toggle is clicked
|
|
7
|
+
Then the menu is visible
|
|
8
|
+
And the component is visible
|
|
9
|
+
|
|
10
|
+
Scenario: The user closes the Popper
|
|
11
|
+
Given a SplitButton is rendered
|
|
12
|
+
And the SplitButton menu is open
|
|
13
|
+
When the user clicks the backdrop Layer
|
|
14
|
+
Then the menu is not visible
|
|
15
|
+
And the component is not visible
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Given, Then } from '@badeball/cypress-cucumber-preprocessor'
|
|
2
|
+
|
|
3
|
+
Given('a SplitButton with onClick hander is rendered', () => {
|
|
4
|
+
cy.visitStory('SplitButton', 'With on click')
|
|
5
|
+
})
|
|
6
|
+
|
|
7
|
+
Then('the onClick handler is called', () => {
|
|
8
|
+
cy.window().its('onClick').should('be.calledWith', {
|
|
9
|
+
name: 'Button',
|
|
10
|
+
value: 'default',
|
|
11
|
+
open: false,
|
|
12
|
+
})
|
|
13
|
+
})
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Given, When, Then } from '@badeball/cypress-cucumber-preprocessor'
|
|
2
|
+
|
|
3
|
+
Given('a disabled SplitButton is rendered', () => {
|
|
4
|
+
cy.visitStory('SplitButton', 'With disabled')
|
|
5
|
+
})
|
|
6
|
+
|
|
7
|
+
When('the user clicks the SplitButton Button', () => {
|
|
8
|
+
cy.get('[data-test="dhis2-uicore-splitbutton-button"]').click({
|
|
9
|
+
force: true,
|
|
10
|
+
})
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
Then('the SplitButton Button is not focused', () => {
|
|
14
|
+
cy.focused().should('not.exist')
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
When('the user clicks the SplitButton Toggle', () => {
|
|
18
|
+
cy.get('[data-test="dhis2-uicore-splitbutton-toggle"]').click({
|
|
19
|
+
force: true,
|
|
20
|
+
})
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
Then('the SplitButton Toggle is not focused', () => {
|
|
24
|
+
cy.focused().should('not.exist')
|
|
25
|
+
})
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
Feature: The SplitButton can be disabled
|
|
2
|
+
|
|
3
|
+
Scenario: The user clicks a disabled SplitButton Button
|
|
4
|
+
Given a disabled SplitButton is rendered
|
|
5
|
+
When the user clicks the SplitButton Button
|
|
6
|
+
Then the SplitButton Button is not focused
|
|
7
|
+
|
|
8
|
+
Scenario: The user clicks a disabled SplitButton Toggle
|
|
9
|
+
Given a disabled SplitButton is rendered
|
|
10
|
+
When the user clicks the SplitButton Toggle
|
|
11
|
+
Then the SplitButton Toggle is not focused
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { When } from '@badeball/cypress-cucumber-preprocessor'
|
|
2
|
+
|
|
3
|
+
When('the SplitButton is clicked', () => {
|
|
4
|
+
cy.get('[data-test="dhis2-uicore-splitbutton-button"]').click()
|
|
5
|
+
})
|
|
6
|
+
|
|
7
|
+
When('the SplitButton toggle is clicked', () => {
|
|
8
|
+
cy.get('[data-test="dhis2-uicore-splitbutton-toggle"]').click()
|
|
9
|
+
})
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { SplitButton } from './split-button.js'
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { SplitButton } from './split-button.js'
|
|
3
|
+
|
|
4
|
+
window.onClick = window.Cypress && window.Cypress.cy.stub()
|
|
5
|
+
|
|
6
|
+
export default { title: 'SplitButton' }
|
|
7
|
+
export const Default = () => (
|
|
8
|
+
<SplitButton name="Button" value="default" component={<div>Component</div>}>
|
|
9
|
+
Label me!
|
|
10
|
+
</SplitButton>
|
|
11
|
+
)
|
|
12
|
+
export const WithOnClick = () => (
|
|
13
|
+
<SplitButton
|
|
14
|
+
name="Button"
|
|
15
|
+
value="default"
|
|
16
|
+
component={<div>Component</div>}
|
|
17
|
+
onClick={window.onClick}
|
|
18
|
+
>
|
|
19
|
+
Label me!
|
|
20
|
+
</SplitButton>
|
|
21
|
+
)
|
|
22
|
+
export const WithChildren = () => (
|
|
23
|
+
<SplitButton name="Button" value="default" component={<div>Component</div>}>
|
|
24
|
+
I am a child
|
|
25
|
+
</SplitButton>
|
|
26
|
+
)
|
|
27
|
+
export const WithIcon = () => (
|
|
28
|
+
<SplitButton
|
|
29
|
+
name="Button"
|
|
30
|
+
value="default"
|
|
31
|
+
component={<div>Component</div>}
|
|
32
|
+
icon={<div>Icon</div>}
|
|
33
|
+
>
|
|
34
|
+
Children
|
|
35
|
+
</SplitButton>
|
|
36
|
+
)
|
|
37
|
+
export const WithInitialFocus = () => (
|
|
38
|
+
<SplitButton
|
|
39
|
+
name="Button"
|
|
40
|
+
value="default"
|
|
41
|
+
component={<div>Component</div>}
|
|
42
|
+
initialFocus
|
|
43
|
+
>
|
|
44
|
+
Children
|
|
45
|
+
</SplitButton>
|
|
46
|
+
)
|
|
47
|
+
export const WithDisabled = () => (
|
|
48
|
+
<SplitButton
|
|
49
|
+
name="Button"
|
|
50
|
+
value="default"
|
|
51
|
+
component={<div>Component</div>}
|
|
52
|
+
disabled
|
|
53
|
+
>
|
|
54
|
+
Children
|
|
55
|
+
</SplitButton>
|
|
56
|
+
)
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import { requiredIf } from '@dhis2/prop-types'
|
|
2
|
+
import { spacers, sharedPropTypes } from '@dhis2/ui-constants'
|
|
3
|
+
import { IconChevronUp16, IconChevronDown16 } from '@dhis2/ui-icons'
|
|
4
|
+
import { Layer } from '@dhis2-ui/layer'
|
|
5
|
+
import { Popper } from '@dhis2-ui/popper'
|
|
6
|
+
import cx from 'classnames'
|
|
7
|
+
import PropTypes from 'prop-types'
|
|
8
|
+
import React, { Component } from 'react'
|
|
9
|
+
import css from 'styled-jsx/css'
|
|
10
|
+
import { Button } from '../button/index.js'
|
|
11
|
+
import i18n from '../locales/index.js'
|
|
12
|
+
|
|
13
|
+
const rightButton = css.resolve`
|
|
14
|
+
button {
|
|
15
|
+
padding: 0 ${spacers.dp12};
|
|
16
|
+
}
|
|
17
|
+
`
|
|
18
|
+
|
|
19
|
+
class SplitButton extends Component {
|
|
20
|
+
state = {
|
|
21
|
+
open: false,
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
static defaultProps = {
|
|
25
|
+
dataTest: 'dhis2-uicore-splitbutton',
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
anchorRef = React.createRef()
|
|
29
|
+
|
|
30
|
+
isControlled = () => typeof this.props.open === 'boolean'
|
|
31
|
+
|
|
32
|
+
componentDidMount() {
|
|
33
|
+
document.addEventListener('keydown', this.handleKeyDown)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
componentWillUnmount() {
|
|
37
|
+
document.removeEventListener('keydown', this.handleKeyDown)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
handleKeyDown = (event) => {
|
|
41
|
+
const open = this.isControlled() ? this.props.open : this.state.open
|
|
42
|
+
if (event.key === 'Escape' && open) {
|
|
43
|
+
event.preventDefault()
|
|
44
|
+
event.stopPropagation()
|
|
45
|
+
if (this.isControlled()) {
|
|
46
|
+
if (this.props.onToggle) {
|
|
47
|
+
this.props.onToggle(
|
|
48
|
+
{
|
|
49
|
+
name: this.props.name,
|
|
50
|
+
value: this.props.value,
|
|
51
|
+
open: false,
|
|
52
|
+
},
|
|
53
|
+
event
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
} else {
|
|
57
|
+
this.setState({ open: false })
|
|
58
|
+
}
|
|
59
|
+
this.anchorRef.current && this.anchorRef.current.focus()
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
handlePrimaryAction = (payload, event) => {
|
|
64
|
+
if (this.props.onClick) {
|
|
65
|
+
this.props.onClick(
|
|
66
|
+
{
|
|
67
|
+
name: payload.name,
|
|
68
|
+
value: payload.value,
|
|
69
|
+
open: this.isControlled()
|
|
70
|
+
? this.props.open
|
|
71
|
+
: this.state.open,
|
|
72
|
+
},
|
|
73
|
+
event
|
|
74
|
+
)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
handleToggle = (payload, event) => {
|
|
79
|
+
if (this.isControlled()) {
|
|
80
|
+
if (this.props.onToggle) {
|
|
81
|
+
this.props.onToggle(
|
|
82
|
+
{
|
|
83
|
+
name: payload.name,
|
|
84
|
+
value: payload.value,
|
|
85
|
+
open: !this.props.open,
|
|
86
|
+
},
|
|
87
|
+
event
|
|
88
|
+
)
|
|
89
|
+
}
|
|
90
|
+
} else {
|
|
91
|
+
this.setState((prevState) => ({ open: !prevState.open }))
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
handleBackdropClick = (event) => {
|
|
96
|
+
if (this.isControlled()) {
|
|
97
|
+
if (this.props.onToggle) {
|
|
98
|
+
this.props.onToggle(
|
|
99
|
+
{
|
|
100
|
+
name: this.props.name,
|
|
101
|
+
value: this.props.value,
|
|
102
|
+
open: false,
|
|
103
|
+
},
|
|
104
|
+
event
|
|
105
|
+
)
|
|
106
|
+
}
|
|
107
|
+
} else {
|
|
108
|
+
this.setState({ open: false })
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
render() {
|
|
113
|
+
const open = this.isControlled() ? this.props.open : this.state.open
|
|
114
|
+
const {
|
|
115
|
+
component,
|
|
116
|
+
children,
|
|
117
|
+
className,
|
|
118
|
+
name,
|
|
119
|
+
value,
|
|
120
|
+
icon,
|
|
121
|
+
small,
|
|
122
|
+
large,
|
|
123
|
+
primary,
|
|
124
|
+
secondary,
|
|
125
|
+
destructive,
|
|
126
|
+
disabled,
|
|
127
|
+
type,
|
|
128
|
+
tabIndex,
|
|
129
|
+
dataTest = 'dhis2-uicore-splitbutton',
|
|
130
|
+
initialFocus,
|
|
131
|
+
} = this.props
|
|
132
|
+
|
|
133
|
+
const arrow = open ? <IconChevronUp16 /> : <IconChevronDown16 />
|
|
134
|
+
|
|
135
|
+
return (
|
|
136
|
+
<div ref={this.anchorRef} data-test={dataTest}>
|
|
137
|
+
<Button
|
|
138
|
+
name={name}
|
|
139
|
+
value={value}
|
|
140
|
+
icon={icon}
|
|
141
|
+
small={small}
|
|
142
|
+
large={large}
|
|
143
|
+
primary={primary}
|
|
144
|
+
secondary={secondary}
|
|
145
|
+
destructive={destructive}
|
|
146
|
+
disabled={disabled}
|
|
147
|
+
onClick={this.handlePrimaryAction}
|
|
148
|
+
type={type}
|
|
149
|
+
tabIndex={tabIndex}
|
|
150
|
+
className={cx(className)}
|
|
151
|
+
initialFocus={initialFocus}
|
|
152
|
+
dataTest={`${dataTest}-button`}
|
|
153
|
+
>
|
|
154
|
+
{children}
|
|
155
|
+
</Button>
|
|
156
|
+
|
|
157
|
+
<Button
|
|
158
|
+
name={name}
|
|
159
|
+
value={value}
|
|
160
|
+
small={small}
|
|
161
|
+
large={large}
|
|
162
|
+
primary={primary}
|
|
163
|
+
secondary={secondary}
|
|
164
|
+
destructive={destructive}
|
|
165
|
+
disabled={disabled}
|
|
166
|
+
onClick={this.handleToggle}
|
|
167
|
+
type={type}
|
|
168
|
+
tabIndex={tabIndex}
|
|
169
|
+
className={cx(className, rightButton.className)}
|
|
170
|
+
dataTest={`${dataTest}-toggle`}
|
|
171
|
+
title={i18n.t('Toggle dropdown')}
|
|
172
|
+
aria-label={i18n.t('Toggle dropdown')}
|
|
173
|
+
>
|
|
174
|
+
{arrow}
|
|
175
|
+
</Button>
|
|
176
|
+
|
|
177
|
+
{open && (
|
|
178
|
+
<Layer
|
|
179
|
+
onBackdropClick={this.handleBackdropClick}
|
|
180
|
+
transparent
|
|
181
|
+
>
|
|
182
|
+
<Popper
|
|
183
|
+
dataTest={`${dataTest}-menu`}
|
|
184
|
+
placement="bottom-end"
|
|
185
|
+
reference={this.anchorRef}
|
|
186
|
+
>
|
|
187
|
+
{component}
|
|
188
|
+
</Popper>
|
|
189
|
+
</Layer>
|
|
190
|
+
)}
|
|
191
|
+
|
|
192
|
+
{rightButton.styles}
|
|
193
|
+
<style jsx>{`
|
|
194
|
+
div {
|
|
195
|
+
display: inline-flex;
|
|
196
|
+
color: inherit;
|
|
197
|
+
white-space: nowrap;
|
|
198
|
+
// create a stacking context for the children
|
|
199
|
+
position: relative;
|
|
200
|
+
z-index: 0;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
div > :global(button:first-child) {
|
|
204
|
+
border-start-end-radius: 0;
|
|
205
|
+
border-end-end-radius: 0;
|
|
206
|
+
border-inline-end: 0;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
div > :global(button:last-child) {
|
|
210
|
+
border-start-start-radius: 0;
|
|
211
|
+
border-end-start-radius: 0;
|
|
212
|
+
}
|
|
213
|
+
`}</style>
|
|
214
|
+
</div>
|
|
215
|
+
)
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
SplitButton.propTypes = {
|
|
220
|
+
children: PropTypes.string,
|
|
221
|
+
className: PropTypes.string,
|
|
222
|
+
/** Component to render when the dropdown is opened */
|
|
223
|
+
component: PropTypes.element,
|
|
224
|
+
dataTest: PropTypes.string,
|
|
225
|
+
/**
|
|
226
|
+
* Applies 'destructive' button appearance, implying a dangerous action.
|
|
227
|
+
*/
|
|
228
|
+
destructive: PropTypes.bool,
|
|
229
|
+
/** Disables the button and makes it uninteractive */
|
|
230
|
+
disabled: PropTypes.bool,
|
|
231
|
+
/** An icon to add inside the button */
|
|
232
|
+
icon: PropTypes.element,
|
|
233
|
+
/** Grants the button the initial focus */
|
|
234
|
+
initialFocus: PropTypes.bool,
|
|
235
|
+
/** Changes button size. Mutually exclusive with `small` prop */
|
|
236
|
+
large: sharedPropTypes.sizePropType,
|
|
237
|
+
name: PropTypes.string,
|
|
238
|
+
/**
|
|
239
|
+
* Controls popper visibility. When implementing this prop the component becomes a controlled component
|
|
240
|
+
*/
|
|
241
|
+
open: PropTypes.bool,
|
|
242
|
+
/**
|
|
243
|
+
* Applies 'primary' button appearance, implying the most important action.
|
|
244
|
+
*/
|
|
245
|
+
primary: PropTypes.bool,
|
|
246
|
+
/**
|
|
247
|
+
* Applies 'secondary' button appearance.
|
|
248
|
+
*/
|
|
249
|
+
secondary: PropTypes.bool,
|
|
250
|
+
/** Changes button size. Mutually exclusive with `large` prop */
|
|
251
|
+
small: sharedPropTypes.sizePropType,
|
|
252
|
+
tabIndex: PropTypes.string,
|
|
253
|
+
/** Type of button. Applied to html `button` element */
|
|
254
|
+
type: PropTypes.oneOf(['submit', 'reset', 'button']),
|
|
255
|
+
/** Value associated with the button. Passed in object to onClick handler */
|
|
256
|
+
value: PropTypes.string,
|
|
257
|
+
/**
|
|
258
|
+
* Callback triggered when the main button is clicked.
|
|
259
|
+
* Called with signature `({ name: string, value: string, open: bool }, event)`
|
|
260
|
+
*/
|
|
261
|
+
onClick: PropTypes.func,
|
|
262
|
+
/**
|
|
263
|
+
* Callback triggered when the dropdown is toggled (by clicking the chevron, pressing Escape, or clicking the backdrop).
|
|
264
|
+
* Called with signature `({ name: string, value: string, open: bool }, event)`.
|
|
265
|
+
* Required if `open` prop is used (controlled component).
|
|
266
|
+
*/
|
|
267
|
+
onToggle: requiredIf(
|
|
268
|
+
(props) => typeof props.open === 'boolean',
|
|
269
|
+
PropTypes.func
|
|
270
|
+
),
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
export { SplitButton }
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { sharedPropTypes } from '@dhis2/ui-constants'
|
|
2
|
+
import { FlyoutMenu, MenuItem } from '@dhis2-ui/menu'
|
|
3
|
+
import { Modal, ModalContent, ModalTitle, ModalActions } from '@dhis2-ui/modal'
|
|
4
|
+
import React from 'react'
|
|
5
|
+
import { Button } from '../button/button.js'
|
|
6
|
+
import { ButtonStrip } from '../button-strip/button-strip.js'
|
|
7
|
+
import { SplitButton } from './split-button.js'
|
|
8
|
+
|
|
9
|
+
const description = `
|
|
10
|
+
Similar to the dropdown button, but can be triggered independently of opening the contained action list. The main action may be 'Save' and the contained actions may be "Save and add another" and "Save and open".
|
|
11
|
+
|
|
12
|
+
\`\`\`js
|
|
13
|
+
import { SplitButton } from '@dhis2/ui'
|
|
14
|
+
\`\`\`
|
|
15
|
+
`
|
|
16
|
+
|
|
17
|
+
window.onClick = (payload, event) => {
|
|
18
|
+
console.log('onClick payload', payload)
|
|
19
|
+
console.log('onClick event', event)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const onClick = (...args) => window.onClick(...args)
|
|
23
|
+
|
|
24
|
+
const DropdownComponent = <span>Dropdown component</span>
|
|
25
|
+
|
|
26
|
+
export default {
|
|
27
|
+
title: 'Split Button',
|
|
28
|
+
component: SplitButton,
|
|
29
|
+
parameters: { docs: { description: { component: description } } },
|
|
30
|
+
args: {
|
|
31
|
+
name: 'buttonName',
|
|
32
|
+
value: 'buttonValue',
|
|
33
|
+
component: DropdownComponent,
|
|
34
|
+
onClick: onClick,
|
|
35
|
+
children: 'Label me!',
|
|
36
|
+
},
|
|
37
|
+
argTypes: {
|
|
38
|
+
small: { ...sharedPropTypes.sizeArgType },
|
|
39
|
+
large: { ...sharedPropTypes.sizeArgType },
|
|
40
|
+
primary: { ...sharedPropTypes.buttonVariantArgType },
|
|
41
|
+
secondary: { ...sharedPropTypes.buttonVariantArgType },
|
|
42
|
+
destructive: { ...sharedPropTypes.buttonVariantArgType },
|
|
43
|
+
},
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const Template = (args) => <SplitButton {...args} />
|
|
47
|
+
|
|
48
|
+
export const Default = Template.bind({})
|
|
49
|
+
|
|
50
|
+
export const Primary = Template.bind({})
|
|
51
|
+
Primary.args = { primary: true }
|
|
52
|
+
Primary.parameters = {
|
|
53
|
+
docs: {
|
|
54
|
+
description: {
|
|
55
|
+
story: `_**Note**: The dropdown components in the following examples do not appear in the right place on this page. View the following examples in the 'Canvas' tab for the correct placement._`,
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export const Secondary = Template.bind({})
|
|
61
|
+
Secondary.args = { secondary: true }
|
|
62
|
+
|
|
63
|
+
export const Destructive = Template.bind({})
|
|
64
|
+
Destructive.props = { destructive: true }
|
|
65
|
+
|
|
66
|
+
export const Disabled = Template.bind({})
|
|
67
|
+
Disabled.args = { disabled: true }
|
|
68
|
+
|
|
69
|
+
export const Small = Template.bind({})
|
|
70
|
+
Small.args = { small: true }
|
|
71
|
+
|
|
72
|
+
export const Large = Template.bind({})
|
|
73
|
+
Large.args = { large: true }
|
|
74
|
+
|
|
75
|
+
export const InitialFocus = Template.bind({})
|
|
76
|
+
InitialFocus.args = { initialFocus: true }
|
|
77
|
+
// Disable this on docs page because grabbing focus repeatedly is annoying
|
|
78
|
+
InitialFocus.parameters = { docs: { disable: true } }
|
|
79
|
+
|
|
80
|
+
export const WithIcon = Template.bind({})
|
|
81
|
+
WithIcon.args = {
|
|
82
|
+
children: 'Children',
|
|
83
|
+
component: <div>Component</div>,
|
|
84
|
+
icon: <div>Icon</div>,
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export const RTL = (args) => (
|
|
88
|
+
<div dir="rtl">
|
|
89
|
+
<SplitButton {...args} />
|
|
90
|
+
</div>
|
|
91
|
+
)
|
|
92
|
+
RTL.args = {
|
|
93
|
+
children: 'RTL text',
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export const ControlledOpen = (args) => {
|
|
97
|
+
const [openFlyout, setOpenFlyout] = React.useState(false)
|
|
98
|
+
const [openModal, setOpenModal] = React.useState(false)
|
|
99
|
+
|
|
100
|
+
const handleClick = () => {
|
|
101
|
+
console.log('Main button clicked. This will not open the flyout menu.')
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const handleToggle = () => {
|
|
105
|
+
setOpenFlyout((prevState) => !prevState)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const component = (
|
|
109
|
+
<FlyoutMenu>
|
|
110
|
+
<MenuItem
|
|
111
|
+
onClick={() => {
|
|
112
|
+
console.log('Console log and close flyout')
|
|
113
|
+
setOpenFlyout(false)
|
|
114
|
+
}}
|
|
115
|
+
label="Action 1"
|
|
116
|
+
/>
|
|
117
|
+
<MenuItem
|
|
118
|
+
onClick={() => {
|
|
119
|
+
console.log('Open modal and close flyout')
|
|
120
|
+
setOpenFlyout(false)
|
|
121
|
+
setOpenModal(true)
|
|
122
|
+
}}
|
|
123
|
+
label="Open modal"
|
|
124
|
+
/>
|
|
125
|
+
</FlyoutMenu>
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
return (
|
|
129
|
+
<div>
|
|
130
|
+
<SplitButton
|
|
131
|
+
{...args}
|
|
132
|
+
open={openFlyout}
|
|
133
|
+
onClick={handleClick}
|
|
134
|
+
onToggle={handleToggle}
|
|
135
|
+
component={component}
|
|
136
|
+
/>
|
|
137
|
+
|
|
138
|
+
{openModal && (
|
|
139
|
+
<Modal onClose={() => setOpenModal(false)}>
|
|
140
|
+
<ModalTitle>Modal</ModalTitle>
|
|
141
|
+
<ModalContent>
|
|
142
|
+
<p>Modal content</p>
|
|
143
|
+
</ModalContent>
|
|
144
|
+
<ModalActions>
|
|
145
|
+
<ButtonStrip>
|
|
146
|
+
<Button onClick={() => setOpenModal(false)}>
|
|
147
|
+
Close
|
|
148
|
+
</Button>
|
|
149
|
+
</ButtonStrip>
|
|
150
|
+
</ModalActions>
|
|
151
|
+
</Modal>
|
|
152
|
+
)}
|
|
153
|
+
</div>
|
|
154
|
+
)
|
|
155
|
+
}
|
|
156
|
+
ControlledOpen.parameters = {
|
|
157
|
+
docs: {
|
|
158
|
+
description: {
|
|
159
|
+
story: 'This story demonstrates how the `SplitButton` can be controlled from the outside using the `open` prop and an `onToggle` handler that updates the external state.',
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
}
|