diff --git a/e2e/components/Popover/Popover-test.e2e.js b/e2e/components/Popover/Popover-test.e2e.js
new file mode 100644
index 000000000000..fcedb5ed4a8a
--- /dev/null
+++ b/e2e/components/Popover/Popover-test.e2e.js
@@ -0,0 +1,45 @@
+/**
+ * Copyright IBM Corp. 2022
+ *
+ * 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('Popover', () => {
+ themes.forEach((theme) => {
+ test.describe(theme, () => {
+ test('Popover - auto align @vrt', async ({ page }) => {
+ await snapshotStory(page, {
+ component: 'Popover',
+ id: 'components-popover--auto-align',
+ theme,
+ });
+ });
+
+ test('Popover - isTabTip @vrt', async ({ page }) => {
+ await snapshotStory(page, {
+ component: 'Popover',
+ id: 'components-popover--tab-tip',
+ theme,
+ });
+ });
+ });
+ });
+
+ test('accessibility-checker @avt', async ({ page }) => {
+ await visitStory(page, {
+ component: 'Popover',
+ id: 'components-popover--auto-align',
+ globals: {
+ theme: 'white',
+ },
+ });
+ await expect(page).toHaveNoACViolations('Popover');
+ });
+});
diff --git a/packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap b/packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap
index 4f969ad71b21..a72fca38f3fe 100644
--- a/packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap
+++ b/packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap
@@ -5669,6 +5669,9 @@ Map {
"highContrast": Object {
"type": "bool",
},
+ "isTabTip": Object {
+ "type": "bool",
+ },
"open": Object {
"isRequired": true,
"type": "bool",
diff --git a/packages/react/src/components/Popover/Popover.stories.js b/packages/react/src/components/Popover/Popover.stories.js
index 46ba2e9b402a..735fa35c9678 100644
--- a/packages/react/src/components/Popover/Popover.stories.js
+++ b/packages/react/src/components/Popover/Popover.stories.js
@@ -6,10 +6,17 @@
*/
import './story.scss';
-import { Checkbox } from '@carbon/icons-react';
+import { Checkbox as CheckboxIcon } from '@carbon/icons-react';
import React, { useState } from 'react';
import { Popover, PopoverContent } from '../Popover';
+import RadioButton from '../RadioButton';
+import RadioButtonGroup from '../RadioButtonGroup';
+import { default as Checkbox } from '../Checkbox';
import mdx from './Popover.mdx';
+import { Settings } from '@carbon/icons-react';
+import { keys, match } from '../../internal/keyboard';
+
+const prefix = 'cds';
export default {
title: 'Components/Popover',
@@ -59,7 +66,7 @@ const PlaygroundStory = (props) => {
highContrast={highContrast}
open={open}>
-
+
Available storage
@@ -71,6 +78,85 @@ const PlaygroundStory = (props) => {
);
};
+export const TabTip = () => {
+ const [open, setOpen] = useState(true);
+ const [openTwo, setOpenTwo] = useState(false);
+ return (
+
+
{
+ if (match(evt, keys.Escape)) {
+ setOpen(false);
+ }
+ }}
+ isTabTip>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
export const Playground = PlaygroundStory.bind({});
Playground.argTypes = {
@@ -141,7 +227,7 @@ export const AutoAlign = () => {
}}>
- {
setOpen(!open);
}}
@@ -157,7 +243,7 @@ export const AutoAlign = () => {
-
+
Available storage
@@ -169,7 +255,7 @@ export const AutoAlign = () => {
-
+
Available storage
@@ -183,7 +269,7 @@ export const AutoAlign = () => {
style={{ position: 'absolute', bottom: 0, right: 0, margin: '3rem' }}>
-
+
Available storage
@@ -196,7 +282,7 @@ export const AutoAlign = () => {
-
+
Available storage
diff --git a/packages/react/src/components/Popover/__tests__/Popover-test.js b/packages/react/src/components/Popover/__tests__/Popover-test.js
index e93fa35adda7..1c616d0e3323 100644
--- a/packages/react/src/components/Popover/__tests__/Popover-test.js
+++ b/packages/react/src/components/Popover/__tests__/Popover-test.js
@@ -9,6 +9,8 @@ import { render, screen } from '@testing-library/react';
import React from 'react';
import { Popover, PopoverContent } from '../../Popover';
+const prefix = 'cds';
+
describe('Popover', () => {
it('should support a ref on the outermost element', () => {
const ref = jest.fn();
@@ -75,5 +77,31 @@ describe('Popover', () => {
);
expect(container.firstChild).toHaveAttribute('id', 'test');
});
+
+ // Tab Tip tests
+ it('should respect isTabTip prop', () => {
+ const { container } = render(
+
+
+ test
+
+ );
+ expect(container.firstChild).toHaveClass(`${prefix}--popover--tab-tip`);
+ });
+
+ it('should not allow other alignments than bottom-left or bottom-right when isTabTip is present', () => {
+ const { container } = render(
+
+
+ test
+
+ );
+ expect(container.firstChild).not.toHaveClass(
+ `${prefix}--popover--top-left`
+ );
+ expect(container.firstChild).toHaveClass(
+ `${prefix}--popover--bottom-left`
+ );
+ });
});
});
diff --git a/packages/react/src/components/Popover/index.tsx b/packages/react/src/components/Popover/index.tsx
index c71acef12f24..3bccc34eead7 100644
--- a/packages/react/src/components/Popover/index.tsx
+++ b/packages/react/src/components/Popover/index.tsx
@@ -44,7 +44,7 @@ interface PopoverBaseProps {
align?: PopoverAlignment;
/**
- * Will auto-align the popover on first render if it is not visible. This prop is currently experimental and is subject to futurue changes.
+ * Will auto-align the popover on first render if it is not visible. This prop is currently experimental and is subject to future changes.
*/
autoAlign?: boolean;
@@ -74,6 +74,11 @@ interface PopoverBaseProps {
*/
highContrast?: boolean;
+ /**
+ * Render the component using the tab tip variant
+ */
+ isTabTip?: boolean;
+
/**
* Specify whether the component is currently open or closed
*/
@@ -88,10 +93,11 @@ export type PopoverProps = PolymorphicProps<
const Popover = React.forwardRef(
(
{
- align = 'bottom',
+ isTabTip,
+ align = isTabTip ? 'bottom-left' : 'bottom',
as,
autoAlign = false,
- caret = true,
+ caret = isTabTip ? false : true,
className: customClassName,
children,
dropShadow = true,
@@ -111,6 +117,17 @@ const Popover = React.forwardRef(
};
}, []);
+ if (isTabTip) {
+ const tabTipAlignments: PopoverAlignment[] = [
+ 'bottom-left',
+ 'bottom-right',
+ ];
+
+ if (!tabTipAlignments.includes(align)) {
+ align = 'bottom-left';
+ }
+ }
+
const ref = useMergedRefs([forwardRef, popover]);
const [autoAligned, setAutoAligned] = useState(false);
const [autoAlignment, setAutoAlignment] = useState(align);
@@ -121,8 +138,9 @@ const Popover = React.forwardRef(
[`${prefix}--popover--drop-shadow`]: dropShadow,
[`${prefix}--popover--high-contrast`]: highContrast,
[`${prefix}--popover--open`]: open,
- [`${prefix}--popover--${autoAlignment}`]: autoAligned,
+ [`${prefix}--popover--${autoAlignment}`]: autoAligned && !isTabTip,
[`${prefix}--popover--${align}`]: !autoAligned,
+ [`${prefix}--popover--tab-tip`]: isTabTip,
},
customClassName
);
@@ -132,7 +150,7 @@ const Popover = React.forwardRef(
return;
}
- if (!autoAlign) {
+ if (!autoAlign || isTabTip) {
setAutoAligned(false);
return;
}
@@ -251,13 +269,31 @@ const Popover = React.forwardRef(
setAutoAligned(true);
setAutoAlignment(alignment);
}
- }, [autoAligned, align, autoAlign, prefix, open]);
+ }, [autoAligned, align, autoAlign, prefix, open, isTabTip]);
const BaseComponent: React.ElementType = as ?? 'span';
+
+ const mappedChildren = React.Children.map(children, (child) => {
+ const item = child as any;
+
+ if (item?.type === 'button') {
+ const { className } = item.props;
+ const tabTipClasses = cx(
+ `${prefix}--popover--tab-tip__button`,
+ className
+ );
+ return React.cloneElement(item, {
+ className: tabTipClasses,
+ });
+ } else {
+ return item;
+ }
+ });
+
return (
- {children}
+ {isTabTip ? mappedChildren : children}
);
@@ -299,7 +335,7 @@ Popover.propTypes = {
as: PropTypes.oneOfType([PropTypes.string, PropTypes.elementType]),
/**
- * Will auto-align the popover on first render if it is not visible. This prop is currently experimental and is subject to futurue changes.
+ * Will auto-align the popover on first render if it is not visible. This prop is currently experimental and is subject to future changes.
*/
autoAlign: PropTypes.bool,
@@ -329,6 +365,11 @@ Popover.propTypes = {
*/
highContrast: PropTypes.bool,
+ /**
+ * Render the component using the tab tip variant
+ */
+ isTabTip: PropTypes.bool,
+
/**
* Specify whether the component is currently open or closed
*/
diff --git a/packages/react/src/components/Popover/story.scss b/packages/react/src/components/Popover/story.scss
index 08aea270b5f4..83432e1f0076 100644
--- a/packages/react/src/components/Popover/story.scss
+++ b/packages/react/src/components/Popover/story.scss
@@ -98,3 +98,22 @@
display: flex;
flex-direction: column;
}
+
+.popover-tabtip-story .cds--popover-content {
+ width: 16rem;
+}
+
+.popover-tabtip-story .cds--radio-button-wrapper {
+ margin-bottom: 0.5rem;
+}
+
+.popover-tabtip-story hr {
+ border: none;
+ background: theme.$border-subtle;
+ height: 1px;
+ margin: 8px 0 16px;
+}
+
+.popover-tabtip-story .cds--popover-container:last-of-type {
+ margin-left: 15rem;
+}
diff --git a/packages/react/src/components/RadioButtonGroup/RadioButtonGroup.js b/packages/react/src/components/RadioButtonGroup/RadioButtonGroup.js
index b65419125200..0c0172461ba9 100644
--- a/packages/react/src/components/RadioButtonGroup/RadioButtonGroup.js
+++ b/packages/react/src/components/RadioButtonGroup/RadioButtonGroup.js
@@ -26,6 +26,7 @@ const RadioButtonGroup = React.forwardRef(function RadioButtonGroup(
orientation = 'horizontal',
readOnly,
valueSelected,
+ ...rest
},
ref
) {
@@ -88,7 +89,8 @@ const RadioButtonGroup = React.forwardRef(function RadioButtonGroup(