Add dropdown menu for book settings in sidebar
This commit is contained in:
@@ -71,4 +71,60 @@ foliate-view {
|
||||
.rounded-window-right {
|
||||
border-top-right-radius: 10px;
|
||||
border-bottom-right-radius: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-content {
|
||||
position: relative;
|
||||
background-color: white;
|
||||
padding: 10px;
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.15);
|
||||
border-radius: 14px;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||||
font-size: 14px;
|
||||
@apply text-gray-700;
|
||||
}
|
||||
|
||||
.dropdown-content::before,
|
||||
.dropdown-content::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -12px;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-left: 10px solid transparent;
|
||||
border-right: 10px solid transparent;
|
||||
}
|
||||
|
||||
.dropdown-content::before {
|
||||
border-bottom: 12px solid white;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.dropdown-content::after {
|
||||
top: -14px;
|
||||
border-bottom: 13px solid rgba(0, 0, 0, 0.02);
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.dropdown-left::before,
|
||||
.dropdown-left::after {
|
||||
left: 20px;
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
.dropdown-center::before,
|
||||
.dropdown-center::after {
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.dropdown-right::before,
|
||||
.dropdown-right::after {
|
||||
right: 20px;
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
.dropdown-content.no-triangle::before,
|
||||
.dropdown-content.no-triangle::after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
import React from 'react';
|
||||
import { useRouter, useSearchParams } from 'next/navigation';
|
||||
|
||||
interface BookMenuProps {
|
||||
bookId: string;
|
||||
toggleDropdown?: () => void;
|
||||
}
|
||||
|
||||
const BookMenu: React.FC<BookMenuProps> = ({ bookId, toggleDropdown }) => {
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
|
||||
const openSplitView = () => {
|
||||
const params = new URLSearchParams(searchParams.toString());
|
||||
const ids = (params.get('ids') || '').split(',');
|
||||
const updatedIds = [...ids, bookId].join(',');
|
||||
params.set('ids', updatedIds);
|
||||
router.push(`?${params.toString()}`);
|
||||
toggleDropdown?.();
|
||||
};
|
||||
const reloadPage = () => {
|
||||
window.location.reload();
|
||||
toggleDropdown?.();
|
||||
};
|
||||
const showAboutReadest = () => {
|
||||
toggleDropdown?.();
|
||||
};
|
||||
return (
|
||||
<div
|
||||
tabIndex={0}
|
||||
className='book-menu dropdown-content dropdown-center z-20 mt-3 w-56 border bg-white shadow-2xl'
|
||||
>
|
||||
<button
|
||||
className='flex w-full items-center rounded-md p-2 hover:bg-gray-100'
|
||||
onClick={openSplitView}
|
||||
>
|
||||
<span className='ml-2'>Split View</span>
|
||||
</button>
|
||||
<button
|
||||
className='flex w-full items-center rounded-md p-2 hover:bg-gray-100'
|
||||
onClick={reloadPage}
|
||||
>
|
||||
<span className='ml-2'>Reload Page</span>
|
||||
</button>
|
||||
<hr className='my-1' />
|
||||
<button
|
||||
className='flex w-full items-center rounded-md p-2 hover:bg-gray-100'
|
||||
onClick={showAboutReadest}
|
||||
>
|
||||
<span className='ml-2'>About Readest</span>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default BookMenu;
|
||||
@@ -1,18 +1,21 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import clsx from 'clsx';
|
||||
import {
|
||||
MdSearch,
|
||||
MdOutlinePushPin,
|
||||
MdPushPin,
|
||||
MdToc,
|
||||
MdEditNote,
|
||||
MdBookmarkBorder,
|
||||
MdOutlineMenu,
|
||||
} from 'react-icons/md';
|
||||
import { GiBookshelf } from 'react-icons/gi';
|
||||
import { CiSearch } from 'react-icons/ci';
|
||||
|
||||
import { BookState, DEFAULT_BOOK_STATE, useReaderStore } from '@/store/readerStore';
|
||||
import Dropdown from '@/components/Dropdown';
|
||||
import BookCard from './BookCard';
|
||||
import TOCView from './TOCView';
|
||||
import { BookState, DEFAULT_BOOK_STATE, useReaderStore } from '@/store/readerStore';
|
||||
import BookMenu from './BookMenu';
|
||||
|
||||
const MIN_SIDEBAR_WIDTH = 0.15;
|
||||
const MAX_SIDEBAR_WIDTH = 0.45;
|
||||
@@ -84,7 +87,7 @@ const SideBar: React.FC<{
|
||||
<div
|
||||
className={clsx(
|
||||
'sidebar-container bg-base-200 rounded-window-left z-20 h-full select-none',
|
||||
!isPinned && 'shadow-',
|
||||
!isPinned && 'shadow-2xl',
|
||||
)}
|
||||
style={{
|
||||
width: `${width}`,
|
||||
@@ -94,16 +97,23 @@ const SideBar: React.FC<{
|
||||
}}
|
||||
>
|
||||
<div className={'sidebar h-full'}>
|
||||
<div className='flex h-11 items-center justify-between pl-1.5 pr-3'>
|
||||
<div className='sidebar-header flex h-11 items-center justify-between pl-1.5 pr-3'>
|
||||
<div className='flex items-center'>
|
||||
<button className='btn btn-ghost h-8 min-h-8 w-8 p-0' onClick={onGoToLibrary}>
|
||||
<GiBookshelf size={20} className='fill-base-content' />
|
||||
</button>
|
||||
</div>
|
||||
<div className='flex size-[30%] min-w-20 items-center justify-between'>
|
||||
<div className='flex size-[50%] min-w-20 items-center justify-between'>
|
||||
<button className='btn btn-ghost left-0 h-8 min-h-8 w-8 p-0'>
|
||||
<MdSearch size={20} className='fill-base-content' />
|
||||
<CiSearch size={20} className='fill-base-content' />
|
||||
</button>
|
||||
<Dropdown
|
||||
className='dropdown-bottom flex justify-center'
|
||||
buttonClassName='btn btn-ghost h-8 min-h-8 w-8 p-0'
|
||||
toggleButton={<MdOutlineMenu size={20} />}
|
||||
>
|
||||
<BookMenu bookId={book.hash} />
|
||||
</Dropdown>
|
||||
<button
|
||||
onClick={onTogglePin}
|
||||
className={`${isPinned ? 'bg-gray-300' : 'bg-base-300'} btn btn-ghost btn-circle right-0 h-6 min-h-6 w-6`}
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
import clsx from 'clsx';
|
||||
import React, { useRef, useState, isValidElement, ReactElement } from 'react';
|
||||
|
||||
interface DropdownProps {
|
||||
className?: string;
|
||||
buttonClassName?: string;
|
||||
toggleButton: React.ReactNode;
|
||||
children: ReactElement<{ toggleDropdown: () => void }>;
|
||||
onToggle?: (isOpen: boolean) => void;
|
||||
}
|
||||
|
||||
const Dropdown: React.FC<DropdownProps> = ({
|
||||
className,
|
||||
buttonClassName,
|
||||
toggleButton,
|
||||
children,
|
||||
onToggle,
|
||||
}) => {
|
||||
const dropdownRef = useRef<HTMLDivElement>(null);
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
const toggleDropdown = () => {
|
||||
const newIsOpen = !isOpen;
|
||||
setIsOpen(newIsOpen);
|
||||
onToggle?.(newIsOpen);
|
||||
};
|
||||
|
||||
const handleBlur = (event: React.FocusEvent<HTMLDivElement>) => {
|
||||
if (!dropdownRef.current?.contains(event.relatedTarget as Node)) {
|
||||
setIsOpen(false);
|
||||
onToggle?.(false);
|
||||
}
|
||||
};
|
||||
|
||||
const childrenWithToggle = isValidElement(children)
|
||||
? React.cloneElement(children, { toggleDropdown })
|
||||
: children;
|
||||
|
||||
return (
|
||||
<div className={clsx('dropdown', className)} ref={dropdownRef}>
|
||||
<div
|
||||
tabIndex={-1}
|
||||
onClick={toggleDropdown}
|
||||
onBlur={handleBlur}
|
||||
className={clsx(
|
||||
'dropdown-toggle hover:bg-gray-300',
|
||||
buttonClassName,
|
||||
isOpen && 'bg-base-300',
|
||||
)}
|
||||
>
|
||||
{toggleButton}
|
||||
</div>
|
||||
{isOpen && childrenWithToggle}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Dropdown;
|
||||
Reference in New Issue
Block a user