From 07bfa3ba7382bd97e71267373646f6f6e0f0c4bb Mon Sep 17 00:00:00 2001 From: Philipinho <16838612+Philipinho@users.noreply.github.com> Date: Mon, 15 Jun 2026 05:23:19 +0100 Subject: [PATCH] fix(base): close view popovers on Escape regardless of focus; drop redundant property switch tab stop --- .../ee/base/components/views/view-filter-config.tsx | 2 ++ .../components/views/view-property-visibility.tsx | 7 +++++++ .../ee/base/components/views/view-sort-config.tsx | 2 ++ apps/client/src/ee/base/hooks/use-escape-close.ts | 12 ++++++++++++ 4 files changed, 23 insertions(+) create mode 100644 apps/client/src/ee/base/hooks/use-escape-close.ts diff --git a/apps/client/src/ee/base/components/views/view-filter-config.tsx b/apps/client/src/ee/base/components/views/view-filter-config.tsx index 200f7224f..c860845d5 100644 --- a/apps/client/src/ee/base/components/views/view-filter-config.tsx +++ b/apps/client/src/ee/base/components/views/view-filter-config.tsx @@ -24,6 +24,7 @@ import { } from "@/ee/base/property-types/property-type.registry"; import { FilterPersonInput } from "./filter-person-input"; import { FilterDateInput } from "./filter-date-input"; +import { useEscapeClose } from "@/ee/base/hooks/use-escape-close"; import viewClasses from "@/ee/base/styles/views.module.css"; const OPERATORS: { value: FilterOperator; labelKey: string }[] = [ @@ -191,6 +192,7 @@ export function ViewFilterConfigPopover({ children, }: ViewFilterConfigProps) { const { t } = useTranslation(); + useEscapeClose(opened, onClose); const propertyOptions = properties.map((p) => ({ value: p.id, diff --git a/apps/client/src/ee/base/components/views/view-property-visibility.tsx b/apps/client/src/ee/base/components/views/view-property-visibility.tsx index 4fb7db7bb..befbf4b03 100644 --- a/apps/client/src/ee/base/components/views/view-property-visibility.tsx +++ b/apps/client/src/ee/base/components/views/view-property-visibility.tsx @@ -4,6 +4,7 @@ import { Table } from "@tanstack/react-table"; import { IBaseRow, IBaseProperty } from "@/ee/base/types/base.types"; import { propertyTypes } from "@/ee/base/property-types/property-type.registry"; import { useTranslation } from "react-i18next"; +import { useEscapeClose } from "@/ee/base/hooks/use-escape-close"; import cellClasses from "@/ee/base/styles/cells.module.css"; import viewClasses from "@/ee/base/styles/views.module.css"; @@ -25,6 +26,7 @@ export function ViewPropertyVisibility({ children, }: ViewPropertyVisibilityProps) { const { t } = useTranslation(); + useEscapeClose(opened, onClose); const columns = useMemo(() => { return table @@ -122,6 +124,9 @@ export function ViewPropertyVisibility({ return ( { if (canHide) { @@ -140,6 +145,8 @@ export function ViewPropertyVisibility({ size="xs" checked={isVisible} disabled={!canHide} + tabIndex={-1} + aria-hidden onChange={() => {}} // Clicking the track synthesizes a second click on the hidden input which bubbles // to UnstyledButton, firing handleToggle twice. stopPropagation blocks only that diff --git a/apps/client/src/ee/base/components/views/view-sort-config.tsx b/apps/client/src/ee/base/components/views/view-sort-config.tsx index a2751279c..d80d2cf7c 100644 --- a/apps/client/src/ee/base/components/views/view-sort-config.tsx +++ b/apps/client/src/ee/base/components/views/view-sort-config.tsx @@ -15,6 +15,7 @@ import { ViewSortConfig, } from "@/ee/base/types/base.types"; import { useTranslation } from "react-i18next"; +import { useEscapeClose } from "@/ee/base/hooks/use-escape-close"; import viewClasses from "@/ee/base/styles/views.module.css"; type ViewSortConfigProps = { @@ -35,6 +36,7 @@ export function ViewSortConfigPopover({ children, }: ViewSortConfigProps) { const { t } = useTranslation(); + useEscapeClose(opened, onClose); const [draft, setDraft] = useState(null); useEffect(() => { diff --git a/apps/client/src/ee/base/hooks/use-escape-close.ts b/apps/client/src/ee/base/hooks/use-escape-close.ts new file mode 100644 index 000000000..67c78b2a5 --- /dev/null +++ b/apps/client/src/ee/base/hooks/use-escape-close.ts @@ -0,0 +1,12 @@ +import { useEffect } from "react"; + +export function useEscapeClose(opened: boolean, onClose: () => void) { + useEffect(() => { + if (!opened) return; + const onKeyDown = (e: KeyboardEvent) => { + if (e.key === "Escape" && !e.defaultPrevented) onClose(); + }; + document.addEventListener("keydown", onKeyDown); + return () => document.removeEventListener("keydown", onKeyDown); + }, [opened, onClose]); +}