fix: improve mobile header and board menus (#6715)
* fix: improve mobile header and board menus * fix: keep mobile header logo condensed * refactor: share mobile breakpoint handling
This commit is contained in:
1 parent
643ecf3df7
commit
9c441571bc
13 files changed
+208
-64
No files matched your search
@@ -15,6 +15,10 @@
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.results {
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.searchInput {
|
||||
border-color: color-mix(in srgb, var(--mantine-color-default-border) 72%, transparent);
|
||||
background: color-mix(in srgb, var(--mantine-color-body) 54%, transparent);
|
||||
@@ -44,3 +48,19 @@
|
||||
border-color: var(--mantine-primary-color-filled);
|
||||
box-shadow: var(--mantine-shadow-md);
|
||||
}
|
||||
|
||||
@media (max-width: $mantine-breakpoint-sm) {
|
||||
.modalContent {
|
||||
height: calc(100dvh - var(--modal-y-offset) * 2);
|
||||
}
|
||||
|
||||
.modalBody,
|
||||
.content {
|
||||
min-height: 0;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.results {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
@@ -228,7 +228,13 @@ export const BoardSwitcher = ({ children }: BoardSwitcherProps) => {
|
||||
radius="xl"
|
||||
classNames={{ input: classes.searchInput }}
|
||||
/>
|
||||
<ScrollArea.Autosize mah="min(80vh, 42rem)" type="never">
|
||||
<ScrollArea.Autosize
|
||||
mah="min(80vh, 42rem)"
|
||||
type="never"
|
||||
scrollbars="y"
|
||||
overscrollBehavior="contain"
|
||||
className={classes.results}
|
||||
>
|
||||
{results}
|
||||
</ScrollArea.Autosize>
|
||||
</Stack>
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
.mobileDrawerContent {
|
||||
height: min(80dvh, rem(640px));
|
||||
max-height: 80dvh;
|
||||
overscroll-behavior: contain;
|
||||
}
|
||||
|
||||
.mobileDrawerBody {
|
||||
padding-bottom: max(var(--mantine-spacing-md), env(safe-area-inset-bottom));
|
||||
}
|
||||
|
||||
.mobileActionList {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import type { MutableRefObject, ReactNode, RefObject } from "react";
|
||||
import { useCallback, useMemo, useState } from "react";
|
||||
import { Group, Loader, Menu, Text, Tooltip } from "@mantine/core";
|
||||
import { Drawer, Group, Loader, Menu, Text, Tooltip } from "@mantine/core";
|
||||
import { IconAlertTriangle, IconCircleCheck, IconMaximize, IconRefresh, IconSettings } from "@tabler/icons-react";
|
||||
import type { QueryClient, QueryKey } from "@tanstack/react-query";
|
||||
import { useIsFetching, useQueryClient } from "@tanstack/react-query";
|
||||
@@ -20,6 +20,7 @@ import { getWidgetName } from "@homarr/definitions";
|
||||
import { translateIfNecessary } from "@homarr/translation";
|
||||
import type { TranslationFunction } from "@homarr/translation";
|
||||
import { useI18n } from "@homarr/translation/client";
|
||||
import { useIsMobile } from "@homarr/ui/hooks";
|
||||
import type { WidgetDefinition, WidgetRuntimeRef } from "@homarr/widgets/definition";
|
||||
import { getWidgetQueryKeys, getWidgetRuntimeQueries, supportsAdvancedFocus } from "@homarr/widgets/definition";
|
||||
import { reduceWidgetOptionsWithDefinition } from "@homarr/widgets/manifest";
|
||||
@@ -31,6 +32,9 @@ import { useBoardPermissions } from "../permissions/client";
|
||||
import { useItemActions } from "./item-actions";
|
||||
import { LazyWidgetEditModal, preloadWidgetEditModal } from "./lazy-widget-edit-modal";
|
||||
import { matchesWidgetItemQuery } from "./widget-query-scope";
|
||||
import classes from "./widget-context-menu.module.css";
|
||||
|
||||
type ToggleOption = [key: string, option: { type: string; skipContextMenu?: boolean }];
|
||||
|
||||
interface WidgetContextMenuProps {
|
||||
item: SectionItem;
|
||||
@@ -134,11 +138,12 @@ export const WidgetContextMenu = ({
|
||||
[item.id, persistBoard],
|
||||
);
|
||||
|
||||
type OptionDef = { type: string; skipContextMenu?: boolean };
|
||||
const toggleOptions = useMemo(() => {
|
||||
if (!canConfigureWidget) return [];
|
||||
const rawOptions = definition.createOptions(settings) as unknown as Record<string, OptionDef>;
|
||||
return Object.entries(rawOptions).filter(([, option]) => option.type === "switch" && !option.skipContextMenu);
|
||||
const rawOptions = definition.createOptions(settings) as unknown as Record<string, ToggleOption[1]>;
|
||||
return Object.entries(rawOptions).filter(
|
||||
([, option]) => option.type === "switch" && !option.skipContextMenu,
|
||||
) as ToggleOption[];
|
||||
}, [canConfigureWidget, definition, settings]);
|
||||
|
||||
const widgetContextActions =
|
||||
@@ -222,6 +227,18 @@ export const WidgetContextMenu = ({
|
||||
[setItemOptions],
|
||||
);
|
||||
|
||||
const handleOpenAdvancedFocus = useCallback(() => {
|
||||
if (!sourceRef.current) return;
|
||||
openAdvancedFocus(item.id, sourceRef.current, {
|
||||
restoreFocusTarget:
|
||||
sourceRef.current.querySelector<HTMLElement>("[data-advanced-focus-trigger]") ?? sourceRef.current,
|
||||
});
|
||||
}, [item.id, openAdvancedFocus, sourceRef]);
|
||||
|
||||
const handleCloseMenu = useCallback(() => {
|
||||
setMenuOpened(false);
|
||||
}, []);
|
||||
|
||||
if (!session || !settings.enableRightClickOnWidgets || isEditMode || item.kind === "app") {
|
||||
return <>{children}</>;
|
||||
}
|
||||
@@ -239,21 +256,10 @@ export const WidgetContextMenu = ({
|
||||
onChange={setMenuOpened}
|
||||
>
|
||||
<Menu.ContextMenu>{children}</Menu.ContextMenu>
|
||||
<Menu.Dropdown>
|
||||
<WidgetContextMenuDropdown opened={menuOpened} onClose={handleCloseMenu} title={getWidgetName(item.kind, t)}>
|
||||
{canOpenAdvancedFocus && (
|
||||
<>
|
||||
<Menu.Item
|
||||
closeMenuOnClick
|
||||
leftSection={<IconMaximize size={16} />}
|
||||
onClick={() => {
|
||||
if (sourceRef.current)
|
||||
openAdvancedFocus(item.id, sourceRef.current, {
|
||||
restoreFocusTarget:
|
||||
sourceRef.current.querySelector<HTMLElement>("[data-advanced-focus-trigger]") ??
|
||||
sourceRef.current,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Menu.Item closeMenuOnClick leftSection={<IconMaximize size={16} />} onClick={handleOpenAdvancedFocus}>
|
||||
{t("item.advancedFocus.open")}
|
||||
</Menu.Item>
|
||||
<Menu.Divider />
|
||||
@@ -319,11 +325,39 @@ export const WidgetContextMenu = ({
|
||||
>
|
||||
{tMenu("settings")}
|
||||
</Menu.Item>
|
||||
</Menu.Dropdown>
|
||||
</WidgetContextMenuDropdown>
|
||||
</Menu>
|
||||
);
|
||||
};
|
||||
|
||||
interface WidgetContextMenuDropdownProps {
|
||||
opened: boolean;
|
||||
onClose: () => void;
|
||||
title: string;
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
const WidgetContextMenuDropdown = ({ opened, onClose, title, children }: WidgetContextMenuDropdownProps) => {
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
if (!isMobile) return <Menu.Dropdown>{children}</Menu.Dropdown>;
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
opened={opened}
|
||||
onClose={onClose}
|
||||
position="bottom"
|
||||
size="min(80dvh, 40rem)"
|
||||
title={title}
|
||||
classNames={{ content: classes.mobileDrawerContent, body: classes.mobileDrawerBody }}
|
||||
>
|
||||
<div className={classes.mobileActionList} role="menu" aria-orientation="vertical" data-menu-dropdown>
|
||||
{children}
|
||||
</div>
|
||||
</Drawer>
|
||||
);
|
||||
};
|
||||
|
||||
interface WidgetQueryStatusProps {
|
||||
queryClient: QueryClient;
|
||||
matchesQuery: (queryKey: QueryKey) => boolean;
|
||||
|
||||
Reference in new issue
Block a user