Skip to content

Commit b135dac

Browse files
authored
Merge branch 'develop' into fix-docs-pr-template
2 parents bd43dd6 + d49db11 commit b135dac

107 files changed

Lines changed: 5473 additions & 2538 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/PULL_REQUEST_TEMPLATE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ I have verified that this pull request:
66

77
* [ ] has no linting errors (`npm run lint`)
88
* [ ] has no test errors (`npm run test`)
9+
* [ ] has no typecheck errors (`npm run typecheck`)
910
* [ ] is from a uniquely-named feature branch and is up to date with the `develop` branch.
1011
* [ ] is descriptively named and links to an issue number, i.e. `Fixes #123`
1112
* [ ] meets the standards outlined in the [accessibility guidelines](https://github.com/processing/p5.js-web-editor/blob/develop/contributor_docs/accessibility.md)

client/common/Button.stories.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React from 'react';
22
import { action } from '@storybook/addon-actions';
33

4-
import { Button, ButtonDisplays, ButtonKinds, ButtonTypes } from './Button';
4+
import { Button, ButtonDisplays, ButtonTypes } from './Button';
55
import { GithubIcon, DropdownArrowIcon, PlusIcon } from './icons';
66

77
export default {

client/common/Button.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ export interface ButtonProps extends React.HTMLAttributes<HTMLElement> {
8080
/**
8181
* If using a native button, specifies on an onClick action
8282
*/
83-
onClick?: () => void;
83+
onClick?: (evt: React.MouseEvent<HTMLButtonElement>) => void;
8484
/**
8585
* If using a button, then type is defines the type of button
8686
*/

client/common/Tooltip.test.tsx

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import React from 'react';
2+
import userEvent from '@testing-library/user-event';
3+
import { render, screen } from '../test-utils';
4+
import { Tooltip } from './Tooltip';
5+
6+
describe('Tooltip', () => {
7+
it('renders the child element', () => {
8+
render(
9+
<Tooltip content="This is a tooltip">
10+
<button>Hover me</button>
11+
</Tooltip>
12+
);
13+
expect(screen.getByRole('button')).toBeInTheDocument();
14+
expect(screen.getByText('Hover me')).toBeInTheDocument();
15+
});
16+
17+
it('does not show the tooltip when the user is not hovering over the element', () => {
18+
render(
19+
<Tooltip content="Tooltip text">
20+
<button>Button</button>
21+
</Tooltip>
22+
);
23+
24+
const button = screen.getByRole('button');
25+
expect(button).toBeInTheDocument();
26+
expect(button).not.toHaveClass('tooltipped-visible');
27+
});
28+
29+
it('shows the tooltip if the user hovers over the element', async () => {
30+
const user = userEvent.setup();
31+
render(
32+
<Tooltip content="Tooltip text">
33+
<button>Button</button>
34+
</Tooltip>
35+
);
36+
37+
const button = screen.getByRole('button');
38+
await user.hover(button);
39+
40+
expect(button).toHaveClass('tooltipped');
41+
expect(button).toHaveAttribute('aria-label', 'Tooltip text');
42+
});
43+
44+
it('adds the aria-label with tooltip content to the child element', () => {
45+
render(
46+
<Tooltip content="Save your changes">
47+
<button>Save</button>
48+
</Tooltip>
49+
);
50+
51+
const button = screen.getByRole('button');
52+
expect(button).toHaveAttribute('aria-label', 'Save your changes');
53+
});
54+
55+
it('applies tooltipped-no-delay class when noDelay is true', () => {
56+
render(
57+
<Tooltip content="No delay tooltip" noDelay>
58+
<button>Button</button>
59+
</Tooltip>
60+
);
61+
62+
const button = screen.getByRole('button');
63+
expect(button).toHaveClass('tooltipped-no-delay');
64+
});
65+
66+
it('does not apply tooltipped-no-delay class when noDelay is false', () => {
67+
render(
68+
<Tooltip content="Normal tooltip" noDelay={false}>
69+
<button>Button</button>
70+
</Tooltip>
71+
);
72+
73+
const button = screen.getByRole('button');
74+
expect(button).not.toHaveClass('tooltipped-no-delay');
75+
});
76+
77+
it('preserves existing className on the child element', () => {
78+
render(
79+
<Tooltip content="Tooltip">
80+
<button className="custom-class">Button</button>
81+
</Tooltip>
82+
);
83+
84+
const button = screen.getByRole('button');
85+
expect(button).toHaveClass('custom-class');
86+
expect(button).toHaveClass('tooltipped');
87+
});
88+
89+
it('wraps the child in a tooltip-wrapper span', () => {
90+
const { container } = render(
91+
<Tooltip content="Tooltip">
92+
<button>Button</button>
93+
</Tooltip>
94+
);
95+
96+
const wrapper = container.querySelector('.tooltip-wrapper');
97+
expect(wrapper).toBeInTheDocument();
98+
expect(wrapper?.tagName.toLowerCase()).toBe('span');
99+
});
100+
});

client/common/Tooltip.tsx

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import React, { ReactElement, useMemo } from 'react';
2+
3+
export type TooltipProps = {
4+
content: string;
5+
noDelay?: boolean;
6+
children: ReactElement;
7+
};
8+
9+
export function Tooltip({ content, noDelay = false, children }: TooltipProps) {
10+
const tooltipClasses = useMemo(() => {
11+
const existingClassName = children.props?.className || '';
12+
return [
13+
existingClassName,
14+
'tooltipped',
15+
'tooltipped-n',
16+
noDelay && 'tooltipped-no-delay'
17+
]
18+
.filter(Boolean)
19+
.join(' ');
20+
}, [children.props?.className, noDelay]);
21+
22+
const childProps = useMemo(
23+
() => ({
24+
'aria-label': content,
25+
className: tooltipClasses
26+
}),
27+
[content, tooltipClasses]
28+
);
29+
30+
return (
31+
<span className="tooltip-wrapper">
32+
{React.cloneElement(children, childProps)}
33+
</span>
34+
);
35+
}

client/common/useSyncFormTranslations.ts

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,30 @@
11
import { useEffect, MutableRefObject } from 'react';
2+
import type { FormApi } from 'final-form';
23

3-
export interface FormLike {
4-
getState(): { values: Record<string, unknown> };
5-
reset(): void;
6-
change(field: string, value: unknown): void;
7-
}
4+
// Generic FormLike that mirrors FormApi for any form value type
5+
export interface FormLike<FormValues = Record<string, unknown>>
6+
extends Pick<FormApi<FormValues>, 'getState' | 'reset' | 'change'> {}
87

98
/**
109
* This hook ensures that form values are preserved when the language changes.
1110
* @param formRef
1211
* @param language
1312
*/
14-
export const useSyncFormTranslations = (
15-
formRef: MutableRefObject<FormLike>,
13+
export const useSyncFormTranslations = <FormValues extends Record<string, any>>(
14+
formRef: MutableRefObject<FormLike<FormValues> | null>,
1615
language: string
1716
) => {
1817
useEffect(() => {
19-
const form = formRef.current;
18+
const form = formRef?.current;
2019
if (!form) return;
2120

2221
const { values } = form.getState();
2322
form.reset();
2423

25-
Object.keys(values).forEach((field) => {
26-
if (values[field]) {
27-
form.change(field, values[field]);
24+
(Object.keys(values) as (keyof FormValues)[]).forEach((field) => {
25+
const value = values[field];
26+
if (value !== undefined && value !== null && value !== '') {
27+
form.change(field, value);
2828
}
2929
});
3030
}, [language]);

client/components/Menubar/MenubarItem.tsx

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import React, { useEffect, useContext, useRef } from 'react';
22
import { MenubarContext, SubmenuContext, ParentMenuContext } from './contexts';
33
import { ButtonOrLink, ButtonOrLinkProps } from '../../common/ButtonOrLink';
4+
import { Tooltip, TooltipProps } from '../../common/Tooltip';
45

56
export enum MenubarItemRole {
67
MENU_ITEM = 'menuitem',
@@ -13,6 +14,7 @@ export interface MenubarItemProps extends Omit<ButtonOrLinkProps, 'role'> {
1314
*/
1415
role?: MenubarItemRole;
1516
selected?: boolean;
17+
tooltipContent?: TooltipProps['content'];
1618
}
1719

1820
/**
@@ -54,6 +56,7 @@ export function MenubarItem({
5456
role: customRole = MenubarItemRole.MENU_ITEM,
5557
isDisabled = false,
5658
selected = false,
59+
tooltipContent,
5760
...rest
5861
}: MenubarItemProps) {
5962
const { createMenuItemHandlers, hasFocus } = useContext(MenubarContext);
@@ -94,15 +97,29 @@ export function MenubarItem({
9497
ref={menuItemRef}
9598
onMouseEnter={handleMouseEnter}
9699
>
97-
<ButtonOrLink
98-
{...rest}
99-
{...handlers}
100-
{...ariaSelected}
101-
role={role}
102-
tabIndex={-1}
103-
id={id}
104-
isDisabled={isDisabled}
105-
/>
100+
{tooltipContent ? (
101+
<Tooltip content={tooltipContent}>
102+
<ButtonOrLink
103+
{...rest}
104+
{...handlers}
105+
{...ariaSelected}
106+
role={role}
107+
tabIndex={-1}
108+
id={id}
109+
isDisabled={isDisabled}
110+
/>
111+
</Tooltip>
112+
) : (
113+
<ButtonOrLink
114+
{...rest}
115+
{...handlers}
116+
{...ariaSelected}
117+
role={role}
118+
tabIndex={-1}
119+
id={id}
120+
isDisabled={isDisabled}
121+
/>
122+
)}
106123
</li>
107124
);
108125
}

client/index.jsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ import { useTranslation } from 'react-i18next';
77
import browserHistory from './browserHistory';
88
import { setupStore } from './store';
99
import Routing from './routes';
10-
import ThemeProvider from './modules/App/components/ThemeProvider';
11-
import Loader from './modules/App/components/loader';
10+
import { ThemeProvider } from './modules/App/components/ThemeProvider';
11+
import { Loader } from './modules/App/components/Loader';
1212
import './i18n';
1313
import { SkipLink } from './components/SkipLink';
1414

Lines changed: 11 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,38 @@
1-
import PropTypes from 'prop-types';
21
import React, { useEffect, useRef, useState } from 'react';
32
import { useDispatch, useSelector } from 'react-redux';
43
import { useLocation } from 'react-router-dom';
54
import { showReduxDevTools } from '../../store';
6-
import DevTools from './components/DevTools';
5+
import { DevTools } from './components/DevTools';
76
import { setPreviousPath } from '../IDE/actions/ide';
87
import { setLanguage } from '../IDE/actions/preferences';
9-
import CookieConsent from '../User/components/CookieConsent';
8+
import { CookieConsent } from '../User/components/CookieConsent';
9+
import type { RootState } from '../../reducers';
1010

11-
function hideCookieConsent(pathname) {
11+
function hideCookieConsent(pathname: string) {
1212
if (pathname.includes('/full/') || pathname.includes('/embed/')) {
1313
return true;
1414
}
1515
return false;
1616
}
1717

18-
const App = ({ children }) => {
18+
export const App = ({ children }: { children?: React.ReactNode }) => {
1919
const dispatch = useDispatch();
2020

21-
const location = useLocation();
21+
const location = useLocation<{ skipSavingPath?: boolean }>();
2222

23-
const theme = useSelector((state) => state.preferences.theme);
23+
const theme = useSelector((state: RootState) => state.preferences.theme);
2424
useEffect(() => {
2525
document.body.className = theme;
2626
}, [theme]);
2727

2828
// TODO: this is only needed for the initial load and would be better handled elsewhere - Linda
29-
const language = useSelector((state) => state.preferences.language);
29+
const language = useSelector(
30+
(state: RootState) => state.preferences.language
31+
);
3032
useEffect(() => {
3133
dispatch(setLanguage(language, { persistPreference: false }));
3234
}, [language]);
3335

34-
// TODO: do we actually need this? - Linda
35-
const [isMounted, setIsMounted] = useState(false);
36-
useEffect(() => setIsMounted(true), []);
37-
3836
const previousLocationRef = useRef(location);
3937
useEffect(() => {
4038
const prevLocation = previousLocationRef.current;
@@ -52,18 +50,8 @@ const App = ({ children }) => {
5250
return (
5351
<div className="app">
5452
<CookieConsent hide={hide} />
55-
{isMounted && showReduxDevTools() && <DevTools />}
53+
{showReduxDevTools() && <DevTools />}
5654
{children}
5755
</div>
5856
);
5957
};
60-
61-
App.propTypes = {
62-
children: PropTypes.element
63-
};
64-
65-
App.defaultProps = {
66-
children: null
67-
};
68-
69-
export default App;

client/modules/App/components/DevTools.jsx renamed to client/modules/App/components/DevTools.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,4 @@ const devTools = (
99
</DockMonitor>
1010
);
1111

12-
export default createDevTools(devTools);
12+
export const DevTools = createDevTools(devTools);

0 commit comments

Comments
 (0)