fix: finish widget modernization audit

This commit is contained in:
Thomas Camlong authored and ajnart committed 2026-08-13 19:54:50 +02:00
1 parent bbcfc505bc
commit 747ccdfa9e
27 files changed
+340 -115

No files matched your search

@@ -4,6 +4,7 @@ import { notFound, redirect } from "next/navigation";
import { AppShellMain } from "@mantine/core";
import { TRPCError } from "@trpc/server";
import { getRscUserSettingsAsync } from "@homarr/api/user-server";
import { auth } from "@homarr/auth/next";
import { BoardProvider } from "@homarr/boards/context";
import { EditModeProvider } from "@homarr/boards/edit-mode";
@@ -14,7 +15,8 @@ import { ModalProvider } from "@homarr/modals";
import { MainHeader } from "~/components/layout/header";
import { BoardLogoWithTitle } from "~/components/layout/logo/board-logo";
import { ClientShell } from "~/components/layout/shell";
import { BoardTourProvider } from "~/components/onboarding/board-tour";
import { BoardTourGate } from "~/components/onboarding/board-tour-gate";
import { env } from "~/env";
import { getCurrentColorSchemeAsync } from "~/theme/color-scheme";
import type { Board } from "./_types";
import type { Params } from "./(content)/_creator";
@@ -24,11 +26,6 @@ import { BoardMantineProvider } from "./(content)/_theme";
const logger = createLogger({ module: "createBoardLayout" });
const BoardTourWrapper = ({ hasSession, children }: PropsWithChildren<{ hasSession: boolean }>) => {
if (!hasSession) return <>{children}</>;
return <BoardTourProvider>{children}</BoardTourProvider>;
};
interface CreateBoardLayoutProps<TParams extends Params> {
headerActions: JSX.Element;
getInitialBoardAsync: (params: TParams) => Promise<Board>;
@@ -46,8 +43,34 @@ export const createBoardLayout = <TParams extends Params>({
}: PropsWithChildren<{
params: Promise<TParams>;
}>) => {
const session = await auth();
const initialBoard = await getInitialBoard(await params).catch((error) => {
const resolvedParams = await params;
const sessionPromise = auth();
const initialBoardPromise = getInitialBoard(resolvedParams).then(
(board) => ({ status: "fulfilled", board }) as const,
(error: unknown) => ({ status: "rejected", error }) as const,
);
const colorSchemePromise = getCurrentColorSchemeAsync();
const initialViewportWidthPromise = getInitialViewportWidthAsync();
const shouldRunBoardTourPromise = sessionPromise.then(async (session) => {
if (!withTour || !session || env.DEMO_MODE) return false;
try {
const user = await getRscUserSettingsAsync(session.user.id);
return user !== undefined && !user.completedBoardTour;
} catch (error) {
logger.error(new Error("Failed to load the board tour status", { cause: error }));
return false;
}
});
const [session, initialBoardResult, colorScheme, shouldRunBoardTour, initialViewportWidth] = await Promise.all([
sessionPromise,
initialBoardPromise,
colorSchemePromise,
shouldRunBoardTourPromise,
initialViewportWidthPromise,
]);
if (initialBoardResult.status === "rejected") {
const { error } = initialBoardResult;
if (error instanceof TRPCError && error.code === "NOT_FOUND") {
if (!session) {
logger.debug("No home board found for anonymous user, redirecting to login");
@@ -63,9 +86,8 @@ export const createBoardLayout = <TParams extends Params>({
}
throw error;
});
const colorScheme = await getCurrentColorSchemeAsync();
const initialViewportWidth = await getInitialViewportWidthAsync();
}
const initialBoard = initialBoardResult.board;
const initialLayoutId = getLayoutIdForViewportWidth(initialBoard.layouts, initialViewportWidth);
return (
@@ -79,16 +101,16 @@ export const createBoardLayout = <TParams extends Params>({
<BoardMantineProvider defaultColorScheme={colorScheme}>
<ModalProvider>
<CustomCss />
<BoardTourWrapper hasSession={withTour && !!session}>
<BoardTourGate enabled={shouldRunBoardTour}>
<ClientShell hasNavigation={false}>
<MainHeader
logo={<BoardLogoWithTitle size="md" hideTitleOnMobile />}
actions={headerActions}
hasNavigation={false}
/>
<AppShellMain>{children}</AppShellMain>
<AppShellMain data-advanced-focus-background>{children}</AppShellMain>
</ClientShell>
</BoardTourWrapper>
</BoardTourGate>
</ModalProvider>
</BoardMantineProvider>
</EditModeProvider>
@@ -70,7 +70,7 @@
.surfaceHostAdvanced {
position: fixed;
inset: 0;
z-index: calc(var(--mantine-z-index-modal) - 1);
z-index: var(--homarr-z-index-widget-focus);
width: auto;
height: auto;
pointer-events: none;
@@ -229,6 +229,19 @@ export const BoardAdvancedFocusProvider = ({ children }: PropsWithChildren) => {
if (isEditMode && activeRef.current) close(false);
}, [close, isEditMode]);
useEffect(() => {
if (active?.activation !== "manual") return;
const background = document.querySelectorAll<HTMLElement>("[data-advanced-focus-background]");
background.forEach((element) => {
element.inert = true;
});
return () => {
background.forEach((element) => {
element.inert = false;
});
};
}, [active?.activation]);
const value = useMemo(
() => ({ active, open, close, dismiss, hover, leave }),
[active, close, dismiss, hover, leave, open],
@@ -244,7 +257,7 @@ export const BoardAdvancedFocusProvider = ({ children }: PropsWithChildren) => {
data-advanced-focus-overlay
backgroundOpacity={0.38}
blur={0}
zIndex="calc(var(--mantine-z-index-modal) - 2)"
zIndex="var(--homarr-z-index-widget-preview-backdrop)"
className={`${classes.backdrop} ${active.phase === "closing" ? classes.backdropClosing : ""}`}
aria-hidden
/>
@@ -59,7 +59,7 @@ export const AdvancedFocusManualSurface = ({
radius={radius}
padding={0}
shadow="none"
zIndex="calc(var(--mantine-z-index-modal) - 1)"
zIndex="var(--homarr-z-index-widget-focus)"
closeOnEscape={false}
closeOnClickOutside
lockScroll
+7 -1
View File
@@ -18,7 +18,13 @@ interface Props {
export const MainHeader = ({ logo, actions, hasNavigation = true }: Props) => {
return (
<AppShellHeader maw="100vw" zIndex={201} style={{ overflowX: "hidden" }} data-app-shell-header>
<AppShellHeader
maw="100vw"
zIndex="var(--homarr-z-index-board-header)"
style={{ overflowX: "hidden" }}
data-advanced-focus-background
data-app-shell-header
>
<Group h="100%" gap="xl" px="md" justify="apart" wrap="nowrap">
<Group h="100%" align="center" style={{ flex: 1 }} wrap="nowrap">
{hasNavigation && <ClientBurger />}