fix(deps): update dependency eslint-plugin-react-hooks to v5 (#1280)

* fix(deps): update dependency eslint-plugin-react-hooks to v5

* fix: lint issues after reenabling hook rules

* fix: format issues

---------

Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
Co-authored-by: Meier Lukas <meierschlumpf@gmail.com>
This commit is contained in:
homarr-renovate[bot]
2024-10-16 21:43:51 +02:00
committed by GitHub
parent ea43ed0ca4
commit a87c937b69
39 changed files with 251 additions and 224 deletions

View File

@@ -49,6 +49,7 @@ export const BoardProvider = ({
useEffect(() => { useEffect(() => {
setReadySections((previous) => previous.filter((id) => data.sections.some((section) => section.id === id))); setReadySections((previous) => previous.filter((id) => data.sections.some((section) => section.id === id)));
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [data.sections.length, setReadySections]); }, [data.sections.length, setReadySections]);
const markAsReady = useCallback((id: string) => { const markAsReady = useCallback((id: string) => {

View File

@@ -47,7 +47,7 @@ export const ApiKeysManagement = ({ apiKeys }: ApiKeysManagementProps) => {
), ),
}, },
], ],
[], [t],
); );
const table = useMantineReactTable({ const table = useMantineReactTable({

View File

@@ -61,7 +61,7 @@ export const UserProfileForm = ({ user }: UserProfileFormProps) => {
id: user.id, id: user.id,
}); });
}, },
[user.id, mutate], [isProviderCredentials, mutate, user.id],
); );
return ( return (

View File

@@ -8,7 +8,6 @@ import { useI18n, useScopedI18n } from "@homarr/translation/client";
import { z } from "@homarr/validation"; import { z } from "@homarr/validation";
import type { Item } from "~/app/[locale]/boards/_types"; import type { Item } from "~/app/[locale]/boards/_types";
import { useItemActions } from "./item-actions";
interface InnerProps { interface InnerProps {
gridStack: GridStack; gridStack: GridStack;
@@ -21,7 +20,6 @@ export const ItemMoveModal = createModal<InnerProps>(({ actions, innerProps }) =
const t = useI18n(); const t = useI18n();
// Keep track of the maximum width based on the x offset // Keep track of the maximum width based on the x offset
const maxWidthRef = useRef(innerProps.columnCount - innerProps.item.xOffset); const maxWidthRef = useRef(innerProps.columnCount - innerProps.item.xOffset);
const { moveAndResizeItem } = useItemActions();
const form = useZodForm( const form = useZodForm(
z.object({ z.object({
xOffset: z xOffset: z
@@ -62,7 +60,7 @@ export const ItemMoveModal = createModal<InnerProps>(({ actions, innerProps }) =
}); });
actions.closeModal(); actions.closeModal();
}, },
[moveAndResizeItem], [actions, innerProps.gridStack, innerProps.item.id],
); );
return ( return (

View File

@@ -39,7 +39,7 @@ export const GridStackItem = ({
if (type !== "section") return; if (type !== "section") return;
innerRef.current.gridstackNode.minW = minWidth; innerRef.current.gridstackNode.minW = minWidth;
innerRef.current.gridstackNode.minH = minHeight; innerRef.current.gridstackNode.minH = minHeight;
}, [minWidth, minHeight, innerRef]); }, [minWidth, minHeight, innerRef, type]);
return ( return (
<Box <Box

View File

@@ -215,6 +215,7 @@ export const useGridstack = (section: Omit<Section, "items">, itemIds: string[])
} }
// Only run this effect when the section items change // Only run this effect when the section items change
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [itemIds.length, columnCount]); }, [itemIds.length, columnCount]);
/** /**

View File

@@ -58,7 +58,7 @@ export const UserAvatarMenu = ({ children }: UserAvatarMenuProps) => {
router.refresh(); router.refresh();
}, },
}); });
}, [openModal, router]); }, [logoutUrl, openModal, router]);
return ( return (
<Menu width={300} withArrow withinPortal> <Menu width={300} withArrow withinPortal>

View File

@@ -53,7 +53,7 @@ export const ConfirmModal = createModal<Omit<ConfirmModalProps, "title">>(({ act
actions.closeModal(); actions.closeModal();
} }
}, },
[cancelProps?.onClick, onCancel, actions.closeModal], [cancelProps, onCancel, closeOnCancel, actions],
); );
const handleConfirm = useCallback( const handleConfirm = useCallback(
@@ -73,7 +73,7 @@ export const ConfirmModal = createModal<Omit<ConfirmModalProps, "title">>(({ act
} }
setLoading(false); setLoading(false);
}, },
[confirmProps?.onClick, onConfirm, actions.closeModal], [confirmProps, onConfirm, closeOnConfirm, actions],
); );
return ( return (

View File

@@ -38,7 +38,7 @@ export const ModalProvider = ({ children }: PropsWithChildren) => {
(id: string, canceled?: boolean) => { (id: string, canceled?: boolean) => {
dispatch({ type: "CLOSE", modalId: id, canceled }); dispatch({ type: "CLOSE", modalId: id, canceled });
}, },
[stateRef, dispatch], [dispatch],
); );
const openModalInner: ModalContextProps["openModalInner"] = useCallback( const openModalInner: ModalContextProps["openModalInner"] = useCallback(
@@ -63,10 +63,7 @@ export const ModalProvider = ({ children }: PropsWithChildren) => {
[dispatch], [dispatch],
); );
const handleCloseModal = useCallback( const handleCloseModal = useCallback(() => state.current && closeModal(state.current.id), [closeModal, state]);
() => state.current && closeModal(state.current.id),
[closeModal, state.current?.id],
);
const activeModals = state.modals.filter((modal) => modal.id === state.current?.id || modal.props.keepMounted); const activeModals = state.modals.filter((modal) => modal.id === state.current?.id || modal.props.keepMounted);

View File

@@ -24,7 +24,7 @@ export const ChildrenActionItem = ({ childrenOptions, action, query }: ChildrenA
return ( return (
<Spotlight.Action renderRoot={renderRoot} onClick={onClick} className={classes.spotlightAction}> <Spotlight.Action renderRoot={renderRoot} onClick={onClick} className={classes.spotlightAction}>
<action.component {...childrenOptions.option} /> <action.Component {...childrenOptions.option} />
</Spotlight.Action> </Spotlight.Action>
); );
}; };

View File

@@ -48,7 +48,7 @@ export const SpotlightGroupActionItem = <TOption extends Record<string, unknown>
closeSpotlightOnTrigger={interaction.type !== "mode" && interaction.type !== "children"} closeSpotlightOnTrigger={interaction.type !== "mode" && interaction.type !== "children"}
className={classes.spotlightAction} className={classes.spotlightAction}
> >
<group.component {...option} /> <group.Component {...option} />
</Spotlight.Action> </Spotlight.Action>
); );
}; };

View File

@@ -92,7 +92,7 @@ export const Spotlight = () => {
{childrenOptions ? ( {childrenOptions ? (
<Group> <Group>
<childrenOptions.detailComponent options={childrenOptions.option as never} /> <childrenOptions.DetailComponent options={childrenOptions.option as never} />
</Group> </Group>
) : null} ) : null}

View File

@@ -3,13 +3,13 @@ import type { ReactNode } from "react";
import type { inferSearchInteractionDefinition } from "./interaction"; import type { inferSearchInteractionDefinition } from "./interaction";
export interface CreateChildrenOptionsProps<TParentOptions extends Record<string, unknown>> { export interface CreateChildrenOptionsProps<TParentOptions extends Record<string, unknown>> {
detailComponent: ({ options }: { options: TParentOptions }) => ReactNode; DetailComponent: ({ options }: { options: TParentOptions }) => ReactNode;
useActions: (options: TParentOptions, query: string) => ChildrenAction<TParentOptions>[]; useActions: (options: TParentOptions, query: string) => ChildrenAction<TParentOptions>[];
} }
export interface ChildrenAction<TParentOptions extends Record<string, unknown>> { export interface ChildrenAction<TParentOptions extends Record<string, unknown>> {
key: string; key: string;
component: (option: TParentOptions) => JSX.Element; Component: (option: TParentOptions) => JSX.Element;
useInteraction: (option: TParentOptions, query: string) => inferSearchInteractionDefinition<"link" | "javaScript">; useInteraction: (option: TParentOptions, query: string) => inferSearchInteractionDefinition<"link" | "javaScript">;
hide?: boolean | ((option: TParentOptions) => boolean); hide?: boolean | ((option: TParentOptions) => boolean);
} }

View File

@@ -8,7 +8,7 @@ type CommonSearchGroup<TOption extends Record<string, unknown>, TOptionProps ext
// key path is used to define the path to a unique key in the option object // key path is used to define the path to a unique key in the option object
keyPath: keyof TOption; keyPath: keyof TOption;
title: stringOrTranslation; title: stringOrTranslation;
component: (option: TOption) => JSX.Element; Component: (option: TOption) => JSX.Element;
useInteraction: (option: TOption, query: string) => inferSearchInteractionDefinition<SearchInteraction>; useInteraction: (option: TOption, query: string) => inferSearchInteractionDefinition<SearchInteraction>;
onKeyDown?: ( onKeyDown?: (
event: KeyboardEvent, event: KeyboardEvent,

View File

@@ -16,7 +16,7 @@ const searchInteractions = [
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
useActions: CreateChildrenOptionsProps<any>["useActions"]; useActions: CreateChildrenOptionsProps<any>["useActions"];
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
detailComponent: CreateChildrenOptionsProps<any>["detailComponent"]; DetailComponent: CreateChildrenOptionsProps<any>["DetailComponent"];
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
option: any; option: any;
}>(), }>(),

View File

@@ -16,7 +16,7 @@ const appChildrenOptions = createChildrenOptions<App>({
useActions: () => [ useActions: () => [
{ {
key: "open", key: "open",
component: () => { Component: () => {
const t = useI18n(); const t = useI18n();
return ( return (
@@ -34,7 +34,7 @@ const appChildrenOptions = createChildrenOptions<App>({
}, },
{ {
key: "edit", key: "edit",
component: () => { Component: () => {
const t = useI18n(); const t = useI18n();
return ( return (
@@ -47,7 +47,7 @@ const appChildrenOptions = createChildrenOptions<App>({
useInteraction: interaction.link(({ id }) => ({ href: `/manage/apps/edit/${id}` })), useInteraction: interaction.link(({ id }) => ({ href: `/manage/apps/edit/${id}` })),
}, },
], ],
detailComponent: ({ options }) => { DetailComponent: ({ options }) => {
const t = useI18n(); const t = useI18n();
return ( return (
@@ -75,7 +75,7 @@ const appChildrenOptions = createChildrenOptions<App>({
export const appsSearchGroup = createGroup<App>({ export const appsSearchGroup = createGroup<App>({
keyPath: "id", keyPath: "id",
title: (t) => t("search.mode.appIntegrationBoard.group.app.title"), title: (t) => t("search.mode.appIntegrationBoard.group.app.title"),
component: (app) => ( Component: (app) => (
<Group px="md" py="sm"> <Group px="md" py="sm">
<Avatar <Avatar
size="sm" size="sm"

View File

@@ -23,7 +23,7 @@ const boardChildrenOptions = createChildrenOptions<Board>({
const actions: (ChildrenAction<Board> & { hidden?: boolean })[] = [ const actions: (ChildrenAction<Board> & { hidden?: boolean })[] = [
{ {
key: "open", key: "open",
component: () => { Component: () => {
const t = useI18n(); const t = useI18n();
return ( return (
@@ -37,7 +37,7 @@ const boardChildrenOptions = createChildrenOptions<Board>({
}, },
{ {
key: "homeBoard", key: "homeBoard",
component: () => { Component: () => {
const t = useI18n(); const t = useI18n();
return ( return (
@@ -61,7 +61,7 @@ const boardChildrenOptions = createChildrenOptions<Board>({
}, },
{ {
key: "settings", key: "settings",
component: () => { Component: () => {
const t = useI18n(); const t = useI18n();
return ( return (
@@ -78,7 +78,7 @@ const boardChildrenOptions = createChildrenOptions<Board>({
return actions; return actions;
}, },
detailComponent: ({ options: board }) => { DetailComponent: ({ options: board }) => {
const t = useI18n(); const t = useI18n();
return ( return (
@@ -102,7 +102,7 @@ const boardChildrenOptions = createChildrenOptions<Board>({
export const boardsSearchGroup = createGroup<Board>({ export const boardsSearchGroup = createGroup<Board>({
keyPath: "id", keyPath: "id",
title: "Boards", title: "Boards",
component: (board) => ( Component: (board) => (
<Group px="md" py="sm"> <Group px="md" py="sm">
{board.logoImageUrl ? ( {board.logoImageUrl ? (
<img src={board.logoImageUrl} alt={board.name} width={24} height={24} /> <img src={board.logoImageUrl} alt={board.name} width={24} height={24} />

View File

@@ -10,7 +10,7 @@ import { interaction } from "../../lib/interaction";
export const integrationsSearchGroup = createGroup<{ id: string; kind: IntegrationKind; name: string }>({ export const integrationsSearchGroup = createGroup<{ id: string; kind: IntegrationKind; name: string }>({
keyPath: "id", keyPath: "id",
title: (t) => t("search.mode.appIntegrationBoard.group.integration.title"), title: (t) => t("search.mode.appIntegrationBoard.group.integration.title"),
component: (integration) => ( Component: (integration) => (
<Group px="md" py="sm"> <Group px="md" py="sm">
<IntegrationAvatar size="sm" kind={integration.kind} /> <IntegrationAvatar size="sm" kind={integration.kind} />

View File

@@ -30,7 +30,7 @@ export const languageChildrenOptions = createChildrenOptions<Record<string, unkn
) )
.map(({ localeKey, attributes }) => ({ .map(({ localeKey, attributes }) => ({
key: localeKey, key: localeKey,
component() { Component() {
return ( return (
<Group mx="md" my="sm" wrap="nowrap" justify="space-between" w="100%"> <Group mx="md" my="sm" wrap="nowrap" justify="space-between" w="100%">
<Group wrap="nowrap"> <Group wrap="nowrap">
@@ -53,7 +53,7 @@ export const languageChildrenOptions = createChildrenOptions<Record<string, unkn
}, },
})); }));
}, },
detailComponent: () => { DetailComponent: () => {
const t = useI18n(); const t = useI18n();
return ( return (

View File

@@ -20,7 +20,7 @@ export const newIntegrationChildrenOptions = createChildrenOptions<Record<string
) )
.map(([kind, integrationDef]) => ({ .map(([kind, integrationDef]) => ({
key: kind, key: kind,
component() { Component() {
return ( return (
<Group mx="md" my="sm" wrap="nowrap" w="100%"> <Group mx="md" my="sm" wrap="nowrap" w="100%">
<IntegrationAvatar kind={kind} size="sm" /> <IntegrationAvatar kind={kind} size="sm" />
@@ -31,7 +31,7 @@ export const newIntegrationChildrenOptions = createChildrenOptions<Record<string
useInteraction: interaction.link(() => ({ href: `/manage/integrations/new?kind=${kind}` })), useInteraction: interaction.link(() => ({ href: `/manage/integrations/new?kind=${kind}` })),
})); }));
}, },
detailComponent() { DetailComponent() {
const t = useI18n(); const t = useI18n();
return ( return (

View File

@@ -44,7 +44,7 @@ export const commandMode = {
keyPath: "commandKey", keyPath: "commandKey",
title: "Global commands", title: "Global commands",
useInteraction: (option, query) => option.useInteraction(option, query), useInteraction: (option, query) => option.useInteraction(option, query),
component: ({ icon: Icon, name }) => ( Component: ({ icon: Icon, name }) => (
<Group px="md" py="sm"> <Group px="md" py="sm">
<Icon stroke={1.5} /> <Icon stroke={1.5} />
<Text>{name}</Text> <Text>{name}</Text>

View File

@@ -15,7 +15,7 @@ export const searchEnginesChildrenOptions = createChildrenOptions<SearchEngine>(
useActions: () => [ useActions: () => [
{ {
key: "search", key: "search",
component: ({ name }) => { Component: ({ name }) => {
const tChildren = useScopedI18n("search.mode.external.group.searchEngine.children"); const tChildren = useScopedI18n("search.mode.external.group.searchEngine.children");
return ( return (
@@ -30,7 +30,7 @@ export const searchEnginesChildrenOptions = createChildrenOptions<SearchEngine>(
})), })),
}, },
], ],
detailComponent({ options }) { DetailComponent({ options }) {
const tChildren = useScopedI18n("search.mode.external.group.searchEngine.children"); const tChildren = useScopedI18n("search.mode.external.group.searchEngine.children");
return ( return (
<Stack mx="md" my="sm"> <Stack mx="md" my="sm">
@@ -47,7 +47,7 @@ export const searchEnginesChildrenOptions = createChildrenOptions<SearchEngine>(
export const searchEnginesSearchGroups = createGroup<SearchEngine>({ export const searchEnginesSearchGroups = createGroup<SearchEngine>({
keyPath: "short", keyPath: "short",
title: (t) => t("search.mode.external.group.searchEngine.title"), title: (t) => t("search.mode.external.group.searchEngine.title"),
component: ({ iconUrl, name, short, description }) => { Component: ({ iconUrl, name, short, description }) => {
return ( return (
<Group w="100%" wrap="nowrap" justify="space-between" align="center" px="md" py="xs"> <Group w="100%" wrap="nowrap" justify="space-between" align="center" px="md" py="xs">
<Group wrap="nowrap"> <Group wrap="nowrap">

View File

@@ -22,7 +22,7 @@ const helpMode = {
keyPath: "character", keyPath: "character",
title: (t) => t("search.mode.help.group.mode.title"), title: (t) => t("search.mode.help.group.mode.title"),
options: searchModesWithoutHelp.map(({ character, modeKey }) => ({ character, modeKey })), options: searchModesWithoutHelp.map(({ character, modeKey }) => ({ character, modeKey })),
component: ({ modeKey, character }) => { Component: ({ modeKey, character }) => {
const t = useScopedI18n(`search.mode.${modeKey}`); const t = useScopedI18n(`search.mode.${modeKey}`);
return ( return (
@@ -59,7 +59,7 @@ const helpMode = {
}, },
]; ];
}, },
component: (props) => ( Component: (props) => (
<Group px="md" py="xs" w="100%" wrap="nowrap" align="center"> <Group px="md" py="xs" w="100%" wrap="nowrap" align="center">
<props.icon /> <props.icon />
<Text>{props.label}</Text> <Text>{props.label}</Text>

View File

@@ -29,7 +29,7 @@ export const pagesSearchGroup = createGroup<{
}>({ }>({
keyPath: "path", keyPath: "path",
title: (t) => t("search.mode.page.group.page.title"), title: (t) => t("search.mode.page.group.page.title"),
component: ({ name, icon: Icon }) => ( Component: ({ name, icon: Icon }) => (
<Group px="md" py="sm"> <Group px="md" py="sm">
<Icon stroke={1.5} /> <Icon stroke={1.5} />
<Text>{name}</Text> <Text>{name}</Text>

View File

@@ -16,7 +16,7 @@ const groupChildrenOptions = createChildrenOptions<Group>({
useActions: () => [ useActions: () => [
{ {
key: "detail", key: "detail",
component: () => { Component: () => {
const t = useI18n(); const t = useI18n();
return ( return (
<Group mx="md" my="sm"> <Group mx="md" my="sm">
@@ -29,7 +29,7 @@ const groupChildrenOptions = createChildrenOptions<Group>({
}, },
{ {
key: "manageMember", key: "manageMember",
component: () => { Component: () => {
const t = useI18n(); const t = useI18n();
return ( return (
<Group mx="md" my="sm"> <Group mx="md" my="sm">
@@ -42,7 +42,7 @@ const groupChildrenOptions = createChildrenOptions<Group>({
}, },
{ {
key: "managePermission", key: "managePermission",
component: () => { Component: () => {
const t = useI18n(); const t = useI18n();
return ( return (
<Group mx="md" my="sm"> <Group mx="md" my="sm">
@@ -54,7 +54,7 @@ const groupChildrenOptions = createChildrenOptions<Group>({
useInteraction: interaction.link(({ id }) => ({ href: `/manage/users/groups/${id}/permissions` })), useInteraction: interaction.link(({ id }) => ({ href: `/manage/users/groups/${id}/permissions` })),
}, },
], ],
detailComponent: ({ options }) => { DetailComponent: ({ options }) => {
const t = useI18n(); const t = useI18n();
return ( return (
<Stack mx="md" my="sm"> <Stack mx="md" my="sm">
@@ -71,7 +71,7 @@ const groupChildrenOptions = createChildrenOptions<Group>({
export const groupsSearchGroup = createGroup<Group>({ export const groupsSearchGroup = createGroup<Group>({
keyPath: "id", keyPath: "id",
title: "Groups", title: "Groups",
component: ({ name }) => ( Component: ({ name }) => (
<Group px="md" py="sm"> <Group px="md" py="sm">
<Text>{name}</Text> <Text>{name}</Text>
</Group> </Group>

View File

@@ -17,7 +17,7 @@ const userChildrenOptions = createChildrenOptions<User>({
useActions: () => [ useActions: () => [
{ {
key: "detail", key: "detail",
component: () => { Component: () => {
const t = useI18n(); const t = useI18n();
return ( return (
@@ -30,7 +30,7 @@ const userChildrenOptions = createChildrenOptions<User>({
useInteraction: interaction.link(({ id }) => ({ href: `/manage/users/${id}/general` })), useInteraction: interaction.link(({ id }) => ({ href: `/manage/users/${id}/general` })),
}, },
], ],
detailComponent: ({ options }) => { DetailComponent: ({ options }) => {
const t = useI18n(); const t = useI18n();
return ( return (
@@ -49,7 +49,7 @@ const userChildrenOptions = createChildrenOptions<User>({
export const usersSearchGroup = createGroup<User>({ export const usersSearchGroup = createGroup<User>({
keyPath: "id", keyPath: "id",
title: (t) => t("search.mode.userGroup.group.user.title"), title: (t) => t("search.mode.userGroup.group.user.title"),
component: (user) => ( Component: (user) => (
<Group px="md" py="sm"> <Group px="md" py="sm">
<UserAvatar user={user} size="sm" /> <UserAvatar user={user} size="sm" />
<Text>{user.name}</Text> <Text>{user.name}</Text>

View File

@@ -34,7 +34,7 @@ export const TablePagination = ({ total }: TablePaginationProps) => {
(control: ControlType) => { (control: ControlType) => {
return getItemProps(calculatePageFor(control, current, total)); return getItemProps(calculatePageFor(control, current, total));
}, },
[current], [current, getItemProps, total],
); );
const handleChange = useCallback( const handleChange = useCallback(
@@ -43,7 +43,7 @@ export const TablePagination = ({ total }: TablePaginationProps) => {
params.set("page", page.toString()); params.set("page", page.toString());
replace(`${pathName}?${params.toString()}`); replace(`${pathName}?${params.toString()}`);
}, },
[pathName, searchParams], [pathName, replace, searchParams],
); );
return ( return (

View File

@@ -46,7 +46,7 @@ export const WidgetLocationInput = ({ property, kind }: CommonWidgetInputProps<"
form.clearFieldError(`options.${property}.latitude`); form.clearFieldError(`options.${property}.latitude`);
form.clearFieldError(`options.${property}.longitude`); form.clearFieldError(`options.${property}.longitude`);
}, },
[handleChange], [form, handleChange, property],
); );
const onSearch = useCallback(() => { const onSearch = useCallback(() => {

View File

@@ -39,7 +39,7 @@ export const WidgetMultiTextInput = ({ property, kind, options }: CommonWidgetIn
success: validationResult.success, success: validationResult.success,
result: validationResult, result: validationResult,
}; };
}, [search]); }, [options.validate, search]);
const error = React.useMemo(() => { const error = React.useMemo(() => {
/* hide the error when nothing is being typed since "" is not valid but is not an explicit error */ /* hide the error when nothing is being typed since "" is not valid but is not an explicit error */

View File

@@ -2,7 +2,7 @@
import "../widgets-common.css"; import "../widgets-common.css";
import { useMemo, useState } from "react"; import { useCallback, useMemo, useState } from "react";
import type { MantineStyleProp } from "@mantine/core"; import type { MantineStyleProp } from "@mantine/core";
import { import {
ActionIcon, ActionIcon,
@@ -233,7 +233,19 @@ export default function DownloadClientsWidget({
) )
//flatMap already sorts by integration by nature, add sorting by integration type (usenet | torrent) //flatMap already sorts by integration by nature, add sorting by integration type (usenet | torrent)
.sort(({ type: typeA }, { type: typeB }) => typeA.length - typeB.length), .sort(({ type: typeA }, { type: typeB }) => typeA.length - typeB.length),
[currentItems, integrationIds, options], [
currentItems,
integrationIds,
integrationsWithInteractions,
mutateDeleteItem,
mutatePauseItem,
mutateResumeItem,
options.activeTorrentThreshold,
options.categoryFilter,
options.filterIsWhitelist,
options.showCompletedTorrent,
options.showCompletedUsenet,
],
); );
//Flatten Clients Array for which each elements has the integration and general client infos. //Flatten Clients Array for which each elements has the integration and general client infos.
@@ -278,7 +290,14 @@ export default function DownloadClientsWidget({
({ status: statusA }, { status: statusB }) => ({ status: statusA }, { status: statusB }) =>
(statusA?.type.length ?? Infinity) - (statusB?.type.length ?? Infinity), (statusA?.type.length ?? Infinity) - (statusB?.type.length ?? Infinity),
), ),
[currentItems, integrationIds, options], [
currentItems,
integrationIds,
integrationsWithInteractions,
options.applyFilterToRatio,
options.categoryFilter,
options.filterIsWhitelist,
],
); );
//Check existing types between torrents and usenet //Check existing types between torrents and usenet
@@ -333,37 +352,40 @@ export default function DownloadClientsWidget({
}; };
//Base element in common with all columns //Base element in common with all columns
const columnsDefBase = ({ const columnsDefBase = useCallback(
key, ({
showHeader, key,
align, showHeader,
}: { align,
key: keyof ExtendedDownloadClientItem; }: {
showHeader: boolean; key: keyof ExtendedDownloadClientItem;
align?: "center" | "left" | "right" | "justify" | "char"; showHeader: boolean;
}): MRT_ColumnDef<ExtendedDownloadClientItem> => { align?: "center" | "left" | "right" | "justify" | "char";
const style: MantineStyleProp = { }): MRT_ColumnDef<ExtendedDownloadClientItem> => {
minWidth: 0, const style: MantineStyleProp = {
width: "var(--column-width)", minWidth: 0,
height: "var(--ratio-width)", width: "var(--column-width)",
padding: "var(--space-size)", height: "var(--ratio-width)",
transition: "unset", padding: "var(--space-size)",
"--key-width": columnsRatios[key], transition: "unset",
"--column-width": "calc((var(--key-width)/var(--total-width) * 100cqw))", "--key-width": columnsRatios[key],
}; "--column-width": "calc((var(--key-width)/var(--total-width) * 100cqw))",
return { };
id: key, return {
accessorKey: key, id: key,
header: key, accessorKey: key,
size: columnsRatios[key], header: key,
mantineTableBodyCellProps: { style, align }, size: columnsRatios[key],
mantineTableHeadCellProps: { mantineTableBodyCellProps: { style, align },
style, mantineTableHeadCellProps: {
align: isEditMode ? "center" : align, style,
}, align: isEditMode ? "center" : align,
Header: () => (showHeader && !isEditMode ? <Text fw={700}>{t(`items.${key}.columnTitle`)}</Text> : ""), },
}; Header: () => (showHeader && !isEditMode ? <Text fw={700}>{t(`items.${key}.columnTitle`)}</Text> : ""),
}; };
},
[isEditMode, t],
);
//Make columns and cell elements, Memoized to data with deps on data and EditMode //Make columns and cell elements, Memoized to data with deps on data and EditMode
const columns = useMemo<MRT_ColumnDef<ExtendedDownloadClientItem>[]>( const columns = useMemo<MRT_ColumnDef<ExtendedDownloadClientItem>[]>(
@@ -580,7 +602,7 @@ export default function DownloadClientsWidget({
}, },
}, },
], ],
[clickedIndex, isEditMode, data, integrationIds, options], [columnsDefBase, t, tCommon],
); );
//Table build and config //Table build and config
@@ -704,10 +726,7 @@ interface ItemInfoModalProps {
} }
const ItemInfoModal = ({ items, currentIndex, opened, onClose }: ItemInfoModalProps) => { const ItemInfoModal = ({ items, currentIndex, opened, onClose }: ItemInfoModalProps) => {
const item = useMemo<ExtendedDownloadClientItem | undefined>( const item = useMemo<ExtendedDownloadClientItem | undefined>(() => items[currentIndex], [items, currentIndex]);
() => items[currentIndex],
[items, currentIndex, opened],
);
const t = useScopedI18n("widget.downloads.states"); const t = useScopedI18n("widget.downloads.states");
//The use case for "No item found" should be impossible, hence no translation //The use case for "No item found" should be impossible, hence no translation
return ( return (

View File

@@ -57,22 +57,19 @@ export default function HealthMonitoringWidget({ options, integrationIds }: Widg
throw new NoIntegrationSelectedError(); throw new NoIntegrationSelectedError();
} }
return ( return (
<Box h="100%" className="health-monitoring"> <Stack h="100%" gap="2.5cqmin" className="health-monitoring">
{healthData.map(({ integrationId, integrationName, healthInfo }) => { {healthData.map(({ integrationId, integrationName, healthInfo }) => {
const memoryUsage = formatMemoryUsage(healthInfo.memAvailable, healthInfo.memUsed);
const disksData = matchFileSystemAndSmart(healthInfo.fileSystem, healthInfo.smart); const disksData = matchFileSystemAndSmart(healthInfo.fileSystem, healthInfo.smart);
const { ref, width } = useElementSize(); const memoryUsage = formatMemoryUsage(healthInfo.memAvailable, healthInfo.memUsed);
const ringSize = width * 0.95;
const ringThickness = width / 10;
const progressSize = width * 0.2;
return ( return (
<Box <Stack
gap="2.5cqmin"
key={integrationId} key={integrationId}
h="100%" h="100%"
className={`health-monitoring-information health-monitoring-${integrationName}`} className={`health-monitoring-information health-monitoring-${integrationName}`}
p="2.5cqmin"
> >
<Card className="health-monitoring-information-card" m="2.5cqmin" p="2.5cqmin" withBorder> <Card className="health-monitoring-information-card" p="2.5cqmin" withBorder>
<Flex <Flex
className="health-monitoring-information-card-elements" className="health-monitoring-information-card-elements"
h="100%" h="100%"
@@ -155,95 +152,17 @@ export default function HealthMonitoringWidget({ options, integrationIds }: Widg
</Stack> </Stack>
</Modal> </Modal>
</Box> </Box>
{options.cpu && ( {options.cpu && <CpuRing cpuUtilization={healthInfo.cpuUtilization} />}
<Box ref={ref} w="100%" h="100%" className="health-monitoring-cpu">
<RingProgress
className="health-monitoring-cpu-utilization"
roundCaps
size={ringSize}
thickness={ringThickness}
label={
<Center style={{ flexDirection: "column" }}>
<Text
className="health-monitoring-cpu-utilization-value"
size="3cqmin"
>{`${healthInfo.cpuUtilization.toFixed(2)}%`}</Text>
<IconCpu className="health-monitoring-cpu-utilization-icon" size="7cqmin" />
</Center>
}
sections={[
{
value: Number(healthInfo.cpuUtilization.toFixed(2)),
color: progressColor(Number(healthInfo.cpuUtilization.toFixed(2))),
},
]}
/>
</Box>
)}
{healthInfo.cpuTemp && options.cpu && ( {healthInfo.cpuTemp && options.cpu && (
<Box ref={ref} w="100%" h="100%" className="health-monitoring-cpu-temperature"> <CpuTempRing fahrenheit={options.fahrenheit} cpuTemp={healthInfo.cpuTemp} />
<RingProgress
ref={ref}
className="health-monitoring-cpu-temp"
roundCaps
size={ringSize}
thickness={ringThickness}
label={
<Center style={{ flexDirection: "column" }}>
<Text className="health-monitoring-cpu-temp-value" size="3cqmin">
{options.fahrenheit
? `${(healthInfo.cpuTemp * 1.8 + 32).toFixed(1)}°F`
: `${healthInfo.cpuTemp}°C`}
</Text>
<IconCpu className="health-monitoring-cpu-temp-icon" size="7cqmin" />
</Center>
}
sections={[
{
value: healthInfo.cpuTemp,
color: progressColor(healthInfo.cpuTemp),
},
]}
/>
</Box>
)}
{options.memory && (
<Box ref={ref} w="100%" h="100%" className="health-monitoring-memory">
<RingProgress
className="health-monitoring-memory-use"
roundCaps
size={ringSize}
thickness={ringThickness}
label={
<Center style={{ flexDirection: "column" }}>
<Text className="health-monitoring-memory-value" size="3cqmin">
{memoryUsage.memUsed.GB}GiB
</Text>
<IconBrain className="health-monitoring-memory-icon" size="7cqmin" />
</Center>
}
sections={[
{
value: Number(memoryUsage.memUsed.percent),
color: progressColor(Number(memoryUsage.memUsed.percent)),
tooltip: `${memoryUsage.memUsed.percent}%`,
},
]}
/>
</Box>
)} )}
{options.memory && <MemoryRing available={healthInfo.memAvailable} used={healthInfo.memUsed} />}
</Flex> </Flex>
</Card> </Card>
{options.fileSystem && {options.fileSystem &&
disksData.map((disk) => { disksData.map((disk) => {
return ( return (
<Card <Card className="health-monitoring-disk-card" key={disk.deviceName} p="2.5cqmin" withBorder>
className="health-monitoring-disk-card"
key={disk.deviceName}
m="2.5cqmin"
p="2.5cqmin"
withBorder
>
<Flex className="health-monitoring-disk-status" justify="space-between" align="center" m="1.5cqmin"> <Flex className="health-monitoring-disk-status" justify="space-between" align="center" m="1.5cqmin">
<Group gap="1cqmin"> <Group gap="1cqmin">
<IconServer className="health-monitoring-disk-icon" size="5cqmin" /> <IconServer className="health-monitoring-disk-icon" size="5cqmin" />
@@ -266,14 +185,14 @@ export default function HealthMonitoringWidget({ options, integrationIds }: Widg
</Text> </Text>
</Group> </Group>
</Flex> </Flex>
<Progress.Root className="health-monitoring-disk-use" size={progressSize}> <Progress.Root className="health-monitoring-disk-use" h="6cqmin">
<Tooltip label={disk.used}> <Tooltip label={disk.used}>
<Progress.Section <Progress.Section
value={disk.percentage} value={disk.percentage}
color={progressColor(disk.percentage)} color={progressColor(disk.percentage)}
className="health-monitoring-disk-use-percentage" className="health-monitoring-disk-use-percentage"
> >
<Progress.Label className="health-monitoring-disk-use-value"> <Progress.Label className="health-monitoring-disk-use-value" fz="2.5cqmin">
{t("widget.healthMonitoring.popover.used")} {t("widget.healthMonitoring.popover.used")}
</Progress.Label> </Progress.Label>
</Progress.Section> </Progress.Section>
@@ -291,7 +210,7 @@ export default function HealthMonitoringWidget({ options, integrationIds }: Widg
value={100 - disk.percentage} value={100 - disk.percentage}
color="default" color="default"
> >
<Progress.Label className="health-monitoring-disk-available-value"> <Progress.Label className="health-monitoring-disk-available-value" fz="2.5cqmin">
{t("widget.healthMonitoring.popover.diskAvailable")} {t("widget.healthMonitoring.popover.diskAvailable")}
</Progress.Label> </Progress.Label>
</Progress.Section> </Progress.Section>
@@ -300,10 +219,10 @@ export default function HealthMonitoringWidget({ options, integrationIds }: Widg
</Card> </Card>
); );
})} })}
</Box> </Stack>
); );
})} })}
</Box> </Stack>
); );
} }
@@ -349,6 +268,95 @@ export const matchFileSystemAndSmart = (fileSystems: FileSystem[], smartData: Sm
}); });
}; };
const CpuRing = ({ cpuUtilization }: { cpuUtilization: number }) => {
const { width, ref } = useElementSize();
return (
<Box ref={ref} w="100%" h="100%" className="health-monitoring-cpu">
<RingProgress
className="health-monitoring-cpu-utilization"
roundCaps
size={width * 0.95}
thickness={width / 10}
label={
<Center style={{ flexDirection: "column" }}>
<Text
className="health-monitoring-cpu-utilization-value"
size="3cqmin"
>{`${cpuUtilization.toFixed(2)}%`}</Text>
<IconCpu className="health-monitoring-cpu-utilization-icon" size="7cqmin" />
</Center>
}
sections={[
{
value: Number(cpuUtilization.toFixed(2)),
color: progressColor(Number(cpuUtilization.toFixed(2))),
},
]}
/>
</Box>
);
};
const CpuTempRing = ({ fahrenheit, cpuTemp }: { fahrenheit: boolean; cpuTemp: number }) => {
const { width, ref } = useElementSize();
return (
<Box ref={ref} w="100%" h="100%" className="health-monitoring-cpu-temperature">
<RingProgress
className="health-monitoring-cpu-temp"
roundCaps
size={width * 0.95}
thickness={width / 10}
label={
<Center style={{ flexDirection: "column" }}>
<Text className="health-monitoring-cpu-temp-value" size="3cqmin">
{fahrenheit ? `${(cpuTemp * 1.8 + 32).toFixed(1)}°F` : `${cpuTemp}°C`}
</Text>
<IconCpu className="health-monitoring-cpu-temp-icon" size="7cqmin" />
</Center>
}
sections={[
{
value: cpuTemp,
color: progressColor(cpuTemp),
},
]}
/>
</Box>
);
};
const MemoryRing = ({ available, used }: { available: string; used: string }) => {
const { width, ref } = useElementSize();
const memoryUsage = formatMemoryUsage(available, used);
return (
<Box ref={ref} w="100%" h="100%" className="health-monitoring-memory">
<RingProgress
className="health-monitoring-memory-use"
roundCaps
size={width * 0.95}
thickness={width / 10}
label={
<Center style={{ flexDirection: "column" }}>
<Text className="health-monitoring-memory-value" size="3cqmin">
{memoryUsage.memUsed.GB}GiB
</Text>
<IconBrain className="health-monitoring-memory-icon" size="7cqmin" />
</Center>
}
sections={[
{
value: Number(memoryUsage.memUsed.percent),
color: progressColor(Number(memoryUsage.memUsed.percent)),
tooltip: `${memoryUsage.memUsed.percent}%`,
},
]}
/>
</Box>
);
};
export const formatMemoryUsage = (memFree: string, memUsed: string) => { export const formatMemoryUsage = (memFree: string, memUsed: string) => {
const memFreeBytes = Number(memFree); const memFreeBytes = Number(memFree);
const memUsedBytes = Number(memUsed); const memUsedBytes = Number(memUsed);

View File

@@ -46,7 +46,7 @@ export default function MediaServerWidget({
} }
return 0; return 0;
}), }),
[mediaRequests, integrationIds], [mediaRequests],
); );
const { mutate: mutateRequestAnswer } = clientApi.widget.mediaRequests.answerRequest.useMutation(); const { mutate: mutateRequestAnswer } = clientApi.widget.mediaRequests.answerRequest.useMutation();

View File

@@ -189,17 +189,31 @@ export function Notebook({ options, isEditMode, boardId, itemId }: WidgetCompone
addEventListener("onReadOnlyCheck", handleOnReadOnlyCheck); addEventListener("onReadOnlyCheck", handleOnReadOnlyCheck);
const handleEditToggleCallback = (previous: boolean) => { const handleContentUpdate = useCallback(
const current = !previous; (contentUpdate: string) => {
if (!editor) return current; setToSaveContent(contentUpdate);
editor.setEditable(current); // This is not available in preview mode
if (boardId && itemId) {
void mutateAsync({ boardId, itemId, content: contentUpdate });
}
},
[boardId, itemId, mutateAsync],
);
handleContentUpdate(content); const handleEditToggleCallback = useCallback(
(previous: boolean) => {
const current = !previous;
if (!editor) return current;
editor.setEditable(current);
return current; handleContentUpdate(content);
};
const handleEditCancelCallback = () => { return current;
},
[content, editor, handleContentUpdate],
);
const handleEditCancelCallback = useCallback(() => {
if (!editor) return false; if (!editor) return false;
editor.setEditable(false); editor.setEditable(false);
@@ -207,20 +221,12 @@ export function Notebook({ options, isEditMode, boardId, itemId }: WidgetCompone
editor.commands.setContent(toSaveContent); editor.commands.setContent(toSaveContent);
return false; return false;
}; }, [editor, toSaveContent]);
const handleEditCancel = useCallback(() => { const handleEditCancel = useCallback(() => {
setIsEditing(handleEditCancelCallback); setIsEditing(handleEditCancelCallback);
}, [setIsEditing, handleEditCancelCallback]); }, [setIsEditing, handleEditCancelCallback]);
const handleContentUpdate = (contentUpdate: string) => {
setToSaveContent(contentUpdate);
// This is not available in preview mode
if (boardId && itemId) {
void mutateAsync({ boardId, itemId, content: contentUpdate });
}
};
const handleEditToggle = useCallback(() => { const handleEditToggle = useCallback(() => {
setIsEditing(handleEditToggleCallback); setIsEditing(handleEditToggleCallback);
}, [setIsEditing, handleEditToggleCallback]); }, [setIsEditing, handleEditToggleCallback]);

View File

@@ -1,6 +1,6 @@
"use client"; "use client";
import React, { useState } from "react"; import React, { useCallback, useState } from "react";
import { Center, Stack, Text, UnstyledButton } from "@mantine/core"; import { Center, Stack, Text, UnstyledButton } from "@mantine/core";
import { clientApi } from "@homarr/api/client"; import { clientApi } from "@homarr/api/client";
@@ -38,7 +38,7 @@ export default function SmartHomeEntityStateWidget({
const attribute = options.entityUnit.length > 0 ? " " + options.entityUnit : ""; const attribute = options.entityUnit.length > 0 ? " " + options.entityUnit : "";
const handleClick = React.useCallback(() => { const handleClick = useCallback(() => {
if (isEditMode) { if (isEditMode) {
return; return;
} }
@@ -51,7 +51,7 @@ export default function SmartHomeEntityStateWidget({
entityId: options.entityId, entityId: options.entityId,
integrationId: integrationIds[0] ?? "", integrationId: integrationIds[0] ?? "",
}); });
}, []); }, [integrationIds, isEditMode, mutate, options.clickable, options.entityId]);
return ( return (
<UnstyledButton <UnstyledButton

View File

@@ -31,7 +31,7 @@ export default function SmartHomeTriggerAutomationWidget({
automationId: options.automationId, automationId: options.automationId,
integrationId: integrationIds[0] ?? "", integrationId: integrationIds[0] ?? "",
}); });
}, [isEditMode]); }, [integrationIds, isEditMode, mutateAsync, options.automationId]);
return ( return (
<UnstyledButton onClick={handleClick} style={{ cursor: !isEditMode ? "pointer" : "initial" }} w="100%" h="100%"> <UnstyledButton onClick={handleClick} style={{ cursor: !isEditMode ? "pointer" : "initial" }} w="100%" h="100%">
{isShowSuccess && ( {isShowSuccess && (

View File

@@ -72,7 +72,7 @@ const Feed = ({ options }: Pick<WidgetComponentProps<"video">, "options">) => {
() => undefined, () => undefined,
); );
} }
}, [videoRef]); }, [options.hasAutoPlay, options.hasControls, options.isMuted, videoRef]);
return ( return (
<Group justify="center" w="100%" h="100%" pos="relative"> <Group justify="center" w="100%" h="100%" pos="relative">

16
pnpm-lock.yaml generated
View File

@@ -24,7 +24,7 @@ importers:
version: 4.3.2(vite@5.4.5(@types/node@20.16.11)(sass@1.79.5)(sugarss@4.0.1(postcss@8.4.47))(terser@5.32.0)) version: 4.3.2(vite@5.4.5(@types/node@20.16.11)(sass@1.79.5)(sugarss@4.0.1(postcss@8.4.47))(terser@5.32.0))
'@vitest/coverage-v8': '@vitest/coverage-v8':
specifier: ^2.1.3 specifier: ^2.1.3
version: 2.1.3(vitest@2.1.3) version: 2.1.3(vitest@2.1.3(@types/node@20.16.11)(@vitest/ui@2.1.3)(jsdom@25.0.1)(sass@1.79.5)(sugarss@4.0.1(postcss@8.4.47))(terser@5.32.0))
'@vitest/ui': '@vitest/ui':
specifier: ^2.1.3 specifier: ^2.1.3
version: 2.1.3(vitest@2.1.3) version: 2.1.3(vitest@2.1.3)
@@ -1655,8 +1655,8 @@ importers:
specifier: ^7.37.1 specifier: ^7.37.1
version: 7.37.1(eslint@9.12.0) version: 7.37.1(eslint@9.12.0)
eslint-plugin-react-hooks: eslint-plugin-react-hooks:
specifier: ^4.6.2 specifier: ^5.0.0
version: 4.6.2(eslint@9.12.0) version: 5.0.0(eslint@9.12.0)
typescript-eslint: typescript-eslint:
specifier: ^8.9.0 specifier: ^8.9.0
version: 8.9.0(eslint@9.12.0)(typescript@5.6.3) version: 8.9.0(eslint@9.12.0)(typescript@5.6.3)
@@ -4795,11 +4795,11 @@ packages:
peerDependencies: peerDependencies:
eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9 eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9
eslint-plugin-react-hooks@4.6.2: eslint-plugin-react-hooks@5.0.0:
resolution: {integrity: sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==} resolution: {integrity: sha512-hIOwI+5hYGpJEc4uPRmz2ulCjAGD/N13Lukkh8cLV0i2IRk/bdZDYjgLVHj+U9Z704kLIdIO6iueGvxNur0sgw==}
engines: {node: '>=10'} engines: {node: '>=10'}
peerDependencies: peerDependencies:
eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0
eslint-plugin-react@7.37.1: eslint-plugin-react@7.37.1:
resolution: {integrity: sha512-xwTnwDqzbDRA8uJ7BMxPs/EXRB3i8ZfnOIp8BsxEQkT0nHPp+WWceqGgo6rKb9ctNi8GJLDT4Go5HAWELa/WMg==} resolution: {integrity: sha512-xwTnwDqzbDRA8uJ7BMxPs/EXRB3i8ZfnOIp8BsxEQkT0nHPp+WWceqGgo6rKb9ctNi8GJLDT4Go5HAWELa/WMg==}
@@ -9967,7 +9967,7 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
'@vitest/coverage-v8@2.1.3(vitest@2.1.3)': '@vitest/coverage-v8@2.1.3(vitest@2.1.3(@types/node@20.16.11)(@vitest/ui@2.1.3)(jsdom@25.0.1)(sass@1.79.5)(sugarss@4.0.1(postcss@8.4.47))(terser@5.32.0))':
dependencies: dependencies:
'@ampproject/remapping': 2.3.0 '@ampproject/remapping': 2.3.0
'@bcoe/v8-coverage': 0.2.3 '@bcoe/v8-coverage': 0.2.3
@@ -11361,7 +11361,7 @@ snapshots:
safe-regex-test: 1.0.3 safe-regex-test: 1.0.3
string.prototype.includes: 2.0.0 string.prototype.includes: 2.0.0
eslint-plugin-react-hooks@4.6.2(eslint@9.12.0): eslint-plugin-react-hooks@5.0.0(eslint@9.12.0):
dependencies: dependencies:
eslint: 9.12.0 eslint: 9.12.0

View File

@@ -22,7 +22,7 @@
"eslint-plugin-import": "^2.31.0", "eslint-plugin-import": "^2.31.0",
"eslint-plugin-jsx-a11y": "^6.10.0", "eslint-plugin-jsx-a11y": "^6.10.0",
"eslint-plugin-react": "^7.37.1", "eslint-plugin-react": "^7.37.1",
"eslint-plugin-react-hooks": "^4.6.2", "eslint-plugin-react-hooks": "^5.0.0",
"typescript-eslint": "^8.9.0" "typescript-eslint": "^8.9.0"
}, },
"devDependencies": { "devDependencies": {

View File

@@ -12,9 +12,6 @@ export default [
rules: { rules: {
...reactPlugin.configs["jsx-runtime"].rules, ...reactPlugin.configs["jsx-runtime"].rules,
...hooksPlugin.configs.recommended.rules, ...hooksPlugin.configs.recommended.rules,
// context.getSource is not a function
"react-hooks/rules-of-hooks": "off",
"react-hooks/exhaustive-deps": "off",
}, },
languageOptions: { languageOptions: {
globals: { globals: {