diff --git a/packages/desktop-client/src/components/common/AlignedText.tsx b/packages/desktop-client/src/components/common/AlignedText.tsx index dbd5c690a1d3b6a8d6d1f7cddf33ea1d24a1e986..79b055265d83eec46eee911c44886a669907c3f3 100644 --- a/packages/desktop-client/src/components/common/AlignedText.tsx +++ b/packages/desktop-client/src/components/common/AlignedText.tsx @@ -1,5 +1,4 @@ -// @ts-strict-ignore -import { type ComponentProps } from 'react'; +import { type ComponentProps, type ReactNode } from 'react'; import { type CSSProperties } from '../../style'; @@ -7,8 +6,8 @@ import { Block } from './Block'; import { View } from './View'; type AlignedTextProps = ComponentProps<typeof View> & { - left; - right; + left: ReactNode; + right: ReactNode; style?: CSSProperties; leftStyle?: CSSProperties; rightStyle?: CSSProperties; diff --git a/packages/desktop-client/src/components/common/Button.tsx b/packages/desktop-client/src/components/common/Button.tsx index 3158d0b2b182cf6e5506427d3a028b466301c283..1c738eb7fd5d6fa2d5ab0e95c392e268f2452404 100644 --- a/packages/desktop-client/src/components/common/Button.tsx +++ b/packages/desktop-client/src/components/common/Button.tsx @@ -1,4 +1,3 @@ -// @ts-strict-ignore import React, { forwardRef, type ElementType, type HTMLProps } from 'react'; import { css } from 'glamor'; @@ -31,7 +30,9 @@ type ButtonType = | 'menu' | 'menuSelected'; -const backgroundColor = { +const backgroundColor: { + [key in ButtonType | `${ButtonType}Disabled`]?: string; +} = { normal: theme.buttonNormalBackground, normalDisabled: theme.buttonNormalDisabledBackground, primary: theme.buttonPrimaryBackground, @@ -43,7 +44,7 @@ const backgroundColor = { link: theme.buttonBareBackground, }; -const backgroundColorHover = { +const backgroundColorHover: Record<ButtonType, string> = { normal: theme.buttonNormalBackgroundHover, primary: theme.buttonPrimaryBackgroundHover, bare: theme.buttonBareBackgroundHover, @@ -52,7 +53,9 @@ const backgroundColorHover = { link: theme.buttonBareBackground, }; -const borderColor = { +const borderColor: { + [key in ButtonType | `${ButtonType}Disabled`]?: string; +} = { normal: theme.buttonNormalBorder, normalDisabled: theme.buttonNormalDisabledBorder, primary: theme.buttonPrimaryBorder, @@ -62,7 +65,9 @@ const borderColor = { link: theme.buttonBareBackground, }; -const textColor = { +const textColor: { + [key in ButtonType | `${ButtonType}Disabled`]?: string; +} = { normal: theme.buttonNormalText, normalDisabled: theme.buttonNormalDisabledText, primary: theme.buttonPrimaryText, @@ -74,7 +79,9 @@ const textColor = { link: theme.pageTextLink, }; -const textColorHover = { +const textColorHover: { + [key in ButtonType]?: string; +} = { normal: theme.buttonNormalTextHover, primary: theme.buttonPrimaryTextHover, bare: theme.buttonBareTextHover, @@ -87,7 +94,10 @@ const linkButtonHoverStyles = { boxShadow: 'none', }; -const _getBorder = (type, typeWithDisabled) => { +const _getBorder = ( + type: ButtonType, + typeWithDisabled: keyof typeof borderColor, +): string => { switch (type) { case 'bare': case 'link': @@ -98,7 +108,7 @@ const _getBorder = (type, typeWithDisabled) => { } }; -const _getPadding = type => { +const _getPadding = (type: ButtonType): string => { switch (type) { case 'bare': return '5px'; @@ -109,7 +119,7 @@ const _getPadding = type => { } }; -const _getActiveStyles = (type, bounce) => { +const _getActiveStyles = (type: ButtonType, bounce: boolean): CSSProperties => { switch (type) { case 'bare': return { backgroundColor: theme.buttonBareBackgroundActive }; @@ -120,7 +130,7 @@ const _getActiveStyles = (type, bounce) => { }; default: return { - transform: bounce && 'translateY(1px)', + transform: bounce ? 'translateY(1px)' : undefined, boxShadow: `0 1px 4px 0 ${ type === 'primary' ? theme.buttonPrimaryShadow @@ -150,7 +160,9 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>( }, ref, ) => { - const typeWithDisabled = disabled ? type + 'Disabled' : type; + const typeWithDisabled: ButtonType | `${ButtonType}Disabled` = disabled + ? `${type}Disabled` + : type; hoveredStyle = { ...(type !== 'bare' && styles.shadow), diff --git a/packages/desktop-client/src/components/common/InitialFocus.ts b/packages/desktop-client/src/components/common/InitialFocus.ts index c00eebd1fb5092d6fa3d29319ba14c346271332e..c7ee965b08ea66491fc3860eb5ae2cd7fc87c482 100644 --- a/packages/desktop-client/src/components/common/InitialFocus.ts +++ b/packages/desktop-client/src/components/common/InitialFocus.ts @@ -1,4 +1,3 @@ -// @ts-strict-ignore import { type ReactElement, type Ref, @@ -8,14 +7,16 @@ import { } from 'react'; type InitialFocusProps = { - children?: ReactElement | ((node: Ref<HTMLInputElement>) => ReactElement); + children: + | ReactElement<{ inputRef: Ref<HTMLInputElement> }> + | ((node: Ref<HTMLInputElement>) => ReactElement); }; export function InitialFocus({ children }: InitialFocusProps) { - const node = useRef(null); + const node = useRef<HTMLInputElement>(null); useEffect(() => { - if (node.current && !global.IS_DESIGN_MODE) { + if (node.current) { // This is needed to avoid a strange interaction with // `ScopeTab`, which doesn't allow it to be focused at first for // some reason. Need to look into it. diff --git a/packages/desktop-client/src/components/common/Input.tsx b/packages/desktop-client/src/components/common/Input.tsx index 7b24ccf45a76fca18dba27e11a9d2cb91e98987c..2be2f7d6b1c7ae3a3bdd0a06e3fca43bf7417484 100644 --- a/packages/desktop-client/src/components/common/Input.tsx +++ b/packages/desktop-client/src/components/common/Input.tsx @@ -1,9 +1,8 @@ -// @ts-strict-ignore import React, { - useRef, + type InputHTMLAttributes, type KeyboardEvent, type Ref, - type InputHTMLAttributes, + useRef, } from 'react'; import mergeRefs from 'react-merge-refs'; @@ -42,7 +41,7 @@ export function Input({ focused, ...nativeProps }: InputProps) { - const ref = useRef(); + const ref = useRef<HTMLInputElement>(null); useProperFocus(ref, focused); return ( diff --git a/packages/desktop-client/src/components/common/InputWithContent.tsx b/packages/desktop-client/src/components/common/InputWithContent.tsx index 7f59fa3fba1cf272475e4689eb7c5b9ac92b4409..ca8007f9b5369f1456e0e005b53eb40245853330 100644 --- a/packages/desktop-client/src/components/common/InputWithContent.tsx +++ b/packages/desktop-client/src/components/common/InputWithContent.tsx @@ -1,4 +1,3 @@ -// @ts-strict-ignore import { useState, type ComponentProps, type ReactNode } from 'react'; import { type CSSProperties, theme } from '../../style'; @@ -23,7 +22,7 @@ export function InputWithContent({ getStyle, ...props }: InputWithContentProps) { - const [focused, setFocused] = useState(props.focused); + const [focused, setFocused] = useState(props.focused ?? false); return ( <View @@ -37,7 +36,7 @@ export function InputWithContent({ (focusStyle ?? { boxShadow: '0 0 0 1px ' + theme.formInputShadowSelected, })), - ...(getStyle && getStyle(focused)), + ...getStyle?.(focused), }} > {leftContent} diff --git a/packages/desktop-client/src/components/common/Link.tsx b/packages/desktop-client/src/components/common/Link.tsx index 4a9410f30ca1927fcf0927db1fec38f74c0792cb..91364f1c10738050035e520f1d71425168655960 100644 --- a/packages/desktop-client/src/components/common/Link.tsx +++ b/packages/desktop-client/src/components/common/Link.tsx @@ -1,5 +1,8 @@ -// @ts-strict-ignore -import React, { type ReactNode, type ComponentProps } from 'react'; +import React, { + type ComponentProps, + type MouseEvent, + type ReactNode, +} from 'react'; import { NavLink, useMatch } from 'react-router-dom'; import { css } from 'glamor'; @@ -31,7 +34,7 @@ const ButtonLink = ({ const navigate = useNavigate(); const match = useMatch({ path: to }); - const handleClick = e => { + const handleClick = (e: MouseEvent<HTMLButtonElement>) => { onClick?.(e); navigate(to); }; diff --git a/packages/desktop-client/src/components/common/Menu.tsx b/packages/desktop-client/src/components/common/Menu.tsx index 4577a275648b11cd57925b4c8e3a78d86d7ec38e..f7b913349a4199cb65f78fc7d173cda95752a09c 100644 --- a/packages/desktop-client/src/components/common/Menu.tsx +++ b/packages/desktop-client/src/components/common/Menu.tsx @@ -1,5 +1,6 @@ -// @ts-strict-ignore import { + type FunctionComponent, + type ReactElement, type ReactNode, createElement, useEffect, @@ -13,6 +14,10 @@ import { Text } from './Text'; import { Toggle } from './Toggle'; import { View } from './View'; +const MenuLine: unique symbol = Symbol('menu-line'); +Menu.line = MenuLine; +Menu.label = Symbol('menu-label'); + type KeybindingProps = { keyName: ReactNode; }; @@ -29,7 +34,12 @@ type MenuItem = { type?: string | symbol; name: string; disabled?: boolean; - icon?; + // eslint-disable-next-line @typescript-eslint/ban-types + icon?: FunctionComponent<{ + width: number; + height: number; + style: CSSProperties; + }>; iconSize?: number; text: string; key?: string; @@ -53,21 +63,21 @@ export function Menu<T extends MenuItem>({ onMenuSelect, style, }: MenuProps<T>) { - const elRef = useRef(null); + const elRef = useRef<HTMLDivElement>(null); const items = allItems.filter(x => x); - const [hoveredIndex, setHoveredIndex] = useState(null); + const [hoveredIndex, setHoveredIndex] = useState<number | null>(null); useEffect(() => { const el = elRef.current; - el.focus(); + el?.focus(); - const onKeyDown = e => { + const onKeyDown = (e: KeyboardEvent) => { const filteredItems = items.filter( item => item && item !== Menu.line && item.type !== Menu.label, ); - const currentIndex = filteredItems.indexOf(items[hoveredIndex]); + const currentIndex = filteredItems.indexOf(items[hoveredIndex || 0]); - const transformIndex = idx => items.indexOf(filteredItems[idx]); + const transformIndex = (idx: number) => items.indexOf(filteredItems[idx]); switch (e.key) { case 'ArrowUp': @@ -90,7 +100,7 @@ export function Menu<T extends MenuItem>({ break; case 'Enter': e.preventDefault(); - const item = items[hoveredIndex]; + const item = items[hoveredIndex || 0]; if (hoveredIndex !== null && item !== Menu.line) { onMenuSelect?.(item.name); } @@ -99,10 +109,10 @@ export function Menu<T extends MenuItem>({ } }; - el.addEventListener('keydown', onKeyDown); + el?.addEventListener('keydown', onKeyDown); return () => { - el.removeEventListener('keydown', onKeyDown); + el?.removeEventListener('keydown', onKeyDown); }; }, [hoveredIndex]); @@ -203,6 +213,7 @@ export function Menu<T extends MenuItem>({ style={{ marginLeft: 5, ...item.style }} onToggle={() => !item.disabled && + onMenuSelect && item.toggle !== undefined && onMenuSelect(item.name) } @@ -217,7 +228,3 @@ export function Menu<T extends MenuItem>({ </View> ); } - -const MenuLine: unique symbol = Symbol('menu-line'); -Menu.line = MenuLine; -Menu.label = Symbol('menu-label'); diff --git a/packages/desktop-client/src/components/common/MenuTooltip.tsx b/packages/desktop-client/src/components/common/MenuTooltip.tsx index a9d7f42be236542c9c90d653d1bc876fe007c361..2b255d77fd70805979be2e78900d69e48b646d3b 100644 --- a/packages/desktop-client/src/components/common/MenuTooltip.tsx +++ b/packages/desktop-client/src/components/common/MenuTooltip.tsx @@ -1,9 +1,14 @@ -// @ts-strict-ignore -import React from 'react'; +import React, { type ReactNode } from 'react'; import { Tooltip } from '../tooltips'; -export function MenuTooltip({ width, onClose, children }) { +type MenuTooltipProps = { + width: number; + onClose: () => void; + children: ReactNode; +}; + +export function MenuTooltip({ width, onClose, children }: MenuTooltipProps) { return ( <Tooltip position="bottom-right" diff --git a/packages/desktop-client/src/components/common/Modal.tsx b/packages/desktop-client/src/components/common/Modal.tsx index 61c7026584fa893030029bb24ffea8f180f6daeb..13a7e32085259d2e410cc131c999df27d7394c5c 100644 --- a/packages/desktop-client/src/components/common/Modal.tsx +++ b/packages/desktop-client/src/components/common/Modal.tsx @@ -103,7 +103,7 @@ export const Modal = ({ isOpen={true} onRequestClose={onClose} shouldCloseOnOverlayClick={true} - shouldFocusAfterRender={!global.IS_DESIGN_MODE} + shouldFocusAfterRender shouldReturnFocusAfterClose={focusAfterClose} appElement={document.querySelector('#root') as HTMLElement} parentSelector={parent && (() => parent)} diff --git a/packages/desktop-client/src/components/common/Search.tsx b/packages/desktop-client/src/components/common/Search.tsx index 1c7af09102b909a13684a7396ad68b7c2ea1bd4d..1cf98e8bf057ea75af9776b5a668890f5f970071 100644 --- a/packages/desktop-client/src/components/common/Search.tsx +++ b/packages/desktop-client/src/components/common/Search.tsx @@ -1,4 +1,3 @@ -// @ts-strict-ignore import { type Ref } from 'react'; import { SvgRemove, SvgSearchAlternate } from '../../icons/v2'; @@ -10,7 +9,7 @@ import { InputWithContent } from './InputWithContent'; type SearchProps = { inputRef?: Ref<HTMLInputElement>; value: string; - onChange: (value: string) => unknown; + onChange: (value: string) => void; placeholder: string; isInModal?: boolean; width?: number; @@ -30,12 +29,12 @@ export function Search({ style={{ width, flex: '', - borderColor: isInModal ? null : 'transparent', - backgroundColor: isInModal ? null : theme.formInputBackground, + borderColor: isInModal ? undefined : 'transparent', + backgroundColor: isInModal ? undefined : theme.formInputBackground, }} focusStyle={ isInModal - ? null + ? undefined : { boxShadow: '0 0 0 1px ' + theme.formInputShadowSelected, backgroundColor: theme.formInputBackgroundSelected, diff --git a/packages/desktop-client/src/components/util/AmountInput.tsx b/packages/desktop-client/src/components/util/AmountInput.tsx index e968423e3d818055f1cc2371aeb100ef9a4cf87e..c0831412438e102b48d6cb43a91229b03580bbcc 100644 --- a/packages/desktop-client/src/components/util/AmountInput.tsx +++ b/packages/desktop-client/src/components/util/AmountInput.tsx @@ -55,7 +55,7 @@ export function AmountInput({ useEffect(() => setValue(initialValueAbsolute), [initialValueAbsolute]); const buttonRef = useRef(); - const ref = useRef<HTMLInputElement>(); + const ref = useRef<HTMLInputElement>(null); const mergedRef = useMergedRefs<HTMLInputElement>(inputRef, ref); useEffect(() => { diff --git a/packages/desktop-client/src/hooks/useMergedRefs.ts b/packages/desktop-client/src/hooks/useMergedRefs.ts index e9b18a98c6610cbebf2fea5c8840760aad980919..a6561b6cb49d73f49487832c768930b0a57d3693 100644 --- a/packages/desktop-client/src/hooks/useMergedRefs.ts +++ b/packages/desktop-client/src/hooks/useMergedRefs.ts @@ -1,4 +1,3 @@ -// @ts-strict-ignore import { useMemo } from 'react'; import type { MutableRefObject, Ref, RefCallback } from 'react'; @@ -7,7 +6,7 @@ export function useMergedRefs<T>( ref2: RefCallback<T> | MutableRefObject<T>, ): Ref<T> { return useMemo(() => { - function ref(value) { + function ref(value: T) { [ref1, ref2].forEach(ref => { if (typeof ref === 'function') { ref(value); diff --git a/packages/desktop-client/src/hooks/useProperFocus.tsx b/packages/desktop-client/src/hooks/useProperFocus.tsx index abec12fadcb03956e182a6c7ce6f24097de5a037..dba487bdce50e9c28459f7ce0691d72827a798ab 100644 --- a/packages/desktop-client/src/hooks/useProperFocus.tsx +++ b/packages/desktop-client/src/hooks/useProperFocus.tsx @@ -77,7 +77,7 @@ export function AvoidRefocusScrollProvider({ export function useProperFocus( ref: RefObject<HTMLElement>, - shouldFocus: boolean, + shouldFocus = false, ): void { const context = useContext(AvoidRefocusScrollContext); const prevShouldFocus = useRef(null); diff --git a/upcoming-release-notes/2481.md b/upcoming-release-notes/2481.md new file mode 100644 index 0000000000000000000000000000000000000000..8d9212b738ddd0055c9404392dfc1b71cbb65e3d --- /dev/null +++ b/upcoming-release-notes/2481.md @@ -0,0 +1,6 @@ +--- +category: Maintenance +authors: [MatissJanis] +--- + +Convert most common components to strict TypeScript