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";