Make InitialFocus component generic (#5008)

* Make InitialFocus component generic

* Fix lint error
This commit is contained in:
Joel Jeremy Marquez
2025-05-16 15:02:06 -07:00
committed by GitHub
parent 351e252129
commit b5ece8e221
3 changed files with 23 additions and 19 deletions

View File

@@ -8,15 +8,11 @@ import {
useRef,
} from 'react';
type FocusableElement = HTMLElement;
type InitialFocusProps = {
type InitialFocusProps<T extends HTMLElement> = {
/**
* The child element to focus when the component mounts. This can be either a single React element or a function that returns a React element.
*/
children:
| ReactElement<{ ref: Ref<HTMLElement> }>
| ((node: Ref<HTMLElement>) => ReactElement);
children: ReactElement<{ ref: Ref<T> }> | ((ref: Ref<T>) => ReactElement);
};
/**
@@ -25,22 +21,24 @@ type InitialFocusProps = {
* @param {Object} props - The component props.
* @param {ReactElement | function} props.children - A single React element or a function that returns a React element.
*/
export function InitialFocus({ children }: InitialFocusProps) {
const node = useRef<FocusableElement>(null);
export function InitialFocus<T extends HTMLElement = HTMLElement>({
children,
}: InitialFocusProps<T>) {
const ref = useRef<T | null>(null);
useEffect(() => {
if (node.current) {
if (ref.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.
setTimeout(() => {
if (node.current) {
node.current.focus();
if (ref.current) {
ref.current.focus();
if (
node.current instanceof HTMLInputElement ||
node.current instanceof HTMLTextAreaElement
ref.current instanceof HTMLInputElement ||
ref.current instanceof HTMLTextAreaElement
) {
node.current.setSelectionRange(0, 10000);
ref.current.setSelectionRange(0, 10000);
}
}
}, 0);
@@ -48,12 +46,12 @@ export function InitialFocus({ children }: InitialFocusProps) {
}, []);
if (typeof children === 'function') {
return children(node);
return children(ref);
}
const child = Children.only(children);
if (isValidElement(child)) {
return cloneElement(child, { ref: node });
return cloneElement(child, { ref });
}
throw new Error(
'InitialFocus expects a single valid React element as its child.',

View File

@@ -1,4 +1,4 @@
import React, { type Ref, useMemo, useState } from 'react';
import React, { useMemo, useState } from 'react';
import { Trans, useTranslation } from 'react-i18next';
import { Button } from '@actual-app/components/button';
@@ -54,7 +54,7 @@ export function CoverMenu({
<Trans>Cover from a category:</Trans>
</View>
<InitialFocus>
<InitialFocus<HTMLInputElement>>
{node => (
<CategoryAutocomplete
categoryGroups={filteredCategoryGroups}
@@ -62,7 +62,7 @@ export function CoverMenu({
openOnFocus={true}
onSelect={(id: string | undefined) => setFromCategoryId(id || null)}
inputProps={{
inputRef: node as Ref<HTMLInputElement>,
inputRef: node,
onEnter: event => !event.defaultPrevented && submit(),
placeholder: t('(none)'),
}}

View File

@@ -0,0 +1,6 @@
---
category: Maintenance
authors: [joel-jeremy]
---
Make InitialFocus component generic