refactor(board): modernize item and application select modals with SelectableCard (#6642)

Modernize board item, application, and integration selection flows with shared Mantine components, batch board actions, improved accessibility, and hardened persistence.
This commit is contained in:
Thomas authored and GitHub committed 2026-08-21 03:19:18 +02:00
1 parent bd4f6a92c6
commit f3014fe271
60 files changed
+3791 -1839

No files matched your search

@@ -1,9 +1,10 @@
"use client";
import { useState } from "react";
import { Group, ScrollArea, Title, UnstyledButton } from "@mantine/core";
import { useEffect, useState } from "react";
import { Alert, Button, Group, ScrollArea, Stack, Text, Title, UnstyledButton } from "@mantine/core";
import { IconArrowLeft, IconX } from "@tabler/icons-react";
import { clientApi } from "@homarr/api/client";
import type { IntegrationKind } from "@homarr/definitions";
import { getIntegrationName } from "@homarr/definitions";
import { createModal, modalSizeSelect, useModalAction } from "@homarr/modals";
@@ -27,9 +28,28 @@ interface IntegrationSelectModalProps {
export const IntegrationSelectModal = createModal<IntegrationSelectModalProps>(({ actions, innerProps }) => {
const t = useI18n();
const [step, setStep] = useState<"select" | "form">(innerProps.initialKind ? "form" : "select");
const [selectedKind, setSelectedKind] = useState<IntegrationKind | null>(innerProps.initialKind ?? null);
const directKind =
innerProps.initialKind ?? (innerProps.allowedKinds?.length === 1 ? innerProps.allowedKinds[0] : null);
const isDirectForm = Boolean(directKind);
const [step, setStep] = useState<"select" | "form">(isDirectForm ? "form" : "select");
const [selectedKind, setSelectedKind] = useState<IntegrationKind | null>(directKind ?? null);
const { openModal: openCompletionModal } = useModalAction(IntegrationCompletionModal);
const { data: integrationData, isError: isIntegrationDataError, refetch } = clientApi.integration.all.useQuery();
useEffect(() => {
if (step === "form" && !isDirectForm) {
actions.setCloseInterceptor?.(() => {
setStep("select");
setSelectedKind(null);
return false;
});
} else {
actions.setCloseInterceptor?.(null);
}
return () => {
actions.setCloseInterceptor?.(null);
};
}, [step, isDirectForm, actions]);
const handleSelect = (kind: IntegrationKind) => {
setSelectedKind(kind);
@@ -40,7 +60,7 @@ export const IntegrationSelectModal = createModal<IntegrationSelectModalProps>((
setStep("select");
setSelectedKind(null);
};
const handleFormBack = innerProps.initialKind ? actions.closeModal : handleBack;
const handleFormBack = isDirectForm ? actions.closeModal : handleBack;
const handleSuccess = (result?: CreatedIntegrationResult) => {
actions.closeModal();
@@ -52,12 +72,8 @@ export const IntegrationSelectModal = createModal<IntegrationSelectModalProps>((
};
if (step === "form" && selectedKind) {
let HeaderIcon = IconArrowLeft;
let headerLabel = t("common.action.previous");
if (innerProps.initialKind) {
HeaderIcon = IconX;
headerLabel = t("common.action.close");
}
const HeaderIcon = isDirectForm ? IconX : IconArrowLeft;
const headerLabel = isDirectForm ? t("common.action.close") : t("common.action.previous");
return (
<ScrollArea.Autosize mah="80vh">
@@ -79,11 +95,25 @@ export const IntegrationSelectModal = createModal<IntegrationSelectModalProps>((
);
}
if (isIntegrationDataError) {
return (
<Alert color="red" title={t("common.error")}>
<Stack gap="sm">
<Text size="sm">{t("integration.grid.loadError")}</Text>
<Button variant="light" color="red" onClick={() => void refetch()}>
{t("common.action.tryAgain")}
</Button>
</Stack>
</Alert>
);
}
return (
<IntegrationSelectGrid
onSelect={handleSelect}
enableMockIntegration={innerProps.enableMockIntegration}
allowedKinds={innerProps.allowedKinds}
integrationData={integrationData}
/>
);
}).withOptions({