diff --git a/apps/docs/docs/widgets/notebook/index.mdx b/apps/docs/docs/widgets/notebook/index.mdx index a245600aa..df4d14ec2 100644 --- a/apps/docs/docs/widgets/notebook/index.mdx +++ b/apps/docs/docs/widgets/notebook/index.mdx @@ -38,7 +38,7 @@ import editOn from "./img/edit.png"; ### Editing -When the permissions of the user allows it, you can enter the editing of the text by clicking the following button in the top right. +When your permissions allow it, hover the widget, focus within it, or use a touch device to reveal the edit button in the top right. You can also double-click the document to edit it directly.  diff --git a/apps/docs/src/components/widgets/interactions.tsx b/apps/docs/src/components/widgets/interactions.tsx index 80ba47b46..1328aa548 100644 --- a/apps/docs/src/components/widgets/interactions.tsx +++ b/apps/docs/src/components/widgets/interactions.tsx @@ -1,11 +1,11 @@ -import { IconArrowsMaximize, IconMouse2, IconPlus } from "@tabler/icons-react"; +import { IconArrowsMaximize, IconLayoutGrid, IconMouse2, IconPlus } from "@tabler/icons-react"; const interactions = [ { icon: IconArrowsMaximize, title: "Advanced view", description: - "Hold Shift while hovering (then pin it), press Shift + Enter, or choose Open advanced view from the widget menu.", + "Hold Shift while hovering for an in-place preview, press Shift + Enter, or choose Open advanced view from the widget menu.", }, { icon: IconMouse2, @@ -19,6 +19,12 @@ const interactions = [ description: "With board changes allowed, right-click or touch and hold empty grid space to add there. Keyboard users can press Shift + F10.", }, + { + icon: IconLayoutGrid, + title: "Adaptive detail", + description: + "Resize a widget to tune its density. Compact views keep primary status visible, then reveal details and controls as space or intent allows.", + }, ] as const; export const WidgetInteractionGuide = () => ( @@ -30,7 +36,7 @@ export const WidgetInteractionGuide = () => ( Hide shortcuts -
- 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} +