From c09f3e7501437c4a66c195994d27e30b4d140985 Mon Sep 17 00:00:00 2001 From: ajnart Date: Thu, 20 Aug 2026 11:48:13 +0200 Subject: [PATCH] feat: rework management settings layout --- apps/docs/docs/management/settings/index.mdx | 2 +- .../_components/appearance-settings-form.tsx | 1 + .../_components/board-settings-form.tsx | 2 + .../_components/culture-settings-form.tsx | 1 + .../_components/settings-form.module.css | 35 ++++++++++ .../settings/_components/settings-form.tsx | 68 ++++++++++++++++--- 6 files changed, 100 insertions(+), 9 deletions(-) create mode 100644 apps/nextjs/src/app/[locale]/manage/settings/_components/settings-form.module.css diff --git a/apps/docs/docs/management/settings/index.mdx b/apps/docs/docs/management/settings/index.mdx index 8507d2b4f..7ab1ef4e3 100644 --- a/apps/docs/docs/management/settings/index.mdx +++ b/apps/docs/docs/management/settings/index.mdx @@ -13,7 +13,7 @@ tags: The settings page applies options to the whole Homarr application. Some changes need a full page reload before they take effect. -All changes are collected in a single form. Click the **Save changes** button in the save bar at the bottom of the page to apply them. The save bar only appears when you have unsaved changes, and the **Discard** button reverts your changes. +Choose a section from the settings navigation to edit its options. All sections are collected in a single form, so you can move between them without losing unsaved changes. Click the **Save changes** button in the save bar at the bottom of the page to apply them. The save bar only appears when you have unsaved changes, and the **Discard** button reverts your changes. ## Analytics diff --git a/apps/nextjs/src/app/[locale]/manage/settings/_components/appearance-settings-form.tsx b/apps/nextjs/src/app/[locale]/manage/settings/_components/appearance-settings-form.tsx index e33afe1b6..bd09e35df 100644 --- a/apps/nextjs/src/app/[locale]/manage/settings/_components/appearance-settings-form.tsx +++ b/apps/nextjs/src/app/[locale]/manage/settings/_components/appearance-settings-form.tsx @@ -29,6 +29,7 @@ export const AppearanceSettingsForm = ({ form }: AppearanceSettingsFormProps) => }))} {...form.getInputProps("defaultColorScheme")} SelectOption={AppearanceCustomOption} + withinPortal /> ); diff --git a/apps/nextjs/src/app/[locale]/manage/settings/_components/board-settings-form.tsx b/apps/nextjs/src/app/[locale]/manage/settings/_components/board-settings-form.tsx index 801e0df9f..2a5b733ff 100644 --- a/apps/nextjs/src/app/[locale]/manage/settings/_components/board-settings-form.tsx +++ b/apps/nextjs/src/app/[locale]/manage/settings/_components/board-settings-form.tsx @@ -26,6 +26,7 @@ export const BoardSettingsForm = ({ form }: BoardSettingsFormProps) => { clearable boards={selectableBoards} {...form.getInputProps("homeBoardId")} + withinPortal /> { clearable boards={selectableBoards} {...form.getInputProps("mobileHomeBoardId")} + withinPortal /> {tBoard("status.title")} diff --git a/apps/nextjs/src/app/[locale]/manage/settings/_components/culture-settings-form.tsx b/apps/nextjs/src/app/[locale]/manage/settings/_components/culture-settings-form.tsx index da34c7019..aa8b8ae10 100644 --- a/apps/nextjs/src/app/[locale]/manage/settings/_components/culture-settings-form.tsx +++ b/apps/nextjs/src/app/[locale]/manage/settings/_components/culture-settings-form.tsx @@ -22,6 +22,7 @@ export const CultureSettingsForm = ({ form }: CultureSettingsFormProps) => { label={tCulture("defaultLocale.label")} {...localeInputProps} value={localeInputProps.value as SupportedLanguage} + withinPortal /> ); diff --git a/apps/nextjs/src/app/[locale]/manage/settings/_components/settings-form.module.css b/apps/nextjs/src/app/[locale]/manage/settings/_components/settings-form.module.css new file mode 100644 index 000000000..80e3386f4 --- /dev/null +++ b/apps/nextjs/src/app/[locale]/manage/settings/_components/settings-form.module.css @@ -0,0 +1,35 @@ +.list { + align-self: flex-start; + flex-wrap: nowrap; + min-width: 14rem; + position: sticky; + top: calc(var(--app-shell-header-height) + var(--mantine-spacing-md)); +} + +.tab { + justify-content: flex-start; +} + +.panel { + flex: 1; + min-width: 0; + padding-inline-start: var(--mantine-spacing-xl); +} + +@media (max-width: $mantine-breakpoint-sm) { + .list { + max-width: calc(100vw - var(--mantine-spacing-xl)); + min-width: 0; + overflow-x: auto; + position: static; + } + + .tab { + flex: 0 0 auto; + } + + .panel { + padding-block-start: var(--mantine-spacing-lg); + padding-inline-start: 0; + } +} diff --git a/apps/nextjs/src/app/[locale]/manage/settings/_components/settings-form.tsx b/apps/nextjs/src/app/[locale]/manage/settings/_components/settings-form.tsx index ce64a3404..f267f00b8 100644 --- a/apps/nextjs/src/app/[locale]/manage/settings/_components/settings-form.tsx +++ b/apps/nextjs/src/app/[locale]/manage/settings/_components/settings-form.tsx @@ -1,7 +1,17 @@ "use client"; import { useEffect, useRef, useState } from "react"; -import { Button, Stack } from "@mantine/core"; +import { Button, Stack, Tabs, TabsList, TabsPanel, TabsTab } from "@mantine/core"; +import { useMediaQuery } from "@mantine/hooks"; +import { + IconChartBar, + IconLanguage, + IconLayoutDashboard, + IconPalette, + IconSearch, + IconUsers, + IconWorldSearch, +} from "@tabler/icons-react"; import { z } from "zod/v4"; import { clientApi } from "@homarr/api/client"; @@ -20,6 +30,7 @@ import { BoardSettingsForm } from "./board-settings-form"; import { CrawlingAndIndexingSettings } from "./crawling-and-indexing.settings"; import { CultureSettingsForm } from "./culture-settings-form"; import { SearchSettingsForm } from "./search-settings-form"; +import classes from "./settings-form.module.css"; import { UserSettingsForm } from "./user-settings-form"; const settingsFormSchema = z.object({ @@ -63,6 +74,7 @@ interface SettingsFormProps { export const SettingsForm = ({ initialData }: SettingsFormProps) => { const t = useI18n(); const tSettings = useScopedI18n("management.page.settings"); + const isDesktop = useMediaQuery("(min-width: 48em)"); const initialValues = buildInitialValues(initialData); const initialValuesRef = useRef(initialValues); @@ -169,13 +181,53 @@ export const SettingsForm = ({ initialData }: SettingsFormProps) => { return (
void handleSubmitAsync(values))}> - - - - - - - + + + }> + {tSettings("section.board.title")} + + }> + {tSettings("section.user.title")} + + }> + {tSettings("section.search.title")} + + }> + {tSettings("section.appearance.title")} + + }> + {tSettings("section.culture.title")} + + }> + {tSettings("section.analytics.title")} + + }> + {tSettings("section.crawlingAndIndexing.title")} + + + + + + + + + + + + + + + + + + + + + + + + + {form.isDirty() && (