diff --git a/docs/style_guide/ts_style_guide.md b/docs/style_guide/ts_style_guide.md
index 74ef6fa5fb..7dabf487ad 100644
--- a/docs/style_guide/ts_style_guide.md
+++ b/docs/style_guide/ts_style_guide.md
@@ -19,10 +19,12 @@ Key Sections:
- [Formatting](#formatting)
- [Quotes](#quotes) (single vs. double)
- [Use semicolons](#semicolons)
-- [Annotate Arrays as `Type[]`](#array)
-- [`type` vs `interface`](#type-vs-interface)
+- [Arrays (annotate as `Type[]`)](#arrays)
+- [`type` vs. `interface`](#type-vs-interface)
- [One-line `if` statements](#one-line-if-statements)
- [`import`s](#imports)
+ - [Absolute vs. relative paths](#absolute-vs-relative-paths)
+ - [Specificity preferred](#specificity-preferred)
- [`KeyboardEvent`s](#keyboardevents)
- [Components](#components)
- [Function return type](#function-return-type)
@@ -59,17 +61,17 @@ function barFunc() {}
**Bad**
```tsx
-const component: React.FC = () => {
+function component(): ReactElement {
return ;
-};
+}
```
**Good**
```tsx
-const Component: React.FC = () => {
+function Component(): ReactElement {
return ;
-};
+}
```
### Class
@@ -235,7 +237,7 @@ interface Foo {}
> Reason: `*Types.ts` files are ignored by our CodeCov settings.
-## Null vs. Undefined
+## `null` vs. `undefined`
- Prefer not to use either for explicit unavailability
@@ -352,14 +354,14 @@ Use [Prettier](https://prettier.io/) to format TypeScript code as described in t
> [google/angular](https://github.com/angular/angular/), [facebook/react](https://github.com/facebook/react),
> [Microsoft/TypeScript](https://github.com/Microsoft/TypeScript/).
-## Array
+## Arrays
- Annotate arrays as `foos:Foo[]` instead of `foos:Array`.
> Reasons: Its easier to read. Its used by the TypeScript team. Makes easier to know something is an array as the mind
> is trained to detect `[]`.
-## type vs. interface
+## `type` vs. `interface`
- Use `type` when you _might_ need a union or intersection:
@@ -406,28 +408,55 @@ if (isEmpty)
> Reason: Avoiding braces can cause developers to miss bugs, such as Apple's infamous
> [goto-fail bug](https://nakedsecurity.sophos.com/2014/02/24/anatomy-of-a-goto-fail-apples-ssl-bug-explained-plus-an-unofficial-patch/)
-## imports
+## `import`s
+
+### Absolute vs. relative paths
- Use absolute `import` statements everywhere for consistency.
**Good**
```ts
+import { type Project } from "api/models";
import { getAllProjects } from "backend";
-import { Project } from "types/project";
```
**Bad**
```ts
+import { type Project } from "../../../../api/models";
import { getAllProjects } from "../../../../backend";
-import { Project } from "../../../../types/project";
```
> Reason: Provides consistency for imports across all files and shortens imports of commonly used top level modules.
> Developers don't have to count `../` to know where a module is, they can simply start from the root of `src/`.
-## KeyboardEvents
+### Specificity preferred
+
+- Generally import the specific things needed (e.g., not `React` when `{ type ReactElement }` will do), and from a more
+ specific target (e.g., `from "api/models"` rather than `from "api"`):
+
+**Good**
+
+```ts
+import { type ReactElement } from "react";
+
+import { type Project } from "api/models";
+
+function Component(props: { project: Project }): ReactElement {}
+```
+
+**Bad**
+
+```ts
+import React from "react";
+
+import { type Project } from "api";
+
+function Component(props: { project: Project }): React.ReactElement {}
+```
+
+## `KeyboardEvent`s
- Use `ts-key-enum` when comparing to `React.KeyboardEvent`s.
diff --git a/src/components/AppBar/SpeakerMenu.tsx b/src/components/AppBar/SpeakerMenu.tsx
index 5b7a434794..56c64b44fa 100644
--- a/src/components/AppBar/SpeakerMenu.tsx
+++ b/src/components/AppBar/SpeakerMenu.tsx
@@ -10,20 +10,20 @@ import {
Typography,
} from "@mui/material";
import {
- ForwardedRef,
- MouseEvent,
- ReactElement,
+ type ForwardedRef,
+ type MouseEvent,
+ type ReactElement,
useEffect,
useState,
} from "react";
import { useTranslation } from "react-i18next";
import { useSelector } from "react-redux";
-import { Speaker } from "api";
+import { type Speaker } from "api/models";
import { getAllSpeakers } from "backend";
import { buttonMinHeight } from "components/AppBar/AppBarTypes";
import { setCurrentSpeaker } from "components/Project/ProjectActions";
-import { StoreState } from "types";
+import { type StoreState } from "types";
import { useAppDispatch } from "types/hooks";
import { themeColors } from "types/theme";
diff --git a/src/components/AppBar/UserMenu.tsx b/src/components/AppBar/UserMenu.tsx
index 505ad69c32..0136a025ac 100644
--- a/src/components/AppBar/UserMenu.tsx
+++ b/src/components/AppBar/UserMenu.tsx
@@ -12,17 +12,24 @@ import {
MenuItem,
Typography,
} from "@mui/material";
-import React, { Fragment, ReactElement, useState } from "react";
+import {
+ type CSSProperties,
+ type ForwardedRef,
+ Fragment,
+ type MouseEvent,
+ type ReactElement,
+ useState,
+} from "react";
import { useTranslation } from "react-i18next";
import { useNavigate } from "react-router-dom";
import { isSiteAdmin } from "backend";
import * as LocalStorage from "backend/localStorage";
import {
+ type TabProps,
buttonMinHeight,
shortenName,
tabColor,
- TabProps,
} from "components/AppBar/AppBarTypes";
import { clearCurrentProject } from "components/Project/ProjectActions";
import { useAppDispatch } from "types/hooks";
@@ -46,7 +53,7 @@ export default function UserMenu(props: TabProps): ReactElement {
const [isAdmin, setIsAdmin] = useState(false);
const username = LocalStorage.getCurrentUser()?.username;
- function handleClick(event: React.MouseEvent): void {
+ function handleClick(event: MouseEvent): void {
setAnchorElement(event.currentTarget);
}
@@ -106,7 +113,7 @@ export default function UserMenu(props: TabProps): ReactElement {
interface UserMenuListProps {
isAdmin: boolean;
onSelect: () => void;
- forwardedRef?: React.ForwardedRef;
+ forwardedRef?: ForwardedRef;
}
/**
@@ -118,7 +125,7 @@ export function UserMenuList(props: UserMenuListProps): ReactElement {
const { t } = useTranslation();
const navigate = useNavigate();
- const iconStyle: React.CSSProperties =
+ const iconStyle: CSSProperties =
document.body.dir == "rtl" ? { marginLeft: 6 } : { marginRight: 6 };
return (
diff --git a/src/components/AppBar/tests/NavigationButtons.test.tsx b/src/components/AppBar/tests/NavigationButtons.test.tsx
index d9e1fd8d7a..45d02e0f55 100644
--- a/src/components/AppBar/tests/NavigationButtons.test.tsx
+++ b/src/components/AppBar/tests/NavigationButtons.test.tsx
@@ -1,8 +1,8 @@
import { Provider } from "react-redux";
-import renderer, { ReactTestInstance } from "react-test-renderer";
+import renderer, { type ReactTestInstance } from "react-test-renderer";
import configureMockStore from "redux-mock-store";
-import { Permission } from "api";
+import { Permission } from "api/models";
import NavigationButtons, {
dataCleanupButtonId,
dataEntryButtonId,
diff --git a/src/components/DataEntry/DataEntryTable/EntryCellComponents/GlossWithSuggestions.tsx b/src/components/DataEntry/DataEntryTable/EntryCellComponents/GlossWithSuggestions.tsx
index ed3bc95ce7..eb79e61399 100644
--- a/src/components/DataEntry/DataEntryTable/EntryCellComponents/GlossWithSuggestions.tsx
+++ b/src/components/DataEntry/DataEntryTable/EntryCellComponents/GlossWithSuggestions.tsx
@@ -1,8 +1,14 @@
import { Autocomplete } from "@mui/material";
-import React, { ReactElement, useContext, useEffect } from "react";
+import {
+ type KeyboardEvent,
+ type ReactElement,
+ type RefObject,
+ useContext,
+ useEffect,
+} from "react";
import { Key } from "ts-key-enum";
-import { WritingSystem } from "api/models";
+import { type WritingSystem } from "api/models";
import { LiWithFont, TextFieldWithFont } from "utilities/fontComponents";
import SpellCheckerContext from "utilities/spellCheckerContext";
@@ -10,7 +16,7 @@ interface GlossWithSuggestionsProps {
isNew?: boolean;
isDisabled?: boolean;
gloss: string;
- glossInput?: React.RefObject;
+ glossInput?: RefObject;
updateGlossField: (newValue: string) => void;
handleEnter: () => void;
onBlur?: () => void;
@@ -81,7 +87,7 @@ export default function GlossWithSuggestions(
{option}
)}
- onKeyPress={(e: React.KeyboardEvent) => {
+ onKeyPress={(e: KeyboardEvent) => {
if (e.key === Key.Enter) {
props.handleEnter();
}
diff --git a/src/components/DataEntry/DataEntryTable/EntryCellComponents/VernWithSuggestions.tsx b/src/components/DataEntry/DataEntryTable/EntryCellComponents/VernWithSuggestions.tsx
index 3372045020..6b63a6e759 100644
--- a/src/components/DataEntry/DataEntryTable/EntryCellComponents/VernWithSuggestions.tsx
+++ b/src/components/DataEntry/DataEntryTable/EntryCellComponents/VernWithSuggestions.tsx
@@ -1,18 +1,24 @@
import { Autocomplete, AutocompleteCloseReason } from "@mui/material";
-import React, { ReactElement, useEffect } from "react";
+import {
+ type KeyboardEvent,
+ type ReactElement,
+ type RefObject,
+ type SyntheticEvent,
+ useEffect,
+} from "react";
import { Key } from "ts-key-enum";
-import { WritingSystem } from "api/models";
+import { type WritingSystem } from "api/models";
import { LiWithFont, TextFieldWithFont } from "utilities/fontComponents";
interface VernWithSuggestionsProps {
isNew?: boolean;
isDisabled?: boolean;
vernacular: string;
- vernInput?: React.RefObject;
+ vernInput?: RefObject;
updateVernField: (newValue: string, openDialog?: boolean) => void;
onBlur: () => void;
- onClose?: (e: React.SyntheticEvent, reason: AutocompleteCloseReason) => void;
+ onClose?: (e: SyntheticEvent, reason: AutocompleteCloseReason) => void;
suggestedVerns?: string[];
handleEnter: () => void;
vernacularLang: WritingSystem;
@@ -49,7 +55,7 @@ export default function VernWithSuggestions(
// onInputChange is triggered by typing
props.updateVernField(value);
}}
- onKeyPress={(e: React.KeyboardEvent) => {
+ onKeyPress={(e: KeyboardEvent) => {
if (e.key === Key.Enter) {
props.handleEnter();
}
diff --git a/src/components/DataEntry/DataEntryTable/EntryCellComponents/tests/GlossWithSuggestions.test.tsx b/src/components/DataEntry/DataEntryTable/EntryCellComponents/tests/GlossWithSuggestions.test.tsx
index 4321d474ff..4ccc637cd2 100644
--- a/src/components/DataEntry/DataEntryTable/EntryCellComponents/tests/GlossWithSuggestions.test.tsx
+++ b/src/components/DataEntry/DataEntryTable/EntryCellComponents/tests/GlossWithSuggestions.test.tsx
@@ -1,4 +1,4 @@
-import React from "react";
+import { createRef } from "react";
import renderer from "react-test-renderer";
import GlossWithSuggestions from "components/DataEntry/DataEntryTable/EntryCellComponents/GlossWithSuggestions";
@@ -20,7 +20,7 @@ describe("GlossWithSuggestions", () => {
renderer.create(
()}
+ glossInput={createRef()}
updateGlossField={jest.fn()}
handleEnter={jest.fn()}
analysisLang={newWritingSystem()}
@@ -36,7 +36,7 @@ describe("GlossWithSuggestions", () => {
()}
+ glossInput={createRef()}
updateGlossField={jest.fn()}
handleEnter={jest.fn()}
analysisLang={newWritingSystem()}
@@ -52,7 +52,7 @@ describe("GlossWithSuggestions", () => {
()}
+ glossInput={createRef()}
updateGlossField={jest.fn()}
handleEnter={jest.fn()}
analysisLang={newWritingSystem()}
diff --git a/src/components/DataEntry/DataEntryTable/EntryCellComponents/tests/VernWithSuggestions.test.tsx b/src/components/DataEntry/DataEntryTable/EntryCellComponents/tests/VernWithSuggestions.test.tsx
index 206a34f248..1e26613420 100644
--- a/src/components/DataEntry/DataEntryTable/EntryCellComponents/tests/VernWithSuggestions.test.tsx
+++ b/src/components/DataEntry/DataEntryTable/EntryCellComponents/tests/VernWithSuggestions.test.tsx
@@ -1,4 +1,4 @@
-import React from "react";
+import { createRef } from "react";
import renderer from "react-test-renderer";
import VernWithSuggestions from "components/DataEntry/DataEntryTable/EntryCellComponents/VernWithSuggestions";
@@ -20,7 +20,7 @@ describe("VernWithSuggestions", () => {
renderer.create(
()}
+ vernInput={createRef()}
updateVernField={jest.fn()}
handleEnter={jest.fn()}
onBlur={jest.fn()}
@@ -37,7 +37,7 @@ describe("VernWithSuggestions", () => {
()}
+ vernInput={createRef()}
updateVernField={jest.fn()}
handleEnter={jest.fn()}
onBlur={jest.fn()}
@@ -54,7 +54,7 @@ describe("VernWithSuggestions", () => {
()}
+ vernInput={createRef()}
updateVernField={jest.fn()}
handleEnter={jest.fn()}
onBlur={jest.fn()}
diff --git a/src/components/Dialogs/DeleteEditTextDialog.tsx b/src/components/Dialogs/DeleteEditTextDialog.tsx
index d50307e9f1..75e8aff4de 100644
--- a/src/components/Dialogs/DeleteEditTextDialog.tsx
+++ b/src/components/Dialogs/DeleteEditTextDialog.tsx
@@ -10,7 +10,7 @@ import {
TextField,
Tooltip,
} from "@mui/material";
-import React, { ReactElement, useState } from "react";
+import { type KeyboardEvent, type ReactElement, useState } from "react";
import { useTranslation } from "react-i18next";
import { Key } from "ts-key-enum";
@@ -69,7 +69,7 @@ export default function DeleteEditTextDialog(
}
}
- function confirmIfEnter(event: React.KeyboardEvent): void {
+ function confirmIfEnter(event: KeyboardEvent): void {
if (event.key === Key.Enter) {
onSave();
}
diff --git a/src/components/Dialogs/EditTextDialog.tsx b/src/components/Dialogs/EditTextDialog.tsx
index 8c53b8272f..0cbe009358 100644
--- a/src/components/Dialogs/EditTextDialog.tsx
+++ b/src/components/Dialogs/EditTextDialog.tsx
@@ -9,7 +9,12 @@ import {
InputAdornment,
TextField,
} from "@mui/material";
-import React, { ReactElement, useEffect, useState } from "react";
+import {
+ type KeyboardEvent,
+ type ReactElement,
+ useEffect,
+ useState,
+} from "react";
import { useTranslation } from "react-i18next";
import { Key } from "ts-key-enum";
@@ -60,7 +65,7 @@ export default function EditTextDialog(
}
}
- function confirmIfEnter(event: React.KeyboardEvent): void {
+ function confirmIfEnter(event: KeyboardEvent): void {
if (event.key === Key.Enter) {
onConfirm();
}
diff --git a/src/components/Dialogs/SubmitTextDialog.tsx b/src/components/Dialogs/SubmitTextDialog.tsx
index d5f959a361..c05cba57b1 100644
--- a/src/components/Dialogs/SubmitTextDialog.tsx
+++ b/src/components/Dialogs/SubmitTextDialog.tsx
@@ -9,7 +9,7 @@ import {
InputAdornment,
TextField,
} from "@mui/material";
-import React, { ReactElement, useState } from "react";
+import { type KeyboardEvent, type ReactElement, useState } from "react";
import { useTranslation } from "react-i18next";
import { Key } from "ts-key-enum";
@@ -54,7 +54,7 @@ export default function SubmitTextDialog(
}
}
- function confirmIfEnter(event: React.KeyboardEvent): void {
+ function confirmIfEnter(event: KeyboardEvent): void {
if (event.key === Key.Enter) {
onConfirm();
}
diff --git a/src/components/PasswordReset/ResetPage.tsx b/src/components/PasswordReset/ResetPage.tsx
index 5642fab48d..5e469fb18d 100644
--- a/src/components/PasswordReset/ResetPage.tsx
+++ b/src/components/PasswordReset/ResetPage.tsx
@@ -1,6 +1,12 @@
import ExitToAppIcon from "@mui/icons-material/ExitToApp";
import { Button, Card, Grid, TextField, Typography } from "@mui/material";
-import React, { ReactElement, useCallback, useEffect, useState } from "react";
+import {
+ type FormEvent,
+ type ReactElement,
+ useCallback,
+ useEffect,
+ useState,
+} from "react";
import { useTranslation } from "react-i18next";
import { useNavigate, useParams } from "react-router-dom";
@@ -49,12 +55,12 @@ export default function PasswordReset(): ReactElement {
validateLink();
});
- const backToLogin = (e: React.FormEvent): void => {
+ const backToLogin = (e: FormEvent): void => {
e.preventDefault();
navigate(Path.Login);
};
- const onSubmit = async (e: React.FormEvent): Promise => {
+ const onSubmit = async (e: FormEvent): Promise => {
if (token) {
setRequestState(RequestState.Attempt);
await asyncReset(token, password);
diff --git a/src/components/PasswordReset/tests/ResetPage.test.tsx b/src/components/PasswordReset/tests/ResetPage.test.tsx
index d271ec1061..b573829569 100644
--- a/src/components/PasswordReset/tests/ResetPage.test.tsx
+++ b/src/components/PasswordReset/tests/ResetPage.test.tsx
@@ -1,13 +1,13 @@
import "@testing-library/jest-dom";
import {
+ type RenderOptions,
act,
cleanup,
render,
- RenderOptions,
screen,
} from "@testing-library/react";
import userEvent from "@testing-library/user-event";
-import { ReactElement } from "react";
+import { type ReactElement, type ReactNode } from "react";
import { Provider } from "react-redux";
import { MemoryRouter, Route, Routes } from "react-router-dom";
import configureMockStore from "redux-mock-store";
@@ -42,7 +42,7 @@ afterEach(cleanup);
const ResetPageProviders = ({
children,
}: {
- children: React.ReactNode;
+ children: ReactNode;
}): ReactElement => {
return (
diff --git a/src/components/ProjectScreen/CreateProject.tsx b/src/components/ProjectScreen/CreateProject.tsx
index 8174810bae..848822df20 100644
--- a/src/components/ProjectScreen/CreateProject.tsx
+++ b/src/components/ProjectScreen/CreateProject.tsx
@@ -11,10 +11,16 @@ import {
Typography,
} from "@mui/material";
import { LanguagePicker, languagePickerStrings_en } from "mui-language-picker";
-import React, { Fragment, ReactElement, useState } from "react";
+import {
+ type ChangeEvent,
+ type FormEvent,
+ Fragment,
+ type ReactElement,
+ useState,
+} from "react";
import { Trans, useTranslation } from "react-i18next";
-import { WritingSystem } from "api/models";
+import { type WritingSystem } from "api/models";
import { projectDuplicateCheck, uploadLiftAndGetWritingSystems } from "backend";
import { FileInputButton, LoadingDoneButton } from "components/Buttons";
import {
@@ -90,9 +96,7 @@ export default function CreateProject(): ReactElement {
};
const updateName = (
- e: React.ChangeEvent<
- HTMLTextAreaElement | HTMLInputElement | HTMLSelectElement
- >
+ e: ChangeEvent
): void => {
const name = e.target.value;
setName(name);
@@ -153,9 +157,7 @@ export default function CreateProject(): ReactElement {
);
};
- const createProject = async (
- e: React.FormEvent
- ): Promise => {
+ const createProject = async (e: FormEvent): Promise => {
e.preventDefault();
if (success) {
return;
diff --git a/src/components/ProjectScreen/tests/CreateProject.test.tsx b/src/components/ProjectScreen/tests/CreateProject.test.tsx
index effdbe48f9..85b48cdd0c 100644
--- a/src/components/ProjectScreen/tests/CreateProject.test.tsx
+++ b/src/components/ProjectScreen/tests/CreateProject.test.tsx
@@ -1,8 +1,9 @@
import { LanguagePicker } from "mui-language-picker";
+import { type FormEvent } from "react";
import { Provider } from "react-redux";
import {
- ReactTestInstance,
- ReactTestRenderer,
+ type ReactTestInstance,
+ type ReactTestRenderer,
act,
create,
} from "react-test-renderer";
@@ -36,7 +37,7 @@ const mockChangeEvent = (
): { target: Partial } => ({
target: { value },
});
-const mockSubmitEvent = (): Partial> => ({
+const mockSubmitEvent = (): Partial> => ({
preventDefault: jest.fn(),
});
diff --git a/src/components/ProjectSettings/index.tsx b/src/components/ProjectSettings/index.tsx
index d5708a0aac..a62c92b87c 100644
--- a/src/components/ProjectSettings/index.tsx
+++ b/src/components/ProjectSettings/index.tsx
@@ -23,8 +23,9 @@ import {
Typography,
} from "@mui/material";
import {
- ReactElement,
- SyntheticEvent,
+ type ReactElement,
+ type ReactNode,
+ type SyntheticEvent,
useCallback,
useEffect,
useState,
@@ -33,7 +34,7 @@ import { useTranslation } from "react-i18next";
import { useNavigate } from "react-router-dom";
import { toast } from "react-toastify";
-import { Permission, Project } from "api/models";
+import { Permission, type Project } from "api/models";
import { canUploadLift, getCurrentPermissions } from "backend";
import BaseSettings from "components/BaseSettings";
import {
@@ -52,7 +53,7 @@ import ProjectSelect from "components/ProjectSettings/ProjectSelect";
import ActiveProjectUsers from "components/ProjectUsers/ActiveProjectUsers";
import AddProjectUsers from "components/ProjectUsers/AddProjectUsers";
import ProjectSpeakersList from "components/ProjectUsers/ProjectSpeakersList";
-import { StoreState } from "types";
+import { type StoreState } from "types";
import { useAppDispatch, useAppSelector } from "types/hooks";
import { Path } from "types/path";
@@ -422,7 +423,7 @@ function TabLabel(props: TabLabelProps): ReactElement {
}
interface TabPanelProps {
- children?: React.ReactNode;
+ children?: ReactNode;
index: ProjectSettingsTab;
value: ProjectSettingsTab;
}
diff --git a/src/components/Pronunciations/utilities.ts b/src/components/Pronunciations/utilities.ts
index 3f43592ff9..2315449a52 100644
--- a/src/components/Pronunciations/utilities.ts
+++ b/src/components/Pronunciations/utilities.ts
@@ -1,6 +1,6 @@
-import { Pronunciation } from "api";
+import { type Pronunciation } from "api/models";
import { uploadAudio } from "backend";
-import { FileWithSpeakerId } from "types/word";
+import { type FileWithSpeakerId } from "types/word";
/** Generate a timestamp-based file name for the given `wordId`. */
export function getFileNameForWord(wordId: string): string {
diff --git a/src/components/SiteSettings/index.tsx b/src/components/SiteSettings/index.tsx
index e268d8d951..91629d4b14 100644
--- a/src/components/SiteSettings/index.tsx
+++ b/src/components/SiteSettings/index.tsx
@@ -1,6 +1,11 @@
import { Announcement, List, People } from "@mui/icons-material";
import { Box, Grid, Tab, Tabs, Typography } from "@mui/material";
-import { ReactElement, SyntheticEvent, useState } from "react";
+import {
+ type ReactElement,
+ type ReactNode,
+ type SyntheticEvent,
+ useState,
+} from "react";
import { useTranslation } from "react-i18next";
import Banners from "components/SiteSettings/Banners";
@@ -74,7 +79,7 @@ export default function SiteSettings(): ReactElement {
}
interface TabPanelProps {
- children?: React.ReactNode;
+ children?: ReactNode;
index: SiteSettingsTab;
value: SiteSettingsTab;
}
diff --git a/src/components/Statistics/LineChartComponent.tsx b/src/components/Statistics/LineChartComponent.tsx
index e84931c442..72e28f1ff5 100644
--- a/src/components/Statistics/LineChartComponent.tsx
+++ b/src/components/Statistics/LineChartComponent.tsx
@@ -9,11 +9,11 @@ import {
PointElement,
} from "chart.js";
import distinctColors from "distinct-colors";
-import { ReactElement, useEffect, useState } from "react";
+import { type ReactElement, useEffect, useState } from "react";
import { Line } from "react-chartjs-2";
import { useTranslation } from "react-i18next";
-import { ChartRootData } from "api";
+import { type ChartRootData } from "api/models";
ChartJS.defaults.font.size = 18;
ChartJS.register(
diff --git a/src/components/Statistics/tests/DomainStatistics.test.tsx b/src/components/Statistics/tests/DomainStatistics.test.tsx
index c9d669eead..c6d495f028 100644
--- a/src/components/Statistics/tests/DomainStatistics.test.tsx
+++ b/src/components/Statistics/tests/DomainStatistics.test.tsx
@@ -1,7 +1,7 @@
import { ListItem } from "@mui/material";
-import { ReactTestRenderer, act, create } from "react-test-renderer";
+import { type ReactTestRenderer, act, create } from "react-test-renderer";
-import { SemanticDomainCount } from "api";
+import { SemanticDomainCount } from "api/models";
import DomainStatistics from "components/Statistics/DomainStatistics";
import {
newSemanticDomainCount,
diff --git a/src/components/Statistics/tests/UserStatistics.test.tsx b/src/components/Statistics/tests/UserStatistics.test.tsx
index 63bf2e96ea..ef65c5d6ad 100644
--- a/src/components/Statistics/tests/UserStatistics.test.tsx
+++ b/src/components/Statistics/tests/UserStatistics.test.tsx
@@ -1,7 +1,7 @@
import { ListItem } from "@mui/material";
-import { ReactTestRenderer, act, create } from "react-test-renderer";
+import { type ReactTestRenderer, act, create } from "react-test-renderer";
-import { SemanticDomainUserCount } from "api";
+import { SemanticDomainUserCount } from "api/models";
import UserStatistics from "components/Statistics/UserStatistics";
import { newSemanticDomainUserCount } from "types/semanticDomain";
diff --git a/src/components/TreeView/TreeDepiction/tests/index.test.tsx b/src/components/TreeView/TreeDepiction/tests/index.test.tsx
index 4840ae4c3c..7c400fb0a6 100644
--- a/src/components/TreeView/TreeDepiction/tests/index.test.tsx
+++ b/src/components/TreeView/TreeDepiction/tests/index.test.tsx
@@ -1,7 +1,7 @@
import { match } from "css-mediaquery";
-import { ReactTestRenderer, act, create } from "react-test-renderer";
+import { type ReactTestRenderer, act, create } from "react-test-renderer";
-import { SemanticDomainTreeNode } from "api";
+import { SemanticDomainTreeNode } from "api/models";
import TreeDepiction from "components/TreeView/TreeDepiction";
import testDomainMap, {
mapIds,
diff --git a/src/components/TreeView/TreeNavigator.tsx b/src/components/TreeView/TreeNavigator.tsx
index 2d623f2bb9..0c17b769d1 100644
--- a/src/components/TreeView/TreeNavigator.tsx
+++ b/src/components/TreeView/TreeNavigator.tsx
@@ -1,7 +1,7 @@
-import { Fragment, ReactElement, useEffect } from "react";
+import { Fragment, type ReactElement, useEffect } from "react";
import { Key } from "ts-key-enum";
-import { SemanticDomain, SemanticDomainTreeNode } from "api";
+import { type SemanticDomain, type SemanticDomainTreeNode } from "api/models";
export interface TreeNavigatorProps {
currentDomain: SemanticDomainTreeNode;
diff --git a/src/components/TreeView/TreeSearch.tsx b/src/components/TreeView/TreeSearch.tsx
index 34d8d6dd46..36f81598d3 100644
--- a/src/components/TreeView/TreeSearch.tsx
+++ b/src/components/TreeView/TreeSearch.tsx
@@ -1,9 +1,14 @@
import { Grid, TextField } from "@mui/material";
-import React, { ReactElement, useState } from "react";
+import {
+ type ChangeEvent,
+ type KeyboardEvent,
+ type ReactElement,
+ useState,
+} from "react";
import { useTranslation } from "react-i18next";
import { Key } from "ts-key-enum";
-import { SemanticDomainTreeNode } from "api";
+import { type SemanticDomainTreeNode } from "api/models";
import {
getSemanticDomainTreeNode,
getSemanticDomainTreeNodeByName,
@@ -21,7 +26,7 @@ export default function TreeSearch(props: TreeSearchProps): ReactElement {
const { input, handleChange, searchAndSelectDomain, searchError, setInput } =
useTreeSearch(props);
- const handleOnKeyUp = (event: React.KeyboardEvent): void => {
+ const handleOnKeyUp = (event: KeyboardEvent): void => {
event.bubbles = false;
if (event.key === Key.Enter) {
// Use onKeyUp so that this fires after onChange, to facilitate
@@ -73,8 +78,8 @@ export function insertDecimalPoints(value: string): string {
interface TreeSearchState {
input: string;
- handleChange: (event: React.ChangeEvent) => void;
- searchAndSelectDomain: (event: React.KeyboardEvent) => void;
+ handleChange: (event: ChangeEvent) => void;
+ searchAndSelectDomain: (event: KeyboardEvent) => void;
searchError: boolean;
setInput: (text: string) => void;
}
@@ -96,7 +101,7 @@ export function useTreeSearch(props: TreeSearchProps): TreeSearchState {
* for a new domain. */
function animateSuccessfulSearch(
domain: SemanticDomainTreeNode,
- event: React.KeyboardEvent
+ event: KeyboardEvent
): void {
props.animate(domain);
setInput("");
@@ -105,9 +110,7 @@ export function useTreeSearch(props: TreeSearchProps): TreeSearchState {
}
// Dispatch the search for a specified domain, and switches to it if it exists
- async function searchAndSelectDomain(
- event: React.KeyboardEvent
- ): Promise {
+ async function searchAndSelectDomain(event: KeyboardEvent): Promise {
// Search for domain
let domain: SemanticDomainTreeNode | undefined;
if (!isNaN(parseInt(input))) {
@@ -125,7 +128,7 @@ export function useTreeSearch(props: TreeSearchProps): TreeSearchState {
}
// Change the input on typing
- function handleChange(event: React.ChangeEvent): void {
+ function handleChange(event: ChangeEvent): void {
setInput(insertDecimalPoints(event.target.value));
// Reset the error dialogue when input is changes to avoid showing an error
// when a valid domain is entered, but Enter hasn't been pushed yet.
@@ -142,7 +145,7 @@ export function useTreeSearch(props: TreeSearchProps): TreeSearchState {
}
// Prevents keystrokes from reaching parent components; must be called onKeyDown
-function stopPropagation(event: React.KeyboardEvent): void {
+function stopPropagation(event: KeyboardEvent): void {
if (event.stopPropagation) {
event.stopPropagation();
}
diff --git a/src/components/TreeView/index.tsx b/src/components/TreeView/index.tsx
index 1527363fcf..c9cc8430c7 100644
--- a/src/components/TreeView/index.tsx
+++ b/src/components/TreeView/index.tsx
@@ -1,11 +1,11 @@
import { Close, KeyboardDoubleArrowUp } from "@mui/icons-material";
import { Grid, Zoom } from "@mui/material";
import { animate } from "motion";
-import { ReactElement, useCallback, useEffect, useState } from "react";
+import { type ReactElement, useCallback, useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { Key } from "ts-key-enum";
-import { SemanticDomain, WritingSystem } from "api";
+import { type SemanticDomain, type WritingSystem } from "api/models";
import { IconButtonWithTooltip } from "components/Buttons";
import {
initTreeDomain,
@@ -16,7 +16,7 @@ import { defaultTreeNode } from "components/TreeView/Redux/TreeViewReduxTypes";
import TreeDepiction from "components/TreeView/TreeDepiction";
import TreeNavigator from "components/TreeView/TreeNavigator";
import TreeSearch from "components/TreeView/TreeSearch";
-import { StoreState } from "types";
+import { type StoreState } from "types";
import { useAppDispatch, useAppSelector } from "types/hooks";
import { newSemanticDomain } from "types/semanticDomain";
import { semDomWritingSystems } from "types/writingSystem";
diff --git a/src/components/TreeView/tests/TreeSearch.test.tsx b/src/components/TreeView/tests/TreeSearch.test.tsx
index e9be06dc4b..f01baa692c 100644
--- a/src/components/TreeView/tests/TreeSearch.test.tsx
+++ b/src/components/TreeView/tests/TreeSearch.test.tsx
@@ -1,14 +1,14 @@
import { act, render, renderHook, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
-import React from "react";
+import { type ChangeEvent, type KeyboardEvent } from "react";
import { Key } from "ts-key-enum";
-import { SemanticDomainTreeNode } from "api";
+import { SemanticDomainTreeNode } from "api/models";
import * as backend from "backend";
import TreeSearch, {
+ type TreeSearchProps,
insertDecimalPoints,
testId,
- TreeSearchProps,
useTreeSearch,
} from "components/TreeView/TreeSearch";
import domMap, { mapIds } from "components/TreeView/tests/SemanticDomainMock";
@@ -45,11 +45,11 @@ describe("TreeSearch", () => {
// Simulate the user typing a string
const simulatedInput = {
target: { value: input },
- } as React.ChangeEvent;
+ } as ChangeEvent;
const keyboardTarget = new EventTarget();
// Simulate the user typing the enter key
- const simulatedEnterKey: Partial = {
+ const simulatedEnterKey: Partial = {
bubbles: true,
key: Key.Enter,
preventDefault: jest.fn(),
@@ -62,9 +62,7 @@ describe("TreeSearch", () => {
const { result } = renderHook(() => useTreeSearch(testProps));
act(() => result.current.handleChange(simulatedInput));
await act(async () =>
- result.current.searchAndSelectDomain(
- simulatedEnterKey as React.KeyboardEvent
- )
+ result.current.searchAndSelectDomain(simulatedEnterKey as KeyboardEvent)
);
}
diff --git a/src/components/WordCard/tests/SenseCard.test.tsx b/src/components/WordCard/tests/SenseCard.test.tsx
index ef34c3db80..6f3bf9bbe7 100644
--- a/src/components/WordCard/tests/SenseCard.test.tsx
+++ b/src/components/WordCard/tests/SenseCard.test.tsx
@@ -2,7 +2,7 @@ import { Provider } from "react-redux";
import { type ReactTestRenderer, act, create } from "react-test-renderer";
import configureMockStore from "redux-mock-store";
-import { GramCatGroup, type Sense } from "api";
+import { GramCatGroup, type Sense } from "api/models";
import { PartOfSpeechButton } from "components/Buttons";
import { defaultState } from "components/Project/ProjectReduxTypes";
import DomainChip from "components/WordCard/DomainChip";
diff --git a/src/types/theme.ts b/src/types/theme.ts
index dbc574d677..004b866e65 100644
--- a/src/types/theme.ts
+++ b/src/types/theme.ts
@@ -1,9 +1,10 @@
import { blue, green, grey, orange, red, yellow } from "@mui/material/colors";
import {
+ type PaletteOptions,
createTheme,
responsiveFontSizes,
- PaletteOptions,
} from "@mui/material/styles";
+import { type CSSProperties } from "react";
export type HEX = `#${string}`;
@@ -33,7 +34,7 @@ const palette: PaletteOptions = {
tonalOffset: 0.2,
};
-const fontFamily: React.CSSProperties["fontFamily"] = [
+const fontFamily: CSSProperties["fontFamily"] = [
"'Noto Sans'",
"'Open Sans'",
"Roboto",
diff --git a/src/utilities/fontComponents.tsx b/src/utilities/fontComponents.tsx
index a59f8e0924..c2377bbe2a 100644
--- a/src/utilities/fontComponents.tsx
+++ b/src/utilities/fontComponents.tsx
@@ -1,12 +1,17 @@
import {
TextField,
- TextFieldProps,
+ type TextFieldProps,
Typography,
- TypographyProps,
+ type TypographyProps,
} from "@mui/material";
-import { ReactElement, useContext } from "react";
+import {
+ type DetailedHTMLProps,
+ type LiHTMLAttributes,
+ type ReactElement,
+ useContext,
+} from "react";
-import FontContext, { WithFontProps } from "utilities/fontContext";
+import FontContext, { type WithFontProps } from "utilities/fontContext";
/* Various MUI components for use within a FontContext
* to add the appropriate font to that component. */
@@ -65,8 +70,8 @@ export function TypographyWithFont(
);
}
-type LiWithFontProps = React.DetailedHTMLProps<
- React.LiHTMLAttributes,
+type LiWithFontProps = DetailedHTMLProps<
+ LiHTMLAttributes,
HTMLLIElement
> &
WithFontProps;
diff --git a/src/utilities/fontCssUtilities.ts b/src/utilities/fontCssUtilities.ts
index 85d71b7c9f..798c8c27bf 100644
--- a/src/utilities/fontCssUtilities.ts
+++ b/src/utilities/fontCssUtilities.ts
@@ -1,5 +1,5 @@
-import { Project } from "api";
-import { Hash } from "types/hash";
+import { type Project } from "api/models";
+import { type Hash } from "types/hash";
import { RuntimeConfig } from "types/runtimeConfig";
const fontDir = "/fonts";
diff --git a/src/utilities/tests/fontCssUtilities.test.ts b/src/utilities/tests/fontCssUtilities.test.ts
index a6155690cf..29c5dd8262 100644
--- a/src/utilities/tests/fontCssUtilities.test.ts
+++ b/src/utilities/tests/fontCssUtilities.test.ts
@@ -1,4 +1,4 @@
-import { Project } from "api";
+import { type Project } from "api/models";
import { newWritingSystem } from "types/writingSystem";
import { fetchCss, getCss, getProjCss } from "utilities/fontCssUtilities";