fix(init): improve integrations step UX

- Replace plain Base URL input with a prominent Alert callout so users
  don't overlook it
- Add disableScroll prop to SelectGridLayout and use it in onboarding
  so integrations expand the page instead of scrolling
- Fix Collapse `in` → `expanded` prop for Mantine v9 migration in both
  the URL helpers panel and the Docker discovery panel
This commit is contained in:
Thomas Camlong committed 2026-05-28 12:15:56 +02:00
1 parent 1065e30c8d
commit 3ff3d41d7c
4 files changed
+61 -55

No files matched your search

@@ -142,7 +142,7 @@ export const DockerDiscoveryIndicator = ({
</Group>
</UnstyledButton>
<Collapse in={expanded}>
<Collapse expanded={expanded}>
<Stack gap="xs" mt="sm">
{integrations.length > 0 && (
<Stack gap={4}>
@@ -4,8 +4,8 @@ import { useCallback, useEffect, useRef, useState } from "react";
import { useRouter } from "next/navigation";
import {
ActionIcon,
Alert,
Button,
Card,
Center,
Collapse,
Group,
@@ -18,7 +18,7 @@ import {
Tooltip,
} from "@mantine/core";
import { useDisclosure } from "@mantine/hooks";
import { IconArrowRight, IconSettings } from "@tabler/icons-react";
import { IconArrowRight, IconInfoCircle, IconSettings } from "@tabler/icons-react";
import { clientApi } from "@homarr/api/client";
import type { IntegrationKind, UrlTemplateMode } from "@homarr/definitions";
@@ -29,6 +29,7 @@ import { IntegrationAvatar } from "@homarr/ui";
import { NewIntegrationForm } from "~/app/[locale]/manage/integrations/new/_integration-new-form";
import { IntegrationMultiSelectGrid } from "~/components/integration/integration-multi-select-grid";
import { InitStepCard } from "../../_components/init-step-card";
import { DockerDiscoveryIndicator } from "./docker-discovery-panel";
type Phase = "select" | "configure" | "done";
@@ -41,8 +42,6 @@ interface IntegrationDraft {
source: "manual" | "docker";
}
const CARD_WIDTH = 64 * 16;
export const InitIntegrations = () => {
const [phase, setPhase] = useState<Phase>("select");
const [drafts, setDrafts] = useState<Map<IntegrationKind, IntegrationDraft>>(new Map());
@@ -171,14 +170,14 @@ export const InitIntegrations = () => {
if (phase === "done") {
return (
<Card w={CARD_WIDTH} maw="90vw" withBorder>
<InitStepCard>
<Center py="xl">
<Stack align="center" gap="md">
<Loader size="lg" />
<Text>{t("configure.finishing")}</Text>
</Stack>
</Center>
</Card>
</InitStepCard>
);
}
@@ -200,7 +199,7 @@ export const InitIntegrations = () => {
}
return (
<Card w={CARD_WIDTH} maw="90vw" withBorder>
<InitStepCard>
<Stack>
<Text>{t("description")}</Text>
@@ -212,32 +211,32 @@ export const InitIntegrations = () => {
isLoading={isDockerLoading}
/>
<Stack gap="xs">
<Text size="sm" fw={500}>
{t("urlTemplate.label")}
</Text>
<Group gap="sm" grow>
<TextInput
placeholder={t("urlTemplate.placeholder")}
value={baseHost}
onChange={(e) => setBaseHost(e.currentTarget.value)}
/>
<SegmentedControl
value={urlMode}
onChange={(value) => setUrlMode(value as UrlTemplateMode)}
data={[
{ label: t("urlTemplate.mode.subdomain"), value: "subdomain" },
{ label: t("urlTemplate.mode.hostPort"), value: "hostPort" },
]}
style={{ flex: "0 0 auto" }}
/>
</Group>
<Text size="xs" c="dimmed">
{baseHost
? t("urlTemplate.preview", { example: buildIntegrationUrl("sonarr", baseHost, urlMode) })
: t("urlTemplate.hint")}
</Text>
</Stack>
<Alert variant="light" color="blue" icon={<IconInfoCircle />} title={t("urlTemplate.label")}>
<Stack gap="xs">
<Text size="sm">{t("urlTemplate.hint")}</Text>
<Group gap="sm" grow>
<TextInput
placeholder={t("urlTemplate.placeholder")}
value={baseHost}
onChange={(e) => setBaseHost(e.currentTarget.value)}
/>
<SegmentedControl
value={urlMode}
onChange={(value) => setUrlMode(value as UrlTemplateMode)}
data={[
{ label: t("urlTemplate.mode.subdomain"), value: "subdomain" },
{ label: t("urlTemplate.mode.hostPort"), value: "hostPort" },
]}
style={{ flex: "0 0 auto" }}
/>
</Group>
{baseHost && (
<Text size="xs" c="dimmed">
{t("urlTemplate.preview", { example: buildIntegrationUrl("sonarr", baseHost, urlMode) })}
</Text>
)}
</Stack>
</Alert>
<IntegrationMultiSelectGrid
selectedKinds={selectedKinds}
@@ -259,7 +258,7 @@ export const InitIntegrations = () => {
</Button>
</Group>
</Stack>
</Card>
</InitStepCard>
);
};
@@ -300,7 +299,7 @@ const ConfigurePhase = ({
: draft?.initialUrl;
return (
<Card w={CARD_WIDTH} maw="90vw" withBorder>
<InitStepCard>
<Stack>
<Group justify="space-between" align="center">
<Group gap="xs">
@@ -321,7 +320,7 @@ const ConfigurePhase = ({
</Text>
</Stack>
<Collapse in={helpersOpen}>
<Collapse expanded={helpersOpen}>
<Stack
gap="xs"
p="xs"
@@ -369,6 +368,6 @@ const ConfigurePhase = ({
isOnboarding
/>
</Stack>
</Card>
</InitStepCard>
);
};
@@ -51,6 +51,7 @@ export const IntegrationMultiSelectGrid = ({
search={search}
onSearchChange={setSearch}
placeholder={`${t("integration.page.list.search")}...`}
disableScroll={onboarding}
>
{filtered.map((integration) => {
const isSelected = selectedKinds.has(integration.kind);
+23 -17
View File
@@ -13,6 +13,7 @@ interface SelectGridLayoutProps {
onSearchChange: (value: string) => void;
placeholder: string;
onSearchKeyDown?: (event: KeyboardEvent<HTMLInputElement>) => void;
disableScroll?: boolean;
children: ReactNode;
}
@@ -21,22 +22,27 @@ export const SelectGridLayout = ({
onSearchChange,
placeholder,
onSearchKeyDown,
disableScroll = false,
children,
}: SelectGridLayoutProps) => (
<Stack>
<Input
value={search}
onChange={(event) => onSearchChange(event.currentTarget.value)}
leftSection={<IconSearch />}
placeholder={placeholder}
data-autofocus
onKeyDown={onSearchKeyDown}
/>
}: SelectGridLayoutProps) => {
const grid = (
<SimpleGrid cols={selectGridCols} spacing="sm">
{children}
</SimpleGrid>
);
<ScrollArea.Autosize mah={selectGridScrollMaxHeight}>
<SimpleGrid cols={selectGridCols} spacing="sm">
{children}
</SimpleGrid>
</ScrollArea.Autosize>
</Stack>
);
return (
<Stack>
<Input
value={search}
onChange={(event) => onSearchChange(event.currentTarget.value)}
leftSection={<IconSearch />}
placeholder={placeholder}
data-autofocus
onKeyDown={onSearchKeyDown}
/>
{disableScroll ? grid : <ScrollArea.Autosize mah={selectGridScrollMaxHeight}>{grid}</ScrollArea.Autosize>}
</Stack>
);
};