@dhis2-ui/button 10.16.2 → 10.16.3

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.
Files changed (60) hide show
  1. package/package.json +8 -7
  2. package/src/button/__tests__/Button.test.js +126 -0
  3. package/src/button/button.e2e.stories.js +24 -0
  4. package/src/button/button.js +162 -0
  5. package/src/button/button.prod.stories.js +305 -0
  6. package/src/button/button.styles.js +298 -0
  7. package/src/button/features/can_be_blurred/index.js +19 -0
  8. package/src/button/features/can_be_blurred.feature +6 -0
  9. package/src/button/features/can_be_clicked/index.js +18 -0
  10. package/src/button/features/can_be_clicked.feature +6 -0
  11. package/src/button/features/can_be_focused/index.js +18 -0
  12. package/src/button/features/can_be_focused.feature +6 -0
  13. package/src/button/index.js +1 -0
  14. package/src/button-strip/button-strip.e2e.stories.js +13 -0
  15. package/src/button-strip/button-strip.js +58 -0
  16. package/src/button-strip/button-strip.prod.stories.js +88 -0
  17. package/src/button-strip/features/accepts_children/index.js +10 -0
  18. package/src/button-strip/features/accepts_children.feature +5 -0
  19. package/src/button-strip/index.js +1 -0
  20. package/src/dropdown-button/__tests__/dropdown-button.test.js +135 -0
  21. package/src/dropdown-button/dropdown-button.e2e.stories.js +67 -0
  22. package/src/dropdown-button/dropdown-button.js +239 -0
  23. package/src/dropdown-button/dropdown-button.prod.stories.js +129 -0
  24. package/src/dropdown-button/features/accepts_children/index.js +10 -0
  25. package/src/dropdown-button/features/accepts_children.feature +5 -0
  26. package/src/dropdown-button/features/accepts_component/index.js +18 -0
  27. package/src/dropdown-button/features/accepts_component.feature +5 -0
  28. package/src/dropdown-button/features/accepts_icon/index.js +10 -0
  29. package/src/dropdown-button/features/accepts_icon.feature +5 -0
  30. package/src/dropdown-button/features/accepts_initial_focus/index.js +11 -0
  31. package/src/dropdown-button/features/accepts_initial_focus.feature +5 -0
  32. package/src/dropdown-button/features/button_is_clickable/index.js +15 -0
  33. package/src/dropdown-button/features/button_is_clickable.feature +6 -0
  34. package/src/dropdown-button/features/can_be_disabled/index.js +11 -0
  35. package/src/dropdown-button/features/can_be_disabled.feature +6 -0
  36. package/src/dropdown-button/features/common/index.js +5 -0
  37. package/src/dropdown-button/features/opens_a_dropdown/index.js +26 -0
  38. package/src/dropdown-button/features/opens_a_dropdown.feature +11 -0
  39. package/src/dropdown-button/index.js +1 -0
  40. package/src/index.js +4 -0
  41. package/src/locales/en/translations.json +3 -0
  42. package/src/locales/index.js +16 -0
  43. package/src/split-button/features/accepts_children/index.js +12 -0
  44. package/src/split-button/features/accepts_children.feature +5 -0
  45. package/src/split-button/features/accepts_icon/index.js +16 -0
  46. package/src/split-button/features/accepts_icon.feature +5 -0
  47. package/src/split-button/features/accepts_initial_focus/index.js +13 -0
  48. package/src/split-button/features/accepts_initial_focus.feature +5 -0
  49. package/src/split-button/features/arrow_opens_menu/index.js +34 -0
  50. package/src/split-button/features/arrow_opens_menu.feature +15 -0
  51. package/src/split-button/features/button_is_clickable/index.js +13 -0
  52. package/src/split-button/features/button_is_clickable.feature +6 -0
  53. package/src/split-button/features/can_be_disabled/index.js +25 -0
  54. package/src/split-button/features/can_be_disabled.feature +11 -0
  55. package/src/split-button/features/common/index.js +9 -0
  56. package/src/split-button/index.js +1 -0
  57. package/src/split-button/split-button.e2e.stories.js +56 -0
  58. package/src/split-button/split-button.js +273 -0
  59. package/src/split-button/split-button.prod.stories.js +162 -0
  60. package/src/split-button/split-button.test.js +84 -0
@@ -0,0 +1,5 @@
1
+ Feature: The SplitButton renders icon
2
+
3
+ Scenario: A SplitButton with an icon
4
+ Given a SplitButton with an icon is rendered
5
+ Then the icon is visible on the left button only
@@ -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,5 @@
1
+ Feature: Focusing the SplitButton on mount
2
+
3
+ Scenario: The SplitButton renders with focus
4
+ Given a SplitButton with initialFocus is rendered
5
+ Then the SplitButton button is focused
@@ -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,6 @@
1
+ Feature: The SplitButton has an onClick api
2
+
3
+ Scenario: The user clicks on the SplitButton
4
+ Given a SplitButton with onClick hander is rendered
5
+ When the SplitButton is clicked
6
+ Then the onClick handler is called
@@ -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
+ }