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(Popover): Add onChange callback #88

Merged
merged 2 commits into from Feb 19, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
15 changes: 15 additions & 0 deletions src/components/Popover/index.stories.tsx
Expand Up @@ -20,6 +20,7 @@ import ColumnLayout, { Column } from '../../layouts/ColumnLayout';
import StatusIndicator from '../StatusIndicator';
import Button from '../Button';
import KeyValuePair from '../KeyValuePair';
import { action } from '@storybook/addon-actions';

export default {
component: Popover,
Expand Down Expand Up @@ -98,3 +99,17 @@ export const Large = () => (
</Popover>
</Stack>
);

export const OnChange = () => (
<Stack>
<Popover
position="right"
size="small"
triggerType="custom"
content={<StatusIndicator statusType="positive">Code snippet copied</StatusIndicator>}
onChange={action('state changed')}
>
<Button>Copy</Button>
</Popover>
</Stack>
);
19 changes: 19 additions & 0 deletions src/components/Popover/index.test.tsx
Expand Up @@ -19,6 +19,12 @@ import Popover, { PopoverProps } from '.';
import { BrowserRouter } from 'react-router-dom';

describe('Popover', () => {
const mockOnChange = jest.fn();

afterEach(() => {
jest.clearAllMocks();
});

it('renders text trigger correctly', () => {
const { queryByRole, getByRole } = render(
<Popover triggerType="text">
Expand Down Expand Up @@ -59,6 +65,19 @@ describe('Popover', () => {
expect(getByText('popover content')).toHaveTextContent('popover content');
});

it('calls the onChange callback when the popover is opened and closed', () => {
const { getByText, queryByTestId } = render(
<Popover header="header text" content="popover content" onChange={mockOnChange}>
<span>Trigger</span>
</Popover>
);
expect(mockOnChange).not.toHaveBeenCalled();
getByText('Trigger').click();
expect(mockOnChange).toHaveBeenNthCalledWith(1, true);
queryByTestId('dismiss-button')!.click();
expect(mockOnChange).toHaveBeenNthCalledWith(2, false);
});

describe('Dismiss button', () => {
it('renders a dismiss button by default', () => {
const { getByText, queryByTestId } = render(
Expand Down
12 changes: 10 additions & 2 deletions src/components/Popover/index.tsx
Expand Up @@ -136,6 +136,11 @@ export interface PopoverProps {
* Adds an `aria-label` to the dismiss button for accessibility.
*/
dismissAriaLabel?: string;

/**
* Optional callback called when the popover is opened or closed
*/
onChange?: (visible: boolean) => void;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we rename it to onClose and onOpen to be more explicit? MaterialUI Popover already has a onClose property that we could use to trigger the onClose

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking about it being more in line with the ExpandableSection where there's an onChange which is passed true or false, but I guess that only makes sense if the user can control the components' state too. Will see if I can also add a visible prop so you can then do something like:

const [visible, setVisible] = useState(false);

<Popover visible={visible} onChange={setVisible}> ... </Popover>

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wow that was a learning experience! Having a visible: boolean parameter doesn't work nicely on the first render - ie visible = true still doesn't show the popover since the ref is not set until after our first render... Doesn't seem to be a neat way to get that working, so I think best not to expose a visible prop :)

Have renamed to onClose and onOpen :)

}

/**
Expand All @@ -151,6 +156,7 @@ const Popover: FunctionComponent<PopoverProps> = ({
content,
dismissAriaLabel,
header,
onChange,
...restProps
}) => {
const labelledById = useUniqueId('awsui-popover-');
Expand All @@ -161,11 +167,13 @@ const Popover: FunctionComponent<PopoverProps> = ({

const onTriggerClick = useCallback(() => {
setVisible(true);
}, []);
onChange && onChange(true);
}, [onChange]);

const onPopoverClose = useCallback(() => {
setVisible(false);
}, []);
onChange && onChange(false);
}, [onChange]);

const mapPositionToMuiPopoverOrigins = useCallback(() => {
let anchorOrigin: MaterialPopoverProps['anchorOrigin'],
Expand Down