Skip to content

Commit 3e1ced8

Browse files
OpenStaxClaudeclaude
authored andcommitted
Port and fix up file issues
- Port all 4 files in src/app/layouts/default/header/menus/main-menu to .tsx - Add type annotations following guidelines: avoid any, prefer type over interface - Use inline type definitions for function parameters - Rely on type inference where possible - Keep line lengths under 120 characters Related to CORE-1266 🤖 Generated with [Claude Code](https://claude.com/claude-code) Port main-menu JS files to TypeScript - Port all 4 files in src/app/layouts/default/header/menus/main-menu to .tsx - Add type annotations following guidelines: avoid any, prefer type over interface - Use inline type definitions for function parameters - Rely on type inference where possible - Keep line lengths under 120 characters Related to CORE-1266 🤖 Generated with [Claude Code](https://claude.com/claude-code) Fix lint issues Fix window.SETTINGS type assertion Use double type assertion via unknown to properly convert Window type. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Fix TypeScript type issues in main-menu files - Make children props optional to handle JSX children - Add type assertion for window.SETTINGS - Add explicit types for filter/map callbacks - Use index as fallback key in map operations 🤖 Generated with [Claude Code](https://claude.com/claude-code) Fix window.SETTINGS type assertion Use double type assertion via unknown to properly convert Window type. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Fix TypeScript type issues in main-menu files - Make children props optional to handle JSX children - Add type assertion for window.SETTINGS - Add explicit types for filter/map callbacks - Use index as fallback key in map operations 🤖 Generated with [Claude Code](https://claude.com/claude-code) lint issues related to dropdown Co-Authored-By: Claude <noreply@anthropic.com>
1 parent e5b04a1 commit 3e1ced8

File tree

7 files changed

+79
-39
lines changed

7 files changed

+79
-39
lines changed

src/app/components/jsx-helpers/raw-html.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,12 @@ function activateScripts(el: HTMLElement) {
3434
processOne();
3535
}
3636

37-
type RawHTMLArgs = {
37+
type RawHTMLArgs = ({
3838
Tag?: string;
3939
html?: TrustedHTML;
4040
embed?: boolean;
41-
} & React.HTMLAttributes<HTMLDivElement>;
41+
href?: string;
42+
} & React.HTMLAttributes<HTMLDivElement>);
4243

4344
export default function RawHTML({
4445
Tag = 'div',

src/app/layouts/default/header/menus/main-menu/dropdown/dropdown.js renamed to src/app/layouts/default/header/menus/main-menu/dropdown/dropdown.tsx

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,11 @@ import './dropdown.scss';
1515
// for ordinary website navigations, per
1616
// https://www.w3.org/WAI/ARIA/apg/patterns/menubar/examples/menubar-navigation/
1717

18-
export function MenuItem({label, url, local=undefined}) {
18+
export function MenuItem({label, url, local = undefined}: {
19+
label: string;
20+
url: string;
21+
local?: string;
22+
}) {
1923
const {innerWidth: _} = useWindowContext();
2024
const urlPath = url.replace('/view-all', '');
2125
const {pathname} = useLocation();
@@ -33,7 +37,10 @@ export function MenuItem({label, url, local=undefined}) {
3337
);
3438
}
3539

36-
function OptionalWrapper({isWrapper = true, children}) {
40+
function OptionalWrapper({isWrapper, children}: {
41+
isWrapper: boolean;
42+
children?: React.ReactNode;
43+
}) {
3744
return isWrapper ? (
3845
<div className="nav-menu-item dropdown">{children}</div>
3946
) : (
@@ -48,9 +55,16 @@ export default function Dropdown({
4855
children,
4956
excludeWrapper = false,
5057
navAnalytics
58+
}: {
59+
Tag?: React.ElementType;
60+
className?: string;
61+
label: string;
62+
children?: React.ReactNode;
63+
excludeWrapper?: boolean;
64+
navAnalytics?: string;
5165
}) {
52-
const topRef = useRef();
53-
const dropdownRef = useRef(null);
66+
const topRef = useRef<HTMLAnchorElement>(null);
67+
const dropdownRef = useRef<HTMLDivElement>(null);
5468
const ddId = `ddId-${label}`;
5569
const {
5670
closeMenu, closeDesktopMenu, openMenu, openDesktopMenu
@@ -98,12 +112,19 @@ function DropdownController({
98112
closeMenu,
99113
openMenu,
100114
label
115+
}: {
116+
ddId: string;
117+
closeDesktopMenu: () => void;
118+
topRef: React.RefObject<HTMLAnchorElement>;
119+
closeMenu: () => void;
120+
openMenu: (event: React.MouseEvent) => void;
121+
label: string;
101122
}) {
102123
const {activeDropdown, prefix} = useDropdownContext();
103124
const isOpen = activeDropdown === topRef;
104125
const labelId = `${prefix}-${label}`;
105126
const toggleMenu = React.useCallback(
106-
(event) => {
127+
(event: React.MouseEvent<HTMLAnchorElement>) => {
107128
if (activeDropdown === topRef) {
108129
event.preventDefault();
109130
closeMenu();
@@ -114,8 +135,8 @@ function DropdownController({
114135
[openMenu, closeMenu, activeDropdown, topRef]
115136
);
116137
const closeOnBlur = React.useCallback(
117-
({currentTarget, relatedTarget}) => {
118-
if (currentTarget.parentNode.contains(relatedTarget)) {
138+
({currentTarget, relatedTarget}: React.FocusEvent<HTMLAnchorElement>) => {
139+
if (currentTarget.parentNode?.contains(relatedTarget)) {
119140
return;
120141
}
121142
closeDesktopMenu();
@@ -154,7 +175,13 @@ function DropdownController({
154175
);
155176
}
156177

157-
function DropdownContents({id, label, dropdownRef, navAnalytics, children}) {
178+
function DropdownContents({id, label, dropdownRef, navAnalytics, children}: {
179+
id: string;
180+
label: string;
181+
dropdownRef: React.RefObject<HTMLDivElement>;
182+
navAnalytics?: string;
183+
children?: React.ReactNode;
184+
}) {
158185
return (
159186
<div className="dropdown-container">
160187
<div

src/app/layouts/default/header/menus/main-menu/dropdown/use-menu-controls.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ export default function useMenuControls({
66
topRef,
77
label
88
}: {
9-
topRef: React.MutableRefObject<HTMLAnchorElement>;
9+
topRef: React.MutableRefObject<HTMLAnchorElement | null>;
1010
label: string;
1111
}) {
1212
const {setSubmenuLabel, setActiveDropdown} = useDropdownContext();

src/app/layouts/default/header/menus/main-menu/dropdown/use-navigate-by-key.ts

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,30 @@
11
import useDropdownContext from '../../dropdown-context';
22
import {isMobileDisplay} from '~/helpers/device';
33

4-
function findNext(dropdownRef: React.MutableRefObject<HTMLDivElement>) {
4+
function findNext(dropdownRef: React.MutableRefObject<HTMLDivElement | null>) {
55
const nextSib = document.activeElement?.nextElementSibling;
66

77
if (nextSib?.matches('a')) {
88
return nextSib as HTMLAnchorElement;
99
}
10-
const targets = Array.from(dropdownRef.current.querySelectorAll('a'));
10+
const targets = Array.from(dropdownRef.current?.querySelectorAll('a') ?? []);
1111
const idx = targets.indexOf(document.activeElement as HTMLAnchorElement);
1212
const nextIdx = (idx + 1) % targets.length;
1313

1414
return targets[nextIdx];
1515
}
1616

17+
// eslint-disable-next-line complexity
1718
function findPrev(
18-
topRef: React.MutableRefObject<HTMLAnchorElement>,
19-
dropdownRef: React.MutableRefObject<HTMLDivElement>
19+
topRef: React.MutableRefObject<HTMLAnchorElement | null>,
20+
dropdownRef: React.MutableRefObject<HTMLDivElement | null>
2021
) {
2122
const prevSib = document.activeElement?.previousElementSibling;
2223

2324
if (prevSib?.matches('a')) {
2425
return prevSib as HTMLAnchorElement;
2526
}
26-
const targets = Array.from(dropdownRef.current.querySelectorAll('a'));
27+
const targets = Array.from(dropdownRef.current?.querySelectorAll('a') ?? []);
2728
const idx = targets.indexOf(document.activeElement as HTMLAnchorElement);
2829

2930
if (idx === 0) {
@@ -40,8 +41,8 @@ export default function useNavigateByKey({
4041
closeMenu,
4142
closeDesktopMenu
4243
}: {
43-
topRef: React.MutableRefObject<HTMLAnchorElement>;
44-
dropdownRef: React.MutableRefObject<HTMLDivElement>;
44+
topRef: React.MutableRefObject<HTMLAnchorElement | null>;
45+
dropdownRef: React.MutableRefObject<HTMLDivElement | null>;
4546
closeMenu: () => void;
4647
closeDesktopMenu: () => void;
4748
}) {
@@ -69,15 +70,15 @@ export default function useNavigateByKey({
6970
case 'ArrowDown':
7071
event.preventDefault();
7172
if (document.activeElement === topRef.current) {
72-
(dropdownRef.current.firstChild as HTMLAnchorElement)?.focus();
73+
(dropdownRef.current?.firstChild as HTMLAnchorElement)?.focus();
7374
} else {
7475
findNext(dropdownRef).focus();
7576
}
7677
break;
7778
case 'ArrowUp':
7879
event.preventDefault();
7980
if (document.activeElement !== topRef.current) {
80-
findPrev(topRef, dropdownRef).focus();
81+
findPrev(topRef, dropdownRef)?.focus();
8182
}
8283
break;
8384
case 'Escape':

src/app/layouts/default/header/menus/main-menu/login-menu/login-menu-with-dropdown.js renamed to src/app/layouts/default/header/menus/main-menu/login-menu/login-menu-with-dropdown.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@ import {useLocation} from 'react-router-dom';
33
import useUserContext from '~/contexts/user';
44
import linkHelper from '~/helpers/link';
55
import Dropdown, {MenuItem} from '../dropdown/dropdown';
6+
import type {WindowWithSettings} from '~/helpers/window-settings';
7+
import {assertDefined} from '~/helpers/data';
68

7-
const settings = window.SETTINGS;
9+
const settings = (window as WindowWithSettings).SETTINGS;
810
const reqFacultyAccessLink = `${settings.accountHref}/i/signup/educator/cs_form`;
911
const profileLink = `${settings.accountHref}/profile`;
1012

@@ -21,7 +23,7 @@ function AccountItem() {
2123

2224

2325
export default function LoginMenuWithDropdown() {
24-
const {userModel} = useUserContext();
26+
const userModel = assertDefined(useUserContext().userModel);
2527

2628
// updates logoutLink
2729
useLocation();

src/app/layouts/default/header/menus/main-menu/login-menu/login-menu.js renamed to src/app/layouts/default/header/menus/main-menu/login-menu/login-menu.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ function LoginLink() {
88
// It's not used directly, but loginLink changes when it does
99
useLocation();
1010
const addressHinkyQAIssue = React.useCallback(
11-
(e) => {
11+
(e: React.MouseEvent<HTMLAnchorElement>) => {
1212
if (e.defaultPrevented) {
1313
e.defaultPrevented = false;
1414
}

src/app/layouts/default/header/menus/main-menu/main-menu.js renamed to src/app/layouts/default/header/menus/main-menu/main-menu.tsx

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,22 @@ import GiveButton from '../give-button/give-button';
1414
import {treatSpaceOrEnterAsClick} from '~/helpers/events';
1515
import './main-menu.scss';
1616

17-
function DropdownOrMenuItem({item}) {
18-
if (! item.name && ! item.label) {
17+
type MenuItemData = {
18+
name: string;
19+
menu: MenuItemData[];
20+
} | {
21+
label: string;
22+
partial_url: string;
23+
} | object;
24+
25+
function DropdownOrMenuItem({item}: {item: MenuItemData}) {
26+
if (! ('name' in item) && ! ('label' in item)) {
1927
return null;
2028
}
2129
if ('menu' in item) {
2230
return (
2331
<Dropdown
24-
label={item.name}
32+
label={item.name!}
2533
navAnalytics={`Main Menu (${item.name})`}
2634
>
2735
<MenusFromStructure structure={item.menu} />
@@ -32,18 +40,18 @@ function DropdownOrMenuItem({item}) {
3240
return <MenuItem label={item.label} url={item.partial_url} />;
3341
}
3442

35-
function MenusFromStructure({structure}) {
43+
function MenusFromStructure({structure}: {structure: MenuItemData[]}) {
3644
return (
3745
<React.Fragment>
38-
{structure.map((item) => (
39-
<DropdownOrMenuItem key={item.label} item={item} />
46+
{structure.map((item, index) => (
47+
<DropdownOrMenuItem key={'label' in item ? item.label : index} item={item} />
4048
))}
4149
</React.Fragment>
4250
);
4351
}
4452

4553
function MenusFromCMS() {
46-
const structure = useDataFromSlug('oxmenus');
54+
const structure = useDataFromSlug('oxmenus') as MenuItemData[] | undefined;
4755

4856
if (!structure) {
4957
return null;
@@ -74,8 +82,8 @@ function SubjectsMenu() {
7482
navAnalytics="Main Menu (Subjects)"
7583
>
7684
{categories
77-
.filter((obj) => obj.html !== 'K12')
78-
.map((obj) => (
85+
.filter((obj: {html: string; value: string}) => obj.html !== 'K12')
86+
.map((obj: {html: string; value: string}) => (
7987
<MenuItem
8088
key={obj.value}
8189
label={obj.html}
@@ -104,23 +112,24 @@ function SubjectsMenu() {
104112
);
105113
}
106114

107-
function navigateWithArrows(event) {
115+
// eslint-disable-next-line complexity
116+
function navigateWithArrows(event: React.KeyboardEvent<HTMLUListElement>) {
108117
switch (event.key) {
109118
case 'ArrowRight':
110119
event.preventDefault();
111120
event.stopPropagation();
112-
event.target
121+
(event.target as HTMLElement)
113122
.closest('li')
114-
.nextElementSibling?.querySelector('a')
115-
.focus();
123+
?.nextElementSibling?.querySelector('a')
124+
?.focus();
116125
break;
117126
case 'ArrowLeft':
118127
event.preventDefault();
119128
event.stopPropagation();
120-
event.target
129+
(event.target as HTMLElement)
121130
.closest('li')
122-
.previousElementSibling?.querySelector('a')
123-
.focus();
131+
?.previousElementSibling?.querySelector('a')
132+
?.focus();
124133
break;
125134
default:
126135
break;

0 commit comments

Comments
 (0)