Skip to content

Commit 413454b

Browse files
refactor: update propTypes -> TypeScript in Studio Header (#643)
1 parent ed7cc8a commit 413454b

File tree

7 files changed

+110
-185
lines changed

7 files changed

+110
-185
lines changed

src/studio-header/BrandNav.tsx

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
1-
import React from 'react';
2-
import PropTypes from 'prop-types';
1+
import React, { type FunctionComponent } from 'react';
32
import { Link } from 'react-router-dom';
43

5-
const BrandNav = ({
4+
interface Props {
5+
studioBaseUrl: string;
6+
logo: string;
7+
logoAltText: string;
8+
}
9+
10+
const BrandNav: FunctionComponent<Props> = ({
611
studioBaseUrl,
712
logo,
813
logoAltText,
@@ -16,10 +21,4 @@ const BrandNav = ({
1621
</Link>
1722
);
1823

19-
BrandNav.propTypes = {
20-
studioBaseUrl: PropTypes.string.isRequired,
21-
logo: PropTypes.string.isRequired,
22-
logoAltText: PropTypes.string.isRequired,
23-
};
24-
2524
export default BrandNav;

src/studio-header/CourseLockUp.tsx

Lines changed: 14 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import React from 'react';
2-
import PropTypes from 'prop-types';
1+
import React, { type FunctionComponent } from 'react';
32
import { useIntl } from '@edx/frontend-platform/i18n';
43
import {
54
OverlayTrigger,
@@ -9,14 +8,19 @@ import { Link } from 'react-router-dom';
98

109
import messages from './messages';
1110

12-
const CourseLockUp = (
13-
{
14-
outlineLink,
15-
org,
16-
number,
17-
title,
18-
},
19-
) => {
11+
interface Props {
12+
outlineLink?: string;
13+
org?: string;
14+
number?: string;
15+
title?: string;
16+
}
17+
18+
const CourseLockUp: FunctionComponent<Props> = ({
19+
outlineLink = '',
20+
org = '',
21+
number = '',
22+
title = '',
23+
}) => {
2024
const intl = useIntl();
2125

2226
return (
@@ -41,18 +45,4 @@ const CourseLockUp = (
4145
);
4246
};
4347

44-
CourseLockUp.propTypes = {
45-
number: PropTypes.string,
46-
org: PropTypes.string,
47-
title: PropTypes.string,
48-
outlineLink: PropTypes.string,
49-
};
50-
51-
CourseLockUp.defaultProps = {
52-
number: null,
53-
org: null,
54-
title: null,
55-
outlineLink: null,
56-
};
57-
5848
export default CourseLockUp;

src/studio-header/HeaderBody.tsx

Lines changed: 34 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import React from 'react';
2-
import PropTypes from 'prop-types';
1+
import React, { type ReactNode, type ComponentProps } from 'react';
32
import { useIntl } from '@edx/frontend-platform/i18n';
43
import classNames from 'classnames';
54
import {
@@ -19,6 +18,32 @@ import BrandNav from './BrandNav';
1918
import NavDropdownMenu from './NavDropdownMenu';
2019
import messages from './messages';
2120

21+
export interface HeaderBodyProps {
22+
studioBaseUrl: string;
23+
logoutUrl: string;
24+
setModalPopupTarget?: ((instance: HTMLButtonElement | null) => void) | null;
25+
toggleModalPopup?: React.MouseEventHandler<HTMLButtonElement>;
26+
isModalPopupOpen?: boolean;
27+
number?: string;
28+
org?: string;
29+
title: string;
30+
logo: string;
31+
logoAltText: string;
32+
authenticatedUserAvatar?: string;
33+
username?: string;
34+
isAdmin?: boolean;
35+
isMobile?: boolean;
36+
isHiddenMainMenu?: boolean;
37+
mainMenuDropdowns?: {
38+
id: string;
39+
buttonTitle: ReactNode;
40+
items: { title: ReactNode; href: string; }[];
41+
}[];
42+
outlineLink?: string;
43+
searchButtonAction?: React.MouseEventHandler<HTMLButtonElement>;
44+
containerProps?: Omit<ComponentProps<typeof Container>, 'children'>;
45+
}
46+
2247
const HeaderBody = ({
2348
logo,
2449
logoAltText,
@@ -31,15 +56,15 @@ const HeaderBody = ({
3156
logoutUrl,
3257
authenticatedUserAvatar,
3358
isMobile,
34-
setModalPopupTarget,
59+
setModalPopupTarget = null,
3560
toggleModalPopup,
36-
isModalPopupOpen,
37-
isHiddenMainMenu,
38-
mainMenuDropdowns,
61+
isModalPopupOpen = false,
62+
isHiddenMainMenu = false,
63+
mainMenuDropdowns = [],
3964
outlineLink,
4065
searchButtonAction,
41-
containerProps,
42-
}) => {
66+
containerProps = {},
67+
}: HeaderBodyProps) => {
4368
const intl = useIntl();
4469

4570
const renderBrandNav = (
@@ -52,7 +77,7 @@ const HeaderBody = ({
5277
/>
5378
);
5479

55-
const { className: containerClassName, ...restContainerProps } = containerProps || {};
80+
const { className: containerClassName, ...restContainerProps } = containerProps;
5681

5782
return (
5883
<Container
@@ -144,53 +169,4 @@ const HeaderBody = ({
144169
);
145170
};
146171

147-
HeaderBody.propTypes = {
148-
studioBaseUrl: PropTypes.string.isRequired,
149-
logoutUrl: PropTypes.string.isRequired,
150-
setModalPopupTarget: PropTypes.func,
151-
toggleModalPopup: PropTypes.func,
152-
isModalPopupOpen: PropTypes.bool,
153-
number: PropTypes.string,
154-
org: PropTypes.string,
155-
title: PropTypes.string,
156-
logo: PropTypes.string,
157-
logoAltText: PropTypes.string,
158-
authenticatedUserAvatar: PropTypes.string,
159-
username: PropTypes.string,
160-
isAdmin: PropTypes.bool,
161-
isMobile: PropTypes.bool,
162-
isHiddenMainMenu: PropTypes.bool,
163-
mainMenuDropdowns: PropTypes.arrayOf(PropTypes.shape({
164-
id: PropTypes.string,
165-
buttonTitle: PropTypes.node,
166-
items: PropTypes.arrayOf(PropTypes.shape({
167-
href: PropTypes.string,
168-
title: PropTypes.node,
169-
})),
170-
})),
171-
outlineLink: PropTypes.string,
172-
searchButtonAction: PropTypes.func,
173-
containerProps: PropTypes.shape(Container.propTypes),
174-
};
175-
176-
HeaderBody.defaultProps = {
177-
setModalPopupTarget: null,
178-
toggleModalPopup: null,
179-
isModalPopupOpen: false,
180-
logo: null,
181-
logoAltText: null,
182-
number: '',
183-
org: '',
184-
title: '',
185-
authenticatedUserAvatar: null,
186-
username: null,
187-
isAdmin: false,
188-
isMobile: false,
189-
isHiddenMainMenu: false,
190-
mainMenuDropdowns: [],
191-
outlineLink: null,
192-
searchButtonAction: null,
193-
containerProps: {},
194-
};
195-
196172
export default HeaderBody;

src/studio-header/MobileHeader.tsx

Lines changed: 19 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,32 @@
1-
import React, { useState } from 'react';
2-
import PropTypes from 'prop-types';
1+
import React, { type FunctionComponent, useState } from 'react';
32
import { useToggle, ModalPopup } from '@openedx/paragon';
4-
import HeaderBody from './HeaderBody';
3+
import HeaderBody, { type HeaderBodyProps } from './HeaderBody';
54
import MobileMenu from './MobileMenu';
65

7-
const MobileHeader = ({
6+
type Props = Pick<HeaderBodyProps,
7+
| 'studioBaseUrl'
8+
| 'logoutUrl'
9+
| 'number'
10+
| 'org'
11+
| 'title'
12+
| 'logo'
13+
| 'logoAltText'
14+
| 'authenticatedUserAvatar'
15+
| 'username'
16+
| 'isAdmin'
17+
| 'mainMenuDropdowns'
18+
| 'outlineLink'
19+
>;
20+
21+
const MobileHeader: FunctionComponent<Props> = ({
822
mainMenuDropdowns,
923
...props
1024
}) => {
1125
const [isOpen, , close, toggle] = useToggle(false);
12-
const [target, setTarget] = useState(null);
26+
const [target, setTarget] = useState<HTMLButtonElement | null>(null);
1327

1428
return (
1529
<>
16-
{/* @ts-expect-error The type of 'props' is any until we convert from propTypes to TypeScript interface/types */}
1730
<HeaderBody
1831
{...props}
1932
isMobile
@@ -36,39 +49,4 @@ const MobileHeader = ({
3649
);
3750
};
3851

39-
MobileHeader.propTypes = {
40-
studioBaseUrl: PropTypes.string.isRequired, // eslint-disable-line react/no-unused-prop-types
41-
logoutUrl: PropTypes.string.isRequired, // eslint-disable-line react/no-unused-prop-types
42-
number: PropTypes.string, // eslint-disable-line react/no-unused-prop-types
43-
org: PropTypes.string, // eslint-disable-line react/no-unused-prop-types
44-
title: PropTypes.string, // eslint-disable-line react/no-unused-prop-types
45-
logo: PropTypes.string, // eslint-disable-line react/no-unused-prop-types
46-
logoAltText: PropTypes.string, // eslint-disable-line react/no-unused-prop-types
47-
authenticatedUserAvatar: PropTypes.string, // eslint-disable-line react/no-unused-prop-types
48-
username: PropTypes.string, // eslint-disable-line react/no-unused-prop-types
49-
isAdmin: PropTypes.bool, // eslint-disable-line react/no-unused-prop-types
50-
mainMenuDropdowns: PropTypes.arrayOf(PropTypes.shape({
51-
id: PropTypes.string,
52-
buttonTitle: PropTypes.node,
53-
items: PropTypes.arrayOf(PropTypes.shape({
54-
href: PropTypes.string,
55-
title: PropTypes.node,
56-
})),
57-
})),
58-
outlineLink: PropTypes.string, // eslint-disable-line react/no-unused-prop-types
59-
};
60-
61-
MobileHeader.defaultProps = {
62-
logo: null,
63-
logoAltText: null,
64-
number: null,
65-
org: null,
66-
title: null,
67-
authenticatedUserAvatar: null,
68-
username: null,
69-
isAdmin: false,
70-
mainMenuDropdowns: [],
71-
outlineLink: null,
72-
};
73-
7452
export default MobileHeader;
Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,21 @@
1-
import React from 'react';
2-
import PropTypes from 'prop-types';
1+
import React, { type ReactNode } from 'react';
32
import {
43
Dropdown,
54
DropdownButton,
65
} from '@openedx/paragon';
76
import { Link } from 'react-router-dom';
87

8+
interface Props {
9+
id: string;
10+
buttonTitle: ReactNode;
11+
items: { title: ReactNode; href: string; }[];
12+
}
13+
914
const NavDropdownMenu = ({
1015
id,
1116
buttonTitle,
1217
items,
13-
}) => (
18+
}: Props) => (
1419
<DropdownButton
1520
id={id}
1621
title={buttonTitle}
@@ -30,13 +35,4 @@ const NavDropdownMenu = ({
3035
</DropdownButton>
3136
);
3237

33-
NavDropdownMenu.propTypes = {
34-
id: PropTypes.string.isRequired,
35-
buttonTitle: PropTypes.node.isRequired,
36-
items: PropTypes.arrayOf(PropTypes.shape({
37-
href: PropTypes.string.isRequired,
38-
title: PropTypes.node.isRequired,
39-
})).isRequired,
40-
};
41-
4238
export default NavDropdownMenu;

src/studio-header/StudioHeader.test.tsx

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -71,12 +71,8 @@ const props: React.ComponentProps<typeof StudioHeader> = {
7171
},
7272
],
7373
outlineLink: 'tEsTLInK',
74-
searchButtonAction: null,
74+
searchButtonAction: undefined,
7575
isNewHomePage: true,
76-
// These default values shouldn't be needed but typescript is confused by propTypes; can remove after converting
77-
// from propTypes to TypeScript:
78-
containerProps: {},
79-
isHiddenMainMenu: false,
8076
};
8177

8278
describe('Header', () => {
@@ -146,7 +142,7 @@ describe('Header', () => {
146142
});
147143

148144
it('should not show search button', async () => {
149-
const testProps = { ...props, searchButtonAction: null };
145+
const testProps = { ...props, searchButtonAction: undefined };
150146
const { queryByRole } = render(<RootWrapper {...testProps} />);
151147
expect(queryByRole('button', { name: 'Search content' })).not.toBeInTheDocument();
152148
});

0 commit comments

Comments
 (0)