diff --git a/apps/docs/docs/advanced/keyboard-shortcuts/index.mdx b/apps/docs/docs/advanced/keyboard-shortcuts/index.mdx
index 5d7f01cde..1f7842fd7 100644
--- a/apps/docs/docs/advanced/keyboard-shortcuts/index.mdx
+++ b/apps/docs/docs/advanced/keyboard-shortcuts/index.mdx
@@ -12,7 +12,7 @@ Homarr has the following keyboard shortcuts:
| `MOD` + `J` | Toggles Dark Mode |
| `MOD` + `E` | Toggles Edit Mode |
| Hold `Shift` while hovering a widget | Opens its temporary advanced view |
-| `Shift` + `Enter` on a focused widget | Opens and pins its advanced view |
+| `Shift` + `Enter` on a focused widget | Opens its advanced view until it is closed |
| `Esc` | Closes an open advanced view or board menu |
| `Shift` + `F10` on a focused board section | Opens the precise add-item menu at the next available cell |
@@ -20,8 +20,9 @@ Homarr has the following keyboard shortcuts:
Here `MOD` refers to the modifier key, which is `Ctrl` on Windows and `Cmd` on macOS.
-Advanced view and precise placement are available outside edit mode. Precise placement still requires permission to
-change the board and the right-click interaction preference to be enabled.
+Advanced view and precise placement are available outside edit mode. App tiles stay compact and do not have an
+advanced view. Precise placement still requires permission to change the board and the right-click interaction
+preference to be enabled.
:::
diff --git a/apps/docs/docs/widgets/anchor-note/index.mdx b/apps/docs/docs/widgets/anchor-note/index.mdx
index bfc520c6a..522252a1a 100644
--- a/apps/docs/docs/widgets/anchor-note/index.mdx
+++ b/apps/docs/docs/widgets/anchor-note/index.mdx
@@ -35,7 +35,7 @@ import anchorNoteWidgetScreenshot from "./img/widget.png";
### Interactions
-- Click **Edit** to start editing the selected note.
+- Hover the widget, focus within it, or use a touch device to reveal **Edit**, then select it to start editing the note.
- Click **Save** to persist changes to Anchor.
- Click **Cancel** to discard unsaved changes.
diff --git a/apps/docs/docs/widgets/app/index.mdx b/apps/docs/docs/widgets/app/index.mdx
index 4fdc3ff5f..dd5cef590 100644
--- a/apps/docs/docs/widgets/app/index.mdx
+++ b/apps/docs/docs/widgets/app/index.mdx
@@ -4,8 +4,6 @@ description: "Embeds an app into the board."
hide_title: true
---
-import { WidgetInteractionGuide } from "@site/src/components/widgets/interactions";
-
import { AddingWidget } from "@site/src/components/widgets/adding";
import { WidgetConfig } from "@site/src/components/widgets/configuration";
import { WidgetHeader } from "@site/src/components/widgets/header";
@@ -27,7 +25,10 @@ import piHoleApp from "./img/pi-hole-ping-icon.png";
-
- Advanced view reuses the widget's live data and cache. Touch devices expose a maximize control; board actions - respect your permissions and right-click preference. + Advanced view reuses the widget's live data and cache. The rest of the board stays visible but dimmed. Touch + devices expose a maximize control; app tiles stay compact.
); diff --git a/apps/nextjs/src/components/board/advanced-focus/advanced-focus.module.css b/apps/nextjs/src/components/board/advanced-focus/advanced-focus.module.css index 39965dbdf..60cc31954 100644 --- a/apps/nextjs/src/components/board/advanced-focus/advanced-focus.module.css +++ b/apps/nextjs/src/components/board/advanced-focus/advanced-focus.module.css @@ -1,28 +1,18 @@ -.focusScope { - height: 100%; -} - .backdrop { - position: fixed; - inset: 0; - z-index: 198; - background: rgba(0, 0, 0, 0.64); - backdrop-filter: blur(6px) saturate(0.75); - touch-action: none; + pointer-events: none; animation: focus-backdrop-in 180ms cubic-bezier(0.16, 1, 0.3, 1); } .surface { position: fixed; - z-index: 199; + z-index: 1; pointer-events: auto; overflow: hidden; background: var(--mantine-color-body); - box-shadow: var(--mantine-shadow-lg); + box-shadow: var(--mantine-shadow-md); overscroll-behavior: contain; - animation: focus-surface-in 220ms cubic-bezier(0.16, 1, 0.3, 1); + animation: focus-surface-in 180ms cubic-bezier(0.16, 1, 0.3, 1); transform-origin: top left; - padding-top: 60px; } .backdropClosing { @@ -45,7 +35,7 @@ .surfaceHostAdvanced { position: fixed; inset: 0; - z-index: 199; + z-index: calc(var(--mantine-z-index-modal) - 1); width: auto; height: auto; pointer-events: none; @@ -58,33 +48,8 @@ pointer-events: none; } -.surfaceControlsAdvanced { - right: 0; - bottom: auto; - height: 60px; - background: color-mix(in srgb, var(--mantine-color-body) 92%, transparent); - border-bottom: 1px solid var(--mantine-color-default-border); -} - -.surfaceTitle { - position: absolute; - top: 20px; - right: 120px; - left: 16px; -} - .closeButton { - position: absolute; - top: 8px; - right: 8px; - z-index: 2; - pointer-events: auto; -} - -.pinButton { - position: absolute; - top: 8px; - right: 60px; + position: fixed; z-index: 2; pointer-events: auto; } @@ -141,8 +106,7 @@ @media (forced-colors: active) { .backdrop { - backdrop-filter: none; background: Canvas; - opacity: 0.8; + opacity: 0.4; } } diff --git a/apps/nextjs/src/components/board/advanced-focus/context.tsx b/apps/nextjs/src/components/board/advanced-focus/context.tsx index 79e4ff50d..3f38bdc44 100644 --- a/apps/nextjs/src/components/board/advanced-focus/context.tsx +++ b/apps/nextjs/src/components/board/advanced-focus/context.tsx @@ -2,7 +2,7 @@ import type { PropsWithChildren } from "react"; import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react"; -import { Box, Portal, RemoveScroll } from "@mantine/core"; +import { Overlay, Portal } from "@mantine/core"; import { useEditMode } from "@homarr/boards/edit-mode"; @@ -14,17 +14,21 @@ interface ActiveFocus { itemId: string; source: HTMLElement; sourceRect: FocusRect; - pinned: boolean; + activation: "preview" | "manual"; autofocusClose: boolean; phase: "visible" | "closing"; } +interface OpenAdvancedFocusOptions { + activation?: ActiveFocus["activation"]; + autofocusClose?: boolean; +} + interface AdvancedFocusContextValue { active: ActiveFocus | null; - open: (itemId: string, source: HTMLElement, pinned?: boolean) => void; + open: (itemId: string, source: HTMLElement, options?: OpenAdvancedFocusOptions) => void; close: (restoreFocus?: boolean) => void; dismiss: (itemId: string) => void; - pin: (autofocusClose?: boolean) => void; hover: (itemId: string, source: HTMLElement) => void; leave: (itemId: string) => void; } @@ -64,7 +68,7 @@ export const BoardAdvancedFocusProvider = ({ children }: PropsWithChildren) => { }, []); const open = useCallback( - (itemId: string, source: HTMLElement, pinned = false) => { + (itemId: string, source: HTMLElement, options: OpenAdvancedFocusOptions = {}) => { if (isEditMode) return; cancelHoverTimer(); cancelCloseTimer(); @@ -73,8 +77,8 @@ export const BoardAdvancedFocusProvider = ({ children }: PropsWithChildren) => { itemId, source, sourceRect: { left: rect.left, top: rect.top, width: rect.width, height: rect.height }, - pinned, - autofocusClose: pinned, + activation: options.activation ?? "manual", + autofocusClose: options.autofocusClose ?? false, phase: "visible", }); }, @@ -101,15 +105,23 @@ export const BoardAdvancedFocusProvider = ({ children }: PropsWithChildren) => { cancelHoverTimer(); const hovered = hoveredRef.current; if (!hovered || !shiftHeldRef.current || activeRef.current) return; - hoverTimerRef.current = setTimeout(() => open(hovered.itemId, hovered.source), HOVER_DELAY_MS); + hoverTimerRef.current = setTimeout( + () => open(hovered.itemId, hovered.source, { activation: "preview" }), + HOVER_DELAY_MS, + ); }, [cancelHoverTimer, open]); const hover = useCallback( (itemId: string, source: HTMLElement) => { hoveredRef.current = { itemId, source }; + const current = activeRef.current; + if (current?.activation === "preview" && current.itemId !== itemId) { + cancelCloseTimer(); + updateActive(null); + } startHoverTimer(); }, - [startHoverTimer], + [cancelCloseTimer, startHoverTimer, updateActive], ); const leave = useCallback( @@ -130,15 +142,6 @@ export const BoardAdvancedFocusProvider = ({ children }: PropsWithChildren) => { [cancelCloseTimer, cancelHoverTimer, updateActive], ); - const pin = useCallback( - (autofocusClose = false) => { - const current = activeRef.current; - if (!current || current.pinned || current.phase === "closing") return; - updateActive({ ...current, pinned: true, autofocusClose }); - }, - [updateActive], - ); - useEffect(() => { const handleKeyDown = (event: KeyboardEvent) => { if (event.key === "Escape" && activeRef.current) { @@ -151,7 +154,7 @@ export const BoardAdvancedFocusProvider = ({ children }: PropsWithChildren) => { } if (isEditMode) return; if (event.key !== "Shift" || event.repeat || isEditableTarget(event.target)) return; - if (activeRef.current?.pinned) return; + if (activeRef.current?.activation === "manual") return; shiftHeldRef.current = true; startHoverTimer(); }; @@ -159,12 +162,12 @@ export const BoardAdvancedFocusProvider = ({ children }: PropsWithChildren) => { if (event.key !== "Shift") return; shiftHeldRef.current = false; cancelHoverTimer(); - if (activeRef.current && !activeRef.current.pinned) close(false); + if (activeRef.current?.activation === "preview") close(false); }; const handleBlur = () => { shiftHeldRef.current = false; cancelHoverTimer(); - if (activeRef.current && !activeRef.current.pinned) close(false); + if (activeRef.current?.activation === "preview") close(false); }; window.addEventListener("keydown", handleKeyDown); @@ -184,20 +187,22 @@ export const BoardAdvancedFocusProvider = ({ children }: PropsWithChildren) => { }, [close, isEditMode]); const value = useMemo( - () => ({ active, open, close, dismiss, pin, hover, leave }), - [active, close, dismiss, hover, leave, open, pin], + () => ({ active, open, close, dismiss, hover, leave }), + [active, close, dismiss, hover, leave, open], ); return (
+
{temperature}°C
: null} +