Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add experimental combo-button and menu-button components #13224

Merged
merged 47 commits into from
Mar 14, 2023
Merged
Show file tree
Hide file tree
Changes from 43 commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
f1c7893
feat: add experimental combo-button component
janhassel Feb 24, 2023
93a3553
docs(combo-button): add disabled and danger item to demo
janhassel Feb 24, 2023
c3704eb
docs: add menuitem subcomponents where applicable
janhassel Feb 24, 2023
59ad788
docs(combo-button): fix subcomponents placing
janhassel Feb 24, 2023
c8b56b6
refactor(combo-button): outsource common logic to useAttachedMenu hook
janhassel Feb 24, 2023
aa1045f
feat: add experimental menu-button component
janhassel Feb 24, 2023
1f06c2f
feat(combo-button, menu-button): add props.className support
janhassel Mar 1, 2023
5f84117
style(combo-button): remove props.kind as only primary is supported
janhassel Mar 1, 2023
7236a8a
feat(combo-button, menu-button): add to ts index
janhassel Mar 1, 2023
858e58e
style(menu-button): remove support for secondary button kind
janhassel Mar 1, 2023
a4e75fa
style(menu-button): add min-width and align trigger with menu
janhassel Mar 1, 2023
ae3bdae
fix(menu): avoid column gap from empty icon grid cell
janhassel Mar 1, 2023
215273e
style(combo-button): ensure container min-width of 10rem
janhassel Mar 1, 2023
dc60aca
feat(combo-button): add i18n support
janhassel Mar 2, 2023
3479b43
feat(combo-button): add support for prop-controllable tooltip alignment
janhassel Mar 2, 2023
f162faa
feat(menu-button): add support for ref and additional props
janhassel Mar 2, 2023
3265678
test(menu-button): add tests
janhassel Mar 2, 2023
5e963d6
feat(combo-button): add support for ref and additional props
janhassel Mar 2, 2023
9e83508
test(combo-button): add tests
janhassel Mar 2, 2023
9702941
test(menu-button): align tests
janhassel Mar 2, 2023
c820c0c
test: update react exports snapshot
janhassel Mar 2, 2023
2c45fb1
test(menu-button): add test to verify disabled prop works
janhassel Mar 2, 2023
c10a2e9
Merge branch 'main' into 3371
janhassel Mar 2, 2023
c0a3f23
test(combo-button): extend tests
janhassel Mar 3, 2023
e049aa0
test(menu-button): extend tests
janhassel Mar 3, 2023
629f798
docs(combo-buton): add default story
janhassel Mar 3, 2023
3c2df12
docs(menu-button): add default story
janhassel Mar 3, 2023
87b683d
style(combo-button, menu-button): make lg default size
janhassel Mar 3, 2023
9b19326
fix(menu): remove inline style
janhassel Mar 3, 2023
93bde1e
fix(combo-button): remove inline style
janhassel Mar 3, 2023
2495a1b
fix(menu-button): remove inline style
janhassel Mar 3, 2023
d16dc5d
test: fix typos
janhassel Mar 6, 2023
29d051f
docs(menu-button): sync playground and default story
janhassel Mar 6, 2023
f0d091d
docs(combo-button): sync playground and default story
janhassel Mar 6, 2023
5350c7a
feat(menu): add support fort props.onOpen
janhassel Mar 6, 2023
64b949a
fix(combo-button, menu-button): simplify menu width styling
janhassel Mar 6, 2023
d0d4137
fix(combo-button): fix menu width on firefox
janhassel Mar 6, 2023
fa79692
fix(menu): set position style immediately when calculated
janhassel Mar 6, 2023
fba15c0
Merge branch 'main' into 3371
janhassel Mar 9, 2023
ff5a540
docs(menu-button): add more stories
janhassel Mar 10, 2023
c2f475c
docs(combo-button): add "with-danger" story
janhassel Mar 10, 2023
2b4593a
docs(menu-button): hide children, className in story
janhassel Mar 13, 2023
c43dd2e
docs(combo-button): hide children, className, translateWithId in story
janhassel Mar 13, 2023
b323368
docs(combo-button): only hide certain props in playground story
janhassel Mar 14, 2023
0c0da20
docs(menu-button): only hide certain props in playground story
janhassel Mar 14, 2023
fd17d3f
Merge branch 'main' into 3371
francinelucca Mar 14, 2023
d0df7d3
Merge branch 'main' into 3371
kodiakhq[bot] Mar 14, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions e2e/components/ComboButton/ComboButton-test.e2e.js
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');
});
});
37 changes: 37 additions & 0 deletions e2e/components/MenuButton/MenuButton-test.e2e.js
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');
});
});
94 changes: 94 additions & 0 deletions packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -9022,6 +9022,57 @@ Map {
"8": "cool-gray",
"9": "warm-gray",
},
"unstable_ComboButton" => Object {
"$$typeof": Symbol(react.forward_ref),
"propTypes": Object {
"children": Object {
"isRequired": true,
"type": "node",
},
"className": Object {
"type": "string",
},
"disabled": Object {
"type": "bool",
},
"label": Object {
"isRequired": true,
"type": "string",
},
"onClick": Object {
"type": "func",
},
"size": Object {
"args": Array [
Array [
"sm",
"md",
"lg",
],
],
"type": "oneOf",
},
"tooltipAlign": Object {
"args": Array [
Array [
"top",
"top-left",
"top-right",
"bottom",
"bottom-left",
"bottom-right",
"left",
"right",
],
],
"type": "oneOf",
},
"translateWithId": Object {
"type": "func",
},
},
"render": [Function],
},
"unstable_FeatureFlags" => Object {
"propTypes": Object {
"children": Object {
Expand Down Expand Up @@ -9085,6 +9136,9 @@ Map {
"onClose": Object {
"type": "func",
},
"onOpen": Object {
"type": "func",
},
"open": Object {
"type": "bool",
},
Expand Down Expand Up @@ -9141,6 +9195,46 @@ Map {
},
"render": [Function],
},
"unstable_MenuButton" => Object {
"$$typeof": Symbol(react.forward_ref),
"propTypes": Object {
"children": Object {
"isRequired": true,
"type": "node",
},
"className": Object {
"type": "string",
},
"disabled": Object {
"type": "bool",
},
"kind": Object {
"args": Array [
Array [
"primary",
"tertiary",
"ghost",
],
],
"type": "oneOf",
},
"label": Object {
"isRequired": true,
"type": "string",
},
"size": Object {
"args": Array [
Array [
"sm",
"md",
"lg",
],
],
"type": "oneOf",
},
},
"render": [Function],
},
"unstable_MenuItem" => Object {
"$$typeof": Symbol(react.forward_ref),
"propTypes": Object {
Expand Down
2 changes: 2 additions & 0 deletions packages/react/src/__tests__/index-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -221,9 +221,11 @@ describe('Carbon Components React', () => {
"UnorderedList",
"VStack",
"types",
"unstable_ComboButton",
"unstable_FeatureFlags",
"unstable_LayoutDirection",
"unstable_Menu",
"unstable_MenuButton",
"unstable_MenuItem",
"unstable_MenuItemDivider",
"unstable_MenuItemGroup",
Expand Down
162 changes: 162 additions & 0 deletions packages/react/src/components/ComboButton/ComboButton-test.js
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$/
);
});
});
});
francinelucca marked this conversation as resolved.
Show resolved Hide resolved
Loading