import React from 'react'; import type { ReaderStatus, ReaderSurfaceProps } from '../../../../types/reader'; import { AppBar } from '../../../app-bar/public'; import { IconButton } from '../../../button/public'; import { View } from '../../../layout/public'; import { Progress } from '../../../progress/public'; import { Surface } from '../../../surface/public'; import { withZoraThemeScope } from '../../../theme/adapters/inbound/withZoraThemeScope'; import { useZoraTheme } from '../../../theme/composition/useZoraTheme'; import { Text } from '../../../typography/public'; import { resolveReaderProgress } from '../../utils/resolveReaderProgress'; function resolvePageLabel(page: number | undefined, pageCount: number | undefined): string | null { if (page === undefined || !Number.isFinite(page)) return null; if (pageCount === undefined || !Number.isFinite(pageCount)) return `Page ${page}`; return `Page ${page} of ${pageCount}`; } function resolveCanGoPrevious({ canGoPrevious, page, }: Pick): boolean { if (canGoPrevious !== undefined) return canGoPrevious; return page === undefined || page > 1; } function resolveCanGoNext({ canGoNext, page, pageCount, }: Pick): boolean { if (canGoNext !== undefined) return canGoNext; return page === undefined || pageCount === undefined || page < pageCount; } function ReaderEmptyState({ status, loadingLabel, errorTitle, unavailableTitle, }: { status: ReaderStatus; loadingLabel: string; errorTitle: string; unavailableTitle: string; }) { const title = status === 'loading' ? loadingLabel : status === 'error' ? errorTitle : unavailableTitle; return ( {title} ); } function invokeReaderAction(action: (() => void | Promise) | undefined): void { void action?.(); } function ReaderHeader({ interactionPolicy, title, subtitle, contentsLabel, appearanceLabel, highlightLabel, highlighted, headerActions, onOpenContents, onOpenAppearance, onToggleHighlight, }: Pick< ReaderSurfaceProps, | 'interactionPolicy' | 'title' | 'subtitle' | 'contentsLabel' | 'appearanceLabel' | 'highlightLabel' | 'highlighted' | 'headerActions' | 'onOpenContents' | 'onOpenAppearance' | 'onToggleHighlight' >) { const actions = onOpenContents || onOpenAppearance || onToggleHighlight || headerActions ? ( {onOpenContents ? ( invokeReaderAction(onOpenContents)} size="l" /> ) : null} {onOpenAppearance ? ( invokeReaderAction(onOpenAppearance)} size="l" /> ) : null} {onToggleHighlight ? ( invokeReaderAction(onToggleHighlight)} size="l" /> ) : null} {headerActions} ) : undefined; return ( ); } function ReaderFooter({ interactionPolicy, status, page, pageCount, progress, canGoPrevious, canGoNext, chapterLabel, pageLabel, previousPageLabel, nextPageLabel, footerActions, onPreviousPage, onNextPage, }: Pick< ReaderSurfaceProps, | 'interactionPolicy' | 'status' | 'page' | 'pageCount' | 'progress' | 'canGoPrevious' | 'canGoNext' | 'chapterLabel' | 'pageLabel' | 'previousPageLabel' | 'nextPageLabel' | 'footerActions' | 'onPreviousPage' | 'onNextPage' >) { const { theme } = useZoraTheme(); const resolvedProgress = resolveReaderProgress({ progress, page, pageCount }); const resolvedPageLabel = pageLabel ?? resolvePageLabel(page, pageCount); const previousEnabled = status === 'ready' && Boolean(onPreviousPage) && resolveCanGoPrevious({ canGoPrevious, page }); const nextEnabled = status === 'ready' && Boolean(onNextPage) && resolveCanGoNext({ canGoNext, page, pageCount }); return ( invokeReaderAction(onPreviousPage)} size="l" /> {chapterLabel ? ( {chapterLabel} ) : null} {resolvedPageLabel ? ( {resolvedPageLabel} ) : null} invokeReaderAction(onNextPage)} size="l" /> {footerActions ? {footerActions} : null} ); } function ReaderSurfaceInner({ themeId: _themeId, mode: _mode, interactionPolicy, source: _source, format: _format, location: _location, status = 'idle', page, pageCount, progress, canGoPrevious, canGoNext, title, subtitle, chapterLabel, pageLabel, previousPageLabel = 'Previous page', nextPageLabel = 'Next page', contentsLabel = 'Table of contents', appearanceLabel = 'Reading appearance', highlightLabel = 'Toggle highlight', loadingLabel = 'Loading document…', errorTitle = 'Unable to open this document.', unavailableTitle = 'Reader preview unavailable.', showChrome = true, readerColorScheme: _readerColorScheme = 'system', fontScale: _fontScale = 1, lineHeight: _lineHeight = 'normal', highlighted = false, viewport, headerActions, footerActions, onPreviousPage, onNextPage, onLocationChange: _onLocationChange, onOpenContents, onOpenAppearance, onToggleHighlight, onOpenExternalLink: _onOpenExternalLink, onReaderError: _onReaderError, testID, }: ReaderSurfaceProps) { const { theme } = useZoraTheme(); const viewportContent = viewport && status !== 'error' ? ( viewport ) : ( ); return ( {showChrome ? ( ) : null} {viewportContent} {showChrome ? ( ) : null} ); } /*** * Adapter-neutral reader shell for EPUB and PDF experiences. * * Supply the actual renderer through `viewport`; ZORA owns only the reader * chrome, controlled state, progress, accessible controls, and normalized * adapter callbacks. */ export const ReaderSurface = withZoraThemeScope(ReaderSurfaceInner);