mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-04-24 01:28:35 +02:00
Add FocusTrap to dropdown menu to fix filtering
This commit is contained in:
@@ -12,6 +12,7 @@ interface Props {
|
|||||||
onClose?: () => void;
|
onClose?: () => void;
|
||||||
zIndex?: keyof typeof zIndexes;
|
zIndex?: keyof typeof zIndexes;
|
||||||
variant?: 'default' | 'transparent';
|
variant?: 'default' | 'transparent';
|
||||||
|
noBackdrop?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const zIndexes: Record<number, string> = {
|
const zIndexes: Record<number, string> = {
|
||||||
@@ -28,8 +29,21 @@ export function Overlay({
|
|||||||
open,
|
open,
|
||||||
onClose,
|
onClose,
|
||||||
portalName,
|
portalName,
|
||||||
|
noBackdrop,
|
||||||
children,
|
children,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
|
if (noBackdrop) {
|
||||||
|
return (
|
||||||
|
<Portal name={portalName}>
|
||||||
|
{open && (
|
||||||
|
<FocusTrap focusTrapOptions={{ clickOutsideDeactivates: true }}>
|
||||||
|
{/* NOTE: <div> wrapper is required for some reason, or FocusTrap complains */}
|
||||||
|
<div>{children}</div>
|
||||||
|
</FocusTrap>
|
||||||
|
)}
|
||||||
|
</Portal>
|
||||||
|
);
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<Portal name={portalName}>
|
<Portal name={portalName}>
|
||||||
{open && (
|
{open && (
|
||||||
@@ -48,7 +62,7 @@ export function Overlay({
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Show draggable region at the top */}
|
{/* Show the draggable region at the top */}
|
||||||
{/* TODO: Figure out tauri drag region and also make clickable still */}
|
{/* TODO: Figure out tauri drag region and also make clickable still */}
|
||||||
{variant === 'default' && (
|
{variant === 'default' && (
|
||||||
<div data-tauri-drag-region className="absolute top-0 left-0 h-md right-0" />
|
<div data-tauri-drag-region className="absolute top-0 left-0 h-md right-0" />
|
||||||
|
|||||||
@@ -11,11 +11,11 @@ import type {
|
|||||||
SetStateAction,
|
SetStateAction,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import React, {
|
import React, {
|
||||||
useEffect,
|
|
||||||
Children,
|
Children,
|
||||||
cloneElement,
|
cloneElement,
|
||||||
forwardRef,
|
forwardRef,
|
||||||
useCallback,
|
useCallback,
|
||||||
|
useEffect,
|
||||||
useImperativeHandle,
|
useImperativeHandle,
|
||||||
useMemo,
|
useMemo,
|
||||||
useRef,
|
useRef,
|
||||||
@@ -27,7 +27,7 @@ import type { HotkeyAction } from '../../hooks/useHotKey';
|
|||||||
import { useHotKey } from '../../hooks/useHotKey';
|
import { useHotKey } from '../../hooks/useHotKey';
|
||||||
import { useStateWithDeps } from '../../hooks/useStateWithDeps';
|
import { useStateWithDeps } from '../../hooks/useStateWithDeps';
|
||||||
import { getNodeText } from '../../lib/getNodeText';
|
import { getNodeText } from '../../lib/getNodeText';
|
||||||
import { Portal } from '../Portal';
|
import { Overlay } from '../Overlay';
|
||||||
import { Button } from './Button';
|
import { Button } from './Button';
|
||||||
import { HotKey } from './HotKey';
|
import { HotKey } from './HotKey';
|
||||||
import { Icon } from './Icon';
|
import { Icon } from './Icon';
|
||||||
@@ -446,79 +446,79 @@ const Menu = forwardRef<Omit<DropdownRef, 'open' | 'isOpen' | 'toggle' | 'items'
|
|||||||
),
|
),
|
||||||
)}
|
)}
|
||||||
{isOpen && (
|
{isOpen && (
|
||||||
<Portal name="dropdown-menu">
|
<Overlay noBackdrop open={isOpen} portalName="dropdown-menu">
|
||||||
<div ref={menuRef} className="x-theme-menu">
|
<motion.div
|
||||||
<motion.div
|
ref={menuRef}
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
onKeyDown={handleMenuKeyDown}
|
onKeyDown={handleMenuKeyDown}
|
||||||
onContextMenu={(e) => {
|
onContextMenu={(e) => {
|
||||||
// Prevent showing any ancestor context menus
|
// Prevent showing any ancestor context menus
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
}}
|
}}
|
||||||
initial={{ opacity: 0, y: (styles.upsideDown ? 1 : -1) * 5, scale: 0.98 }}
|
initial={{ opacity: 0, y: (styles.upsideDown ? 1 : -1) * 5, scale: 0.98 }}
|
||||||
animate={{ opacity: 1, y: 0, scale: 1 }}
|
animate={{ opacity: 1, y: 0, scale: 1 }}
|
||||||
role="menu"
|
role="menu"
|
||||||
aria-orientation="vertical"
|
aria-orientation="vertical"
|
||||||
dir="ltr"
|
dir="ltr"
|
||||||
style={styles.container}
|
style={styles.container}
|
||||||
|
className={classNames(
|
||||||
|
className,
|
||||||
|
'x-theme-menu',
|
||||||
|
'outline-none my-1 pointer-events-auto fixed z-50',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{showTriangle && (
|
||||||
|
<span
|
||||||
|
aria-hidden
|
||||||
|
style={styles.triangle}
|
||||||
|
className="bg-surface absolute border-border-subtle border-t border-l"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<VStack
|
||||||
|
style={styles.menu}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
className,
|
className,
|
||||||
'outline-none my-1 pointer-events-auto fixed z-50',
|
'h-auto bg-surface rounded-md shadow-lg py-1.5 border',
|
||||||
|
'border-border-subtle overflow-auto mx-0.5',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{showTriangle && (
|
{filter && (
|
||||||
<span
|
<HStack
|
||||||
aria-hidden
|
space={2}
|
||||||
style={styles.triangle}
|
className="pb-0.5 px-1.5 mb-2 text-sm border border-border-subtle mx-2 rounded font-mono h-xs"
|
||||||
className="bg-surface absolute border-border-subtle border-t border-l"
|
>
|
||||||
/>
|
<Icon icon="search" size="xs" className="text-text-subtle" />
|
||||||
|
<div className="text">{filter}</div>
|
||||||
|
</HStack>
|
||||||
)}
|
)}
|
||||||
<VStack
|
{filteredItems.length === 0 && (
|
||||||
style={styles.menu}
|
<span className="text-text-subtlest text-center px-2 py-1">No matches</span>
|
||||||
className={classNames(
|
)}
|
||||||
className,
|
{filteredItems.map((item, i) => {
|
||||||
'h-auto bg-surface rounded-md shadow-lg py-1.5 border',
|
if (item.hidden) {
|
||||||
'border-border-subtle overflow-auto mx-0.5',
|
return null;
|
||||||
)}
|
}
|
||||||
>
|
if (item.type === 'separator') {
|
||||||
{filter && (
|
|
||||||
<HStack
|
|
||||||
space={2}
|
|
||||||
className="pb-0.5 px-1.5 mb-2 text-sm border border-border-subtle mx-2 rounded font-mono h-xs"
|
|
||||||
>
|
|
||||||
<Icon icon="search" size="xs" className="text-text-subtle" />
|
|
||||||
<div className="text">{filter}</div>
|
|
||||||
</HStack>
|
|
||||||
)}
|
|
||||||
{filteredItems.length === 0 && (
|
|
||||||
<span className="text-text-subtlest text-center px-2 py-1">No matches</span>
|
|
||||||
)}
|
|
||||||
{filteredItems.map((item, i) => {
|
|
||||||
if (item.hidden) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (item.type === 'separator') {
|
|
||||||
return (
|
|
||||||
<Separator key={i} className={classNames('my-1.5', item.label && 'ml-2')}>
|
|
||||||
{item.label}
|
|
||||||
</Separator>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return (
|
return (
|
||||||
<MenuItem
|
<Separator key={i} className={classNames('my-1.5', item.label && 'ml-2')}>
|
||||||
focused={i === selectedIndex}
|
{item.label}
|
||||||
onFocus={handleFocus}
|
</Separator>
|
||||||
onSelect={handleSelect}
|
|
||||||
key={item.key}
|
|
||||||
item={item}
|
|
||||||
/>
|
|
||||||
);
|
);
|
||||||
})}
|
}
|
||||||
</VStack>
|
return (
|
||||||
</motion.div>
|
<MenuItem
|
||||||
</div>
|
focused={i === selectedIndex}
|
||||||
</Portal>
|
onFocus={handleFocus}
|
||||||
|
onSelect={handleSelect}
|
||||||
|
key={item.key}
|
||||||
|
item={item}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</VStack>
|
||||||
|
</motion.div>
|
||||||
|
</Overlay>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -105,7 +105,6 @@ export function getLanguageExtension({
|
|||||||
return graphql();
|
return graphql();
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("SELECTING SYNTAX", language);
|
|
||||||
const base = syntaxExtensions[language ?? 'text'] ?? text();
|
const base = syntaxExtensions[language ?? 'text'] ?? text();
|
||||||
if (!useTemplating) {
|
if (!useTemplating) {
|
||||||
return base;
|
return base;
|
||||||
|
|||||||
Reference in New Issue
Block a user