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(react): support react 17 #1680

Open
wants to merge 30 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
ad9e4b0
feat(react): update to oficially support react 17
scurker Jul 3, 2024
d2a7ad1
add prop-types dependency
scurker Jul 3, 2024
f205cd4
fix combobox test
scurker Jul 3, 2024
9eed663
update clickoutside listener
scurker Jul 17, 2024
f2afda7
test: switch to user-event for clicks in OptionsMenu test
anastasialanz Sep 17, 2024
84323a0
test: typos in popover tests
anastasialanz Sep 17, 2024
b8e7c0c
test: failing test for popover bug
anastasialanz Sep 19, 2024
89ab364
fix: revert click outside changes
anastasialanz Sep 24, 2024
9b75a9c
fix: set capture on event listeners
anastasialanz Sep 30, 2024
9f59231
fix: return if clicking on the target element
anastasialanz Sep 30, 2024
d147b85
test: update test for popover trigger
anastasialanz Oct 1, 2024
aaa242c
refactor: convert options menu item to a function component
anastasialanz Oct 1, 2024
1ea1059
Merge branch 'develop' into fix--react-17-upgrade
anastasialanz Oct 1, 2024
cda2956
refactor: convert options menu list to a function component
anastasialanz Oct 2, 2024
098a995
refactor: convert options menu to a function component
anastasialanz Oct 2, 2024
65d6ea2
test: add required onSelect prop to OptionsMenu test
anastasialanz Oct 2, 2024
7158934
fix: linting errors for options menu item
anastasialanz Oct 2, 2024
3ffc129
test: add onSelect prop to OptionsMenuList
anastasialanz Oct 2, 2024
d204969
Merge branch 'develop' into fix--react-17-upgrade
anastasialanz Oct 2, 2024
fd74de7
test: add onSelect to options menu in top bar
anastasialanz Oct 2, 2024
952d674
test: fix options menu item tests
anastasialanz Oct 3, 2024
bd25a61
fix: close the options menu if clicking on trigger
anastasialanz Oct 3, 2024
ede47b8
test: fix typescript triggerRef error
anastasialanz Oct 3, 2024
ab96270
docs: fix top bar options menu implementation
anastasialanz Oct 8, 2024
3380ed1
Merge branch 'develop' into fix--react-17-upgrade
anastasialanz Oct 8, 2024
4e1abf8
Merge branch 'develop' into fix--react-17-upgrade
anastasialanz Oct 8, 2024
f438804
Merge branch 'develop' into fix--react-17-upgrade
anastasialanz Oct 8, 2024
250db1f
fix: remove unneeded change
anastasialanz Oct 8, 2024
9df13e6
Merge branch 'develop' into fix--react-17-upgrade
anastasialanz Oct 8, 2024
4e58005
Merge branch 'develop' into fix--react-17-upgrade
anastasialanz Oct 10, 2024
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
4 changes: 3 additions & 1 deletion docs/pages/components/Popover.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,9 @@ For custom purposes, you can use the "custom" variant, which is the default.
```jsx example
function CustomPopoverExample() {
const [show, setShow] = useState(false);
const onTogglePopover = () => setShow(!show);
const onTogglePopover = () => {
setShow(!show);
}
const buttonRef = useRef();

return (
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@
"prettier": "^2",
"puppeteer": "^22.12.1",
"react": "^17",
"react-dom": "^16.13.1",
"react-dom": "^17",
"react-helmet": "^5.2.1",
"react-router-dom": "^5.3.0",
"remark-frontmatter": "^5.0.0",
Expand Down
7 changes: 4 additions & 3 deletions packages/react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
"dependencies": {
"@popperjs/core": "^2.5.4",
"classnames": "^2.2.6",
"focus-trap-react": "8",
"focus-trap-react": "^10.2.3",
"focusable": "^2.3.0",
"keyname": "^0.1.0",
"react-id-generator": "^3.0.1",
Expand Down Expand Up @@ -65,8 +65,9 @@
"postcss-cli": "^7.1.1",
"postcss-import": "^12.0.1",
"postcss-loader": "^3.0.0",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"prop-types": "^15.8.1",
"react": "^17",
"react-dom": "^17",
"rollup": "^2.23.0",
"sinon": "^10.0.0",
"ts-node": "^10.9.2",
Expand Down
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Copy link
Member

Choose a reason for hiding this comment

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

We'll want to be sure we call this out in the release, might be good to tag folks in the slack channel. I don't think anyone should be relying on the capture vs bubble phase but you never know...

Original file line number Diff line number Diff line change
Expand Up @@ -60,15 +60,15 @@ function ClickOutsideListener(

useEffect(() => {
typeof mouseEvent === 'string' &&
document.addEventListener(mouseEvent, handleEvent);
document.addEventListener(mouseEvent, handleEvent, true);
typeof touchEvent === 'string' &&
document.addEventListener(touchEvent, handleEvent);
document.addEventListener(touchEvent, handleEvent, true);

return () => {
typeof mouseEvent === 'string' &&
document.removeEventListener(mouseEvent, handleEvent);
document.removeEventListener(mouseEvent, handleEvent, true);
typeof touchEvent === 'string' &&
document.removeEventListener(touchEvent, handleEvent);
document.removeEventListener(touchEvent, handleEvent, true);
};
}, [mouseEvent, touchEvent]);

Expand Down
6 changes: 6 additions & 0 deletions packages/react/src/components/Combobox/Combobox.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -515,6 +515,9 @@ test('should prevent default with enter keypress and open listbox', () => {
// rtl doesn't let us mock preventDefault
// see: https://github.com/testing-library/react-testing-library/issues/572
event.preventDefault = preventDefault;
if (type === 'focus') {
fireEvent.focusIn(combobox);
}
fireEvent(combobox, event);
};

Expand Down Expand Up @@ -545,6 +548,9 @@ test('should not prevent default with enter keypress and closed listbox', () =>
// rtl doesn't let us mock preventDefault
// see: https://github.com/testing-library/react-testing-library/issues/572
event.preventDefault = preventDefault;
if (type === 'focus') {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

From Jason:

The changes here I suspect are related to testing-library/user-event#592, but we're not using user-event but are calling fireEvent due to specific limitations with testing library.

fireEvent.focusIn(combobox);
}
fireEvent(combobox, event);
};

Expand Down
43 changes: 28 additions & 15 deletions packages/react/src/components/OptionsMenu/OptionsMenu.test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
import { userEvent } from '@testing-library/user-event';
import OptionsMenu from './';
import axe from '../../axe';

Expand Down Expand Up @@ -90,19 +91,23 @@ test('should support menuRef prop', () => {
expect(menuRef.current).toEqual(screen.getByRole('menu'));
});

test('should toggle menu on trigger clicks', () => {
test('should toggle menu on trigger clicks', async () => {
const user = userEvent.setup();
render(
<OptionsMenu trigger={trigger}>
<li className="foo">option 1</li>
</OptionsMenu>
);

fireEvent.click(screen.getByRole('button'));
expect(screen.getByRole('button')).toHaveAttribute('aria-expanded', 'true');
const button = screen.getByRole('button');

await user.click(button);
expect(button).toHaveAttribute('aria-expanded', 'true');
expect(screen.getByRole('menu')).toHaveAttribute('aria-expanded', 'true');

fireEvent.click(screen.getByRole('button'));
expect(screen.getByRole('button')).toHaveAttribute('aria-expanded', 'false');
await user.click(button);

expect(button).toHaveAttribute('aria-expanded', 'false');
expect(screen.getByRole('menu')).toHaveAttribute('aria-expanded', 'false');
});

Expand All @@ -113,11 +118,15 @@ test('should click trigger with down key on trigger', () => {
</OptionsMenu>
);

fireEvent.keyDown(screen.getByRole('button'), { keyCode: 40 });
expect(screen.getByRole('button')).toHaveAttribute('aria-expanded', 'true');
const button = screen.getByRole('button');

fireEvent.keyDown(button, { keyCode: 40 });
expect(button).toHaveAttribute('aria-expanded', 'true');
});

test('should focus trigger on close', () => {
test('should focus trigger on close', async () => {
const user = userEvent.setup();

render(
<OptionsMenu trigger={trigger}>
<li className="foo">option 1</li>
Expand All @@ -126,12 +135,13 @@ test('should focus trigger on close', () => {

const button = screen.getByRole('button');

fireEvent.click(button);
fireEvent.click(button); // to close
await user.click(button); // opens menu
await user.click(button); // closes menu
expect(button).toHaveFocus();
});

test('should call onClose when closed', () => {
test('should call onClose when closed', async () => {
const user = userEvent.setup();
const onClose = jest.fn();

render(
Expand All @@ -142,12 +152,14 @@ test('should call onClose when closed', () => {

const option = screen.getByRole('menuitem');

fireEvent.click(option);
await user.click(option);

expect(onClose).toBeCalled();
});

test('should close menu when click outside event occurs', () => {
test('should close menu when click outside event occurs', async () => {
const user = userEvent.setup();

render(
<>
<button>Click me!</button>
Expand All @@ -156,9 +168,10 @@ test('should close menu when click outside event occurs', () => {
</OptionsMenu>
</>
);
fireEvent.click(screen.getByRole('button', { name: 'trigger' }));

await user.click(screen.getByRole('button', { name: 'trigger' }));
expect(screen.getByRole('menu')).toHaveAttribute('aria-expanded', 'true');
fireEvent.click(screen.getByRole('button', { name: 'Click me!' }));
await user.click(screen.getByRole('button', { name: 'Click me!' }));
expect(screen.getByRole('menu')).toHaveAttribute('aria-expanded', 'false');
});

Expand Down
118 changes: 46 additions & 72 deletions packages/react/src/components/OptionsMenu/OptionsMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { Component } from 'react';
import React, { useState, useRef, useCallback } from 'react';
import OptionsMenuWrapper from './OptionsMenuWrapper';
import OptionsMenuList from './OptionsMenuList';
import setRef from '../../utils/setRef';
Expand Down Expand Up @@ -28,91 +28,65 @@ export interface OptionsMenuProps extends OptionsMenuAlignmentProps {
children: React.ReactNode;
}

interface OptionsMenuState {
show: boolean;
}

type AllOptionsMenuProps = OptionsMenuProps &
React.HTMLAttributes<HTMLLIElement>;

export default class OptionsMenu extends Component<
AllOptionsMenuProps,
OptionsMenuState
> {
static defaultProps = {
// eslint-disable-next-line @typescript-eslint/no-empty-function
onClose: () => {},
// eslint-disable-next-line @typescript-eslint/no-empty-function
onSelect: () => {},
align: 'right'
};

private triggerRef: React.RefObject<HTMLButtonElement>;
const OptionsMenu = ({
children,
className,
closeOnSelect,
menuRef,
trigger,
align = 'right',
onClose = () => {}, // eslint-disable-line @typescript-eslint/no-empty-function
onSelect = () => {}, // eslint-disable-line @typescript-eslint/no-empty-function
...other
}: AllOptionsMenuProps) => {
const [show, setShow] = useState(false);
const triggerRef = useRef<HTMLButtonElement>(null);

constructor(props: AllOptionsMenuProps) {
super(props);
this.state = { show: false };
this.triggerRef = React.createRef();
}

toggleMenu = (event: React.MouseEvent<HTMLButtonElement>) => {
this.setState(({ show }) => ({ show: !show }));
const toggleMenu = () => {
setShow(!show);
};

handleClose = () => {
const { onClose = OptionsMenu.defaultProps.onClose } = this.props;
this.setState({ show: false });
const handleClose = () => {
setShow(false);
onClose();
this.triggerRef.current?.focus();
triggerRef.current?.focus();
};

handleTriggerKeyDown = (event: React.KeyboardEvent<HTMLElement>) => {
const handleTriggerKeyDown = (event: React.KeyboardEvent<HTMLElement>) => {
const { which, target } = event;
if (which === down) {
event.preventDefault();
(target as HTMLElement).click();
}
};

render() {
const { toggleMenu, triggerRef, handleTriggerKeyDown } = this;
/* eslint-disable @typescript-eslint/no-unused-vars */
const {
children,
className,
closeOnSelect,
menuRef,
trigger,
align,
onClose,
...other
} = this.props;

/* eslint-enable @typescript-eslint/no-unused-vars */
const { show } = this.state;
return (
<OptionsMenuWrapper align={align} className={className}>
{trigger &&
trigger({
onClick: toggleMenu,
'aria-expanded': show,
ref: triggerRef,
onKeyDown: handleTriggerKeyDown
})}
<OptionsMenuList
show={show}
menuRef={(el) => {
if (menuRef) {
setRef(menuRef, el);
}
}}
onClose={handleClose}
onSelect={onSelect}
{...other}
>
{children}
</OptionsMenuList>
</OptionsMenuWrapper>
);
};

return (
<OptionsMenuWrapper align={align} className={className}>
{trigger &&
trigger({
onClick: toggleMenu,
'aria-expanded': show,
ref: triggerRef,
onKeyDown: handleTriggerKeyDown
})}
<OptionsMenuList
show={show}
menuRef={(el) => {
if (menuRef) {
setRef(menuRef, el);
}
}}
onClose={this.handleClose}
{...other}
>
{children}
</OptionsMenuList>
</OptionsMenuWrapper>
);
}
}
export default OptionsMenu;
Loading
Loading