-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add experimental combo-button and menu-button components (#13224)
* feat: add experimental combo-button component * docs(combo-button): add disabled and danger item to demo * docs: add menuitem subcomponents where applicable * docs(combo-button): fix subcomponents placing * refactor(combo-button): outsource common logic to useAttachedMenu hook * feat: add experimental menu-button component * feat(combo-button, menu-button): add props.className support * style(combo-button): remove props.kind as only primary is supported * feat(combo-button, menu-button): add to ts index * style(menu-button): remove support for secondary button kind * style(menu-button): add min-width and align trigger with menu * fix(menu): avoid column gap from empty icon grid cell * style(combo-button): ensure container min-width of 10rem * feat(combo-button): add i18n support * feat(combo-button): add support for prop-controllable tooltip alignment * feat(menu-button): add support for ref and additional props * test(menu-button): add tests * feat(combo-button): add support for ref and additional props * test(combo-button): add tests * test(menu-button): align tests * test: update react exports snapshot * test(menu-button): add test to verify disabled prop works * test(combo-button): extend tests * test(menu-button): extend tests * docs(combo-buton): add default story * docs(menu-button): add default story * style(combo-button, menu-button): make lg default size * fix(menu): remove inline style * fix(combo-button): remove inline style * fix(menu-button): remove inline style * test: fix typos * docs(menu-button): sync playground and default story * docs(combo-button): sync playground and default story * feat(menu): add support fort props.onOpen * fix(combo-button, menu-button): simplify menu width styling * fix(combo-button): fix menu width on firefox * fix(menu): set position style immediately when calculated * docs(menu-button): add more stories * docs(combo-button): add "with-danger" story * docs(menu-button): hide children, className in story * docs(combo-button): hide children, className, translateWithId in story * docs(combo-button): only hide certain props in playground story * docs(menu-button): only hide certain props in playground story --------- Co-authored-by: Francine Lucca <[email protected]> Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
- Loading branch information
1 parent
d87508d
commit 9ffb292
Showing
27 changed files
with
1,271 additions
and
58 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
/** | ||
* Copyright IBM Corp. 2023 | ||
* | ||
* This source code is licensed under the Apache-2.0 license found in the | ||
* LICENSE file in the root directory of this source tree. | ||
*/ | ||
|
||
'use strict'; | ||
|
||
const { expect, test } = require('@playwright/test'); | ||
const { themes } = require('../../test-utils/env'); | ||
const { snapshotStory, visitStory } = require('../../test-utils/storybook'); | ||
|
||
test.describe('ComboButton', () => { | ||
themes.forEach((theme) => { | ||
test.describe(theme, () => { | ||
test('combo-button @vrt', async ({ page }) => { | ||
await snapshotStory(page, { | ||
component: 'ComboButton', | ||
id: 'experimental-unstable-combobutton--default', | ||
theme, | ||
}); | ||
}); | ||
}); | ||
}); | ||
|
||
test('accessibility-checker @avt', async ({ page }) => { | ||
await visitStory(page, { | ||
component: 'ComboButton', | ||
id: 'experimental-unstable-combobutton--default', | ||
globals: { | ||
theme: 'white', | ||
}, | ||
}); | ||
await expect(page).toHaveNoACViolations('ComboButton'); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
/** | ||
* Copyright IBM Corp. 2023 | ||
* | ||
* This source code is licensed under the Apache-2.0 license found in the | ||
* LICENSE file in the root directory of this source tree. | ||
*/ | ||
|
||
'use strict'; | ||
|
||
const { expect, test } = require('@playwright/test'); | ||
const { themes } = require('../../test-utils/env'); | ||
const { snapshotStory, visitStory } = require('../../test-utils/storybook'); | ||
|
||
test.describe('MenuButton', () => { | ||
themes.forEach((theme) => { | ||
test.describe(theme, () => { | ||
test('menu-button @vrt', async ({ page }) => { | ||
await snapshotStory(page, { | ||
component: 'MenuButton', | ||
id: 'experimental-unstable-menubutton--default', | ||
theme, | ||
}); | ||
}); | ||
}); | ||
}); | ||
|
||
test('accessibility-checker @avt', async ({ page }) => { | ||
await visitStory(page, { | ||
component: 'MenuButton', | ||
id: 'experimental-unstable-menubutton--default', | ||
globals: { | ||
theme: 'white', | ||
}, | ||
}); | ||
await expect(page).toHaveNoACViolations('MenuButton'); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
162 changes: 162 additions & 0 deletions
162
packages/react/src/components/ComboButton/ComboButton-test.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,162 @@ | ||
/** | ||
* Copyright IBM Corp. 2016, 2023 | ||
* | ||
* This source code is licensed under the Apache-2.0 license found in the | ||
* LICENSE file in the root directory of this source tree. | ||
*/ | ||
|
||
import { render, screen } from '@testing-library/react'; | ||
import userEvent from '@testing-library/user-event'; | ||
import React from 'react'; | ||
|
||
import { MenuItem } from '../Menu'; | ||
|
||
import { ComboButton } from './'; | ||
|
||
const prefix = 'cds'; | ||
|
||
describe('ComboButton', () => { | ||
describe('renders as expected - Component API', () => { | ||
it('supports a ref on the outermost element', () => { | ||
const ref = jest.fn(); | ||
const { container } = render( | ||
<ComboButton label="Primary action" ref={ref}> | ||
<MenuItem label="Additional action" /> | ||
</ComboButton> | ||
); | ||
expect(ref).toHaveBeenCalledWith(container.firstChild); | ||
}); | ||
|
||
it('supports a custom class name on the outermost element', () => { | ||
const { container } = render( | ||
<ComboButton label="Primary action" className="test"> | ||
<MenuItem label="Additional action" /> | ||
</ComboButton> | ||
); | ||
expect(container.firstChild).toHaveClass('test'); | ||
}); | ||
|
||
it('forwards additional props on the outermost element', () => { | ||
const { container } = render( | ||
<ComboButton label="Primary action" data-testid="test"> | ||
<MenuItem label="Additional action" /> | ||
</ComboButton> | ||
); | ||
expect(container.firstChild).toHaveAttribute('data-testid', 'test'); | ||
}); | ||
|
||
it('renders props.label on the trigger button', () => { | ||
render( | ||
<ComboButton label="Test"> | ||
<MenuItem label="Additional action" /> | ||
</ComboButton> | ||
); | ||
expect(screen.getAllByRole('button')[0]).toHaveTextContent(/^Test$/); | ||
}); | ||
|
||
it('supports props.disabled', () => { | ||
render( | ||
<ComboButton label="Primary action" disabled> | ||
<MenuItem label="Additional action" /> | ||
</ComboButton> | ||
); | ||
|
||
// primary action button | ||
expect(screen.getAllByRole('button')[0]).toBeDisabled(); | ||
|
||
// trigger button | ||
expect(screen.getAllByRole('button')[1]).toBeDisabled(); | ||
}); | ||
|
||
describe('supports props.size', () => { | ||
const sizes = ['sm', 'md', 'lg']; | ||
|
||
sizes.forEach((size) => { | ||
it(`size="${size}"`, () => { | ||
const { container } = render( | ||
<ComboButton label="Primary action" size={size}> | ||
<MenuItem label="Additional action" /> | ||
</ComboButton> | ||
); | ||
|
||
expect(container.firstChild).toHaveClass( | ||
`${prefix}--combo-button__container--${size}` | ||
); | ||
}); | ||
}); | ||
}); | ||
|
||
describe('supports props.tooltipAlign', () => { | ||
const alignments = [ | ||
'top', | ||
'top-left', | ||
'top-right', | ||
'bottom', | ||
'bottom-left', | ||
'bottom-right', | ||
'left', | ||
'right', | ||
]; | ||
|
||
alignments.forEach((alignment) => { | ||
it(`tooltipAlign="${alignment}"`, () => { | ||
const { container } = render( | ||
<ComboButton label="Primary action" tooltipAlign={alignment}> | ||
<MenuItem label="Additional action" /> | ||
</ComboButton> | ||
); | ||
|
||
expect(container.firstChild.lastChild).toHaveClass( | ||
`${prefix}--popover--${alignment}` | ||
); | ||
}); | ||
}); | ||
}); | ||
|
||
it('supports props.translateWithId', () => { | ||
const t = () => 'test'; | ||
|
||
render( | ||
<ComboButton label="Primary action" translateWithId={t}> | ||
<MenuItem label="Additional action" /> | ||
</ComboButton> | ||
); | ||
|
||
const triggerButton = screen.getAllByRole('button')[1]; | ||
const tooltipId = triggerButton.getAttribute('aria-labelledby'); | ||
const tooltip = document.getElementById(tooltipId); | ||
|
||
expect(tooltip).toHaveTextContent(t()); | ||
}); | ||
}); | ||
|
||
describe('behaves as expected', () => { | ||
it('emits props.onClick on primary action click', async () => { | ||
const onClick = jest.fn(); | ||
render( | ||
<ComboButton label="Test" onClick={onClick}> | ||
<MenuItem label="Additional action" /> | ||
</ComboButton> | ||
); | ||
|
||
expect(onClick).toHaveBeenCalledTimes(0); | ||
await userEvent.click(screen.getAllByRole('button')[0]); | ||
expect(onClick).toHaveBeenCalledTimes(1); | ||
}); | ||
|
||
it('opens a menu on click on the trigger button', async () => { | ||
render( | ||
<ComboButton label="Primary action"> | ||
<MenuItem label="Additional action" /> | ||
</ComboButton> | ||
); | ||
|
||
await userEvent.click(screen.getAllByRole('button')[1]); | ||
|
||
expect(screen.getByRole('menu')).toBeTruthy(); | ||
expect(screen.getByRole('menuitem')).toHaveTextContent( | ||
/^Additional action$/ | ||
); | ||
}); | ||
}); | ||
}); |
Oops, something went wrong.