feat: rework management settings layout
This commit is contained in:
1 parent
a2219b72f6
commit
c09f3e7501
6 files changed
+100
-9
No files matched your search
@@ -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
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ export const AppearanceSettingsForm = ({ form }: AppearanceSettingsFormProps) =>
|
||||
}))}
|
||||
{...form.getInputProps("defaultColorScheme")}
|
||||
SelectOption={AppearanceCustomOption}
|
||||
withinPortal
|
||||
/>
|
||||
</SectionCard>
|
||||
);
|
||||
|
||||
@@ -26,6 +26,7 @@ export const BoardSettingsForm = ({ form }: BoardSettingsFormProps) => {
|
||||
clearable
|
||||
boards={selectableBoards}
|
||||
{...form.getInputProps("homeBoardId")}
|
||||
withinPortal
|
||||
/>
|
||||
|
||||
<BoardSelect
|
||||
@@ -34,6 +35,7 @@ export const BoardSettingsForm = ({ form }: BoardSettingsFormProps) => {
|
||||
clearable
|
||||
boards={selectableBoards}
|
||||
{...form.getInputProps("mobileHomeBoardId")}
|
||||
withinPortal
|
||||
/>
|
||||
|
||||
<Text fw={500}>{tBoard("status.title")}</Text>
|
||||
|
||||
@@ -22,6 +22,7 @@ export const CultureSettingsForm = ({ form }: CultureSettingsFormProps) => {
|
||||
label={tCulture("defaultLocale.label")}
|
||||
{...localeInputProps}
|
||||
value={localeInputProps.value as SupportedLanguage}
|
||||
withinPortal
|
||||
/>
|
||||
</SectionCard>
|
||||
);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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 (
|
||||
<form onSubmit={form.onSubmit((values) => void handleSubmitAsync(values))}>
|
||||
<Stack gap="xl">
|
||||
<AnalyticsSettings form={form} />
|
||||
<CrawlingAndIndexingSettings form={form} />
|
||||
<BoardSettingsForm form={form} />
|
||||
<UserSettingsForm form={form} />
|
||||
<SearchSettingsForm form={form} />
|
||||
<AppearanceSettingsForm form={form} />
|
||||
<CultureSettingsForm form={form} />
|
||||
<Tabs defaultValue="board" orientation={isDesktop ? "vertical" : "horizontal"}>
|
||||
<TabsList className={classes.list} aria-label={tSettings("title")}>
|
||||
<TabsTab value="board" className={classes.tab} leftSection={<IconLayoutDashboard size={18} />}>
|
||||
{tSettings("section.board.title")}
|
||||
</TabsTab>
|
||||
<TabsTab value="user" className={classes.tab} leftSection={<IconUsers size={18} />}>
|
||||
{tSettings("section.user.title")}
|
||||
</TabsTab>
|
||||
<TabsTab value="search" className={classes.tab} leftSection={<IconSearch size={18} />}>
|
||||
{tSettings("section.search.title")}
|
||||
</TabsTab>
|
||||
<TabsTab value="appearance" className={classes.tab} leftSection={<IconPalette size={18} />}>
|
||||
{tSettings("section.appearance.title")}
|
||||
</TabsTab>
|
||||
<TabsTab value="culture" className={classes.tab} leftSection={<IconLanguage size={18} />}>
|
||||
{tSettings("section.culture.title")}
|
||||
</TabsTab>
|
||||
<TabsTab value="analytics" className={classes.tab} leftSection={<IconChartBar size={18} />}>
|
||||
{tSettings("section.analytics.title")}
|
||||
</TabsTab>
|
||||
<TabsTab value="crawling" className={classes.tab} leftSection={<IconWorldSearch size={18} />}>
|
||||
{tSettings("section.crawlingAndIndexing.title")}
|
||||
</TabsTab>
|
||||
</TabsList>
|
||||
|
||||
<TabsPanel value="board" className={classes.panel}>
|
||||
<BoardSettingsForm form={form} />
|
||||
</TabsPanel>
|
||||
<TabsPanel value="user" className={classes.panel}>
|
||||
<UserSettingsForm form={form} />
|
||||
</TabsPanel>
|
||||
<TabsPanel value="search" className={classes.panel}>
|
||||
<SearchSettingsForm form={form} />
|
||||
</TabsPanel>
|
||||
<TabsPanel value="appearance" className={classes.panel}>
|
||||
<AppearanceSettingsForm form={form} />
|
||||
</TabsPanel>
|
||||
<TabsPanel value="culture" className={classes.panel}>
|
||||
<CultureSettingsForm form={form} />
|
||||
</TabsPanel>
|
||||
<TabsPanel value="analytics" className={classes.panel}>
|
||||
<AnalyticsSettings form={form} />
|
||||
</TabsPanel>
|
||||
<TabsPanel value="crawling" className={classes.panel}>
|
||||
<CrawlingAndIndexingSettings form={form} />
|
||||
</TabsPanel>
|
||||
</Tabs>
|
||||
|
||||
{form.isDirty() && (
|
||||
<UnsavedChangesBar>
|
||||
|
||||
Reference in new issue
Block a user