forked from akai/readest
This commit is contained in:
@@ -39,7 +39,9 @@ const ImportMenu: React.FC<ImportMenuProps> = ({
|
||||
|
||||
return (
|
||||
<Menu
|
||||
className={clsx('dropdown-content bg-base-100 rounded-box z-[1] mt-3 p-2 shadow')}
|
||||
className={clsx(
|
||||
'dropdown-content dropdown-center bg-base-100 rounded-box z-[1] mt-3 p-2 shadow',
|
||||
)}
|
||||
onCancel={() => setIsDropdownOpen?.(false)}
|
||||
>
|
||||
<MenuItem
|
||||
|
||||
@@ -149,7 +149,7 @@ const LibraryHeader: React.FC<LibraryHeaderProps> = ({
|
||||
<Dropdown
|
||||
label={_('Import Books')}
|
||||
className={clsx(
|
||||
'exclude-title-bar-mousedown dropdown-bottom dropdown-center flex h-6 cursor-pointer justify-center',
|
||||
'exclude-title-bar-mousedown dropdown-bottom dropdown-center cursor-pointer',
|
||||
)}
|
||||
buttonClassName='p-0 h-6 min-h-6 w-6 flex touch-target items-center justify-center !bg-transparent'
|
||||
toggleButton={<PiPlus role='none' className='m-0.5 h-5 w-5' />}
|
||||
|
||||
@@ -327,7 +327,7 @@ export const MigrateDataWindow = () => {
|
||||
>
|
||||
<div
|
||||
className={clsx(
|
||||
'folder-menu dropdown-content dropdown-center no-triangle',
|
||||
'folder-menu dropdown-content no-triangle left-0',
|
||||
'border-base-300 !bg-base-200 z-20 mt-1 max-w-[90vw] shadow-2xl',
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -175,6 +175,7 @@ const HeaderBar: React.FC<HeaderBarProps> = ({
|
||||
: _('Enable Quick Action on Selection')
|
||||
}
|
||||
className='exclude-title-bar-mousedown dropdown-bottom'
|
||||
menuClassName='dropdown-center'
|
||||
buttonClassName={clsx(
|
||||
'btn btn-ghost h-8 min-h-8 w-8 p-0',
|
||||
viewSettings?.annotationQuickAction && 'bg-base-300/50',
|
||||
|
||||
@@ -9,12 +9,14 @@ import MenuItem from '@/components/MenuItem';
|
||||
import Menu from '@/components/Menu';
|
||||
|
||||
interface QuickActionMenuProps {
|
||||
menuClassName?: string;
|
||||
selectedAction?: AnnotationToolType | null;
|
||||
onActionSelect: (action: AnnotationToolType) => void;
|
||||
setIsDropdownOpen?: (open: boolean) => void;
|
||||
}
|
||||
|
||||
const QuickActionMenu: React.FC<QuickActionMenuProps> = ({
|
||||
menuClassName,
|
||||
selectedAction,
|
||||
onActionSelect,
|
||||
setIsDropdownOpen,
|
||||
@@ -48,6 +50,7 @@ const QuickActionMenu: React.FC<QuickActionMenuProps> = ({
|
||||
className={clsx(
|
||||
'annotation-quick-action-menu dropdown-content z-20 mt-1 border',
|
||||
'bgcolor-base-200 shadow-2xl',
|
||||
menuClassName,
|
||||
)}
|
||||
style={{
|
||||
maxWidth: `${window.innerWidth - 40}px`,
|
||||
|
||||
@@ -68,6 +68,7 @@ const SidebarHeader: React.FC<{
|
||||
)}
|
||||
menuClassName={window.innerWidth < 640 ? 'no-triangle mt-1' : 'dropdown-center mt-3'}
|
||||
buttonClassName='btn btn-ghost h-8 min-h-8 w-8 p-0'
|
||||
containerClassName='h-8'
|
||||
toggleButton={<MdOutlineMenu className='fill-base-content' />}
|
||||
>
|
||||
<BookMenu />
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import clsx from 'clsx';
|
||||
import React, { useState, isValidElement, ReactElement, ReactNode, useRef } from 'react';
|
||||
import React, { useState, isValidElement, ReactElement, ReactNode, useRef, useId } from 'react';
|
||||
import { useDropdownContext } from '@/context/DropdownContext';
|
||||
import { Overlay } from './Overlay';
|
||||
import MenuItem from './MenuItem';
|
||||
|
||||
@@ -8,6 +9,7 @@ interface DropdownProps {
|
||||
className?: string;
|
||||
menuClassName?: string;
|
||||
buttonClassName?: string;
|
||||
containerClassName?: string;
|
||||
toggleButton: React.ReactNode;
|
||||
children: ReactElement<{
|
||||
setIsDropdownOpen: (isOpen: boolean) => void;
|
||||
@@ -62,34 +64,29 @@ const Dropdown: React.FC<DropdownProps> = ({
|
||||
className,
|
||||
menuClassName,
|
||||
buttonClassName,
|
||||
containerClassName,
|
||||
toggleButton,
|
||||
children,
|
||||
disabled,
|
||||
onToggle,
|
||||
showTooltip = true,
|
||||
}) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [isFocused, setIsFocused] = useState(false);
|
||||
const lastInteractionWasTapOrClick = useRef(false);
|
||||
const dropdownId = useId();
|
||||
const context = useDropdownContext();
|
||||
const isOpen = context ? context.openDropdownId === dropdownId : false;
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const [isFocused, setIsFocused] = useState(false);
|
||||
|
||||
const setIsDropdownOpen = (open: boolean) => {
|
||||
if (disabled) return;
|
||||
setIsOpen(open);
|
||||
onToggle?.(open);
|
||||
};
|
||||
|
||||
const handleTouchOrClick = () => {
|
||||
lastInteractionWasTapOrClick.current = true;
|
||||
setTimeout(() => (lastInteractionWasTapOrClick.current = false), 200);
|
||||
};
|
||||
|
||||
const handleFocus = () => {
|
||||
setIsFocused(true);
|
||||
// skip touch and pointer triggered focus, this is only for keyboard and aria navigation
|
||||
if (!lastInteractionWasTapOrClick.current) {
|
||||
setIsDropdownOpen(true);
|
||||
if (context) {
|
||||
if (open) {
|
||||
context.openDropdown(dropdownId);
|
||||
} else {
|
||||
context.closeDropdown(dropdownId);
|
||||
}
|
||||
}
|
||||
onToggle?.(open);
|
||||
};
|
||||
|
||||
const toggleDropdown = () => {
|
||||
@@ -97,17 +94,6 @@ const Dropdown: React.FC<DropdownProps> = ({
|
||||
setIsDropdownOpen(!isOpen);
|
||||
};
|
||||
|
||||
const handleBlur = (e: React.FocusEvent) => {
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
return;
|
||||
}
|
||||
if (!containerRef.current) return;
|
||||
if (!containerRef.current.contains(e.relatedTarget as Node)) {
|
||||
setIsFocused(false);
|
||||
setIsDropdownOpen(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleKeyDown = (e: React.KeyboardEvent) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
if (!isOpen) setIsDropdownOpen(true);
|
||||
@@ -129,16 +115,9 @@ const Dropdown: React.FC<DropdownProps> = ({
|
||||
: children;
|
||||
|
||||
return (
|
||||
<div className='dropdown-container flex'>
|
||||
<div ref={containerRef} className={clsx('dropdown-container flex', containerClassName)}>
|
||||
{isOpen && <Overlay onDismiss={() => setIsDropdownOpen(false)} />}
|
||||
<div
|
||||
ref={containerRef}
|
||||
role='menu'
|
||||
tabIndex={-1}
|
||||
onBlur={handleBlur}
|
||||
onKeyDown={handleKeyDown}
|
||||
className={clsx('dropdown flex flex-col', className)}
|
||||
>
|
||||
<div className='relative'>
|
||||
<button
|
||||
aria-haspopup='menu'
|
||||
aria-expanded={isOpen}
|
||||
@@ -149,16 +128,19 @@ const Dropdown: React.FC<DropdownProps> = ({
|
||||
isFocused && isOpen && 'bg-base-300/50',
|
||||
buttonClassName,
|
||||
)}
|
||||
onTouchStart={handleTouchOrClick}
|
||||
onPointerDown={handleTouchOrClick}
|
||||
onFocus={handleFocus}
|
||||
onClick={toggleDropdown}
|
||||
onKeyDown={handleKeyDown}
|
||||
>
|
||||
{toggleButton}
|
||||
</button>
|
||||
<div role='none' className={clsx('flex items-center justify-center')}>
|
||||
<details
|
||||
open={isOpen}
|
||||
role='none'
|
||||
className={clsx('dropdown flex items-center justify-center', className)}
|
||||
>
|
||||
<summary aria-hidden='true' className='list-none' />
|
||||
{isOpen && childrenWithToggle}
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import clsx from 'clsx';
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import React, { useRef } from 'react';
|
||||
import { useKeyDownActions } from '@/hooks/useKeyDownActions';
|
||||
|
||||
interface MenuProps {
|
||||
@@ -14,17 +14,6 @@ const Menu: React.FC<MenuProps> = ({ children, className, style, onCancel }) =>
|
||||
|
||||
useKeyDownActions({ onCancel, elementRef: menuRef });
|
||||
|
||||
useEffect(() => {
|
||||
setTimeout(() => {
|
||||
if (menuRef.current) {
|
||||
const firstItem = menuRef.current.querySelector('[role="menuitem"]');
|
||||
if (firstItem) {
|
||||
(firstItem as HTMLElement).focus();
|
||||
}
|
||||
}
|
||||
}, 200);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={menuRef}
|
||||
|
||||
@@ -16,6 +16,7 @@ import { useBackgroundTexture } from '@/hooks/useBackgroundTexture';
|
||||
import { useEinkMode } from '@/hooks/useEinkMode';
|
||||
import { getLocale } from '@/utils/misc';
|
||||
import { getDirFromUILanguage } from '@/utils/rtl';
|
||||
import { DropdownProvider } from '@/context/DropdownContext';
|
||||
import { CommandPaletteProvider, CommandPalette } from '@/components/command-palette';
|
||||
|
||||
const Providers = ({ children }: { children: React.ReactNode }) => {
|
||||
@@ -69,10 +70,12 @@ const Providers = ({ children }: { children: React.ReactNode }) => {
|
||||
<AuthProvider>
|
||||
<IconContext.Provider value={{ size: `${iconSize}px` }}>
|
||||
<SyncProvider>
|
||||
<CommandPaletteProvider>
|
||||
{children}
|
||||
<CommandPalette />
|
||||
</CommandPaletteProvider>
|
||||
<DropdownProvider>
|
||||
<CommandPaletteProvider>
|
||||
{children}
|
||||
<CommandPalette />
|
||||
</CommandPaletteProvider>
|
||||
</DropdownProvider>
|
||||
</SyncProvider>
|
||||
</IconContext.Provider>
|
||||
</AuthProvider>
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
// DropdownContext.tsx
|
||||
import React, { createContext, useContext, useState, useCallback, ReactNode } from 'react';
|
||||
|
||||
interface DropdownContextValue {
|
||||
openDropdownId: string | null;
|
||||
openDropdown: (id: string) => void;
|
||||
closeDropdown: (id: string) => void;
|
||||
closeAll: () => void;
|
||||
}
|
||||
|
||||
const DropdownContext = createContext<DropdownContextValue | null>(null);
|
||||
|
||||
export const DropdownProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
|
||||
const [openDropdownId, setOpenDropdownId] = useState<string | null>(null);
|
||||
|
||||
const openDropdown = useCallback((id: string) => {
|
||||
setOpenDropdownId(id);
|
||||
}, []);
|
||||
|
||||
const closeDropdown = useCallback((id: string) => {
|
||||
setOpenDropdownId((current) => (current === id ? null : current));
|
||||
}, []);
|
||||
|
||||
const closeAll = useCallback(() => {
|
||||
setOpenDropdownId(null);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<DropdownContext.Provider value={{ openDropdownId, openDropdown, closeDropdown, closeAll }}>
|
||||
{children}
|
||||
</DropdownContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useDropdownContext = () => useContext(DropdownContext);
|
||||
@@ -203,8 +203,7 @@ foliate-fxl {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
.dropdown-center::before,
|
||||
.dropdown-center::after {
|
||||
.dropdown-center {
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user