openreader/src/components/reader/ReaderSidebarShell.tsx
Richard R 265e1cb56a feat(tts,config,ui): add advanced segment preloading and block length controls
Introduce configurable segment preloading depth, sentence lookahead, and TTS segment max block length for both PDF and EPUB readers. Add new settings to user preferences, config context, and document settings UI. Refactor TTS segment splitting logic to support per-user max block length and propagate these options through PDF/EPUB adapters and TTS segment generation. Update API, manifest pagination, and segment deduplication for improved performance and correctness. Add tests for new NLP options and manifest logic.

BREAKING CHANGE: TTS segment splitting and manifest APIs now require max block length and preloading parameters; user config schema updated.
2026-05-07 12:26:08 -06:00

101 lines
3.2 KiB
TypeScript

'use client';
import { Fragment, type ReactNode } from 'react';
import { Transition } from '@headlessui/react';
import { XCircleIcon } from '@/components/icons/Icons';
import { useReaderSidebarBounds } from '@/hooks/useReaderSidebarBounds';
interface ReaderSidebarShellProps {
isOpen: boolean;
onClose: () => void;
ariaLabel: string;
title: string;
subtitle?: string;
children: ReactNode;
headerActions?: ReactNode;
footer?: ReactNode;
bodyClassName?: string;
panelClassName?: string;
}
export function ReaderSidebarShell({
isOpen,
onClose,
ariaLabel,
title,
subtitle,
children,
headerActions,
footer,
bodyClassName = 'flex-1 overflow-y-auto px-4 py-4',
panelClassName = '',
}: ReaderSidebarShellProps) {
const bounds = useReaderSidebarBounds(isOpen);
return (
<Transition show={isOpen} as={Fragment}>
<div
className="fixed inset-x-0 z-50 pointer-events-none"
style={{ top: bounds.top, bottom: bounds.bottom }}
>
<Transition.Child
as={Fragment}
enter="transition-opacity ease-out duration-200"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="transition-opacity ease-in duration-150"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<button
type="button"
aria-label="Close"
onClick={onClose}
className="sm:hidden absolute inset-0 overlay-dim backdrop-blur-sm pointer-events-auto"
/>
</Transition.Child>
<Transition.Child
as={Fragment}
enter="transition ease-out duration-220"
enterFrom="translate-x-full"
enterTo="translate-x-0"
leave="transition ease-in duration-180"
leaveFrom="translate-x-0"
leaveTo="translate-x-full"
>
<aside
role="dialog"
aria-label={ariaLabel}
className={`reader-sidebar-panel absolute inset-y-0 right-0 sm:right-3 sm:top-3 sm:bottom-3 pointer-events-auto bg-base border-l sm:border border-offbase shadow-xl sm:rounded-xl flex flex-col ${panelClassName}`}
>
<div className="border-b border-offbase px-4 py-3 flex items-start justify-between gap-3">
<div className="min-w-0">
<h2 className="text-sm font-medium text-foreground">{title}</h2>
{subtitle ? <p className="mt-0.5 text-xs text-muted">{subtitle}</p> : null}
</div>
<div className="flex items-center gap-1 shrink-0">
{headerActions}
<button
type="button"
onClick={onClose}
aria-label="Close"
title="Close"
className="inline-flex items-center justify-center w-8 h-8 rounded-lg border border-offbase bg-base text-muted hover:bg-offbase hover:text-accent transition-colors"
>
<XCircleIcon className="w-4 h-4" />
</button>
</div>
</div>
<div className={bodyClassName}>
{children}
</div>
{footer}
</aside>
</Transition.Child>
</div>
</Transition>
);
}